diff --git a/.gitattributes b/.gitattributes index 412eeda78d..cfd07fbb0b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,3 @@ -# Auto detect text files and perform LF normalization -* text=auto - # Custom for Visual Studio *.cs diff=csharp *.sln merge=union diff --git a/.gitignore b/.gitignore index b033d0828c..9970124e10 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,73 @@ +## .gitignore strategy +# +# Only keep ren-c project specific file exclusions in .gitignore +# +# For files created by... +# +# * Editors (for eg. Vim, Eclipse, etc) +# * Machines (Windows, Linux, OSX, etc) +# * or any artifacts of your development environment +# +# then please keep these exclusions in a global .gitignore file which +# should not be commited into the ren-c repo. +# +# For more info please see "Create a global .gitignore" at: +# +# https://help.github.com/articles/ignoring-files/ +# +# Our recommended advice is to use a .gitignore_global file described +# in link. +# +# Here is an example .gitignore_global used on OS X (Mac): +# +# ## Mac stuff +# .DS_Store +# +# ## Vim stuff +# *.un~ +# *.swp +# +# This is kept in the users home directory: ~/.gitignore_global +# + + ################# ## REBOL R3 ################# +make/makefile make/r3* make/objs/ + +# It can be convenient if you are doing multiple builds of the same Rebol +# codebase for different platforms to put them in separate make +# directories (such as make-win32 or make-osx-x64) so they don't erase +# each other's state +# +make-* +!tests/**/make-* + +# While some generated files are intended to be semi-persistent products +# (e.g. an API file), many generated files are temporary and not meant to be +# the concern of users. Over time, these have been standardized to start +# with "tmp-". Ignore any such file in any directory. +# +tmp-* + src/boot/boot-code.r src/boot/host-init.r + src/core/b-boot.c -src/include/ext-types.h -src/include/host-* + +src/include/host-ext-* +src/include/host-init.h +src/include/host-lib.h +src/include/host-table.inc src/include/reb-dialect.h src/include/reb-evtypes.h src/include/reb-lib-lib.h src/include/reb-lib.h src/include/reb-types.h -src/include/tmp-* -src/tools/reb-lib-doc.txt -src/reb-lib-doc.txt ################# ## Eclipse @@ -178,6 +229,3 @@ pip-log.txt #Mr Developer .mr.developer.cfg - -# Mac crap -.DS_Store diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..d37f2f8b07 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "external/libffi"] + path = external/libffi + url = git://github.com/metaeducation/libffi.git +[submodule "external/tcc"] + path = external/tcc + url = git://github.com/metaeducation/tcc.git diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..f7ab51b6ff --- /dev/null +++ b/.travis.yml @@ -0,0 +1,452 @@ +# +# .travis.yaml contains YAML-formatted (http://www.yaml.org/) build +# instructions for continuous integration via Travis CI +# (http://docs.travis-ci.com/). +# + + +notifications: + email: false + + +language: c + + +matrix: + include: + # OSX x64, debug, g++ + # + # !!! Can't do all warnings as errors, because the switch for + # files that end in .c as c++ ("-x c++") is deprecated for some overly + # prescriptive and annoying reason. + # + - os: osx + osx_image: xcode8.2 + language: cpp + env: OS_ID=0.2.40 DEBUG=asserts STANDARD=c++14 RIGOROUS=no STATIC=no FFI="dynamic" + + # OSX x64, debug, gcc + # + # TCC currently doesn't quite support OSX yet + # + # !!! Also, does not honor GCC's static linking flag for libc. + # + - os: osx + osx_image: xcode8.2 + language: c + env: OS_ID=0.2.40 DEBUG=asserts STANDARD=c99 RIGOROUS=no STATIC=no FFI="dynamic" + + # OSX x64, release, gcc + # + # !!! Again, does not honor static linking switch. + # + - os: osx + language: c + env: OS_ID=0.2.40 DEBUG=none STANDARD=c RIGOROUS=no STATIC=no FFI="dynamic" + + # Linux x86, release, gcc + # + - os: linux + sudo: false #Force new container-based infrastructure. + language: c + env: OS_ID=0.4.4 DEBUG=none STANDARD=gnu89 RIGOROUS=yes STATIC=yes TCC=i386-tcc ARCH_CFLAGS=-m32 FFI="dynamic" + + # Linux x64, debug, g++ + # + # !!! Note this binary does not support c++11 completely, it's an old + # gcc 4.6 + # + - os: linux + dist: trusty #gcc on Ubuntu 12.04 does not support sanitizers + sudo: false #Force new container-based infrastructure. + language: cpp + env: OS_ID=0.4.40 DEBUG=asserts STANDARD=c++0x RIGOROUS=yes STATIC=yes TCC=tcc FFI="dynamic" + + # Linux x64, release, gcc + # + - os: linux + sudo: false #Force new container-based infrastructure. + language: c + env: OS_ID=0.4.40 DEBUG=none STANDARD=gnu99 RIGOROUS=yes STATIC=yes TCC=tcc FFI="dynamic" + + # Windows x86, release, gcc + # + - os: linux + sudo: false #Force new container-based infrastructure. + language: c + env: OS_ID=0.3.1 DEBUG=none TOOLS=i686-w64-mingw32- STANDARD=c RIGOROUS=yes STATIC=yes TCC=i386-win32-tcc TCC_CPP_EXTRA_FLAGS='-I../external/tcc/win32/include -DPVAR=TVAR -DTVAR="extern __attribute__((dllimport))"' HOST=i686-w64-mingw32 ARCH_CFLAGS=-m32 FFI="dynamic" + + # Windows x64, debug, gcc + # + - os: linux + sudo: false #Force new container-based infrastructure. + language: c + env: OS_ID=0.3.40 DEBUG=asserts TOOLS=x86_64-w64-mingw32- STANDARD=c RIGOROUS=yes STATIC=yes TCC=x86_64-win32-tcc TCC_CPP_EXTRA_FLAGS='-I../external/tcc/win32/include -DPVAR=TVAR -DTVAR="extern __attribute__((dllimport))"' HOST=x86_64-w64-mingw32 FFI="dynamic" + + # Windows x64, debug, g++ + # + # !!! Note this binary does not support c++11 or above + # + - os: linux + sudo: false #Force new container-based infrastructure. + language: cpp + env: OS_ID=0.3.40 DEBUG=asserts TOOLS=x86_64-w64-mingw32- STANDARD=c++ RIGOROUS=yes STATIC=yes TCC=x86_64-win32-tcc TCC_CPP_EXTRA_FLAGS='-I../external/tcc/win32/include -DPVAR=TVAR -DTVAR="extern __attribute__((dllimport))"' HOST=x86_64-w64-mingw32 FFI="dynamic" + + # Android5, debug, gcc + # + - os: linux + dist: trusty #gcc on Ubuntu 12.04 does not support sanitizers + sudo: false #Force new container-based infrastructure. + language: c + env: OS_ID=0.13.2 DEBUG=asserts STANDARD=c RIGOROUS=yes STATIC=yes HOST=arm-eabi FFI="no" + + # Android5, release, gcc + # + - os: linux + dist: trusty #gcc on Ubuntu 12.04 does not support sanitizers + sudo: false #Force new container-based infrastructure. + language: c + env: OS_ID=0.13.2 DEBUG=none STANDARD=c RIGOROUS=yes STATIC=yes HOST=arm-eabi FFI="no" + +env: + global: + # + # travis encrypt AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID + # + - secure: "Bex3tqrlsnv+t3+AJu6nG8bcbfHXeBNWIUUdcEeyB8gWnWnVuBsC5hTw9cUhEWJchJSsV4LeWVL51harzOQRntszyfjeNvPQozCXbTQVGd1tn5Rpt1QKN9VBK007c+ERj9L8JzFkM2HdeVusYY4Bz5tI883DSJkydyJpJp21mG9i8a17bqJsgBW0JmMsMsdv1ilaeb8/Luo8bn0ObIWTTz+4/6RF4XU9UcWLH7I4HlGb3qufR9chWCX7jTT0SLRkEgfudr+KVrY4xIspiPlVwrKvagnOTFcYLxN4JpGOgn1rnCcOxsWo4kE4dwgXZvEn8W2HJmJhzhAHDLkF0S7YhIDQaScJLwSVECI9xu68V5siWdyhzyrSb2K7V8Mtzryjzq1QueCrRRTj7XLY7sx5OxeP//RVMY0Poil5DdB84nI1wezzmT1kj7dkc1Fr1ZqdYSEfCZNd1v+DeRmAf/N70xUyx1tSxAHD96kjDM3lGILIrlt9RLWdeT0BqxQxzaKCowPVgfztH0nzPcoe1DfNfIhG9mEdjeJfLC7hAgc9Dn0KTo/oSwX/TBsTavV+6SPxH1D4q1xVdY9p4G2hS/N1xaqf7ys4DQOPwWZwvhujwGtto4fy7VMvDtX7jI6++0dJe+baG0DetlHvUGKzWpBJgk02k3mREH+9Ui8f7T9vn8Y=" + + # travis encrypt AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY + # + - secure: "IlBRG9mRM0BDtb9ZJDKl4QVRjs/e3KxvjEdVS9e8+PlGq+xMDVGQdje9WOED/bhTcoAYabhLKkXY8YZg6rlVj4ecyjjmZRfPA4D9YVMVHZVNldLX9Ed79Kv95dTvFdn6xl9Tbk/CEqtxfDwcN2hZqv9M3TXN2+sKzny6p4ENc8O7sz0Stb4GyFgPdWSIs4SZv/r8/feMgWiUx+q1NFFarMmFsLtKVuiPIyoU6fGW1zZPyh10jKuhi9GYBStcMHIWqvU+9+jbqchMJT1t/1fyEf0fJokNMH2KXCVDbsu7nKhaVZbIxirLdZNicKfzype1uRgzAB/Crpup+TwnINd17HPSqjCnqntuS+pO0mIRcXVhNSE8TG9S8x4N0pgtKYHKyfAjElmjLwPfoMhu5VlZishn6heeUALbQ7y44YwWwG8EoW4PnRFIGg7V4EjlHJkcmDhJWrZX2hVvSGJ72lFhHXFMcr+VKhXWlmK97XdFAz/c/LlSyyrmKtIE6W5kwhJC8bbrpETA/wQ9pP3WEVY28bka24LqI1g0hiDn7cyXae7Ikss36Y8eB/9/00EovCPHw1o+dyenXI10Q8+yorQ42xrjo1bXuYRohCvI+FmV4XFLkJ+c6wDTSKhJTcUhZsQva2F0ipeyqhGQQGkLiZ8BvdoSPHHBx2odikgho9VQZ48=" + + - AWS_S3_BUCKET_NAME: "metaeducation" + + +addons: + apt: + packages: + # + # For building 32b binaries on a 64b host (not necessary when we + # build for 64b): + # + - gcc-multilib + - g++-multilib + + # For cross-compiling to Windows. + # + - binutils-mingw-w64-i686 + - binutils-mingw-w64-x86-64 + - gcc-mingw-w64-i686 + - gcc-mingw-w64-x86-64 + - g++-mingw-w64-i686 + - g++-mingw-w64-x86-64 + - mingw-w64 + + # for makeinfo, required to build FFI + - texinfo + + +install: + # + # Fetch a Rebol bootstrap binary, which is needed for building Rebol. + # Travis uses the last r3-alpha that was ever published on rebol.com + # in order to ensure that still works. + # + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export REBOL_TOOL=r3-linux-x64-gbf237fc-static ; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export REBOL_TOOL=r3-osx-x64-gbf237fc ; fi + +script: + # Nice to know what version of gcc this is + - ${TOOLS}gcc --version + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export MAKE_JOBS=`nproc`; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export MAKE_JOBS=`sysctl -n hw.ncpu`; fi + + - TOP_DIR=${PWD} + - | + if [[ ${OS_ID} = "0.13.2" || ${OS_ID} = "0.13.1" ]]; then + if [ `uname -m` = x86_64 ]; then wget https://github.com/giuliolunati/android-travis/releases/download/v1.0.0/android-ndk-r13.tgz; ANDROID_NDK=$TOP_DIR/android-ndk-r13; else exit 1; fi + tar zxf android-ndk-r13.tgz + echo $PWD + ls -dl $PWD/android-ndk-r13 + export TOOLS=$ANDROID_NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi- + export EXTRA_CC_FLAGS="--sysroot=$ANDROID_NDK/platforms/android-19/arch-arm" + fi + if [[ ${OS_ID} = "0.3.40" || ${OS_ID} = "0.3.1" ]]; then + # Use prebuilt binaries + if [[ ${OS_ID} = "0.3.40" ]]; then + export PKG_CONFIG_PATH=${TOP_DIR}/external/ffi-prebuilt/lib64/pkgconfig + else + export PKG_CONFIG_PATH=${TOP_DIR}/external/ffi-prebuilt/lib32/pkgconfig + fi + # --define-prefix would be better, but it is not recognized + export PKGCONFIG="pkg-config --define-variable=prefix=${TOP_DIR}/external/ffi-prebuilt" + # check cflags and libs + ${PKGCONFIG} --cflags libffi + ${PKGCONFIG} --libs libffi + elif [[ -z ${FFI} || ${FFI} != "no" ]]; then + # Build libffi + mkdir build + cd external/libffi + ./autogen.sh + cd ${TOP_DIR}/build + if [[ -z ${HOST} ]]; then + ${TOP_DIR}/external/libffi/configure --prefix=$PWD/fakeroot CFLAGS=${ARCH_CFLAGS} + else #cross-compiling + ${TOP_DIR}/external/libffi/configure --prefix=$PWD/fakeroot --host=${HOST} + fi + make -j ${MAKE_JOBS} + make install + export PKG_CONFIG_PATH=$PWD/fakeroot/lib/pkgconfig + # check cflags and libs + pkg-config --cflags libffi + pkg-config --libs libffi + + ls `pkg-config --variable=toolexeclibdir libffi` + #remove dynamic libraries to force it to link with static libraries + rm -f `pkg-config --variable=toolexeclibdir libffi`/*.so* + rm -f `pkg-config --variable=toolexeclibdir libffi`/*.dylib* + rm -f `pkg-config --variable=toolexeclibdir libffi`/*.dll* + ls `pkg-config --variable=toolexeclibdir libffi` + fi + + - cd ${TOP_DIR}/make/ + + # Build TCC + - | + if [[ ! -z ${TCC} ]]; then + mkdir tcc + cd tcc + if [[ ${OS_ID} != "0.4.40" ]]; then + #generate cross-compiler (on x86_64 host and target for i386) + echo "Generating the cross-compiler" + ${TOP_DIR}/external/tcc/configure --enable-cross --extra-cflags="-DEMBEDDED_IN_R3" + make -j ${MAKE_JOBS} + mkdir bin + cp *tcc bin #save cross-compilers + ls bin/ #take a look at the cross-compilers + make clean + #generate libtcc.a + # libtcc.a requires --enable-mingw32, or it doesn't think it's a native compiler and disables tcc_run + echo "Generating libtcc.a" + if [[ ${OS_ID} = "0.4.4" ]]; then + ${TOP_DIR}/external/tcc/configure --cpu=x86 --extra-cflags="-DEMBEDDED_IN_R3 ${ARCH_CFLAGS}" + elif [[ ${OS_ID} == "0.3.1" ]]; then #x86-win32 + ${TOP_DIR}/external/tcc/configure --cpu=x86 --extra-cflags="-DEMBEDDED_IN_R3" --enable-mingw32 --cross-prefix=${TOOLS} + else #x86_64-win32 + ${TOP_DIR}/external/tcc/configure --enable-mingw32 --cpu=x86_64 --extra-cflags="-DEMBEDDED_IN_R3" --cross-prefix=${TOOLS} + fi + make libtcc.a && cp libtcc.a libtcc.a.bak + + #generate libtcc1.a + # --enable-mingw32 must be turned off, or it will try to compile with tcc.exe + make clean + + echo "Generating libtcc1.a" + if [[ ${OS_ID} = "0.4.4" ]]; then + ${TOP_DIR}/external/tcc/configure --cpu=x86 --extra-cflags="-DEMBEDDED_IN_R3 ${ARCH_CFLAGS}" + elif [[ ${OS_ID} == "0.3.1" ]]; then #x86-win32 + ${TOP_DIR}/external/tcc/configure --cpu=x86 --extra-cflags="-DEMBEDDED_IN_R3" --cross-prefix=${TOOLS} + else #x86_64-win32 + ${TOP_DIR}/external/tcc/configure --cpu=x86_64 --extra-cflags="-DEMBEDDED_IN_R3" --cross-prefix=${TOOLS} + fi + + echo "make libtcc1.a" + make libtcc1.a XCC=${TOOLS}gcc XAR=${TOOLS}ar || echo "ignoring error in building libtcc1.a" #this could fail to build tcc due to lack of '-ldl' on Windows + cp bin/* . #restore cross-compilers, libtcc1.a depends on tcc + touch tcc #update the timestamp so it won't be rebuilt + echo "ls" + ls #take a look at files under current directory + echo "make libtcc1.a" + make libtcc1.a XCC=${TOOLS}gcc XAR=${TOOLS}ar + + echo "Looking for symbol r3_tcc_alloca" + if [[ ${OS_ID} == "0.3.1" ]]; then #x86-win32 + ${TOOLS}objdump -t lib/i386/alloca86.o |grep alloca + elif [[ ${OS_ID} == "0.3.40" ]]; then + ${TOOLS}objdump -t lib/x86_64/alloca86_64.o |grep alloca + fi + + #restore libtcc.a + # make libtcc1.a could have generated a new libtcc.a + cp libtcc.a.bak libtcc.a + else + ${TOP_DIR}/external/tcc/configure --extra-cflags="-DEMBEDDED_IN_R3 ${ARCH_CFLAGS}" + fi + make + cd ${TOP_DIR}/make + fi + + # Grab the abbreviated and full git commit ID into environment variables. + # The full commit is passed to make to build into the binary, and the + # abbreviated commit is used to name the executable. + # + # http://stackoverflow.com/a/42549385/211160 + # + - GIT_COMMIT="$(git show --format="%H" --no-patch)" + - echo ${GIT_COMMIT} + - GIT_COMMIT_SHORT="$(git show --format="%h" --no-patch)" + - echo ${GIT_COMMIT_SHORT} + + # Take a look at assert.h + # - find /usr/include -name assert.h | xargs cat + + # We have to set REBOL_TOOL explicitly to circumvent the automatic r3-make + # filename inference, as we always use Linux "r3-make" (not "r3-make.exe") + # even when doing windows builds, since this is a cross-compilation. + # + # As an extra step to test bootstrap ability, we make the 64-bit debug + # build go even further by doing another full build, but using the + # just built r3 as its own r3-make. + # + - | + if [[ ("${OS_ID}" = "0.4.40" || "${OS_ID}" = "0.2.40") && "${DEBUG}" != "none" ]]; then + # + # If building twice, don't specify GIT_COMMIT for the first build. + # This means there's a test of the build process when one is not + # specified, in case something is broken about that. (This is how + # most people will build locally, so good to test it.) + # + # Also request address sanitizer to be used for the first build. It + # is very heavyweight and makes the executable *huge* and slow, so + # we do not apply it to any of the binaries which are uploaded to s3 + # -- not even debug ones. + # + make -f makefile.boot NUM_JOBS=${MAKE_JOBS} REBOL_TOOL=${REBOL_TOOL} STANDARD="${STANDARD}" OS_ID="${OS_ID}" RIGOROUS="${RIGOROUS}" DEBUG=sanitize OPTIMIZE=2 STATIC=no + + rm r3-make; + mv r3 r3-make; + make clean; + export R3_ALWAYS_MALLOC=1 + export REBOL_TOOL=r3-make + fi + + # On the second build of building twice, or just building once, include + # the GIT_COMMIT + # + - | + if [[ -z ${TCC} ]]; then + make -f makefile.boot NUM_JOBS=${MAKE_JOBS} REBOL_TOOL=${REBOL_TOOL} STANDARD="${STANDARD}" OS_ID="${OS_ID}" DEBUG="${DEBUG}" GIT_COMMIT="${GIT_COMMIT}" RIGOROUS="${RIGOROUS}" STATIC="${STATIC}" WITH_FFI=${FFI} WITH_TCC="no" + else + make -f makefile.boot NUM_JOBS=${MAKE_JOBS} REBOL_TOOL=${REBOL_TOOL} STANDARD="${STANDARD}" OS_ID="${OS_ID}" DEBUG="${DEBUG}" GIT_COMMIT="${GIT_COMMIT}" RIGOROUS="${RIGOROUS}" STATIC="${STATIC}" WITH_FFI=${FFI} WITH_TCC="${PWD}/tcc/${TCC}" + fi + + # take a look at the preprocess header file + # - cat ../src/include/sys-core.i || true + + # output the needed libraries + - | + if [[ "${OS_ID}" = "0.4.40" || "${OS_ID}" = "0.4.4" ]]; then + ldd ./r3 + elif [[ "${OS_ID}" = "0.2.40" ]]; then + otool -L ./r3 + fi + + # Run once but don't pipe output, in case it prints out useful crash msg + # that we want to see in the Travis log (especially helpful for failures + # only happening in the Travis builds that aren't reproducing locally) + # Save the exit code ($?) so we can return it to Travis as last step + # + # !!! This is a very minimal sanity check to ensure the built R3 does + # *something*, and it can obviously only be used on the linux version. + # Running the full test suite would be a bit much, and developers are + # expected to have already done that. But doing an HTTPS read exercises + # a fair amount of code. + # + - | + if [[ "${OS_ID}" = "0.4.40" || "${OS_ID}" = "0.4.4" || "${OS_ID}" = "0.2.40" ]]; then + ./r3 --do "print {Testing...} quit/with either find to-string read https://example.com {

Example Domain

} [0] [1]"; + R3_EXIT_STATUS=$?; + else + R3_EXIT_STATUS=0; + fi + - echo ${R3_EXIT_STATUS} + + # Run basic testing with FFI, this is a linux-only script + - | + if [[ "${OS_ID}" = "0.4.40" || "${OS_ID}" = "0.4.4" ]]; then + ./r3 ../tests/misc/qsort_r.r + R3_EXIT_STATUS=$?; + else + R3_EXIT_STATUS=0; + fi + - echo ${R3_EXIT_STATUS} + + # Run basic testing with user natives + - | + if [[ ! -z "$TCC" && "$TCC" != "no" && ( "${OS_ID}" = "0.4.40" || "${OS_ID}" = "0.4.4" ) ]]; then + ./r3 ../tests/misc/fib.r + R3_EXIT_STATUS=$?; + else + R3_EXIT_STATUS=0; + fi + - echo ${R3_EXIT_STATUS} + + # Clean Android elf executable + - | + if [[ ${OS_ID} = "0.13.2" || ${OS_ID} = "0.13.1" ]]; then + $ANDROID_NDK/android-elf-cleaner r3 + fi + + # Delete the obj file directory so we don't upload those to S3 + # + - rm -rf objs + + - rm -f makefile* + - rm -f Toolchain* + - rm -f r3-make* #-f makes retval a success even when r3-make* doesn't exist + - rm r3-linux-x64-gbf237fc-static + - rm r3-osx-x64-gbf237fc + - rm -f CMakeLists.txt + - rm -rf tcc + + # Name the executable based on the abbreviated commit, whether it is a + # debug or release build, and if it was built using C++ or not. Note that + # the C++ debug builds have additional runtime checks in the debug + # build...though there should not be any impact on the release build. + # (Though there may be additional DLL dependencies regardless.) + # + # !!! All Ren-C stakeholders should be using debug builds at this time. + # + # Note: -z tests if a variable is undefined + # + - NEW_NAME=${OS_ID}/r3-${GIT_COMMIT_SHORT} + - if [[ "${DEBUG}" != "none" ]]; then NEW_NAME+="-debug"; fi + - | + if [[ "${STANDARD}" = "c++" || "${STANDARD}" = "c++0x" || "${STANDARD}" = "c++11" || "${STANDARD}" = "c++14" || "${STANDARD}" = "c++17" ]]; then + NEW_NAME+="-cpp"; + fi + - echo ${NEW_NAME} + + # Move the executable into a directory based on its OS_ID platform. + # This is because the deploy step is run for each OS and would + # otherwise overwrite executables in the same location. + # + - mkdir ${OS_ID} + - | + if [[ -e "r3.exe" ]]; then + mv r3.exe ${NEW_NAME}.exe; + else + mv r3 ${NEW_NAME}; + fi + + # Return whether the build succeeded or not to Travis. If this succeeded + # then the deploy step to S3 will run + # + # http://stackoverflow.com/a/10457902/211160 + # + - (exit ${R3_EXIT_STATUS}) + + +# After everything is finished (e.g. script section above), upload build +# product to Amazon S3. For how the configuration works and manages to keep +# the private key secure, see: +# +# http://stackoverflow.com/a/42547424/211160 +# +deploy: + provider: s3 + access_key_id: $AWS_ACCESS_KEY_ID + secret_access_key: $AWS_SECRET_ACCESS_KEY + bucket: $AWS_S3_BUCKET_NAME + skip_cleanup: true + upload-dir: travis-builds diff --git a/.travis.yml.tcc b/.travis.yml.tcc new file mode 100644 index 0000000000..5304020cb5 --- /dev/null +++ b/.travis.yml.tcc @@ -0,0 +1,156 @@ +# +# .travis.yaml contains YAML-formatted (http://www.yaml.org/) build +# instructions for continuous integration via Travis CI +# (http://docs.travis-ci.com/). +# + +notifications: + email: false + +language: c + +matrix: + include: + # Linux x86, release + - os: linux + sudo: required + dist: trusty + env: OS_ID=0.4.4 BUILD_TYPE=RELEASE R3_CPP=0 CFLAGS=-m32 CXXFLAGS=-m32 EXTRA_CMAKE_ARGS="-DCMAKE_ASM_FLAGS=-m32" + + # Linux x86, debug + - os: linux + sudo: required + dist: trusty + env: OS_ID=0.4.4 BUILD_TYPE=DEBUG R3_CPP=0 CFLAGS=-m32 CXXFLAGS=-m32 EXTRA_CMAKE_ARGS="-DCMAKE_ASM_FLAGS=-m32" + + # Linux x86, debug, build with CPP + - os: linux + sudo: required + dist: trusty + env: OS_ID=0.4.4 BUILD_TYPE=DEBUG R3_CPP=1 CFLAGS=-m32 CXXFLAGS=-m32 EXTRA_CMAKE_ARGS="-DCMAKE_ASM_FLAGS=-m32" + + # Linux x64, debug + - os: linux + sudo: required + dist: trusty + env: OS_ID=0.4.40 BUILD_TYPE=DEBUG R3_CPP=0 + + # Linux x64, debug, build with CPP + - os: linux + sudo: required + dist: trusty + env: OS_ID=0.4.40 BUILD_TYPE=DEBUG R3_CPP=1 + + # Linux x64, release + - os: linux + sudo: required + dist: trusty + env: OS_ID=0.4.40 BUILD_TYPE=RELEASE R3_CPP=0 + + # Windows x86, release + - os: linux + sudo: required + dist: trusty + env: OS_ID=0.3.1 EXE_SUFFIX=.exe BUILD_TYPE=RELEASE R3_CPP=0 EXTRA_CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=Toolchain-cross-mingw32-linux.cmake" + # Windows x86, debug + - os: linux + sudo: required + dist: trusty + env: OS_ID=0.3.1 EXE_SUFFIX=.exe BUILD_TYPE=DEBUG R3_CPP=0 EXTRA_CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=Toolchain-cross-mingw32-linux.cmake" + # Windows x86, debug, CPP + - os: linux + sudo: required + dist: trusty + env: OS_ID=0.3.1 EXE_SUFFIX=.exe BUILD_TYPE=DEBUG R3_CPP=1 EXTRA_CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=Toolchain-cross-mingw32-linux.cmake" + # Windows x64, release + - os: linux + sudo: required + dist: trusty + env: OS_ID=0.3.40 EXE_SUFFIX=.exe BUILD_TYPE=RELEASE R3_CPP=0 EXTRA_CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=Toolchain-cross-mingw64-linux.cmake" + # Windows x64, debug + - os: linux + sudo: required + dist: trusty + env: OS_ID=0.3.40 EXE_SUFFIX=.exe BUILD_TYPE=DEBUG R3_CPP=0 EXTRA_CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=Toolchain-cross-mingw64-linux.cmake" + # Windows x64, debug, CPP + - os: linux + sudo: required + dist: trusty + env: OS_ID=0.3.40 EXE_SUFFIX=.exe BUILD_TYPE=DEBUG R3_CPP=1 EXTRA_CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=Toolchain-cross-mingw64-linux.cmake" + # OSX x86 + - os: osx + osx_image: xcode8 + env: OS_ID=0.2.5 BUILD_TYPE=RELEASE R3_CPP=0 EXTRA_CMAKE_ARGS="-DCMAKE_OSX_ARCHITECTURES=i386 -DCMAKE_ASM_FLAGS=\"-arch i386\"" + - os: osx + osx_image: xcode8 + env: OS_ID=0.2.5 BUILD_TYPE=DEBUG R3_CPP=0 EXTRA_CMAKE_ARGS="-DCMAKE_OSX_ARCHITECTURES=i386 -DCMAKE_ASM_FLAGS=\"-arch i386\"" + - os: osx + osx_image: xcode8 + env: OS_ID=0.2.5 BUILD_TYPE=DEBUG R3_CPP=1 EXTRA_CMAKE_ARGS="-DCMAKE_OSX_ARCHITECTURES=i386 -DCMAKE_ASM_FLAGS=\"-arch i386\"" + # OSX x64 + - os: osx + osx_image: xcode8 + env: OS_ID=0.2.40 BUILD_TYPE=RELEASE R3_CPP=0 + - os: osx + osx_image: xcode8 + env: OS_ID=0.2.40 BUILD_TYPE=DEBUG R3_CPP=0 + - os: osx + osx_image: xcode8 + env: OS_ID=0.2.40 BUILD_TYPE=DEBUG R3_CPP=1 + +addons: + apt: + packages: + # For building 32b binaries on a 64b host (not necessary when we + # build for 64b): + - gcc-multilib + - g++-multilib + + # For cross-compiling to Windows. + - binutils-mingw-w64-i686 + - binutils-mingw-w64-x86-64 + - gcc-mingw-w64-i686 + - gcc-mingw-w64-x86-64 + - g++-mingw-w64-i686 + - g++-mingw-w64-x86-64 + - mingw-w64 + +install: + # Fetch a Rebol bootstrap binary, which is needed for building Rebol. + #- wget http://www.rebol.com/r3/downloads/r3-a111-4-2.tar.gz + #- tar xvzf r3-a111-4-2.tar.gz + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then wget http://www.rebolsource.net/downloads/experimental/r3-linux-x64-gbf237fc-static && cp r3-linux-x64-gbf237fc-static make/r3-make; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then wget http://rebolsource.net/downloads/experimental/r3-osx-x64-gbf237fc && cp r3-osx-x64-gbf237fc make/r3-make; fi + - chmod +x make/r3-make + +script: + - cd make/ + #compile tcc as a cross-compiler + - if [ "${OS_ID}" = "0.3.1" ]; then mkdir tcc-build && cd tcc-build && cmake -DTCC_BUILD_WIN32=1 -G "Unix Makefiles" ../../external/tcc && make i386-w64-mingw32-tcc i386-w64-mingw32-libtcc1 VERBOSE=1 && cp i386-w64-mingw32-tcc ../cross-tcc && cp i386-w64-mingw32-libtcc1.a ../cross-libtcc1.a && cd .. && rm -fr tcc-build; fi + - if [ "${OS_ID}" = "0.3.40" ]; then mkdir tcc-build && cd tcc-build && cmake -DTCC_BUILD_WIN64=1 -G "Unix Makefiles" ../../external/tcc && make x86_64-w64-mingw32-tcc x86_64-w64-mingw32-libtcc1 VERBOSE=1 && cp x86_64-w64-mingw32-tcc ../cross-tcc && cp x86_64-w64-mingw32-libtcc1.a ../cross-libtcc1.a && cd .. && rm -fr tcc-build; fi + - cmake -DR3_OS_ID="${OS_ID}" -DR3_EXTERNAL_FFI=0 -DR3_CPP="${R3_CPP}" -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" -DR3_WITH_TCC=1 -G "Unix Makefiles" "${EXTRA_CMAKE_ARGS}" + - make r3-core VERBOSE=1 + # A minimal sanity check that the built R3 does _something_. Eventually, we + # should run the full test suite. + + # Run once but don't pipe output, in case it prints out useful crash msg + - if [ "${OS_ID}" = "0.4.40" ]; then ./r3-core --do 'print {OK}'; fi + + # Run a second time with piped output to return success/faiure to Travis + - if [ "${OS_ID}" = "0.4.40" ]; then ./r3-core --do 'print {OK}' | grep OK; fi + + # overwriting libtcc1.a with the cross-compiled version + - if [ "${OS_ID}" = "0.3.1" -o "${OS_ID}" = "0.3.40" ]; then cp cross-libtcc1.a tcc/libtcc1.a; fi + + # Rename files before uploading + - zip r3-core-${OS_ID}-${TRAVIS_COMMIT}-${BUILD_TYPE}-CPP${R3_CPP}.zip r3-core${EXE_SUFFIX} tcc/libtcc1.a + +deploy: + provider: releases + api_key: + secure: V6a5VzBv+ut3hKZKMmnuY4Urzc4QA/EBcfarve837q+7p9QgDseiuW93yUVys7LacIl8D6y13m71QBxzG6LC9WnttNgfy+PfyrMbWfaMvg9zLQQ1jTGKWjW6Fn4/xyU0NYyrjvgxW2itQ4/r9r0lcmKHbsAcm/ZhvLzg4o3dnc0= + file: + - r3-core-${OS_ID}-${TRAVIS_COMMIT}-${BUILD_TYPE}-CPP${R3_CPP}.zip + skip_cleanup: true #or, Travis CI deletes all the files created during the build + on: + tags: true diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000000..795791deaf --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,25 @@ +# Rebol 3 (Ren/C branch) Changes + +All notable changes to this project will be documented in this file. + +Alternate ChangeLogs: + +* [ChangeLog + pre-built binaries](https://forum.rebol.info/t/rebol3-ren-c-branch-change-logs/54/10000) +* [Complete Github commits](https://github.com/metaeducation/ren-c/commits/master) + +Alternatively using Git: + + git log + + +Below is a stub for summary of changes by version. + +## 2.102.0 +### Added +- Ren/C branch by @hostfilefork + +### Changed +### Fixed + + +## R3-Alpha diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 0000000000..73642171b8 --- /dev/null +++ b/CREDITS.md @@ -0,0 +1,165 @@ +> This file's intent is to centralize credit to organizations, individuals, +> and to code + libraries + tools used. For all new source contributions +> please give copyright attribution to "Rebol Open Source Contributors". +> Include any new credits in pull requests as a modification to this file. +> +> **NOTE** The project has also benefited from significant supporting work +> outside this code repository by members of the community--too numerous to +> list here! + + +CREDITS +======= + +Originators of REBOL +-------------------- + +Carl Sassenrath, Rebol Technologies +* http://www.rebol.com +* [@carls](https://github.com/carls) on GitHub + +_REBOL is a trademark of REBOL Technologies_ + +Rebol 3 Alpha was [released to the open source community][1] under an Apache 2 +license on 12-Dec-2012: + +[1]: http://www.rebol.com/cgi-bin/blog.r?view=0519#comments + + +Code Contributors +----------------- + +Contributors to this project are encouraged to add/edit entries here, with a +one-line summary and a link to a landing webpage of their choice: + +**Andreas Bolka** +- [@earl](https://github.com/earl) on GitHub +- http://rebolsource.net +- 64-bit and other porting, build farm, core design, core patches, test suite... + +**Brian Dickens** +- [@hostilefork](https://github.com/hostilefork) on GitHub +- http://hostilefork.com +- "Ren-C" branch founder, core evaluator rethinking and design... + +**Brett Handley** +- [@codebybrett](https://github.com/codebybrett) on GitHub +- http://codeconscious.com +- Libraries to parse and process Rebol's C code using Rebol, file conversions. + +**Brian Hawley** +- [@BrianHawley](https://github.com/brianh) on GitHub +- Mezzanine design and module system, core patches, PARSE design for Rebol3. + +**Giulio Lunati** +- [@giuliolunati](https://github.com/giuliolunati) on GitHub +- MAP! and hashing updates, Android builds, source serialization improvements. + +**Joshua Shireman** +- [@kealist](https://github.com/kealist) on GitHub +- Serial port driver work (based on code by Carl Sassenrath) + +**Ladislav Mecir** +- [@ladislav](https://github.com/ladislav) on GitHub +- Advanced math and currency support, test suite, core patches, core design... + +**Richard Smolak** +- [@cyphre](https://github.com/cyphre) on GitHub +- TLS and HTTPS, Diffie-Hellman and crypto, extension model, GUI support... + +**Shixin Zeng** +* [@zsx](https://github.com/zsx) on GitHub +- FFI library, CALL implementation, unix signals, native math, GUI support... + + +Corporate Support +----------------- + +**Atronix Engineering, Inc** +- http://www.atronixengineering.com/downloads +- David den Haring, Director of Engineering + +**Saphirion AG** +- http://development.saphirion.com/rebol/ +- Robert M.Münch, CEO, Prototype sponsoring + + +Third-Party Components +---------------------- + +This aims to list all the third-party components of this distribution but may +not be complete. Please amend with any corrections. + +**AES** +- Copyright (c) 2007, Cameron Rich +- `%src/codecs/aes/aes.h` +- `%src/codecs/aes/aes.c` + +**bigint** +- Copyright (c) 2007, Cameron Rich +- `%src/codecs/bigint/bigint_impl.h` +- `%src/codecs/bigint/bigint_config.h` +- `%src/codecs/bigint/bigint.h` +- `%src/codecs/bigint/bigint.c` + +**crc32** +- Derived from code in chapter 19 of the book "C Programmer's Guide to Serial + Communications", by Joe Campbell. Generalized to any CRC width by Philip + Zimmermann. +- `%src/core/s-crc.c` + +**debugbreak** +- Copyright (c) 2011-2015, Scott Tsai +- `%src/include/debugbreak.h` + +**dtoa** +- Copyright (c) 1991, 2000, 2001 by Lucent Technologies. +- `%src/core/f-dtoa.c` + +**JPEG** +- Copyright 1994-1996, Thomas G. Lane. +- `%src/core/u-jpg.c` +- `%src/include/sys-jpg.h` + +**LodePNG** +- Copyright (c) 2005-2013 Lode Vandevenne +- `%src/codecs/png/lodepng.h` +- `%src/codecs/png/lodepng.c` + +**MD5** +- This software contains code derived from the RSA Data Security Inc. MD5 + Message-Digest Algorithm, including various modifications by Spyglass Inc., + Carnegie Mellon University, and Bell Communications Research, Inc (Bellcore). +- `%src/core/u-md5.c` + +**qsort** +- Copyright (c) 1992, 1993 The Regents of the University of California. +- `%src/core/f-qsort.c` + +**rc4** +- Copyright (c) 2007, Cameron Rich +- `%src/codecs/rc4/rc4.h` +- `%src/codecs/rc4/rc4.c` + +**rsa** +- Copyright (c) 2007, Cameron Rich +- `%src/codecs/rsa/rsa.h` +- `%src/codecs/rsa/rsa.c` + +**sha1** +- Copyright 1995-1998 Eric Young +- `%src/core/u-sha1.c` + +**sha256** +- Copyright 2006-2012 (?) Brad Conte +- `%src/codecs/sha256.c` +- `%src/codecs/sha256.h` + +**Unicode** +- Copyright 2001-2004 Unicode, Inc. +- `%src/core/s-unicode.c` + +**ZLIB** +- Copyright 1995-1998 Jean-loup Gailly and Mark Adler +- `%src/core/u-zlib.c` +- `%src/include/sys-zlib.h` diff --git a/NOTICE b/NOTICE deleted file mode 100644 index a4814f87a2..0000000000 --- a/NOTICE +++ /dev/null @@ -1,37 +0,0 @@ -REBOL [R3] Language Interpreter and Run-time Environment -Copyright 2012 REBOL Technologies -REBOL is a trademark of REBOL Technologies -Licensed under the Apache License, Version 2.0 -See included LICENSE file for details - - -Credits for Non-REBOL orginated C files and modules ---------------------------------------------------- - -Unicode encoding/decoding functions: -Copyright 2001-2004 Unicode, Inc. - -MD5: -This software contains code derived from the RSA Data Security -Inc. MD5 Message-Digest Algorithm, including various -modifications by Spyglass Inc., Carnegie Mellon University, and -Bell Communications Research, Inc (Bellcore). - -SHA1: -Copyright 1995-1998 Eric Young (eay@cryptsoft.com) -All rights reserved. - -ZLIB general purpose compression library: -Version 1.1.2, March 19th, 1998 -Copyright 1995-1998 Jean-loup Gailly and Mark Adler - -JPEG decoder: -Copyright 1994-1996, Thomas G. Lane. -This file is part of the Independent JPEG Group's software. - -dtoa: -The author of this software is David M. Gay. -Copyright (c) 1991, 2000, 2001 by Lucent Technologies. - -qsort: -Copyright (c) 1992, 1993 The Regents of the University of California. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..497f05458e --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +![Ren-C Logo][100] + +# Ren/C +[![Build Status][101]](https://travis-ci.org/metaeducation/ren-c) + + +**Ren-C** is an interim fork of the [Apache 2.0 open-sourced][1] [Rebol][2] codebase. The +goal of the project isn't to be a new language or a different console, rather to provide +a smooth API for embedding a Rebol interpreter in C programs. This API would offer +nearly the full range of power that is internally offered to the core, making it +easy to write new clients or REPLs using it. + +[1]: http://www.rebol.com/cgi-bin/blog.r?view=0519 +[2]: https://en.wikipedia.org/wiki/Rebol + +Because the API is not fully ready for publication, the current way to explore the new +features of Ren-C is using the `r3` console built by the makefile. It should function +nearly identically (though it has been extended through user contribution to support a +multi-line continuation method similar to Rebol2.) For those interested in a more +novel application of the Ren-C library, see the C++ binding and [Ren Garden][3]. + +[3]: http://rencpp.hostilefork.com + +In the process of designing the library, Ren-C also aspires to solve several of the +major outstanding design problems that were left unfinished in the R3-Alpha codebase. +Several of these problems have been solved already--and for progress and notes on +these issues, a [Trello board][4] is frequently updated to reflect a summary of +some of the changes. + +[4]: https://trello.com/b/l385BE7a/rebol3-porting-guide-ren-c-branch + +In doing this work, the hope is to provide an artifact that would rally common +usage between the [mainline builds][5], community builds, and those made by +[Atronix Engineering][6] and [Saphirion AG][7]. + +[5]: http://rebolsource.net +[6]: http://www.atronixengineering.com/downloads +[7]: http://development.saphirion.com/rebol/saphir/ + +For more information, please visit the FAQ: + +https://github.com/metaeducation/ren-c/wiki/FAQ + +Feel free to add your own questions to the bottom of the list. + + +## Community + +To promote the Rebol community's participation in public forums, development discussion +for Ren-C generally takes place in the [Rebol and Red StackOverflow Chat][8]. + +[8]: http://rebolsource.net/go/chat-faq + +It is also possible to contact the developers through the [Ren-C GitHub Issues][9] +page. This should be limited to questions regarding the Ren-C builds specifically, as +overall language design wishes and debates are kept in the `rebol-issues` repository +of Rebol's GitHub. + +[9]: https://github.com/metaeducation/ren-c/issues + + +## Building + +There are currently two build systems in Ren-C: plain make files for basic features, and CMake for extended features. + +* With plain make files + +First get the sources -- from cloning the repository with `git`, or downloading a ZIP: + +https://github.com/metaeducation/ren-c/archive/master.zip + +Next you need to [get a pre-built R3-Alpha interpreter](http://rebolsource.net), rename +it to `r3-make` or `r3-make.exe`, and put it in the `%make/` subdirectory. + +Then run: + + make -f makefile.boot + +The platform to target will be assumed to be the same as the build type of the +`r3-make` you use. If your needs are more complex *(such as doing a cross-compilation, +or if the `system/version` in your r3-make doesn't match the target you want)*, refer +to the bootstrap makefile `%src/make/makefile.boot`: + +https://github.com/metaeducation/ren-c/blob/master/make/makefile.boot + +*(Note: Ren-C's build process cannot be performed with Rebol2. It requires R3-Alpha +or Ren-C itself. However, it can build using an old pre-open-source R3-Alpha A111.)* + +* With CMake + +Please see https://github.com/metaeducation/ren-c/wiki/Building-Ren-C-with-CMake + +[100]: https://raw.githubusercontent.com/metaeducation/ren-c/master/ren-c-logo.png +[101]: https://travis-ci.org/metaeducation/ren-c.svg?branch=master diff --git a/external/README.md b/external/README.md new file mode 100644 index 0000000000..1bc8210658 --- /dev/null +++ b/external/README.md @@ -0,0 +1,10 @@ +This is an external dependencies directory; e.g. source for projects that are not +part of the Ren/C project or maintained as part of its version history. Directories +are either Git submodules or instructions will be added to this file for how to +get ahold of the dependencies. + +Inspired by Rebol's choice to do targeted subsetting of dependent C libraries via +Rebol scripts and include that source, the goal is not to require a large dependent +build process. Hence any build process required by these dependencies should be +taken care of by a script. (See make-zlib for an example.) + diff --git a/external/ffi-prebuilt/lib32/libffi-3.2.1/include/ffi.h b/external/ffi-prebuilt/lib32/libffi-3.2.1/include/ffi.h new file mode 100644 index 0000000000..c0059b06cb --- /dev/null +++ b/external/ffi-prebuilt/lib32/libffi-3.2.1/include/ffi.h @@ -0,0 +1,487 @@ +/* -----------------------------------------------------------------*-C-*- + libffi 3.2.1 - Copyright (c) 2011, 2014 Anthony Green + - Copyright (c) 1996-2003, 2007, 2008 Red Hat, Inc. + + 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. + + ----------------------------------------------------------------------- */ + +/* ------------------------------------------------------------------- + The basic API is described in the README file. + + The raw API is designed to bypass some of the argument packing + and unpacking on architectures for which it can be avoided. + + The closure API allows interpreted functions to be packaged up + inside a C function pointer, so that they can be called as C functions, + with no understanding on the client side that they are interpreted. + It can also be used in other cases in which it is necessary to package + up a user specified parameter and a function pointer as a single + function pointer. + + The closure API must be implemented in order to get its functionality, + e.g. for use by gij. Routines are provided to emulate the raw API + if the underlying platform doesn't allow faster implementation. + + More details on the raw and cloure API can be found in: + + http://gcc.gnu.org/ml/java/1999-q3/msg00138.html + + and + + http://gcc.gnu.org/ml/java/1999-q3/msg00174.html + -------------------------------------------------------------------- */ + +#ifndef LIBFFI_H +#define LIBFFI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Specify which architecture libffi is configured for. */ +#ifndef X86_WIN32 +#define X86_WIN32 +#endif + +/* ---- System configuration information --------------------------------- */ + +#include + +#ifndef LIBFFI_ASM + +#if defined(_MSC_VER) && !defined(__clang__) +#define __attribute__(X) +#endif + +#include +#include + +/* LONG_LONG_MAX is not always defined (not if STRICT_ANSI, for example). + But we can find it either under the correct ANSI name, or under GNU + C's internal name. */ + +#define FFI_64_BIT_MAX 9223372036854775807 + +#ifdef LONG_LONG_MAX +# define FFI_LONG_LONG_MAX LONG_LONG_MAX +#else +# ifdef LLONG_MAX +# define FFI_LONG_LONG_MAX LLONG_MAX +# ifdef _AIX52 /* or newer has C99 LLONG_MAX */ +# undef FFI_64_BIT_MAX +# define FFI_64_BIT_MAX 9223372036854775807LL +# endif /* _AIX52 or newer */ +# else +# ifdef __GNUC__ +# define FFI_LONG_LONG_MAX __LONG_LONG_MAX__ +# endif +# ifdef _AIX /* AIX 5.1 and earlier have LONGLONG_MAX */ +# ifndef __PPC64__ +# if defined (__IBMC__) || defined (__IBMCPP__) +# define FFI_LONG_LONG_MAX LONGLONG_MAX +# endif +# endif /* __PPC64__ */ +# undef FFI_64_BIT_MAX +# define FFI_64_BIT_MAX 9223372036854775807LL +# endif +# endif +#endif + +/* The closure code assumes that this works on pointers, i.e. a size_t */ +/* can hold a pointer. */ + +typedef struct _ffi_type +{ + size_t size; + unsigned short alignment; + unsigned short type; + struct _ffi_type **elements; +} ffi_type; + +#ifndef LIBFFI_HIDE_BASIC_TYPES +#if SCHAR_MAX == 127 +# define ffi_type_uchar ffi_type_uint8 +# define ffi_type_schar ffi_type_sint8 +#else + #error "char size not supported" +#endif + +#if SHRT_MAX == 32767 +# define ffi_type_ushort ffi_type_uint16 +# define ffi_type_sshort ffi_type_sint16 +#elif SHRT_MAX == 2147483647 +# define ffi_type_ushort ffi_type_uint32 +# define ffi_type_sshort ffi_type_sint32 +#else + #error "short size not supported" +#endif + +#if INT_MAX == 32767 +# define ffi_type_uint ffi_type_uint16 +# define ffi_type_sint ffi_type_sint16 +#elif INT_MAX == 2147483647 +# define ffi_type_uint ffi_type_uint32 +# define ffi_type_sint ffi_type_sint32 +#elif INT_MAX == 9223372036854775807 +# define ffi_type_uint ffi_type_uint64 +# define ffi_type_sint ffi_type_sint64 +#else + #error "int size not supported" +#endif + +#if LONG_MAX == 2147483647 +# if FFI_LONG_LONG_MAX != FFI_64_BIT_MAX + #error "no 64-bit data type supported" +# endif +#elif LONG_MAX != FFI_64_BIT_MAX + #error "long size not supported" +#endif + +#if LONG_MAX == 2147483647 +# define ffi_type_ulong ffi_type_uint32 +# define ffi_type_slong ffi_type_sint32 +#elif LONG_MAX == FFI_64_BIT_MAX +# define ffi_type_ulong ffi_type_uint64 +# define ffi_type_slong ffi_type_sint64 +#else + #error "long size not supported" +#endif + +/* Need minimal decorations for DLLs to works on Windows. */ +/* GCC has autoimport and autoexport. Rely on Libtool to */ +/* help MSVC export from a DLL, but always declare data */ +/* to be imported for MSVC clients. This costs an extra */ +/* indirection for MSVC clients using the static version */ +/* of the library, but don't worry about that. Besides, */ +/* as a workaround, they can define FFI_BUILDING if they */ +/* *know* they are going to link with the static library. */ +#if defined _MSC_VER && !defined FFI_BUILDING +#define FFI_EXTERN extern __declspec(dllimport) +#else +#define FFI_EXTERN extern +#endif + +/* These are defined in types.c */ +FFI_EXTERN ffi_type ffi_type_void; +FFI_EXTERN ffi_type ffi_type_uint8; +FFI_EXTERN ffi_type ffi_type_sint8; +FFI_EXTERN ffi_type ffi_type_uint16; +FFI_EXTERN ffi_type ffi_type_sint16; +FFI_EXTERN ffi_type ffi_type_uint32; +FFI_EXTERN ffi_type ffi_type_sint32; +FFI_EXTERN ffi_type ffi_type_uint64; +FFI_EXTERN ffi_type ffi_type_sint64; +FFI_EXTERN ffi_type ffi_type_float; +FFI_EXTERN ffi_type ffi_type_double; +FFI_EXTERN ffi_type ffi_type_pointer; + +#if 1 +FFI_EXTERN ffi_type ffi_type_longdouble; +#else +#define ffi_type_longdouble ffi_type_double +#endif + +#ifdef FFI_TARGET_HAS_COMPLEX_TYPE +FFI_EXTERN ffi_type ffi_type_complex_float; +FFI_EXTERN ffi_type ffi_type_complex_double; +#if 1 +FFI_EXTERN ffi_type ffi_type_complex_longdouble; +#else +#define ffi_type_complex_longdouble ffi_type_complex_double +#endif +#endif +#endif /* LIBFFI_HIDE_BASIC_TYPES */ + +typedef enum { + FFI_OK = 0, + FFI_BAD_TYPEDEF, + FFI_BAD_ABI +} ffi_status; + +typedef unsigned FFI_TYPE; + +typedef struct { + ffi_abi abi; + unsigned nargs; + ffi_type **arg_types; + ffi_type *rtype; + unsigned bytes; + unsigned flags; +#ifdef FFI_EXTRA_CIF_FIELDS + FFI_EXTRA_CIF_FIELDS; +#endif +} ffi_cif; + +#if 0 +/* Used to adjust size/alignment of ffi types. */ +void ffi_prep_types (ffi_abi abi); +#endif + +/* Used internally, but overridden by some architectures */ +ffi_status ffi_prep_cif_core(ffi_cif *cif, + ffi_abi abi, + unsigned int isvariadic, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type *rtype, + ffi_type **atypes); + +/* ---- Definitions for the raw API -------------------------------------- */ + +#ifndef FFI_SIZEOF_ARG +# if LONG_MAX == 2147483647 +# define FFI_SIZEOF_ARG 4 +# elif LONG_MAX == FFI_64_BIT_MAX +# define FFI_SIZEOF_ARG 8 +# endif +#endif + +#ifndef FFI_SIZEOF_JAVA_RAW +# define FFI_SIZEOF_JAVA_RAW FFI_SIZEOF_ARG +#endif + +typedef union { + ffi_sarg sint; + ffi_arg uint; + float flt; + char data[FFI_SIZEOF_ARG]; + void* ptr; +} ffi_raw; + +#if FFI_SIZEOF_JAVA_RAW == 4 && FFI_SIZEOF_ARG == 8 +/* This is a special case for mips64/n32 ABI (and perhaps others) where + sizeof(void *) is 4 and FFI_SIZEOF_ARG is 8. */ +typedef union { + signed int sint; + unsigned int uint; + float flt; + char data[FFI_SIZEOF_JAVA_RAW]; + void* ptr; +} ffi_java_raw; +#else +typedef ffi_raw ffi_java_raw; +#endif + + +void ffi_raw_call (ffi_cif *cif, + void (*fn)(void), + void *rvalue, + ffi_raw *avalue); + +void ffi_ptrarray_to_raw (ffi_cif *cif, void **args, ffi_raw *raw); +void ffi_raw_to_ptrarray (ffi_cif *cif, ffi_raw *raw, void **args); +size_t ffi_raw_size (ffi_cif *cif); + +/* This is analogous to the raw API, except it uses Java parameter */ +/* packing, even on 64-bit machines. I.e. on 64-bit machines */ +/* longs and doubles are followed by an empty 64-bit word. */ + +void ffi_java_raw_call (ffi_cif *cif, + void (*fn)(void), + void *rvalue, + ffi_java_raw *avalue); + +void ffi_java_ptrarray_to_raw (ffi_cif *cif, void **args, ffi_java_raw *raw); +void ffi_java_raw_to_ptrarray (ffi_cif *cif, ffi_java_raw *raw, void **args); +size_t ffi_java_raw_size (ffi_cif *cif); + +/* ---- Definitions for closures ----------------------------------------- */ + +#if FFI_CLOSURES + +#ifdef _MSC_VER +__declspec(align(8)) +#endif +typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + ffi_cif *cif; + void (*fun)(ffi_cif*,void*,void**,void*); + void *user_data; +#ifdef __GNUC__ +} ffi_closure __attribute__((aligned (8))); +#else +} ffi_closure; +# ifdef __sgi +# pragma pack 0 +# endif +#endif + +void *ffi_closure_alloc (size_t size, void **code); +void ffi_closure_free (void *); + +ffi_status +ffi_prep_closure (ffi_closure*, + ffi_cif *, + void (*fun)(ffi_cif*,void*,void**,void*), + void *user_data); + +ffi_status +ffi_prep_closure_loc (ffi_closure*, + ffi_cif *, + void (*fun)(ffi_cif*,void*,void**,void*), + void *user_data, + void*codeloc); + +#ifdef __sgi +# pragma pack 8 +#endif +typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + ffi_cif *cif; + +#if !FFI_NATIVE_RAW_API + + /* if this is enabled, then a raw closure has the same layout + as a regular closure. We use this to install an intermediate + handler to do the transaltion, void** -> ffi_raw*. */ + + void (*translate_args)(ffi_cif*,void*,void**,void*); + void *this_closure; + +#endif + + void (*fun)(ffi_cif*,void*,ffi_raw*,void*); + void *user_data; + +} ffi_raw_closure; + +typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + + ffi_cif *cif; + +#if !FFI_NATIVE_RAW_API + + /* if this is enabled, then a raw closure has the same layout + as a regular closure. We use this to install an intermediate + handler to do the transaltion, void** -> ffi_raw*. */ + + void (*translate_args)(ffi_cif*,void*,void**,void*); + void *this_closure; + +#endif + + void (*fun)(ffi_cif*,void*,ffi_java_raw*,void*); + void *user_data; + +} ffi_java_raw_closure; + +ffi_status +ffi_prep_raw_closure (ffi_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_raw*,void*), + void *user_data); + +ffi_status +ffi_prep_raw_closure_loc (ffi_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_raw*,void*), + void *user_data, + void *codeloc); + +ffi_status +ffi_prep_java_raw_closure (ffi_java_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_java_raw*,void*), + void *user_data); + +ffi_status +ffi_prep_java_raw_closure_loc (ffi_java_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_java_raw*,void*), + void *user_data, + void *codeloc); + +#endif /* FFI_CLOSURES */ + +/* ---- Public interface definition -------------------------------------- */ + +ffi_status ffi_prep_cif(ffi_cif *cif, + ffi_abi abi, + unsigned int nargs, + ffi_type *rtype, + ffi_type **atypes); + +ffi_status ffi_prep_cif_var(ffi_cif *cif, + ffi_abi abi, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type *rtype, + ffi_type **atypes); + +void ffi_call(ffi_cif *cif, + void (*fn)(void), + void *rvalue, + void **avalue); + +/* Useful for eliminating compiler warnings */ +#define FFI_FN(f) ((void (*)(void))f) + +/* ---- Definitions shared with assembly code ---------------------------- */ + +#endif + +/* If these change, update src/mips/ffitarget.h. */ +#define FFI_TYPE_VOID 0 +#define FFI_TYPE_INT 1 +#define FFI_TYPE_FLOAT 2 +#define FFI_TYPE_DOUBLE 3 +#if 1 +#define FFI_TYPE_LONGDOUBLE 4 +#else +#define FFI_TYPE_LONGDOUBLE FFI_TYPE_DOUBLE +#endif +#define FFI_TYPE_UINT8 5 +#define FFI_TYPE_SINT8 6 +#define FFI_TYPE_UINT16 7 +#define FFI_TYPE_SINT16 8 +#define FFI_TYPE_UINT32 9 +#define FFI_TYPE_SINT32 10 +#define FFI_TYPE_UINT64 11 +#define FFI_TYPE_SINT64 12 +#define FFI_TYPE_STRUCT 13 +#define FFI_TYPE_POINTER 14 +#define FFI_TYPE_COMPLEX 15 + +/* This should always refer to the last type code (for sanity checks) */ +#define FFI_TYPE_LAST FFI_TYPE_COMPLEX + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/external/ffi-prebuilt/lib32/libffi-3.2.1/include/ffitarget.h b/external/ffi-prebuilt/lib32/libffi-3.2.1/include/ffitarget.h new file mode 100644 index 0000000000..214278a6cf --- /dev/null +++ b/external/ffi-prebuilt/lib32/libffi-3.2.1/include/ffitarget.h @@ -0,0 +1,154 @@ +/* -----------------------------------------------------------------*-C-*- + ffitarget.h - Copyright (c) 2012, 2014 Anthony Green + Copyright (c) 1996-2003, 2010 Red Hat, Inc. + Copyright (C) 2008 Free Software Foundation, Inc. + + Target configuration macros for x86 and x86-64. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + ``Software''), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + ----------------------------------------------------------------------- */ + +#ifndef LIBFFI_TARGET_H +#define LIBFFI_TARGET_H + +#ifndef LIBFFI_H +#error "Please do not include ffitarget.h directly into your source. Use ffi.h instead." +#endif + +/* ---- System specific configurations ----------------------------------- */ + +/* For code common to all platforms on x86 and x86_64. */ +#define X86_ANY + +#if defined (X86_64) && defined (__i386__) +#undef X86_64 +#define X86 +#endif + +#ifdef X86_WIN64 +#define FFI_SIZEOF_ARG 8 +#define USE_BUILTIN_FFS 0 /* not yet implemented in mingw-64 */ +#endif + +#define FFI_TARGET_SPECIFIC_STACK_SPACE_ALLOCATION +#ifndef _MSC_VER +/* MSC doesn't support _Complex types: +https://connect.microsoft.com/VisualStudio/feedback/details/1551173/msvc-2015-complex-hs-complex-i-macro-does-not-conform-to-the-standard */ +#define FFI_TARGET_HAS_COMPLEX_TYPE +#endif + +/* ---- Generic type definitions ----------------------------------------- */ + +#ifndef LIBFFI_ASM +#ifdef X86_WIN64 +#ifdef _MSC_VER +typedef unsigned __int64 ffi_arg; +typedef __int64 ffi_sarg; +#else +typedef unsigned long long ffi_arg; +typedef long long ffi_sarg; +#endif +#else +#if defined __x86_64__ && defined __ILP32__ +#define FFI_SIZEOF_ARG 8 +#define FFI_SIZEOF_JAVA_RAW 4 +typedef unsigned long long ffi_arg; +typedef long long ffi_sarg; +#else +typedef unsigned long ffi_arg; +typedef signed long ffi_sarg; +#endif +#endif + +typedef enum ffi_abi { + FFI_FIRST_ABI = 0, + + /* ---- Intel x86 Win32 ---------- */ +#ifdef X86_WIN32 + FFI_SYSV, + FFI_STDCALL, + FFI_THISCALL, + FFI_FASTCALL, + FFI_MS_CDECL, + FFI_PASCAL, + FFI_REGISTER, + FFI_LAST_ABI, +#ifdef _MSC_VER + FFI_DEFAULT_ABI = FFI_MS_CDECL +#else + FFI_DEFAULT_ABI = FFI_SYSV +#endif + +#elif defined(X86_WIN64) + FFI_WIN64, + FFI_LAST_ABI, + FFI_DEFAULT_ABI = FFI_WIN64 + +#else + /* ---- Intel x86 and AMD x86-64 - */ + FFI_SYSV, + FFI_UNIX64, /* Unix variants all use the same ABI for x86-64 */ + FFI_THISCALL, + FFI_FASTCALL, + FFI_STDCALL, + FFI_PASCAL, + FFI_REGISTER, + FFI_LAST_ABI, +#if defined(__i386__) || defined(__i386) + FFI_DEFAULT_ABI = FFI_SYSV +#else + FFI_DEFAULT_ABI = FFI_UNIX64 +#endif +#endif +} ffi_abi; +#endif + +/* ---- Definitions for closures ----------------------------------------- */ + +#define FFI_CLOSURES 1 +#define FFI_TYPE_SMALL_STRUCT_1B (FFI_TYPE_LAST + 1) +#define FFI_TYPE_SMALL_STRUCT_2B (FFI_TYPE_LAST + 2) +#define FFI_TYPE_SMALL_STRUCT_4B (FFI_TYPE_LAST + 3) +#define FFI_TYPE_MS_STRUCT (FFI_TYPE_LAST + 4) + +#if defined (X86_64) || (defined (__x86_64__) && defined (X86_DARWIN)) +#define FFI_TRAMPOLINE_SIZE 24 +#define FFI_NATIVE_RAW_API 0 +#else +#ifdef X86_WIN32 +#define FFI_TRAMPOLINE_SIZE 52 +#else +#ifdef X86_WIN64 +#define FFI_TRAMPOLINE_SIZE 29 +#define FFI_NATIVE_RAW_API 0 +#define FFI_NO_RAW_API 1 +#else +#define FFI_TRAMPOLINE_SIZE 10 +#endif +#endif +#ifndef X86_WIN64 +#define FFI_NATIVE_RAW_API 1 /* x86 has native raw api support */ +#endif +#endif + +#endif + diff --git a/external/ffi-prebuilt/lib32/libffi.a b/external/ffi-prebuilt/lib32/libffi.a new file mode 100644 index 0000000000..0b0edc6b2b Binary files /dev/null and b/external/ffi-prebuilt/lib32/libffi.a differ diff --git a/external/ffi-prebuilt/lib32/pkgconfig/libffi.pc b/external/ffi-prebuilt/lib32/pkgconfig/libffi.pc new file mode 100644 index 0000000000..f970edf675 --- /dev/null +++ b/external/ffi-prebuilt/lib32/pkgconfig/libffi.pc @@ -0,0 +1,11 @@ +prefix=/home/zsx/r3-dev/external/ffi-prebuilt +exec_prefix=${prefix} +libdir=${exec_prefix}/lib32 +toolexeclibdir=${exec_prefix}/lib32 +includedir=${libdir}/libffi-3.2.1/include + +Name: libffi +Description: Library supporting Foreign Function Interfaces +Version: 3.2.1 +Libs: -L${toolexeclibdir} -lffi +Cflags: -I${includedir} diff --git a/external/ffi-prebuilt/lib64/libffi-3.2.1/include/ffi.h b/external/ffi-prebuilt/lib64/libffi-3.2.1/include/ffi.h new file mode 100644 index 0000000000..a884b596f3 --- /dev/null +++ b/external/ffi-prebuilt/lib64/libffi-3.2.1/include/ffi.h @@ -0,0 +1,487 @@ +/* -----------------------------------------------------------------*-C-*- + libffi 3.2.1 - Copyright (c) 2011, 2014 Anthony Green + - Copyright (c) 1996-2003, 2007, 2008 Red Hat, Inc. + + 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. + + ----------------------------------------------------------------------- */ + +/* ------------------------------------------------------------------- + The basic API is described in the README file. + + The raw API is designed to bypass some of the argument packing + and unpacking on architectures for which it can be avoided. + + The closure API allows interpreted functions to be packaged up + inside a C function pointer, so that they can be called as C functions, + with no understanding on the client side that they are interpreted. + It can also be used in other cases in which it is necessary to package + up a user specified parameter and a function pointer as a single + function pointer. + + The closure API must be implemented in order to get its functionality, + e.g. for use by gij. Routines are provided to emulate the raw API + if the underlying platform doesn't allow faster implementation. + + More details on the raw and cloure API can be found in: + + http://gcc.gnu.org/ml/java/1999-q3/msg00138.html + + and + + http://gcc.gnu.org/ml/java/1999-q3/msg00174.html + -------------------------------------------------------------------- */ + +#ifndef LIBFFI_H +#define LIBFFI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Specify which architecture libffi is configured for. */ +#ifndef X86_WIN64 +#define X86_WIN64 +#endif + +/* ---- System configuration information --------------------------------- */ + +#include + +#ifndef LIBFFI_ASM + +#if defined(_MSC_VER) && !defined(__clang__) +#define __attribute__(X) +#endif + +#include +#include + +/* LONG_LONG_MAX is not always defined (not if STRICT_ANSI, for example). + But we can find it either under the correct ANSI name, or under GNU + C's internal name. */ + +#define FFI_64_BIT_MAX 9223372036854775807 + +#ifdef LONG_LONG_MAX +# define FFI_LONG_LONG_MAX LONG_LONG_MAX +#else +# ifdef LLONG_MAX +# define FFI_LONG_LONG_MAX LLONG_MAX +# ifdef _AIX52 /* or newer has C99 LLONG_MAX */ +# undef FFI_64_BIT_MAX +# define FFI_64_BIT_MAX 9223372036854775807LL +# endif /* _AIX52 or newer */ +# else +# ifdef __GNUC__ +# define FFI_LONG_LONG_MAX __LONG_LONG_MAX__ +# endif +# ifdef _AIX /* AIX 5.1 and earlier have LONGLONG_MAX */ +# ifndef __PPC64__ +# if defined (__IBMC__) || defined (__IBMCPP__) +# define FFI_LONG_LONG_MAX LONGLONG_MAX +# endif +# endif /* __PPC64__ */ +# undef FFI_64_BIT_MAX +# define FFI_64_BIT_MAX 9223372036854775807LL +# endif +# endif +#endif + +/* The closure code assumes that this works on pointers, i.e. a size_t */ +/* can hold a pointer. */ + +typedef struct _ffi_type +{ + size_t size; + unsigned short alignment; + unsigned short type; + struct _ffi_type **elements; +} ffi_type; + +#ifndef LIBFFI_HIDE_BASIC_TYPES +#if SCHAR_MAX == 127 +# define ffi_type_uchar ffi_type_uint8 +# define ffi_type_schar ffi_type_sint8 +#else + #error "char size not supported" +#endif + +#if SHRT_MAX == 32767 +# define ffi_type_ushort ffi_type_uint16 +# define ffi_type_sshort ffi_type_sint16 +#elif SHRT_MAX == 2147483647 +# define ffi_type_ushort ffi_type_uint32 +# define ffi_type_sshort ffi_type_sint32 +#else + #error "short size not supported" +#endif + +#if INT_MAX == 32767 +# define ffi_type_uint ffi_type_uint16 +# define ffi_type_sint ffi_type_sint16 +#elif INT_MAX == 2147483647 +# define ffi_type_uint ffi_type_uint32 +# define ffi_type_sint ffi_type_sint32 +#elif INT_MAX == 9223372036854775807 +# define ffi_type_uint ffi_type_uint64 +# define ffi_type_sint ffi_type_sint64 +#else + #error "int size not supported" +#endif + +#if LONG_MAX == 2147483647 +# if FFI_LONG_LONG_MAX != FFI_64_BIT_MAX + #error "no 64-bit data type supported" +# endif +#elif LONG_MAX != FFI_64_BIT_MAX + #error "long size not supported" +#endif + +#if LONG_MAX == 2147483647 +# define ffi_type_ulong ffi_type_uint32 +# define ffi_type_slong ffi_type_sint32 +#elif LONG_MAX == FFI_64_BIT_MAX +# define ffi_type_ulong ffi_type_uint64 +# define ffi_type_slong ffi_type_sint64 +#else + #error "long size not supported" +#endif + +/* Need minimal decorations for DLLs to works on Windows. */ +/* GCC has autoimport and autoexport. Rely on Libtool to */ +/* help MSVC export from a DLL, but always declare data */ +/* to be imported for MSVC clients. This costs an extra */ +/* indirection for MSVC clients using the static version */ +/* of the library, but don't worry about that. Besides, */ +/* as a workaround, they can define FFI_BUILDING if they */ +/* *know* they are going to link with the static library. */ +#if defined _MSC_VER && !defined FFI_BUILDING +#define FFI_EXTERN extern __declspec(dllimport) +#else +#define FFI_EXTERN extern +#endif + +/* These are defined in types.c */ +FFI_EXTERN ffi_type ffi_type_void; +FFI_EXTERN ffi_type ffi_type_uint8; +FFI_EXTERN ffi_type ffi_type_sint8; +FFI_EXTERN ffi_type ffi_type_uint16; +FFI_EXTERN ffi_type ffi_type_sint16; +FFI_EXTERN ffi_type ffi_type_uint32; +FFI_EXTERN ffi_type ffi_type_sint32; +FFI_EXTERN ffi_type ffi_type_uint64; +FFI_EXTERN ffi_type ffi_type_sint64; +FFI_EXTERN ffi_type ffi_type_float; +FFI_EXTERN ffi_type ffi_type_double; +FFI_EXTERN ffi_type ffi_type_pointer; + +#if 1 +FFI_EXTERN ffi_type ffi_type_longdouble; +#else +#define ffi_type_longdouble ffi_type_double +#endif + +#ifdef FFI_TARGET_HAS_COMPLEX_TYPE +FFI_EXTERN ffi_type ffi_type_complex_float; +FFI_EXTERN ffi_type ffi_type_complex_double; +#if 1 +FFI_EXTERN ffi_type ffi_type_complex_longdouble; +#else +#define ffi_type_complex_longdouble ffi_type_complex_double +#endif +#endif +#endif /* LIBFFI_HIDE_BASIC_TYPES */ + +typedef enum { + FFI_OK = 0, + FFI_BAD_TYPEDEF, + FFI_BAD_ABI +} ffi_status; + +typedef unsigned FFI_TYPE; + +typedef struct { + ffi_abi abi; + unsigned nargs; + ffi_type **arg_types; + ffi_type *rtype; + unsigned bytes; + unsigned flags; +#ifdef FFI_EXTRA_CIF_FIELDS + FFI_EXTRA_CIF_FIELDS; +#endif +} ffi_cif; + +#if 0 +/* Used to adjust size/alignment of ffi types. */ +void ffi_prep_types (ffi_abi abi); +#endif + +/* Used internally, but overridden by some architectures */ +ffi_status ffi_prep_cif_core(ffi_cif *cif, + ffi_abi abi, + unsigned int isvariadic, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type *rtype, + ffi_type **atypes); + +/* ---- Definitions for the raw API -------------------------------------- */ + +#ifndef FFI_SIZEOF_ARG +# if LONG_MAX == 2147483647 +# define FFI_SIZEOF_ARG 4 +# elif LONG_MAX == FFI_64_BIT_MAX +# define FFI_SIZEOF_ARG 8 +# endif +#endif + +#ifndef FFI_SIZEOF_JAVA_RAW +# define FFI_SIZEOF_JAVA_RAW FFI_SIZEOF_ARG +#endif + +typedef union { + ffi_sarg sint; + ffi_arg uint; + float flt; + char data[FFI_SIZEOF_ARG]; + void* ptr; +} ffi_raw; + +#if FFI_SIZEOF_JAVA_RAW == 4 && FFI_SIZEOF_ARG == 8 +/* This is a special case for mips64/n32 ABI (and perhaps others) where + sizeof(void *) is 4 and FFI_SIZEOF_ARG is 8. */ +typedef union { + signed int sint; + unsigned int uint; + float flt; + char data[FFI_SIZEOF_JAVA_RAW]; + void* ptr; +} ffi_java_raw; +#else +typedef ffi_raw ffi_java_raw; +#endif + + +void ffi_raw_call (ffi_cif *cif, + void (*fn)(void), + void *rvalue, + ffi_raw *avalue); + +void ffi_ptrarray_to_raw (ffi_cif *cif, void **args, ffi_raw *raw); +void ffi_raw_to_ptrarray (ffi_cif *cif, ffi_raw *raw, void **args); +size_t ffi_raw_size (ffi_cif *cif); + +/* This is analogous to the raw API, except it uses Java parameter */ +/* packing, even on 64-bit machines. I.e. on 64-bit machines */ +/* longs and doubles are followed by an empty 64-bit word. */ + +void ffi_java_raw_call (ffi_cif *cif, + void (*fn)(void), + void *rvalue, + ffi_java_raw *avalue); + +void ffi_java_ptrarray_to_raw (ffi_cif *cif, void **args, ffi_java_raw *raw); +void ffi_java_raw_to_ptrarray (ffi_cif *cif, ffi_java_raw *raw, void **args); +size_t ffi_java_raw_size (ffi_cif *cif); + +/* ---- Definitions for closures ----------------------------------------- */ + +#if FFI_CLOSURES + +#ifdef _MSC_VER +__declspec(align(8)) +#endif +typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + ffi_cif *cif; + void (*fun)(ffi_cif*,void*,void**,void*); + void *user_data; +#ifdef __GNUC__ +} ffi_closure __attribute__((aligned (8))); +#else +} ffi_closure; +# ifdef __sgi +# pragma pack 0 +# endif +#endif + +void *ffi_closure_alloc (size_t size, void **code); +void ffi_closure_free (void *); + +ffi_status +ffi_prep_closure (ffi_closure*, + ffi_cif *, + void (*fun)(ffi_cif*,void*,void**,void*), + void *user_data); + +ffi_status +ffi_prep_closure_loc (ffi_closure*, + ffi_cif *, + void (*fun)(ffi_cif*,void*,void**,void*), + void *user_data, + void*codeloc); + +#ifdef __sgi +# pragma pack 8 +#endif +typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + ffi_cif *cif; + +#if !FFI_NATIVE_RAW_API + + /* if this is enabled, then a raw closure has the same layout + as a regular closure. We use this to install an intermediate + handler to do the transaltion, void** -> ffi_raw*. */ + + void (*translate_args)(ffi_cif*,void*,void**,void*); + void *this_closure; + +#endif + + void (*fun)(ffi_cif*,void*,ffi_raw*,void*); + void *user_data; + +} ffi_raw_closure; + +typedef struct { +#if 0 + void *trampoline_table; + void *trampoline_table_entry; +#else + char tramp[FFI_TRAMPOLINE_SIZE]; +#endif + + ffi_cif *cif; + +#if !FFI_NATIVE_RAW_API + + /* if this is enabled, then a raw closure has the same layout + as a regular closure. We use this to install an intermediate + handler to do the transaltion, void** -> ffi_raw*. */ + + void (*translate_args)(ffi_cif*,void*,void**,void*); + void *this_closure; + +#endif + + void (*fun)(ffi_cif*,void*,ffi_java_raw*,void*); + void *user_data; + +} ffi_java_raw_closure; + +ffi_status +ffi_prep_raw_closure (ffi_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_raw*,void*), + void *user_data); + +ffi_status +ffi_prep_raw_closure_loc (ffi_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_raw*,void*), + void *user_data, + void *codeloc); + +ffi_status +ffi_prep_java_raw_closure (ffi_java_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_java_raw*,void*), + void *user_data); + +ffi_status +ffi_prep_java_raw_closure_loc (ffi_java_raw_closure*, + ffi_cif *cif, + void (*fun)(ffi_cif*,void*,ffi_java_raw*,void*), + void *user_data, + void *codeloc); + +#endif /* FFI_CLOSURES */ + +/* ---- Public interface definition -------------------------------------- */ + +ffi_status ffi_prep_cif(ffi_cif *cif, + ffi_abi abi, + unsigned int nargs, + ffi_type *rtype, + ffi_type **atypes); + +ffi_status ffi_prep_cif_var(ffi_cif *cif, + ffi_abi abi, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type *rtype, + ffi_type **atypes); + +void ffi_call(ffi_cif *cif, + void (*fn)(void), + void *rvalue, + void **avalue); + +/* Useful for eliminating compiler warnings */ +#define FFI_FN(f) ((void (*)(void))f) + +/* ---- Definitions shared with assembly code ---------------------------- */ + +#endif + +/* If these change, update src/mips/ffitarget.h. */ +#define FFI_TYPE_VOID 0 +#define FFI_TYPE_INT 1 +#define FFI_TYPE_FLOAT 2 +#define FFI_TYPE_DOUBLE 3 +#if 1 +#define FFI_TYPE_LONGDOUBLE 4 +#else +#define FFI_TYPE_LONGDOUBLE FFI_TYPE_DOUBLE +#endif +#define FFI_TYPE_UINT8 5 +#define FFI_TYPE_SINT8 6 +#define FFI_TYPE_UINT16 7 +#define FFI_TYPE_SINT16 8 +#define FFI_TYPE_UINT32 9 +#define FFI_TYPE_SINT32 10 +#define FFI_TYPE_UINT64 11 +#define FFI_TYPE_SINT64 12 +#define FFI_TYPE_STRUCT 13 +#define FFI_TYPE_POINTER 14 +#define FFI_TYPE_COMPLEX 15 + +/* This should always refer to the last type code (for sanity checks) */ +#define FFI_TYPE_LAST FFI_TYPE_COMPLEX + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/external/ffi-prebuilt/lib64/libffi-3.2.1/include/ffitarget.h b/external/ffi-prebuilt/lib64/libffi-3.2.1/include/ffitarget.h new file mode 100644 index 0000000000..214278a6cf --- /dev/null +++ b/external/ffi-prebuilt/lib64/libffi-3.2.1/include/ffitarget.h @@ -0,0 +1,154 @@ +/* -----------------------------------------------------------------*-C-*- + ffitarget.h - Copyright (c) 2012, 2014 Anthony Green + Copyright (c) 1996-2003, 2010 Red Hat, Inc. + Copyright (C) 2008 Free Software Foundation, Inc. + + Target configuration macros for x86 and x86-64. + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + ``Software''), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + ----------------------------------------------------------------------- */ + +#ifndef LIBFFI_TARGET_H +#define LIBFFI_TARGET_H + +#ifndef LIBFFI_H +#error "Please do not include ffitarget.h directly into your source. Use ffi.h instead." +#endif + +/* ---- System specific configurations ----------------------------------- */ + +/* For code common to all platforms on x86 and x86_64. */ +#define X86_ANY + +#if defined (X86_64) && defined (__i386__) +#undef X86_64 +#define X86 +#endif + +#ifdef X86_WIN64 +#define FFI_SIZEOF_ARG 8 +#define USE_BUILTIN_FFS 0 /* not yet implemented in mingw-64 */ +#endif + +#define FFI_TARGET_SPECIFIC_STACK_SPACE_ALLOCATION +#ifndef _MSC_VER +/* MSC doesn't support _Complex types: +https://connect.microsoft.com/VisualStudio/feedback/details/1551173/msvc-2015-complex-hs-complex-i-macro-does-not-conform-to-the-standard */ +#define FFI_TARGET_HAS_COMPLEX_TYPE +#endif + +/* ---- Generic type definitions ----------------------------------------- */ + +#ifndef LIBFFI_ASM +#ifdef X86_WIN64 +#ifdef _MSC_VER +typedef unsigned __int64 ffi_arg; +typedef __int64 ffi_sarg; +#else +typedef unsigned long long ffi_arg; +typedef long long ffi_sarg; +#endif +#else +#if defined __x86_64__ && defined __ILP32__ +#define FFI_SIZEOF_ARG 8 +#define FFI_SIZEOF_JAVA_RAW 4 +typedef unsigned long long ffi_arg; +typedef long long ffi_sarg; +#else +typedef unsigned long ffi_arg; +typedef signed long ffi_sarg; +#endif +#endif + +typedef enum ffi_abi { + FFI_FIRST_ABI = 0, + + /* ---- Intel x86 Win32 ---------- */ +#ifdef X86_WIN32 + FFI_SYSV, + FFI_STDCALL, + FFI_THISCALL, + FFI_FASTCALL, + FFI_MS_CDECL, + FFI_PASCAL, + FFI_REGISTER, + FFI_LAST_ABI, +#ifdef _MSC_VER + FFI_DEFAULT_ABI = FFI_MS_CDECL +#else + FFI_DEFAULT_ABI = FFI_SYSV +#endif + +#elif defined(X86_WIN64) + FFI_WIN64, + FFI_LAST_ABI, + FFI_DEFAULT_ABI = FFI_WIN64 + +#else + /* ---- Intel x86 and AMD x86-64 - */ + FFI_SYSV, + FFI_UNIX64, /* Unix variants all use the same ABI for x86-64 */ + FFI_THISCALL, + FFI_FASTCALL, + FFI_STDCALL, + FFI_PASCAL, + FFI_REGISTER, + FFI_LAST_ABI, +#if defined(__i386__) || defined(__i386) + FFI_DEFAULT_ABI = FFI_SYSV +#else + FFI_DEFAULT_ABI = FFI_UNIX64 +#endif +#endif +} ffi_abi; +#endif + +/* ---- Definitions for closures ----------------------------------------- */ + +#define FFI_CLOSURES 1 +#define FFI_TYPE_SMALL_STRUCT_1B (FFI_TYPE_LAST + 1) +#define FFI_TYPE_SMALL_STRUCT_2B (FFI_TYPE_LAST + 2) +#define FFI_TYPE_SMALL_STRUCT_4B (FFI_TYPE_LAST + 3) +#define FFI_TYPE_MS_STRUCT (FFI_TYPE_LAST + 4) + +#if defined (X86_64) || (defined (__x86_64__) && defined (X86_DARWIN)) +#define FFI_TRAMPOLINE_SIZE 24 +#define FFI_NATIVE_RAW_API 0 +#else +#ifdef X86_WIN32 +#define FFI_TRAMPOLINE_SIZE 52 +#else +#ifdef X86_WIN64 +#define FFI_TRAMPOLINE_SIZE 29 +#define FFI_NATIVE_RAW_API 0 +#define FFI_NO_RAW_API 1 +#else +#define FFI_TRAMPOLINE_SIZE 10 +#endif +#endif +#ifndef X86_WIN64 +#define FFI_NATIVE_RAW_API 1 /* x86 has native raw api support */ +#endif +#endif + +#endif + diff --git a/external/ffi-prebuilt/lib64/libffi.a b/external/ffi-prebuilt/lib64/libffi.a new file mode 100644 index 0000000000..aa1c0ef315 Binary files /dev/null and b/external/ffi-prebuilt/lib64/libffi.a differ diff --git a/external/ffi-prebuilt/lib64/pkgconfig/libffi.pc b/external/ffi-prebuilt/lib64/pkgconfig/libffi.pc new file mode 100644 index 0000000000..40ce53b66b --- /dev/null +++ b/external/ffi-prebuilt/lib64/pkgconfig/libffi.pc @@ -0,0 +1,11 @@ +prefix=/home/zsx/r3-dev/external/ffi-prebuilt +exec_prefix=${prefix} +libdir=${exec_prefix}/lib64 +toolexeclibdir=${exec_prefix}/lib64 +includedir=${libdir}/libffi-3.2.1/include + +Name: libffi +Description: Library supporting Foreign Function Interfaces +Version: 3.2.1 +Libs: -L${toolexeclibdir} -lffi +Cflags: -I${includedir} diff --git a/external/libffi b/external/libffi new file mode 160000 index 0000000000..12e1d551b0 --- /dev/null +++ b/external/libffi @@ -0,0 +1 @@ +Subproject commit 12e1d551b09f149128e56191dcf7503d575f0de9 diff --git a/external/tcc b/external/tcc new file mode 160000 index 0000000000..06bb332687 --- /dev/null +++ b/external/tcc @@ -0,0 +1 @@ +Subproject commit 06bb3326876bcac738ba302218ecf81b4861488e diff --git a/git-hooks/README.md b/git-hooks/README.md new file mode 100644 index 0000000000..a5ceff63e6 --- /dev/null +++ b/git-hooks/README.md @@ -0,0 +1,13 @@ +The hooks that git executes (such as a "pre-commit" hook), are placed +in `.git/hooks`. These are *not* part of the tracked files under +version control, so you will not get them installed from a `git clone` + +So it is necessary for each developer to copy these over after they +have cloned. The hooks may rely on functions that are not available +on some build systems, so that should be considered. + +Improvements are welcome. For now, the goal is just to keep spaces from +sneaking into files where they shouldn't be, but much more can be +done. Please at minimum do this if you are going to be contributing: + + cp git-hooks/pre-commit .git/hooks/pre-commit diff --git a/git-hooks/pre-commit b/git-hooks/pre-commit new file mode 100755 index 0000000000..2dc02d4678 --- /dev/null +++ b/git-hooks/pre-commit @@ -0,0 +1,79 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by git-commit with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, make this file executable. + +# This is slightly modified from Andrew Morton's Perfect Patch. +# Lines you introduce should not have trailing whitespace. +# Also check for an indentation that has SP before a TAB. + +# Original code borrowed from: +# +# https://gist.github.com/benprew/6384274 +# http://git.xiph.org/speex.git/hooks/pre-commit +# +# !!! Should use a Rebol solution + +if git rev-parse --verify HEAD 2>/dev/null +then + git diff-index -p -M --cached HEAD +else + # NEEDSWORK: we should produce a diff with an empty tree here + # if we want to do the same verification for the initial import. + : +fi | +perl -e ' + my $found_bad = 0; + my $filename; + my $reported_filename = ""; + my $lineno; + + sub bad_line { + my ($why, $line) = @_; + if (!$found_bad) { + print STDERR "*\n"; + print STDERR "* You have some suspicious patch lines:\n"; + print STDERR "*\n"; + $found_bad = 1; + } + if ($reported_filename ne $filename) { + print STDERR "* In $filename\n"; + $reported_filename = $filename; + } + print STDERR "* $why (line $lineno)\n"; + print STDERR "$filename:$lineno:$line\n"; + } + + while (<>) { + if (m|^diff --git a/(.*) b/\1$|) { + $filename = $1; + next; + } + if (/^@@ -\S+ \+(\d+)/) { + $lineno = $1 - 1; + next; + } + if (/^ /) { + $lineno++; + next; + } + if (s/^\+//) { + $lineno++; + chomp; + if (/\s$/) { + bad_line("trailing whitespace", $_); + } + if (/^\s* /) { + bad_line("indent SP followed by a TAB", $_); + } + if (/^(?:[<>=]){7}/) { + bad_line("unresolved merge conflict", $_); + } + } + } + exit($found_bad); +' diff --git a/make/CMakeLists.txt b/make/CMakeLists.txt new file mode 100644 index 0000000000..4804447822 --- /dev/null +++ b/make/CMakeLists.txt @@ -0,0 +1,1133 @@ +# vim: ts=4 shiftwidth=4 filetype=cmake expandtab + +# +# Sample command lines: +# +# cd make +# mkdir build +# cd build +# CMake .. -G "Visual Studio 14 Win64" -DR3_OS_ID=0.3.40 +# -DR3_EXTERNAL_FFI=yes -DR3_CPP=no +# + +cmake_minimum_required (VERSION 2.8) + +project (Rebol3 C CXX) + +# While the project is technically able to build as C++98, the interesting +# added checks are only enabled in the C++11 (or later) builds. +# +set (CMAKE_CXX_STANDARD 11) + +# Simplify appending flags to the C build settings, C++ settings, or both + +macro(add_cxx_flags flags) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flags}") +endmacro() + +macro(add_c_flags flags) + set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} ${flags}") +endmacro() + +macro(add_c_and_cxx_flags flags) + set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} ${flags}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flags}") +endmacro() + +#Only interpret if() arguments as variables or keywords when unquoted +if (POLICY CMP0054) + cmake_policy(SET CMP0054 NEW) +endif () + +set (TOP_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../src") +set (TOP_GENERATED_SRC_DIR "${CMAKE_CURRENT_BINARY_DIR}/src") +set (CORE_DIR "${TOP_SRC_DIR}/core") +set (CORE_GENERATED_DIR "${TOP_GENERATED_SRC_DIR}/core") +set (OS_DIR "${TOP_SRC_DIR}/os") +set (EXT_DIR "${TOP_SRC_DIR}/extensions") +set (AGG_DIR "${TOP_SRC_DIR}/agg") +set (TOOLS_DIR "${TOP_SRC_DIR}/tools") +set (EXTERNAL_DIR "${TOP_SRC_DIR}/../external") +set (FFI_DIR "${EXTERNAL_DIR}/libffi") +set (TCC_DIR "${EXTERNAL_DIR}/tcc") +set (GIT_EXE "git" CACHE FILEPATH "Path to the git executable") + +if(CMAKE_HOST_WIN32) + set (REBOL "${CMAKE_CURRENT_BINARY_DIR}/r3-make.exe") +else() + set (REBOL "${CMAKE_CURRENT_BINARY_DIR}/r3-make") +endif() +set (R3_MAKE ${REBOL} CACHE FILEPATH "Path to an R3 executable for tool scripts") +set (REBOL ${R3_MAKE}) + +option(R3_EXTERNAL_FFI "Build with external FFI" OFF) +option(R3_CPP "Build C files as C++" OFF) +option(R3_WITH_TCC "Build with libtcc" OFF) + +if (NOT EXISTS ${REBOL}) + message(FATAL_ERROR "${REBOL} doesn't exist, an executable r3 is required") +endif() + +execute_process(COMMAND ${GIT_EXE} show --format="%H" --no-patch + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_COMMIT_STATUS + OUTPUT_VARIABLE GIT_COMMIT) + +#message("commit status: ${GIT_COMMIT_STATUS}, commit: ${GIT_COMMIT}") +if (NOT "${GIT_COMMIT_STATUS}" EQUAL "0") + set(GIT_COMMIT "unknown") +endif () + +if (NOT DEFINED R3_OS_ID) + message("Trying to obtain OS_ID from r3-make, as R3_OS_ID is not defined ...") + execute_process(COMMAND ${REBOL} --do "print rejoin [\"0.\" system/version/4 \".\" system/version/5]" OUTPUT_VARIABLE R3_OS_ID) + string(STRIP ${R3_OS_ID} R3_OS_ID) +endif() + +if (NOT DEFINED R3_OS_ID) + message(FATAL_ERROR "R3_OS_ID is not defined") +endif() +message("Building Rebol 3 for OS_ID: ${R3_OS_ID}") + +string(REPLACE "." ";" OS_ID_LIST ${R3_OS_ID}) +list(GET OS_ID_LIST 1 OS_MAJOR) +list(GET OS_ID_LIST 2 OS_MINOR) +message("OS: ${OS_MAJOR}.${OS_MINOR}") +if (NOT DEFINED OS_MAJOR OR NOT DEFINED OS_MINOR) + message(FATAL_ERROR "Unrecognized R3_OS_ID: ${R3_OS_ID}") +endif() +set (COMMON_MACROS "") +if ("${OS_MAJOR}" STREQUAL "2") + set (TO_OSX TRUE) + list (APPEND COMMON_MACROS + ENDIAN_LITTLE + HAS_LL_CONSTS + ) +elseif ("${OS_MAJOR}" STREQUAL "3") + set (TO_WINDOWS TRUE) + list (APPEND COMMON_MACROS + WIN32 + TO_WINDOWS + ENDIAN_LITTLE + UNICODE + _UNICODE + ZLIB_DLL #to export symbols in zlib for u-png.c usage + ) + set(LIBS wsock32 comdlg32) +elseif ("${OS_MAJOR}" STREQUAL "4") + set (TO_LINUX TRUE) + list (APPEND COMMON_MACROS + TO_LINUX + ENDIAN_LITTLE + HAS_LL_CONSTS + ) + set(LIBS m dl) +elseif ("${OS_MAJOR}" STREQUAL "13") + set (TO_ANDROID TRUE) + list (APPEND COMMON_MACROS + TO_ANDROID + ENDIAN_LITTLE + HAS_LL_CONSTS + ) + set(LIBS m dl) +else () + message(FATAL_ERROR "Unsupported system") +endif () + +if (R3_OS_ID STREQUAL "0.2.5") + set (TO_OSX_X86 TRUE) + list (APPEND COMMON_MACROS + TO_OSX_X86 + ) + set(CMAKE_OSX_ARCHITECTURE i386) +elseif (R3_OS_ID STREQUAL "0.2.40") + set (TO_OSX_X64 TRUE) + list (APPEND COMMON_MACROS + TO_OSX_X64 + __LP64__ + ) + set(CMAKE_OSX_ARCHITECTURE x86_64) +elseif (R3_OS_ID STREQUAL "0.3.1") + set (TO_WINDOWS_X86 TRUE) + list (APPEND COMMON_MACROS + TO_WINDOWS_X86 + ) +elseif (R3_OS_ID STREQUAL "0.3.40") + set (TO_WINDOWS_X64 TRUE) + list (APPEND COMMON_MACROS + TO_WINDOWS_X64 + __LLP64__ + ) +elseif (R3_OS_ID STREQUAL "0.4.4") + set (TO_LINUX_X86 TRUE) + list (APPEND COMMON_MACROS + TO_LINUX_X86 + ) +elseif (R3_OS_ID STREQUAL "0.4.20") #linux arm + set (TO_LINUX_ARM TRUE) + list (APPEND COMMON_MACROS + TO_LINUX_ARM + ) +elseif (R3_OS_ID STREQUAL "0.4.22") #linux arm64 + set (TO_LINUX_AARCH64 TRUE) + list (APPEND COMMON_MACROS + TO_LINUX_AARCH64 + __LP64__ + ) +elseif (R3_OS_ID STREQUAL "0.4.40") #linux x86_64 + set (TO_LINUX_X64 TRUE) + list (APPEND COMMON_MACROS + TO_LINUX_X64 + __LP64__ + ) +elseif (R3_OS_ID STREQUAL "0.13.2") #android5 arm + set (TO_ANDROID5 TRUE) + set (TO_ANDROID5_ARM TRUE) + list (APPEND COMMON_MACROS + TO_ANDROID_ARM + ) +endif () + +if (MSVC) + set (LINK_FLAGS /STACK:4194304) + + # !!! At the moment, there are many places where in the 64-bit build, a + # 64-bit integer is used in places where a 32-bit integer is expected. + # Ren-C intends to use 64-bit series indices in 64-bit builds, but that + # just hasn't been done yet. + # + # (Note: /WD is "Warning Disable") + # + add_c_and_cxx_flags(/wd4244) # possible data loss in argument conversion + add_c_and_cxx_flags(/wd4267) # possible data loss in initialization + + # MSVC complains if you use old-style functions like `strcpy` instead of + # `strcpy_s`. There should be a review of these cases, but for now they + # are allowed as-is + # + add_c_and_cxx_flags(-D_CRT_SECURE_NO_WARNINGS) +endif() + +if (TO_WINDOWS) + set (CORE_PLATFORM_SOURCE + ${OS_DIR}/windows/host-lib.c + ${OS_DIR}/windows/dev-stdio.c + ${OS_DIR}/windows/dev-file.c + ${OS_DIR}/windows/dev-serial.c + ${OS_DIR}/windows/dev-event.c + ${OS_DIR}/windows/dev-clipboard.c + ) +else () + set (CORE_PLATFORM_SOURCE + ${OS_DIR}/generic/host-memory.c + ${OS_DIR}/generic/host-locale.c + ${OS_DIR}/generic/iso-639.c + ${OS_DIR}/generic/iso-3166.c + ${OS_DIR}/generic/host-gob.c + ${OS_DIR}/posix/host-readline.c + ${OS_DIR}/posix/dev-stdio.c + ${OS_DIR}/posix/dev-event.c + ${OS_DIR}/posix/dev-file.c + ${OS_DIR}/posix/host-browse.c + ${OS_DIR}/posix/host-config.c + ${OS_DIR}/posix/host-error.c + ${OS_DIR}/posix/host-library.c + ${OS_DIR}/posix/host-process.c + ${OS_DIR}/posix/host-time.c + ${OS_DIR}/posix/dev-serial.c + ) + if (TO_LINUX) + set (CORE_PLATFORM_SOURCE ${CORE_PLATFORM_SOURCE} + # Linux supports siginfo_t-style signals + ${OS_DIR}/linux/dev-signal.c + ) + endif () + if (TO_OSX) + list(APPEND CORE_PLATFORM_SOURCE + ${OS_DIR}/osx/host-exec-path.c + ) + else () + list(APPEND CORE_PLATFORM_SOURCE + ${OS_DIR}/posix/host-exec-path.c + ) + endif () +endif () + +if (R3_EXTERNAL_FFI) + find_package(PkgConfig) + pkg_search_module(FFI libffi) + if (${FFI_FOUND}) + set (COMMON_MACROS ${COMMON_MACROS} HAVE_LIBFFI_AVAILABLE) + else () + message(WARNING "FFI is not found, External Library Access will not work!") + endif () +else () + if (XCODE) #Xcode has problem with EXCLUDE_FROM_ALL + add_subdirectory(${FFI_DIR} ${CMAKE_CURRENT_BINARY_DIR}/ffi) + else () + add_subdirectory(${FFI_DIR} ${CMAKE_CURRENT_BINARY_DIR}/ffi EXCLUDE_FROM_ALL) + endif () + set (COMMON_MACROS ${COMMON_MACROS} HAVE_LIBFFI_AVAILABLE FFI_BUILDING) + set (FFI_LIBRARIES ffi_s) +endif () + +if (R3_WITH_TCC) + set (COMMON_MACROS ${COMMON_MACROS} WITH_TCC) +endif () + +#CORE +set (CORE_SOURCE + ${CORE_DIR}/a-constants.c + ${CORE_DIR}/a-globals.c + ${CORE_DIR}/a-lib.c + ${CORE_DIR}/b-init.c + ${CORE_DIR}/c-bind.c + ${CORE_DIR}/c-context.c + ${CORE_DIR}/c-do.c + ${CORE_DIR}/c-eval.c + ${CORE_DIR}/c-error.c + ${CORE_DIR}/c-function.c + ${CORE_DIR}/c-path.c + ${CORE_DIR}/c-port.c + ${CORE_DIR}/c-signal.c + ${CORE_DIR}/c-word.c + ${CORE_DIR}/c-value.c + ${CORE_DIR}/d-break.c + ${CORE_DIR}/d-crash.c + ${CORE_DIR}/d-dump.c + ${CORE_DIR}/d-eval.c + ${CORE_DIR}/d-legacy.c + ${CORE_DIR}/d-print.c + ${CORE_DIR}/d-stack.c + ${CORE_DIR}/d-trace.c + ${CORE_DIR}/f-blocks.c + ${CORE_DIR}/f-deci.c + ${CORE_DIR}/f-dtoa.c + ${CORE_DIR}/f-enbase.c + ${CORE_DIR}/f-extension.c + ${CORE_DIR}/f-int.c + ${CORE_DIR}/f-math.c + ${CORE_DIR}/f-modify.c + ${CORE_DIR}/f-qsort.c + ${CORE_DIR}/f-random.c + ${CORE_DIR}/f-round.c + ${CORE_DIR}/f-series.c + ${CORE_DIR}/f-stubs.c + ${CORE_DIR}/l-scan.c + ${CORE_DIR}/l-types.c + ${CORE_DIR}/m-gc.c + ${CORE_DIR}/m-pools.c + ${CORE_DIR}/m-series.c + ${CORE_DIR}/m-stacks.c + ${CORE_DIR}/n-control.c + ${CORE_DIR}/n-data.c + ${CORE_DIR}/n-do.c + ${CORE_DIR}/n-error.c + ${CORE_DIR}/n-function.c + ${CORE_DIR}/n-io.c + ${CORE_DIR}/n-loop.c + ${CORE_DIR}/n-math.c + ${CORE_DIR}/n-native.c + ${CORE_DIR}/n-protect.c + ${CORE_DIR}/n-reduce.c + ${CORE_DIR}/n-sets.c + ${CORE_DIR}/n-strings.c + ${CORE_DIR}/n-system.c + ${CORE_DIR}/n-textcodecs.c + ${CORE_DIR}/p-clipboard.c + ${CORE_DIR}/p-console.c + ${CORE_DIR}/p-dir.c + ${CORE_DIR}/p-dns.c + ${CORE_DIR}/p-event.c + ${CORE_DIR}/p-file.c + ${CORE_DIR}/p-net.c + ${CORE_DIR}/p-serial.c + ${CORE_DIR}/p-signal.c + ${CORE_DIR}/s-cases.c + ${CORE_DIR}/s-crc.c + ${CORE_DIR}/s-file.c + ${CORE_DIR}/s-find.c + ${CORE_DIR}/s-make.c + ${CORE_DIR}/s-mold.c + ${CORE_DIR}/s-ops.c + ${CORE_DIR}/s-trim.c + ${CORE_DIR}/s-unicode.c + ${CORE_DIR}/t-bitset.c + ${CORE_DIR}/t-blank.c + ${CORE_DIR}/t-block.c + ${CORE_DIR}/t-char.c + ${CORE_DIR}/t-datatype.c + ${CORE_DIR}/t-date.c + ${CORE_DIR}/t-decimal.c + ${CORE_DIR}/t-event.c + ${CORE_DIR}/t-function.c + ${CORE_DIR}/t-gob.c + ${CORE_DIR}/t-image.c + ${CORE_DIR}/t-integer.c + ${CORE_DIR}/t-library.c + ${CORE_DIR}/t-logic.c + ${CORE_DIR}/t-map.c + ${CORE_DIR}/t-money.c + ${CORE_DIR}/t-object.c + ${CORE_DIR}/t-pair.c + ${CORE_DIR}/t-port.c + ${CORE_DIR}/t-routine.c + ${CORE_DIR}/t-string.c + ${CORE_DIR}/t-struct.c + ${CORE_DIR}/t-time.c + ${CORE_DIR}/t-tuple.c + ${CORE_DIR}/t-typeset.c + ${CORE_DIR}/t-varargs.c + ${CORE_DIR}/t-vector.c + ${CORE_DIR}/t-word.c + ${CORE_DIR}/u-compress.c + ${CORE_DIR}/u-md5.c + ${CORE_DIR}/u-parse.c + ${CORE_DIR}/u-sha1.c + ${CORE_DIR}/u-zlib.c + ) + +set (CORE_HEADER + ${TOP_SRC_DIR}/include/debugbreak.h + ${TOP_SRC_DIR}/include/mem-pools.h + ${TOP_SRC_DIR}/include/mem-series.h + ${TOP_SRC_DIR}/include/reb-c.h + ${TOP_SRC_DIR}/include/reb-config.h + ${TOP_SRC_DIR}/include/reb-defs.h + ${TOP_SRC_DIR}/include/reb-device.h + ${TOP_SRC_DIR}/include/reb-dtoa.h + ${TOP_SRC_DIR}/include/reb-event.h + ${TOP_SRC_DIR}/include/reb-ext.h + ${TOP_SRC_DIR}/include/reb-file.h + ${TOP_SRC_DIR}/include/reb-filereq.h + ${TOP_SRC_DIR}/include/reb-gob.h + ${TOP_SRC_DIR}/include/reb-host.h + ${TOP_SRC_DIR}/include/reb-math.h + ${TOP_SRC_DIR}/include/reb-net.h + ${TOP_SRC_DIR}/include/reb-struct.h + ${TOP_SRC_DIR}/include/sys-action.h + ${TOP_SRC_DIR}/include/sys-array.h + ${TOP_SRC_DIR}/include/sys-binary.h + ${TOP_SRC_DIR}/include/sys-bind.h + ${TOP_SRC_DIR}/include/sys-context.h + ${TOP_SRC_DIR}/include/sys-core.h + ${TOP_SRC_DIR}/include/sys-deci-funcs.h + ${TOP_SRC_DIR}/include/sys-deci.h + ${TOP_SRC_DIR}/include/sys-dec-to-char.h + ${TOP_SRC_DIR}/include/sys-do.h + ${TOP_SRC_DIR}/include/sys-ext.h + ${TOP_SRC_DIR}/include/sys-frame.h + ${TOP_SRC_DIR}/include/sys-function.h + ${TOP_SRC_DIR}/include/sys-globals.h + ${TOP_SRC_DIR}/include/sys-handle.h + ${TOP_SRC_DIR}/include/sys-indexor.h + ${TOP_SRC_DIR}/include/sys-int-funcs.h + ${TOP_SRC_DIR}/include/sys-map.h + ${TOP_SRC_DIR}/include/sys-pair.h + ${TOP_SRC_DIR}/include/sys-path.h + ${TOP_SRC_DIR}/include/sys-rebfrm.h + ${TOP_SRC_DIR}/include/sys-rebnod.h + ${TOP_SRC_DIR}/include/sys-rebser.h + ${TOP_SRC_DIR}/include/sys-rebval.h + ${TOP_SRC_DIR}/include/sys-scan.h + ${TOP_SRC_DIR}/include/sys-series.h + ${TOP_SRC_DIR}/include/sys-stack.h + ${TOP_SRC_DIR}/include/sys-state.h + ${TOP_SRC_DIR}/include/sys-string.h + ${TOP_SRC_DIR}/include/sys-trap.h + ${TOP_SRC_DIR}/include/sys-typeset.h + ${TOP_SRC_DIR}/include/sys-value.h + ${TOP_SRC_DIR}/include/sys-varargs.h + ${TOP_SRC_DIR}/include/sys-word.h + ${TOP_SRC_DIR}/include/sys-zlib.h +) + +file(GLOB CORE_C_FILES ${CORE_DIR}/*.c) +#message("CORE_C_FILES: ${CORE_C_FILES}") + +set(NATIVE_OUTPUT + ${TOP_GENERATED_SRC_DIR}/boot/tmp-natives.r + ${TOP_GENERATED_SRC_DIR}/boot/tmp-actions.r + ) + +set(NATIVE_DEPENDS + ${TOP_SRC_DIR}/boot/actions.r + ${TOP_SRC_DIR}/boot/types.r +) + +set(TOOL_DEPENDS + ${TOOLS_DIR}/common.r + ${TOOLS_DIR}/common-parsers.r +) + +#all files with REBNATIVE +foreach(CORE_C_FILE ${CORE_C_FILES}) + FILE(STRINGS ${CORE_C_FILE} HAS_NATIVE REGEX "^REBNATIVE\\([a-zA-Z_][a-zA-Z0-9_]*\\)$") + #message("HAS_NATIVE in ${CORE_C_FILE}: ${HAS_NATIVE}") + if(HAS_NATIVE) + #message("${CORE_C_FILE} has natives") + list(APPEND NATIVE_DEPENDS ${CORE_C_FILE}) + endif() +endforeach() +#message("NATIVE_DEPENDS: ${NATIVE_DEPENDS}") + +add_custom_command(OUTPUT + ${NATIVE_OUTPUT} + COMMAND ${REBOL} ${TOOLS_DIR}/make-natives.r OUTDIR=${TOP_GENERATED_SRC_DIR} + DEPENDS + ${REBOL} ${TOOLS_DIR}/make-natives.r + ${TOOL_DEPENDS} + ${NATIVE_DEPENDS} +) + +SET(GENERATED_HEADER + ${TOP_GENERATED_SRC_DIR}/include/tmp-funcs.h + ${TOP_GENERATED_SRC_DIR}/include/tmp-paramlists.h + ${TOP_GENERATED_SRC_DIR}/include/tmp-strings.h + ) +set(HEADER_OUTPUT + ${GENERATED_HEADER} + ${CORE_GENERATED_DIR}/tmp-symbols.c + ) +set(HEADER_DEPENDS + ${TOOLS_DIR}/common.r + ${TOOLS_DIR}/common-parsers.r + ${TOOLS_DIR}/form-header.r + ${CORE_C_FILES} + ${NATIVE_OUTPUT} + ${BOOT_OUTPUT} + ) + +add_custom_command(OUTPUT + ${HEADER_OUTPUT} + COMMAND ${REBOL} ${TOOLS_DIR}/make-headers.r OUTDIR=${TOP_GENERATED_SRC_DIR} + DEPENDS + ${TOOLS_DIR}/make-headers.r + ${TOOL_DEPENDS} + ${HEADER_DEPENDS} + ) + +set (BOOT_HEADER + ${TOP_GENERATED_SRC_DIR}/include/reb-types.h + ${TOP_GENERATED_SRC_DIR}/include/tmp-bootdefs.h + ${TOP_GENERATED_SRC_DIR}/include/tmp-sysobj.h + ${TOP_GENERATED_SRC_DIR}/include/reb-evtypes.h + ${TOP_GENERATED_SRC_DIR}/include/tmp-errnums.h + ${TOP_GENERATED_SRC_DIR}/include/tmp-error-funcs.h + ${TOP_GENERATED_SRC_DIR}/include/tmp-portmodes.h + ${TOP_GENERATED_SRC_DIR}/include/tmp-sysctx.h + ${TOP_GENERATED_SRC_DIR}/include/tmp-boot.h + ) +set(BOOT_OUTPUT + ${BOOT_HEADER} + ${CORE_GENERATED_DIR}/tmp-boot-block.c + ${CORE_GENERATED_DIR}/tmp-evaltypes.c + ${CORE_GENERATED_DIR}/tmp-maketypes.c + ${CORE_GENERATED_DIR}/tmp-comptypes.c + ) +set(BOOT_DEPENDS + # all of the files loaded by make-boot.r + ${TOP_SRC_DIR}/boot/version.r + ${TOP_SRC_DIR}/boot/types.r + ${TOP_SRC_DIR}/boot/errors.r + ${TOP_SRC_DIR}/boot/words.r + ${TOP_SRC_DIR}/boot/modes.r + ${TOP_GENERATED_SRC_DIR}/boot/tmp-actions.r + ${TOP_SRC_DIR}/boot/sysobj.r + ${TOP_SRC_DIR}/boot/platforms.r + + ${TOP_SRC_DIR}/mezz/boot-files.r + + ${TOP_GENERATED_SRC_DIR}/boot/tmp-natives.r + ${TOP_SRC_DIR}/boot/typespec.r + ${TOP_SRC_DIR}/boot/root.r + ${TOP_SRC_DIR}/boot/task.r + + # all of the files in boot-files.r + ${TOP_SRC_DIR}/mezz/base-constants.r + ${TOP_SRC_DIR}/mezz/base-funcs.r + ${TOP_SRC_DIR}/mezz/base-infix.r + ${TOP_SRC_DIR}/mezz/base-series.r + ${TOP_SRC_DIR}/mezz/base-files.r + ${TOP_SRC_DIR}/mezz/base-defs.r + + ${TOP_SRC_DIR}/mezz/sys-base.r + ${TOP_SRC_DIR}/mezz/sys-ports.r + ${TOP_SRC_DIR}/mezz/sys-codec.r # export to lib! + ${TOP_SRC_DIR}/mezz/sys-load.r + ${TOP_SRC_DIR}/mezz/sys-start.r + + ${TOP_SRC_DIR}/mezz/mezz-types.r + ${TOP_SRC_DIR}/mezz/mezz-func.r + ${TOP_SRC_DIR}/mezz/mezz-debug.r + ${TOP_SRC_DIR}/mezz/mezz-control.r + ${TOP_SRC_DIR}/mezz/mezz-save.r + ${TOP_SRC_DIR}/mezz/mezz-series.r + ${TOP_SRC_DIR}/mezz/mezz-files.r + ${TOP_SRC_DIR}/mezz/mezz-shell.r + ${TOP_SRC_DIR}/mezz/mezz-math.r + ${TOP_SRC_DIR}/mezz/mezz-help.r # move dump-obj! + ${TOP_SRC_DIR}/mezz/mezz-colors.r + ${TOP_SRC_DIR}/mezz/mezz-tail.r + ${TOP_SRC_DIR}/mezz/mezz-legacy.r + + ${TOP_SRC_DIR}/mezz/prot-http.r + ${TOP_SRC_DIR}/mezz/prot-tls.r + ) + +add_custom_command(OUTPUT + ${BOOT_OUTPUT} + COMMAND ${REBOL} ${TOOLS_DIR}/make-boot.r OS_ID=${R3_OS_ID} OUTDIR=${TOP_GENERATED_SRC_DIR} GIT_COMMIT=${GIT_COMMIT} + DEPENDS + ${TOOLS_DIR}/make-boot.r + ${TOOL_DEPENDS} + ${BOOT_DEPENDS} + ) + +set(REB_LIB_OUTPUT + ${TOP_GENERATED_SRC_DIR}/include/reb-lib.h + ${TOP_GENERATED_SRC_DIR}/include/reb-lib-lib.h + ) + +set(REB_LIB_DEPENDS + ${TOOLS_DIR}/common.r + ${TOOLS_DIR}/common-parsers.r + ${TOOLS_DIR}/form-header.r + ${TOP_SRC_DIR}/boot/version.r + ${CORE_DIR}/a-lib.c + ${CORE_DIR}/f-extension.c + ) + +add_custom_command(OUTPUT + ${REB_LIB_OUTPUT} + COMMAND ${REBOL} ${TOOLS_DIR}/make-reb-lib.r OUTDIR=${TOP_GENERATED_SRC_DIR} + DEPENDS + ${TOOLS_DIR}/make-reb-lib.r + ${TOOL_DEPENDS} + ${REB_LIB_DEPENDS} + ) + +set (GENERATED_CORE_SOURCE + ${CORE_GENERATED_DIR}/tmp-boot-block.c + ${CORE_GENERATED_DIR}/tmp-evaltypes.c + ${CORE_GENERATED_DIR}/tmp-maketypes.c + ${CORE_GENERATED_DIR}/tmp-comptypes.c + ${CORE_GENERATED_DIR}/tmp-symbols.c + ) + +set (GENERATED_CORE_HEADER + ${GENERATED_HEADER} + ${REB_LIB_OUTPUT} + ${BOOT_HEADER} + ${EXT_CORE_OUTPUT} + ) + +#HOST +set(EXT_OS_OUTPUT + ${TOP_GENERATED_SRC_DIR}/include/host-lib.h + ${TOP_GENERATED_SRC_DIR}/include/host-table.inc + ${TOP_GENERATED_SRC_DIR}/os/tmp-host-start.inc + ) + +macro(add_module name source other) + string (TOUPPER ${name} u_name) + string (TOLOWER ${name} l_name) + + set (GENERATED_MOD_${u_name}_HEADER + ${TOP_GENERATED_SRC_DIR}/include/tmp-mod-${l_name}-first.h + ${TOP_GENERATED_SRC_DIR}/include/tmp-mod-${l_name}-last.h + ) + add_custom_command(OUTPUT + ${GENERATED_MOD_${u_name}_HEADER} + COMMAND ${REBOL} ${TOOLS_DIR}/make-ext-natives.r SRC=${source} MODULE=${name} OUTDIR=${TOP_GENERATED_SRC_DIR} + DEPENDS + ${TOOL_DEPENDS} + ${TOOLS_DIR}/make-ext-natives.r + ${source} + ) + + set (MODULE_${u_name}_SOURCE ${source} ${other}) + set_source_files_properties( + ${GENERATED_MOD_${u_name}_HEADER} + PROPERTIES + HEADER_FILE_ONLY TRUE + ) +endmacro (add_module) + +macro(add_extension libname name can_be_module source modules init_script includes definitions) + string (TOUPPER ${name} u_name) + string (TOLOWER ${name} l_name) + if (${can_be_module}) + set (R3_EXT_${u_name} "Y" CACHE STRING "Build with extension ${l_name} ([Y], M or N)") + else () + option (R3_EXT_${u_name} "Build with extension ${l_name}" TRUE) + endif () + + if ((${can_be_module} + AND (R3_EXT_${u_name} STREQUAL "M" + OR R3_EXT_${u_name} STREQUAL "Y") + ) OR ((NOT ${can_be_module}) AND R3_EXT_${u_name})) + set(MODULE_SOURCES "") + set(GENERATED_MODULE_HEADERS "") + foreach (mod ${modules}) + string (TOUPPER ${mod} u_mod_name) + list(APPEND MODULE_SOURCES "${MODULE_${u_mod_name}_SOURCE}") + list(APPEND GENERATED_MODULE_HEADERS "${GENERATED_MOD_${u_mod_name}_HEADER}") + endforeach(mod) + + set(R3_EXT_${u_name}_SOURCES + ${source} ${MODULE_SOURCES} ${GENERATED_MODULE_HEADERS}) + + set_source_files_properties( + ${GENERATED_MODULE_HEADERS} + PROPERTIES + HEADER_FILE_ONLY TRUE + ) + + message("init_script: ${init_script}") + if (NOT "${init_script}" STREQUAL "") + get_filename_component(script_ext ${init_script} EXT) + get_filename_component(script_name ${init_script} NAME) + get_filename_component(script_dir ${init_script} DIRECTORY) + message("script ext: ${script_ext}") + string(REPLACE ${script_ext} ".inc" INIT_SCRIPT_C ${script_name}) + string(CONCAT DEST ${script_dir} "/tmp-" ${INIT_SCRIPT_C}) + message("Generating: ${DEST}") + add_custom_command(OUTPUT + ${DEST} + COMMAND ${REBOL} ${TOOLS_DIR}/make-ext-init.r SRC=${init_script} DEST=${DEST} + DEPENDS + ${TOOLS_DIR}/make-ext-init.r + ${TOOL_DEPENDS} + ) + + list(APPEND R3_EXT_${u_name}_SOURCES ${DEST}) + set_source_files_properties(${DEST} PROPERTIES HEADER_FILE_ONLY TRUE) + endif () + + if (R3_CPP) + set_property(SOURCE ${R3_EXT_${u_name}_SOURCES} PROPERTY LANGUAGE CXX) + endif (R3_CPP) + + set(defs "${definitions}") #it seems that macro input can't be modified, so make a copy here + if (R3_EXT_${u_name} STREQUAL "M") + if (NOT ${can_be_module}) #sanity check + message (FATAL "Wrong configuration: ${name} can not be an external extension") + endif () + message("Building ${name} as an external extension") + add_library(${libname} SHARED ${R3_EXT_${u_name}_SOURCES}) + target_link_libraries(${libname} r3-core) + add_dependencies(${libname} r3-core) + list(APPEND defs "EXT_DLL") + elseif ((${can_be_module} AND R3_EXT_${u_name} STREQUAL "Y") + OR ((NOT ${can_be_module}) AND R3_EXT_${u_name})) + message("Building ${name} into the executable") + add_library(${libname} OBJECT ${R3_EXT_${u_name}_SOURCES}) + + #setup the dependency for the files included by sys-core.h + set_source_files_properties( + ${source} + PROPERTIES + OBJECT_DEPENDS "${BOOT_HEADER};${GENERATED_HEADER};${EXT_OS_OUTPUT}" + ) + + list(APPEND BOOT_EXTENSIONS ${name}) + list(APPEND EXT_OBJECTS "$") + endif () + target_include_directories(${libname} PUBLIC ${includes}) + target_compile_definitions(${libname} PUBLIC ${defs}) + else () + message("${name} is not built") + endif () +endmacro(add_extension) + + +#<-------------- modules (put all add_module in this section) ---------------> + +# CRYPT extension +# +# Crypt has to be built-in because TLS (loaded by the host at boot time) +# requires it, or RC4 and RSA natives will not be bound properly. +# +add_module( + Crypt + "${EXT_DIR}/crypt/mod-crypt.c" + "${EXT_DIR}/crypt/aes/aes.c;${EXT_DIR}/crypt/bigint/bigint.c;${EXT_DIR}/crypt/dh/dh.c;${EXT_DIR}/crypt/rc4/rc4.c;${EXT_DIR}/crypt/rsa/rsa.c;${EXT_DIR}/crypt/sha256/sha256.c" +) +add_extension( + r3-crypt Crypt FALSE "${EXT_DIR}/crypt/ext-crypt.c" "Crypt" "${EXT_DIR}/crypt/ext-crypt-init.reb" + "${TOP_GENERATED_SRC_DIR}/include;${TOP_SRC_DIR}/include;${FFI_INCLUDE_DIRS};${INC}" + "${COMMON_MACROS}" +) +if (TO_WINDOWS AND ${R3_EXT_CRYPT} STREQUAL "M") + target_link_libraries(r3-crypt "Ws2_32.lib") +endif () + +# CALL extension +# +add_module( + Process + "${EXT_DIR}/process/mod-process.c" + "" +) +add_extension( + r3-process Process TRUE "${EXT_DIR}/process/ext-process.c" "Process" "${EXT_DIR}/process/ext-process-init.reb" + "${TOP_GENERATED_SRC_DIR}/include;${TOP_SRC_DIR}/include;${FFI_INCLUDE_DIRS};${INC}" + "${COMMON_MACROS}" +) + +# PNG extension +# +add_module( + LodePNG + "${EXT_DIR}/png/mod-lodepng.c" + "${EXT_DIR}/png/lodepng.c;" +) +add_module( + uPNG + "${EXT_DIR}/png/u-png.c" + "" +) +add_extension( + r3-png PNG TRUE "${EXT_DIR}/png/ext-png.c" "LodePNG;uPNG" "" + "${TOP_GENERATED_SRC_DIR}/include;${TOP_SRC_DIR}/include;${FFI_INCLUDE_DIRS};${INC}" + "${COMMON_MACROS}" +) + +# GIF extension +# +add_module( + GIF + "${EXT_DIR}/gif/mod-gif.c" + "" +) +add_extension( + r3-gif GIF TRUE "${EXT_DIR}/gif/ext-gif.c" "GIF" "" + "${TOP_GENERATED_SRC_DIR}/include;${TOP_SRC_DIR}/include;${FFI_INCLUDE_DIRS};${INC}" + "${COMMON_MACROS}" +) + +# BMP extension +# +add_module( + BMP + "${EXT_DIR}/bmp/mod-bmp.c" + "" +) +add_extension( + r3-bmp BMP TRUE "${EXT_DIR}/bmp/ext-bmp.c" "BMP" "" + "${TOP_GENERATED_SRC_DIR}/include;${TOP_SRC_DIR}/include;${FFI_INCLUDE_DIRS};${INC}" + "${COMMON_MACROS}" +) + +# JPG extension +# +add_module( + JPG + "${EXT_DIR}/jpg/mod-jpg.c" + "${EXT_DIR}/jpg/u-jpg.c" +) +add_extension( + r3-jpg JPG TRUE "${EXT_DIR}/jpg/ext-jpg.c" "JPG" "" + "${TOP_GENERATED_SRC_DIR}/include;${TOP_SRC_DIR}/include;${FFI_INCLUDE_DIRS};${INC}" + "${COMMON_MACROS}" +) + +# Locale extension +# +add_module( + Locale + "${EXT_DIR}/locale/mod-locale.c" + "" +) +add_extension( + r3-locale Locale TRUE "${EXT_DIR}/locale/ext-locale.c" "Locale" "${EXT_DIR}/locale/ext-locale-init.reb" + "${TOP_GENERATED_SRC_DIR}/include;${TOP_SRC_DIR}/include;${FFI_INCLUDE_DIRS};${INC}" + "${COMMON_MACROS}" +) + +#<------------- end of modules ---------> + +string(REPLACE ";" "," BOOT_EXTENSION_LIST "${BOOT_EXTENSIONS}") +SET(BOOT_MODULE_HEADER ${TOP_GENERATED_SRC_DIR}/include/tmp-boot-extensions.h) +add_custom_command(OUTPUT + ${BOOT_MODULE_HEADER} + COMMAND ${REBOL} ${TOOLS_DIR}/make-boot-ext-header.r EXTENSIONS=${BOOT_EXTENSION_LIST} OUTDIR=${TOP_GENERATED_SRC_DIR} + DEPENDS + ${TOOL_DEPENDS} + ${TOOLS_DIR}/make-boot-ext-header.r + ) + +if (R3_WITH_TCC) + enable_language(ASM) + if (XCODE) #Xcode has problem with EXCLUDE_FROM_ALL + add_subdirectory(${TCC_DIR} ${CMAKE_CURRENT_BINARY_DIR}/tcc) + else () + add_subdirectory(${TCC_DIR} ${CMAKE_CURRENT_BINARY_DIR}/tcc EXCLUDE_FROM_ALL) + #add_subdirectory(${TCC_DIR} ${CMAKE_CURRENT_BINARY_DIR}/tcc) + endif () + list (APPEND INC ${TCC_DIR}) + + set (TCC_ARGS "") + foreach (macro ${COMMON_MACROS}) + list(APPEND TCC_ARGS "-D${macro}") + endforeach(macro) + foreach (macro ${FFI_INCLUDE_DIRS}) + list(APPEND TCC_ARGS "-I${macro}") + endforeach(macro) + + string (TOLOWER "${CMAKE_BUILD_TYPE}" build_type) + + if ("${build_type}" STREQUAL "debug") + set(tcc_c_flags "${CMAKE_C_FLAGS_DEBUG}") + elseif ("${build_type}" STREQUAL "minsizerel") + set(tcc_c_flags "${CMAKE_C_FLAGS_MINSIZEREL}") + elseif ("${build_type}" STREQUAL "relwithdebinfo") + set(tcc_c_flags "${CMAKE_C_FLAGS_RELWITHDEBINFO}") + elseif ("${build_type}" STREQUAL "release") + set(tcc_c_flags "${CMAKE_C_FLAGS_RELEASE}") + else () + message (FATAL_ERROR "Unknown CMAKE_BUILD_TYPE: ${build_type}") + endif () + + #flags needs to be a list + string(REGEX MATCH "[-/][DI][ \t]*[^ \t]+" interested "${tcc_c_flags}") + string(REGEX REPLACE "[-/]([DI])[ \t]*" "-\\1" no_spaces "${interested}") + + string(REGEX REPLACE "[ \t]+" ";" tcc_c_flag_list "${no_spaces}") + #message("cflags: ${tcc_c_flag_list}") + + set (SYS_CORE_PREP ${TOP_GENERATED_SRC_DIR}/include/sys-core.i) + set (SYS_CORE_HEADER ${TOP_SRC_DIR}/include/sys-core.h) + + set (TCC_DEPENDS "libtcc") + message("HOST: ${CMAKE_HOST_SYSTEM} SYSTEM: ${CMAKE_SYSTEM}") + if ("${CMAKE_HOST_SYSTEM}" STREQUAL "${CMAKE_SYSTEM}") #native build + # + # Using `get_target_property(TCC_EXE tcc LOCATION)` causes an error + # Explained at https://cmake.org/cmake/help/v3.0/policy/CMP0026.html + # + set(TCC_EXE "tcc") + + list(APPEND TCC_DEPENDS "tcc") + else () #cross-compile + set(TCC_EXE ${CMAKE_CURRENT_BINARY_DIR}/cross-tcc) + if (NOT EXISTS ${TCC_EXE}) + message (FATAL_ERROR "Can't find tcc at: ${TCC_EXE}") + endif () + list(APPEND tcc_c_flag_list "-nostdlib") + endif () + list(APPEND TCC_DEPENDS "r3_libtcc1") + + if (TO_WINDOWS) + list(APPEND tcc_c_flag_list "-I${TCC_DIR}/win32/include") + list(APPEND tcc_c_flag_list "-DPVAR=TVAR;-DTVAR=extern __attribute__((dllimport))") + elseif(TO_ANDROID) + list(APPEND tcc_c_flag_list "-I${ANDROID_SYSROOT}/usr/include") + + #FIXME: pretend to be GCC, or it will fail: + #In file included from ../src/include/sys-core.h:54: + #In file included from /opt/android-ndk/platforms/android-16/arch-arm/usr/include/stdlib.h:31: + #/opt/android-ndk/platforms/android-16/arch-arm/usr/include/sys/cdefs.h:277: error: #error "No function renaming possible" + #Not sure what problem it could cause, but it worked for %tests/misc/fib.r + list(APPEND tcc_c_flag_list "-D__GNUC__") + endif () + + list(APPEND tcc_c_flag_list "-DREN_C_STDIO_OK;-UHAVE_ASAN_INTERFACE_H") #allow stdio.h + message("cflags: ${tcc_c_flags}") + + add_custom_command(OUTPUT + ${SYS_CORE_PREP} + COMMAND ${TCC_EXE} -E -dD -o ${SYS_CORE_PREP} ${SYS_CORE_HEADER} -I${TCC_DIR}/include -I${TOP_GENERATED_SRC_DIR}/include -I${TOP_SRC_DIR}/include ${TCC_ARGS} ${tcc_c_flag_list} + DEPENDS + ${TCC_DEPENDS} + ${TOOLS_DIR}/make-embedded-header.r + ${BOOT_OUTPUT} + ${HEADER_OUTPUT} + ${SYS_CORE_HEADER} + ${TOP_GENERATED_SRC_DIR}/include/host-lib.h + ${TOP_GENERATED_SRC_DIR}/include/tmp-funcs.h + VERBATIM + ) + + add_custom_command(OUTPUT + ${CORE_GENERATED_DIR}/e-embedded-header.c + COMMAND ${REBOL} ${TOOLS_DIR}/make-embedded-header.r OUTDIR=${TOP_GENERATED_SRC_DIR} + DEPENDS + ${TOOLS_DIR}/make-embedded-header.r + ${SYS_CORE_PREP} + ) + + list (APPEND GENERATED_CORE_SOURCE ${CORE_GENERATED_DIR}/e-embedded-header.c) + +endif() + +#HOST +set_source_files_properties( + ${TOP_GENERATED_SRC_DIR}/include/host-table.inc + ${GENERATED_EXT_HEADER} + ${BOOT_MODULE_HEADER} + PROPERTIES + HEADER_FILE_ONLY TRUE + ) +set_source_files_properties( + ${CORE_HEADER} + ${GENERATED_CORE_HEADER} + PROPERTIES + HEADER_FILE_ONLY TRUE + ) + +set(EXT_OS_DEPENDS + ${TOP_SRC_DIR}/boot/version.r + ${TOOLS_DIR}/common.r + ${TOOLS_DIR}/common-parsers.r + ${TOOLS_DIR}/form-header.r + ${TOOLS_DIR}/file-base.r + ${OS_SOURCE} + ) + +if (R3_BUILD_VIEW) + add_custom_command(OUTPUT + ${EXT_OS_OUTPUT} + COMMAND ${REBOL} ${TOOLS_DIR}/make-os-ext.r OS_ID=${R3_OS_ID} GFX=1 OUTDIR=${TOP_GENERATED_SRC_DIR} + COMMAND ${REBOL} ${TOOLS_DIR}/make-host-init.r OS_ID=${R3_OS_ID} OUTDIR=${TOP_GENERATED_SRC_DIR} + DEPENDS + ${TOOLS_DIR}/make-os-ext.r + ${TOOLS_DIR}/make-host-init.r + ${TOP_SRC_DIR}/os/host-start.r + ${TOOL_DEPENDS} + ${EXT_OS_DEPENDS} + ) +else () + add_custom_command(OUTPUT + ${EXT_OS_OUTPUT} + COMMAND ${REBOL} ${TOOLS_DIR}/make-os-ext.r OS_ID=${R3_OS_ID} OUTDIR=${TOP_GENERATED_SRC_DIR} + COMMAND ${REBOL} ${TOOLS_DIR}/make-host-init.r OS_ID=${R3_OS_ID} OUTDIR=${TOP_GENERATED_SRC_DIR} + DEPENDS + ${TOOLS_DIR}/make-os-ext.r + ${TOOLS_DIR}/make-host-init.r + ${TOP_SRC_DIR}/os/host-start.r + ${TOOL_DEPENDS} + ${EXT_OS_DEPENDS} + ) +endif () + +set (OS_SOURCE + ${OS_DIR}/host-main.c + ${OS_DIR}/host-device.c + ${OS_DIR}/host-stdio.c + ${OS_DIR}/host-table.c + ${OS_DIR}/dev-net.c + ${OS_DIR}/dev-dns.c + ${OS_DIR}/generic/host-memory.c + ${CORE_PLATFORM_SOURCE} + ) + +set (GENERATED_OS_SOURCE + ${EXT_OS_SOURCE} + ${BOOT_MODULE_HEADER} + ) + +if(NOT (MSVC_IDE OR XCODE)) + add_custom_target(clean-generated COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/clean-generated.cmake) + add_custom_target(clean-all + COMMAND ${CMAKE_BUILD_TOOL} clean + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/clean-generated.cmake + ) +endif() + +#get rid of '-rdynamic' option at link stage +#Do not add flags to export symbols from executables without the ENABLE_EXPORTS target property. +#https://cmake.org/cmake/help/v3.4/policy/CMP0065.html +if (POLICY CMP0065) + cmake_policy(SET CMP0065 NEW) +else () + if (LINUX) + if (CMAKE_COMILER_IS_GNUCC) + set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + endif() + if (CMAKE_COMILER_IS_GNUCXX) + set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "") + endif () + endif (LINUX) +endif () + +add_library(libr3_core_core OBJECT ${CORE_SOURCE} ${GENERATED_CORE_SOURCE} ${CORE_HEADER} ${GENERATED_CORE_HEADER}) + +set (HOST_SOURCE + ${OS_SOURCE} + ${GENERATED_OS_SOURCE} + ) + +if(R3_WITH_TCC) + add_executable(r3-core $ $ $ ${EXT_OBJECTS} ${HOST_SOURCE}) +else() + add_executable(r3-core $ ${EXT_OBJECTS} ${HOST_SOURCE}) +endif() + +set_target_properties(r3-core PROPERTIES ENABLE_EXPORTS TRUE) + +target_include_directories(libr3_core_core PUBLIC + ${TOP_GENERATED_SRC_DIR}/include + ${TOP_SRC_DIR}/include + ${FFI_INCLUDE_DIRS} + ${INC} + ) +target_include_directories(r3-core PUBLIC + ${TOP_GENERATED_SRC_DIR}/include + ${TOP_GENERATED_SRC_DIR}/os + ${TOP_SRC_DIR}/include + ${FFI_INCLUDE_DIRS} + ${INC} + ) +target_compile_definitions(libr3_core_core PUBLIC + REB_API + REB_CORE + ${COMMON_MACROS}) + +if (TO_WINDOWS) + target_compile_definitions(libr3_core_core PRIVATE ZLIB_INTERNAL) + + #WINSYS_WIN32 is needed for dev-event.c + target_compile_definitions(r3-core PUBLIC REB_CORE WINSYS_WIN32 ${COMMON_MACROS}) +else () + target_compile_definitions(r3-core PUBLIC REB_CORE ${COMMON_MACROS}) +endif () +target_compile_definitions(r3-core PRIVATE REB_EXE) + +set (EXTRA_C_FLAGS "") +set (R3_CORE_SOURCE + ${CORE_SOURCE} + ${GENERATED_CORE_SOURCE} + ${HOST_SOURCE} + ) +# Build as C by default, but there are extra checks if you build as C++11 +# (fewer interesting checks if you only build as C++98) +# +if (R3_CPP) + set_property(SOURCE ${R3_CORE_SOURCE} PROPERTY LANGUAGE CXX) + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + #to get rid of this clang warning: + #warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated + set(EXTRA_C_FLAGS "${EXTRA_C_FLAGS} -x c++") + #warning: 50 enumeration values not handled in switch: 'REB_0', 'REB_FUNCTION', 'REB_BAR'... [-Wswitch] + set(EXTRA_C_FLAGS "${EXTRA_C_FLAGS} -Wno-switch") + endif () + set_target_properties(r3-core PROPERTIES LINKER_LANGUAGE CXX) +else () + if ("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + set(EXTRA_C_FLAGS "${EXTRA_C_FLAGS} -Wno-switch") + endif() +endif() +message("Extra compile flags: ${EXTRA_C_FLAGS}") +set_property(SOURCE ${R3_CORE_SOURCE} PROPERTY COMPILE_FLAGS ${EXTRA_C_FLAGS}) + +if (DEFINED LINK_FLAGS) + set_target_properties(r3-core PROPERTIES LINK_FLAGS ${LINK_FLAGS}) +endif() + +if (TO_ANDROID5) + list(APPEND LIBS "-fPIC -fPIE") +endif () + +target_link_libraries(r3-core ${LIBS} ${FFI_LIBRARIES}) diff --git a/make/Toolchain-cross-mingw32-linux.cmake b/make/Toolchain-cross-mingw32-linux.cmake new file mode 100644 index 0000000000..92e729b5e5 --- /dev/null +++ b/make/Toolchain-cross-mingw32-linux.cmake @@ -0,0 +1,29 @@ +# the name of the target operating system +SET(CMAKE_SYSTEM_NAME Windows) +SET(CMAKE_SYSTEM_PROCESSOR "X86") + +# Choose an appropriate compiler prefix + +# for classical mingw32 +# see http://www.mingw.org/ +#set(COMPILER_PREFIX "i586-mingw32msvc") + +# for 32 or 64 bits mingw-w64 +# see http://mingw-w64.sourceforge.net/ +set(COMPILER_PREFIX "i686-w64-mingw32") + +# which compilers to use for C and C++ +find_program(CMAKE_RC_COMPILER NAMES ${COMPILER_PREFIX}-windres) +find_program(CMAKE_C_COMPILER NAMES ${COMPILER_PREFIX}-gcc) +find_program(CMAKE_CXX_COMPILER NAMES ${COMPILER_PREFIX}-g++) + + +# here is the target environment located +SET(CMAKE_FIND_ROOT_PATH /usr/${COMPILER_PREFIX} ${USER_ROOT_PATH}) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, search +# programs in the host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/make/Toolchain-cross-mingw64-linux.cmake b/make/Toolchain-cross-mingw64-linux.cmake new file mode 100644 index 0000000000..87dbba39c8 --- /dev/null +++ b/make/Toolchain-cross-mingw64-linux.cmake @@ -0,0 +1,27 @@ +# the name of the target operating system +SET(CMAKE_SYSTEM_NAME Windows) +SET(CMAKE_SYSTEM_PROCESSOR "x86_64") + +# Choose an appropriate compiler prefix + +# for classical mingw32 +# see http://www.mingw.org/ +#set(COMPILER_PREFIX "i586-mingw32msvc") + +# for 32 or 64 bits mingw-w64 +# see http://mingw-w64.sourceforge.net/ +set(COMPILER_PREFIX "x86_64-w64-mingw32") + +# which compilers to use for C and C++ +find_program(CMAKE_RC_COMPILER NAMES ${COMPILER_PREFIX}-windres) +find_program(CMAKE_C_COMPILER NAMES ${COMPILER_PREFIX}-gcc) +find_program(CMAKE_CXX_COMPILER NAMES ${COMPILER_PREFIX}-g++) + +SET(CMAKE_FIND_ROOT_PATH /usr/${COMPILER_PREFIX} ${USER_ROOT_PATH}) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, search +# programs in the host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/make/makefile b/make/makefile deleted file mode 100644 index fa91ce0ea3..0000000000 --- a/make/makefile +++ /dev/null @@ -1,497 +0,0 @@ -# REBOL Makefile -- Generated by make-make.r (do not edit) on 5-Oct-2013/20:20:10+2:00 -# This makefile is intentional kept simple to make builds possible on -# a wider range of target platforms. - -# To regenerate this file: -# make make - -# To generate this file for a different platform, check systems.r file -# and provide an OS_ID (from the systems table). Linux 2.5 for example: -# make make OS_ID=0.4.3 - -# To cross compile using a different toolchain and include files: -# $TOOLS - should point to bin where gcc is found -# $INCL - should point to the dir for includes -# Example make: -# make TOOLS=~/amiga/amiga/bin/ppc-amigaos- INCL=/SDK/newlib/include - -# For the build toolchain: -CC= $(TOOLS)gcc -NM= $(TOOLS)nm -STRIP= $(TOOLS)strip - -# CP allows different copy progs: -CP= cp -# LS allows different ls progs: -LS= ls -l -# UP - some systems do not use ../ -UP= .. -# CD - some systems do not use ./ -CD= ./ -# Special tools: -T= $(UP)/src/tools -# Paths used by make: -S= ../src -R= $S/core - -INCL ?= . -I= -I$(INCL) -I$S/include/ - -TO_OS?= TO_LINUX -OS_ID?= 0.4.4 -BIN_SUFFIX= -RAPI_FLAGS= -O2 -fvisibility=hidden -m32 -HOST_FLAGS= -DREB_EXE -O2 -fvisibility=hidden -m32 -D_FILE_OFFSET_BITS=64 -RLIB_FLAGS= - -# Flags for core and for host: -RFLAGS= -c -D$(TO_OS) -DREB_API $(RAPI_FLAGS) $I -HFLAGS= -c -D$(TO_OS) -DREB_CORE $(HOST_FLAGS) $I -CLIB= -ldl -m32 -lm - -# REBOL is needed to build various include files: -REBOL_TOOL= r3-make -REBOL= $(CD)$(REBOL_TOOL) -qs - -# For running tests, ship, build, etc. -R3= $(CD)r3$(BIN_SUFFIX) -qs - -### Build targets: -top: - $(MAKE) r3$(BIN_SUFFIX) - -update: - -cd $(UP)/; cvs -q update src - -make: - $(REBOL) $T/make-make.r $(OS_ID) - -clean: - @-rm -rf libr3.so objs/ - -all: - $(MAKE) clean - $(MAKE) prep - $(MAKE) r3$(BIN_SUFFIX) - $(MAKE) lib - $(MAKE) host$(BIN_SUFFIX) - -prep: - $(REBOL) $T/make-headers.r - $(REBOL) $T/make-boot.r $(OS_ID) - $(REBOL) $T/make-host-init.r - $(REBOL) $T/make-os-ext.r # ok, but not always - $(REBOL) $T/make-host-ext.r - $(REBOL) $T/make-reb-lib.r - -### Provide more info if make fails due to no local Rebol build tool: -tmps: $S/include/tmp-bootdefs.h - -$S/include/tmp-bootdefs.h: $(REBOL_TOOL) - $(MAKE) prep - -$(REBOL_TOOL): - @echo - @echo "*** ERROR: Missing $(REBOL_TOOL) to build various tmp files." - @echo "*** Download Rebol 3 and copy it here as $(REBOL_TOOL), then" - @echo "*** make prep. Or, make prep on some other machine and copy" - @echo "*** the src/include files here. See README for details." - @echo - false - -### Post build actions -purge: - -rm libr3.* - -rm host$(BIN_SUFFIX) - $(MAKE) lib - $(MAKE) host$(BIN_SUFFIX) - -test: - $(CP) r3$(BIN_SUFFIX) $(UP)/src/tests/ - $(R3) $S/tests/test.r - -install: - sudo cp r3$(BIN_SUFFIX) /usr/local/bin - -ship: - $(R3) $S/tools/upload.r - -build: libr3.so - $(R3) $S/tools/make-build.r - -cln: - rm libr3.* r3.o - -check: - $(STRIP) -s -o r3.s r3$(BIN_SUFFIX) - $(STRIP) -x -o r3.x r3$(BIN_SUFFIX) - $(STRIP) -X -o r3.X r3$(BIN_SUFFIX) - $(LS) r3* - -OBJS = objs/a-constants.o objs/a-globals.o objs/a-lib.o objs/b-boot.o \ - objs/b-init.o objs/c-do.o objs/c-error.o objs/c-frame.o \ - objs/c-function.o objs/c-port.o objs/c-task.o objs/c-word.o \ - objs/d-crash.o objs/d-dump.o objs/d-print.o objs/f-blocks.o \ - objs/f-deci.o objs/f-dtoa.o objs/f-enbase.o objs/f-extension.o \ - objs/f-math.o objs/f-modify.o objs/f-qsort.o objs/f-random.o \ - objs/f-round.o objs/f-series.o objs/f-stubs.o objs/l-scan.o \ - objs/l-types.o objs/m-gc.o objs/m-pools.o objs/m-series.o \ - objs/n-control.o objs/n-data.o objs/n-io.o objs/n-loop.o \ - objs/n-math.o objs/n-sets.o objs/n-strings.o objs/n-system.o \ - objs/p-clipboard.o objs/p-console.o objs/p-dir.o objs/p-dns.o \ - objs/p-event.o objs/p-file.o objs/p-net.o objs/s-cases.o \ - objs/s-crc.o objs/s-file.o objs/s-find.o objs/s-make.o \ - objs/s-mold.o objs/s-ops.o objs/s-trim.o objs/s-unicode.o \ - objs/t-bitset.o objs/t-block.o objs/t-char.o objs/t-datatype.o \ - objs/t-date.o objs/t-decimal.o objs/t-event.o objs/t-function.o \ - objs/t-gob.o objs/t-image.o objs/t-integer.o objs/t-logic.o \ - objs/t-map.o objs/t-money.o objs/t-none.o objs/t-object.o \ - objs/t-pair.o objs/t-port.o objs/t-string.o objs/t-time.o \ - objs/t-tuple.o objs/t-typeset.o objs/t-utype.o objs/t-vector.o \ - objs/t-word.o objs/u-bmp.o objs/u-compress.o objs/u-dialect.o \ - objs/u-gif.o objs/u-jpg.o objs/u-md5.o objs/u-parse.o \ - objs/u-png.o objs/u-sha1.o objs/u-zlib.o - -HOST = objs/host-main.o objs/host-args.o objs/host-device.o objs/host-stdio.o \ - objs/dev-net.o objs/dev-dns.o objs/host-lib.o objs/host-readline.o \ - objs/dev-stdio.o objs/dev-event.o objs/dev-file.o - - -# Directly linked r3 executable: -r3$(BIN_SUFFIX): tmps objs $(OBJS) $(HOST) - $(CC) -o r3$(BIN_SUFFIX) $(OBJS) $(HOST) $(CLIB) - $(STRIP) r3$(BIN_SUFFIX) - -$(NM) -a r3$(BIN_SUFFIX) - $(LS) r3$(BIN_SUFFIX) - -objs: - mkdir -p objs - -lib: libr3.so - -# PUBLIC: Shared library: -# NOTE: Did not use "-Wl,-soname,libr3.so" because won't find .so in local dir. -libr3.so: $(OBJS) - $(CC) -o libr3.so -shared $(OBJS) $(CLIB) - $(STRIP) libr3.so - -$(NM) -D libr3.so - -$(NM) -a libr3.so | grep "Do_" - $(LS) libr3.so - -# PUBLIC: Host using the shared lib: -host$(BIN_SUFFIX): $(HOST) - $(CC) -o host$(BIN_SUFFIX) $(HOST) libr3.so $(CLIB) - $(STRIP) host$(BIN_SUFFIX) - $(LS) host$(BIN_SUFFIX) - echo "export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH" - -### File build targets: -b-boot.c: $(SRC)/boot/boot.r - $(REBOL) -sqw $(SRC)/tools/make-boot.r - -objs/a-constants.o: $R/a-constants.c - $(CC) $R/a-constants.c $(RFLAGS) -o objs/a-constants.o - -objs/a-globals.o: $R/a-globals.c - $(CC) $R/a-globals.c $(RFLAGS) -o objs/a-globals.o - -objs/a-lib.o: $R/a-lib.c - $(CC) $R/a-lib.c $(RFLAGS) -o objs/a-lib.o - -objs/b-boot.o: $R/b-boot.c - $(CC) $R/b-boot.c $(RFLAGS) -o objs/b-boot.o - -objs/b-init.o: $R/b-init.c - $(CC) $R/b-init.c $(RFLAGS) -o objs/b-init.o - -objs/c-do.o: $R/c-do.c - $(CC) $R/c-do.c $(RFLAGS) -o objs/c-do.o - -objs/c-error.o: $R/c-error.c - $(CC) $R/c-error.c $(RFLAGS) -o objs/c-error.o - -objs/c-frame.o: $R/c-frame.c - $(CC) $R/c-frame.c $(RFLAGS) -o objs/c-frame.o - -objs/c-function.o: $R/c-function.c - $(CC) $R/c-function.c $(RFLAGS) -o objs/c-function.o - -objs/c-port.o: $R/c-port.c - $(CC) $R/c-port.c $(RFLAGS) -o objs/c-port.o - -objs/c-task.o: $R/c-task.c - $(CC) $R/c-task.c $(RFLAGS) -o objs/c-task.o - -objs/c-word.o: $R/c-word.c - $(CC) $R/c-word.c $(RFLAGS) -o objs/c-word.o - -objs/d-crash.o: $R/d-crash.c - $(CC) $R/d-crash.c $(RFLAGS) -o objs/d-crash.o - -objs/d-dump.o: $R/d-dump.c - $(CC) $R/d-dump.c $(RFLAGS) -o objs/d-dump.o - -objs/d-print.o: $R/d-print.c - $(CC) $R/d-print.c $(RFLAGS) -o objs/d-print.o - -objs/f-blocks.o: $R/f-blocks.c - $(CC) $R/f-blocks.c $(RFLAGS) -o objs/f-blocks.o - -objs/f-deci.o: $R/f-deci.c - $(CC) $R/f-deci.c $(RFLAGS) -o objs/f-deci.o - -objs/f-dtoa.o: $R/f-dtoa.c - $(CC) $R/f-dtoa.c $(RFLAGS) -o objs/f-dtoa.o - -objs/f-enbase.o: $R/f-enbase.c - $(CC) $R/f-enbase.c $(RFLAGS) -o objs/f-enbase.o - -objs/f-extension.o: $R/f-extension.c - $(CC) $R/f-extension.c $(RFLAGS) -o objs/f-extension.o - -objs/f-math.o: $R/f-math.c - $(CC) $R/f-math.c $(RFLAGS) -o objs/f-math.o - -objs/f-modify.o: $R/f-modify.c - $(CC) $R/f-modify.c $(RFLAGS) -o objs/f-modify.o - -objs/f-qsort.o: $R/f-qsort.c - $(CC) $R/f-qsort.c $(RFLAGS) -o objs/f-qsort.o - -objs/f-random.o: $R/f-random.c - $(CC) $R/f-random.c $(RFLAGS) -o objs/f-random.o - -objs/f-round.o: $R/f-round.c - $(CC) $R/f-round.c $(RFLAGS) -o objs/f-round.o - -objs/f-series.o: $R/f-series.c - $(CC) $R/f-series.c $(RFLAGS) -o objs/f-series.o - -objs/f-stubs.o: $R/f-stubs.c - $(CC) $R/f-stubs.c $(RFLAGS) -o objs/f-stubs.o - -objs/l-scan.o: $R/l-scan.c - $(CC) $R/l-scan.c $(RFLAGS) -o objs/l-scan.o - -objs/l-types.o: $R/l-types.c - $(CC) $R/l-types.c $(RFLAGS) -o objs/l-types.o - -objs/m-gc.o: $R/m-gc.c - $(CC) $R/m-gc.c $(RFLAGS) -o objs/m-gc.o - -objs/m-pools.o: $R/m-pools.c - $(CC) $R/m-pools.c $(RFLAGS) -o objs/m-pools.o - -objs/m-series.o: $R/m-series.c - $(CC) $R/m-series.c $(RFLAGS) -o objs/m-series.o - -objs/n-control.o: $R/n-control.c - $(CC) $R/n-control.c $(RFLAGS) -o objs/n-control.o - -objs/n-data.o: $R/n-data.c - $(CC) $R/n-data.c $(RFLAGS) -o objs/n-data.o - -objs/n-io.o: $R/n-io.c - $(CC) $R/n-io.c $(RFLAGS) -o objs/n-io.o - -objs/n-loop.o: $R/n-loop.c - $(CC) $R/n-loop.c $(RFLAGS) -o objs/n-loop.o - -objs/n-math.o: $R/n-math.c - $(CC) $R/n-math.c $(RFLAGS) -o objs/n-math.o - -objs/n-sets.o: $R/n-sets.c - $(CC) $R/n-sets.c $(RFLAGS) -o objs/n-sets.o - -objs/n-strings.o: $R/n-strings.c - $(CC) $R/n-strings.c $(RFLAGS) -o objs/n-strings.o - -objs/n-system.o: $R/n-system.c - $(CC) $R/n-system.c $(RFLAGS) -o objs/n-system.o - -objs/p-clipboard.o: $R/p-clipboard.c - $(CC) $R/p-clipboard.c $(RFLAGS) -o objs/p-clipboard.o - -objs/p-console.o: $R/p-console.c - $(CC) $R/p-console.c $(RFLAGS) -o objs/p-console.o - -objs/p-dir.o: $R/p-dir.c - $(CC) $R/p-dir.c $(RFLAGS) -o objs/p-dir.o - -objs/p-dns.o: $R/p-dns.c - $(CC) $R/p-dns.c $(RFLAGS) -o objs/p-dns.o - -objs/p-event.o: $R/p-event.c - $(CC) $R/p-event.c $(RFLAGS) -o objs/p-event.o - -objs/p-file.o: $R/p-file.c - $(CC) $R/p-file.c $(RFLAGS) -o objs/p-file.o - -objs/p-net.o: $R/p-net.c - $(CC) $R/p-net.c $(RFLAGS) -o objs/p-net.o - -objs/s-cases.o: $R/s-cases.c - $(CC) $R/s-cases.c $(RFLAGS) -o objs/s-cases.o - -objs/s-crc.o: $R/s-crc.c - $(CC) $R/s-crc.c $(RFLAGS) -o objs/s-crc.o - -objs/s-file.o: $R/s-file.c - $(CC) $R/s-file.c $(RFLAGS) -o objs/s-file.o - -objs/s-find.o: $R/s-find.c - $(CC) $R/s-find.c $(RFLAGS) -o objs/s-find.o - -objs/s-make.o: $R/s-make.c - $(CC) $R/s-make.c $(RFLAGS) -o objs/s-make.o - -objs/s-mold.o: $R/s-mold.c - $(CC) $R/s-mold.c $(RFLAGS) -o objs/s-mold.o - -objs/s-ops.o: $R/s-ops.c - $(CC) $R/s-ops.c $(RFLAGS) -o objs/s-ops.o - -objs/s-trim.o: $R/s-trim.c - $(CC) $R/s-trim.c $(RFLAGS) -o objs/s-trim.o - -objs/s-unicode.o: $R/s-unicode.c - $(CC) $R/s-unicode.c $(RFLAGS) -o objs/s-unicode.o - -objs/t-bitset.o: $R/t-bitset.c - $(CC) $R/t-bitset.c $(RFLAGS) -o objs/t-bitset.o - -objs/t-block.o: $R/t-block.c - $(CC) $R/t-block.c $(RFLAGS) -o objs/t-block.o - -objs/t-char.o: $R/t-char.c - $(CC) $R/t-char.c $(RFLAGS) -o objs/t-char.o - -objs/t-datatype.o: $R/t-datatype.c - $(CC) $R/t-datatype.c $(RFLAGS) -o objs/t-datatype.o - -objs/t-date.o: $R/t-date.c - $(CC) $R/t-date.c $(RFLAGS) -o objs/t-date.o - -objs/t-decimal.o: $R/t-decimal.c - $(CC) $R/t-decimal.c $(RFLAGS) -o objs/t-decimal.o - -objs/t-event.o: $R/t-event.c - $(CC) $R/t-event.c $(RFLAGS) -o objs/t-event.o - -objs/t-function.o: $R/t-function.c - $(CC) $R/t-function.c $(RFLAGS) -o objs/t-function.o - -objs/t-gob.o: $R/t-gob.c - $(CC) $R/t-gob.c $(RFLAGS) -o objs/t-gob.o - -objs/t-image.o: $R/t-image.c - $(CC) $R/t-image.c $(RFLAGS) -o objs/t-image.o - -objs/t-integer.o: $R/t-integer.c - $(CC) $R/t-integer.c $(RFLAGS) -o objs/t-integer.o - -objs/t-logic.o: $R/t-logic.c - $(CC) $R/t-logic.c $(RFLAGS) -o objs/t-logic.o - -objs/t-map.o: $R/t-map.c - $(CC) $R/t-map.c $(RFLAGS) -o objs/t-map.o - -objs/t-money.o: $R/t-money.c - $(CC) $R/t-money.c $(RFLAGS) -o objs/t-money.o - -objs/t-none.o: $R/t-none.c - $(CC) $R/t-none.c $(RFLAGS) -o objs/t-none.o - -objs/t-object.o: $R/t-object.c - $(CC) $R/t-object.c $(RFLAGS) -o objs/t-object.o - -objs/t-pair.o: $R/t-pair.c - $(CC) $R/t-pair.c $(RFLAGS) -o objs/t-pair.o - -objs/t-port.o: $R/t-port.c - $(CC) $R/t-port.c $(RFLAGS) -o objs/t-port.o - -objs/t-string.o: $R/t-string.c - $(CC) $R/t-string.c $(RFLAGS) -o objs/t-string.o - -objs/t-time.o: $R/t-time.c - $(CC) $R/t-time.c $(RFLAGS) -o objs/t-time.o - -objs/t-tuple.o: $R/t-tuple.c - $(CC) $R/t-tuple.c $(RFLAGS) -o objs/t-tuple.o - -objs/t-typeset.o: $R/t-typeset.c - $(CC) $R/t-typeset.c $(RFLAGS) -o objs/t-typeset.o - -objs/t-utype.o: $R/t-utype.c - $(CC) $R/t-utype.c $(RFLAGS) -o objs/t-utype.o - -objs/t-vector.o: $R/t-vector.c - $(CC) $R/t-vector.c $(RFLAGS) -o objs/t-vector.o - -objs/t-word.o: $R/t-word.c - $(CC) $R/t-word.c $(RFLAGS) -o objs/t-word.o - -objs/u-bmp.o: $R/u-bmp.c - $(CC) $R/u-bmp.c $(RFLAGS) -o objs/u-bmp.o - -objs/u-compress.o: $R/u-compress.c - $(CC) $R/u-compress.c $(RFLAGS) -o objs/u-compress.o - -objs/u-dialect.o: $R/u-dialect.c - $(CC) $R/u-dialect.c $(RFLAGS) -o objs/u-dialect.o - -objs/u-gif.o: $R/u-gif.c - $(CC) $R/u-gif.c $(RFLAGS) -o objs/u-gif.o - -objs/u-jpg.o: $R/u-jpg.c - $(CC) $R/u-jpg.c $(RFLAGS) -o objs/u-jpg.o - -objs/u-md5.o: $R/u-md5.c - $(CC) $R/u-md5.c $(RFLAGS) -o objs/u-md5.o - -objs/u-parse.o: $R/u-parse.c - $(CC) $R/u-parse.c $(RFLAGS) -o objs/u-parse.o - -objs/u-png.o: $R/u-png.c - $(CC) $R/u-png.c $(RFLAGS) -o objs/u-png.o - -objs/u-sha1.o: $R/u-sha1.c - $(CC) $R/u-sha1.c $(RFLAGS) -o objs/u-sha1.o - -objs/u-zlib.o: $R/u-zlib.c - $(CC) $R/u-zlib.c $(RFLAGS) -o objs/u-zlib.o - -objs/host-main.o: $S/os/host-main.c - $(CC) $S/os/host-main.c $(HFLAGS) -o objs/host-main.o - -objs/host-args.o: $S/os/host-args.c - $(CC) $S/os/host-args.c $(HFLAGS) -o objs/host-args.o - -objs/host-device.o: $S/os/host-device.c - $(CC) $S/os/host-device.c $(HFLAGS) -o objs/host-device.o - -objs/host-stdio.o: $S/os/host-stdio.c - $(CC) $S/os/host-stdio.c $(HFLAGS) -o objs/host-stdio.o - -objs/dev-net.o: $S/os/dev-net.c - $(CC) $S/os/dev-net.c $(HFLAGS) -o objs/dev-net.o - -objs/dev-dns.o: $S/os/dev-dns.c - $(CC) $S/os/dev-dns.c $(HFLAGS) -o objs/dev-dns.o - -objs/host-lib.o: $S/os/posix/host-lib.c - $(CC) $S/os/posix/host-lib.c $(HFLAGS) -o objs/host-lib.o - -objs/host-readline.o: $S/os/posix/host-readline.c - $(CC) $S/os/posix/host-readline.c $(HFLAGS) -o objs/host-readline.o - -objs/dev-stdio.o: $S/os/posix/dev-stdio.c - $(CC) $S/os/posix/dev-stdio.c $(HFLAGS) -o objs/dev-stdio.o - -objs/dev-event.o: $S/os/posix/dev-event.c - $(CC) $S/os/posix/dev-event.c $(HFLAGS) -o objs/dev-event.o - -objs/dev-file.o: $S/os/posix/dev-file.c - $(CC) $S/os/posix/dev-file.c $(HFLAGS) -o objs/dev-file.o - diff --git a/make/makefile.boot b/make/makefile.boot new file mode 100644 index 0000000000..76e326477a --- /dev/null +++ b/make/makefile.boot @@ -0,0 +1,174 @@ +# Bootstrap Makefile for the Rebol Interpreter Core (a.k.a. Ren/C) +# This manually produced file was created 17-Jul-2015/10:20:03-04:00 + +# This makefile is tracked in version control, and can be used to kick off +# a build process. To do so you can either copy it to 'makefile' and type +# 'make', or pass a command line switch to tell it to use this file: +# +# make -f makefile.boot +# +# What will happen is that it will first kick off a call to: +# +# make -f makefile.boot make +# +# This runs a Rebol script in the %src/tools directory called %make-make.r +# which will generate a platform-specific makefile. Since it is a Rebol +# script, you will need a Rebol3 interpreter...and it expects you to have +# one in the %make/ directory called 'r3-make' (or 'r3-make.exe' on Windows) +# +# The next thing it will do is run 'make r3' using the new makefile: +# +# make r3 +# +# For most purposes this should "just work". The platform detection is very +# simple: it assumes that you want to build a version that's the same as what +# the 'r3-make' interpreter was built with. However, you may be wanting to +# "cross-compile" Rebol's generated code to copy %src/include/* over to a +# machine that needs an executable to bootstrap. (Or maybe it just guessed +# wrong.) In which case you should check the %src/tools/systems.r file, and +# provide an OS_ID from the table. For example, Linux with clib 2.5: +# +# make -f makefile.boot OS_ID=0.4.3 +# +# (Note: These numbers are what appear at the tail of a full Rebol version +# number. So you might find the ones above in a tuple like `2.101.0.4.3`, +# where the first numbers are referring to the version of the actual Rebol +# codebase itself. This tuple can be retrieved as `system/version`.) +# +# Rebol's bootstrapping scripts are supposed to be kept stable, even in the +# presence of language changes. So you *should* even be able to use an old +# executable from the pre-open-source Rebol3 downloads on rebol.com: +# +# http://www.rebol.com/r3/downloads.html +# +# (At least, in theory. If you notice bootstrap with an old interpreter is +# broken on your system, please report it! Few are testing old binaries.) +# +# For a more recent download, try getting your r3-make from: +# +# http://rebolsource.net/ +# +# !!! Efforts to be able to have Rebol build itself using itself (without a +# make tool, and perhaps even without a separate C toolchain) are being +# considered. If you want to chime in on that, or need support while +# building, please come chime in on chat: +# +# http://rebolsource.net/go/chat-faq +# + +# PARAMETERS %MAKE-MAKE.R WILL ACCEPT +# +# Note: variables assigned with ?= will only take the value if the variable +# is not already defined (e.g. not passed as a parameter to `make`) +# +# Note: LANGUAGE is some kind of reserved variable in make, so use STANDARD +# +# DEBUG can be "none", "asserts", "symbols", "sanitize"...each a level of +# assumed greater debugging. Adding symbols makes the executable much +# larger, and Address Sanitization makes the executable much slower. To +# try and get casual builders to bear a modest useful burden, the default +# is set to just including the asserts. +# +OS_ID?= detect +DEBUG?= asserts +OPTIMIZE?=auto +GIT_COMMIT?= unknown +STANDARD?= c +RIGOROUS?= no +WITH_FFI?= no +WITH_TCC?= no +STATIC?= no + +NUM_JOBS?=8 + +# UP - some systems do not use ../ +UP= .. +# CD - some systems do not use ./ +CD= ./ +# Special tools: +T= $(UP)/src/tools + +# http://stackoverflow.com/a/12099167/211160 +ifeq ($(OS),Windows_NT) + BIN_SUFFIX = .exe +else + BIN_SUFFIX = +endif + +REBOL_TOOL= r3-make$(BIN_SUFFIX) +REBOL= $(CD)$(REBOL_TOOL) -qs + +### Build targets: +top: makefile + $(MAKE) clean + $(MAKE) prep + echo "Going to build with $(NUM_JOBS) jobs" + $(MAKE) -j $(NUM_JOBS) top + +# .FORCE is a file assumed to not exist, and is an idiom in makefiles to have +# a null "phony target" you can use as a dependency for a target representing +# a real file to say "always generate the real target, even if it already +# exists. (We named our target 'makefile', so we need this to overwrite it) +.FORCE: + +makefile: $(REBOL_TOOL) .FORCE + $(REBOL) $T/make-make.r OS_ID="$(OS_ID)" DEBUG="$(DEBUG)" \ + GIT_COMMIT="$(GIT_COMMIT)" SANITIZE="$(SANITIZE)" \ + STANDARD="$(STANDARD)" RIGOROUS="$(RIGOROUS)" WITH_FFI="$(WITH_FFI)" \ + WITH_TCC="$(WITH_TCC)" STATIC="$(STATIC)" SYMBOLS="$(SYMBOLS)" \ + OPTIMIZE="$(OPTIMIZE)" + +# Synonym for `make -f makefile.boot makefile` which can also be used in the +# generated makefile (without causing repeated regenerations) +# +# http://stackoverflow.com/questions/31490689/ +# +make: makefile + +$(REBOL_TOOL): + @echo + @echo "*** ERROR: Missing $(REBOL_TOOL) to build various tmp files." + @echo "*** Download Rebol 3 and copy it here as $(REBOL_TOOL), then" + @echo "*** make prep. Or, make prep on some other machine and copy" + @echo "*** the src/include files here. You can download executable" + @echo "*** images of Rebol for several platforms from:" + @echo "***" + @echo "*** http://rebolsource.net" + @echo "***" + @echo "*** The bootstrap process is kept simple so it should be able" + @echo "*** to run even on old Rebol builds prior to open-sourcing:" + @echo "***" + @echo "*** http://www.rebol.com/r3/downloads.html" + @echo "***" + @echo "*** Visit chat for support: http://rebolsource.net/go/chat-faq" + @echo +# !!! Is false the best way to return an error code? + false + +# !!! This is supposed to be a catch-all rule. Not working. If it did work, +# this is what it should say (more or less) + +#%:: $(REBOL_TOOL)$(BIN_SUFFIX) +# @echo +# @echo +# @echo "*** The %makefile.boot bootstrapping makefile only handles an" +# @echo "*** automatic build with these options:" +# @echo "***" +# @echo "*** make -f makefile.boot" +# @echo "*** make -f makefile.boot OS_ID=##.##.##" +# @echo "***" +# @echo "*** The first will assume you want to build the same OS_ID as" +# @echo "*** what your r3-make is. The second lets you override what" +# @echo "*** OS to build for from system identification numbers in the" +# @echo "*** systems table (see %src/tools/systems.r)" +# @echo "***" +# @echo "*** If you want to prepare the platform-specific makefile without" +# @echo "*** *actually* building, then choose 'makefile' as your target:" +# @echo "***" +# @echo "*** make -f makefile.boot makefile" +# @echo "*** make -f makefile.boot makefile OS_ID=##.##.##" +# @echo "***" +# @echo "*** Visit chat for support: http://rebolsource.net/go/chat-faq" +# @echo +# !!! Is false the best way to return an error code? +# false diff --git a/make/makefile.vc b/make/makefile.vc deleted file mode 100644 index f36a050d99..0000000000 --- a/make/makefile.vc +++ /dev/null @@ -1,82 +0,0 @@ -# Makefile for Visual Studio's nmake -# TODO: this should be automatically generated, like Makefile - -CC= cl.exe -LD= link.exe - -UP= .. -T= $(UP)\src\tools -CD= ./ - -I= /I..\src\include -TO_OS= TO_WIN32 -OS_ID= 0.4.4 - -CFLAGS=/c /Os /D "UNICODE" /D "WIN32" /W3 /GR- /Zi /GS /Gy /GF /EHs-c- /GL /D "NDEBUG" /D "_CRT_SECURE_NO_WARNINGS" - -RAPI_FLAGS= $(CFLAGS) -HOST_FLAGS= $(CFLAGS) -RLIB_FLAGS= - -RFLAGS= $(RAPI_FLAGS) $(I) /D$(TO_OS) /DREB_API -HFLAGS= $(HOST_FLAGS) $(I) /D$(TO_OS) /DREB_CORE - -LIBS= user32.lib ws2_32.lib advapi32.lib shell32.lib comdlg32.lib -LDFLAGS = /nologo /DEBUG /RELEASE /opt:ref /opt:icf /LTCG - -REBOL= r3-make.exe -qs - -all: prep r3.exe - -prep: - $(REBOL) $T/make-headers.r - $(REBOL) $T/make-boot.r $(OS_ID) - $(REBOL) $T/make-host-init.r - $(REBOL) $T/make-os-ext.r # ok, but not always - $(REBOL) $T/make-host-ext.r - $(REBOL) $T/make-reb-lib.r - -objs: - mkdir objs - -OBJS = objs/a-constants.obj objs/a-globals.obj objs/a-lib.obj objs/b-boot.obj \ - objs/b-init.obj objs/c-do.obj objs/c-error.obj objs/c-frame.obj \ - objs/c-function.obj objs/c-port.obj objs/c-task.obj objs/c-word.obj \ - objs/d-crash.obj objs/d-dump.obj objs/d-print.obj objs/f-blocks.obj \ - objs/f-deci.obj objs/f-enbase.obj objs/f-extension.obj objs/f-math.obj \ - objs/f-modify.obj objs/f-random.obj objs/f-round.obj objs/f-series.obj \ - objs/f-stubs.obj objs/l-scan.obj objs/l-types.obj objs/m-gc.obj \ - objs/m-pools.obj objs/m-series.obj objs/n-control.obj objs/n-data.obj \ - objs/n-io.obj objs/n-loop.obj objs/n-math.obj objs/n-sets.obj \ - objs/n-strings.obj objs/n-system.obj objs/p-clipboard.obj objs/p-console.obj \ - objs/p-dir.obj objs/p-dns.obj objs/p-event.obj objs/p-file.obj \ - objs/p-net.obj objs/s-cases.obj objs/s-crc.obj objs/s-file.obj \ - objs/s-find.obj objs/s-make.obj objs/s-mold.obj objs/s-ops.obj \ - objs/s-trim.obj objs/s-unicode.obj objs/t-bitset.obj objs/t-block.obj \ - objs/t-char.obj objs/t-datatype.obj objs/t-date.obj objs/t-decimal.obj \ - objs/t-event.obj objs/t-function.obj objs/t-gob.obj objs/t-image.obj \ - objs/t-integer.obj objs/t-logic.obj objs/t-map.obj objs/t-money.obj \ - objs/t-none.obj objs/t-object.obj objs/t-pair.obj objs/t-port.obj \ - objs/t-string.obj objs/t-time.obj objs/t-tuple.obj objs/t-typeset.obj \ - objs/t-utype.obj objs/t-vector.obj objs/t-word.obj objs/u-bmp.obj \ - objs/u-compress.obj objs/u-dialect.obj objs/u-gif.obj objs/u-jpg.obj \ - objs/u-md5.obj objs/u-parse.obj objs/u-png.obj objs/u-sha1.obj \ - objs/u-zlib.obj - -HOST = objs/host-main.obj objs/host-args.obj objs/host-device.obj objs/host-stdio.obj \ - objs/dev-net.obj objs/dev-dns.obj objs/host-lib.obj \ - objs/dev-stdio.obj objs/dev-event.obj objs/dev-file.obj \ - objs/dev-clipboard.obj - -# Directly linked r3 executable: -r3.exe: objs $(OBJS) $(HOST) - $(LD) $(LDFLAGS) $(OBJS) $(HOST) $(LIBS) /PDB:$*.pdb /OUT:r3.exe /SUBSYSTEM:WINDOWS - -{..\src\core}.c{objs}.obj:: - $(CC) $(RFLAGS) /Foobjs\ /Fdobjs\vc80.pdb $< - -{..\src\os}.c{objs}.obj:: - $(CC) $(HFLAGS) /Foobjs\ /Fdobjs\vc80.pdb $< - -{..\src\os\win32}.c{objs}.obj:: - $(CC) $(HFLAGS) /Foobjs\ /Fdobjs\vc80.pdb $< diff --git a/make/r3-linux-x64-gbf237fc-static b/make/r3-linux-x64-gbf237fc-static new file mode 100755 index 0000000000..cd0331d40e Binary files /dev/null and b/make/r3-linux-x64-gbf237fc-static differ diff --git a/make/r3-osx-x64-gbf237fc b/make/r3-osx-x64-gbf237fc new file mode 100755 index 0000000000..f720288998 Binary files /dev/null and b/make/r3-osx-x64-gbf237fc differ diff --git a/make/vcbuild.bat b/make/vcbuild.bat deleted file mode 100755 index 02c4584890..0000000000 --- a/make/vcbuild.bat +++ /dev/null @@ -1,28 +0,0 @@ -@ECHO OFF - -REM Allow to explicitly specify the desired Visual Studio version -IF /I "%1" == "vc12" GOTO TRY_VS12 -IF /I "%1" == "vc10" GOTO TRY_VS10 -IF /I "%1" == "vc9" GOTO TRY_VS9 - -REM vs9 is VS 2008 -:TRY_VS9 -CALL "%VS90COMNTOOLS%\vsvars32.bat" 2>NUL -IF NOT ERRORLEVEL 1 GOTO BUILD - -REM vs10 is VS 2010 -:TRY_VS10 -CALL "%VS100COMNTOOLS%\vsvars32.bat" 2>NUL -IF NOT ERRORLEVEL 1 GOTO BUILD - -REM vs12 is VS 2012 -:TRY_VS12 -CALL "%VS110COMNTOOLS%\vsvars32.bat" 2>NUL -IF NOT ERRORLEVEL 1 GOTO BUILD - -ECHO Visual Studio 2012, 2010, or 2008 doesn't seem to be installed -EXIT /B 1 - -:BUILD -nmake -f makefile.vc all - diff --git a/ren-c-logo.png b/ren-c-logo.png new file mode 100644 index 0000000000..aaac1f9e02 Binary files /dev/null and b/ren-c-logo.png differ diff --git a/src/boot/actions.r b/src/boot/actions.r index dd1c90679c..f9206130af 100644 --- a/src/boot/actions.r +++ b/src/boot/actions.r @@ -1,447 +1,425 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Datatype action definitions" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Note: [ - "This list is order dependent!" - "Used to generate C enums and tables" - "Boot bind attributes are SET and not DEEP" - "Todo: before beta release remove extra/unused refinements" - ] + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Datatype action definitions" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0. + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Note: [ + "This list is order dependent!" + "Used to generate C enums and tables" + "Boot bind attributes are SET and not DEEP" + "Todo: before beta release remove extra/unused refinements" + ] ] ;-- Binary Math & Logic add: action [ - {Returns the addition of two values.} - value1 [scalar! date!] - value2 + {Returns the addition of two values.} + value1 [any-scalar! date! binary!] + value2 ] subtract: action [ - {Returns the second value subtracted from the first.} - value1 [scalar! date!] - value2 [scalar! date!] + {Returns the second value subtracted from the first.} + value1 [any-scalar! date! binary!] + value2 [any-scalar! date!] ] multiply: action [ - {Returns the first value multiplied by the second.} - value1 [scalar!] - value2 [scalar!] + {Returns the first value multiplied by the second.} + value1 [any-scalar!] + value2 [any-scalar!] ] divide: action [ - {Returns the first value divided by the second.} - value1 [scalar!] - value2 [scalar!] + {Returns the first value divided by the second.} + value1 [any-scalar!] + value2 [any-scalar!] ] remainder: action [ - {Returns the remainder of first value divided by second.} - value1 [scalar!] - value2 [scalar!] + {Returns the remainder of first value divided by second.} + value1 [any-scalar!] + value2 [any-scalar!] ] power: action [ - {Returns the first number raised to the second number.} - number [number!] - exponent [number!] + {Returns the first number raised to the second number.} + number [any-number!] + exponent [any-number!] ] and~: action [ - {Returns the first value ANDed with the second.} - value1 [logic! integer! char! tuple! binary! bitset! typeset! datatype!] - value2 [logic! integer! char! tuple! binary! bitset! typeset! datatype!] + {Returns the first value ANDed with the second.} + value1 [logic! integer! char! tuple! binary! bitset! typeset! datatype!] + value2 [logic! integer! char! tuple! binary! bitset! typeset! datatype!] ] or~: action [ - {Returns the first value ORed with the second.} - value1 [logic! integer! char! tuple! binary! bitset! typeset! datatype!] - value2 [logic! integer! char! tuple! binary! bitset! typeset! datatype!] + {Returns the first value ORed with the second.} + value1 [logic! integer! char! tuple! binary! bitset! typeset! datatype!] + value2 [logic! integer! char! tuple! binary! bitset! typeset! datatype!] ] xor~: action [ - {Returns the first value exclusive ORed with the second.} - value1 [logic! integer! char! tuple! binary! bitset! typeset! datatype!] - value2 [logic! integer! char! tuple! binary! bitset! typeset! datatype!] + {Returns the first value exclusive ORed with the second.} + value1 [logic! integer! char! tuple! binary! bitset! typeset! datatype!] + value2 [logic! integer! char! tuple! binary! bitset! typeset! datatype!] ] ;-- Unary negate: action [ - {Changes the sign of a number.} - number [number! pair! money! time! bitset!] + {Changes the sign of a number.} + number [any-number! pair! money! time! bitset!] ] complement: action [ - {Returns the one's complement value.} - value [logic! integer! tuple! binary! bitset! typeset! image!] + {Returns the one's complement value.} + value [logic! integer! tuple! binary! bitset! typeset! image!] ] absolute: action [ - {Returns the absolute value.} - value [number! pair! money! time!] + {Returns the absolute value.} + value [any-number! pair! money! time!] ] round: action [ - {Rounds a numeric value; halves round up (away from zero) by default.} - value [number! pair! money! time!] "The value to round" - /to "Return the nearest multiple of the scale parameter" - scale [number! money! time!] "Must be a non-zero value" - /even "Halves round toward even results" - /down "Round toward zero, ignoring discarded digits. (truncate)" - /half-down "Halves round toward zero" - /floor "Round in negative direction" - /ceiling "Round in positive direction" - /half-ceiling "Halves round in positive direction" + {Rounds a numeric value; halves round up (away from zero) by default.} + value [any-number! pair! money! time!] "The value to round" + /to "Return the nearest multiple of the scale parameter" + scale [any-number! money! time!] "Must be a non-zero value" + /even "Halves round toward even results" + /down "Round toward zero, ignoring discarded digits. (truncate)" + /half-down "Halves round toward zero" + /floor "Round in negative direction" + /ceiling "Round in positive direction" + /half-ceiling "Halves round in positive direction" ] random: action [ - {Returns a random value of the same datatype; or shuffles series.} - value {Maximum value of result (modified when series)} - /seed {Restart or randomize} - /secure {Returns a cryptographically secure random number} - /only {Pick a random value from a series} + {Returns a random value of the same datatype; or shuffles series.} + return: [ any-value!] + value {Maximum value of result (modified when series)} + /seed {Restart or randomize} + /secure {Returns a cryptographically secure random number} + /only {Pick a random value from a series} ] odd?: action [ - {Returns TRUE if the number is odd.} - number [number! char! date! money! time! pair!] + {Returns TRUE if the number is odd.} + number [any-number! char! date! money! time! pair!] ] even?: action [ - {Returns TRUE if the number is even.} - number [number! char! date! money! time! pair!] + {Returns TRUE if the number is even.} + number [any-number! char! date! money! time! pair!] ] ;-- Series Navigation -head: action [ - {Returns the series at its beginning.} - series [series! gob! port!] +head-of: action [ + {Returns the series at its beginning.} + series [any-series! gob! port!] ] -tail: action [ - {Returns the series just past its end.} - series [series! gob! port!] +tail-of: action [ + {Returns the series just past its end.} + series [any-series! gob! port!] ] head?: action [ - {Returns TRUE if a series is at its beginning.} - series [series! gob! port!] + {Returns TRUE if a series is at its beginning.} + series [any-series! gob! port!] ] tail?: action [ - {Returns TRUE if series is at or past its end; or empty for other types.} - series [series! gob! port! bitset! map!] + {Returns TRUE if series is at or past its end; or empty for other types.} + series [any-series! object! gob! port! bitset! map! blank! varargs!] ] past?: action [ - {Returns TRUE if series is past its end.} - series [series! gob! port!] -] - -next: action [ - {Returns the series at its next position.} - series [series! gob! port!] -] - -back: action [ - {Returns the series at its previous position.} - series [series! gob! port!] + {Returns TRUE if series is past its end.} + series [any-series! gob! port!] ] skip: action [ - {Returns the series forward or backward from the current position.} - series [series! gob! port!] - offset [number! logic! pair!] + {Returns the series forward or backward from the current position.} + series [any-series! gob! port!] + offset [any-number! logic! pair!] ] at: action [ - {Returns the series at the specified index.} - series [series! gob! port!] - index [number! logic! pair!] + {Returns the series at the specified index.} + series [any-series! gob! port!] + index [any-number! logic! pair!] ] -index?: action [ - {Returns the current position (index) of the series.} - series [series! gob! port! none!] - /xy {Returns index as an XY pair offset} +index-of: action [ + {Returns the current position (index) of the series.} + series [any-series! gob! port! blank!] + /xy {Returns index as an XY pair offset} ] -length?: action [ - {Returns the length (from the current position for series.)} - series [series! port! map! tuple! bitset! object! gob! struct! any-word! none!] -] - -;-- Series Extraction - -pick: action [ - {Returns the value at the specified position.} - aggregate [series! map! gob! pair! date! time! tuple! bitset! port!] - index {Index offset, symbol, or other value to use as index} +length-of: action [ + {Returns the length (from the current position for series.)} + series [any-series! port! map! tuple! bitset! object! gob! struct! any-word! blank!] ] ;-- Series Search find: action [ - {Searches for a value; for series returns where found, else none.} - series [series! gob! port! bitset! typeset! object! none!] - value [any-type!] - /part {Limits the search to a given length or position} - length [number! series! pair!] - /only {Treats a series value as only a single value} - /case {Characters are case-sensitive} - /any {Enables the * and ? wildcards} - /with {Allows custom wildcards} - wild [string!] "Specifies alternates for * and ?" - /skip {Treat the series as records of fixed size} - size [integer!] - /last {Backwards from end of series} - /reverse {Backwards from the current position} - /tail {Returns the end of the series} - /match {Performs comparison and returns the tail of the match} -] - -select: action [ - {Searches for a value; returns the value that follows, else none.} - series [series! port! map! object! none!] - value [any-type!] - /part {Limits the search to a given length or position} - length [number! series! pair!] - /only {Treats a series value as only a single value} - /case {Characters are case-sensitive} - /any {Enables the * and ? wildcards} - /with {Allows custom wildcards} - wild [string!] "Specifies alternates for * and ?" - /skip {Treat the series as records of fixed size} - size [integer!] - /last {Backwards from end of series} - /reverse {Backwards from the current position} + {Searches for a value; for series returns where found, else blank.} + return: [any-series! blank! logic!] + series [any-series! any-context! map! gob! bitset! typeset! blank!] + value [ any-value!] + /part {Limits the search to a given length or position} + limit [any-number! any-series! pair!] + /only {Treats a series value as only a single value} + /case {Characters are case-sensitive} + /skip {Treat the series as records of fixed size} + size [integer!] + /last {Backwards from end of series} + /reverse {Backwards from the current position} + /tail {Returns the end of the series} + /match {Performs comparison and returns the tail of the match} +] + +select*: action [ + {Searches for a value; returns the value that follows, else void.} + return: [ any-value!] + series [any-series! any-context! map! blank!] + value [any-value!] + /part {Limits the search to a given length or position} + limit [any-number! any-series! pair!] + /only {Treats a series value as only a single value} + /case {Characters are case-sensitive} + /skip {Treat the series as records of fixed size} + size [integer!] + /last {Backwards from end of series} + /reverse {Backwards from the current position} + /tail ;-- for frame compatibility with FIND + /match ;-- for frame compatibility with FIND + ] ;;;;!!! MATCH reflect: action [ - {Returns specific details about a datatype.} - value [any-type!] - field [word!] "Such as: spec, body, words, values, title" + {Returns specific details about a datatype.} + value [any-value!] + field [word!] "Such as: spec, body, words, values, title" ] ;-- Making, copying, modifying -make: action [ - {Constructs or allocates the specified datatype.} - type [any-type!] {The datatype or an example value} - spec [any-type!] {Attributes or size of the new value (modified)} -] - -to: action [ - {Converts to a specified datatype.} - type [any-type!] {The datatype or example value} - spec [any-type!] {The attributes of the new value} -] - copy: action [ - {Copies a series, object, or other value.} - value [series! port! map! object! bitset! any-function!] {At position} - /part {Limits to a given length or position} - length [number! series! pair!] - /deep {Also copies series values within the block} - /types {What datatypes to copy} - kinds [typeset! datatype!] -] - -take: action [ - {Removes and returns one or more elements.} - series [series! port! gob! none!] {At position (modified)} - /part {Specifies a length or end position} - length [number! series! pair!] - /deep {Also copies series values within the block} - /last {Take it from the tail end} + {Copies a series, object, or other value.} + + return: [any-value!] + {Return type will match the input type.} + value [any-value!] + {If an ANY-SERIES!, it is only copied from its current position} + /part + {Limits to a given length or position} + limit [any-number! any-series! pair!] + /deep + {Also copies series values within the block} + /types + {What datatypes to copy} + kinds [typeset! datatype!] +] + +take*: action [ + {Removes and returns one or more elements.} + return: [ any-value!] + series [any-series! port! gob! blank! varargs!] {At position (modified)} + /part {Specifies a length or end position} + limit [any-number! any-series! pair! bar!] + /deep {Also copies series values within the block} + /last {Take it from the tail end} ] insert: action [ - {Inserts element(s); for series, returns just past the insert.} - series [series! port! map! gob! object! bitset! port!] {At position (modified)} - value [any-type!] {The value to insert} - /part {Limits to a given length or position} - length [number! series! pair!] - /only {Only insert a block as a single value (not the contents of the block)} - /dup {Duplicates the insert a specified number of times} - count [number! pair!] + {Inserts element(s); for series, returns just past the insert.} + series [any-series! port! map! gob! object! bitset! port!] {At position (modified)} + value [ any-value!] {The value to insert} + /part {Limits to a given length or position} + limit [any-number! any-series! pair!] + /only {Only insert a block as a single value (not the contents of the block)} + /dup {Duplicates the insert a specified number of times} + count [any-number! pair!] ] append: action [ - {Inserts element(s) at tail; for series, returns head.} - series [series! port! map! gob! object! bitset!] {Any position (modified)} - value [any-type!] {The value to insert} - /part {Limits to a given length or position} - length [number! series! pair!] - /only {Only insert a block as a single value (not the contents of the block)} - /dup {Duplicates the insert a specified number of times} - count [number! pair!] + {Inserts element(s) at tail; for series, returns head.} + series [any-series! port! map! gob! object! module! bitset!] + {Any position (modified)} + value [ any-value!] {The value to insert} + /part {Limits to a given length or position} + limit [any-number! any-series! pair!] + /only {Only insert a block as a single value (not the contents of the block)} + /dup {Duplicates the insert a specified number of times} + count [any-number! pair!] ] remove: action [ - {Removes element(s); returns same position.} - series [series! gob! port! bitset! none!] {At position (modified)} - /part {Removes multiple elements or to a given position} - length [number! series! pair! char!] + {Removes element(s); returns same position.} + series [any-series! map! gob! port! bitset! blank!] {At position (modified)} + /part {Removes multiple elements or to a given position} + limit [any-number! any-series! pair! char!] + /map {Remove key from map} + key ] change: action [ - {Replaces element(s); returns just past the change.} - series [series! gob! port!]{At position (modified)} - value [any-type!] {The new value} - /part {Limits the amount to change to a given length or position} - length [number! series! pair!] - /only {Only change a block as a single value (not the contents of the block)} - /dup {Duplicates the change a specified number of times} - count [number! pair!] -] - -poke: action [ - {Replaces an element at a given position.} - series [series! port! map! gob! bitset!] {(modified)} - index {Index offset, symbol, or other value to use as index} - value [any-type!] {The new value (returned)} + {Replaces element(s); returns just past the change.} + series [any-series! gob! port! struct!]{At position (modified)} + value [ any-value!] {The new value} + /part {Limits the amount to change to a given length or position} + limit [any-number! any-series! pair!] + /only {Only change a block as a single value (not the contents of the block)} + /dup {Duplicates the change a specified number of times} + count [any-number! pair!] ] clear: action [ - {Removes elements from current position to tail; returns at new tail.} - series [series! port! map! gob! bitset! none!] {At position (modified)} + {Removes elements from current position to tail; returns at new tail.} + series [any-series! port! map! gob! bitset! blank!] {At position (modified)} ] trim: action [ - {Removes spaces from strings or nones from blocks or objects.} - series [series! object! error! module!] {Series (modified) or object (made)} - /head {Removes only from the head} - /tail {Removes only from the tail} - /auto {Auto indents lines relative to first line} - /lines {Removes all line breaks and extra spaces} - /all {Removes all whitespace} - /with str [char! string! binary! integer!] {Same as /all, but removes characters in 'str'} + {Removes spaces from strings or blanks from blocks or objects.} + series [any-series! object! error! module!] {Series (modified) or object (made)} + /head {Removes only from the head} + /tail {Removes only from the tail} + /auto {Auto indents lines relative to first line} + /lines {Removes all line breaks and extra spaces} + /all {Removes all whitespace} + /with str [char! string! binary! integer!] {Same as /all, but removes characters in 'str'} ] swap: action [ - {Swaps elements between two series or the same series.} - series1 [series! gob!] {At position (modified)} - series2 [series! gob!] {At position (modified)} + {Swaps elements between two series or the same series.} + series1 [any-series! gob!] {At position (modified)} + series2 [any-series! gob!] {At position (modified)} ] reverse: action [ - {Reverses the order of elements; returns at same position.} - series [series! gob! tuple! pair!] {At position (modified)} - /part {Limits to a given length or position} - length [number! series!] + {Reverses the order of elements; returns at same position.} + series [any-series! gob! tuple! pair!] {At position (modified)} + /part {Limits to a given length or position} + limit [any-number! any-series!] ] sort: action [ - {Sorts a series; default sort order is ascending.} - series [series!] {At position (modified)} - /case {Case sensitive sort} - /skip {Treat the series as records of fixed size} - size [integer!] {Size of each record} - /compare {Comparator offset, block or function} - comparator [integer! block! any-function!] - /part {Sort only part of a series} - length [number! series!] {Length of series to sort} - /all {Compare all fields} - /reverse {Reverse sort order} + {Sorts a series; default sort order is ascending.} + series [any-series!] {At position (modified)} + /case {Case sensitive sort} + /skip {Treat the series as records of fixed size} + size [integer!] {Size of each record} + /compare {Comparator offset, block or function} + comparator [integer! block! function!] + /part {Sort only part of a series} + limit [any-number! any-series!] {Length of series to sort} + /all {Compare all fields} + /reverse {Reverse sort order} ] ;-- Port actions: create: action [ - {Send port a create request.} - port [port! file! url! block!] + {Send port a create request.} + port [port! file! url! block!] ] delete: action [ - {Send port a delete request.} - port [port! file! url! block!] + {Send port a delete request.} + port [port! file! url! block!] ] open: action [ - {Opens a port; makes a new port from a specification if necessary.} - spec [port! file! url! block!] - /new {Create new file - if it exists, reset it (truncate)} - /read {Open for read access} - /write {Open for write access} - /seek {Optimize for random access} - /allow {Specifies protection attributes} - access [block!] + {Opens a port; makes a new port from a specification if necessary.} + spec [port! file! url! block!] + /new {Create new file - if it exists, reset it (truncate)} + /read {Open for read access} + /write {Open for write access} + /seek {Optimize for random access} + /allow {Specifies protection attributes} + access [block!] ] close: action [ - {Closes a port.} - port [port!] + {Closes a port/library.} + return: [ any-value!] + port [port! library!] ] read: action [ - {Read from a file, URL, or other port.} - source [port! file! url! block!] - /part {Partial read a given number of units (source relative)} - length [number!] - /seek {Read from a specific position (source relative)} - index [number!] - /string {Convert UTF and line terminators to standard text string} - /lines {Convert to block of strings (implies /string)} -; /as {Convert to string using a specified encoding} -; encoding [none! number!] {UTF number (0 8 16 -16)} + {Read from a file, URL, or other port.} + source [port! file! url! block!] + /part {Partial read a given number of units (source relative)} + limit [any-number!] + /seek {Read from a specific position (source relative)} + index [any-number!] + /string {Convert UTF and line terminators to standard text string} + /lines {Convert to block of strings (implies /string)} +; /as {Convert to string using a specified encoding} +; encoding [blank! any-number!] {UTF number (0 8 16 -16)} ] write: action [ - {Writes to a file, URL, or port - auto-converts text strings.} - destination [port! file! url! block!] - data [binary! string! block!] {Data to write (non-binary converts to UTF-8)} - /part {Partial write a given number of units} - length [number!] - /seek {Write at a specific position} - index [number!] - /append {Write data at end of file} - /allow {Specifies protection attributes} - access [block!] - /lines {Write each value in a block as a separate line} -; /as {Convert string to a specified encoding} -; encoding [none! number!] {UTF number (0 8 16 -16)} + {Writes to a file, URL, or port - auto-converts text strings.} + destination [port! file! url! block!] + data [binary! string! block! object!] ; !!! CHAR! support? + {Data to write (non-binary converts to UTF-8)} + /part {Partial write a given number of units} + limit [any-number!] + /seek {Write at a specific position} + index [any-number!] + /append {Write data at end of file} + /allow {Specifies protection attributes} + access [block!] + /lines {Write each value in a block as a separate line} +; /as {Convert string to a specified encoding} +; encoding [blank! any-number!] {UTF number (0 8 16 -16)} ] open?: action [ - {Returns TRUE if port is open.} - port [port!] + {Returns TRUE if port is open.} + port [port!] ] query: action [ - {Returns information about a port, file, or URL.} - target [port! file! url! block!] - /mode "Get mode information" - field [word! none!] "NONE will return valid modes for port type" + {Returns information about a port, file, or URL.} + target [port! file! url! block!] + /mode "Get mode information" + field [word! blank!] "NONE will return valid modes for port type" ] modify: action [ - {Change mode or control for port or file.} - target [port! file!] - field [word! none!] - value + {Change mode or control for port or file.} + target [port! file!] + field [word! blank!] + value ] update: action [ - {Updates external and internal states (normally after read/write).} - port [port!] + {Updates external and internal states (normally after read/write).} + port [port!] ] rename: action [ - {Rename a file.} - from [port! file! url! block!] - to [port! file! url! block!] + {Rename a file.} + from [port! file! url! block!] + to [port! file! url! block!] ] +;-- Expectation is that evaluation ends with no result, empty GROUP! does that +() diff --git a/src/boot/booters.r b/src/boot/booters.r deleted file mode 100644 index 61ecf54a73..0000000000 --- a/src/boot/booters.r +++ /dev/null @@ -1,31 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Special boot native function specs" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Purpose: { - These are used to define natives and actions. - Bind attributes for this block are: BIND_SET and SHALLOW - } -] - -; Special block used as spec to the datatype test functions (e.g. time?): -["Returns TRUE if it is this type." value [any-type!] 0] - -; The native function must be defined first. This is a -; special boot function created manually within the C code. -native: native [ - {Creates native function (for internal usage only).} - spec ; [block!] -- no check required, we know it is correct -] - -action: native [ - {Creates datatype action (for internal usage only).} - spec ; [block!] -- no check required, we know it is correct -] diff --git a/src/boot/draw.r b/src/boot/draw.r deleted file mode 100644 index c38611b6c7..0000000000 --- a/src/boot/draw.r +++ /dev/null @@ -1,299 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL Graphics - DRAW commands" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Name: draw - Type: extension - Exports: none - Note: "Run make-host-ext.r to convert" -] - -;don't change order of already defined words unless you know what you are doing - -words: [ - ;fill-rule - even-odd - non-zero - - ;grad-pen - conic - cubic - diagonal - diamond - linear - radial - normal - repeat - reflect - - ;line-cap - butt - square - rounded - - ;line-join - miter - miter-bevel - round - bevel - - ;line-width - variable - fixed - - ;arc - opened - closed - - ;image - no-border - border - - ;image-filter - nearest - bilinear - bicubic - gaussian - resize - resample - - ;text - raster - vectorial -] - -;temp hack - will be removed later -init-words: command [ - words [block!] -] - -init-words words - -;please alphabetize the order of commands so it easier to lookup things - -anti-alias: command [ - "Turns anti-aliasing on or off." - state [logic!] -] - -arc: command [ - "Draws a partial section of an ellipse or circle." - center [pair!] "The center of the circle" - radius [pair!] "The radius of the circle" - angle-begin [number!] "The angle where the arc begins, in degrees" - angle-length [number!] "The length of the arc in degrees" - 'arc-ending [word!] "Leave the arc: OPENED or CLOSED" -] - -arrow: command [ - "Sets the arrow mode." - mode [pair!] "Possible numbers for combination. 0 for none, 1 for head, 2 for tail" - color [tuple! none!] "Color of the head/tail of the arrow. NONE means use PEN color" -] - -box: command [ - "Draws a rectangular box." - origin [pair!] "Corner of box" - end [pair!] "End of box" - corner-radius [number!] "Rounds corners" -] - -circle: command [ - "Draws a circle or ellipse." - center [pair!] - radius [pair!] -] - -curve: command [ - "Draws a smooth Bezier curve.(using 3 or 4 points)" - point-1 [pair!] "End point A" - point-2 [pair!] "Control point A" - point-3 [pair!] "End point B, or control point B" - point-4 [pair! none!] "End point B" -] - -clip: command [ - "Specifies a clipping region." - origin [pair!] "Corner of box" - end [pair!] "End of box" -] - -ellipse: command [ - "Draws an ellipse." - origin [pair!] "The upper-left-point of the ellipse bounding box" - diameter [pair!] -] - -fill-pen: command [ - "Sets the area fill pen color." - color [tuple! image! logic!] "Set to OFF to disable fill pen" -] - -fill-rule: command [ - "Determines the algorithm used to determine what area to fill." - 'mode [word!] "Rule type: EVEN-ODD or NON-ZERO" -] - -gamma: command [ - "Sets the gamma correction value." - gamma-value [number!] -] - -grad-pen: command [ - "Sets the color gradient for area filling. To disable it set the color block to NONE." - 'type [word!] "The gradient type: RADIAL CONIC DIAMOND LINEAR DIAGONAL CUBIC" - 'mode [word!] "The gradient rendering mode: NORMAL REPEAT REFLECT" - offset [pair!] "offset from where should the gradient be rendered" - range [pair!] "begin and end of the gradient range" - angle [number!] "rotation of the gradient in degrees" - scale [pair!] "X and Y scale factor" - colors [block! none!] "block containing up to 256 gradient colors (optionally with color offsets)" -] - -image: command [ - "Draws an image, with optional scaling, borders, and color keying." - image [image!] - offset-points [pair! block!] -] - -image-filter: command [ - "Specifies type of algorithm used when an image is scaled." - 'filter-type [word!] "supported filters: NEAREST, BILINEAR, BICUBIC, GAUSSIAN" - 'filter-mode [word!] "Output quality: RESIZE(low, faster) or RESAMPLE(high, slower)" - blur [number! none!] "Used only in RESAMPLE mode" -] - -image-options: command [ - "Sets options related to image rendering." - key-color [tuple! none!] "Color to be rendered as transparent or NONE to disable it" - 'border-flag [word!] "can be BORDER or NO-BORDER" -] - -image-pattern: command [ - "Configure the image pattern fill settings." - 'pattern-mode [word!] "can be NORMAL, REPEAT or REFLECT" - pattern-offset [pair!] - pattern-size [pair!] "set to 0x0 for auto-size" - -] - -line: command [ - "Draws (poly)line from a number of points." - lines [block!] "Block of pairs" -] - -line-cap: command [ - "Sets the style that will be used when drawing the ends of lines." - 'type [word!] "Cap type: BUTT, SQUARE or ROUNDED" -] - -line-join: command [ - "Sets the style that will be used where lines are joined." - 'type [word!] "Join type: MITER, MITER-BEVEL, ROUND, or BEVEL" -] - -line-pattern: command [ - "Sets the line pattern. To disable it set the pattern block to NONE." - color [tuple!] "Dash color" - pattern [block! none!] "Block of dash-size/stroke-size number pairs" -] - -line-width: command [ - "Sets the line width." - width [number!] "Zero, or negative values, produce a line-width of 1." - 'mode [word!] "Line width mode during scaling: FIXED or VARIABLE" -] - -invert-matrix: command [ - "Applies an algebraic matrix inversion operation on the current transformation matrix." -] - -matrix: command [ - "Premultiplies the current transformation matrix with the given block." - matrix-setup [block!] "content must be 6 numbers" -] - -pen: command [ - "Sets the line pen color." - color [tuple! image! logic!] "Set to OFF to disable pen" -] - -polygon: command [ - "Draws a closed area of line segments. First and last points are connected." - vertices [block!] "Block of pairs" -] - -push: command [ - "Stores the current attribute setup in stack." - draw-block [block!] -] - -reset-matrix: command [ - "Resets the current transformation matrix to its default values." -] - -rotate: command [ - "Sets the clockwise rotation in current transformation matrix." - angle [number!] "in degrees" -] - -scale: command [ - "Sets the scaling factor in current transformation matrix." - factor [pair!] -] - -shape: command [ - "Draws shapes using the SHAPE sub-dialect." - commands [block!] "Block of SHAPE sub-commands" -] - -skew: command [ - "Sets a coordinate system skewed from the original by the given number of degrees in specified axis." - angle [pair!] "Positive numbers skew to the right; negative numbers skew to the left." -] - -spline: command [ - "Draws a curve through any number of points. The smoothness of the curve will be determined by the segment factor." - points [block!] "Block of pairs" - segmentation [integer!] - 'spline-ending [word!] "Leave the spline: OPENED or CLOSED" -] - -text: command [ - "Draws a string of text." - offset [pair!] "offset from where should the text be rendered" - size [pair!] "size of the text area" - 'render-mode [word!] "RASTER or VECTORIAL" - rich-text-block [block!] -] - -transform: command [ - "Applies transformation such as translation, scaling, and rotation." - angle [number!] - center [pair!] - scale [pair!] - translation [pair!] -] - -translate: command [ - "Sets the translation in current transformation matrix." - offset [pair!] -] - -triangle: command [ - "Draws triangular polygon with shading parameters (Gouraud shading). Set colors to NONE to turn of shading." - vertex-1 [pair!] - vertex-2 [pair!] - vertex-3 [pair!] - color-1 [tuple! none!] - color-2 [tuple! none!] - color-3 [tuple! none!] - dilation [number!] "Useful for eliminating anitaliased edges" -] diff --git a/src/boot/errors.r b/src/boot/errors.r index 51d573f78f..b1deee7128 100644 --- a/src/boot/errors.r +++ b/src/boot/errors.r @@ -1,195 +1,333 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Error objects" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Purpose: { - Specifies error categories and default error messages. - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Error objects" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0. + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + Specifies error categories and default error messages. + } ] -Throw: [ - code: 0 - type: "throw error" - break: {no loop to break} - return: {return or exit not in function} - throw: [{no catch for throw:} :arg1] - continue: {no loop to continue} - halt: [{halted by user or script}] - quit: [{user script quit}] +Special: [ + code: 0 + type: "special" ; Not really "errors" + null: {invalid error code zero} + halt: {halted by user or script} ] -Note: [ - code: 100 - type: "note" - no-load: [{cannot load: } :arg1] - exited: [{exit occurred}] - deprecated: {deprecated function not allowed} +Internal: [ + code: 1000 + type: "internal" + + ; !!! Should there be a distinction made between different kinds of + ; stack overflows? (Call stack, Data stack?) + ; + stack-overflow: {stack overflow} + + not-done: {reserved for future use (or not yet implemented)} + + no-memory: [{not enough memory:} :arg1 {bytes}] + + io-error: {problem with IO} + locked-series: {locked series expansion} + unexpected-case: {no case in switch statement} + invalid-datatype: [{invalid datatype #} :arg1] + bad-path: [{bad path:} :arg1] + not-here: [:arg1 {not supported on your system}] + globals-full: {no more global variable space} + bad-sys-func: [{invalid or missing system function:} :arg1] + invalid-error: [{error object or fields were not valid:} :arg1] + hash-overflow: {Hash ran out of space} + no-print-ptr: {print is missing string pointer} + + bad-utf8: {invalid UTF-8 byte sequence found during decoding} + codepoint-too-high: [{codepoint} :arg1 {too large (or data is not UTF-8)}] + + debug-only: {Feature available only in DEBUG builds} + + host-no-breakpoint: {Interpreter host code has no breakpoint handler} + no-current-pause: {No current PAUSE or BREAKPOINT instruction in effect} + + invalid-exit: {Frame does not exist on the stack to EXIT from} + out-of-error-numbers: {There is no more base error code available} ] Syntax: [ - code: 200 - type: "syntax error" - invalid: [{invalid} :arg1 {--} :arg2] - missing: [{missing} :arg2 {at} :arg1] - no-header: [{script is missing a REBOL header:} :arg1] - bad-header: [{script header is not valid:} :arg1] - bad-checksum: [{script checksum failed:} :arg1] - malconstruct: [{invalid construction spec:} :arg1] - bad-char: [{invalid character in:} :arg1] - needs: [{this script needs} :arg1 :arg2 {or better to run correctly}] + code: 2000 + type: "syntax error" + scan-invalid: [{invalid} :arg1 {--} :arg2] + scan-missing: [{missing} :arg1] + scan-extra: [{extra} :arg1] + scan-mismatch: [{expected} :arg1 {but got} :arg2] + + no-header: [{script is missing a REBOL header:} :arg1] + bad-header: [{script header is not valid:} :arg1] + bad-checksum: [{script checksum failed:} :arg1] + malconstruct: [{invalid construction spec:} :arg1] + bad-char: [{invalid character in:} :arg1] + needs: [{this script needs} :arg1 :arg2 {or better to run correctly}] ] Script: [ - code: 300 - type: "script error" - no-value: [:arg1 {has no value}] - need-value: [:arg1 {needs a value}] - not-defined: [:arg1 {word is not bound to a context}] - not-in-context: [:arg1 {is not in the specified context}] - - no-arg: [:arg1 {is missing its} :arg2 {argument}] - expect-arg: [:arg1 {does not allow} :arg3 {for its} :arg2 {argument}] - expect-val: [{expected} :arg1 {not} :arg2] - expect-type: [:arg1 :arg2 {field must be of type} :arg3] - cannot-use: [{cannot use} :arg1 {on} :arg2 {value}] - - invalid-arg: [{invalid argument:} :arg1] - invalid-type: [:arg1 {type is not allowed here}] - invalid-op: [{invalid operator:} :arg1] - no-op-arg: [:arg1 {operator is missing an argument}] - invalid-data: [{data not in correct format:} :arg1] - not-same-type: {values must be of the same type} - not-related: [{incompatible argument for} :arg1 {of} :arg2] - bad-func-def: [{invalid function definition:} :arg1] - bad-func-arg: [{function argument} :arg1 {is not valid}] ; can be a number - - no-refine: [:arg1 {has no refinement called} :arg2] - bad-refines: {incompatible or invalid refinements} - bad-refine: [{incompatible refinement:} :arg1] - invalid-path: [{cannot access} :arg2 {in path} :arg1] - bad-path-type: [{path} :arg1 {is not valid for} :arg2 {type}] - bad-path-set: [{cannot set} :arg2 {in path} :arg1] - bad-field-set: [{cannot set} :arg1 {field to} :arg2 {datatype}] - dup-vars: [{duplicate variable specified:} :arg1] - - past-end: {out of range or past end} - missing-arg: {missing a required argument or refinement} - out-of-range: [{value out of range:} :arg1] - too-short: {content too short (or just whitespace)} - too-long: {content too long} - invalid-chars: {contains invalid characters} - invalid-compare: [{cannot compare} :arg1 {with} :arg2] - assert-failed: [{assertion failed for:} :arg1] - wrong-type: [{datatype assertion failed for:} :arg1] - - invalid-part: [{invalid /part count:} :arg1] - type-limit: [:arg1 {overflow/underflow}] - size-limit: [{maximum limit reached:} :arg1] - - no-return: {block did not return a value} - block-lines: {expected block of lines} - throw-usage: {invalid use of a thrown error value} - - locked-word: [{protected variable - cannot modify:} :arg1] - protected: {protected value or series - cannot modify} - hidden: {not allowed - would expose or modify hidden values} - self-protected: {cannot set/unset self - it is protected} - bad-bad: [:arg1 {error:} :arg2] - - bad-make-arg: [{cannot MAKE/TO} :arg1 {from:} :arg2] - bad-decode: {missing or unsupported encoding marker} -; no-decode: [{cannot decode} :arg1 {encoding}] - already-used: [{alias word is already in use:} :arg1] - wrong-denom: [:arg1 {not same denomination as} :arg2] + code: 3000 + type: "script error" + + no-value: [:arg1 {has no value}] + need-value: [:arg1 {needs a value}] + not-bound: [:arg1 {word is not bound to a context}] + no-relative: [:arg1 {word is bound relative to context not on stack}] + not-in-context: [:arg1 {is not in the specified context}] + + no-arg: [:arg1 {is missing its} :arg2 {argument}] + expect-arg: [:arg1 {does not allow} :arg2 {for its} :arg3 {argument}] + arg-required: [:arg1 {requires} :arg2 {argument to not be void}] + expect-val: [{expected} :arg1 {not} :arg2] + expect-type: [:arg1 :arg2 {field must be of type} :arg3] + cannot-use: [{cannot use} :arg1 {on} :arg2 {value}] + + do-running-frame: [{Must COPY a FRAME! that's RUNNING? before DOing it}] + do-expired-frame: [{Cannot DO a FRAME! whose stack storage expired}] + + multiple-do-errors: [{DO-ALL encountered multiple failures} :arg1 :arg2] + + apply-too-many: {Too many values in processed argument block of APPLY.} + apply-has-changed: {APPLY takes frame def block (or see r3-alpha-apply)} + apply-non-function: [:arg1 {needs to be a function for APPLY/SPECIALIZE}] + + invalid-tighten: {TIGHTEN does not support SPECIALIZE/ADAPT/CHAIN} + print-needs-eval: {PRINT needs /EVAL to process non-literal blocks} + + hijack-blank: {Hijacked function was captured but no body given yet} + + expression-barrier: {Expression barrier hit while processing arguments} + bar-hit-mid-case: {Expression barrier hit in middle of CASE pairing} + enfix-quote-late: [:arg1 {can't left quote a forward quoted value}] + partial-lookback: [:arg1 {can't complete} :arg2 {expression on left}] + evaluate-void: {voids cannot be evaluated} + + enfix-path-group: [:arg1 {GROUP! can't be in a lookback quoted PATH!}] + + hard-quote-void: [:arg1 {is hard quoted and can't be optionally void}] + + reduce-made-void: {Expression in REDUCE evaluated to void} + break-not-continue: {Use BREAK/WITH when body is the breaking condition} + + ; !!! Temporary errors while faulty constructs are still outstanding + ; (more informative than just saying "function doesn't take that type") + use-eval-for-eval: {Use EVAL or APPLY to call functions arity > 0, not DO} + use-fail-for-error: [{Use FAIL (not THROW or DO) to raise} :arg1] + use-split-simple: {Use SPLIT (instead of PARSE) for "simple" parsing} + + limited-fail-input: {FAIL requires complex expressions to be in a GROUP!} + + invalid-arg: [{invalid argument:} :arg1] + invalid-type: [:arg1 {type is not allowed here}] + invalid-op: [{invalid operator:} :arg1] + no-op-arg: [:arg1 {operator is missing an argument}] + invalid-data: [{data not in correct format:} :arg1] + not-same-type: {values must be of the same type} + not-related: [{incompatible argument for} :arg1 {of} :arg2] + bad-func-def: [{invalid function definition:} :arg1] + bad-func-arg: [{function argument} :arg1 {is not valid}] ; can be a number + + needs-return-value: [:arg1 {must return value (use PROC or RETURN: )}] + bad-return-type: [:arg1 {doesn't have RETURN: enabled for} :arg2] + + no-refine: [:arg1 {has no refinement called} :arg2] + bad-refines: {incompatible or invalid refinements} + bad-refine: [{incompatible or duplicate refinement:} :arg1] + argument-revoked: [:arg1 {refinement revoked, cannot supply} :arg2] + bad-refine-revoke: [:arg1 {refinement in use, can't be revoked by} :arg2] + non-logic-refine: [:arg1 {refinement must be LOGIC!, not} :arg2] + refinement-arg-opt: [{refinement arguments cannot be }] + + invalid-path: [{cannot access} :arg2 {in path} :arg1] + bad-path-type: [{path} :arg1 {is not valid for} :arg2 {type}] + bad-path-set: [{cannot set} :arg2 {in path} :arg1] + bad-field-set: [{cannot set} :arg1 {field to} :arg2 {datatype}] + dup-vars: [{duplicate variable specified:} :arg1] + + past-end: {out of range or past end} + missing-arg: {missing a required argument or refinement} + too-short: {content too short (or just whitespace)} + too-long: {content too long} + invalid-chars: {contains invalid characters} + invalid-compare: [{cannot compare} :arg1 {with} :arg2] + + verify-void: [{verification condition void at:} :arg1] + verify-failed: [{verification failed for:} :arg1] + + invalid-part: [{invalid /part count:} :arg1] + + no-return: {block did not return a value} + block-lines: {expected block of lines} + no-catch: [{Missing CATCH for THROW of} :arg1] + no-catch-named: [{Missing CATCH for THROW of} :arg1 {with /NAME:} :arg2] + + bad-bad: [:arg1 {error:} :arg2] + + bad-make-arg: [{cannot MAKE/TO} :arg1 {from:} :arg2] +; no-decode: [{cannot decode} :arg1 {encoding}] + wrong-denom: [:arg1 {not same denomination as} :arg2] ; bad-convert: [{invalid conversion value:} :arg1] - bad-press: [{invalid compressed data - problem:} :arg1] - dialect: [{incorrect} :arg1 {dialect usage at:} :arg2] - bad-command: {invalid command format (extension function)} + bad-compression: [{invalid compressed data - problem:} :arg1] + dialect: [{incorrect} :arg1 {dialect usage at:} :arg2] + bad-command: {invalid command format (extension function)} + bad-cast: [{cannot cast} :arg1 {as} :arg2] + + return-archetype: {RETURN called with no generator providing it in use} + leave-archetype: {LEAVE called with no generator providing it in use} - parse-rule: [{PARSE - invalid rule or usage of rule:} :arg1] - parse-end: [{PARSE - unexpected end of rule after:} :arg1] - parse-variable: [{PARSE - expected a variable, not:} :arg1] - parse-command: [{PARSE - command cannot be used as variable:} :arg1] - parse-series: [{PARSE - input must be a series:} :arg1] + parse-rule: {PARSE - invalid rule or usage of rule} + parse-end: {PARSE - unexpected end of rule} + parse-variable: [{PARSE - expected a variable, not:} :arg1] + parse-command: [{PARSE - command cannot be used as variable:} :arg1] + parse-series: [{PARSE - input must be a series:} :arg1] + not-ffi-build: {This Rebol build wasn't linked with libffi features} + not-tcc-build: {This Rebol build wasn't linked with libtcc features} + bad-library: {bad library (already closed?)} + only-callback-ptr: {Only callback functions may be passed by FFI pointer} + free-needs-routine: {Function to destroy struct storage must be routine} + + block-skip-wrong: {Block is not even multiple of skip size} ; bad-prompt: [{Error executing prompt block}] ; bad-port-action: [{Cannot use} :arg1 {on this type port}] ; face-error: [{Invalid graphics face object}] ; face-reused: [{Face object reused (in more than one pane):} :arg1] + + frame-already-used: [{Frame currently in use by a function call} :arg1] + frame-not-on-stack: {Frame is no longer running on the stack} + + recursive-varargs: {VARARGS! chained into itself (maybe try ?)} + varargs-no-stack: {Call originating VARARGS! has finished running} + varargs-make-only: {MAKE *shared* BLOCK! supported on VARARGS! (not TO)} + varargs-no-look: {VARARGS! may only lookahead by 1 if "hard quoted"} + varargs-take-last: {VARARGS! does not support TAKE-ing only /LAST item} + + void-vararg-array: {Can't MAKE ANY-ARRAY! from VARARGS! that allow } + void-object-block: {Can't create block from object if it has void values} + + map-key-unlocked: [{key must be LOCK-ed to add to MAP!} :arg1] + tcc-not-supported-opt: [{Option} :arg1 {is not supported}] + tcc-expect-word: [{Option expecting a word:} :arg1] + tcc-invalid-include: [{Include expects a block or a path:} :arg1] + tcc-invalid-options: [{Options expect string} :arg1] + tcc-invalid-library: [{Library expects a block or a path:} :arg1] + tcc-invalid-library-path: [{Library path expects a block or a path:} :arg1] + tcc-invalid-runtime-path: [{Runtime library path expects a block or a path:} :arg1] + tcc-empty-spec: {Spec for natives must not be empty} + tcc-empty-source: {Source for natives must not be empty} + tcc-construction: {TCC failed to create a TCC context} + tcc-set-options: {TCC failed to set TCC options} + tcc-include: [{TCC failed to add include path:} :arg1] + tcc-library: [{TCC failed to add library:} :arg1] + tcc-library-path: [{TCC failed to add library path:} :arg1] + tcc-runtime-path: [{TCC failed to add runtime library path:} :arg1] + tcc-output-type: {TCC failed to set output to memory} + tcc-compile: [{TCC failed to compile the code} :arg1] + tcc-relocate: {TCC failed to relocate the code} + tcc-invalid-name: [{C name must be a string:} :arg1] + tcc-sym-not-found: [{TCC failed to find symbol:} :arg1] + tcc-error-warn: [{TCC reported error/warnings. Fix error/warnings, or use '-w' to disable all of the warnings:} :arg1] + + block-conditional: [{Literal block used as conditional} :arg1] + block-switch: [{Literal block used as switch value} :arg1] + + non-unloadable-native: [{Not an unloadable native:} :arg1] + native-unloaded: [{Native has been unloaded:} :arg1] + fail-to-quit-extension: [{Failed to quit the extension:} :arg1] ] Math: [ - code: 400 - type: "math error" - zero-divide: {attempt to divide by zero} - overflow: {math or number overflow} - positive: {positive number required} + code: 4000 + type: "math error" + + zero-divide: {attempt to divide by zero} + overflow: {math or number overflow} + positive: {positive number required} + + type-limit: [:arg1 {overflow/underflow}] + size-limit: [{maximum limit reached:} :arg1] + out-of-range: [{value out of range:} :arg1] ] Access: [ - code: 500 - type: "access error" + code: 5000 + type: "access error" + + protected-word: [{variable} :arg1 {locked by PROTECT (see UNPROTECT)}] + + series-protected: {series read-only due to PROTECT (see UNPROTECT)} + series-frozen: {series is source or permanently locked, can't modify} + series-running: {series temporarily read-only for running (DO, PARSE)} + + hidden: {not allowed - would expose or modify hidden values} - cannot-open: [{cannot open:} :arg1 {reason:} :arg2] - not-open: [{port is not open:} :arg1] - already-open: [{port is already open:} :arg1] + cannot-open: [{cannot open:} :arg1 {reason:} :arg2] + not-open: [{port is not open:} :arg1] + already-open: [{port is already open:} :arg1] ; already-closed: [{port} :arg1 {already closed}] - no-connect: [{cannot connect:} :arg1 {reason:} :arg2] - not-connected: [{port is not connected:} :arg1] + no-connect: [{cannot connect:} :arg1 {reason:} :arg2] + not-connected: [{port is not connected:} :arg1] ; socket-open: [{error opening socket:} :arg1] - no-script: [{script not found:} :arg1] - - no-scheme-name: [{new scheme must have a name:} :arg1] - no-scheme: [{missing port scheme:} :arg1] - - invalid-spec: [{invalid spec or options:} :arg1] - invalid-port: [{invalid port object (invalid field values)}] - invalid-actor: [{invalid port actor (must be native or object)}] - invalid-port-arg: [{invalid port argument:} arg1] - no-port-action: [{this port does not support:} :arg1] - protocol: [{protocol error:} :arg1] - invalid-check: [{invalid checksum (tampered file):} :arg1] - - write-error: [{write failed:} :arg1 {reason:} :arg2] - read-error: [{read failed:} :arg1 {reason:} :arg2] - read-only: [{read-only - write not allowed:} :arg1] - no-buffer: [{port has no data buffer:} :arg1] - timeout: [{port action timed out:} :arg1] - - no-create: [{cannot create:} :arg1] - no-delete: [{cannot delete:} :arg1] - no-rename: [{cannot rename:} :arg1] - bad-file-path: [{bad file path:} :arg1] - bad-file-mode: [{bad file mode:} :arg1] + no-script: [{script not found:} :arg1] + + no-scheme-name: {Scheme has no `name:` field (must be WORD!)} + no-scheme: [{missing port scheme:} :arg1] + + invalid-spec: [{invalid spec or options:} :arg1] + invalid-port: [{invalid port object (invalid field values)}] + invalid-actor: [{invalid port actor (must be native or object)}] + invalid-port-arg: [{invalid port argument:} :arg1] + no-port-action: [{this port does not support:} :arg1] + protocol: [{protocol error:} :arg1] + invalid-check: [{invalid checksum (tampered file):} :arg1] + + write-error: [{write failed:} :arg1 {reason:} :arg2] + read-error: [{read failed:} :arg1 {reason:} :arg2] + read-only: [{read-only - write not allowed:} :arg1] + timeout: [{port action timed out:} :arg1] + + no-create: [{cannot create:} :arg1] + no-delete: [{cannot delete:} :arg1] + no-rename: [{cannot rename:} :arg1] + bad-file-path: [{bad file path:} :arg1] + bad-file-mode: [{bad file mode:} :arg1] ; protocol: [{protocol error} :arg1] security: [{security violation:} :arg1 { (refer to SECURE function)}] security-level: [{attempt to lower security to} :arg1] security-error: [{invalid} :arg1 {security policy:} :arg2] - no-codec: [{cannot decode or encode (no codec):} :arg1] - bad-media: [{bad media data (corrupt image, sound, video)}] + no-codec: [{cannot decode or encode (no codec):} :arg1] + bad-media: [{bad media data (corrupt image, sound, video)}] ; would-block: [{operation on port} :arg1 {would block}] ; no-action: [{this type of port does not support the} :arg1 {action}] ; serial-timeout: {serial port timeout} - no-extension: [{cannot open extension:} :arg1] - bad-extension: [{invalid extension format:} :arg1] - extension-init: [{extension cannot be initialized (check version):} :arg1] + no-extension: [{cannot open extension:} :arg1] + bad-extension: [{invalid extension format:} :arg1] + extension-init: [{extension cannot be initialized (check version):} :arg1] - call-fail: [{external process failed:} :arg1] + call-fail: [{external process failed:} :arg1] + symbol-not-found: [{symbol not found:} :arg1] + bad-memory: [{non-accessible memory at} :arg1 {in} :arg2] + no-external-storage: [{no external storage in the series}] + already-destroyed: [{storage at} :arg1 {already destroyed}] ] Command: [ - code: 600 - type: "command error" + code: 6000 + type: "command error" + bad-cmd-args: ["Bad command arguments"] + no-cmd: ["No command"] ; fmt-too-short: {Format string is too short} ; fmt-no-struct-size: [{Missing size spec for struct at arg#} :arg1] ; fmt-no-struct-align: [{Missing align spec for struct at arg#} :arg1] @@ -203,32 +341,10 @@ Command: [ ; cant-free: [{Cannot free} :arg1] ; nothing-to-free: {Nothing to free} ; ssl-error: [{SSL Error: } :arg1] + command-fail: ["Command failed"] ] -resv700: [ - code: 700 - type: "reserved" -] - -User: [ - code: 800 - type: "user error" - message: [:arg1] -] +; If new category added, be sure to update RE_MAX in %make-boot.r +; (currently RE_COMMAND_MAX because `Command: [...]` is the last category) -Internal: [ - code: 900 - type: "internal error" - bad-path: [{bad path:} arg1] - not-here: [arg1 {not supported on your system}] - no-memory: {not enough memory} - stack-overflow: {stack overflow} - globals-full: {no more global variable space} - max-natives: {too many natives} - bad-series: {invalid series} - limit-hit: [{internal limit reached:} :arg1] - bad-sys-func: [{invalid or missing system function:} :arg1] - feature-na: {feature not available} - not-done: {reserved for future use (or not yet implemented)} - invalid-error: {error object or fields were not valid} -] +; Note that MAX_I32 is the hardcoded constant in %make-boot.r used for RE_USER diff --git a/src/boot/graphics.r b/src/boot/graphics.r deleted file mode 100644 index b592db5e95..0000000000 --- a/src/boot/graphics.r +++ /dev/null @@ -1,87 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL Graphics" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Name: graphics - Type: extension - Exports: [] ; added by make-host-ext.r - Note: "Run make-host-ext.r to convert" -] - -words: [ - ;gui-metric - screen-size - border-size - border-fixed - title-size - work-origin - work-size -] - -;temp hack - will be removed later -init-words: command [ - words [block!] -] - -init-words words - -init: command [ - "Initialize graphics subsystem." - gob [gob!] "The screen gob (root gob)" -] - -caret-to-offset: command [ - "Returns the xy offset (pair) for a specific string position in a graphics object." - gob [gob!] - element [integer! block!] "The position of the string in the richtext block" - position [integer! string!] "The position within the string" -] - -cursor: command [ - "Changes the mouse cursor image." - image [integer! image! none!] -] - -offset-to-caret: command [ ;returns pair! instead of the block..needs to be fixed - "Returns the richtext block at the string position for an XY offset in the graphics object." - gob [gob!] - position [pair!] -] - -show: command [ - "Display or update a graphical object or block of them." - gob [gob! none!] -] - -size-text: command [ - "Returns the size of text rendered by a graphics object." - gob [gob!] -] - -draw: command [ - "Renders draw dialect (scalable vector graphics) to an image (returned)." - image [image! pair!] "Image or size of image" - commands [block!] "Draw commands" -] - -gui-metric: command [ - "Returns specific gui related metric setting." - keyword [word!] "Available keywords: SCREEN-SIZE, BORDER-SIZE, BORDER-FIXED, TITLE-SIZE, WORK-ORIGIN and WORK-SIZE." -] - -;#not-yet-used [ -; -;effect: command [ -; "Renders effect dialect to an image (returned)." -; image [image! pair!] "Image or size of image" -; commands [block!] "Effect commands" -;] -; -;] diff --git a/src/boot/modes.r b/src/boot/modes.r index e4c01f5384..c6cd4b2c9c 100644 --- a/src/boot/modes.r +++ b/src/boot/modes.r @@ -1,14 +1,14 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Port modes" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Port modes" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0. + See: http://www.apache.org/licenses/LICENSE-2.0 + } ] owner-read diff --git a/src/boot/natives.r b/src/boot/natives.r deleted file mode 100644 index 5e1b1c8ac9..0000000000 --- a/src/boot/natives.r +++ /dev/null @@ -1,1082 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Native function specs" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Note: [ - "Used to generates C enums and tables" - "Boot bind attributes are SET and not DEEP" - "Todo: before beta release remove extra/unused refinements" - ] -] - -;-- Control Natives - nat_control.c - -ajoin: native [ - {Reduces and joins a block of values into a new string.} - block [block!] -] - -also: native [ - {Returns the first value, but also evaluates the second.} - value1 [any-type!] - value2 [any-type!] -] - -all: native [ - {Shortcut AND. Evaluates and returns at the first FALSE or NONE.} - block [block!] {Block of expressions} -] - -any: native [ - {Shortcut OR. Evaluates and returns the first value that is not FALSE or NONE.} - block [block!] {Block of expressions} -] - -apply: native [ - {Apply a function to a reduced block of arguments.} - func [any-function!] "Function value to apply" - block [block!] "Block of args, reduced first (unless /only)" - /only "Use arg values as-is, do not reduce the block" -] - -assert: native [ - "Assert that condition is true, else cause an assertion error." - conditions [block!] - /type "Safely check datatypes of variables (words and paths)" -] - -attempt: native [ - "Tries to evaluate a block and returns result or NONE on error." - block [block!] -] - -break: native [ - {Breaks out of a loop, while, until, repeat, foreach, etc.} - /return {Forces the loop function to return a value} - value [any-type!] -] - -case: native [ - {Evaluates each condition, and when true, evaluates what follows it.} - block [block!] {Block of cases (conditions followed by values)} - /all {Evaluate all cases (do not stop at first true case)} -] - -catch: native [ - {Catches a throw from a block and returns its value.} - block [block!] {Block to evaluate} - /name {Catches a named throw} - word [word! block!] {One or more names} - /quit {Special catch for QUIT native} -] - -;cause: native [ -; {Force error processing on an error value.} -; error [error!] -;] - -comment: native [ - {Ignores the argument value and returns nothing.} - value {A string, block, file, etc.} -] - -compose: native [ - {Evaluates a block of expressions, only evaluating parens, and returns a block.} - value "Block to compose" - /deep "Compose nested blocks" - /only {Insert a block as a single value (not the contents of the block)} - /into {Output results into a block with no intermediate storage} - out [any-block!] -] - -context: native [ - {Creates an object.} - spec ; [block!] -- no check required, we know it is correct -] - -continue: native [ - {Throws control back to top of loop.} -] - -;dir?: native [ -; {Returns true if file is a directory.} -; file [any-string! none!] -; /any {Allow * or ? wildcards for directory} -;] - -;disarm: native [ -; {(Deprecated - not needed) Converts error to an object. Other types not modified.} -; error [any-type!] -;] - -do: native [ - {Evaluates a block, file, URL, function, word, or any other value.} - value [any-type!] "Normally a file name, URL, or block" - /args "If value is a script, this will set its system/script/args" - arg "Args passed to a script (normally a string)" - /next "Do next expression only, return it, update block variable" - var [word!] "Variable updated with new block position" -] - -;eval: native [ -; {Evaluates a block, file, URL, function, word, or any other value.} -; value "Normally a file name, URL, or block" -;] - -either: native [ - {If TRUE condition return first arg, else second; evaluate blocks by default.} - condition - true-branch - false-branch - /only "Suppress evaluation of block args." -] - -exit: native [ - {Exits a function, returning no value.} -] - -find-script: native [ - {Find a script header within a binary string. Returns starting position.} - script [binary!] -] - -for: native [ - {Evaluate a block over a range of values. (See also: REPEAT)} - 'word [word!] "Variable to hold current value" - start [series! number!] "Starting value" - end [series! number!] "Ending value" - bump [number!] "Amount to skip each time" - body [block!] "Block to evaluate" -] - -forall: native [ - "Evaluates a block for every value in a series." - 'word [word!] {Word that refers to the series, set to each position in series} - body [block!] "Block to evaluate each time" -] - -forever: native [ - {Evaluates a block endlessly.} - body [block!] {Block to evaluate each time} -] - -foreach: native [ - {Evaluates a block for each value(s) in a series.} - 'word [word! block!] {Word or block of words to set each time (local)} - data [series! any-object! map! none!] {The series to traverse} - body [block!] {Block to evaluate each time} -] - -forskip: native [ - "Evaluates a block for periodic values in a series." - 'word [word!] {Word that refers to the series, set to each position in series} - size [integer! decimal!] "Number of positions to skip each time" - body [block!] "Block to evaluate each time" - /local orig result -] - -halt: native [ - {Stops evaluation and returns to the input prompt.} -] - -if: native [ - {If TRUE condition, return arg; evaluate blocks by default.} - condition - true-branch - /only "Return block arg instead of evaluating it." -] - -loop: native [ - {Evaluates a block a specified number of times.} - count [number!] {Number of repetitions} - block [block!] {Block to evaluate} -] - -map-each: native [ - {Evaluates a block for each value(s) in a series and returns them as a block.} - 'word [word! block!] {Word or block of words to set each time (local)} - data [block! vector!] {The series to traverse} - body [block!] {Block to evaluate each time} -] - -;replace-all: native [ -; "Search and replace multiple values with a series; returns a new series." -; target [block! string! binary!] -; values [block!] "A block of [old new] search/replace pairs" -;] - -quit: native [ - {Stops evaluation and exits the interpreter.} - /return {Returns a value (to prior script or command shell)} - value {Note: use integers for command shell} - /now {Quit immediately} -] - -protect: native [ - "Protect a series or a variable from being modified." - value [word! series! bitset! map! object! module!] - /deep "Protect all sub-series/objects as well" - /words "Process list as words (and path words)" - /values "Process list of values (implied GET)" - /hide "Hide variables (avoid binding and lookup)" -] - -unprotect: native [ - "Unprotect a series or a variable (it can again be modified)." - value [word! series! bitset! map! object! module!] - /deep "Protect all sub-series as well" - /words "Block is a list of words" - /values "Process list of values (implied GET)" -] - -recycle: native [ - {Recycles unused memory.} - /off {Disable auto-recycling} - /on {Enable auto-recycling} - /ballast {Trigger for auto-recycle (memory used)} - size [integer!] - /torture {Constant recycle (for internal debugging)} -] - -reduce: native [ - {Evaluates expressions and returns multiple results.} - value - /no-set {Keep set-words as-is. Do not set them.} - /only {Only evaluate words and paths, not functions} - words [block! none!] {Optional words that are not evaluated (keywords)} - /into {Output results into a block with no intermediate storage} - out [any-block!] -] - -repeat: native [ - {Evaluates a block a number of times or over a series.} - 'word [word!] {Word to set each time} - value [number! series! none!] {Maximum number or series to traverse} - body [block!] {Block to evaluate each time} -] - -remove-each: native [ - {Removes values for each block that returns true; returns removal count.} - 'word [word! block!] {Word or block of words to set each time (local)} - data [series!] {The series to traverse (modified)} - body [block!] {Block to evaluate (return TRUE to remove)} -] - -return: native [ - {Returns a value from a function.} - value [any-type!] -] - -switch: native [ - "Selects a choice and evaluates the block that follows it." - value "Target value" - cases [block!] "Block of cases to check" - /default case "Default case if no others found" - /all "Evaluate all matches (not just first one)" -] - -throw: native [ - {Throws control back to a previous catch.} - value [any-type!] {Value returned from catch} - /name {Throws to a named catch} - word [word!] -] - -trace: native [ - {Enables and disables evaluation tracing and backtrace.} - mode [integer! logic!] - /back {Set mode ON to enable or integer for lines to display} - /function {Traces functions only (less output)} -; /stack {Show stack index} -] - -try: native [ - {Tries to DO a block and returns its value or an error.} - block [block!] - /except "On exception, evaluate this code block" - code [block! any-function!] -] - -unless: native [ - {If FALSE condition, return arg; evaluate blocks by default.} - condition - false-branch - /only "Return block arg instead of evaluating it." -] - -until: native [ - {Evaluates a block until it is TRUE. } - block [block!] -] - -while: native [ - {While a condition block is TRUE, evaluates another block.} - cond-block [block!] - body-block [block!] -] - -;-- Data Natives - nat_data.c - -;alias: native [ -; See CC#1835 -; {Creates an alternate spelling for a word.} -; word [word!] {Word to alias} -; name [string!] {Name of alias} -;] - -;as-binary: native [ -; {Coerces any type of string into a binary! datatype without copying it.} -; string [any-string!] -;] -; -;as-string: native [ -; {Coerces any type of string into a string! datatype without copying it.} -; string [any-string!] -;] - -bind: native [ - {Binds words to the specified context.} - word [block! any-word!] {A word or block (modified) (returned)} - context [any-word! any-object!] {A reference to the target context} - /copy {Bind and return a deep copy of a block, don't modify original} - /only {Bind only first block (not deep)} - /new {Add to context any new words found} - /set {Add to context any new set-words found} -] - -unbind: native [ - {Unbinds words from context.} - word [block! any-word!] {A word or block (modified) (returned)} - /deep "Process nested blocks" -] - -bound?: native [ - {Returns the context in which a word is bound.} - word [any-word!] -] - -collect-words: native [ - "Collect unique words used in a block (used for context construction)." - block [block!] - /deep "Include nested blocks" - /set "Only include set-words" - /ignore "Ignore prior words" - words [any-object! block! none!] "Words to ignore" -] - -checksum: native [ - {Computes a checksum, CRC, or hash.} - data [binary!] {Bytes to checksum} - /part length {Length of data} - /tcp {Returns an Internet TCP 16-bit checksum} - /secure {Returns a cryptographically secure checksum} - /hash {Returns a hash value} - size [integer!] {Size of the hash table} - /method {Method to use} - word [word!] {Methods: SHA1 MD5 CRC32} - /key {Returns keyed HMAC value} - key-value [any-string!] {Key to use} -] - -compress: native [ - {Compresses a string series and returns it.} - data [binary! string!] {If string, it will be UTF8 encoded} - /part length {Length of data (elements)} - /gzip {Use GZIP checksum} -] - -decompress: native [ - {Decompresses data. Result is binary.} - data [binary!] {Data to decompress} - /part length {Length of compressed data (must match end marker)} - /gzip {Use GZIP checksum} - /limit size {Error out if result is larger than this} -] - -construct: native [ - {Creates an object with scant (safe) evaluation.} - block [block! string! binary!] "Specification (modified)" - /with "Default object" object [object!] - /only "Values are kept as-is" -] - -debase: native [ - {Decodes binary-coded string (BASE-64 default) to binary value.} - value [binary! string!] {The string to decode} - /base {Binary base to use} - base-value [integer!] {The base to convert from: 64, 16, or 2} -] - -enbase: native [ - {Encodes a string into a binary-coded string (BASE-64 default).} - value [binary! string!] {If string, will be UTF8 encoded} - /base {Binary base to use} - base-value [integer!] {The base to convert to: 64, 16, or 2} -] - -decloak: native [ - {Decodes a binary string scrambled previously by encloak.} - data [binary!] "Binary series to descramble (modified)" - key [string! binary! integer!] "Encryption key or pass phrase" - /with "Use a string! key as-is (do not generate hash)" -] - -encloak: native [ - {Scrambles a binary string based on a key.} - data [binary!] "Binary series to scramble (modified)" - key [string! binary! integer!] "Encryption key or pass phrase" - /with "Use a string! key as-is (do not generate hash)" -] - -deline: native [ - "Converts string terminators to standard format, e.g. CRLF to LF." - string [any-string!] {(modified)} - /lines "Return block of lines (works for LF, CR, CR-LF endings) (no modify)" -] - -enline: native [ - "Converts string terminators to native OS format, e.g. LF to CRLF." - series [any-string! block!] {(modified)} -] - -detab: native [ - "Converts tabs to spaces (default tab size is 4)." - string [any-string!] {(modified)} - /size "Specifies the number of spaces per tab" - number [integer!] -] - -entab: native [ - "Converts spaces to tabs (default tab size is 4)." - string [any-string!] {(modified)} - /size "Specifies the number of spaces per tab" - number [integer!] -] - -delect: native [ - "Parses a common form of dialects. Returns updated input block." - dialect [object!] "Describes the words and datatypes of the dialect" - input [block!] "Input stream to parse" - output [block!] "Resulting values, ordered as defined (modified)" - /in "Search for var words in specific objects (contexts)" - where [block!] "Block of objects to search (non objects ignored)" - /all "Parse entire block, not just one command at a time" -] - -difference: native [ - {Returns the special difference of two values.} - set1 [block! string! binary! bitset! date! typeset!] "First data set" - set2 [block! string! binary! bitset! date! typeset!] "Second data set" - /case {Uses case-sensitive comparison} - /skip {Treat the series as records of fixed size} - size [integer!] -] - -exclude: native [ - {Returns the first data set less the second data set.} - set1 [block! string! binary! bitset! typeset!] "First data set" - set2 [block! string! binary! bitset! typeset!] "Second data set" - /case {Uses case-sensitive comparison} - /skip {Treat the series as records of fixed size} - size [integer!] -] - -intersect: native [ - {Returns the intersection of two data sets.} - set1 [block! string! binary! bitset! typeset!] "first set" - set2 [block! string! binary! bitset! typeset!] "second set" - /case {Uses case-sensitive comparison} - /skip {Treat the series as records of fixed size} - size [integer!] -] - -union: native [ - {Returns the union of two data sets.} - set1 [block! string! binary! bitset! typeset!] "first set" - set2 [block! string! binary! bitset! typeset!] "second set" - /case {Use case-sensitive comparison} - /skip {Treat the series as records of fixed size} - size [integer!] -] - -unique: native [ - {Returns the data set with duplicates removed.} - set1 [block! string! binary! bitset! typeset!] - /case {Use case-sensitive comparison (except bitsets)} - /skip {Treat the series as records of fixed size} - size [integer!] -] - -lowercase: native [ - "Converts string of characters to lowercase." - string [any-string! char!] {(modified if series)} - /part {Limits to a given length or position} - length [number! any-string!] -] - -uppercase: native [ - "Converts string of characters to uppercase." - string [any-string! char!] {(modified if series)} - /part {Limits to a given length or position} - length [number! any-string!] -] - -dehex: native [ - {Converts URL-style hex encoded (%xx) strings.} - value [any-string!] {The string to dehex} -] - -get: native [ - {Gets the value of a word or path, or values of an object.} - word {Word, path, object to get} - /any {Allows word to have no value (allows unset)} -] - -in: native [ - {Returns the word or block in the object's context.} - object [any-object! block!] - word [any-word! block! paren!] {(modified if series)} -] - -parse: native [ - {Parses a string or block series according to grammar rules.} - input [series!] {Input series to parse} - rules [block! string! char! none!] {Rules to parse by (none = ",;")} - /all {For simple rules (not blocks) parse all chars including whitespace} - /case {Uses case-sensitive comparison} -] - -set: native [ - {Sets a word, path, block of words, or object to specified value(s).} - word [any-word! any-path! block! object!] {Word, block of words, path, or object to be set (modified)} - value [any-type!] {Value or block of values} - /any {Allows setting words to any value, including unset} - /pad {For objects, if block is too short, remaining words are set to NONE} -] - -to-hex: native [ - {Converts numeric value to a hex issue! datatype (with leading # and 0's).} - value [integer! tuple!] {Value to be converted} - /size {Specify number of hex digits in result} - len [integer!] -] - -type?: native [ - {Returns the datatype of a value.} - value [any-type!] - /word {Returns the datatype as a word} -] - -unset: native [ - {Unsets the value of a word (in its current context.)} - word [word! block!] {Word or block of words} -] - -utf?: native [ - {Returns UTF BOM (byte order marker) encoding; + for BE, - for LE.} - data [binary!] -] - -invalid-utf?: native [ - {Checks UTF encoding; if correct, returns none else position of error.} - data [binary!] - /utf "Check encodings other than UTF-8" - num [integer!] "Bit size - positive for BE negative for LE" -] - -value?: native [ - {Returns TRUE if the word has a value.} - value -] - -;-- IO Natives - nat_io.c - -print: native [ - {Outputs a value followed by a line break.} - value [any-type!] {The value to print} -] - -prin: native [ - {Outputs a value with no line break.} - value [any-type!] -] - -mold: native [ - {Converts a value to a REBOL-readable string.} - value [any-type!] {The value to mold} - /only {For a block value, mold only its contents, no outer []} - /all {Use construction syntax} - /flat {No indentation} -] - -form: native [ - {Converts a value to a human-readable string.} - value [any-type!] {The value to form} -] - -new-line: native [ - {Sets or clears the new-line marker within a block or paren.} - position [block! paren!] {Position to change marker (modified)} - value {Set TRUE for newline} - /all {Set/clear marker to end of series} - /skip {Set/clear marker periodically to the end of the series} - size [integer!] -] - -new-line?: native [ - {Returns the state of the new-line marker within a block or paren.} - position [block! paren!] {Position to check marker} -] - -to-local-file: native [ - {Converts a REBOL file path to the local system file path.} - path [file! string!] - /full "Prepends current dir for full path (for relative paths only)" -] - -to-rebol-file: native [ - {Converts a local system file path to a REBOL file path.} - path [file! string!] -] - -transcode: native [ - {Translates UTF-8 binary source to values. Returns [value binary].} - source [binary!] "Must be Unicode UTF-8 encoded" - /next "Translate next complete value (blocks as single value)" - /only "Translate only a single value (blocks dissected)" - /error "Do not cause errors - return error object as value in place" -] - -echo: native [ - {Copies console output to a file.} - target [file! none! logic!] -] - -now: native [ - {Returns date and time.} - /year {Returns year only} - /month {Returns month only} - /day {Returns day of the month only} - /time {Returns time only} - /zone {Returns time zone offset from UCT (GMT) only} - /date {Returns date only} - /weekday {Returns day of the week as integer (Monday is day 1)} - /yearday {Returns day of the year (Julian)} - /precise {High precision time} - /utc {Universal time (no zone)} -] - -wait: native [ - {Waits for a duration, port, or both.} - value [number! time! port! block! none!] - /all {Returns all in a block} -] - -wake-up: native [ - {Awake and update a port with event.} - port [port!] - event [event!] -] - -what-dir: native ["Returns the current directory path."] - -change-dir: native [ - "Changes the current directory path." - path [file!] -] - -;-- Series Natives - -first: native [ - {Returns the first value of a series.} - value -] - -second: native [ - {Returns the second value of a series.} - value -] - -third: native [ - {Returns the third value of a series.} - value -] - -fourth: native [ - {Returns the fourth value of a series.} - value -] - -fifth: native [ - {Returns the fifth value of a series.} - value -] - -sixth: native [ - {Returns the sixth value of a series.} - value -] - -seventh: native [ - {Returns the seventh value of a series.} - value -] - -eighth: native [ - {Returns the eighth value of a series.} - value -] - -ninth: native [ - {Returns the ninth value of a series.} - value -] - -tenth: native [ - {Returns the tenth value of a series.} - value -] - -last: native [ - {Returns the last value of a series.} - value [series! tuple! gob!] -] - -;-- Math Natives - nat_math.c - -cosine: native [ - {Returns the trigonometric cosine.} - value [number!] {In degrees by default} - /radians {Value is specified in radians} -] - -sine: native [ - {Returns the trigonometric sine.} - value [number!] {In degrees by default} - /radians {Value is specified in radians} -] - -tangent: native [ - {Returns the trigonometric tangent.} - value [number!] {In degrees by default} - /radians {Value is specified in radians} -] - -arccosine: native [ - {Returns the trigonometric arccosine (in degrees by default).} - value [number!] - /radians {Returns result in radians} -] - -arcsine: native [ - {Returns the trigonometric arcsine (in degrees by default).} - value [number!] - /radians {Returns result in radians} -] - -arctangent: native [ - {Returns the trigonometric arctangent (in degrees by default).} - value [number!] - /radians {Returns result in radians} -] - -exp: native [ - {Raises E (the base of natural logarithm) to the power specified} - power [number!] -] - -log-10: native [ - {Returns the base-10 logarithm.} - value [number!] -] - -log-2: native [ - {Return the base-2 logarithm.} - value [number!] -] - -log-e: native [ - {Returns the natural (base-E) logarithm of the given value} - value [number!] -] - -not: native [ - {Returns the logic complement.} - value {(Only FALSE and NONE return TRUE)} -] - -square-root: native [ - {Returns the square root of a number.} - value [number!] -] - -shift: native [ - {Shifts an integer left or right by a number of bits.} - value [integer!] - bits [integer!] "Positive for left shift, negative for right shift" - /logical "Logical shift (sign bit ignored)" -] - -;-- New, hackish stuff: - -++: native [ - {Increment an integer or series index. Return its prior value.} - 'word [word!] "Integer or series variable" -] - ---: native [ - {Decrement an integer or series index. Return its prior value.} - 'word [word!] "Integer or series variable" -] - -first+: native [ - {Return the FIRST of a series then increment the series index.} - 'word [word!] "Word must refer to a series" -] - -stack: native [ - {Returns stack backtrace or other values.} - offset [integer!] "Relative backward offset" - /block "Block evaluation position" - /word "Function or object name, if known" - /func "Function value" - /args "Block of args (may be modified)" - /size "Current stack size (in value units)" - /depth "Stack depth (frames)" - /limit "Stack bounds (auto expanding)" -] - -resolve: native [ - {Copy context by setting values in the target from those in the source.} - target [any-object!] {(modified)} - source [any-object!] - /only from [block! integer!] "Only specific words (exports) or new words in target (index to tail)" - /all "Set all words, even those in the target that already have a value" - /extend "Add source words to the target if necessary" -] - -;in-context: native [ -; {Set the default context for global words.} -; context [object!] -;] - -get-env: native [ - {Returns the value of an OS environment variable (for current process).} - var [any-string! any-word!] -] - -set-env: native [ - {Sets the value of an operating system environment variable (for current process).} - var [any-string! any-word!] "Variable to set" - value [string! none!] "Value to set, or NONE to unset it" -] - -list-env: native [ - {Returns a map of OS environment variables (for current process).} -] - -call: native [ - {Run another program; return immediately.} - command [string!] "An OS-local command line, quoted as necessary" - /wait "Wait for command to terminate before returning" -] - -browse: native [ - {Open web browser to a URL or local file.} - url [url! file! none!] -] - -evoke: native [ - {Special guru meditations. (Not for beginners.)} - chant [word! block! integer!] "Single or block of words ('? to list)" -] - -request-file: native [ - {Asks user to select a file and returns full file path (or block of paths).} - /save "File save mode" - /multi "Allows multiple file selection, returned as a block" - /file name [file!] "Default file name or directory" - /title text [string!] "Window title" - /filter list [block!] "Block of filters (filter-name filter)" -] - -ascii?: native [ - {Returns TRUE if value or string is in ASCII character range (below 128).} - value [any-string! char! integer!] -] - -latin1?: native [ - {Returns TRUE if value or string is in Latin-1 character range (below 256).} - value [any-string! char! integer!] -] - -; Temps... - -stats: native [ - {Provides status and statistics information about the interpreter.} - /show {Print formatted results to console} - /profile {Returns profiler object} - /timer {High resolution time difference from start} - /evals {Number of values evaluated by interpreter} -] - -do-codec: native [ - {Evaluate a CODEC function to encode or decode media types.} - handle [handle!] "Internal link to codec" - action [word!] "Decode, encode, identify" - data [binary! image!] -] - -set-scheme: native [ - "Low-level port scheme actor initialization." - scheme [object!] -] - -load-extension: native [ - "Low level extension module loader (for DLLs)." - name [file! binary!] "DLL file or UTF-8 source" - /dispatch "Specify native command dispatch (from hosted extensions)" - function [handle!] "Command dispatcher (native)" -] - -do-commands: native [ - "Evaluate a block of extension module command functions (special evaluation rules.)" - commands [block!] "Series of commands and their arguments" -] - -ds: native ["Temporary stack debug"] -dump: native ["Temporary debug dump" v] -check: native ["Temporary series debug check" val [series!]] - -do-callback: native [ - "Internal function to process callback events." - event [event!] "Callback event" -] - - -limit-usage: native [ - "Set a usage limit only once (used for SECURE)." - field [word!] "eval (count) or memory (bytes)" - limit [number!] -] - -selfless?: native [ - "Returns true if the context doesn't bind 'self." - context [any-word! any-object!] "A reference to the target context" -] - -map-event: native [ - "Returns event with inner-most graphical object and coordinate." - event [event!] -] - -map-gob-offset: native [ - "Translates a gob and offset to the deepest gob and offset in it, returned as a block." - gob [gob!] "Starting object" - xy [pair!] "Staring offset" - /reverse "Translate from deeper gob to top gob." -] - -as-pair: native [ - "Combine X and Y values into a pair." - x [number!] - y [number!] -] - -;read-file: native [f [file!]] - -equal?: native [ - {Returns TRUE if the values are equal.} - value1 [any-type!] - value2 [any-type!] -] - -not-equal?: native [ - {Returns TRUE if the values are not equal.} - value1 [any-type!] - value2 [any-type!] -] - -equiv?: native [ - {Returns TRUE if the values are equivalent.} - value1 [any-type!] - value2 [any-type!] -] - -not-equiv?: native [ - {Returns TRUE if the values are not equivalent.} - value1 [any-type!] - value2 [any-type!] -] - -strict-equal?: native [ - {Returns TRUE if the values are strictly equal.} - value1 [any-type!] - value2 [any-type!] -] - -strict-not-equal?: native [ - {Returns TRUE if the values are not strictly equal.} - value1 [any-type!] - value2 [any-type!] -] - -same?: native [ - {Returns TRUE if the values are identical.} - value1 [any-type!] - value2 [any-type!] -] - -greater?: native [ ; Note: some datatypes expect >, <, >=, <= to be in this order. - {Returns TRUE if the first value is greater than the second value.} - value1 value2 -] - -greater-or-equal?: native [ - {Returns TRUE if the first value is greater than or equal to the second value.} - value1 value2 -] - -lesser?: native [ - {Returns TRUE if the first value is less than the second value.} - value1 value2 -] - -lesser-or-equal?: native [ - {Returns TRUE if the first value is less than or equal to the second value.} - value1 value2 -] - -minimum: native [ - {Returns the lesser of the two values.} - value1 [scalar! date! series!] - value2 [scalar! date! series!] -] - -maximum: native [ ; Note: Some datatypes expect all binary ops to be <= this - {Returns the greater of the two values.} - value1 [scalar! date! series!] - value2 [scalar! date! series!] -] - -negative?: native [ - {Returns TRUE if the number is negative.} - number [number! money! time! pair!] -] - -positive?: native [ - {Returns TRUE if the value is positive.} - number [number! money! time! pair!] -] - -zero?: native [ - {Returns TRUE if the value is zero (for its datatype).} - value -] diff --git a/src/boot/ops.r b/src/boot/ops.r deleted file mode 100644 index a2b016ab14..0000000000 --- a/src/boot/ops.r +++ /dev/null @@ -1,37 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Infix operator symbol definitions" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Purpose: { - This table maps infix operator symbols to function names. - } -] - -+ add -- subtract -* multiply -/ divide -// remainder -** power -= equal? -=? same? -== strict-equal? -!= not-equal? -<> not-equal? -!== strict-not-equal? -< lesser? -<= lesser-or-equal? -> greater? ->= greater-or-equal? -& and~ -| or~ -and and~ -or or~ -xor xor~ diff --git a/src/boot/platforms.r b/src/boot/platforms.r index c4f454fbcc..196c27b739 100644 --- a/src/boot/platforms.r +++ b/src/boot/platforms.r @@ -1,31 +1,102 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Platform definitions" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Purpose: { - Platform identification found in system object. - } -] - -Amiga [1 m68k20+ 2 m68k 3 ppc] -Macintosh [1 mac-ppc 2 mac-m68k 3 mac-misc 4 osx-ppc 5 osx-x86] -Windows [1 win32-x86 2 dec-alpha] -Linux [1 libc5-x86 2 libc6-2-3-x86 3 libc6-2-5-x86 4 libc6-2-11-x86 10 libc6-ppc 20 libc6-arm 21 bionic-arm 30 libc6-mips] -Haiku [75 x86-32] -BSDi [1 x86] -FreeBSD [1 x86 2 elf-x86] -NetBSD [1 x86 2 ppc 3 m68k 4 dec-alpha 5 sparc] -OpenBSD [1 x86 2 ppc 3 m68k 4 elf-x86 5 sparc] -Sun [1 sparc] -SGI [] -HP [] -Android [1 arm] -free-slot [] -WindowsCE [1 sh3 2 mips 5 arm 6 sh4] + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Platform definitions" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0. + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + Platform identification found in system object. + } +] + +1 Amiga [ + 1 m68k20+ + 2 m68k + 3 ppc +] + +2 Macintosh [ + 1 mac-ppc + 2 mac-m68k + 3 mac-misc + 4 osx-ppc + 5 osx-x86 +] + +3 Windows [ + 1 win32-x86 + 2 dec-alpha + 40 win32-x64 +] + +4 Linux [ + 1 libc5-x86 + 2 libc6-2-3-x86 + 3 libc6-2-5-x86 + 4 libc6-2-11-x86 + 10 libc6-ppc + 11 libc6-ppc64 + 20 libc6-arm + 22 libc6-aarch64 + 30 libc6-mips + 31 libc6-mips32be + 40 libc-x64 + 60 dec-alpha + 61 libc-ia64 +] + +5 Haiku [ + 75 x86-32 +] + +6 BSDi [ + 1 x86 +] + +7 FreeBSD [ + 1 x86 + 2 elf-x86 +] + +8 NetBSD [ + 1 x86 + 2 ppc + 3 m68k + 4 dec-alpha + 5 sparc +] + +9 OpenBSD [ + 1 x86 + 2 ppc + 3 m68k + 4 elf-x86 + 5 sparc + 40 elf-x64 +] + +10 Sun [ + 1 sparc +] + +11 SGI [] + +12 HP [] + +13 Android [ + 1 arm +] + +14 free-slot [] + +15 WindowsCE [ + 1 sh3 + 2 mips + 5 arm + 6 sh4 +] diff --git a/src/boot/root.r b/src/boot/root.r index b617cffc80..eec43d485b 100644 --- a/src/boot/root.r +++ b/src/boot/root.r @@ -1,31 +1,40 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Root context" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Purpose: { - Root system values. This context is hand-made very early at boot time - to allow it to hold key system values during boot up. Most of these - are put here to prevent them from being garbage collected. - } - Note: "See Task Context for per-task globals" + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Root context" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0. + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + Root system values. This context is hand-made very early at boot time + to allow it to hold key system values during boot up. Most of these + are put here to prevent them from being garbage collected. + } + Note: "See Task Context for per-task globals" ] -self ; (hand-built CONTEXT! value - but, has no WORD table!) -root ; the root context as a block (for GC protection) +system ; system object +typesets ; block of TYPESETs used by system; expandable +empty-block ; a value that is an empty BLOCK! +empty-string ; a value that is an empty STRING! -system ; system object -errobj ; error object template -strings ; low-level strings accessed via Boot_Strs[] (GC protection) -typesets ; block of TYPESETs used by system; expandable -noneval ; NONE value -noname ; noname function word +space-char ; a value that is a space CHAR! +newline-char ; a value that is a newline CHAR! -boot ; boot block defined in boot.r (GC'd after boot is done) +;; Tags used in the native-optimized versions of user-function-generators +;; FUNC and PROC +with-tag ; for no locals gather (disables RETURN/LEAVE in FUNC) +ellipsis-tag ; FUNC+PROC use as alternative to [[]] to mark varargs +opt-tag ; FUNC+PROC use as alternative to _ to mark optional void? args +end-tag ; FUNC+PROC use as alternative to | to mark endable args +local-tag ; marks the beginning of a list of "pure locals" +durable-tag ; !!! In progress - argument word lookup survives call ending + +;; !!! See notes on FUNCTION-META in %sysobj.r + +function-meta diff --git a/src/boot/shape.r b/src/boot/shape.r deleted file mode 100644 index dd3e100eef..0000000000 --- a/src/boot/shape.r +++ /dev/null @@ -1,137 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL Graphics - SHAPE commands" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Name: shape - Type: extension - Exports: none - Note: "Run make-host-ext.r to convert" -] - -;don't change order of already defined words unless you know what you are doing - -words: [ - ;arc - negative - positive - small - large -] - -;temp hack - will be removed later -init-words: command [ - words [block!] -] - -init-words words - -;please alphabetize the order of commands so it easier to lookup things - -arc: command [ - "Draws an elliptical arc from the current point." - end-point [pair!] - radius [pair!] - angle [number!] - 'sweep-flag [word!] "The arc will be drawn in POSITIVE or NEGATIVE angle direction" - 'arc-flag [word!] "User SMALL or LARGE arc sweep" -] - -arc': command [ - "Draws an elliptical arc from the current point.(uses relative coordinates)" - end-point [pair!] - radius [pair!] - angle [number!] - 'sweep-flag [word!] "The arc will be drawn in POSITIVE or NEGATIVE angle direction" - 'arc-flag [word!] "User SMALL or LARGE arc sweep" -] - -close: command [ - "Closes previously defined set of lines in the SHAPE block." -] - -curv: command [ - "Draws a cubic Bezier curve or polybezier using two points." - points [block!] "Block of point pairs (2nd control point, end point)" -] - -curv': command [ - "Draws a cubic Bezier curve or polybezier using two points.(uses relative coordinates)" - points [block!] "Block of point pairs (2nd control point, end point)" -] - -curve: command [ - "Draws a cubic Bezier curve or polybezier using three points." - points [block!] "Block of point triplets (1st control point, 2nd control point, end point)" -] - -curve': command [ - "Draws a cubic Bezier curve or polybezier using three points.(uses relative coordinates)" - points [block!] "Block of point triplets (1st control point, 2nd control point, end point)" -] - -hline: command [ - "Draws a horizontal line from the current point." - end-x [number!] -] - -hline': command [ - "Draws a horizontal line from the current point.(uses relative coordinates)" - end-x [number!] -] - -line: command [ - "Draws a line from the current point through the given points." - points [pair! block!] -] - -line': command [ - "Draws a line from the current point through the given points.(uses relative coordinates)" - points [pair! block!] -] - -move: command [ - "Set's the starting point for a new path without drawing anything." - point [pair!] -] - -move': command [ - "Set's the starting point for a new path without drawing anything.(uses relative coordinates)" - point [pair!] -] - -qcurv: command [ - "Draws a quadratic Bezier curve from the current point to end point." - end-point [pair!] -] - -qcurv': command [ - "Draws a quadratic Bezier curve from the current point to end point.(uses relative coordinates)" - end-point [pair!] -] - -qcurve: command [ - "Draws a quadratic Bezier curve using two points." - points [block!] "Block of point pairs (control point, end point)" -] - -qcurve': command [ - "Draws a quadratic Bezier curve using two points.(uses relative coordinates)" - points [block!] "Block of point pairs (control point, end point)" -] - -vline: command [ - "Draws a vertical line from the current point." - end-y [number!] -] - -vline': command [ - "Draws a vertical line from the current point.(uses relative coordinates)" - end-y [number!] -] diff --git a/src/boot/strings.r b/src/boot/strings.r deleted file mode 100644 index 1afae127b8..0000000000 --- a/src/boot/strings.r +++ /dev/null @@ -1,149 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Low-level strings" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Purpose: { - This section holds lower-level C strings used in various parts - of the system. This section is unique, because it constructs a - single C string that contains all the specified strings below. - This is done to eliminate series headers required for normal - REBOL strings. The strings are referenced using Boot_Strs[RS_*] - where * is the set-word (and is zero based). For example: - RS_SCAN+3 refers to "end-of-paren" - } -] - -scan: ; Used by scanner. Keep in sync with Value_Types in scan.h file! - "end-of-script" - "line" - "end-of-block" - "end-of-paren" - "word" - "word-set" - "word-get" - "word-lit" - "none" - "logic" - "integer" - "decimal" - "percent" - "money" - "time" - "date" - "char" - "block" - "paren" - "string" - "binary" - "pair" - "tuple" - "file" - "email" - "url" - "issue" - "tag" - "path" - "refine" - "construct" - -info: - "Booting..." - -;secure: -; "Script requests permission to " -; "Script requests permission to lower security level" -; "REBOL - Security Violation" -; "unknown" - -;secopts: -; "open a port for read only on: " -; "open a port for read/write on: " -; "delete: " -; "rename: " -; "make a directory named: " -; "lower security" -; "execute a system shell command: " - -trace: - "trace" - "%-02d: %50r" - " : %50r" - " : %s %50m" - " : %s" - "--> %s" - "<-- %s ==" - "Parse match: %r" - "Parse input: %s" - "Parse back: %r" - "**: error : %r %r" ; 10 - -stack: - "STACK Expanded - DSP: %d MAX: %d" - "^/STACK[%d] %s[%d] %s" - -dump: - "^/--REBOL Kernel Dump--" - "Evaluator:" - " Cycles: %d" ; only lower bits - " Counter: %d" - " Dose: %d" - " Signals: %x" - " Sigmask: %x" - " DSP: %d" - " DSF: %d" - "Memory/GC:" - " Ballast: %d" - " Disable: %d" - " Protect: %d" - " Infants: %d" - -;stats: -; "Stats: bad series value: %d in: %x offset: %d size: %d" - -error: - "out of memory (req %d bytes)" - "invalid series width %d got %d type %d" -; "error catalog object out of range" -; "error num in category out of range" - "error already caught" - "stack overflow" - "I/O error" - "too many words" - "word list buffer in use" - "locked series" - "error recycled" - "top level error not caught" - "error state underflow" - "event queue overflow (WAIT recursion?)" - "not available (NA)" - -errs: - " error: " - "(improperly formatted error)" - "** Where: " - "** Near: " - -watch: - "RECYCLING: " - "%d series" - "obj-copy: %d %m" - -extension: - "RX_Init" - "RX_Quit" - "RX_Call" - -;plugin: -; "cannot open" -; "missing function" -; "wrong version" -; "no header" -; "bad header" -; "boot code failed" diff --git a/src/boot/sysobj.r b/src/boot/sysobj.r index 7bab968b3f..65178ab8fc 100644 --- a/src/boot/sysobj.r +++ b/src/boot/sysobj.r @@ -1,27 +1,28 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "System object" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Purpose: { - Defines the system object. This is a special block that is evaluted - such that its words do not get put into the current context. - } - Note: "Remove older/unused fields before beta release" + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "System object" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0. + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + Defines the system object. This is a special block that is evaluted + such that its words do not get put into the current context. + } + Note: "Remove older/unused fields before beta release" ] -product: 'core - -; Next three fields are updated during build: +; Next five fields are updated during build: version: 0.0.0 build: 1 -platform: none +platform: _ +commit: _ + +product: _ ;-- assigned by startup of the host ('core, 'view, 'ren-garden...) license: {Copyright 2012 REBOL Technologies REBOL is a trademark of REBOL Technologies @@ -29,488 +30,634 @@ Licensed under the Apache License, Version 2.0. See: http://www.apache.org/licenses/LICENSE-2.0 } -catalog: context [ - ; Static (non-changing) values, blocks, objects - datatypes: [] - actions: none - natives: none - errors: none - reflectors: [spec body words values types title] - ; Official list of system/options/flags that can appear. - ; Must match host reb-args.h enum! - boot-flags: [ - script args do import version debug secure - help vers quiet verbose - secure-min secure-max trace halt cgi boot-level no-window - ] +; !!! HAS is defined later, so this uses CONSTRUCT [] [body] instead. +; MAKE OBJECT! is not used because that is too low-level (no evaluation or +; collection of fields). Reconsider if base-funcs should be loaded before +; the system object here, or if it should be able to work with just the +; low level MAKE OBJECT! and not use things like `x: y: z: none` etc. + +catalog: construct [] [ + ; + ; These catalogs are filled in by Init_System_Object() + ; + datatypes: _ + actions: _ + natives: _ + errors: _ ] -contexts: context [ - root: - sys: - lib: - user: - none +contexts: construct [] [ + root: + sys: + lib: + user: + _ ] -state: context [ - ; Mutable system state variables - note: "contains protected hidden fields" - policies: context [ ; Security policies - file: ; file access - net: ; network access - eval: ; evaluation limit - memory: ; memory limit - secure: ; secure changes - protect: ; protect function - debug: ; debugging features - envr: ; read/write - call: ; execute only - browse: ; execute only - 0.0.0 - extension: 2.2.2 ; execute only - ] - last-error: none ; used by WHY? +state: construct [] [ + ; Mutable system state variables + note: "contains protected hidden fields" + policies: construct [] [ ; Security policies + file: ; file access + net: ; network access + eval: ; evaluation limit + memory: ; memory limit + secure: ; secure changes + protect: ; protect function + debug: ; debugging features + envr: ; read/write + call: ; execute only + browse: ; execute only + 0.0.0 + extension: 2.2.2 ; execute only + ] + last-error: _ ; used by WHY? ] -modules: [] +modules: [] ;loaded modules +extensions: [] ;loaded extensions -codecs: context [] - -dialects: context [ - secure: - draw: - effect: - text: - rebcode: - none -] +codecs: make object! [[][]] -schemes: context [] +schemes: make object! [[][]] -ports: context [ - wait-list: [] ; List of ports to add to 'wait - input: ; Port for user input. - output: ; Port for user output - echo: ; Port for echoing output - system: ; Port for system events - callback: none ; Port for callback events -; serial: none ; serial device name block +ports: construct [] [ + wait-list: [] ; List of ports to add to 'wait + input: ; Port for user input. + output: ; Port for user output + system: ; Port for system events + callback: _ ; Port for callback events +; serial: _ ; serial device name block ] -locale: context [ - language: ; Human language locale - language*: - locale: - locale*: none - months: [ - "January" "February" "March" "April" "May" "June" - "July" "August" "September" "October" "November" "December" - ] - days: [ - "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday" "Sunday" - ] +locale: construct [] [ + language: ; Human language locale + language*: _ + library: _ ;make object! [modules: utilities: https://raw.githubusercontent.com/r3n/renclib/master/usermodules.reb] + locale: + locale*: _ + months: [ + "January" "February" "March" "April" "May" "June" + "July" "August" "September" "October" "November" "December" + ] + days: [ + "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday" "Sunday" + ] ] -options: context [ ; Options supplied to REBOL during startup - boot: ; The path to the executable - home: ; Path of home directory - path: ; Where script was started or the startup dir - none - - flags: ; Boot flag bits (see system/catalog/boot-flags) - script: ; Filename of script to evaluate - args: ; Command line arguments passed to script - do-arg: ; Set to a block if --do was specified - import: ; imported modules - debug: ; debug flags - secure: ; security policy - version: ; script version needed - boot-level: ; how far to boot up - none - - quiet: false ; do not show startup info (compatibility) - - binary-base: 16 ; Default base for FORMed binary values (64, 16, 2) - decimal-digits: 15 ; Max number of decimal digits to print. - module-paths: [%./] - default-suffix: %.reb ; Used by IMPORT if no suffix is provided - file-types: [] - result-types: none +set in locale 'library construct [][ + modules: https://raw.githubusercontent.com/r3n/renclib/master/usermodules.reb + utilities: https://raw.githubusercontent.com/r3n/renclib/master/userutils.reb +] + +options: construct [] [ ; Options supplied to REBOL during startup + bin: _ ; Path to directory where Rebol executable binary lives + boot: _ ; Path of executable, ie. system/options/bin/r3-exe + home: _ ; Path of home directory + resources: _ ; users resources directory (for %user.r, skins, modules etc) + suppress: _ ; block of user --suppress items, eg [%rebol.r %user.r %console-skin.reb] + loaded: [] ; block with full paths to loaded start-up scripts + path: _ ; Where script was started or the startup dir + + current-path: _ ; Current URL! or FILE! path to use for relative lookups + + encap: _ ; The encapping data extracted + script: _ ; Filename of script to evaluate + args: _ ; Command line arguments passed to script + debug: _ ; debug flags + secure: _ ; security policy + version: _ ; script version needed + + dump-size: 68 ; used by dump + + quiet: false ; do not show startup info (compatibility) + about: false ; do not show full banner (about) on start-up + cgi: false + no-window: false + verbose: false + + binary-base: 16 ; Default base for FORMed binary values (64, 16, 2) + decimal-digits: 15 ; Max number of decimal digits to print. + module-paths: [%./] + default-suffix: %.reb ; Used by IMPORT if no suffix is provided + file-types: [] + result-types: _ + + ; Legacy Behaviors Options (paid attention to only by debug builds) + + lit-word-decay: false + exit-functions-only: false + broken-case-semantics: false + refinements-blank: false + forever-64-bit-ints: false + print-forms-everything: false + break-with-overrides: false + none-instead-of-voids: false + dont-exit-natives: false + paren-instead-of-group: false + get-will-get-anything: false + no-reduce-nested-print: false + unlocked-source: false + + ; These option will only apply if the function which is currently executing + ; was created after legacy mode was enabled, and if refinements-blank is + ; set (because that's what marks functions as "legacy" or not") + ; + no-switch-evals: false + no-switch-fallthrough: false ] -script: context [ - title: ; Title string of script - header: ; Script header as evaluated - parent: ; Script that loaded the current one - path: ; Location of the script being evaluated - args: ; args passed to script - none +script: construct [] [ + title: ; Title string of script + header: ; Script header as evaluated + parent: ; Script that loaded the current one + path: ; Location of the script being evaluated + args: ; args passed to script + _ ] -standard: context [ - - error: context [ ; Template used for all errors: - code: 0 - type: 'user - id: 'message - arg1: - arg2: - arg3: - near: - where: - none - ] - - script: context [ - title: - header: - parent: - path: - args: - none - ] - - header: context [ - title: {Untitled} - name: - type: - version: - date: - file: - author: - needs: - options: - checksum: -; compress: -; exports: -; content: - none - ] - - scheme: context [ - name: ; word of http, ftp, sound, etc. - title: ; user-friendly title for the scheme - spec: ; custom spec for scheme (if needed) - info: ; prototype info object returned from query -; kind: ; network, file, driver -; type: ; bytes, integers, objects, values, block - actor: ; standard action handler for scheme port functions - awake: ; standard awake handler for this scheme's ports - none - ] - - port: context [ ; Port specification object - spec: ; published specification of the port - scheme: ; scheme object used for this port - actor: ; port action handler (script driven) - awake: ; port awake function (event driven) - state: ; internal state values (private) - data: ; data buffer (usually binary or block) - locals: ; user-defined storage of local data -; stats: ; stats on operation (optional) - none - ] - - port-spec-head: context [ - title: ; user-friendly title for port - scheme: ; reference to scheme that defines this port - ref: ; reference path or url (for errors) - path: ; used for files - none ; (extended here) - ] - - port-spec-net: make port-spec-head [ - host: none - port-id: 80 - none - ] - - file-info: context [ - name: - size: - date: - type: - none - ] - - net-info: context [ - local-ip: - local-port: - remote-ip: - remote-port: - none - ] - - extension: context [ - lib-base: ; handle to DLL - lib-file: ; file name loaded - lib-boot: ; module header and body - command: ; command function - cmd-index: ; command index counter - words: ; symbol references - none - ] - - stats: context [ ; port stats - timer: ; timer (nanos) - evals: ; evaluations - eval-natives: - eval-functions: - series-made: - series-freed: - series-expanded: - series-bytes: - series-recycled: - made-blocks: - made-objects: - recycles: - none - ] - - type-spec: context [ - title: - type: - none - ] - - utype: none - font: none ; mezz-graphics.h - para: none ; mezz-graphics.h +standard: construct [] [ + ; FUNC+PROC implement a native-optimized variant of a function generator. + ; This is the body template that it provides as the code *equivalent* of + ; what it is doing (via a more specialized/internal method). Though + ; the only "real" body stored and used is the one the user provided + ; (substituted in #BODY), this template is used to "lie" when asked what + ; the BODY-OF the function is. + ; + ; The substitution location is hardcoded at index 5. It does not "scan" + ; to find #BODY, just asserts the position is an ISSUE!. + ; + func-body: [ + return: make function! [ + [{Returns a value from a function.} value [ any-value!]] + [exit/from/with (context-of 'return) :value] + ] + leave: make function! [ + [{Leaves a function, giving no result to the caller.}] + [exit/from (context-of 'leave)] + ] + #BODY + ] + + func-no-leave-body: [ + return: make function! [ + [{Returns a value from a function.} value [ any-value!]] + [exit/from/with (context-of 'return) :value] + ] + #BODY + ] + + proc-body: [ + leave: make function! [ + [{Leaves a procedure, giving no result to the caller.}] + [exit/from (context-of 'leave)] + ] + #BODY + comment {No return value.} + ] + + ; !!! The PORT! and actor code is deprecated, but this bridges it so + ; it doesn't have to build a spec by hand. + ; + port-actor-spec: [port-actor-parameter [ any-value!]] + + ; !!! The %sysobj.r initialization currently runs natives (notably the + ; natives for making objects, and here using COMMENT because it can). + ; This means that if the FUNCTION-META information is going to be produced + ; from a spec block for natives, it wouldn't be available while the + ; natives are getting initialized. + ; + ; It may be desirable to sort out this dependency by using a construction + ; syntax and making this a MAP! or OBJECT! literal. In the meantime, + ; the archetypal context has to be created "by hand" for natives to use, + ; with this archetype used by the REDESCRIBE Mezzanine. + ; + function-meta: construct [] [ + description: + return-type: + return-note: + parameter-types: + parameter-notes: + _ + ] + + ; The common case is that derived functions will not need to be + ; REDESCRIBE'd besides their title. If they are, then they switch the + ; meta archetype to `function-meta` and subset the parameters. Otherwise + ; HELP just follows the link (`specializee`, `adaptee`) and gets + ; descriptions there. + ; + ; !!! Due to wanting R3-Alpha to be able to run the bootstrap build, + ; these objects can't unset these fields. (make object! [x: ()] fails) + ; Hence the code has to overwrite the missing fields with voids. + + specialized-meta: construct [] [ + description: + specializee: + specializee-name: + _ + ] + + adapted-meta: construct [] [ + description: + adaptee: + adaptee-name: + _ + ] + + chained-meta: construct [] [ + description: + chainees: + chainee-names: + _ + ] + + ; !!! This is the template used for all errors, to which extra fields are + ; added if the error has parameters. It likely makes sense to put this + ; information into the META-OF of the error, so that parameterizing the + ; error does not require a keylist expansion...and also so that fields + ; like FILE and LINE would not conflict with parameters. + ; + error: construct [] [ + code: _ + type: _ + id: _ + message: _ ; a BLOCK! template with arg substitution or just a STRING! + near: _ + where: _ + file: _ + line: _ + + ; Arguments will be allocated in the context at creation time if + ; necessary (errors with no arguments will just have a message) + ] + + script: construct [] [ + title: + header: + parent: + path: + args: + _ + ] + + header: construct [] [ + title: {Untitled} + name: + type: + version: + date: + file: + author: + needs: + options: + checksum: +; compress: +; exports: +; content: + _ + ] + + scheme: construct [] [ + name: ; word of http, ftp, sound, etc. + title: ; user-friendly title for the scheme + spec: ; custom spec for scheme (if needed) + info: ; prototype info object returned from query +; kind: ; network, file, driver +; type: ; bytes, integers, objects, values, block + actor: ; standard action handler for scheme port functions + awake: ; standard awake handler for this scheme's ports + _ + ] + + port: construct [] [ ; Port specification object + spec: ; published specification of the port + scheme: ; scheme object used for this port + actor: ; port action handler (script driven) + awake: ; port awake function (event driven) + state: ; internal state values (private) + data: ; data buffer (usually binary or block) + locals: ; user-defined storage of local data +; stats: ; stats on operation (optional) + _ + ] + + port-spec-head: construct [] [ + title: ; user-friendly title for port + scheme: ; reference to scheme that defines this port + ref: ; reference path or url (for errors) + path: ; used for files + _ ; (extended here) + ] + + port-spec-net: construct port-spec-head [ + host: _ + port-id: 80 + ] + + port-spec-serial: construct port-spec-head [ + speed: 115200 + data-size: 8 + parity: _ + stop-bits: 1 + flow-control: _ ;not supported on all systems + ] + + port-spec-signal: construct port-spec-head [ + mask: [all] + ] + + file-info: construct [] [ + name: + size: + date: + type: + _ + ] + + net-info: construct [] [ + local-ip: + local-port: + remote-ip: + remote-port: + _ + ] + + extension: construct [] [ + header: ; extension hader: name, version, etc. + script: ; script to run after the extension is loaded. Could be + ; string! (uncompressed code) or binary! (compressed code). + ; Could be folded into "header", because it's only used to + ; pass script from the extension to LOAD-EXTENSION, and + ; cleared once the extensino is loaded. But keeping it + ; separate makes its purpose clear... + lib-base: ; handle to DLL + lib-file: ; file name loaded + modules: + _ + ] + + stats: construct [] [ ; port stats + timer: ; timer (nanos) + evals: ; evaluations + eval-natives: + eval-functions: + series-made: + series-freed: + series-expanded: + series-bytes: + series-recycled: + made-blocks: + made-objects: + recycles: + _ + ] + + type-spec: construct [] [ + title: + type: + _ + ] + + utype: _ + font: _ ; mezz-graphics.h + para: _ ; mezz-graphics.h ] -view: context [ - screen-gob: none - handler: none - event-port: none - metrics: context [ - screen-size: 0x0 - border-size: 0x0 - border-fixed: 0x0 - title-size: 0x0 - work-origin: 0x0 - work-size: 0x0 - ] - event-types: [ - ; Event types. Order dependent for C and REBOL. - ; Due to fixed C constants, this list cannot be reordered after release! - ignore ; ignore event (0) - interrupt ; user interrupt - device ; misc device request - callback ; callback event - custom ; custom events - error - init - - open - close - connect - accept - read - write - wrote - lookup - - ready - done - time - - show - hide - offset - resize - active - inactive - minimize - maximize - restore - - move - down - up - alt-down - alt-up - aux-down - aux-up - key - key-up ; Move above when version changes!!! - - scroll-line - scroll-page - - drop-file - ] - event-keys: [ - ; Event types. Order dependent for C and REBOL. - ; Due to fixed C constants, this list cannot be reordered after release! - page-up - page-down - end - home - left - up - right - down - insert - delete - f1 - f2 - f3 - f4 - f5 - f6 - f7 - f8 - f9 - f10 - f11 - f12 - ] +view: construct [] [ + screen-gob: _ + handler: _ + event-port: _ + event-types: [ + ; Event types. Order dependent for C and REBOL. + ; Due to fixed C constants, this list cannot be reordered after release! + ignore ; ignore event (0) + interrupt ; user interrupt + device ; misc device request + callback ; callback event + custom ; custom events + error + init + + open + close + connect + accept + read + write + wrote + lookup + + ready + done + time + + show + hide + offset + resize + rotate + active + inactive + minimize + maximize + restore + + move + down + up + alt-down + alt-up + aux-down + aux-up + key + key-up ; Move above when version changes!!! + + scroll-line + scroll-page + + drop-file + ] + event-keys: [ + ; Event types. Order dependent for C and REBOL. + ; Due to fixed C constants, this list cannot be reordered after release! + page-up + page-down + end + home + left + up + right + down + insert + delete + f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 + f9 + f10 + f11 + f12 + ] ] -;;stats: none +;;stats: _ ;user-license: context [ -; name: -; email: -; id: -; message: -; none +; name: +; email: +; id: +; message: +; _ ;] ; (returns value) -; model: ; Network, File, Driver -; type: ; bytes, integers, values -; user: ; User data - -; host: -; port-id: -; user: -; pass: -; target: -; path: -; proxy: -; access: -; allow: -; buffer-size: -; limit: -; handler: -; status: -; size: -; date: -; sub-port: -; locals: -; state: -; timeout: -; local-ip: -; local-service: -; remote-service: -; last-remote-service: -; direction: -; key: -; strength: -; algorithm: -; block-chaining: -; init-vector: -; padding: -; async-modes: -; remote-ip: -; local-port: -; remote-port: -; backlog: -; device: -; speed: -; data-bits: -; parity: -; stop-bits: -; none -; rts-cts: true -; user-data: -; awake: - -; port-flags: context [ -; direct: -; pass-thru: -; open-append: -; open-new: -; none -; ] - -; email: context [ ; Email header object -; To: -; CC: -; BCC: -; From: -; Reply-To: -; Date: -; Subject: -; Return-Path: -; Organization: -; Message-Id: -; Comment: -; X-REBOL: -; MIME-Version: -; Content-Type: -; Content: -; none -; ] - -;user: context [ -; name: ; User's name -; email: ; User's default email address -; home: ; The HOME environment variable -; words: none -;] +; model: ; Network, File, Driver +; type: ; bytes, integers, values +; user: ; User data + +; host: +; port-id: +; user: +; pass: +; target: +; path: +; proxy: +; access: +; allow: +; buffer-size: +; limit: +; handler: +; status: +; size: +; date: +; sub-port: +; locals: +; state: +; timeout: +; local-ip: +; local-service: +; remote-service: +; last-remote-service: +; direction: +; key: +; strength: +; algorithm: +; block-chaining: +; init-vector: +; padding: +; async-modes: +; remote-ip: +; local-port: +; remote-port: +; backlog: +; device: +; speed: +; data-bits: +; parity: +; stop-bits: +; _ +; rts-cts: true +; user-data: +; awake: + +; port-flags: construct [] [ +; direct: +; pass-thru: +; open-append: +; open-new: +; _ +; ] + +; email: construct [] [ ; Email header object +; To: +; CC: +; BCC: +; From: +; Reply-To: +; Date: +; Subject: +; Return-Path: +; Organization: +; Message-Id: +; Comment: +; X-REBOL: +; MIME-Version: +; Content-Type: +; Content: +; _ +; ] + +user: construct [] [ + name: ; User's name + home: ; The HOME environment variable + words: _ + identity: construct [][email: smtp: pop3: esmtp-user: esmtp-pass: fqdn: _] + identities: [] +] -;network: context [ -; host: "" ; Host name of the user's computer -; host-address: 0.0.0.0 ; Host computer's TCP-IP address -; trace: none +;network: construct [] [ +; host: "" ; Host name of the user's computer +; host-address: 0.0.0.0 ; Host computer's TCP-IP address +; trace: _ ;] -;console: context [ -; hide-types: none ; types not to print -; history: ; Log of user inputs -; keys: none ; Keymap for special key -; prompt: {>> } ; Specifies the prompt -; result: {== } ; Specifies result -; escape: {(escape)} ; Indicates an escape -; busy: {|/-\} ; Spinner for network progress -; tab-size: 4 ; default tab size -; break: true ; whether escape breaks or not +console: _ ;; console (repl) object created in host-start (os/host-start.r) + +; Below is original console construct (unused and comment-out in r3/ren-c) +; Left here for reference (for future development) +; +;console: construct [] [ +; hide-types: _ ; types not to print +; history: _ ; Log of user inputs +; keys: _ ; Keymap for special key +; prompt: {>> } ; Specifies the prompt +; result: {== } ; Specifies result +; escape: {(escape)} ; Indicates an escape +; busy: {|/-\} ; Spinner for network progress +; tab-size: 4 ; default tab size +; break: true ; whether escape breaks or not ;] -; decimal: #"." ; The character used as the decimal point in decimal and money vals -; sig-digits: none ; Significant digits to use for decimals ; none for normal printing -; date-sep: #"-" ; The character used as the date separator -; date-month-num: false ; True if months are displayed as numbers; False for names -; time-sep: #":" ; The character used as the time separator -; cgi: context [ ; CGI environment variables -; server-software: -; server-name: -; gateway-interface: -; server-protocol: -; server-port: -; request-method: -; path-info: -; path-translated: -; script-name: -; query-string: -; remote-host: -; remote-addr: -; auth-type: -; remote-user: -; remote-ident: -; Content-Type: ; cap'd for email header -; content-length: none -; other-headers: [] -; ] -; browser-type: 0 - -; trace: ; True if the --trace flag was specified -; help: none ; True if the --help flags was specified -; halt: none ; halt after script +; decimal: #"." ; The character used as the decimal point in decimal and money vals +; sig-digits: _ ; Significant digits to use for decimals ; blank for normal printing +; date-sep: #"-" ; The character used as the date separator +; date-month-num: false ; True if months are displayed as numbers; False for names +; time-sep: #":" ; The character used as the time separator + +cgi: construct [] [ ; CGI environment variables + server-software: + server-name: + gateway-interface: + server-protocol: + server-port: + request-method: + path-info: + path-translated: + script-name: + query-string: + remote-host: + remote-addr: + auth-type: + remote-user: + remote-ident: + Content-Type: ; cap'd for email header + content-length: _ + other-headers: [] +] +; browser-type: 0 + +; trace: ; True if the --trace flag was specified +; help: _ ; True if the --help flags was specified +; halt: _ ; halt after script +;-- Expectation is that evaluation ends with no result, empty GROUP! does that +() diff --git a/src/boot/task.r b/src/boot/task.r index c415b8a84f..bca925a870 100644 --- a/src/boot/task.r +++ b/src/boot/task.r @@ -1,34 +1,30 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Task context" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Purpose: { - Globals used for each task. Prevents GC of these values. - See also the Root Context (program-wide globals) - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Task context" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0. + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + Globals used for each task. Prevents GC of these values. + See also the Root Vars (program-wide globals) + } ] -self -stack ; data stack -ballast ; current memory ballast (used for GC) -max-ballast ; ballast reset value -this-error ; current error -this-value ; for holding an error argument during throw back -stack-error ; special stack error object -this-context ; current context -buf-emit ; temporary emit output block -buf-words ; temporary word cache -buf-utf8 ; UTF8 reused buffer -buf-print ; temporary print output - used by raw print -buf-form ; temporary form buffer - used by raw print -buf-mold ; temporary mold buffer - used by mold -mold-loop ; mold loop detection -err-temps ; error temporaries +ballast ; current memory ballast (used for GC) +max-ballast ; ballast reset value + +stack-error ; special stack overlow error object +halt-error ; special halt error object + +buf-collect ; temporary cache for collecting object keys or words +buf-utf8 ; UTF8 reused buffer +byte-buf ; temporary byte buffer - used mainly by raw print +uni-buf ; temporary unicode buffer - used mainly by mold + +mold-stack ; mold loop detection diff --git a/src/boot/text.r b/src/boot/text.r deleted file mode 100644 index 2e5d2d82e8..0000000000 --- a/src/boot/text.r +++ /dev/null @@ -1,156 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL Graphics - TEXT commands" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0. - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Name: text - Type: extension - Exports: none - Note: "Run make-host-ext.r to convert" -] - -;don't change order of already defined words unless you know what you are doing - -words: [ - aliased - antialiased - vectorial - - ;font object words - name - style - size - color - offset - space - shadow - - ;para object words - origin - margin - indent - tabs - wrap? - scroll - align - valign - - ;para/align values - center - right - left - - ;para/valign values - middle - top - bottom - - ;font/style values - bold - italic - underline - - ;caret object words - caret - highlight-start - highlight-end -] - -;temp hack - will be removed later -init-words: command [ - words [block!] -] - -init-words words - -;please alphabetize the order of commands so it easier to lookup things - -anti-alias: command [ - "Sets aliasing mode." - state [logic!] -] - -b: bold: command [ - "Sets font BOLD style." - state [logic!] -] - -caret: command [ - "Sets paragraph attributes." - caret-attributes [object!] -] - -center: command [ - "Sets text alignment." -] - -color: command [ - "Sets font color." - font-color [tuple!] -] - -drop: command [ - "Removes N previous style setting from the stack." - count [integer!] -] - -font: command [ - "Sets font attributes." - font-attributes [object!] -] - -i: italic: command [ - "Sets font ITALIC style." - state [logic!] -] - -left: command [ - "Sets text alignment." -] - -nl: newline: command [ - "Breaks the text line." -] - -para: command [ - "Sets paragraph attributes." - para-attributes [object!] -] - -right: command [ - "Sets text alignment." -] - -scroll: command [ - "Sets text position." - offset [pair!] -] - -shadow: command [ - "Enables shadow effect for text." - offset [pair!] - color [tuple!] - spread [integer!] -] - -size: command [ - "Sets font size." - font-size [integer!] -] - -text: command [ - "Renders text string." - text [string!] -] - -u: underline: command [ - "Sets font UNDERLINE style." - state [logic!] -] - diff --git a/src/boot/types-ext.r b/src/boot/types-ext.r deleted file mode 100644 index 8f5dbc223b..0000000000 --- a/src/boot/types-ext.r +++ /dev/null @@ -1,62 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Extension datatypes" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Purpose: { - Used to build C enums and definitions for extensions. - } -] - -end 0 0 -unset * null -none * null -handle * ptr - -logic 4 32 -integer * 64 -decimal * 64 -percent * 64 - -char 10 32 -pair * 64 -tuple * 64 -time * 64 -date * date - -word 16 sym -set-word * sym -get-word * sym -lit-word * sym -refinement * sym -issue * sym - -string 24 ser -file * ser -email * ser -url * ser -tag * ser - -block 32 ser -paren * ser -path * ser -set-path * ser -get-path * ser -lit-path * ser - -binary 40 ser -bitset * ser -vector * ser -image * image - -gob 47 ser - -object 48 ptr -module * ptr - diff --git a/src/boot/types.r b/src/boot/types.r index 49471eba77..307b833d74 100644 --- a/src/boot/types.r +++ b/src/boot/types.r @@ -1,104 +1,125 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Datatype definitions" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Purpose: { - These words define the REBOL datatypes and their related attributes. - This table generates a variety of C defines and intialization tables. - During build, when this file is processed, this section is changed to - hold just the datatype words - the initial entries the word table. - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Datatype definitions" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + These words define the REBOL datatypes and their related attributes. + This table generates a variety of C defines and intialization tables. + During build, when this file is processed, this section is changed to + hold just the datatype words - the initial entries the word table. + + name - name of datatype (generates words) + class - how type actions are dispatched (T_type), + is extension + path - it supports various path forms (* for same as typeclass) + make - It can be made with #[datatype] method + typesets - what typesets the type belongs to + + Note that if there is `somename` in the class column, that means you + will find the ACTION! dispatch for that type in `REBTYPE(Somename)`. + + If the (CLASS) is in a GROUP! that means it has evaluator behavior, + vs. being passed through as-is. (e.g. a lit-word is "evaluative") + This is used to build the table used for fast lookup of whether the + evaluator needs to be called on a given type. + } ] -; Fields: -; Datatype - name of datatype (generates words) -; Evaluator - how it is evaluated (by DO) -; Typeclass - how type actions are dispatched (T_type) -; Mold - mold format: - self, + type, * typeclass -; Form - form format: above, and f* for special form functions -; Path - it supports various path forms (* for same as typeclass) -; Make - It can be made with #[datatype] method - -; Datatype Evaluator Typeclass Mold Form Path Make Typesets -;------------------------------------------------------------------------------ - end end 0 - - - - - - unset self none - - - * - - - ;Scalars - none self none + + - * - - logic self logic * * - * - - integer self integer * * - - [number scalar] - decimal self decimal * * - * [number scalar] - percent self decimal * * - * [number scalar] - money self money * * - - scalar - char self char * f* - - scalar - pair self pair * * * * scalar - tuple self tuple * * * * scalar - time self time * * * * scalar - date self date * * * * - - - ;Series - binary self string + + * * [series] - string self string + f* * * [series string] - file self string + f* file * [series string] - email self string + f* * * [series string] - url self string + f* file * [series string] - tag self string + + * * [series string] - - bitset self bitset * * * * - - image self image + + * * series - vector self vector - - * * series - - block self block * f* * * [series block] - paren paren block * f* * * [series block] - - path path block * * * * [series block path] - set-path path block * * * * [series block path] - get-path path block * * * * [series block path] - lit-path lit-path block * * * * [series block path] - - map self map + f* * * - - - datatype self datatype + f* - * - - typeset self typeset + f* - * - - - ; Order dependent: next few words - ;symbol invalid word * * - - word - word word word + * - - word - set-word set-word word + * - - word - get-word get-word word + * - - word - lit-word lit-word word + * - - word - refinement self word + * - - word - issue self word + * - - word - - native function function * - - * function - action function function * - - * function -; routine function routine - - - * function - rebcode function 0 - - - * function - command function function - - - * function -; macro function 0 - - - - function - op operator function - - - * function - closure function function * - - * function - function function function * - - * function - - frame invalid frame - - * - - - object self object * f* * * object - module self object * f* * * object - error self object + f+ * * object - task self object + + * * object - port self port object object object - object - - gob self gob * * * * - - event self event * * * * - - handle self 0 - - - - - - struct invalid 0 - - - - - - library invalid 0 - - - - - - utype self utype - - - - - +[name class path make typesets] + +; 0 is not a real data type. It is reserved for internal purposes. + +0 0 - - - + +; There is only one FUNCTION! type in Ren-C + +function function * * - + +; ANY-WORD!, order matters (tests like ANY_WORD use >= REB_WORD, <= REB_ISSUE) +; +word word - * word +set-word word - * word +get-word word - * word +lit-word word - * word +refinement word - * word +issue word - * word + +; ANY-ARRAY!, order matters (and contiguous with ANY-SERIES below matters!) +; +path array * * [series path array] +set-path array * * [series path array] +get-path array * * [series path array] +lit-path array * * [series path array] +group array * * [series array] +block array * * [series array] + +; ANY-SERIES!, order matters (and contiguous with ANY-ARRAY above matters!) +; +binary string * * [series] +string string * * [series string] +file string * * [series string] +email string * * [series string] +url string * * [series string] +tag string * * [series string] + +bitset bitset * * - +image image * * [series] +vector vector * * [series] + +map map * * - + +varargs varargs * * - + +object context * * context +frame context * * context +module context * * context +error context * * context +port port context * context + +; ^-------- Everything above is a "bindable" type, see Is_Bindable() --------^ + +; v------- Everything below is an "unbindable" type, see Is_Bindable() ------v + +; "unit types" https://en.wikipedia.org/wiki/Unit_type + +bar unit - * - +lit-bar unit - * - +blank unit - * - + +; scalars + +logic logic - * - +integer integer - * [number scalar] +decimal decimal - * [number scalar] +percent decimal - * [number scalar] +money money - * scalar +char char - * scalar +pair pair * * scalar +tuple tuple * * scalar +time time * * scalar +date date * * - + +; type system + +datatype datatype - * - +typeset typeset - * - + +; things likely to become user-defined types or extensions + +gob gob * * - +event event * * - +handle handle - - - +struct struct * * - +library library - * - + +; Note that the "void?" state has no associated VOID! datatype. Internally +; it uses REB_MAX, but like the REB_0 it stays off the type map. (REB_0 +; is used for lookback as opposed to void in order to implement an +; optimization in Get_Var_Core()) diff --git a/src/boot/typespec.r b/src/boot/typespec.r index d0608c3d4c..5ceaa9e6f3 100644 --- a/src/boot/typespec.r +++ b/src/boot/typespec.r @@ -1,75 +1,68 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Datatype help spec" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Purpose: { - Provides useful information about datatypes. - Can be expanded to include info like min-max ranges. - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Datatype help spec" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + Provides useful information about datatypes. + Can be expanded to include info like min-max ranges. + } ] -action ["datatype native function (standard polymorphic)" function] -binary ["string series of bytes" string] -bitset ["set of bit flags" string] -block ["series of values" block] -char ["8bit and 16bit character" scalar] -closure ["function with persistent locals (indefinite extent)" function] -datatype ["type of datatype" symbol] -date ["day, month, year, time of day, and timezone" scalar] -decimal ["64bit floating point number (IEEE standard)" scalar] -email ["email address" string] -end ["internal marker for end of block" internal] -error ["errors and throws" object] -event ["user interface event (efficiently sized)" opt-object] -file ["file name or path" string] -frame ["internal context frame" internal] -function ["interpreted function (user-defined or mezzanine)" function] -get-path ["the value of a path" block] -get-word ["the value of a word (variable)" word] -gob ["graphical object" opt-object] -handle ["arbitrary internal object or value" internal] -image ["RGB image with alpha channel" vector] -integer ["64 bit integer" scalar] -issue ["identifying marker word" word] -library ["external library reference" internal] -lit-path ["literal path value" block] -lit-word ["literal word value" word] -logic ["boolean true or false" scalar] -map ["name-value pairs (hash associative)" block] -module ["loadable context of code and data" object] -money ["high precision decimals with denomination (opt)" scalar] -native ["direct CPU evaluated function" function] -none ["no value represented" scalar] -object ["context of names with values" object] -op ["infix operator (special evaluation exception)" function] -pair ["two dimensional point or size" scalar] -paren ["automatically evaluating block" block] -path ["refinements to functions, objects, files" block] -percent ["special form of decimals (used mainly for layout)" scalar] -port ["external series, an I/O channel" object] -rebcode ["virtual machine function" block] -refinement ["variation of meaning or location" word] -command ["special dispatch-based function" function] -set-path ["definition of a path's value" block] -set-word ["definition of a word's value" word] -string ["string series of characters" string] -struct ["native structure definition" block] -tag ["markup string (HTML or XML)" string] -task ["evaluation environment" object] -time ["time of day or duration" scalar] -tuple ["sequence of small integers (colors, versions, IP)" scalar] -typeset ["set of datatypes" opt-object] -unicode ["string of unicoded characters" string] -unset ["no value returned or set" internal] -url ["uniform resource locator or identifier" string] -utype ["user defined datatype" object] -vector ["high performance arrays (single datatype)" vector] -word ["word (symbol or variable)" word] - +bar ["expression evaluation barrier" internal] +binary ["string series of bytes" string] +bitset ["set of bit flags" string] +blank ["placeholder unit type which also is conditionally FALSE?" scalar] +block ["array of values that blocks evaluation unless DO is used" block] +char ["8bit and 16bit character" scalar] +datatype ["type of datatype" symbol] +date ["day, month, year, time of day, and timezone" scalar] +decimal ["64bit floating point number (IEEE standard)" scalar] +email ["email address" string] +error ["errors and throws" context] +event ["user interface event (efficiently sized)" opt-object] +file ["file name or path" string] +frame ["arguments and locals of a specific function invocation" context] +function ["interpreted function (user-defined or mezzanine)" function] +get-path ["the value of a path" block] +get-word ["the value of a word (variable)" word] +gob ["graphical object" opt-object] +handle ["arbitrary internal object or value" internal] +image ["RGB image with alpha channel" vector] +integer ["64 bit integer" scalar] +issue ["identifying marker word" word] +library ["external library reference" internal] +lit-bar ["literal expression barrier" internal] +lit-path ["literal path value" block] +lit-word ["literal word value" word] +logic ["boolean true or false" scalar] +map ["name-value pairs (hash associative)" block] +module ["loadable context of code and data" context] +money ["high precision decimals with denomination (opt)" scalar] +object ["context of names with values" context] +pair ["two dimensional point or size" scalar] +group ["array that evaluates expressions as an isolated group" block] +path ["refinements to functions, objects, files" block] +percent ["special form of decimals (used mainly for layout)" scalar] +port ["external series, an I/O channel" context] +refinement ["variation of meaning or location" word] +set-path ["definition of a path's value" block] +set-word ["definition of a word's value" word] +string ["string series of characters" string] +struct ["native structure definition" block] +tag ["markup string (HTML or XML)" string] +time ["time of day or duration" scalar] +tuple ["sequence of small integers (colors, versions, IP)" scalar] +typeset ["set of datatypes" opt-object] +unicode ["string of unicoded characters" string] +url ["uniform resource locator or identifier" string] +varargs ["evaluator position for variable numbers of arguments" internal] +vector ["high performance arrays (single datatype)" vector] +;-- Note that VOID is not a type of value, and should not be in this list +word ["word (symbol or variable)" word] diff --git a/src/boot/version.r b/src/boot/version.r index dd9096b769..e65b6840d6 100644 --- a/src/boot/version.r +++ b/src/boot/version.r @@ -1 +1 @@ -2.101.0.3.1 +2.102.0.0.0 diff --git a/src/boot/words.r b/src/boot/words.r index 9d9d5db1ba..b252e52b81 100644 --- a/src/boot/words.r +++ b/src/boot/words.r @@ -1,53 +1,79 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Canonical words" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Purpose: { - These words are used internally by REBOL and must have specific canon - word values in order to be correctly identified. - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Canonical words" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + These words are used internally by REBOL and must have specific canon + word values in order to be correctly identified. + } ] -any-type! +any-value! ;-- signal typesets start (SYM_ANY_VALUE_X hardcoded reference) any-word! any-path! -any-function! -number! -scalar! -series! +any-number! +any-scalar! +any-series! any-string! -any-object! -any-block! +any-context! +any-array! ;-- replacement for ANY-BLOCK! that doesn't conflate with BLOCK! + +;----------------------------------------------------------------------------- +; Signal that every earlier numbered symbol is for a typeset or datatype... datatypes +; ...note that the words for types are created programmatically before +; this list is applied, so you only see typesets in this file. +;----------------------------------------------------------------------------- + +; !!! Kept for functionality of #[none] in the loader for +none + +; For the moment, TO-WORD of a datatype is willing to canonize a datatype +; as a word. Long term, that specialization is not desirable because it +; is effectively building keywords deep into the system. Better would be +; if datatypes could be communicated e.g. by #[()] for "groups" or "parens". +; +; Hardcoding in the GROUP! symbol is necessary for a legacy switch to be +; willing to convert a "group!" into the word GROUP! +; +paren! + +; The PICK* action was killed in favor of a native that uses the same logic +; as path processing. Code still remains for processing PICK*, and ports or +; other mechanics may wind up using it...or path dispatch itself may be +; rewritten to use the PICK* action (but that would require significiant +; change for setting and getting paths) +; +; Similar story for POKE, which uses the same logic as PICK* to find the +; location to write the value. +; +pick* +poke + native +action self -none +blank true false on off yes no -pi rebol system -;boot levels -base -sys -mods - ;reflectors: spec body @@ -55,6 +81,18 @@ words values types title +;addr already defined + +value ; used by TYPECHECKER to name the argument of the generated function + +; !!! See notes on FUNCTION-META and SPECIALIZER-META in %sysobj.r +description +return-type +return-note +parameter-types +parameter-notes +specializee +specializee-name x y @@ -62,10 +100,7 @@ y - * unsigned --unnamed- ; lambda (unnamed) functions --apply- ; apply func -code ; error field -delect +code ; error field ; Secure: (add to system/state/policies object too) secure @@ -78,8 +113,8 @@ memory debug browse extension -;dir - below -;file - below +file +dir ; Time: hour @@ -98,11 +133,25 @@ yearday zone utc -; Parse: - These words must not reserved above!! -parse -| ; must be first -; prep words: -set +; Used to recognize Rebol2 use of [catch] and [throw] in function specs +catch +throw + +; Needed for processing of THROW's /NAME words used by system +; NOTE: may become something more specific than WORD! +exit +quit +;break ;-- covered by parse below +;return ;-- covered by parse below +leave ;-- for PROC +continue + +subparse ;-- recursions of parse use this for REBNATIVE(subparse) in backtrace + +; PARSE - These words must not be reserved above!! The range of consecutive +; index numbers are used by PARSE to detect keywords. +; +set ; must be first first (SYM_SET referred to by GET_VAR() in %u-parse.c) copy some any @@ -122,7 +171,8 @@ limit ?? accept break -; match words: +; ^--prep words above +; v--match words below skip to thru @@ -130,7 +180,7 @@ quote do into only -end ; must be last +end ; must be last (SYM_END referred to by GET_VAR() in %u-parse.c) ; Event: type @@ -147,22 +197,61 @@ sha1 md4 md5 crc32 +adler32 ; Codec actions identify decode encode -; Schemes -console -file -dir -event -callback -dns -tcp -udp -clipboard +; Serial parameters +; Parity +odd +even +; Control flow +hardware +software + +; Struct +uint8 +int8 +uint16 +int16 +uint32 +int32 +uint64 +int64 +float +;double ;reuse earlier definition +pointer +addr +raw-memory +raw-size +extern +rebval + +;routine +void +library +name +abi +stdcall +fastcall +sysv +thiscall +unix64 +ms-cdecl +win64 +default +vfp ;arm +o32; mips abi +n32; mips abi +n64; mips abi +o32-soft-float; mips abi +n32-soft-float; mips abi +n64-soft-float; mips abi +... +varargs ; Gobs: gob @@ -180,6 +269,7 @@ rgb alpha data resize +rotate no-title no-border dropable @@ -189,13 +279,76 @@ modal on-top hidden owner +active +minimize +maximize +restore +fullscreen *port-modes* +; posix signal names +all +sigalrm +sigabrt +sigbus +sigchld +sigcont +sigfpe +sighup +sigill +sigint +sigkill +sigpipe +sigquit +sigsegv +sigstop +sigterm +sigtstp +sigttin +sigttou +sigusr1 +sigusr2 +sigpoll +sigprof +sigsys +sigtrap +sigurg +sigvtalrm +sigxcpu +sigxfsz + bits crash crash-dump watch-recycle -watch-obj-copy -stack-size +uid +euid +gid +egid +pid + +;call/info +id +exit-code + +; used when a function is executed but not looked up through a word binding +; (product of literal or evaluation) so no name is known for it +--anonymous-- + +; used to signal situations where information that would be available in +; a debug build has been elided +; +--optimized-out-- + +; used to signal a void in a reified va_list call, since voids can't actually +; appear in user-visible arrays +; +--void-- + +include +source +library-path +runtime-path +options diff --git a/src/core/a-constants.c b/src/core/a-constants.c index 052532c258..c4e44b9a38 100644 --- a/src/core/a-constants.c +++ b/src/core/a-constants.c @@ -1,97 +1,178 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: a-constants.c -** Summary: special global constants and strings -** Section: environment -** Author: Carl Sassenrath -** Notes: -** Very few strings should be located here. Most strings are -** put in the compressed embedded boot image. That saves space, -** reduces tampering, and allows UTF8 encoding. See ../boot dir. -** -***********************************************************************/ +// +// File: %a-constants.c +// Summary: "special global constants and strings" +// Section: environment +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Most text strings in Rebol should appear in the bootstrap files as Rebol +// code. This allows for "internationalization" without needing to update +// the C code. Other advantages are that the strings are compressed, +// "reduces tampering", etc. +// +// So to keep track of any stray English strings in the executable which make +// it into the user's view, they should be located here. +// +// Note: It's acceptable for hardcoded English strings to appear in the debug +// build or in other debug settings, as anyone working with the C code itself +// is basically expected to be able to read English (given the variable names +// and comments in the C are English). +// #include "sys-core.h" -#define BP (REBYTE*) - -const REBYTE Str_Banner[] = "REBOL 3 %d.%d.%d.%d.%d"; - const char Str_REBOL[] = "REBOL"; -const REBYTE * Str_Stack_Misaligned = { - BP("!! Stack misaligned: %d") -}; +// A panic() indicates a serious malfunction, and should not make use of +// Rebol-structured error message delivery in the release build. -const REBYTE * const Crash_Msgs[] = { - BP"REBOL System Error", - BP"boot failure", - BP"internal problem", - BP"assertion failed", - BP"invalid datatype %d", - BP"unspecific", - BP"\n\nProgram terminated abnormally.\nThis should never happen.\nPlease contact www.REBOL.com with details." -}; +const char Str_Panic_Title[] = "Rebol Internal Error"; -const REBYTE * const Str_Dump[] = { - BP"%s Series %x \"%s\": wide: %2d size: %6d bias: %d tail: %d rest: %d flags: %x" +const char Str_Panic_Directions[] = { + "If you need to file a bug in the issue tracker, please give thorough\n" + "details on how to reproduce the problem:\n" + "\n" + " https://github.com/metaeducation/ren-c/issues\n" + "\n" + "Include the following information in the report:\n\n" }; -const REBYTE * Hex_Digits = BP"0123456789ABCDEF"; +const char * Hex_Digits = "0123456789ABCDEF"; -const REBYTE * const Bad_Ptr = BP"#[BAD-PTR]"; - -const REBYTE * const Esc_Names[] = { - // Must match enum REBOL_Esc_Codes! - BP"line", - BP"tab", - BP"page", - BP"escape", - BP"esc", - BP"back", - BP"del", - BP"null" +const char * const Esc_Names[] = { + // Must match enum REBOL_Esc_Codes! + "line", + "tab", + "page", + "escape", + "esc", + "back", + "del", + "null" }; const REBYTE Esc_Codes[] = { - // Must match enum REBOL_Esc_Codes! - 10, - 9, - 12, - 27, - 27, - 8, - 127, - 0 + // Must match enum REBOL_Esc_Codes! + 10, // line + 9, // tab + 12, // page + 27, // escape + 27, // esc + 8, // back + 127, // del + 0 // null +}; + +// Zen Point on naming cues: was "Month_Lengths", but said 29 for Feb! --@HF +const REBYTE Month_Max_Days[12] = { + 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; -const REBYTE Month_Lengths[12] = { - 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +const char * const Month_Names[12] = { + "January", "February", "March", "April", "May", "June", "July", "August", + "September", "October", "November", "December" }; -const REBYTE * const Month_Names[12] = { - BP"January", BP"February", BP"March", - BP"April", BP"May", BP"June", - BP"July", BP"August", BP"September", - BP"October", BP"November", BP"December" + +// Used by scanner. Keep in sync with enum Reb_Token in %scan.h file! +// +const char * const Token_Names[] = { + "end-of-script", + "newline", + "block-end", + "group-end", + "word", + "set", + "get", + "lit", + "blank", + "bar", + "lit-bar", + "logic", + "integer", + "decimal", + "percent", + "money", + "time", + "date", + "char", + "block-begin", + "group-begin", + "string", + "binary", + "pair", + "tuple", + "file", + "email", + "url", + "issue", + "tag", + "path", + "refine", + "construct", + NULL }; + +// !!! For now, (R)ebol (M)essages use the historical Debug_Fmt() output +// method, which is basically like `printf()`. Over the long term, they +// should use declarations like the (R)ebol (E)rrors do with RE_XXX values +// loaded during boot. +// +// The goal should be that any non-debug-build only strings mentioned from C +// that can be seen in the course of normal operation should go through this +// abstraction. Ultimately that would permit internationalization, and the +// benefit of not needing to ship a release build binary with a string-based +// format dialect. +// +// Switching strings to use this convention should ultimately parallel the +// `Error()` generation, where the arguments are Rebol values and not C +// raw memory as parameters. Debug_Fmt() should also just be changed to +// a normal `Print()` naming. +// +const char RM_ERROR_LABEL[] = "Error: "; +const char RM_BAD_ERROR_FORMAT[] = "(improperly formatted error)"; +const char RM_ERROR_WHERE[] = "** Where: "; +const char RM_ERROR_NEAR[] = "** Near: "; +const char RM_ERROR_FILE[] = "** File: "; +const char RM_ERROR_LINE[] = "** Line: "; + +const char RM_WATCH_RECYCLE[] = "RECYCLE: %d series"; + +const char RM_TRACE_FUNCTION[] = "--> %s"; +const char RM_TRACE_RETURN[] = "<-- %s =="; +const char RM_TRACE_ERROR[] = "**: error : %r %r"; + +const char RM_TRACE_PARSE_VALUE[] = "Parse %s: %r"; +const char RM_TRACE_PARSE_INPUT[] = "Parse input: %s"; + +const char RM_BACKTRACE_NOT_ENABLED[] = "backtrace not enabled"; + +const char RM_EVOKE_HELP[] = "Evoke values:\n" + "[stack-size n] crash-dump delect\n" + "watch-recycle watch-obj-copy crash\n" + "1: watch expand\n" + "2: check memory pools\n" + "3: check bind table\n"; diff --git a/src/core/a-globals.c b/src/core/a-globals.c index b2730c6361..7a22c4a91d 100644 --- a/src/core/a-globals.c +++ b/src/core/a-globals.c @@ -1,34 +1,36 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: a-globals.c -** Summary: global variables -** Section: environment -** Author: Carl Sassenrath -** Notes: -** There are two types of global variables: -** process vars - single instance for main process -** thread vars - duplicated within each R3 task -** -***********************************************************************/ +// +// File: %a-globals.c +// Summary: "global variables" +// Section: environment +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// There are two types of global variables: +// process vars - single instance for main process +// thread vars - duplicated within each R3 task +// /* To do: there are still a few globals in various modules that need to be ** incorporated back into sys-globals.h. @@ -40,6 +42,6 @@ #undef TVAR #define PVAR -#define TVAR THREAD +#define TVAR #include "sys-globals.h" diff --git a/src/core/a-lib.c b/src/core/a-lib.c index 57833e3706..672c5e7da9 100644 --- a/src/core/a-lib.c +++ b/src/core/a-lib.c @@ -1,37 +1,44 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: a-lib.c -** Summary: exported REBOL library functions -** Section: environment -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %a-lib.c +// Summary: "exported REBOL library functions" +// Section: environment +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -#include "reb-dialect.h" + +// !!! Most of the Rebol source does not include %reb-ext.h. As a result +// REBRXT and RXIARG and RXIFRM are not defined when %tmp-funcs.h is being +// compiled, so the MAKE PREP process doesn't auto-generate prototypes for +// these functions. +// +// Rather than try and define RX* for all of the core to include, assume that +// the burden of keeping these in sync manually is for the best. +// #include "reb-ext.h" -#include "reb-evtypes.h" -#include "sys-state.h" // Linkage back to HOST functions. Needed when we compile as a DLL // in order to use the OS_* macro functions. @@ -39,895 +46,617 @@ REBOL_HOST_LIB *Host_Lib; #endif -#include "reb-lib.h" - -//#define DUMP_INIT_SCRIPT -#ifdef DUMP_INIT_SCRIPT -#include -#include -#include -#include -#endif -extern const REBYTE Reb_To_RXT[REB_MAX]; -extern RXIARG Value_To_RXI(REBVAL *val); // f-extension.c -extern void RXI_To_Value(REBVAL *val, RXIARG arg, REBCNT type); // f-extension.c -extern void RXI_To_Block(RXIFRM *frm, REBVAL *out); // f-extension.c -extern int Do_Callback(REBSER *obj, u32 name, RXIARG *args, RXIARG *result); - - -/*********************************************************************** -** -*/ RL_API void RL_Version(REBYTE vers[]) -/* -** Obtain current REBOL interpreter version information. -** -** Returns: -** A byte array containing version, revision, update, and more. -** Arguments: -** vers - a byte array to hold the version info. First byte is length, -** followed by version, revision, update, system, variation. -** Notes: -** This function can be called before any other initialization -** to determine version compatiblity with the caller. -** -***********************************************************************/ +static REBRXT Reb_To_RXT[REB_MAX]; +static enum Reb_Kind RXT_To_Reb[RXT_MAX]; + + +#include "reb-lib.h" // forward definitions needed for "extern C" linkage + + +// +// RL_Version: C +// +// Obtain current REBOL interpreter version information. +// +// Returns: +// A byte array containing version, revision, update, and more. +// Arguments: +// vers - a byte array to hold the version info. First byte is length, +// followed by version, revision, update, system, variation. +// Notes: +// In the original RL_API, this function was to be called before any other +// initialization to determine version compatiblity with the caller. +// With the massive changes in Ren-C and the lack of RL_API clients, this +// check is low priority. This is how it was originally done: +// +// REBYTE vers[8]; +// vers[0] = 5; // len +// RL_Version(&vers[0]); +// +// if (vers[1] != RL_VER || vers[2] != RL_REV) +// OS_CRASH(cb_cast("Incompatible reb-lib DLL")); +// +RL_API void RL_Version(REBYTE vers[]) { - // [0] is length - vers[1] = REBOL_VER; - vers[2] = REBOL_REV; - vers[3] = REBOL_UPD; - vers[4] = REBOL_SYS; - vers[5] = REBOL_VAR; -} - - -/*********************************************************************** -** -*/ RL_API int RL_Init(REBARGS *rargs, void *lib) -/* -** Initialize the REBOL interpreter. -** -** Returns: -** Zero on success, otherwise an error indicating that the -** host library is not compatible with this release. -** Arguments: -** rargs - REBOL command line args and options structure. -** See the host-args.c module for details. -** lib - the host lib (OS_ functions) to be used by REBOL. -** See host-lib.c for details. -** Notes: -** This function will allocate and initialize all memory -** structures used by the REBOL interpreter. This is an -** extensive process that takes time. -** -***********************************************************************/ -{ - int marker; - REBCNT bounds; - - Host_Lib = lib; - - if (Host_Lib->size < HOST_LIB_SIZE) return 1; - if (((HOST_LIB_VER << 16) + HOST_LIB_SUM) != Host_Lib->ver_sum) return 2; - - bounds = OS_CONFIG(1, 0); - if (bounds == 0) bounds = STACK_BOUNDS; - -#ifdef OS_STACK_GROWS_UP - Stack_Limit = (REBCNT)(&marker) + bounds; -#else - if (bounds > (REBCNT)(&marker)) Stack_Limit = 100; - else Stack_Limit = (REBCNT)(&marker) - bounds; -#endif - - Init_Core(rargs); - GC_Active = TRUE; // Turn on GC - if (rargs->options & RO_TRACE) { - Trace_Level = 9999; - Trace_Flags = 1; - } - - return 0; -} - - -/*********************************************************************** -** -*/ RL_API int RL_Start(REBYTE *bin, REBINT len, REBCNT flags) -/* -** Evaluate the default boot function. -** -** Returns: -** Zero on success, otherwise indicates an error occurred. -** Arguments: -** bin - optional startup code (compressed), can be null -** len - length of above bin -** flags - special flags -** Notes: -** This function completes the startup sequence by calling -** the sys/start function. -** -***********************************************************************/ + // [0] is length + vers[1] = REBOL_VER; + vers[2] = REBOL_REV; + vers[3] = REBOL_UPD; + vers[4] = REBOL_SYS; + vers[5] = REBOL_VAR; +} + + +// +// RL_Init: C +// +// Initialize the REBOL interpreter. +// +// Returns: +// Zero on success, otherwise an error indicating that the +// host library is not compatible with this release. +// Arguments: +// lib - the host lib (OS_ functions) to be used by REBOL. +// See host-lib.c for details. +// Notes: +// This function will allocate and initialize all memory +// structures used by the REBOL interpreter. This is an +// extensive process that takes time. +// +void RL_Init(void *lib) { - REBVAL *val; - REBSER spec = {0}; - REBSER *ser; - - if (bin) { - spec.data = bin; - spec.tail = len; - ser = Decompress(&spec, 0, -1, 10000000, 0); - if (!ser) return 1; - - val = BLK_SKIP(Sys_Context, SYS_CTX_BOOT_HOST); - Set_Binary(val, ser); - } - - return Init_Mezz(0); -} - - -/*********************************************************************** -** -*/ RL_API void RL_Reset() -/* -** Reset REBOL (not implemented) -** -** Returns: -** nothing -** Arguments: -** none -** Notes: -** Intended to reset the REBOL interpreter. -** -***********************************************************************/ + // These tables used to be built by overcomplicated Rebol scripts. It's + // less hassle to have them built on initialization. + + REBCNT n; + for (n = 0; n < REB_MAX; ++n) { + // + // Though statics are initialized to 0, this makes it more explicit, + // as well as deterministic if there's an Init/Shutdown/Init... + // + Reb_To_RXT[n] = 0; // default that some types have no exported RXT_ + } + + // REB_BAR unsupported? + // REB_LIT_BAR unsupported? + Reb_To_RXT[REB_WORD] = RXT_WORD; + Reb_To_RXT[REB_SET_WORD] = RXT_SET_WORD; + Reb_To_RXT[REB_GET_WORD] = RXT_GET_WORD; + Reb_To_RXT[REB_LIT_WORD] = RXT_GET_WORD; + Reb_To_RXT[REB_REFINEMENT] = RXT_REFINEMENT; + Reb_To_RXT[REB_ISSUE] = RXT_ISSUE; + Reb_To_RXT[REB_PATH] = RXT_PATH; + Reb_To_RXT[REB_SET_PATH] = RXT_SET_PATH; + Reb_To_RXT[REB_GET_PATH] = RXT_GET_PATH; + Reb_To_RXT[REB_LIT_PATH] = RXT_LIT_PATH; + Reb_To_RXT[REB_GROUP] = RXT_GROUP; + Reb_To_RXT[REB_BLOCK] = RXT_BLOCK; + Reb_To_RXT[REB_BINARY] = RXT_BINARY; + Reb_To_RXT[REB_STRING] = RXT_STRING; + Reb_To_RXT[REB_FILE] = RXT_FILE; + Reb_To_RXT[REB_EMAIL] = RXT_EMAIL; + Reb_To_RXT[REB_URL] = RXT_URL; + Reb_To_RXT[REB_BITSET] = RXT_BITSET; + Reb_To_RXT[REB_IMAGE] = RXT_IMAGE; + Reb_To_RXT[REB_VECTOR] = RXT_VECTOR; + Reb_To_RXT[REB_BLANK] = RXT_BLANK; + Reb_To_RXT[REB_LOGIC] = RXT_LOGIC; + Reb_To_RXT[REB_INTEGER] = RXT_INTEGER; + Reb_To_RXT[REB_DECIMAL] = RXT_DECIMAL; + Reb_To_RXT[REB_PERCENT] = RXT_PERCENT; + // REB_MONEY unsupported? + Reb_To_RXT[REB_CHAR] = RXT_CHAR; + Reb_To_RXT[REB_PAIR] = RXT_PAIR; + Reb_To_RXT[REB_TUPLE] = RXT_TUPLE; + Reb_To_RXT[REB_TIME] = RXT_TIME; + Reb_To_RXT[REB_DATE] = RXT_DATE; + // REB_MAP unsupported? + // REB_DATATYPE unsupported? + // REB_TYPESET unsupported? + // REB_VARARGS unsupported? + Reb_To_RXT[REB_OBJECT] = RXT_OBJECT; + // REB_FRAME unsupported? + Reb_To_RXT[REB_MODULE] = RXT_MODULE; + // REB_ERROR unsupported? + // REB_PORT unsupported? + Reb_To_RXT[REB_GOB] = RXT_GOB; + // REB_EVENT unsupported? + Reb_To_RXT[REB_HANDLE] = RXT_HANDLE; + // REB_STRUCT unsupported? + // REB_LIBRARY unsupported? + + for (n = 0; n < REB_MAX; ++n) + RXT_To_Reb[Reb_To_RXT[n]] = cast(enum Reb_Kind, n); // reverse lookup + + // The RL_XXX API functions are stored like a C++ vtable, so they are + // function pointers inside of a struct. It's not completely obvious + // what the applications of this are...theoretically it could be for + // namespacing, or using multiple different versions of the API in a + // single codebase, etc. But all known clients use macros against a + // global "RL" rebol library, so it's not clear what the advantage is + // over just exporting C functions. + + Host_Lib = cast(REBOL_HOST_LIB*, lib); + + if (Host_Lib->size < HOST_LIB_SIZE) + panic ("Host-lib wrong size"); + + if (((HOST_LIB_VER << 16) + HOST_LIB_SUM) != Host_Lib->ver_sum) + panic ("Host-lib wrong version/checksum"); + + Startup_Core(); +} + + +// +// RL_Shutdown: C +// +// Shut down a Rebol interpreter (that was initialized via RL_Init). +// +// Returns: +// nothing +// Arguments: +// clean - whether you want Rebol to release all of its memory +// accrued since initialization. If you pass false, then it will +// only do the minimum needed for data integrity (assuming you +// are planning to exit the process, and hence the OS will +// automatically reclaim all memory/handles/etc.) +// +RL_API void RL_Shutdown(REBOOL clean) { - DS_RESET; -} - - -/*********************************************************************** -** -*/ RL_API void *RL_Extend(REBYTE *source, RXICAL call) -/* -** Appends embedded extension to system/catalog/boot-exts. -** -** Returns: -** A pointer to the REBOL library (see reb-lib.h). -** Arguments: -** source - A pointer to a UTF-8 (or ASCII) string that provides -** extension module header, function definitions, and other -** related functions and data. -** call - A pointer to the extension's command dispatcher. -** Notes: -** This function simply adds the embedded extension to the -** boot-exts list. All other processing and initialization -** happens later during startup. Each embedded extension is -** queried and init using LOAD-EXTENSION system native. -** See c:extensions-embedded -** -***********************************************************************/ + // At time of writing, nothing Shutdown_Core() does pertains to + // committing unfinished data to disk. So really there is + // nothing to do in the case of an "unclean" shutdown...yet. + + if (clean) { + #ifdef NDEBUG + // Only do the work above this line in an unclean shutdown + return; + #else + // Run a clean shutdown anyway in debug builds--even if the + // caller didn't need it--to see if it triggers any alerts. + // + Shutdown_Core(); + #endif + } + else { + Shutdown_Core(); + } +} + + +// +// RL_Escape: C +// +// Signal that code evaluation needs to be interrupted. +// +// Returns: +// nothing +// Notes: +// This function set's a signal that is checked during evaluation +// and will cause the interpreter to begin processing an escape +// trap. Note that control must be passed back to REBOL for the +// signal to be recognized and handled. +// +RL_API void RL_Escape(void) { - REBVAL *value; - REBSER *ser; - - value = BLK_SKIP(Sys_Context, SYS_CTX_BOOT_EXTS); - if (IS_BLOCK(value)) ser = VAL_SERIES(value); - else { - ser = Make_Block(2); - Set_Block(value, ser); - } - value = Append_Value(ser); - Set_Binary(value, Copy_Bytes(source, -1)); // UTF-8 - value = Append_Value(ser); - SET_HANDLE(value, call); - - return Extension_Lib(); -} - - -/*********************************************************************** -** -*/ RL_API void RL_Escape(REBINT reserved) -/* -** Signal that code evaluation needs to be interrupted. -** -** Returns: -** nothing -** Arguments: -** reserved - must be set to zero. -** Notes: -** This function set's a signal that is checked during evaluation -** and will cause the interpreter to begin processing an escape -** trap. Note that control must be passed back to REBOL for the -** signal to be recognized and handled. -** -***********************************************************************/ + // How should HALT vs. BREAKPOINT be decided? When does a Ctrl-C want + // to quit entirely vs. begin an interactive debugging session? + // + // !!! For now default to halting, but use SIG_INTERRUPT when a decision + // is made about how to debug break. + // + SET_SIGNAL(SIG_HALT); +} + + +// +// RL_Event: C +// +// Appends an application event (e.g. GUI) to the event port. +// +// Returns: +// Returns TRUE if queued, or FALSE if event queue is full. +// Arguments: +// evt - A properly initialized event structure. The +// contents of this structure are copied as part of +// the function, allowing use of locals. +// Notes: +// Sets a signal to get REBOL attention for WAIT and awake. +// To avoid environment problems, this function only appends +// to the event queue (no auto-expand). So if the queue is full +// +// !!! Note to whom it may concern: REBEVT would now be 100% compatible with +// a REB_EVENT REBVAL if there was a way of setting the header bits in the +// places that generate them. +// +RL_API int RL_Event(REBEVT *evt) { - SET_SIGNAL(SIG_ESCAPE); -} - - -/*********************************************************************** -** -*/ RL_API int RL_Do_String(REBYTE *text, REBCNT flags, RXIARG *result) -/* -** Load a string and evaluate the resulting block. -** -** Returns: -** The datatype of the result. -** Arguments: -** text - A null terminated UTF-8 (or ASCII) string to transcode -** into a block and evaluate. -** flags - set to zero for now -** result - value returned from evaluation. -** -***********************************************************************/ + REBVAL *event = Append_Event(); // sets signal + + if (event) { // null if no room left in series + VAL_RESET_HEADER(event, REB_EVENT); // has more space, if needed + event->extra.eventee = evt->eventee; + event->payload.event.type = evt->type; + event->payload.event.flags = evt->flags; + event->payload.event.win = evt->win; + event->payload.event.model = evt->model; + event->payload.event.data = evt->data; + return 1; + } + + return 0; +} + + +// +// RL_Update_Event: C +// +// Updates an application event (e.g. GUI) to the event port. +// +// Returns: +// Returns 1 if updated, or 0 if event appended, and -1 if full. +// Arguments: +// evt - A properly initialized event structure. The +// model and type of the event are used to address +// the unhandled event in the queue, when it is found, +// it will be replaced with this one +// +RL_API int RL_Update_Event(REBEVT *evt) { - REBVAL *val; - - val = Do_String(text, 0); - - if (result) { - *result = Value_To_RXI(val); - return Reb_To_RXT[VAL_TYPE(val)]; - } - return 0; -} - - -/*********************************************************************** -** -*/ RL_API int RL_Do_Binary(REBYTE *bin, REBINT length, REBCNT flags, REBCNT key, RXIARG *result) -/* -** Evaluate an encoded binary script such as compressed text. -** -** Returns: -** The datatype of the result or zero if error in the encoding. -** Arguments: -** bin - by default, a REBOL compressed UTF-8 (or ASCII) script. -** length - the length of the data. -** flags - special flags (set to zero at this time). -** key - encoding, encryption, or signature key. -** result - value returned from evaluation. -** Notes: -** As of A104, only compressed scripts are supported, however, -** rebin, cloaked, signed, and encrypted formats will be supported. -** -***********************************************************************/ + REBVAL *event = Find_Last_Event(evt->model, evt->type); + + if (event) { + event->extra.eventee = evt->eventee; + event->payload.event.type = evt->type; + event->payload.event.flags = evt->flags; + event->payload.event.win = evt->win; + event->payload.event.model = evt->model; + event->payload.event.data = evt->data; + return 1; + } + + return RL_Event(evt) - 1; +} + + +// +// RL_Find_Event: C +// +// Find an application event (e.g. GUI) to the event port. +// +// Returns: +// A pointer to the find event +// Arguments: +// model - event model +// type - event type +// +RL_API REBEVT *RL_Find_Event (REBINT model, REBINT type) { - REBSER spec = {0}; - REBSER *text; - REBVAL *val; -#ifdef DUMP_INIT_SCRIPT - int f; -#endif + REBVAL * val = Find_Last_Event(model, type); + if (val != NULL) { + return cast(REBEVT*, val); // should be compatible! + } + return NULL; +} - //Cloak(TRUE, code, NAT_SPEC_SIZE, &key[0], 20, TRUE); - spec.data = bin; - spec.tail = length; - text = Decompress(&spec, 0, -1, 10000000, 0); - if (!text) return FALSE; - Append_Byte(text, 0); - -#ifdef DUMP_INIT_SCRIPT - f = _open("host-boot.r", _O_CREAT | _O_RDWR, _S_IREAD | _S_IWRITE ); - _write(f, STR_HEAD(text), LEN_BYTES(STR_HEAD(text))); - _close(f); -#endif - SAVE_SERIES(text); - val = Do_String(text->data, flags); - UNSAVE_SERIES(text); - if (IS_ERROR(val)) // && (VAL_ERR_NUM(val) != RE_QUIT)) { - Print_Value(val, 1000, FALSE); - - if (result) { - *result = Value_To_RXI(val); - return Reb_To_RXT[VAL_TYPE(val)]; - } - return 0; -} - - -/*********************************************************************** -** -*/ RL_API int RL_Do_Block(REBSER *blk, REBCNT flags, RXIARG *result) -/* -** Evaluate a block. (not implemented) -** -** Returns: -** The datatype of the result or zero if error in the encoding. -** Arguments: -** blk - A pointer to the block series -** flags - set to zero for now -** result - value returned from evaluation -** Notes: -** Not implemented. Contact Carl on R3 Chat if you think you -** could use it for something. -** -***********************************************************************/ -{ - return 0; -} - - -/*********************************************************************** -** -*/ RL_API void RL_Do_Commands(REBSER *blk, REBCNT flags, REBCEC *context) -/* -** Evaluate a block of extension commands at high speed. -** -** Returns: -** Nothing -** Arguments: -** blk - a pointer to the block series -** flags - set to zero for now -** context - command evaluation context struct or zero if not used. -** Notes: -** For command blocks only, not for other blocks. -** The context allows passing to each command a struct that is -** used for back-referencing your environment data or for tracking -** the evaluation block and its index. -** -***********************************************************************/ -{ - Do_Commands(blk, context); -} - - -/*********************************************************************** -** -*/ RL_API void RL_Print(REBYTE *fmt, ...) -/* -** Low level print of formatted data to the console. -** -** Returns: -** nothing -** Arguments: -** fmt - A format string similar but not identical to printf. -** Special options are available. -** ... - Values to be formatted. -** Notes: -** This function is low level and handles only a few C datatypes -** at this time. -** -***********************************************************************/ -{ - va_list args; - va_start(args, fmt); - Debug_Buf(fmt, args); // Limits line size - va_end(args); -} - - -/*********************************************************************** -** -*/ RL_API void RL_Print_TOS(REBCNT flags, REBYTE *marker) -/* -** Print top REBOL stack value to the console. (pending changes) -** -** Returns: -** Nothing -** Arguments: -** flags - special flags (set to zero at this time). -** marker - placed at beginning of line to indicate output. -** Notes: -** This function is used for the main console evaluation -** input loop to print the results of evaluation from stack. -** The REBOL data stack is an abstract structure that can -** change between releases. This function allows the host -** to print the result of processed functions. -** Note that what is printed is actually TOS+1. -** Marker is usually "==" to show output. -** The system/options/result-types determine which values -** are automatically printed. -** -***********************************************************************/ -{ - REBINT dsp = DSP; - REBVAL *top = DS_VALUE(dsp+1); - REBOL_STATE state; - REBVAL *types; - - if (dsp != 0) Debug_Fmt(Str_Stack_Misaligned, dsp); - - PUSH_STATE(state, Saved_State); - if (SET_JUMP(state)) { - POP_STATE(state, Saved_State); - Catch_Error(DS_NEXT); // Stores error value here - Out_Value(DS_NEXT, 0, FALSE, 0); // error - DSP = 0; - return; - } - SET_STATE(state, Saved_State); - - if (!IS_UNSET(top)) { - if (!IS_ERROR(top)) { - types = Get_System(SYS_OPTIONS, OPTIONS_RESULT_TYPES); - if (IS_TYPESET(types) && TYPE_CHECK(types, VAL_TYPE(top))) { - if (marker) Out_Str(marker, 0); - Out_Value(top, 500, TRUE, 1); // limit, molded - } -// else { -// Out_Str(Get_Type_Name(top), 1); -// } - } else { - if (VAL_ERR_NUM(top) != RE_HALT) { - Out_Value(top, 640, FALSE, 0); // error FORMed -// if (VAL_ERR_NUM(top) > RE_THROW_MAX) { -// Out_Str("** Note: use WHY? for more about this error", 1); -// } - } - } - } - - POP_STATE(state, Saved_State); - DSP = 0; -} - - -/*********************************************************************** -** -*/ RL_API int RL_Event(REBEVT *evt) -/* -** Appends an application event (e.g. GUI) to the event port. -** -** Returns: -** Returns TRUE if queued, or FALSE if event queue is full. -** Arguments: -** evt - A properly initialized event structure. The -** contents of this structure are copied as part of -** the function, allowing use of locals. -** Notes: -** Sets a signal to get REBOL attention for WAIT and awake. -** To avoid environment problems, this function only appends -** to the event queue (no auto-expand). So if the queue is full -** -***********************************************************************/ -{ - REBVAL *event = Append_Event(); // sets signal - - if (event) { // null if no room left in series - VAL_SET(event, REB_EVENT); // (has more space, if we need it) - event->data.event = *evt; - return 1; - } - - return 0; -} - - -RL_API void *RL_Make_Block(u32 size) -/* -** Allocate a new block. -** -** Returns: -** A pointer to a block series. -** Arguments: -** size - the length of the block. The system will add one extra -** for the end-of-block marker. -** Notes: -** Blocks are allocated with REBOL's internal memory manager. -** Internal structures may change, so NO assumptions should be made! -** Blocks are automatically garbage collected if there are -** no references to them from REBOL code (C code does nothing.) -** However, you can lock blocks to prevent deallocation. (?? default) -*/ -{ - return Make_Block(size); -} - -RL_API void *RL_Make_String(u32 size, int unicode) -/* -** Allocate a new string or binary series. -** -** Returns: -** A pointer to a string or binary series. -** Arguments: -** size - the length of the string. The system will add one extra -** for a null terminator (not strictly required, but good for C.) -** unicode - set FALSE for ASCII/Latin1 strings, set TRUE for Unicode. -** Notes: -** Strings can be REBYTE or REBCHR sized (depends on R3 config.) -** Strings are allocated with REBOL's internal memory manager. -** Internal structures may change, so NO assumptions should be made! -** Strings are automatically garbage collected if there are -** no references to them from REBOL code (C code does nothing.) -** However, you can lock strings to prevent deallocation. (?? default) -*/ -{ - return unicode ? Make_Unicode(size) : Make_Binary(size); -} - -RL_API void *RL_Make_Image(u32 width, u32 height) -/* -** Allocate a new image of the given size. -** -** Returns: -** A pointer to an image series, or zero if size is too large. -** Arguments: -** width - the width of the image in pixels -** height - the height of the image in lines -** Notes: -** Images are allocated with REBOL's internal memory manager. -** Image are automatically garbage collected if there are -** no references to them from REBOL code (C code does nothing.) -*/ +// +// RL_Gob_Head: C +// +RL_API REBGOB** RL_Gob_Head(REBGOB *gob) { - return Make_Image(width, height, FALSE); -} - -RL_API void RL_Protect_GC(REBSER *series, u32 flags) -/* -** Protect memory from garbage collection. -** -** Returns: -** nothing -** Arguments: -** series - a series to protect (block, string, image, ...) -** flags - set to 1 to protect, 0 to unprotect -** Notes: -** You should only use this function when absolutely necessary, -** because it bypasses garbage collection for the specified series. -** Meaning: if you protect a series, it will never be freed. -** Also, you only need this function if you allocate several series -** such as strings, blocks, images, etc. within the same command -** and you don't store those references somewhere where the GC can -** find them, such as in an existing block or object (variable). -*/ -{ - (flags == 1) ? SERIES_SET_FLAG(series, SER_KEEP) : SERIES_CLR_FLAG(series, SER_KEEP); -} - -RL_API int RL_Get_String(REBSER *series, u32 index, void **str) -/* -** Obtain a pointer into a string (bytes or unicode). -** -** Returns: -** The length and type of string. When len > 0, string is unicode. -** When len < 0, string is bytes. -** Arguments: -** series - string series pointer -** index - index from beginning (zero-based) -** str - pointer to first character -** Notes: -** If the len is less than zero, then the string is optimized to -** codepoints (chars) 255 or less for ASCII and LATIN-1 charsets. -** Strings are allowed to move in memory. Therefore, you will want -** to make a copy of the string if needed. -*/ -{ // ret: len or -len - int len = (index >= series->tail) ? 0 : series->tail - index; - - if (BYTE_SIZE(series)) { - *str = BIN_SKIP(series, index); - len = -len; - } - else { - *str = UNI_SKIP(series, index); - } - - return len; -} - -RL_API u32 RL_Map_Word(REBYTE *string) -/* -** Given a word as a string, return its global word identifier. -** -** Returns: -** The word identifier that matches the string. -** Arguments: -** string - a valid word as a UTF-8 encoded string. -** Notes: -** Word identifiers are persistent, and you can use them anytime. -** If the word is new (not found in master symbol table) -** it will be added and the new word identifier is returned. -*/ -{ - return Make_Word(string, 0); -} - -RL_API u32 *RL_Map_Words(REBSER *series) -/* -** Given a block of word values, return an array of word ids. -** -** Returns: -** An array of global word identifiers (integers). The [0] value is the size. -** Arguments: -** series - block of words as values (from REBOL blocks, not strings.) -** Notes: -** Word identifiers are persistent, and you can use them anytime. -** The block can include any kind of word, including set-words, lit-words, etc. -** If the input block contains non-words, they will be skipped. -** The array is allocated with OS_MAKE and you can OS_FREE it any time. -*/ -{ - REBCNT i = 1; - u32 *words; - REBVAL *val = BLK_HEAD(series); - - words = OS_MAKE((series->tail+2) * sizeof(u32)); - - for (; NOT_END(val); val++) { - if (ANY_WORD(val)) words[i++] = VAL_WORD_CANON(val); - } - - words[0] = i; - words[i] = 0; - - return words; -} - -RL_API REBYTE *RL_Word_String(u32 word) -/* -** Return a string related to a given global word identifier. -** -** Returns: -** A copy of the word string, null terminated. -** Arguments: -** word - a global word identifier -** Notes: -** The result is a null terminated copy of the name for your own use. -** The string is always UTF-8 encoded (chars > 127 are encoded.) -** In this API, word identifiers are always canonical. Therefore, -** the returned string may have different spelling/casing than expected. -** The string is allocated with OS_MAKE and you can OS_FREE it any time. -*/ -{ - REBYTE *s1, *s2; - // !!This code should use a function from c-words.c (but nothing perfect yet.) - if (word == 0 || word >= PG_Word_Table.series->tail) return 0; - s1 = VAL_SYM_NAME(BLK_SKIP(PG_Word_Table.series, word)); - s2 = OS_MAKE(strlen(s1)); - strcpy(s2, s1); - return s2; -} - -RL_API u32 RL_Find_Word(u32 *words, u32 word) -/* -** Given an array of word ids, return the index of the given word. -** -** Returns: -** The index of the given word or zero. -** Arguments: -** words - a word array like that returned from MAP_WORDS (first element is size) -** word - a word id -** Notes: -** The first element of the word array is the length of the array. -*/ -{ - REBCNT n = 0; - - if (words == 0) return 0; - - for (n = 1; n < words[0]; n++) { - if (words[n] == word) return n; - } - return 0; -} - -RL_API int RL_Series(REBSER *series, REBCNT what) -/* -** Get series information. -** -** Returns: -** Returns information related to a series. -** Arguments: -** series - any series pointer (string or block) -** what - indicates what information to return (see RXI_SER enum) -** Notes: -** Invalid what arg nums will return zero. -*/ -{ - switch (what) { - case RXI_SER_DATA: return (int)SERIES_DATA(series); // problem for 64 bit !! - case RXI_SER_TAIL: return SERIES_TAIL(series); - case RXI_SER_LEFT: return SERIES_AVAIL(series); - case RXI_SER_SIZE: return SERIES_REST(series); - case RXI_SER_WIDE: return SERIES_WIDE(series); - } - return 0; -} - -RL_API int RL_Get_Char(REBSER *series, u32 index) -/* -** Get a character from byte or unicode string. -** -** Returns: -** A Unicode character point from string. If index is -** at or past the tail, a -1 is returned. -** Arguments: -** series - string series pointer -** index - zero based index of character -** Notes: -** This function works for byte and unicoded strings. -** The maximum size of a Unicode char is determined by -** R3 build options. The default is 16 bits. -*/ -{ - if (index >= series->tail) return -1; - return GET_ANY_CHAR(series, index); -} - -RL_API u32 RL_Set_Char(REBSER *series, u32 index, u32 chr) -/* -** Set a character into a byte or unicode string. -** -** Returns: -** The index passed as an argument. -** Arguments: -** series - string series pointer -** index - where to store the character. If past the tail, -** the string will be auto-expanded by one and the char -** will be appended. -*/ -{ - if (index >= series->tail) { - index = series->tail; - EXPAND_SERIES_TAIL(series, 1); - } - SET_ANY_CHAR(series, index, chr); - return index; -} - -RL_API int RL_Get_Value(REBSER *series, u32 index, RXIARG *result) -/* -** Get a value from a block. -** -** Returns: -** Datatype of value or zero if index is past tail. -** Arguments: -** series - block series pointer -** index - index of the value in the block (zero based) -** result - set to the value of the field -*/ -{ - REBVAL *value; - if (index >= series->tail) return 0; - value = BLK_SKIP(series, index); - *result = Value_To_RXI(value); - return Reb_To_RXT[VAL_TYPE(value)]; -} - -RL_API int RL_Set_Value(REBSER *series, u32 index, RXIARG val, int type) -/* -** Set a value in a block. -** -** Returns: -** TRUE if index past end and value was appended to tail of block. -** Arguments: -** series - block series pointer -** index - index of the value in the block (zero based) -** val - new value for field -** type - datatype of value -*/ -{ - REBVAL value = {0}; - RXI_To_Value(&value, val, type); - if (index >= series->tail) { - Append_Val(series, &value); - return TRUE; - } - *BLK_SKIP(series, index) = value; - return FALSE; -} - -RL_API u32 *RL_Words_Of_Object(REBSER *obj) -/* -** Returns information about the object. -** -** Returns: -** Returns an array of words used as fields of the object. -** Arguments: -** obj - object pointer (e.g. from RXA_OBJECT) -** Notes: -** Returns a word array similar to MAP_WORDS(). -** The array is allocated with OS_MAKE. You can OS_FREE it any time. -*/ -{ - REBCNT index; - u32 *words; - REBVAL *syms; - - syms = FRM_WORD(obj, 1); - words = OS_MAKE(obj->tail * sizeof(u32)); // One less, because SELF not included. - for (index = 0; index < (obj->tail-1); syms++, index++) { - words[index] = VAL_BIND_CANON(syms); - } - words[index] = 0; - return words; -} - -RL_API int RL_Get_Field(REBSER *obj, u32 word, RXIARG *result) -/* -** Get a field value (context variable) of an object. -** -** Returns: -** Datatype of value or zero if word is not found in the object. -** Arguments: -** obj - object pointer (e.g. from RXA_OBJECT) -** word - global word identifier (integer) -** result - gets set to the value of the field -*/ -{ - REBVAL *value; - if (!(word = Find_Word_Index(obj, word, FALSE))) return 0; - value = BLK_SKIP(obj, word); - *result = Value_To_RXI(value); - return Reb_To_RXT[VAL_TYPE(value)]; -} - -RL_API int RL_Set_Field(REBSER *obj, u32 word, RXIARG val, int type) -/* -** Set a field (context variable) of an object. -** -** Returns: -** The type arg, or zero if word not found in object or if field is protected. -** Arguments: -** obj - object pointer (e.g. from RXA_OBJECT) -** word - global word identifier (integer) -** val - new value for field -** type - datatype of value -*/ -{ - REBVAL value = {0}; - if (!(word = Find_Word_Index(obj, word, FALSE))) return 0; - if (VAL_PROTECTED(FRM_WORDS(obj)+word)) return 0; // Trap1(RE_LOCKED_WORD, word); - RXI_To_Value(FRM_VALUES(obj)+word, val, type); - return type; -} - -RL_API int RL_Callback(RXICBI *cbi) -/* -** Evaluate a REBOL callback function, either synchronous or asynchronous. -** -** Returns: -** Sync callback: type of the result; async callback: true if queued -** Arguments: -** cbi - callback information including special option flags, -** object pointer (where function is located), function name -** as global word identifier (within above object), argument list -** passed to callback (see notes below), and result value. -** Notes: -** The flag value will determine the type of callback. It can be either -** synchronous, where the code will re-enter the interpreter environment -** and call the specified function, or asynchronous where an EVT_CALLBACK -** event is queued, and the callback will be evaluated later when events -** are processed within the interpreter's environment. -** For asynchronous callbacks, the cbi and the args array must be managed -** because the data isn't processed until the callback event is -** handled. Therefore, these cannot be allocated locally on -** the C stack; they should be dynamic (or global if so desired.) -** See c:extensions-callbacks -*/ -{ - REBEVT evt; - - // Synchronous callback? - if (!GET_FLAG(cbi->flags, RXC_ASYNC)) { - return Do_Callback(cbi->obj, cbi->word, cbi->args, &(cbi->result)); - } + return SER_HEAD(REBGOB*, GOB_PANE(gob)); +} - CLEARS(&evt); - evt.type = EVT_CALLBACK; - evt.model = EVM_CALLBACK; - evt.ser = (void*)cbi; - SET_FLAG(cbi->flags, RXC_QUEUED); - return RL_Event(&evt); // (returns 0 if queue is full, ignored) +// +// RL_Gob_String: C +// +RL_API REBYTE* RL_Gob_String(REBGOB *gob) +{ + return BIN_HEAD(GOB_CONTENT(gob)); } +// +// RL_Gob_Len: C +// +RL_API REBCNT RL_Gob_Len(REBGOB *gob) +{ + return SER_LEN(GOB_PANE(gob)); +} + + +// +// RL_Encode_UTF8: C +// +// Encode the unicode into UTF8 byte string. +// +// Returns: +// Number of dst bytes used. +// +// Arguments: +// dst - destination for encoded UTF8 bytes +// max - maximum size of the result in bytes +// src - source array of bytes or wide characters +// len - input is source length, updated to reflect src chars used +// unicode - true if src is in wide character format +// crlf_to_lf - convert carriage-return + linefeed into just linefeed +// +// Notes: +// Does not add a terminator. +// +// !!! Host code is not supposed to call any Rebol routines except +// for those in the RL_Api. This exposes Rebol's internal UTF8 +// length routine, as it was being used by the Linux host code by +// Atronix. Should be reviewed along with the rest of the RL_Api. +// +RL_API REBCNT RL_Encode_UTF8( + REBYTE *dst, + REBINT max, + const void *src, + REBCNT *len, + REBOOL unicode, + REBOOL crlf_to_lf +) { + return Encode_UTF8( + dst, + max, + src, + len, + (unicode ? OPT_ENC_UNISRC : 0) | (crlf_to_lf ? OPT_ENC_CRLF : 0) + ); +} + + +// +// !!! These routines are exports of the macros and inline functions which +// rely upon internal definitions that RL_XXX clients are not expected to have +// available. While this implementation file can see inside the definitions +// of `struct Reb_Value`, the caller has an opaque definition. +// +// These are transitional as part of trying to get rid of RXIARG, RXIFRM, and +// COMMAND! in general. Though it is not a good "API design" to just take +// any internal function you find yourself needing in a client and export it +// here with "RL_" in front of the name, it's at least understandable--and +// not really introducing any routines that don't already have to exist and +// be tested. +// +// However, long term the external "C" user API will not speak about REBSERs. +// It will operate purely on the level of REBVAL*, where those values will +// either be individually managed (as "pairings" under GC control) or have +// their lifetime controlled other ways. That layer of API is of secondary +// importance to refining the internal API (also used by "user natives") +// as well as the Ren-Cpp API...although it will use several of the same +// mechanisms that Ren-Cpp does to achieve its goals. +// + +inline static REBFRM *Extract_Live_Rebfrm_May_Fail(const REBVAL *frame) { + if (!IS_FRAME(frame)) + fail ("Not a FRAME!"); + + REBCTX *frame_ctx = VAL_CONTEXT(frame); + REBFRM *f = CTX_FRAME_IF_ON_STACK(frame_ctx); + if (f == NULL) + fail ("FRAME! is no longer on stack."); + + assert(Is_Any_Function_Frame(f)); + assert(NOT(Is_Function_Frame_Fulfilling(f))); + return f; +} + + +// +// RL_Frm_Num_Args: C +// +RL_API REBCNT RL_Frm_Num_Args(const REBVAL *frame) { + REBFRM *f = Extract_Live_Rebfrm_May_Fail(frame); + return FRM_NUM_ARGS(f); +} + +// +// RL_Frm_Arg: C +// +RL_API REBVAL *RL_Frm_Arg(const REBVAL *frame, REBCNT n) { + REBFRM *f = Extract_Live_Rebfrm_May_Fail(frame); + return FRM_ARG(f, n); +} + +// +// RL_Val_Logic: C +// +RL_API REBOOL RL_Val_Logic(const REBVAL *v) { + return VAL_LOGIC(v); +} + +// +// RL_Val_Type: C +// +// !!! Among the few concepts from the original host kit API that may make +// sense, it could be a good idea to abstract numbers for datatypes from the +// REB_XXX numbering scheme. So for the moment, REBRXT is being kept as is. +// +RL_API REBRXT RL_Val_Type(const REBVAL *v) { + return IS_VOID(v) + ? 0 + : Reb_To_RXT[VAL_TYPE(v)]; +} + + +// +// RL_Val_Update_Header: C +// +RL_API void RL_Val_Update_Header(REBVAL *v, REBRXT rxt) { + if (rxt == 0) + Init_Void(v); + else + VAL_RESET_HEADER(v, RXT_To_Reb[rxt]); +} + + +// +// RL_Val_Int64: C +// +RL_API REBI64 RL_Val_Int64(const REBVAL *v) { + return VAL_INT64(v); +} + +// +// RL_Val_Int32: C +// +RL_API REBINT RL_Val_Int32(const REBVAL *v) { + return VAL_INT32(v); +} + +// +// RL_Val_Decimal: C +// +RL_API REBDEC RL_Val_Decimal(const REBVAL *v) { + return VAL_DECIMAL(v); +} + +// +// RL_Val_Char: C +// +RL_API REBUNI RL_Val_Char(const REBVAL *v) { + return VAL_CHAR(v); +} + +// +// RL_Val_Time: C +// +RL_API REBI64 RL_Val_Time(const REBVAL *v) { + return VAL_NANO(v); +} + +// +// RL_Val_Date: C +// +RL_API REBINT RL_Val_Date(const REBVAL *v) { + return VAL_DATE(v).bits; // !!! Is this right? +} + +// +// RL_Val_Tuple_Data: C +// +RL_API REBYTE *RL_Val_Tuple_Data(const REBVAL *v) { + return VAL_TUPLE_DATA(m_cast(REBVAL*, v)); +} + +// +// RL_Val_Index: C +// +RL_API REBCNT RL_Val_Index(const REBVAL *v) { + return VAL_INDEX(v); +} + +// +// RL_Init_Val_Index: C +// +RL_API void RL_Init_Val_Index(REBVAL *v, REBCNT i) { + VAL_INDEX(v) = i; +} + +// +// RL_Val_Handle_Pointer: C +// +RL_API void *RL_Val_Handle_Pointer(const REBVAL *v) { + return VAL_HANDLE_POINTER(void, v); +} + +// +// RL_Set_Handle_Pointer: C +// +RL_API void RL_Set_Handle_Pointer(REBVAL *v, void *p) { + v->extra.singular = NULL; // !!! only support "dumb" handles for now + SET_HANDLE_POINTER(v, p); +} + +// +// RL_Val_Image_Wide: C +// +RL_API REBCNT RL_Val_Image_Wide(const REBVAL *v) { + return VAL_IMAGE_WIDE(v); +} + +// +// RL_Val_Image_High: C +// +RL_API REBCNT RL_Val_Image_High(const REBVAL *v) { + return VAL_IMAGE_HIGH(v); +} + +// +// RL_Val_Pair_X_Float: C +// +// !!! Pairs in R3-Alpha were not actually pairs of arbitrary values; but +// they were pairs of floats. This meant their precision did not match either +// 64-bit integers or 64-bit decimals, because you can't fit two of those in +// one REBVAL and still have room for a header. Ren-C changed the mechanics +// so that two actual values were efficiently stored in a PAIR! via a special +// kind of GC-able series node (with no further allocation). Hence you can +// tell the difference between 1x2, 1.0x2.0, 1x2.0, 1.0x2, etc. +// +// Yet the R3-Alpha external interface did not make this distinction, so this +// API is for compatibility with those extracting floats. +// +RL_API float RL_Val_Pair_X_Float(const REBVAL *v) { + return VAL_PAIR_X(v); +} + +// +// RL_Val_Pair_Y_Float: C +// +// !!! See notes on RL_Val_Pair_X_Float +// +RL_API float RL_Val_Pair_Y_Float(const REBVAL *v) { + return VAL_PAIR_Y(v); +} + +// +// RL_Init_Date: C +// +// There was a data structure called a REBOL_DAT in R3-Alpha which was defined +// in %reb-defs.h, and it appeared in the host callbacks to be used in +// `os_get_time()` and `os_file_time()`. This allowed the host to pass back +// date information without actually knowing how to construct a date REBVAL. +// +// Today "host code" (which may all become "port code") is expected to either +// be able to speak in terms of Rebol values through linkage to the internal +// API or the more minimal RL_Api. Either way, it should be able to make +// REBVALs corresponding to dates...even if that means making a string of +// the date to load and then RL_Do_String() to produce the value. +// +// This routine is a quick replacement for the format of the struct, as a +// temporary measure while it is considered whether things like os_get_time() +// will have access to the full internal API or not. +// +RL_API void RL_Init_Date( + REBVAL *out, + int year, + int month, + int day, + int seconds, + int nano, + int zone +) { + VAL_RESET_HEADER(out, REB_DATE); + VAL_YEAR(out) = year; + VAL_MONTH(out) = month; + VAL_DAY(out) = day; + VAL_ZONE(out) = zone / ZONE_MINS; + VAL_NANO(out) = SECS_TO_NANO(seconds) + nano; +} + #include "reb-lib-lib.h" -/*********************************************************************** -** -*/ void *Extension_Lib(void) -/* -***********************************************************************/ +// +// Extension_Lib: C +// +void *Extension_Lib(void) { - return &Ext_Lib; + return &Ext_Lib; } diff --git a/src/core/a-lib2.c b/src/core/a-lib2.c deleted file mode 100644 index ec1647196e..0000000000 --- a/src/core/a-lib2.c +++ /dev/null @@ -1,127 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: a-lib2.c -** Summary: skip -** Section: environment -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ - -#include "sys-core.h" -#include "reb-dialect.h" -#include "reb-ext.h" -#include "sys-state.h" - - -// Load this to verify function prototypes: -#include "rebol-lib.h" - -/*********************************************************************** -** -x*/ REBOL_API REBINT Reb_Dialect(REBINT dialect, REBSER *block, REBCNT *index, REBSER **arglist) -/* -** Process a standard dialect. -** -** The index points to the next value to interpret and is updated -** on return (for next loop or error). The system/dialect -** object is used for the dialect specification. -** -** A block is returned with the arguments, ordered according -** to the dialect specification for the command. Note that the -** returned block is reset and reused with each command. (To -** minimize GC trash.). The cmd arg returns the command number -** or error number (when result is zero). -** -** A zero is returned for errors and end-of-block. For the former -** an error is returned in cmd. For the latter, cmd is zero. -** -***********************************************************************/ -{ - REBVAL *val = Get_System(SYS_DIALECTS, 0); - - if (!IS_OBJECT(val) - || dialect <= 0 - || dialect >= (REBINT)SERIES_TAIL(VAL_OBJ_FRAME(val)) - ) { - return -REB_DIALECT_MISSING; - } - - val = Get_System(SYS_DIALECTS, dialect); - if (!IS_OBJECT(val)) return -REB_DIALECT_MISSING;; - return Do_Dialect(VAL_OBJ_FRAME(val), block, index, arglist); -} - - -/*********************************************************************** -** -x*/ REBOL_API void Reb_Set_Var(void *var, void *value) -/* -***********************************************************************/ -{ - Set_Var(var, value); // Check context, index, range -} - - -/*********************************************************************** -** -x*/ REBOL_API REBINT Reb_Map_Words(REBYTE **names, REBCNT *symbols) -/* -** Given null terminated list of word names, supply the -** symbol values for those words. Return length. -** The names must be UTF8 valid. -** -***********************************************************************/ -{ - REBINT count = 0; - - for (; *names; names++, count++) { - *symbols++ = Make_Word(*names, 0); - } - *symbols++ = 0; - - return count; -} - - -/*********************************************************************** -** -x*/ REBOL_API REBINT Reb_Find_Word(REBCNT sym, REBCNT *symbols, REBINT limit) -/* -** Search a symbol list for a word, and return the index for it. -** Return -1 if not found. Limit can be used to control how many -** words in the symbol list will be compared. -** -***********************************************************************/ -{ - REBINT index; - - if (sym >= SERIES_TAIL(PG_Word_Table.series)) return -1; - if (limit == 0) limit = 100000; - - for (index = 0; limit > 0 && symbols[index]; limit--, index++) { - if (sym == symbols[index] || SYMBOL_TO_CANON(sym) == SYMBOL_TO_CANON(symbols[index])) - return index; - } - - return -1; -} diff --git a/src/core/a-stubs.c b/src/core/a-stubs.c deleted file mode 100644 index ddf435b0ae..0000000000 --- a/src/core/a-stubs.c +++ /dev/null @@ -1,30 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: a-stubs.c -** Summary: function stubs -** Section: environment -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ - -#include "sys-core.h" diff --git a/src/core/b-init.c b/src/core/b-init.c index 1978ae0a7e..6d108704c5 100644 --- a/src/core/b-init.c +++ b/src/core/b-init.c @@ -1,990 +1,1466 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: b-init.c -** Summary: initialization functions -** Section: bootstrap -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %b-init.c +// Summary: "initialization functions" +// Section: bootstrap +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The primary routine for starting up Rebol is Startup_Core(). It runs the +// bootstrap in phases, based on processing various portions of the data in +// %tmp-boot-block.r (which is the aggregated code from the %mezz/*.r files, +// packed into one file as part of the build preparation). +// +// As part of an effort to lock down the memory usage, Ren-C added a parallel +// Shutdown_Core() routine which would gracefully exit Rebol, with assurances +// that all accounting was done correctly. This includes being sure that the +// number used to track memory usage for triggering garbage collections would +// balance back out to exactly zero. +// +// (Release builds can instead close only vital resources like files, and +// trust the OS exit() to reclaim memory more quickly. However Ren-C's goal +// is to be usable as a library that may be initialized and shutdown within +// a process that's not exiting, so the ability to clean up is important.) +// + #include "sys-core.h" -#include "sys-state.h" +#include "mem-pools.h" #define EVAL_DOSE 10000 -// Boot Vars used locally: -static REBCNT Native_Count; -static REBCNT Native_Limit; -static REBCNT Action_Count; -static REBCNT Action_Marker; -static REBFUN *Native_Functions; -static BOOT_BLK *Boot_Block; - -extern const REBYTE Str_Banner[]; -#ifdef WATCH_BOOT -#define DOUT(s) puts(s) +// +// Assert_Basics: C +// +static void Assert_Basics(void) +{ +#if !defined(NDEBUG) && defined(SHOW_SIZEOFS) + // + // For debugging ports to some systems + // +#if defined(__LP64__) || defined(__LLP64__) + const char *fmt = "%lu %s\n"; #else -#define DOUT(s) + const char *fmt = "%u %s\n"; #endif - -/*********************************************************************** -** -*/ static void Assert_Basics() -/* -***********************************************************************/ -{ - REBVAL val; - - VAL_SET(&val, 123); -#ifdef WATCH_BOOT - printf("TYPE(123)=%d val=%d dat=%d gob=%d\n", - VAL_TYPE(&val), sizeof(REBVAL), sizeof(REBDAT), sizeof(REBGOB)); + union Reb_Value_Payload *dummy_payload; + + printf(fmt, sizeof(dummy_payload->any_word), "any_word"); + printf(fmt, sizeof(dummy_payload->any_series), "any_series"); + printf(fmt, sizeof(dummy_payload->integer), "integer"); + printf(fmt, sizeof(dummy_payload->decimal), "decimal"); + printf(fmt, sizeof(dummy_payload->character), "char"); + printf(fmt, sizeof(dummy_payload->datatype), "datatype"); + printf(fmt, sizeof(dummy_payload->typeset), "typeset"); + printf(fmt, sizeof(dummy_payload->time), "time"); + printf(fmt, sizeof(dummy_payload->tuple), "tuple"); + printf(fmt, sizeof(dummy_payload->function), "function"); + printf(fmt, sizeof(dummy_payload->any_context), "any_context"); + printf(fmt, sizeof(dummy_payload->pair), "pair"); + printf(fmt, sizeof(dummy_payload->event), "event"); + printf(fmt, sizeof(dummy_payload->library), "library"); + printf(fmt, sizeof(dummy_payload->structure), "struct"); + printf(fmt, sizeof(dummy_payload->gob), "gob"); + printf(fmt, sizeof(dummy_payload->money), "money"); + printf(fmt, sizeof(dummy_payload->handle), "handle"); + printf(fmt, sizeof(dummy_payload->all), "all"); + fflush(stdout); #endif -#ifdef SHOW_SIZEOFS - // For debugging ports to some systems: - printf("%d %s\n", sizeof(REBWRD), "word"); - printf("%d %s\n", sizeof(REBSRI), "series"); - printf("%d %s\n", sizeof(REBCNT), "logic"); - printf("%d %s\n", sizeof(REBI64), "integer"); - printf("%d %s\n", sizeof(REBU64), "unteger"); - printf("%d %s\n", sizeof(REBINT), "int32"); - printf("%d %s\n", sizeof(REBDEC), "decimal"); - printf("%d %s\n", sizeof(REBUNI), "uchar"); - printf("%d %s\n", sizeof(REBERR), "error"); - printf("%d %s\n", sizeof(REBTYP), "datatype"); - printf("%d %s\n", sizeof(REBFRM), "frame"); - printf("%d %s\n", sizeof(REBWRS), "wordspec"); - printf("%d %s\n", sizeof(REBTYS), "typeset"); - printf("%d %s\n", sizeof(REBSYM), "symbol"); - printf("%d %s\n", sizeof(REBTIM), "time"); - printf("%d %s\n", sizeof(REBTUP), "tuple"); - printf("%d %s\n", sizeof(REBFCN), "func"); - printf("%d %s\n", sizeof(REBOBJ), "object"); - printf("%d %s\n", sizeof(REBXYF), "pair"); - printf("%d %s\n", sizeof(REBEVT), "event"); - printf("%d %s\n", sizeof(REBLIB), "library"); - printf("%d %s\n", sizeof(REBROT), "routine"); - printf("%d %s\n", sizeof(REBSTU), "structure"); - printf("%d %s\n", sizeof(REBGBO), "gob"); - printf("%d %s\n", sizeof(REBUDT), "utype"); - printf("%d %s\n", sizeof(REBDCI), "deci"); - printf("%d %s\n", sizeof(REBHAN), "handle"); - printf("%d %s\n", sizeof(REBALL), "all"); +#if !defined(NDEBUG) + // + // Sanity check the platform byte-ordering sensitive flag macros + // + REBUPT flags; + + flags = FLAGIT_LEFT(0); + unsigned char *ch = (unsigned char*)&flags; + if (*ch != 128) { + printf("Expected 128, got %d\n", *ch); + panic ("Bad leftmost bit setting of platform unsigned integer."); + } + + flags = FLAGIT_LEFT(0) | FLAGIT_LEFT(1) | FLAGBYTE_RIGHT(13); + + unsigned int left = LEFT_N_BITS(flags, 3); // == 6 (binary `110`) + unsigned int right = RIGHT_N_BITS(flags, 3); // == 5 (binary `101`) + if (left != 6 || right != 5) { + printf("Expected 6 and 5, got %u and %u\n", left, right); + panic ("Bad composed integer assignment for byte-ordering macro."); + } #endif - ASSERT(VAL_TYPE(&val) == 123, RP_REBVAL_ALIGNMENT); - ASSERT(sizeof(REBVAL) == 16, RP_REBVAL_ALIGNMENT); - ASSERT1(sizeof(REBDAT) == 4, RP_BAD_SIZE); - ASSERT1(sizeof(REBGOB) == 64, RP_BAD_SIZE); + // Although the system is designed to be able to function with REBVAL at + // any size, the optimization of it being 4x(32-bit) on 32-bit platforms + // and 4x(64-bit) on 64-bit platforms is a rather important performance + // point. For the moment we consider it to be essential enough to the + // intended function of the system that it refuses to run if not true. + // + // But if someone is in an odd situation and understands why the size did + // not work out as designed, it *should* be possible to comment this out + // and keep running. + // + if (sizeof(REBVAL) != sizeof(void*) * 4) + panic ("size of REBVAL is not sizeof(void*) * 4"); + + assert(sizeof(REBEVT) == sizeof(REBVAL)); + + // The REBSER is designed to place the `info` bits exactly after a REBVAL + // so they can do double-duty as also a terminator for that REBVAL when + // enumerated as an ARRAY. + // + if ( + offsetof(REBSER, info) - offsetof(REBSER, content) != sizeof(REBVAL) + ){ + panic ("bad structure alignment for internal array termination"); + } + + // Void cells currently use REB_MAX for the type bits, and the debug + // build uses REB_MAX + 1 for signaling "trash". At most 64 "Reb_Kind" + // types are used at the moment, yet the type is a byte for efficient + // reading, so there's little danger of hitting this unless there's + // a big change. + // + assert(REB_MAX + 1 < 256); + + // Make sure tricks for "internal END markers" are lined up as expected. + // + assert(SERIES_INFO_0_IS_TRUE == NODE_FLAG_NODE); + assert(SERIES_INFO_1_IS_FALSE == NODE_FLAG_FREE); + assert(SERIES_INFO_4_IS_TRUE == NODE_FLAG_END); + assert(SERIES_INFO_7_IS_FALSE == NODE_FLAG_CELL); + + assert(DO_FLAG_0_IS_TRUE == NODE_FLAG_NODE); + assert(DO_FLAG_1_IS_FALSE == NODE_FLAG_FREE); + assert(DO_FLAG_4_IS_TRUE == NODE_FLAG_END); + assert(DO_FLAG_7_IS_FALSE == NODE_FLAG_CELL); } -/*********************************************************************** -** -*/ static void Print_Banner(REBARGS *rargs) -/* -***********************************************************************/ +// +// Startup_Base: C +// +// The code in "base" is the lowest level of Rebol initialization written as +// Rebol code. This is where things like `+` being an infix form of ADD is +// set up, or FIRST being a specialization of PICK. It's also where the +// definition of the locals-gathering FUNCTION currently lives. +// +static void Startup_Base(REBARR *boot_base) { - if (rargs->options & RO_VERS) { - Debug_Fmt((REBYTE*)Str_Banner, REBOL_VER, REBOL_REV, REBOL_UPD, REBOL_SYS, REBOL_VAR); - OS_EXIT(0); - } + RELVAL *head = ARR_HEAD(boot_base); + + // By this point, the Lib_Context contains basic definitions for things + // like true, false, the natives, and the actions. But before deeply + // binding the code in the base block to those definitions, add all the + // top-level SET-WORD! in the base block to Lib_Context as well. + // + // Without this shallow walk looking for set words, an assignment like + // `function: func [...] [...]` would not have a slot in the Lib_Context + // for FUNCTION to bind to. So FUNCTION: would be an unbound SET-WORD!, + // and give an error on the assignment. + // + Bind_Values_Set_Midstream_Shallow(head, Lib_Context); + + // With the base block's definitions added to the mix, deep bind the code + // and execute it. As a sanity check, it's expected the base block will + // return no value when executed...hence it should end in `()`. + + Bind_Values_Deep(head, Lib_Context); + + DECLARE_LOCAL (result); + if (Do_At_Throws(result, boot_base, 0, SPECIFIED)) + panic (result); + + if (!IS_VOID(result)) + panic (result); } -/*********************************************************************** -** -*/ static void Do_Global_Block(REBSER *block, REBINT rebind) -/* -** Bind and evaluate a global block. -** Rebind: -** 0: bind set into sys or lib -** -1: bind shallow into sys (for NATIVE and ACTION) -** 1: add new words to LIB, bind/deep to LIB -** 2: add new words to SYS, bind/deep to LIB -** A single result is left on top of data stack (may be an error). -** -***********************************************************************/ -{ - Bind_Block(rebind > 1 ? Sys_Context : Lib_Context, BLK_HEAD(block), BIND_SET); - if (rebind < 0) Bind_Block(Sys_Context, BLK_HEAD(block), 0); - if (rebind > 0) Bind_Block(Lib_Context, BLK_HEAD(block), BIND_DEEP); - if (rebind > 1) Bind_Block(Sys_Context, BLK_HEAD(block), BIND_DEEP); - Do_Blk(block, 0); +// +// Startup_Sys: C +// +// The SYS context contains supporting Rebol code for implementing "system" +// features. The code has natives, actions, and the definitions from +// Startup_Base() available for its implementation. +// +// (Note: The SYS context should not be confused with "the system object", +// which is a different thing.) +// +// The sys context has a #define constant for the index of every definition +// inside of it. That means that you can access it from the C code for the +// core. Any work the core C needs to have done that would be more easily +// done by delegating it to Rebol can use a function in sys as a service. +// +static void Startup_Sys(REBARR *boot_sys) { + RELVAL *head = ARR_HEAD(boot_sys); + + // Add all new top-level SET-WORD! found in the sys boot-block to Lib, + // and then bind deeply all words to Lib and Sys. See Startup_Base() notes + // for why the top-level walk is needed first. + // + Bind_Values_Set_Midstream_Shallow(head, Sys_Context); + Bind_Values_Deep(head, Lib_Context); + Bind_Values_Deep(head, Sys_Context); + + DECLARE_LOCAL (result); + if (Do_At_Throws(result, boot_sys, 0, SPECIFIED)) + panic (result); + + if (!IS_VOID(result)) + panic (result); } -/*********************************************************************** -** -*/ static void Load_Boot(void) -/* -** Decompress and scan in the boot block structure. Can -** only be called at the correct point because it will -** create new symbols. -** -***********************************************************************/ +// +// Startup_Datatypes: C +// +// Create library words for each type, (e.g. make INTEGER! correspond to +// the integer datatype value). Returns an array of words for the added +// datatypes to use in SYSTEM/CATALOG/DATATYPES +// +// Note the type enum starts at 1 (REB_FUNCTION), given that REB_0 is used +// for special purposes and not correspond to a user-visible type. REB_MAX is +// used for void, and also not value type. Hence the total number of types is +// REB_MAX - 1. +// +static REBARR *Startup_Datatypes(REBARR *boot_types, REBARR *boot_typespecs) { - REBSER *boot; - - // Decompress binary data in Native_Specs to get the textual source - // of the function specs of the native routines. (This compressed - // array lives in b-boot.c which is generated by make-boot.r) - // Then load that into a Rebol series as `boot`. Note that the - // first four bytes of Native_Specs is a little-endian 32-bit - // length of the uncompressed spec data. - { - REBSER spec; - REBSER *text; - REBINT textlen; - - // REVIEW: This is a nasty casting away of a const. But there's - // nothing that can be done about it as long as Decompress takes - // a REBSER, as the data field is not const - spec.data = ((REBYTE*)Native_Specs) + 4; - spec.tail = NAT_SPEC_SIZE; - - textlen = Bytes_To_Long(Native_Specs); - text = Decompress(&spec, 0, -1, textlen, 0); - if (!text || (STR_LEN(text) != textlen)) Crash(RP_BOOT_DATA); - boot = Scan_Source(STR_HEAD(text), textlen); - //Dump_Block_Raw(boot, 0, 2); - Free_Series(text); - } - - Set_Root_Series(ROOT_BOOT, boot, "boot block"); // Do not let it get GC'd - - Boot_Block = (BOOT_BLK *)VAL_BLK(BLK_HEAD(boot)); - - ASSERT(VAL_TAIL(&Boot_Block->types) == REB_MAX, RP_BAD_BOOT_TYPE_BLOCK); - ASSERT(VAL_WORD_SYM(VAL_BLK(&Boot_Block->types)) == SYM_END_TYPE, RP_BAD_END_TYPE_WORD); - - // Create low-level string pointers (used by RS_ constants): - { - REBYTE *cp; - REBINT i; - - PG_Boot_Strs = (REBYTE **)Make_Mem(RS_MAX * sizeof(REBYTE *)); - *ROOT_STRINGS = Boot_Block->strings; - cp = VAL_BIN(ROOT_STRINGS); - for (i = 0; i < RS_MAX; i++) { - BOOT_STR(i,0) = cp; - while (*cp++); - } - } - - ASSERT(!CMP_BYTES("end!", Get_Sym_Name(SYM_END_TYPE)), RP_BAD_END_CANON_WORD); - ASSERT(!CMP_BYTES("true", Get_Sym_Name(SYM_TRUE)), RP_BAD_TRUE_CANON_WORD); - ASSERT(!CMP_BYTES("line", BOOT_STR(RS_SCAN,1)), RP_BAD_BOOT_STRING); -} + if (ARR_LEN(boot_types) != REB_MAX - 1) + panic (boot_types); // Every REB_XXX but REB_0 should have a WORD! + RELVAL *word = ARR_HEAD(boot_types); -/*********************************************************************** -** -*/ static void Init_Datatypes(void) -/* -** Create the datatypes. -** -***********************************************************************/ -{ - REBVAL *word = VAL_BLK(&Boot_Block->types); - REBSER *specs = VAL_SERIES(&Boot_Block->typespecs); - REBVAL *value; - REBINT n; - - for (n = 0; NOT_END(word); word++, n++) { - value = Append_Frame(Lib_Context, word, 0); - VAL_SET(value, REB_DATATYPE); - VAL_DATATYPE(value) = n; - VAL_TYPE_SPEC(value) = VAL_SERIES(BLK_SKIP(specs, n)); - } -} - - -/*********************************************************************** -** -*/ static void Init_Datatype_Checks(void) -/* -** Create datatype test functions (e.g. integer?, time?, etc) -** Must be done after typesets are initialized, so this cannot -** be merged with the above. -** -***********************************************************************/ -{ - REBVAL *word = VAL_BLK(&Boot_Block->types); - REBVAL *value; - REBSER *spec; - REBCNT sym; - REBINT n = 1; - REBYTE str[32]; - - spec = VAL_SERIES(VAL_BLK(&Boot_Block->booters)); - - for (word++; NOT_END(word); word++, n++) { - COPY_BYTES(str, Get_Word_Name(word), 32); - str[LEN_BYTES(str)-1] = '?'; - sym = Make_Word(str, 0); - //Print("sym: %s", Get_Sym_Name(sym)); - value = Append_Frame(Lib_Context, 0, sym); - VAL_INT64(BLK_LAST(spec)) = n; // special datatype id location - Make_Native(value, Copy_Block(spec, 0), (REBFUN)A_TYPE, REB_ACTION); - } - - value = Append_Frame(Lib_Context, 0, SYM_DATATYPES); - *value = Boot_Block->types; -} + if (VAL_WORD_SYM(word) != SYM_FUNCTION_X) + panic (word); // First type should be FUNCTION! + REBARR *catalog = Make_Array(REB_MAX - 1); -/*********************************************************************** -** -*/ static void Init_Constants(void) -/* -** Init constant words. -** -** WARNING: Do not create direct pointers into the Lib_Context -** because it may get expanded and the pointers will be invalid. -** -***********************************************************************/ -{ - REBVAL *value; - extern const double pi1; + REBINT n; + for (n = 1; NOT_END(word); word++, n++) { + assert(n < REB_MAX); - value = Append_Frame(Lib_Context, 0, SYM_NONE); - SET_NONE(value); + REBVAL *value = Append_Context(Lib_Context, KNOWN(word), NULL); + VAL_RESET_HEADER(value, REB_DATATYPE); + VAL_TYPE_KIND(value) = cast(enum Reb_Kind, n); + VAL_TYPE_SPEC(value) = VAL_ARRAY(ARR_AT(boot_typespecs, n - 1)); - value = Append_Frame(Lib_Context, 0, SYM_TRUE); - SET_LOGIC(value, TRUE); + // !!! The system depends on these definitions, as they are used by + // Get_Type and Type_Of. Lock it for safety...though consider an + // alternative like using the returned types catalog and locking + // that. (It would be hard to rewrite lib to safely change a type + // definition, given the code doing the rewriting would likely depend + // on lib...but it could still be technically possible, even in + // a limited sense.) + // + assert(value == Get_Type(cast(enum Reb_Kind, n))); + SET_VAL_FLAG(CTX_VAR(Lib_Context, n), VALUE_FLAG_PROTECTED); - value = Append_Frame(Lib_Context, 0, SYM_FALSE); - SET_LOGIC(value, FALSE); + Append_Value(catalog, KNOWN(word)); + } - value = Append_Frame(Lib_Context, 0, SYM_PI); - SET_DECIMAL(value, pi1); + return catalog; } -/*********************************************************************** -** -*/ void Use_Natives(REBFUN *funcs, REBCNT limit) -/* -** Setup to use NATIVE function. If limit == 0, then the -** native function table will be zero terminated (N_native). -** -***********************************************************************/ +// +// Startup_True_And_False: C +// +// !!! Rebol is firm on TRUE and FALSE being WORD!s, as opposed to the literal +// forms of logical true and false. Not only does this frequently lead to +// confusion, but there's not consensus on what a good literal form would be. +// R3-Alpha used #[true] and #[false] (but often molded them as looking like +// the words true and false anyway). $true and $false have been proposed, +// but would not be backward compatible in files read by bootstrap. +// +// Since no good literal form exists, the %sysobj.r file uses the words. They +// have to be defined before the point that it runs (along with the natives). +// +static void Startup_True_And_False(void) { - Native_Count = 0; - Native_Limit = limit; - Native_Functions = funcs; + REBVAL *true_value = Append_Context(Lib_Context, 0, Canon(SYM_TRUE)); + Init_Logic(true_value, TRUE); + assert(VAL_LOGIC(true_value) == TRUE); + assert(IS_CONDITIONAL_TRUE(true_value)); + + REBVAL *false_value = Append_Context(Lib_Context, 0, Canon(SYM_FALSE)); + Init_Logic(false_value, FALSE); + assert(VAL_LOGIC(false_value) == FALSE); + assert(IS_CONDITIONAL_FALSE(false_value)); } -/*********************************************************************** -** -*/ REBNATIVE(native) -/* -***********************************************************************/ +// +// action: native [ +// +// {Creates datatype action (for internal usage only).} +// +// return: [function!] +// :verb [set-word! word!] +// spec [block!] +// ] +// +REBNATIVE(action) +// +// The `action` native is searched for explicitly by %make-natives.r and put +// in second place for initialization (after the `native` native). +// +// It is designed to be a lookback binding that quotes its first argument, +// so when you write FOO: ACTION [...], the FOO: gets quoted to be the verb. +// The SET/LOOKBACK is done by the bootstrap, after the natives are loaded. { - if ((Native_Limit == 0 && *Native_Functions) || (Native_Count < Native_Limit)) - Make_Native(ds, VAL_SERIES(D_ARG(1)), *Native_Functions++, REB_NATIVE); - else Trap0(RE_MAX_NATIVES); - Native_Count++; - return R_RET; + INCLUDE_PARAMS_OF_ACTION; + + REBVAL *spec = ARG(spec); + + // We only want to check the return type in the debug build. In the + // release build, we want to have as few argument slots as possible... + // especially to get the optimization for 1 argument to go in the cell + // and not need to push arguments. + // + REBFLGS flags = MKF_KEYWORDS | MKF_FAKE_RETURN; + + REBFUN *fun = Make_Function( + Make_Paramlist_Managed_May_Fail(spec, flags), + &Action_Dispatcher, + NULL, // no underlying function--this is fundamental + NULL // not providing a specialization + ); + + Move_Value(FUNC_BODY(fun), ARG(verb)); + + // A lookback quoting function that quotes a SET-WORD! on its left is + // responsible for setting the value if it wants it to change since the + // SET-WORD! is not actually active. But if something *looks* like an + // assignment, it's good practice to evaluate the whole expression to + // the result the SET-WORD! was set to, so `x: y: op z` makes `x = y`. + // + Move_Value(Sink_Var_May_Fail(ARG(verb), SPECIFIED), FUNC_VALUE(fun)); + Move_Value(D_OUT, FUNC_VALUE(fun)); + + // !!! A very hacky (yet less hacky than R3-Alpha) re-dispatch of APPEND + // as WRITE/APPEND on ports requires knowing what the WRITE action is. + // Rather than track an entire table of all the actions in order to + // support that and thus endorse this hack being used other places, just + // save the write action into a global. + // + if (VAL_WORD_SYM(ARG(verb)) == SYM_WRITE) { + INIT_CELL(&PG_Write_Action); + Move_Value(&PG_Write_Action, D_OUT); + } + + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(action) -/* -***********************************************************************/ +// +// Add_Lib_Keys_R3Alpha_Cant_Make: C +// +// In order for the bootstrap to assign values to library words, they have to +// exist in the bootstrap context. The way they get into the context is by +// a scan for top-level SET-WORD!s in the %sys-xxx.r and %mezz-xxx.r files. +// +// However, R3-Alpha doesn't allow set-words like /: and <=: The words can +// be gotten with `pick [/] 1` or similar, but they cannot be SET because +// there's nothing in the context to bind them to...since no SET-WORD! was +// picked up in the scan. +// +// As a workaround, this just adds the words to the context manually. Then, +// however the words are created, it will be possible to bind them and set +// them to things. +// +// !!! Even as Ren-C becomes more permissive in letting SET-WORDs for these +// items be created, they should not be seen by %make-boot.r so long as the +// code expects to be bootstrapped with R3-Alpha. This is because as part +// of the bootstrap, the code is loaded/processed and molded out as one +// giant file. Ren-C being able to read `=>:` would not be able to help +// retroactively make old R3-Alphas read it too. +// +static void Add_Lib_Keys_R3Alpha_Cant_Make(void) { - Action_Count++; - if (Action_Count >= A_MAX_ACTION) Crash(RP_ACTION_OVERFLOW); - Make_Native(ds, VAL_SERIES(D_ARG(1)), (REBFUN)Action_Count, REB_ACTION); - return R_RET; -} + const char *names[] = { + "<", + ">", + "<=", // less than or equal to + "=>", // no current system meaning -/*********************************************************************** -** -*/ REBNATIVE(context) -/* -** The spec block has already been bound to Lib_Context, to -** allow any embedded values and functions to evaluate. -** -***********************************************************************/ -{ - REBVAL *spec = D_ARG(1); - - SET_OBJECT(ds, Make_Object(0, VAL_BLK(spec))); - Bind_Block(VAL_OBJ_FRAME(ds), VAL_BLK(spec), BIND_ONLY); // not deep - Do_Blk(VAL_SERIES(spec), 0); // result ignored - return R_RET; -} + ">=", // greater than or equal to + "=<", + "<>", // may ultimately be targeted for empty tag in Ren-C -/*********************************************************************** -** -*/ static void Init_Ops(void) -/* -***********************************************************************/ -{ - REBVAL *word; - REBVAL *func; - REBVAL *val; - - for (word = VAL_BLK(&Boot_Block->ops); NOT_END(word); word+=2) { - // Append the operator name to the lib frame: - val = Append_Frame(Lib_Context, word, 0); - // Find the related function: - func = Find_Word_Value(Lib_Context, VAL_WORD_SYM(word+1)); - if (!func) Crash(9912); - *val = *func; - VAL_SET(val, REB_OP); - VAL_SET_EXT(val, VAL_TYPE(func)); - } -} + "->", // FUNCTION-style lambda ("reaches in") + "<-", // FUNC-style lambda ("reaches out"), + "|>", // Evaluate to next single expression, but do ones afterward + "<|", // Evaluate to previous expression, but do rest (like ALSO) -/*********************************************************************** -** -*/ static void Init_Natives(void) -/* -** Create native functions. -** -***********************************************************************/ -{ - REBVAL *word; - REBVAL *val; - - Action_Count = 0; - Use_Natives((REBFUN *)Native_Funcs, MAX_NATS); - - // Construct the first native, which is the NATIVE function creator itself: - // native: native [spec [block!]] - word = VAL_BLK_SKIP(&Boot_Block->booters, 1); - ASSERT2(IS_SET_WORD(word) && VAL_WORD_SYM(word) == SYM_NATIVE, RE_NATIVE_BOOT); - //val = BLK_SKIP(Sys_Context, SYS_CTX_NATIVE); - val = Append_Frame(Lib_Context, word, 0); - Make_Native(val, VAL_SERIES(word+2), Native_Functions[0], REB_NATIVE); - - word += 3; // action: native [] - //val = BLK_SKIP(Sys_Context, SYS_CTX_ACTION); - val = Append_Frame(Lib_Context, word, 0); - Make_Native(val, VAL_SERIES(word+2), Native_Functions[1], REB_NATIVE); - Native_Count = 2; - Native_Functions += 2; - - Action_Marker = SERIES_TAIL(Lib_Context)-1; // Save index for action words. - Do_Global_Block(VAL_SERIES(&Boot_Block->actions), -1); - Do_Global_Block(VAL_SERIES(&Boot_Block->natives), -1); -} + "/", + "//", // is remainder in R3-Alpha, not ideal + NULL + }; -/*********************************************************************** -** -*/ REBVAL *Get_Action_Word(REBCNT action) -/* -** Return the word symbol for a given Action number. -** -***********************************************************************/ -{ - return FRM_WORD(Lib_Context, Action_Marker+action); + REBINT i = 0; + while (names[i]) { + REBSTR *str = Intern_UTF8_Managed(cb_cast(names[i]), strlen(names[i])); + REBVAL *val = Append_Context(Lib_Context, NULL, str); + Init_Void(val); // functions will fill in (no-op, since void already) + ++i; + } } -/*********************************************************************** -** -*/ REBVAL *Get_Action_Value(REBCNT action) -/* -** Return the value (function) for a given Action number. -** -***********************************************************************/ +// +// Init_Function_Tag: C +// +// !!! It didn't seem there was a "compare UTF8 byte array to arbitrary +// decoded REB_TAG which may or may not be REBUNI" routine, but there was +// an easy way to compare tags to each other. So pre-fabricating these was +// quick, but a better solution should be reviewed in terms of an overall +// string and UTF8 rethinking. +// +static void Init_Function_Tag(const char *name, REBVAL *slot) { - return FRM_VALUE(Lib_Context, Action_Marker+action); + Init_Tag(slot, Make_UTF8_May_Fail(name)); + Freeze_Sequence(VAL_SERIES(slot)); } -/*********************************************************************** -** -*/ void Init_UType_Proto() -/* -** Create prototype func object for UTypes. -** -***********************************************************************/ +// +// Init_Function_Tags: C +// +// FUNC and PROC search for these tags, like and . They are +// natives and run during bootstrap, so these string comparisons are +// needed. This routine does not use a table directly, because the slots +// it initializes are not constants...and older TCCs don't support local +// struct arrays of that form. +// +static void Init_Function_Tags(void) { - REBSER *frm = Make_Frame(A_MAX_ACTION-1); - REBVAL *obj; - REBINT n; - - Insert_Series(FRM_WORD_SERIES(frm), 1, (REBYTE*)FRM_WORD(Lib_Context, Action_Marker+1), A_MAX_ACTION); - - SERIES_TAIL(frm) = A_MAX_ACTION; - for (n = 1; n < A_MAX_ACTION; n++) - SET_NONE(BLK_SKIP(frm, n)); - BLK_TERM(frm); - - obj = Get_System(SYS_STANDARD, STD_UTYPE); - SET_OBJECT(obj, frm); + Init_Function_Tag("with", ROOT_WITH_TAG); + Init_Function_Tag("...", ROOT_ELLIPSIS_TAG); + Init_Function_Tag("opt", ROOT_OPT_TAG); + Init_Function_Tag("end", ROOT_END_TAG); + Init_Function_Tag("local", ROOT_LOCAL_TAG); + Init_Function_Tag("durable", ROOT_DURABLE_TAG); } -/*********************************************************************** -** -*/ static void Init_Data_Stack(REBCNT size) -/* -***********************************************************************/ -{ - DS_Series = Make_Block(size); - Set_Root_Series(TASK_STACK, DS_Series, "data stack"); // uses special GC - DS_Base = BLK_HEAD(DS_Series); - DSP = DSF = 0; - SET_NONE(DS_TOP); // avoids it being set to END (GC problem) +// +// Init_Function_Meta_Shim: C +// +// Make_Paramlist_Managed_May_Fail() needs the object archetype FUNCTION-META +// from %sysobj.r, to have the keylist to use in generating the info used +// by HELP for the natives. However, natives themselves are used in order +// to run the object construction in %sysobj.r +// +// To break this Catch-22, this code builds a field-compatible version of +// FUNCTION-META. After %sysobj.r is loaded, an assert checks to make sure +// that this manual construction actually matches the definition in the file. +// +static void Init_Function_Meta_Shim(void) { + REBSYM field_syms[6] = { + SYM_SELF, SYM_DESCRIPTION, SYM_RETURN_TYPE, SYM_RETURN_NOTE, + SYM_PARAMETER_TYPES, SYM_PARAMETER_NOTES + }; + REBCTX *function_meta = Alloc_Context(REB_OBJECT, 6); + REBCNT i = 1; + for (; i <= 6; ++i) { + // + // BLANK! is used for the fields instead of void (required for + // R3-Alpha compatibility to load the object) + // + Init_Blank( + Append_Context(function_meta, NULL, Canon(field_syms[i - 1])) + ); + } + + Init_Object(CTX_VAR(function_meta, 1), function_meta); // it's "selfish" + + Init_Object(ROOT_FUNCTION_META, function_meta); } -/*********************************************************************** -** -*/ static void Init_Root_Context(void) -/* -** Hand-build the root context where special REBOL values are -** stored. Called early, so it cannot depend on any other -** system structures or values. -** -** Note that the Root_Context's word table is unset! -** None of its values are exported. -** -***********************************************************************/ +// +// Startup_Natives: C +// +// Create native functions. In R3-Alpha this would go as far as actually +// creating a NATIVE native by hand, and then run code that would call that +// native for each function. Ren-C depends on having the native table +// initialized to run the evaluator (for instance to test functions against +// the EXIT native's FUNC signature in definitional returns). So it +// "fakes it" just by calling a C function for each item...and there is no +// actual "native native". +// +// If there *were* a REBNATIVE(native) this would be its spec: +// +// native: native [ +// spec [block!] +// /body +// {Body of user code matching native's behavior (for documentation)} +// code [block!] +// ] +// +// Returns an array of words bound to natives for SYSTEM/CATALOG/NATIVES +// +static REBARR *Startup_Natives(REBARR *boot_natives) { - REBVAL *value; - REBINT n; - REBSER *frame; - - frame = Make_Block(ROOT_MAX); // Only half the context! (No words) - KEEP_SERIES(frame, "root context"); - LOCK_SERIES(frame); - Root_Context = (ROOT_CTX*)(frame->data); - - // Get first value (the SELF for the context): - value = ROOT_SELF; - SET_FRAME(value, 0, 0); // No words or spec (at first) - - // Set all other values to NONE: - for (n = 1; n < ROOT_MAX; n++) SET_NONE(value+n); - SET_END(value+ROOT_MAX); - SERIES_TAIL(frame) = ROOT_MAX; - - // Initialize a few fields: - Set_Block(ROOT_ROOT, frame); - Init_Word(ROOT_NONAME, SYM__UNNAMED_); + // Must be called before first use of Make_Paramlist_Managed_May_Fail() + // + Init_Function_Meta_Shim(); + + RELVAL *item = ARR_HEAD(boot_natives); + + // Although the natives are not being "executed", there are typesets + // being built from the specs. So to process `foo: native [x [integer!]]` + // the INTEGER! word must be bound to its datatype. Deep walk the + // natives in order to bind these datatypes. + // + Bind_Values_Deep(item, Lib_Context); + + REBARR *catalog = Make_Array(NUM_NATIVES); + + REBCNT n = 0; + REBVAL *action_word = NULL; + + while (NOT_END(item)) { + if (n >= NUM_NATIVES) + panic (item); + + // Each entry should be one of these forms: + // + // some-name: native [spec content] + // + // some-name: native/body [spec content] [equivalent user code] + // + // If more refinements are added, this code will have to be made + // more sophisticated. + // + // Though the manual building of this table is not as nice as running + // the evaluator, the evaluator makes comparisons against native + // values. Having all natives loaded fully before ever running + // Do_Core() helps with stability and invariants. + + // Get the name the native will be started at with in Lib_Context + // + if (!IS_SET_WORD(item)) + panic (item); + + REBVAL *name = KNOWN(item); + ++item; + + // See if it's being invoked with NATIVE or NATIVE/BODY + // + REBOOL has_body; + if (IS_WORD(item)) { + if (VAL_WORD_SYM(item) != SYM_NATIVE) + panic (item); + has_body = FALSE; + } + else { + if ( + !IS_PATH(item) + || VAL_LEN_HEAD(item) != 2 + || !IS_WORD(ARR_HEAD(VAL_ARRAY(item))) + || VAL_WORD_SYM(ARR_HEAD(VAL_ARRAY(item))) != SYM_NATIVE + || !IS_WORD(ARR_AT(VAL_ARRAY(item), 1)) + || VAL_WORD_SYM(ARR_AT(VAL_ARRAY(item), 1)) != SYM_BODY + ) { + panic (item); + } + has_body = TRUE; + } + ++item; + + REBVAL *spec = KNOWN(item); + ++item; + if (!IS_BLOCK(spec)) + panic (spec); + + // With the components extracted, generate the native and add it to + // the Natives table. The associated C function is provided by a + // table built in the bootstrap scripts, `Native_C_Funcs`. + + // We only want to check the return type in the debug build. In the + // release build, we want to have as few argument slots as possible... + // especially to get the optimization for 1 argument to go in the cell + // and not need to push arguments. + // + REBFLGS flags = MKF_KEYWORDS | MKF_FAKE_RETURN; + + REBFUN *fun = Make_Function( + Make_Paramlist_Managed_May_Fail(KNOWN(spec), flags), + Native_C_Funcs[n], // "dispatcher" is unique to this "native" + NULL, // no underlying function, this is fundamental + NULL // not providing a specialization + ); + + // If a user-equivalent body was provided, we save it in the native's + // REBVAL for later lookup. + // + if (has_body) { + REBVAL *body = KNOWN(item); // !!! handle relative? + ++item; + if (!IS_BLOCK(body)) + panic (body); + *FUNC_BODY(fun) = *body; + } + + Prep_Global_Cell(&Natives[n]); + Move_Value(&Natives[n], FUNC_VALUE(fun)); + + // Append the native to the Lib_Context under the name given. + // + REBVAL *var = Append_Context(Lib_Context, name, 0); + Move_Value(var, &Natives[n]); + + // Do special case SET/LOOKBACK=TRUE so that SOME-ACTION: ACTION [...] + // allows ACTION to see the SOME-ACTION symbol, and know to use it. + // + if (VAL_WORD_SYM(name) == SYM_ACTION) { + SET_VAL_FLAG(var, VALUE_FLAG_ENFIXED); + action_word = name; + } + + REBVAL *catalog_item = Alloc_Tail_Array(catalog); + Move_Value(catalog_item, name); + VAL_SET_TYPE_BITS(catalog_item, REB_WORD); + + ++n; + } + + if (n != NUM_NATIVES) + panic ("Incorrect number of natives found during processing"); + + if (action_word == NULL) + panic ("ACTION native not found during boot block processing"); + + return catalog; } -/*********************************************************************** -** -*/ void Set_Root_Series(REBVAL *value, REBSER *ser, REBYTE *label) -/* -** Used to set block and string values in the ROOT context. -** -***********************************************************************/ +// +// Startup_Actions: C +// +// Returns an array of words bound to actions for SYSTEM/CATALOG/ACTIONS +// +static REBARR *Startup_Actions(REBARR *boot_actions) { - LABEL_SERIES(ser, label); - - if (SERIES_WIDE(ser) == sizeof(REBVAL)) - Set_Block(value, ser); // VAL_SET(value, REB_BLOCK); - else - Set_String(value, ser); //VAL_SET(value, REB_STRING); + RELVAL *head = ARR_HEAD(boot_actions); + + // Add SET-WORD!s that are top-level in the actions block to the lib + // context, so there is a variable for each action. This means that the + // assignments can execute. + // + Bind_Values_Set_Midstream_Shallow(head, Lib_Context); + + // The above code actually does bind the ACTION word to the ACTION native, + // since the action word is found in the top-level of the block. But as + // with the natives, in order to process `foo: action [x [integer!]]` the + // INTEGER! word must be bound to its datatype. Deep bind the code in + // order to bind the words for these datatypes. + // + Bind_Values_Deep(head, Lib_Context); + + DECLARE_LOCAL (result); + if (Do_At_Throws(result, boot_actions, 0, SPECIFIED)) + panic (result); + + if (!IS_VOID(result)) + panic (result); + + // Sanity check the symbol transformation + // + if (0 != strcmp("open", cs_cast(STR_HEAD(Canon(SYM_OPEN))))) + panic (Canon(SYM_OPEN)); + + REBDSP dsp_orig = DSP; + + RELVAL *item = head; + for (; NOT_END(item); ++item) + if (IS_SET_WORD(item)) { + DS_PUSH_RELVAL(item, SPECIFIED); + VAL_SET_TYPE_BITS(DS_TOP, REB_WORD); // change pushed to WORD! + } + + return Pop_Stack_Values(dsp_orig); // catalog of actions } -/*********************************************************************** -** -*/ static void Init_Task_Context(void) -/* -** See above notes (same as root context, except for tasks) -** -***********************************************************************/ +// +// Init_Root_Vars: C +// +// Hand-build the root array where special REBOL values are stored, and can +// be garbage collected. +// +// This is called early, so it cannot depend on any other system structures +// or values. +// +// !!! Efficiency note: does not need to be a heap allocated array, which +// causes double dereferencing to access its values. Could just be global. +// +static void Init_Root_Vars(void) { - REBVAL *value; - REBINT n; - REBSER *frame; - - //Print_Str("Task Context"); - - Task_Series = frame = Make_Block(TASK_MAX); - KEEP_SERIES(frame, "task context"); - LOCK_SERIES(frame); - Task_Context = (TASK_CTX*)(frame->data); - - // Get first value (the SELF for the context): - value = TASK_SELF; - SET_FRAME(value, 0, 0); // No words or spec (at first) - - // Set all other values to NONE: - for (n = 1; n < TASK_MAX; n++) SET_NONE(value+n); - SET_END(value+TASK_MAX); - SERIES_TAIL(frame) = TASK_MAX; - - // Initialize a few fields: - SET_INTEGER(TASK_BALLAST, MEM_BALLAST); - SET_INTEGER(TASK_MAX_BALLAST, MEM_BALLAST); + REBARR *root = Make_Array_Core( + ROOT_MAX, SERIES_FLAG_FIXED_SIZE | NODE_FLAG_ROOT + ); + + PG_Root_Array = root; + Root_Vars = cast(ROOT_VARS*, ARR_HEAD(root)); + + // These values are simple isolated VOID, NONE, TRUE, and FALSE values + // that can be used in lieu of initializing them. They are initialized + // as two-element series in order to ensure that their address is not + // treated as an array. + // + // They should only be accessed by macros which retrieve their values + // as `const`, to avoid the risk of accidentally changing them. (This + // rule is broken by some special system code which `m_cast`s them for + // the purpose of using them as directly recognizable pointers which + // also look like values.) + // + // It is presumed that these types will never need to have GC behavior, + // and thus can be stored safely in program globals without mention in + // the root set. Should that change, they could be explicitly added + // to the GC's root set. + + Prep_Global_Cell(&PG_Void_Cell[0]); + Prep_Global_Cell(&PG_Void_Cell[1]); + Init_Void(&PG_Void_Cell[0]); + TRASH_CELL_IF_DEBUG(&PG_Void_Cell[1]); + + Prep_Global_Cell(&PG_Blank_Value[0]); + Prep_Global_Cell(&PG_Blank_Value[1]); + Init_Blank(&PG_Blank_Value[0]); + TRASH_CELL_IF_DEBUG(&PG_Blank_Value[1]); + + Prep_Global_Cell(&PG_Bar_Value[0]); + Prep_Global_Cell(&PG_Bar_Value[1]); + Init_Bar(&PG_Bar_Value[0]); + TRASH_CELL_IF_DEBUG(&PG_Bar_Value[1]); + + Prep_Global_Cell(&PG_False_Value[0]); + Prep_Global_Cell(&PG_False_Value[1]); + Init_Logic(&PG_False_Value[0], FALSE); + TRASH_CELL_IF_DEBUG(&PG_False_Value[1]); + + Prep_Global_Cell(&PG_True_Value[0]); + Prep_Global_Cell(&PG_True_Value[1]); + Init_Logic(&PG_True_Value[0], TRUE); + TRASH_CELL_IF_DEBUG(&PG_True_Value[1]); + + Prep_Global_Cell(&PG_Va_List_Pending); + + // We can't actually put an end value in the middle of a block, so we poke + // this one into a program global. It is not legal to bit-copy an + // END (you always use SET_END), so we can make it unwritable. + // + Init_Endlike_Header(&PG_End_Node.header, 0); // mutate to read-only end +#if !defined(NDEBUG) + Set_Track_Payload_Debug(&PG_End_Node, __FILE__, __LINE__); +#endif + assert(IS_END(END)); // sanity check that it took + assert(VAL_TYPE_RAW(END) == REB_0); // this implicit END marker has this + + // The EMPTY_BLOCK provides EMPTY_ARRAY. It is locked for protection. + // + Init_Block(ROOT_EMPTY_BLOCK, Make_Array(0)); + Deep_Freeze_Array(VAL_ARRAY(ROOT_EMPTY_BLOCK)); + + REBSER *empty_series = Make_Binary(1); + *BIN_AT(empty_series, 0) = '\0'; + Init_String(ROOT_EMPTY_STRING, empty_series); + Freeze_Sequence(VAL_SERIES(ROOT_EMPTY_STRING)); + + Init_Char(ROOT_SPACE_CHAR, ' '); + Init_Char(ROOT_NEWLINE_CHAR, '\n'); + + // BUF_UTF8 not initialized, can't init function tags yet + //(at least not how Init_Function_Tags() is written) + // + SET_UNREADABLE_BLANK(ROOT_WITH_TAG); + SET_UNREADABLE_BLANK(ROOT_ELLIPSIS_TAG); + SET_UNREADABLE_BLANK(ROOT_OPT_TAG); + SET_UNREADABLE_BLANK(ROOT_END_TAG); + SET_UNREADABLE_BLANK(ROOT_LOCAL_TAG); + SET_UNREADABLE_BLANK(ROOT_DURABLE_TAG); + + // Evaluator not initialized, can't do system construction yet + // + SET_UNREADABLE_BLANK(ROOT_SYSTEM); + + // Data stack not initialized, can't do typeset construction yet + // (at least not how Startup_Typesets() is written) + // + SET_UNREADABLE_BLANK(ROOT_TYPESETS); + + // Symbols system not initialized, can't init the function meta shim yet + // + SET_UNREADABLE_BLANK(ROOT_FUNCTION_META); + + TERM_ARRAY_LEN(root, ROOT_MAX); + ASSERT_ARRAY(root); + MANAGE_ARRAY(root); } -/*********************************************************************** -** -*/ static void Init_System_Object() -/* -** The system object is defined in boot.r. -** -***********************************************************************/ -{ - REBSER *frame; - REBVAL *value; - REBCNT n; - - // Evaluate the system object and create the global SYSTEM word. - // We do not BIND_ALL here to keep the internal system words out - // of the global context. See also N_context() which creates the - // subobjects of the system object. - - // Create the system object from the sysobj block: - value = VAL_BLK(&Boot_Block->sysobj); - frame = Make_Object(0, value); - - // Bind it so CONTEXT native will work and bind its fields: - Bind_Block(Lib_Context, value, BIND_DEEP); - Bind_Block(frame, value, BIND_ONLY); // No need to go deeper - - // Evaluate the block (will eval FRAMEs within): - Do_Blk(VAL_SERIES(&Boot_Block->sysobj), 0); - - // Create a global value for it: - value = Append_Frame(Lib_Context, 0, SYM_SYSTEM); - SET_OBJECT(value, frame); - SET_OBJECT(ROOT_SYSTEM, frame); - - // Create system/datatypes block: -// value = Get_System(SYS_DATATYPES, 0); - value = Get_System(SYS_CATALOG, CAT_DATATYPES); - frame = VAL_SERIES(value); - Extend_Series(frame, REB_MAX-1); - for (n = 1; n <= REB_MAX; n++) { - Append_Val(frame, FRM_VALUES(Lib_Context) + n); - } - - // Create system/catalog/datatypes block: -// value = Get_System(SYS_CATALOG, CAT_DATATYPES); -// Set_Block(value, Copy_Blk(VAL_SERIES(&Boot_Block->types))); - - // Create system/catalog/actions block: - value = Get_System(SYS_CATALOG, CAT_ACTIONS); - Set_Block(value, Collect_Set_Words(VAL_BLK(&Boot_Block->actions))); - - // Create system/catalog/actions block: - value = Get_System(SYS_CATALOG, CAT_NATIVES); - Set_Block(value, Collect_Set_Words(VAL_BLK(&Boot_Block->natives))); - - // Create system/codecs object: - value = Get_System(SYS_CODECS, 0); - frame = Make_Frame(10); - SET_OBJECT(value, frame); - - // Set system/words to be the main context: -// value = Get_System(SYS_WORDS, 0); -// SET_OBJECT(value, Lib_Context); - - Init_UType_Proto(); +// +// Init_System_Object: C +// +// Evaluate the system object and create the global SYSTEM word. We do not +// BIND_ALL here to keep the internal system words out of the global context. +// (See also N_context() which creates the subobjects of the system object.) +// +static void Init_System_Object( + REBARR *boot_sysobj_spec, + REBARR *datatypes_catalog, + REBARR *natives_catalog, + REBARR *actions_catalog, + REBCTX *errors_catalog +) { + RELVAL *spec_head = ARR_HEAD(boot_sysobj_spec); + + // Create the system object from the sysobj block (defined in %sysobj.r) + // + REBCTX *system = Make_Selfish_Context_Detect( + REB_OBJECT, // type + spec_head, // scan for toplevel set-words + NULL // parent + ); + + Bind_Values_Deep(spec_head, Lib_Context); + + // Bind it so CONTEXT native will work (only used at topmost depth) + // + Bind_Values_Shallow(spec_head, system); + + // Evaluate the block (will eval CONTEXTs within). Expects void result. + // + DECLARE_LOCAL (result); + if (Do_At_Throws(result, boot_sysobj_spec, 0, SPECIFIED)) + panic (result); + if (!IS_VOID(result)) + panic (result); + + // Create a global value for it. (This is why we are able to say `system` + // and have it bound in lines like `sys: system/contexts/sys`) + // + Init_Object( + Append_Context(Lib_Context, NULL, Canon(SYM_SYSTEM)), + system + ); + + // We also add the system object under the root, to ensure it can't be + // garbage collected and be able to access it from the C code. (Someone + // could say `system: blank` in the Lib_Context and then it would be a + // candidate for garbage collection otherwise!) + // + Init_Object(ROOT_SYSTEM, system); + + // Init_Function_Meta_Shim() made ROOT_FUNCTION_META as a bootstrap hack + // since it needed to make function meta information for natives before + // %sysobj.r's code could run using those natives. But make sure what it + // made is actually identical to the definition in %sysobj.r. + // + assert( + 0 == CT_Context( + Get_System(SYS_STANDARD, STD_FUNCTION_META), + ROOT_FUNCTION_META, + TRUE + ) + ); + + // Create system/catalog/* for datatypes, natives, actions, errors + // + Init_Block(Get_System(SYS_CATALOG, CAT_DATATYPES), datatypes_catalog); + Init_Block(Get_System(SYS_CATALOG, CAT_NATIVES), natives_catalog); + Init_Block(Get_System(SYS_CATALOG, CAT_ACTIONS), actions_catalog); + Init_Object(Get_System(SYS_CATALOG, CAT_ERRORS), errors_catalog); + + // Create system/codecs object + // + { + REBCTX *codecs = Alloc_Context(REB_OBJECT, 10); + VAL_RESET_HEADER(CTX_VALUE(codecs), REB_OBJECT); + CTX_VALUE(codecs)->extra.binding = NULL; + CTX_VALUE(codecs)->payload.any_context.phase = NULL; + Init_Object(Get_System(SYS_CODECS, 0), codecs); + } } -/*********************************************************************** -** -*/ static void Init_Contexts_Object() -/* -***********************************************************************/ +// +// Init_Contexts_Object: C +// +// This sets up the system/contexts object. +// +// !!! One of the critical areas in R3-Alpha that was not hammered out +// completely was the question of how the binding process gets started, and +// how contexts might inherit or relate. +// +// However, the basic model for bootstrap is that the "user context" is the +// default area for new code evaluation. It starts out as a copy of an +// initial state set up in the lib context. When native routines or other +// content gets overwritten in the user context, it can be borrowed back +// from `system/contexts/lib` (typically aliased as "lib" in the user context). +// +static void Init_Contexts_Object(void) { - REBVAL *value; -// REBSER *frame; - - value = Get_System(SYS_CONTEXTS, CTX_SYS); - SET_OBJECT(value, Sys_Context); + DROP_GUARD_CONTEXT(Sys_Context); + Init_Object(Get_System(SYS_CONTEXTS, CTX_SYS), Sys_Context); - value = Get_System(SYS_CONTEXTS, CTX_LIB); - SET_OBJECT(value, Lib_Context); - - value = Get_System(SYS_CONTEXTS, CTX_USER); // default for new code evaluation - SET_OBJECT(value, Lib_Context); - - // Make the boot context - used to store values created - // during boot, but processed in REBOL code (e.g. codecs) -// value = Get_System(SYS_CONTEXTS, CTX_BOOT); -// frame = Make_Frame(4); -// SET_OBJECT(value, frame); + DROP_GUARD_CONTEXT(Lib_Context); + Init_Object(Get_System(SYS_CONTEXTS, CTX_LIB), Lib_Context); + Init_Object(Get_System(SYS_CONTEXTS, CTX_USER), Lib_Context); } -/*********************************************************************** -** -*/ REBINT Codec_Text(REBCDI *codi) -/* -***********************************************************************/ + +// +// Startup_Task: C +// +// !!! Prior to the release of R3-Alpha, there had apparently been some amount +// of effort to take single-threaded assumptions and globals, and move to a +// concept where thread-local storage was used for some previously assumed +// globals. This would be a prerequisite for concurrency but not enough: the +// memory pools would need protection from one thread to share any series with +// others, due to contention between reading and writing. +// +// Ren-C kept the separation, but if threading were to be a priority it would +// likely be approached a different way. A nearer short-term feature would be +// "isolates", where independent interpreters can be loaded in the same +// process, just not sharing objects with each other. +// +void Startup_Task(void) { - codi->error = 0; + REBARR *task = Make_Array_Core( + TASK_MAX, + SERIES_FLAG_FIXED_SIZE | NODE_FLAG_ROOT + ); + + TG_Task_Array = task; + Task_Vars = cast(TASK_VARS*, ARR_HEAD(task)); + + Trace_Level = 0; + Saved_State = 0; + + Eval_Cycles = 0; + Eval_Dose = EVAL_DOSE; + Eval_Count = Eval_Dose; + Eval_Signals = 0; + Eval_Sigmask = ALL_BITS; + Eval_Limit = 0; + + Startup_Stacks(STACK_MIN/4); + + // Initialize a few fields: + Init_Integer(TASK_BALLAST, MEM_BALLAST); + Init_Integer(TASK_MAX_BALLAST, MEM_BALLAST); + + // The thrown arg is not intended to ever be around long enough to be + // seen by the GC. + // + Prep_Global_Cell(&TG_Thrown_Arg); + SET_UNREADABLE_BLANK(&TG_Thrown_Arg); + + Startup_Raw_Print(); + Startup_Scanner(); + Startup_Mold(MIN_COMMON/4); + Startup_Collector(); + + // Symbols system not initialized, can't init the errors just yet + // + SET_UNREADABLE_BLANK(TASK_HALT_ERROR); + SET_UNREADABLE_BLANK(TASK_STACK_ERROR); + + TERM_ARRAY_LEN(task, TASK_MAX); + ASSERT_ARRAY(task); + MANAGE_ARRAY(task); +} - if (codi->action == CODI_IDENTIFY) { - return CODI_CHECK; // error code is inverted result - } - if (codi->action == CODI_DECODE) { - return CODI_TEXT; - } +// +// Startup_Core: C +// +// Initialize the interpreter core. +// +// !!! This will either succeed or "panic". Panic currently triggers an exit +// to the OS. The code is not currently written to be able to cleanly shut +// down from a partial initialization. (It should be.) +// +// The phases of initialization are tracked by PG_Boot_Phase. Some system +// functions are unavailable at certain phases. +// +// Though most of the initialization is run as C code, some portions are run +// in Rebol. For instance, ACTION is a function registered very early on in +// the boot process, which is run from within a block to register more +// functions. +// +// At the tail of the initialization, `finish-init-core` is run. This Rebol +// function lives in %sys-start.r. It should be "host agnostic" and not +// assume things about command-line switches (or even that there is a command +// line!) Converting the code that made such assumptions ongoing. +// +void Startup_Core(void) +{ - if (codi->action == CODI_ENCODE) { - return CODI_BINARY; - } +//==//////////////////////////////////////////////////////////////////////==// +// +// INITIALIZE STACK MARKER METRICS +// +//==//////////////////////////////////////////////////////////////////////==// + + // See C_STACK_OVERFLOWING for remarks on this **non-standard** technique + // of stack overflow detection. Note that each thread would have its + // own stack address limits, so this has to be updated for threading. + // + // Note that R3-Alpha tried to use a trick (which it got wrong) to + // determine whether the stack grew up or down. This doesn't work, and + // the solutions that might actually work are too wacky to justify using: + // + // http://stackoverflow.com/a/33222085/211160 + // + // So it's better to go with a build configuration #define. Note that + // stacks growing up is uncommon (e.g. Debian hppa architecture) + + REBUPT bounds; + bounds = cast(REBUPT, OS_CONFIG(1, 0)); + if (bounds == 0) + bounds = cast(REBUPT, STACK_BOUNDS); + +#ifdef OS_STACK_GROWS_UP + Stack_Limit = cast(REBUPT, &bounds) + bounds; +#else + Stack_Limit = cast(REBUPT, &bounds) - bounds; +#endif - codi->error = CODI_ERR_NA; - return CODI_ERROR; -} +//==//////////////////////////////////////////////////////////////////////==// +// +// TEST EARLY BOOT PANIC AND FAIL +// +//==//////////////////////////////////////////////////////////////////////==// + + // It should be legal to panic at any time (especially given that the + // low bar for behavior is "crash out"). fail() is more complex since it + // uses error objects which require the system to be initialized, so it + // should fall back to being a panic at early boot phases. + +#if defined(TEST_EARLY_BOOT_PANIC) + panic ("early panic test"); +#elif defined(TEST_EARLY_BOOT_FAIL) + fail (Error_No_Value_Raw(BLANK_VALUE)); +#endif +//==//////////////////////////////////////////////////////////////////////==// +// +// INITIALIZE BASIC DIAGNOSTICS +// +//==//////////////////////////////////////////////////////////////////////==// -/*********************************************************************** -** -*/ REBINT Codec_Markup(REBCDI *codi) -/* -***********************************************************************/ -{ - codi->error = 0; +#ifndef NDEBUG + PG_Always_Malloc = FALSE; +#endif - if (codi->action == CODI_IDENTIFY) { - return CODI_CHECK; // error code is inverted result - } + // Globals + PG_Boot_Phase = BOOT_START; + PG_Boot_Level = BOOT_LEVEL_FULL; + PG_Mem_Usage = 0; + PG_Mem_Limit = 0; + Reb_Opts = ALLOC(REB_OPTS); + CLEAR(Reb_Opts, sizeof(REB_OPTS)); + Saved_State = NULL; + + Startup_StdIO(); + + Assert_Basics(); + PG_Boot_Time = OS_DELTA_TIME(0, 0); + +//==//////////////////////////////////////////////////////////////////////==// +// +// INITIALIZE MEMORY AND ALLOCATORS +// +//==//////////////////////////////////////////////////////////////////////==// + + Startup_Pools(0); // Memory allocator + Startup_GC(); + +//==//////////////////////////////////////////////////////////////////////==// +// +// CREATE GLOBAL OBJECTS +// +//==//////////////////////////////////////////////////////////////////////==// + + Init_Root_Vars(); // Special REBOL values per program + Init_Char_Cases(); + Startup_CRC(); // For word hashing + Set_Random(0); + Startup_Interning(); + +//==//////////////////////////////////////////////////////////////////////==// +// +// INITIALIZE (SINGULAR) TASK +// +//==//////////////////////////////////////////////////////////////////////==// + + Startup_Task(); + + // !!! REVIEW: Init_Function_Tags() uses BUF_UTF8, not + // available untilthis point in time. + // + Init_Function_Tags(); + +//==//////////////////////////////////////////////////////////////////////==// +// +// LOAD BOOT BLOCK +// +//==//////////////////////////////////////////////////////////////////////==// + + // The %make-boot.r process takes all the various definitions and + // mezzanine code and packs it into one compressed string in + // %tmp-boot-block.c which gets embedded into the executable. This + // includes the type list, word list, error message templates, system + // object, mezzanines, etc. + + REBSER *utf8 = Decompress( + Native_Specs, NAT_COMPRESSED_SIZE, NAT_UNCOMPRESSED_SIZE, FALSE, FALSE + ); + if (utf8 == NULL || SER_LEN(utf8) != NAT_UNCOMPRESSED_SIZE) + panic ("decompressed native specs size mismatch (try `make clean`)"); + + const char *tmp_boot_utf8 = "tmp-boot.r"; + REBSTR *tmp_boot_filename = Intern_UTF8_Managed( + cb_cast(tmp_boot_utf8), strlen(tmp_boot_utf8) + ); + REBARR *boot_array = Scan_UTF8_Managed( + BIN_HEAD(utf8), NAT_UNCOMPRESSED_SIZE, tmp_boot_filename + ); + PUSH_GUARD_ARRAY(boot_array); // managed, so must be guarded + + Free_Series(utf8); // don't need decompressed text after it's scanned + + BOOT_BLK *boot = cast(BOOT_BLK*, VAL_ARRAY_HEAD(ARR_HEAD(boot_array))); + + Startup_Symbols(VAL_ARRAY(&boot->words)); + + // STR_SYMBOL(), VAL_WORD_SYM() and Canon(SYM_XXX) now available + + PG_Boot_Phase = BOOT_LOADED; + +//==//////////////////////////////////////////////////////////////////////==// +// +// CREATE BASIC VALUES +// +//==//////////////////////////////////////////////////////////////////////==// + + // Before any code can start running (even simple bootstrap code), some + // basic words need to be defined. For instance: You can't run %sysobj.r + // unless `true` and `false` have been added to the Lib_Context--they'd be + // undefined. And while analyzing the function specs during the + // definition of natives, things like the tag are needed as a basis + // for comparison to see if a usage matches that. + + // !!! Have MAKE-BOOT compute # of words + // + Lib_Context = Alloc_Context(REB_OBJECT, 600); + MANAGE_ARRAY(CTX_VARLIST(Lib_Context)); + PUSH_GUARD_CONTEXT(Lib_Context); + + Sys_Context = Alloc_Context(REB_OBJECT, 50); + MANAGE_ARRAY(CTX_VARLIST(Sys_Context)); + PUSH_GUARD_CONTEXT(Sys_Context); + + REBARR *datatypes_catalog = Startup_Datatypes( + VAL_ARRAY(&boot->types), VAL_ARRAY(&boot->typespecs) + ); + MANAGE_ARRAY(datatypes_catalog); + PUSH_GUARD_ARRAY(datatypes_catalog); + + // !!! REVIEW: Startup_Typesets() uses symbols, data stack, and + // adds words to lib--not available untilthis point in time. + // + Startup_Typesets(); + + Startup_True_And_False(); + Add_Lib_Keys_R3Alpha_Cant_Make(); + + Prep_Global_Cell(&Callback_Error); + SET_UNREADABLE_BLANK(&Callback_Error); + +//==//////////////////////////////////////////////////////////////////////==// +// +// RUN CODE BEFORE ERROR HANDLING INITIALIZED +// +//==//////////////////////////////////////////////////////////////////////==// + + // boot->natives is from the automatically gathered list of natives found + // by scanning comments in the C sources for `native: ...` declarations. + // + REBARR *natives_catalog = Startup_Natives(VAL_ARRAY(&boot->natives)); + MANAGE_ARRAY(natives_catalog); + PUSH_GUARD_ARRAY(natives_catalog); + + // boot->actions is the list in %actions.r + // + REBARR *actions_catalog = Startup_Actions(VAL_ARRAY(&boot->actions)); + MANAGE_ARRAY(actions_catalog); + PUSH_GUARD_ARRAY(actions_catalog); + + // boot->errors is the error definition list from %errors.r + // + REBCTX *errors_catalog = Startup_Errors(VAL_ARRAY(&boot->errors)); + PUSH_GUARD_CONTEXT(errors_catalog); + + Init_System_Object( + VAL_ARRAY(&boot->sysobj), + datatypes_catalog, + natives_catalog, + actions_catalog, + errors_catalog + ); + + DROP_GUARD_CONTEXT(errors_catalog); + DROP_GUARD_ARRAY(actions_catalog); + DROP_GUARD_ARRAY(natives_catalog); + DROP_GUARD_ARRAY(datatypes_catalog); + + Init_Contexts_Object(); + + PG_Boot_Phase = BOOT_ERRORS; + +#if defined(TEST_MID_BOOT_PANIC) + // + // At this point panics should be able to do a reasonable job of giving + // details on Rebol types. + // + panic (EMPTY_ARRAY); +#elif defined(TEST_MID_BOOT_FAIL) + // + // With no PUSH_TRAP yet, fail should give a localized assert in a debug + // build, and panic the release build. + // + fail (Error_No_Value_Raw(BLANK_VALUE)); +#endif - if (codi->action == CODI_DECODE) { - codi->other = (void*)Load_Markup(codi->data, codi->len); - return CODI_BLOCK; - } + // Special pre-made errors: + Init_Error(TASK_STACK_ERROR, Error_Stack_Overflow_Raw()); + Init_Error(TASK_HALT_ERROR, Error_Halt_Raw()); + + +//==//////////////////////////////////////////////////////////////////////==// +// +// RUN MEZZANINE CODE NOW THAT ERROR HANDLING IS INITIALIZED +// +//==//////////////////////////////////////////////////////////////////////==// + + PG_Boot_Phase = BOOT_MEZZ; + + assert(DSP == 0 && FS_TOP == NULL); + + REBCTX *error = Startup_Mezzanine(&boot->base, &boot->sys, &boot->mezz); + if (error != NULL) { + // + // There is theoretically some level of error recovery that could + // be done here. e.g. the evaluator works, it just doesn't have + // many functions you would expect. How bad it is depends on + // whether base and sys ran, so perhaps only errors running "mezz" + // should be returned. + // + // For now, assume any failure to declare the functions in those + // sections is a critical one. It may be desirable to tell the + // caller that the user halted (quitting may not be appropriate if + // the app is more than just the interpreter) + // + // !!! If halt cannot be handled cleanly, it should be set up so + // that the user isn't even *able* to request a halt at this boot + // phase. + + #ifdef RETURN_ERRORS_FROM_INIT_CORE + REBCNT err_num = ERR_NUM(error); + Shutdown_Core(); // In good enough state to shutdown cleanly by now + return err_num; + #endif + + assert(ERR_NUM(error) != RE_HALT); + + panic (error); + } + + assert(DSP == 0 && FS_TOP == NULL); + + DROP_GUARD_ARRAY(boot_array); + + PG_Boot_Phase = BOOT_DONE; + +#if !defined(NDEBUG) + // + // This memory check from R3-Alpha is somewhat superfluous, but include a + // call to it during Init in the debug build, because otherwise no one + // will think to keep it up to date and working. + // + Check_Memory_Debug(); + + // We can only do a check of the pointer detection service after the + // system is somewhat initialized. + // + Assert_Pointer_Detection_Working(); +#endif - codi->error = CODI_ERR_NA; - return CODI_ERROR; + Recycle(); // necessary? } -/*********************************************************************** -** -*/ void Register_Codec(REBYTE *name, codo dispatcher) -/* -** Internal function for adding a codec. -** -***********************************************************************/ -{ - REBVAL *value = Get_System(SYS_CODECS, 0); - REBCNT sym = Make_Word(name, 0); - - value = Append_Frame(VAL_OBJ_FRAME(value), 0, sym); - SET_HANDLE(value, dispatcher); +// +// Startup_Mezzanine: C +// +// For boring technical reasons, the `boot` variable might be "clobbered" +// by a longjmp in Startup_Core(). The easiest way to work around this is +// by taking the code that setjmp/longjmps (e.g. PUSH_TRAP, fail()) and +// putting it into a separate function. +// +// http://stackoverflow.com/a/2105840/211160 +// +// Returns error from finalizing or NULL. +// +REBCTX *Startup_Mezzanine( + REBVAL *base_block, + REBVAL *sys_block, + REBVAL *mezz_block +) { + REBCTX *error; + struct Reb_State state; + + // With error trapping enabled, set up to catch them if they happen. + PUSH_UNHALTABLE_TRAP(&error, &state); + +// The first time through the following code 'error' will be NULL, but... +// `fail` can longjmp here, so 'error' won't be NULL *if* that happens! + + if (error) + return error; + + Startup_Base(VAL_ARRAY(base_block)); + + Startup_Sys(VAL_ARRAY(sys_block)); + + // The FINISH-INIT-CORE function should likely do very little. But right + // now it is where the user context is created from the lib context (a + // copy with some omissions), and where the mezzanine definitions are + // bound to the lib context and DO'd. + // + DECLARE_LOCAL (result); + if (Apply_Only_Throws( + result, + TRUE, // generate error if all arguments aren't consumed + Sys_Func(SYS_CTX_FINISH_INIT_CORE), // %sys-start.r function to call + mezz_block, // boot-mezz argument + END + )) { + return Error_No_Catch_For_Throw(result); + } + + if (!IS_VOID(result)) { + // + // !!! `finish-init-core` Rebol code should return void, but it may be + // that more graceful error delivery than a panic should be given if + // it does not. It may be that fairly legitimate circumstances which + // the user could fix would cause a more ordinary message delivery. + // For the moment, though, we panic on any non-void return result. + // + panic (result); + } + + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + + return NULL; } -/*********************************************************************** -** -*/ static void Init_Codecs() -/* -***********************************************************************/ +// +// Shutdown_Core: C +// +// The goal of Shutdown_Core() is to release all memory and resources that the +// interpreter has accrued since Startup_Core(). This is a good "sanity check" +// that there aren't unaccounted-for leaks (or semantic errors which such +// leaks may indicate). +// +// Also, being able to clean up is important for a library...which might be +// initialized and shut down multiple times in the same program run. But +// clients wishing a speedy exit may force an exit to the OS instead of doing +// a clean shut down. (Note: There still might be some system resources +// that need to be waited on, such as asynchronous writes.) +// +// While some leaks are detected by the debug build during shutdown, even more +// can be found with a tool like Valgrind or Address Sanitizer. +// +void Shutdown_Core(void) { - Register_Codec((REBYTE*)"text", Codec_Text); - Register_Codec((REBYTE*)"markup", Codec_Markup); - Init_BMP_Codec(); - Init_GIF_Codec(); - Init_PNG_Codec(); - Init_JPEG_Codec(); -} +#if !defined(NDEBUG) + // + // This memory check from R3-Alpha is somewhat superfluous, but include a + // call to it during Shutdown in the debug build, because otherwise no one + // will think to keep it up to date and working. + // + Check_Memory_Debug(); +#endif + assert(!Saved_State); -static void Set_Option_String(REBCHR *str, REBCNT field) -{ - REBVAL *val; - if (str) { - val = Get_System(SYS_OPTIONS, field); - Set_String(val, Copy_OS_Str(str, LEN_STR(str))); - } -} + Shutdown_Stacks(); -static REBCNT Set_Option_Word(REBCHR *str, REBCNT field) -{ - REBVAL *val; - REBYTE *bp; - REBYTE buf[40]; // option words always short ASCII strings - REBCNT n = 0; - - if (str) { - n = LEN_STR(str); // WC correct - if (n > 38) return 0; - bp = &buf[0]; - while ((*bp++ = (REBYTE)*str++)); // clips unicode - n = Make_Word(buf, n); - val = Get_System(SYS_OPTIONS, field); - Init_Word(val, n); - } - return n; -} + // Run Recycle, but the TRUE flag indicates we want every series + // that is managed to be freed. (Only unmanaged should be left.) + // We remove the only two root contexts that the Startup_Core process added + // -however- there may be other roots. But by this point, the roots + // created by Alloc_Pairing() with an owning context should be freed. + // + CLEAR_SER_FLAG(PG_Root_Array, NODE_FLAG_ROOT); + CLEAR_SER_FLAG(TG_Task_Array, NODE_FLAG_ROOT); -/*********************************************************************** -** -*/ static void Init_Main_Args(REBARGS *rargs) -/* -** The system object is defined in boot.r. -** -***********************************************************************/ -{ - REBVAL *val; - REBSER *ser; - REBCHR *data; - REBCNT n; - - - ser = Make_Block(3); - n = 2; // skip first flag (ROF_EXT) - val = Get_System(SYS_CATALOG, CAT_BOOT_FLAGS); - for (val = VAL_BLK(val); NOT_END(val); val++) { - VAL_CLR_LINE(val); - if (rargs->options & n) Append_Val(ser, val); - n <<= 1; - } - val = Append_Value(ser); - SET_TRUE(val); - val = Get_System(SYS_OPTIONS, OPTIONS_FLAGS); - Set_Block(val, ser); - - // For compatibility: - if (rargs->options & RO_QUIET) { - val = Get_System(SYS_OPTIONS, OPTIONS_QUIET); - SET_TRUE(val); - } - - // Print("script: %s", rargs->script); - if (rargs->script) { - ser = To_REBOL_Path(rargs->script, 0, OS_WIDE, 0); - val = Get_System(SYS_OPTIONS, OPTIONS_SCRIPT); - Set_Series(REB_FILE, val, ser); - } - - if (rargs->exe_path) { - ser = To_REBOL_Path(rargs->exe_path, 0, OS_WIDE, 0); - val = Get_System(SYS_OPTIONS, OPTIONS_BOOT); - Set_Series(REB_FILE, val, ser); - } - - // Print("home: %s", rargs->home_dir); - if (rargs->home_dir) { - ser = To_REBOL_Path(rargs->home_dir, 0, OS_WIDE, TRUE); - val = Get_System(SYS_OPTIONS, OPTIONS_HOME); - Set_Series(REB_FILE, val, ser); - } - - n = Set_Option_Word(rargs->boot, OPTIONS_BOOT_LEVEL); - if (n >= SYM_BASE && n <= SYM_MODS) - PG_Boot_Level = n - SYM_BASE; // 0 - 3 - - Set_Option_String(rargs->args, OPTIONS_ARGS); - Set_Option_String(rargs->do_arg, OPTIONS_DO_ARG); - Set_Option_String(rargs->debug, OPTIONS_DEBUG); - Set_Option_String(rargs->version, OPTIONS_VERSION); - Set_Option_String(rargs->import, OPTIONS_IMPORT); - - Set_Option_Word(rargs->secure, OPTIONS_SECURE); - - if (NZ(data = OS_GET_LOCALE(0))) { - val = Get_System(SYS_LOCALE, LOCALE_LANGUAGE); - Set_String(val, Copy_OS_Str(data, LEN_STR(data))); - } - - if (NZ(data = OS_GET_LOCALE(1))) { - val = Get_System(SYS_LOCALE, LOCALE_LANGUAGE_P); - Set_String(val, Copy_OS_Str(data, LEN_STR(data))); - } - - if (NZ(data = OS_GET_LOCALE(2))) { - val = Get_System(SYS_LOCALE, LOCALE_LOCALE); - Set_String(val, Copy_OS_Str(data, LEN_STR(data))); - } - - if (NZ(data = OS_GET_LOCALE(3))) { - val = Get_System(SYS_LOCALE, LOCALE_LOCALE_P); - Set_String(val, Copy_OS_Str(data, LEN_STR(data))); - } -} + Recycle_Core(TRUE, NULL); + Shutdown_Event_Scheme(); + Shutdown_CRC(); + Shutdown_Mold(); + Shutdown_Scanner(); + Shutdown_Char_Cases(); -/*********************************************************************** -** -*/ void Init_Task(void) -/* -***********************************************************************/ -{ - // Thread locals: - Trace_Level = 0; - Saved_State = 0; - - Eval_Cycles = 0; - Eval_Dose = EVAL_DOSE; - Eval_Signals = 0; - Eval_Sigmask = ALL_BITS; - - // errors? problem with PG_Boot_Phase shared? - - Init_Memory(-4); - Init_Task_Context(); // Special REBOL values per task - - Init_Raw_Print(); - Init_Words(TRUE); - Init_Data_Stack(STACK_MIN/4); - Init_Scanner(); - Init_Mold(MIN_COMMON/4); - Init_Frame(); - //Inspect_Series(0); -} + Shutdown_Symbols(); + Shutdown_Interning(); + Shutdown_GC(); -/*********************************************************************** -** -*/ void Init_Year(void) -/* -***********************************************************************/ -{ - REBOL_DAT dat; + // !!! Need to review the relationship between Open_StdIO (which the host + // does) and Startup_StdIO...they both open, and both close. - OS_GET_TIME(&dat); - Current_Year = dat.year; -} + Shutdown_StdIO(); + FREE(REB_OPTS, Reb_Opts); -/*********************************************************************** -** -*/ void Init_Core(REBARGS *rargs) -/* -** GC is disabled during all init code, so these functions -** need not protect themselves. -** -***********************************************************************/ -{ - REBSER *ser; - DOUT("Main init"); - - // Globals - PG_Boot_Phase = BOOT_START; - PG_Boot_Level = BOOT_LEVEL_FULL; - PG_Mem_Usage = 0; - PG_Mem_Limit = 0; - PG_Reb_Stats = Make_Mem(sizeof(*PG_Reb_Stats)); - Reb_Opts = Make_Mem(sizeof(*Reb_Opts)); - - // Thread locals: - Trace_Level = 0; - Saved_State = 0; - Eval_Dose = EVAL_DOSE; - Eval_Limit = 0; - Eval_Signals = 0; - Eval_Sigmask = ALL_BITS; /// dups Init_Task - - Init_StdIO(); - - Assert_Basics(); - PG_Boot_Time = OS_DELTA_TIME(0, 0); - - DOUT("Level 0"); - Init_Memory(0); // Memory allocator - Init_Root_Context(); // Special REBOL values per program - Init_Task_Context(); // Special REBOL values per task - - Init_Raw_Print(); // Low level output (Print) - - Print_Banner(rargs); - - DOUT("Level 1"); - Init_Char_Cases(); - Init_CRC(); // For word hashing - Set_Random(0); - Init_Words(FALSE); // Symbol table - Init_Data_Stack(STACK_MIN*4); - Init_Scanner(); - Init_Mold(MIN_COMMON); // Output buffer - Init_Frame(); // Frames - - Lib_Context = Make_Frame(600); // !! Have MAKE-BOOT compute # of words - Sys_Context = Make_Frame(50); - - DOUT("Level 2"); - Load_Boot(); // Protected strings now available - PG_Boot_Phase = BOOT_LOADED; - //Debug_Str(BOOT_STR(RS_INFO,0)); // Booting... - - // Get the words of the ROOT context (to avoid it being an exception case): - PG_Root_Words = Collect_Frame(BIND_ALL, 0, VAL_BLK(&Boot_Block->root)); - VAL_FRM_WORDS(ROOT_SELF) = PG_Root_Words; - - // Create main values: - DOUT("Level 3"); - Init_Datatypes(); // Create REBOL datatypes - Init_Typesets(); // Create standard typesets - Init_Datatype_Checks(); // The TYPE? checks - Init_Constants(); // Constant values - - // Run actual code: - DOUT("Level 4"); - Init_Natives(); // Built-in native functions - Init_Ops(); // Built-in operators - Init_System_Object(); - Init_Contexts_Object(); - Init_Main_Args(rargs); - Init_Ports(); - Init_Codecs(); - Init_Errors(&Boot_Block->errors); // Needs system/standard/error object - PG_Boot_Phase = BOOT_ERRORS; - - Init_Year(); - - // Special pre-made error: - ser = Make_Error(RE_STACK_OVERFLOW, 0, 0, 0); - SET_ERROR(TASK_STACK_ERROR, RE_STACK_OVERFLOW, ser); - - // Initialize mezzanine functions: - DOUT("Level 5"); - if (PG_Boot_Level >= BOOT_LEVEL_SYS) { - Do_Global_Block(VAL_SERIES(&Boot_Block->base), 1); - Do_Global_Block(VAL_SERIES(&Boot_Block->sys), 2); - } - - *FRM_VALUE(Sys_Context, SYS_CTX_BOOT_MEZZ) = Boot_Block->mezz; - *FRM_VALUE(Sys_Context, SYS_CTX_BOOT_PROT) = Boot_Block->protocols; - - // No longer needs protecting: - SET_NONE(ROOT_BOOT); - Boot_Block = NULL; - PG_Boot_Phase = BOOT_MEZZ; - DS_RESET; - - DOUT("Boot done"); + // Shutting down the memory manager must be done after all the Free_Mem + // calls have been made to balance their Alloc_Mem calls. + // + Shutdown_Pools(); } diff --git a/src/core/b-main.c b/src/core/b-main.c deleted file mode 100644 index 5b2ee98bb2..0000000000 --- a/src/core/b-main.c +++ /dev/null @@ -1,80 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: b-main.c -** Summary: skip -** Section: bootstrap -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ -#include "sys-core.h" - -static REBARGS Main_Args; // Not multi-threaded - -/*********************************************************************** -** -*/ char *Prompt_User() -/* -***********************************************************************/ -{ - char *text; - - Prin("DSP: %d Mem: %d >> ", DSP, PG_Mem_Usage); - text = Input_Str(); - if (*text == '\n') exit(0); - return text; -} - - -/*********************************************************************** -** -*/ int main(int argc, char **argv) -/* -***********************************************************************/ -{ - char *cmd; - - // Parse command line arguments. Done early. May affect REBOL boot. - Parse_Args(argc, argv, &Main_Args); - - Print_Str("REBOL 3.0\n"); - - REBOL_Init(&Main_Args); - - // Evaluate user input: - while (TRUE) { - cmd = Prompt_User(); - REBOL_Do_String(cmd); - if (!IS_UNSET(DS_TOP)) { - //if (DSP > 0) { - if (!IS_ERROR(DS_TOP)) { - Prin("== "); - Print_Value(DS_TOP, 0, TRUE); - } else - Print_Value(DS_TOP, 0, FALSE); - //} - } - //DS_DROP; // result - } - - return 0; -} diff --git a/src/core/c-bind.c b/src/core/c-bind.c new file mode 100644 index 0000000000..460ef7abd3 --- /dev/null +++ b/src/core/c-bind.c @@ -0,0 +1,359 @@ +// +// File: %c-bind.c +// Summary: "Word Binding Routines" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Binding relates a word to a context. Every word can be either bound, +// specifically bound to a particular context, or bound relatively to a +// function (where additional information is needed in order to find the +// specific instance of the variable for that word as a key). +// + +#include "sys-core.h" + + +// +// Bind_Values_Inner_Loop: C +// +// Bind_Values_Core() sets up the binding table and then calls +// this recursive routine to do the actual binding. +// +static void Bind_Values_Inner_Loop( + struct Reb_Binder *binder, + RELVAL head[], + REBCTX *context, + REBU64 bind_types, // !!! REVIEW: force word types low enough for 32-bit? + REBU64 add_midstream_types, + REBFLGS flags +) { + RELVAL *value = head; + for (; NOT_END(value); value++) { + REBU64 type_bit = FLAGIT_KIND(VAL_TYPE(value)); + + if (type_bit & bind_types) { + REBSTR *canon = VAL_WORD_CANON(value); + REBCNT n = Try_Get_Binder_Index(binder, canon); + if (n != 0) { + assert(n <= CTX_LEN(context)); + + // We're overwriting any previous binding, which may have + // been relative. + // + CLEAR_VAL_FLAG(value, VALUE_FLAG_RELATIVE); + + SET_VAL_FLAG(value, WORD_FLAG_BOUND); + INIT_WORD_CONTEXT(value, context); + INIT_WORD_INDEX(value, n); + } + else if (type_bit & add_midstream_types) { + // + // Word is not in context, so add it if option is specified + // + Expand_Context(context, 1); + Append_Context(context, value, 0); + Add_Binder_Index(binder, canon, VAL_WORD_INDEX(value)); + } + } + else if (ANY_ARRAY(value) && (flags & BIND_DEEP)) { + Bind_Values_Inner_Loop( + binder, + VAL_ARRAY_AT(value), + context, + bind_types, + add_midstream_types, + flags + ); + } + else if ( + IS_FUNCTION(value) + && IS_FUNCTION_INTERPRETED(value) + && (flags & BIND_FUNC) + ) { + // !!! Likely-to-be deprecated functionality--rebinding inside the + // content of an already formed function. :-/ + // + Bind_Values_Inner_Loop( + binder, + VAL_FUNC_BODY(value), + context, + bind_types, + add_midstream_types, + flags + ); + } + } +} + + +// +// Bind_Values_Core: C +// +// Bind words in an array of values terminated with END +// to a specified context. See warnings on the functions like +// Bind_Values_Deep() about not passing just a singular REBVAL. +// +// NOTE: If types are added, then they will be added in "midstream". Only +// bindings that come after the added value is seen will be bound. +// +void Bind_Values_Core( + RELVAL head[], + REBCTX *context, + REBU64 bind_types, + REBU64 add_midstream_types, + REBFLGS flags // see %sys-core.h for BIND_DEEP, etc. +) { + struct Reb_Binder binder; + INIT_BINDER(&binder); + + // Via the global hash table, each spelling of the word can find the + // canon form of the word. Associate that with an index number to signal + // a binding should be created to this context (at that index.) + + REBCNT index = 1; + REBVAL *key = CTX_KEYS_HEAD(context); + for (; index <= CTX_LEN(context); key++, index++) + if (NOT_VAL_FLAG(key, TYPESET_FLAG_UNBINDABLE)) + Add_Binder_Index(&binder, VAL_KEY_CANON(key), index); + + Bind_Values_Inner_Loop( + &binder, head, context, bind_types, add_midstream_types, flags + ); + + // Reset all the binder indices to zero, balancing out what was added. + + key = CTX_KEYS_HEAD(context); + for (; NOT_END(key); key++) + Remove_Binder_Index(&binder, VAL_KEY_CANON(key)); + + SHUTDOWN_BINDER(&binder); +} + + +// +// Unbind_Values_Core: C +// +// Unbind words in a block, optionally unbinding those which are +// bound to a particular target (if target is NULL, then all +// words will be unbound regardless of their VAL_WORD_CONTEXT). +// +void Unbind_Values_Core(RELVAL head[], REBCTX *context, REBOOL deep) +{ + RELVAL *value = head; + for (; NOT_END(value); value++) { + if ( + ANY_WORD(value) + && ( + !context + || ( + IS_WORD_BOUND(value) + && !IS_RELATIVE(value) + && VAL_WORD_CONTEXT(KNOWN(value)) == context + ) + ) + ) { + Unbind_Any_Word(value); + } + else if (ANY_ARRAY(value) && deep) + Unbind_Values_Core(VAL_ARRAY_AT(value), context, TRUE); + } +} + + +// +// Try_Bind_Word: C +// +// Binds a word to a context. If word is not part of the context. +// +REBCNT Try_Bind_Word(REBCTX *context, REBVAL *word) +{ + REBCNT n = Find_Canon_In_Context(context, VAL_WORD_CANON(word), FALSE); + if (n != 0) { + // + // Previously may have been bound relative, remove flag. + // + CLEAR_VAL_FLAG(word, VALUE_FLAG_RELATIVE); + + SET_VAL_FLAG(word, WORD_FLAG_BOUND); + INIT_WORD_CONTEXT(word, context); + INIT_WORD_INDEX(word, n); + } + return n; +} + + +// +// Bind_Relative_Inner_Loop: C +// +// Recursive function for relative function word binding. Returns TRUE if +// any relative bindings were made. +// +static void Bind_Relative_Inner_Loop( + struct Reb_Binder *binder, + RELVAL head[], + REBARR *paramlist, + REBU64 bind_types +) { + RELVAL *value = head; + + for (; NOT_END(value); value++) { + REBU64 type_bit = FLAGIT_KIND(VAL_TYPE(value)); + + // The two-pass copy-and-then-bind should have gotten rid of all the + // relative values to other functions during the copy. + // + // !!! Long term, in a single pass copy, this would have to deal + // with relative values and run them through the specification + // process if they were not just getting overwritten. + // + assert(!IS_RELATIVE(value)); + + if (type_bit & bind_types) { + REBINT n = Try_Get_Binder_Index(binder, VAL_WORD_CANON(value)); + if (n != 0) { + // + // Word's canon symbol is in frame. Relatively bind it. + // (clear out existing binding flags first). + // + Unbind_Any_Word(value); + SET_VAL_FLAGS(value, WORD_FLAG_BOUND | VALUE_FLAG_RELATIVE); + INIT_WORD_FUNC(value, AS_FUNC(paramlist)); // incomplete func + INIT_WORD_INDEX(value, n); + } + } + else if (ANY_ARRAY(value)) { + Bind_Relative_Inner_Loop( + binder, VAL_ARRAY_AT(value), paramlist, bind_types + ); + + // Set the bits in the ANY-ARRAY! REBVAL to indicate that it is + // relative to the function. + // + // !!! Technically speaking it is not necessary for an array to + // be marked relative if it doesn't contain any relative words + // under it. However, for uniformity in the near term, it's + // easiest to debug if there is a clear mark on arrays that are + // part of a deep copy of a function body either way. + // + SET_VAL_FLAG(value, VALUE_FLAG_RELATIVE); + INIT_RELATIVE(value, AS_FUNC(paramlist)); // incomplete func + } + } +} + + +// +// Copy_And_Bind_Relative_Deep_Managed: C +// +// This routine is called by Make_Function in order to take the raw material +// given as a function body, and de-relativize any IS_RELATIVE(value)s that +// happen to be in it already (as any Copy does). But it also needs to make +// new relative references to ANY-WORD! that are referencing function +// parameters, as well as to relativize the copies of ANY-ARRAY! that contain +// these relative words...so that they refer to the archetypal function +// to which they should be relative. +// +REBARR *Copy_And_Bind_Relative_Deep_Managed( + const REBVAL *body, + REBARR *paramlist, // body of function is not actually ready yet + REBU64 bind_types +) { + // !!! Currently this is done in two phases, because the historical code + // would use the generic copying code and then do a bind phase afterward. + // Both phases are folded into this routine to make it easier to make + // a one-pass version when time permits. + // + REBARR *copy = COPY_ANY_ARRAY_AT_DEEP_MANAGED(body); + + struct Reb_Binder binder; + INIT_BINDER(&binder); + + // Setup binding table from the argument word list + // + REBCNT index = 1; + RELVAL *param = ARR_AT(paramlist, 1); // [0] is FUNCTION! value + for (; NOT_END(param); param++, index++) + Add_Binder_Index(&binder, VAL_KEY_CANON(param), index); + + Bind_Relative_Inner_Loop(&binder, ARR_HEAD(copy), paramlist, bind_types); + + // Reset binding table + // + param = ARR_AT(paramlist, 1); // [0] is FUNCTION! value + for (; NOT_END(param); param++) + Remove_Binder_Index(&binder, VAL_KEY_CANON(param)); + + SHUTDOWN_BINDER(&binder); + return copy; +} + + +// +// Rebind_Values_Deep: C +// +// Rebind all words that reference src target to dst target. +// Rebind is always deep. +// +void Rebind_Values_Deep( + REBCTX *src, + REBCTX *dst, + RELVAL head[], + struct Reb_Binder *opt_binder +) { + RELVAL *value = head; + for (; NOT_END(value); value++) { + if (ANY_ARRAY(value)) { + Rebind_Values_Deep(src, dst, VAL_ARRAY_AT(value), opt_binder); + } + else if ( + ANY_WORD(value) + && GET_VAL_FLAG(value, WORD_FLAG_BOUND) + && NOT_VAL_FLAG(value, VALUE_FLAG_RELATIVE) + && VAL_WORD_CONTEXT(KNOWN(value)) == src + ) { + INIT_WORD_CONTEXT(value, dst); + + if (opt_binder != NULL) { + INIT_WORD_INDEX( + value, + Try_Get_Binder_Index(opt_binder, VAL_WORD_CANON(value)) + ); + } + } + else if (IS_FUNCTION(value) && IS_FUNCTION_INTERPRETED(value)) { + // + // !!! Extremely questionable feature--walking into function + // bodies and changing them. This R3-Alpha concept was largely + // broken (didn't work for closures) and created a lot of extra + // garbage (inheriting an object's methods meant making deep + // copies of all that object's method bodies...each time). + // Ren-C has a different idea in the works. + // + Rebind_Values_Deep( + src, dst, VAL_FUNC_BODY(value), opt_binder + ); + } + } +} diff --git a/src/core/c-context.c b/src/core/c-context.c new file mode 100755 index 0000000000..b88d5c9a54 --- /dev/null +++ b/src/core/c-context.c @@ -0,0 +1,1451 @@ +// +// File: %c-context.c +// Summary: "Management routines for ANY-CONTEXT! key/value storage" +// Section: core +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Contexts are two arrays of equal length, which are linked together to +// describe "object-like" things (lists of TYPESET! keys and corresponding +// variable values). They are used by OBJECT!, PORT!, FRAME!, etc. +// +// The REBCTX* is how contexts are passed around as a single pointer. This +// pointer is actually just an array REBSER which represents the variable +// values. The keylist can be reached through the ->link field of that +// REBSER, and the [0] value of the variable array is a "canon instance" of +// whatever kind of REBVAL the context represents. +// +// +// VARLIST ARRAY: ---Link-> KEYLIST ARRAY: +// +----------------------------+ +----------------------------+ +// + "ROOTVAR" | | "ROOTKEY" | +// | Canon ANY-CONTEXT! Value | | Canon FUNCTION!, or blank | +// +----------------------------+ +----------------------------+ +// | Value 1 | | Typeset w/symbol 1 | +// +----------------------------+ +----------------------------+ +// | Value 2 | | Typeset w/symbol 2 | +// +----------------------------+ +----------------------------+ +// | Value ... | | Typeset w/symbol 3 ... | +// +----------------------------+ +----------------------------+ +// +// While R3-Alpha used a special kind of WORD! known as an "unword" for the +// keys, Ren-C uses a special kind of TYPESET! which can also hold a symbol. +// The reason is that keylists are common to function paramlists and objects, +// and typesets are more complex than words (and destined to become even +// moreso with user defined types). So it's better to take the small detail +// of storing a symbol in a typeset rather than try and enhance words to have +// typeset features. +// +// Keylists can be shared between objects, and if the context represents a +// call FRAME! then the keylist is actually the paramlist of that function +// being called. If the keylist is not for a function, then the [0] cell +// (a.k.a. "ROOTKEY") is currently not used--and set to a BLANK!. +// + +#include "sys-core.h" + + +// +// Alloc_Context: C +// +// Create context of a given size, allocating space for both words and values. +// +// This context will not have its ANY-OBJECT! REBVAL in the [0] position fully +// configured, hence this is an "Alloc" instead of a "Make" (because there +// is still work to be done before it will pass ASSERT_CONTEXT). +// +REBCTX *Alloc_Context(enum Reb_Kind kind, REBCNT capacity) +{ + REBARR *varlist = Make_Array_Core( + capacity + 1, // size + room for ROOTVAR + ARRAY_FLAG_VARLIST + ); + + // varlist[0] is a value instance of the OBJECT!/MODULE!/PORT!/ERROR! we + // are building which contains this context. + + REBVAL *rootvar = Alloc_Tail_Array(varlist); + VAL_RESET_HEADER(rootvar, kind); + rootvar->payload.any_context.varlist = varlist; + rootvar->payload.any_context.phase = NULL; + rootvar->extra.binding = NULL; + + // keylist[0] is the "rootkey" which we currently initialize to BLANK + + REBARR *keylist = Make_Array_Core( + capacity + 1, // size + room for ROOTKEY + 0 // No keylist flag, but we don't want line numbers + ); + Init_Blank(Alloc_Tail_Array(keylist)); + SER(keylist)->link.meta = NULL; // GC sees meta object, must init + + // varlists link keylists via REBSER.misc field, sharable hence managed + + INIT_CTX_KEYLIST_UNIQUE(CTX(varlist), keylist); + MANAGE_ARRAY(keylist); + + return CTX(varlist); // varlist pointer is context handle +} + + +// +// Expand_Context_Keylist_Core: C +// +// Returns whether or not the expansion invalidated existing keys. +// +REBOOL Expand_Context_Keylist_Core(REBCTX *context, REBCNT delta) +{ + REBARR *keylist = CTX_KEYLIST(context); + + // can't expand or unshare a FRAME!'s list + // + assert(NOT_SER_FLAG(keylist, ARRAY_FLAG_PARAMLIST)); + + if (GET_SER_INFO(keylist, SERIES_INFO_SHARED_KEYLIST)) { + // + // INIT_CTX_KEYLIST_SHARED was used to set the flag that indicates + // this keylist is shared with one or more other contexts. Can't + // expand the shared copy without impacting the others, so break away + // from the sharing group by making a new copy. + // + // (If all shared copies break away in this fashion, then the last + // copy of the dangling keylist will be GC'd.) + // + // Keylists are only typesets, so no need for a specifier. + + REBCTX *meta = SER(keylist)->link.meta; // preserve meta object + + keylist = Copy_Array_Extra_Shallow(keylist, SPECIFIED, delta); + + SER(keylist)->link.meta = meta; + + MANAGE_ARRAY(keylist); + INIT_CTX_KEYLIST_UNIQUE(context, keylist); + + return TRUE; + } + + if (delta == 0) return FALSE; + + // INIT_CTX_KEYLIST_UNIQUE was used to set this keylist in the + // context, and no INIT_CTX_KEYLIST_SHARED was used by another context + // to mark the flag indicating it's shared. Extend it directly. + + Extend_Series(SER(keylist), delta); + TERM_ARRAY_LEN(keylist, ARR_LEN(keylist)); + + return FALSE; +} + + +// +// Expand_Context: C +// +// Expand a context. Copy words if keylist is not unique. +// +void Expand_Context(REBCTX *context, REBCNT delta) +{ + // varlist is unique to each object--expand without making a copy. + // + Extend_Series(SER(CTX_VARLIST(context)), delta); + TERM_ARRAY_LEN(CTX_VARLIST(context), ARR_LEN(CTX_VARLIST(context))); + + Expand_Context_Keylist_Core(context, delta); +} + + +// +// Append_Context: C +// +// Append a word to the context word list. Expands the list if necessary. +// Returns the value cell for the word. The new variable is unset by default. +// +// !!! Review if it would make more sense to use TRASH. +// +// If word is not NULL, use the word sym and bind the word value, otherwise +// use sym. When using a word, it will be modified to be specifically bound +// to this context after the operation. +// +// !!! Should there be a clearer hint in the interface, with a REBVAL* out, +// to give a fully bound value as a result? Given that the caller passed +// in the context and can get the index out of a relatively bound word, +// they usually likely don't need the result directly. +// +REBVAL *Append_Context( + REBCTX *context, + RELVAL *opt_any_word, + REBSTR *opt_name +) { + REBARR *keylist = CTX_KEYLIST(context); + + // Add the key to key list + // + EXPAND_SERIES_TAIL(SER(keylist), 1); + REBVAL *key = SINK(ARR_LAST(keylist)); + Init_Typeset( + key, + ALL_64, + opt_any_word != NULL ? VAL_WORD_SPELLING(opt_any_word) : opt_name + ); + TERM_ARRAY_LEN(keylist, ARR_LEN(keylist)); + + // Add an unset value to var list + // + EXPAND_SERIES_TAIL(SER(CTX_VARLIST(context)), 1); + REBVAL *value = SINK(ARR_LAST(CTX_VARLIST(context))); + Init_Void(value); + TERM_ARRAY_LEN(CTX_VARLIST(context), ARR_LEN(CTX_VARLIST(context))); + + if (opt_any_word) { + REBCNT len = CTX_LEN(context); + + // We want to not just add a key/value pairing to the context, but we + // want to bind a word while we are at it. Make sure symbol is valid. + // + assert(opt_name == NULL); + + // When a binding is made to an ordinary context, the value list is + // used as the target and the index is a positive number. Note that + // for stack-relative bindings, the index will be negative and the + // target will be a function's PARAMLIST series. + // + assert(NOT_VAL_FLAG(opt_any_word, VALUE_FLAG_RELATIVE)); + SET_VAL_FLAG(opt_any_word, WORD_FLAG_BOUND); + INIT_WORD_CONTEXT(opt_any_word, context); + INIT_WORD_INDEX(opt_any_word, len); // length we just bumped + } + else + assert(opt_name != NULL); + + // The variable value location for the key we just added. It's currently + // unset (maybe trash someday?) but in either case, known to not be + // a relative any-word or any-array + // + return value; +} + + +// +// Copy_Context_Shallow_Extra: C +// +// Makes a copy of a context. If no extra storage space is requested, then +// the same keylist will be used. +// +REBCTX *Copy_Context_Shallow_Extra(REBCTX *src, REBCNT extra) { + assert(GET_SER_FLAG(CTX_VARLIST(src), ARRAY_FLAG_VARLIST)); + ASSERT_ARRAY_MANAGED(CTX_KEYLIST(src)); + + REBCTX *meta = CTX_META(src); // preserve meta object (if any) + + // Note that keylists contain only typesets (hence no relative values), + // and no varlist is part of a function body. All the values here should + // be fully specified. + // + REBCTX *dest; + if (extra == 0) { + REBARR *varlist = Copy_Array_Shallow(CTX_VARLIST(src), SPECIFIED); + SET_SER_FLAG(varlist, ARRAY_FLAG_VARLIST); + + dest = CTX(varlist); + INIT_CTX_KEYLIST_SHARED(dest, CTX_KEYLIST(src)); + } + else { + REBARR *keylist = Copy_Array_Extra_Shallow( + CTX_KEYLIST(src), SPECIFIED, extra + ); + REBARR *varlist = Copy_Array_Extra_Shallow( + CTX_VARLIST(src), SPECIFIED, extra + ); + SET_SER_FLAG(varlist, ARRAY_FLAG_VARLIST); + + dest = CTX(varlist); + INIT_CTX_KEYLIST_UNIQUE(dest, keylist); + MANAGE_ARRAY(CTX_KEYLIST(dest)); + } + + CTX_VALUE(dest)->payload.any_context.varlist = CTX_VARLIST(dest); + + INIT_CONTEXT_META(dest, meta); // will be placed on new keylist + + return dest; +} + + +// +// Collect_Keys_Start: C +// +// Use the Bind_Table to start collecting new keys for a context. +// Use Collect_Keys_End() when done. +// +// WARNING: This routine uses the shared BUF_COLLECT rather than +// targeting a new series directly. This way a context can be +// allocated at exactly the right length when contents are copied. +// Therefore do not call code that might call BIND or otherwise +// make use of the Bind_Table or BUF_COLLECT. +// +void Collect_Keys_Start(REBFLGS flags) +{ + assert(ARR_LEN(BUF_COLLECT) == 0); // should be empty + if (flags & COLLECT_ANY_WORD) { + NOOP; // flags not paid attention to for now + } + + // Leave the [0] slot blank while collecting. This will become the + // "rootparam" in function paramlists (where the FUNCTION! archetype + // value goes), the [0] slot in varlists (where the ANY-CONTEXT! archetype + // goes), and the [0] slot in keylists (which sometimes are FUNCTION! if + // it's a FRAME! context...and not yet used in other context types) + // + // The reason it is set to an unreadable blank is because if it were trash + // then the copy routine that grabs the varlist as a copy of this array would + // have to support copying trash--which they do not allow. + // + SET_UNREADABLE_BLANK(ARR_HEAD(BUF_COLLECT)); + SET_ARRAY_LEN_NOTERM(BUF_COLLECT, 1); +} + + +// +// Grab_Collected_Keylist_Managed: C +// +// The BUF_COLLECT is used to gather keys, which may wind up not requiring any +// new keys from the `prior` that was passed in. If this is the case, then +// that prior keylist is returned...otherwise a new one is created. +// +// !!! "Grab" is used because "Copy_Or_Reuse" is long, and is picked to draw +// attention to look at the meaning. Better short communicative name? +// +REBARR *Grab_Collected_Keylist_Managed(REBCTX *prior) +{ + REBARR *keylist; + + // We didn't terminate as we were collecting, so terminate now. + // + assert(ARR_LEN(BUF_COLLECT) >= 1); // always at least [0] for rootkey + TERM_ARRAY_LEN(BUF_COLLECT, ARR_LEN(BUF_COLLECT)); + +#if !defined(NDEBUG) + // + // When the key collecting is done, we may be asked to give back a keylist + // and when we do, if nothing was added beyond the `prior` then that will + // be handed back. The array handed back will always be managed, so if + // we create it then it will be, and if we reuse the prior it will be. + // + if (prior) ASSERT_ARRAY_MANAGED(CTX_KEYLIST(prior)); +#endif + + // If no new words, prior context. Note length must include the slot + // for the rootkey...and note also this means the rootkey cell *may* + // be shared between all keylists when you pass in a prior. + // + if (prior && ARR_LEN(BUF_COLLECT) == CTX_LEN(prior) + 1) { + keylist = CTX_KEYLIST(prior); + } + else { + // The BUF_COLLECT should contain only typesets, so no relative values + // + keylist = Copy_Array_Shallow(BUF_COLLECT, SPECIFIED); + MANAGE_ARRAY(keylist); + } + + SER(keylist)->link.meta = NULL; // clear meta object (GC sees this) + + return keylist; +} + + +// +// Collect_Keys_End: C +// +// Free the Bind_Table for reuse and empty the BUF_COLLECT. +// +void Collect_Keys_End(struct Reb_Binder *binder) +{ + // We didn't terminate as we were collecting, so terminate now. + // + assert(ARR_LEN(BUF_COLLECT) >= 1); // always at least [0] for rootkey + TERM_ARRAY_LEN(BUF_COLLECT, ARR_LEN(BUF_COLLECT)); + + // Reset binding table (note BUF_COLLECT may have expanded) + // + RELVAL *key; + for (key = ARR_HEAD(BUF_COLLECT) + 1; NOT_END(key); key++) { + REBSTR *canon = VAL_KEY_CANON(key); + + if (binder != NULL) { + Remove_Binder_Index(binder, canon); + continue; + } + + // !!! This doesn't have a "binder" available to clear out the + // keys with. The nature of handling error states means that if + // a thread-safe binding system was implemented, we'd have to know + // which thread had the error to roll back any binding structures. + // For now just zero it out based on the collect buffer. + // + assert( + canon->misc.bind_index.high != 0 + || canon->misc.bind_index.low != 0 + ); + canon->misc.bind_index.high = 0; + canon->misc.bind_index.low = 0; + } + + SET_ARRAY_LEN_NOTERM(BUF_COLLECT, 0); +} + + +// +// Collect_Context_Keys: C +// +// Collect words from a prior context. If `check_dups` is passed in then +// there is a check for duplicates, otherwise the keys are assumed to +// be unique and copied in using `memcpy` as an optimization. +// +void Collect_Context_Keys( + struct Reb_Binder *binder, + REBCTX *context, + REBOOL check_dups +) { + REBVAL *key = CTX_KEYS_HEAD(context); + REBINT bind_index = ARR_LEN(BUF_COLLECT); + RELVAL *collect; // can't set until after potential expansion... + + // The BUF_COLLECT buffer should at least have the SYM_0 in its first slot + // to use as a "rootkey" in the generated keylist (and also that the first + // binding index we give out is at least 1, since 0 is used in the + // Bind_Table to mean "word not collected yet"). + // + assert(bind_index >= 1); + + // this is necessary for memcpy below to not overwrite memory BUF_COLLECT + // does not own. (It may make the buffer capacity bigger than necessary + // if duplicates are found, but the actual buffer length will be set + // correctly by the end.) + // + EXPAND_SERIES_TAIL(SER(BUF_COLLECT), CTX_LEN(context)); + + // EXPAND_SERIES_TAIL will increase the ARR_LEN, even though we intend + // to overwrite it with a possibly shorter length. Put the length back + // and now that the expansion is done, get the pointer to where we want + // to start collecting new typesets. + // + SET_ARRAY_LEN_NOTERM(BUF_COLLECT, bind_index); + collect = ARR_TAIL(BUF_COLLECT); + + if (check_dups) { + // We're adding onto the end of the collect buffer and need to + // check for duplicates of what's already there. + // + for (; NOT_END(key); key++) { + REBSTR *canon = VAL_KEY_CANON(key); + if (NOT(Try_Add_Binder_Index(binder, canon, bind_index))) { + // + // If we found the typeset's symbol in the bind table already + // then don't collect it in the buffer again. + // + continue; + } + + ++bind_index; + + // !!! At the moment objects do not heed the typesets in the + // keys. If they did, what sort of rule should the typesets + // have when being inherited? + // + *collect = *key; + ++collect; + } + + // Increase the length of BUF_COLLLECT by how far `collect` advanced + // (would be 0 if all the keys were duplicates...) + // + SET_ARRAY_LEN_NOTERM( + BUF_COLLECT, + ARR_LEN(BUF_COLLECT) + (collect - ARR_TAIL(BUF_COLLECT)) + ); + } + else { + // Optimized copy of the keys. We can use `memcpy` because these are + // typesets that are just 64-bit bitsets plus a symbol ID; there is + // no need to clone the REBVALs to give the copies new identity. + // + // Add the keys and bump the length of the collect buffer after + // (prior to that, the tail should be on the END marker of + // the existing content--if any) + // + memcpy(collect, key, CTX_LEN(context) * sizeof(REBVAL)); + SET_ARRAY_LEN_NOTERM( + BUF_COLLECT, ARR_LEN(BUF_COLLECT) + CTX_LEN(context) + ); + + for (; NOT_END(key); ++key, ++bind_index) + Add_Binder_Index(binder, VAL_KEY_CANON(key), bind_index); + } + + // BUF_COLLECT doesn't get terminated as its being built, but it gets + // terminated in Collect_Keys_End() +} + + +// +// Collect_Context_Inner_Loop: C +// +// The inner recursive loop used for Collect_Context function below. +// +static void Collect_Context_Inner_Loop( + struct Reb_Binder *binder, + const RELVAL head[], + REBFLGS flags +) { + const RELVAL *value = head; + for (; NOT_END(value); value++) { + if (ANY_WORD(value)) { + REBSTR *canon = VAL_WORD_CANON(value); + if (Try_Get_Binder_Index(binder, canon) == 0) { + // once per word + if (IS_SET_WORD(value) || (flags & COLLECT_ANY_WORD)) { + Add_Binder_Index(binder, canon, ARR_LEN(BUF_COLLECT)); + EXPAND_SERIES_TAIL(SER(BUF_COLLECT), 1); + Init_Typeset( + ARR_LAST(BUF_COLLECT), + // Allow all datatypes but no void (initially): + ~FLAGIT_KIND(REB_MAX_VOID), + VAL_WORD_SPELLING(value) + ); + } + } + else { // Word is duplicated + if (flags & COLLECT_NO_DUP) + fail (Error_Dup_Vars_Raw(value)); // cleans binding table + } + continue; + } + // Recurse into sub-blocks: + if (ANY_EVAL_BLOCK(value) && (flags & COLLECT_DEEP)) + Collect_Context_Inner_Loop(binder, VAL_ARRAY_AT(value), flags); + } +} + + +// +// Collect_Keylist_Managed: C +// +// Scans a block for words to extract and make into typeset keys to go in +// a context. The Bind_Table is used to quickly determine duplicate entries. +// +// A `prior` context can be provided to serve as a basis; all the keys in +// the prior will be returned, with only new entries contributed by the +// data coming from the head[] array. If no new values are needed (the +// array has no relevant words, or all were just duplicates of words already +// in prior) then then `prior`'s keylist may be returned. The result is +// always pre-managed, because it may not be legal to free prior's keylist. +// +// Returns: +// A block of typesets that can be used for a context keylist. +// If no new words, the prior list is returned. +// +// !!! There was previously an optimization in object creation which bypassed +// key collection in the case where head[] was empty. Revisit if it is worth +// the complexity to move handling for that case in this routine. +// +REBARR *Collect_Keylist_Managed( + REBCNT *self_index_out, // which context index SELF is in (if COLLECT_SELF) + const RELVAL head[], + REBCTX *prior, + REBFLGS flags // see %sys-core.h for COLLECT_ANY_WORD, etc. +) { + struct Reb_Binder binder; + INIT_BINDER(&binder); + + Collect_Keys_Start(flags); + + if (flags & COLLECT_ENSURE_SELF) { + if ( + !prior + || ( + (*self_index_out = Find_Canon_In_Context( + prior, Canon(SYM_SELF), TRUE) + ) + == 0 + ) + ) { + // No prior or no SELF in prior, so we'll add it as the first key + // + RELVAL *self_key = ARR_AT(BUF_COLLECT, 1); + Init_Typeset(self_key, ALL_64, Canon(SYM_SELF)); + + // !!! See notes on the flags about why SELF is set hidden but + // not unbindable with TYPESET_FLAG_UNBINDABLE. + // + SET_VAL_FLAG(self_key, TYPESET_FLAG_HIDDEN); + + Add_Binder_Index(&binder, VAL_KEY_CANON(self_key), 1); + *self_index_out = 1; + SET_ARRAY_LEN_NOTERM(BUF_COLLECT, 2); // [0] rootkey, plus SELF + } + else { + // No need to add SELF if it's going to be added via the `prior` + // so just return the `self_index_out` as-is. + } + } + else { + assert(self_index_out == NULL); + } + + // Setup binding table with existing words, no need to check duplicates + // + if (prior) Collect_Context_Keys(&binder, prior, FALSE); + + // Scan for words, adding them to BUF_COLLECT and bind table: + Collect_Context_Inner_Loop(&binder, head, flags); + + // Grab the keylist, and set its rootkey in [0] to BLANK! (CTX_KEY and + // CTX_VAR indexing start at 1, and [0] for the variables is an instance + // of the ANY-CONTEXT! value itself). + // + // !!! Usages of the rootkey for non-FRAME! contexts is open for future. + // + REBARR *keylist = Grab_Collected_Keylist_Managed(prior); + + Collect_Keys_End(&binder); + + SHUTDOWN_BINDER(&binder); + return keylist; +} + + +// +// Collect_Words_Inner_Loop: C +// +// Used for Collect_Words() after the binds table has +// been set up. +// +static void Collect_Words_Inner_Loop( + struct Reb_Binder *binder, + const RELVAL head[], + REBFLGS flags +) { + const RELVAL *value = head; + for (; NOT_END(value); value++) { + if (ANY_WORD(value) + && Try_Get_Binder_Index(binder, VAL_WORD_CANON(value)) == 0 + && (IS_SET_WORD(value) || (flags & COLLECT_ANY_WORD)) + ){ + Add_Binder_Index(binder, VAL_WORD_CANON(value), 1); + + REBVAL *word = Alloc_Tail_Array(BUF_COLLECT); + Init_Word(word, VAL_WORD_SPELLING(value)); + } + else if (ANY_EVAL_BLOCK(value) && (flags & COLLECT_DEEP)) + Collect_Words_Inner_Loop(binder, VAL_ARRAY_AT(value), flags); + } +} + + +// +// Collect_Words: C +// +// Collect words from a prior block and new block. +// +REBARR *Collect_Words( + const RELVAL head[], + RELVAL *opt_prior_head, + REBFLGS flags +) { + struct Reb_Binder binder; + INIT_BINDER(&binder); + + assert(ARR_LEN(BUF_COLLECT) == 0); // should be empty + + if (opt_prior_head) + Collect_Words_Inner_Loop(&binder, opt_prior_head, COLLECT_ANY_WORD); + + REBCNT start = ARR_LEN(BUF_COLLECT); + Collect_Words_Inner_Loop(&binder, head, flags); + TERM_ARRAY_LEN(BUF_COLLECT, ARR_LEN(BUF_COLLECT)); + + // Reset word markers: + // + RELVAL *word; + for (word = ARR_HEAD(BUF_COLLECT); NOT_END(word); word++) + Remove_Binder_Index(&binder, VAL_WORD_CANON(word)); + + // The words in BUF_COLLECT are newly created, and should not be bound + // at all... hence fully specified with no relative words + // + REBARR *array = Copy_Array_At_Max_Shallow( + BUF_COLLECT, start, SPECIFIED, ARR_LEN(BUF_COLLECT) - start + ); + SET_ARRAY_LEN_NOTERM(BUF_COLLECT, 0); + + SHUTDOWN_BINDER(&binder); + return array; +} + + +// +// Rebind_Context_Deep: C +// +// Clone old context to new context knowing +// which types of values need to be copied, deep copied, and rebound. +// +void Rebind_Context_Deep( + REBCTX *source, + REBCTX *dest, + struct Reb_Binder *opt_binder +) { + Rebind_Values_Deep(source, dest, CTX_VARS_HEAD(dest), opt_binder); +} + + +// +// Make_Selfish_Context_Detect: C +// +// Create a context by detecting top-level set-words in an array of values. +// So if the values were the contents of the block `[a: 10 b: 20]` then the +// resulting context would be for two words, `a` and `b`. +// +// Optionally a parent context may be passed in, which will contribute its +// keylist of words to the result if provided. +// +// The resulting context will have a SELF: defined as a hidden key (will not +// show up in `words-of` but will be bound during creation). As part of +// the migration away from SELF being a keyword, the logic for adding and +// managing SELF has been confined to this function (called by `make object!` +// and some other context-creating routines). This will ultimately turn +// into something paralleling the non-keyword definitional RETURN:, where +// the generators (like OBJECT) will be taking responsibility for it. +// +// This routine will *always* make a context with a SELF. This lacks the +// nuance that is expected of the generators, which will have an equivalent +// to ` return` or ` leave` to suppress it. +// +REBCTX *Make_Selfish_Context_Detect( + enum Reb_Kind kind, + const RELVAL head[], + REBCTX *opt_parent +) { + REBCNT self_index; + REBARR *keylist = Collect_Keylist_Managed( + &self_index, + head, + opt_parent, + COLLECT_ONLY_SET_WORDS | COLLECT_ENSURE_SELF + ); + + REBCNT len = ARR_LEN(keylist); + + // Make a context of same size as keylist (END already accounted for) + // + REBARR *varlist = Make_Array_Core(len, ARRAY_FLAG_VARLIST); + TERM_ARRAY_LEN(varlist, len); + + REBCTX *context = CTX(varlist); + + // !!! We actually don't know if the keylist coming back from + // Collect_Keylist_Managed was created new or reused. Err on the safe + // side for now, but it could also return a result so we could know + // if it would be legal to call INIT_CTX_KEYLIST_UNIQUE. + // + INIT_CTX_KEYLIST_SHARED(context, keylist); + + // context[0] is an instance value of the OBJECT!/PORT!/ERROR!/MODULE! + // + REBVAL *var = KNOWN(ARR_HEAD(varlist)); + VAL_RESET_HEADER(var, kind); + var->payload.any_context.varlist = varlist; + var->payload.any_context.phase = NULL; + var->extra.binding = NULL; + + ++var; + + // !!! For Ren-C we probably want to go with void default intead of + // blanks. Also the filling of parent vars will overwrite the work here. + // + for (; len > 1; --len, ++var) // 1 is rootvar (context), already done + Init_Blank(var); + + if (opt_parent) { + // + // Bitwise copy parent values (will have bits fixed by Clonify). + // None of these should be relative, because they came from object + // vars (that were not part of the deep copy of a function body) + // + memcpy( + CTX_VARS_HEAD(context), + CTX_VARS_HEAD(opt_parent), + (CTX_LEN(opt_parent)) * sizeof(REBVAL) + ); + + // For values we copied that were blocks and strings, replace + // their series components with deep copies of themselves: + // + Clonify_Values_Len_Managed( + CTX_VARS_HEAD(context), + SPECIFIED, + CTX_LEN(context), + TRUE, + TS_CLONE + ); + } + + // We should have a SELF key in all cases here. Set it to be a copy of + // the object we just created. (It is indeed a copy of the [0] element, + // but it doesn't need to be protected because the user overwriting it + // won't destroy the integrity of the context.) + // + assert(CTX_KEY_SYM(context, self_index) == SYM_SELF); + Move_Value(CTX_VAR(context, self_index), CTX_VALUE(context)); + + // !!! In Ren-C, the idea that functions are rebound when a context is + // inherited is being deprecated. It simply isn't viable for objects + // with N methods to have those N methods permanently cloned in the + // copies and have their bodies rebound to the new object. A more + // conventional method of `this->method()` access is needed with + // cooperation from the evaluator, and that is slated to be `/method` + // as a practical use of paths that implicitly start from "wherever + // you dispatched from" + // + // Temporarily the old behavior is kept, so we deep copy and rebind. + // + if (opt_parent) + Rebind_Context_Deep(opt_parent, context, NULL); // NULL=no more binds + + ASSERT_CONTEXT(context); + +#if !defined(NDEBUG) + PG_Reb_Stats->Objects++; +#endif + + return context; +} + + +// +// Construct_Context: C +// +// Construct an object without evaluation. +// Parent can be null. Values are rebound. +// +// In R3-Alpha the CONSTRUCT native supported a mode where the following: +// +// [a: b: 1 + 2 d: a e:] +// +// ...would have `a` and `b` will be set to 1, while `+` and `2` will be +// ignored, `d` will be the word `a` (where it knows to be bound to the a +// of the object) and `e` would be left as it was. +// +// Ren-C retakes the name CONSTRUCT to be the arity-2 object creation +// function with evaluation, and makes "raw" construction (via /ONLY on both +// 1-arity HAS and CONSTRUCT) more regimented. The requirement for a raw +// construct is that the fields alternate SET-WORD! and then value, with +// no evaluation--hence it is possible to use any value type (a GROUP! or +// another SET-WORD!, for instance) as the value. +// +// !!! Because this is a work in progress, set-words would be gathered if +// they were used as values, so they are not currently permitted. +// +REBCTX *Construct_Context( + enum Reb_Kind kind, + RELVAL head[], // !!! Warning: modified binding + REBSPC *specifier, + REBCTX *opt_parent +) { + REBCTX *context = Make_Selfish_Context_Detect( + kind, // type + head, // values to scan for toplevel set-words + opt_parent // parent + ); + + if (head == NULL) + return context; + + Bind_Values_Shallow(head, context); + + const RELVAL *value = head; + for (; NOT_END(value); value += 2) { + if (!IS_SET_WORD(value)) + fail (Error_Invalid_Type(VAL_TYPE(value))); + + if (IS_END(value + 1)) + fail ("Unexpected end in context spec block."); + + if (IS_SET_WORD(value + 1)) + fail (Error_Invalid_Type(VAL_TYPE(value + 1))); // TBD: support + + REBVAL *var = Sink_Var_May_Fail(value, specifier); + Derelativize(var, value + 1, specifier); + } + + return context; +} + + +// +// Context_To_Array: C +// +// Return a block containing words, values, or set-word: value +// pairs for the given object. Note: words are bound to original +// object. +// +// Modes: +// 1 for word +// 2 for value +// 3 for words and values +// +REBARR *Context_To_Array(REBCTX *context, REBINT mode) +{ + REBVAL *key = CTX_KEYS_HEAD(context); + REBVAL *var = CTX_VARS_HEAD(context); + + assert(!(mode & 4)); + + REBARR *block = Make_Array(CTX_LEN(context) * (mode == 3 ? 2 : 1)); + + REBCNT n = 1; + for (; NOT_END(key); n++, key++, var++) { + if (NOT_VAL_FLAG(key, TYPESET_FLAG_HIDDEN)) { + if (mode & 1) { + REBVAL *value = Alloc_Tail_Array(block); + Init_Any_Word_Bound( + value, + (mode & 2) ? REB_SET_WORD : REB_WORD, + VAL_KEY_SPELLING(key), + context, + n + ); + if (mode & 2) + SET_VAL_FLAG(value, VALUE_FLAG_LINE); + } + if (mode & 2) { + // + // Context might have voids, which denote the value have not + // been set. These contexts cannot be converted to blocks, + // since user arrays may not contain void. + // + if (IS_VOID(var)) + fail (Error_Void_Object_Block_Raw()); + + Append_Value(block, var); + } + } + } + + return block; +} + + +// +// Merge_Contexts_Selfish: C +// +// Create a child context from two parent contexts. Merge common fields. +// Values from the second parent take precedence. +// +// Deep copy and rebind the child. +// +REBCTX *Merge_Contexts_Selfish(REBCTX *parent1, REBCTX *parent2) +{ + struct Reb_Binder binder; + INIT_BINDER(&binder); + + assert(CTX_TYPE(parent1) == CTX_TYPE(parent2)); + + // Merge parent1 and parent2 words. + // Keep the binding table. + Collect_Keys_Start(COLLECT_ANY_WORD | COLLECT_ENSURE_SELF); + + // Setup binding table and BUF_COLLECT with parent1 words. Don't bother + // checking for duplicates, buffer is empty. + // + Collect_Context_Keys(&binder, parent1, FALSE); + + // Add parent2 words to binding table and BUF_COLLECT, and since we know + // BUF_COLLECT isn't empty then *do* check for duplicates. + // + Collect_Context_Keys(&binder, parent2, TRUE); + + // Collect_Keys_End() terminates, but Collect_Context_Inner_Loop() doesn't. + // + TERM_ARRAY_LEN(BUF_COLLECT, ARR_LEN(BUF_COLLECT)); + + // Allocate child (now that we know the correct size). Obey invariant + // that keylists are always managed. The BUF_COLLECT contains only + // typesets, so no need for a specifier in the copy. + // + // !!! Review: should child start fresh with no meta information, or get + // the meta information held by parents? + // + REBARR *keylist = Copy_Array_Shallow(BUF_COLLECT, SPECIFIED); + MANAGE_ARRAY(keylist); + Init_Blank(ARR_HEAD(keylist)); // Currently no rootkey usage + SER(keylist)->link.meta = NULL; + + REBARR *varlist = Make_Array_Core(ARR_LEN(keylist), ARRAY_FLAG_VARLIST); + REBCTX *merged = CTX(varlist); + INIT_CTX_KEYLIST_UNIQUE(merged, keylist); + + // !!! Currently we assume the child will be of the same type as the + // parent...so if the parent was an OBJECT! so will the child be, if + // the parent was an ERROR! so will the child be. This is a new idea, + // so review consequences. + // + REBVAL *rootvar = SINK(ARR_HEAD(varlist)); + VAL_RESET_HEADER(rootvar, CTX_TYPE(parent1)); + rootvar->payload.any_context.varlist = varlist; + rootvar->payload.any_context.phase = NULL; + rootvar->extra.binding = NULL; + + // Copy parent1 values: + memcpy( + CTX_VARS_HEAD(merged), + CTX_VARS_HEAD(parent1), + CTX_LEN(parent1) * sizeof(REBVAL) + ); + + // Update the child tail before making calls to CTX_VAR(), because the + // debug build does a length check. + // + TERM_ARRAY_LEN(CTX_VARLIST(merged), ARR_LEN(keylist)); + + // Copy parent2 values: + REBVAL *key = CTX_KEYS_HEAD(parent2); + REBVAL *value = CTX_VARS_HEAD(parent2); + for (; NOT_END(key); key++, value++) { + // no need to search when the binding table is available + REBINT n = Try_Get_Binder_Index(&binder, VAL_KEY_CANON(key)); + assert(n != 0); + Move_Value(CTX_VAR(merged, n), value); + } + + // Deep copy the child. Context vars are REBVALs, already fully specified + // + Clonify_Values_Len_Managed( + CTX_VARS_HEAD(merged), + SPECIFIED, + CTX_LEN(merged), + TRUE, + TS_CLONE + ); + + // Rebind the child + Rebind_Context_Deep(parent1, merged, NULL); + Rebind_Context_Deep(parent2, merged, &binder); + + // release the bind table + Collect_Keys_End(&binder); + + // We should have gotten a SELF in the results, one way or another. + { + REBCNT self_index = Find_Canon_In_Context(merged, Canon(SYM_SELF), TRUE); + assert(self_index != 0); + assert(CTX_KEY_SYM(merged, self_index) == SYM_SELF); + Move_Value(CTX_VAR(merged, self_index), CTX_VALUE(merged)); + } + + SHUTDOWN_BINDER(&binder); + return merged; +} + + +// +// Resolve_Context: C +// +// Only_words can be a block of words or an index in the target +// (for new words). +// +void Resolve_Context( + REBCTX *target, + REBCTX *source, + REBVAL *only_words, + REBOOL all, + REBOOL expand +) { + FAIL_IF_READ_ONLY_CONTEXT(target); + + REBCNT i; + if (IS_INTEGER(only_words)) { // Must be: 0 < i <= tail + i = VAL_INT32(only_words); + if (i == 0) + i = 1; + if (i > CTX_LEN(target)) + return; + } + else + i = 0; + + struct Reb_Binder binder; + INIT_BINDER(&binder); + + REBVAL *key; + REBVAL *var; + + // !!! This function does its own version of resetting the bind table + // and hence the Collect_Keys_End that would be performed in the case of + // a `fail (Error(...))` will not properly reset it. Because the code + // does array expansion it cannot guarantee a fail won't happen, hence + // the method needs to be reviewed to something that could properly + // reset in the case of an out of memory error. + // + Collect_Keys_Start(COLLECT_ONLY_SET_WORDS); + + REBINT n = 0; + + // If limited resolve, tag the word ids that need to be copied: + if (i != 0) { + // Only the new words of the target: + for (key = CTX_KEY(target, i); NOT_END(key); key++) + Add_Binder_Index(&binder, VAL_KEY_CANON(key), -1); + n = CTX_LEN(target); + } + else if (IS_BLOCK(only_words)) { + // Limit exports to only these words: + RELVAL *word = VAL_ARRAY_AT(only_words); + for (; NOT_END(word); word++) { + if (IS_WORD(word) || IS_SET_WORD(word)) { + Add_Binder_Index(&binder, VAL_WORD_CANON(word), -1); + n++; + } + else { + // !!! There was no error here. :-/ Should it be one? + } + } + } + + // Expand target as needed: + if (expand && n > 0) { + // Determine how many new words to add: + for (key = CTX_KEYS_HEAD(target); NOT_END(key); key++) + if (Try_Get_Binder_Index(&binder, VAL_KEY_CANON(key)) != 0) + --n; + + // Expand context by the amount required: + if (n > 0) + Expand_Context(target, n); + else + expand = FALSE; + } + + // Maps a word to its value index in the source context. + // Done by marking all source words (in bind table): + key = CTX_KEYS_HEAD(source); + for (n = 1; NOT_END(key); n++, key++) { + REBSTR *canon = VAL_KEY_CANON(key); + if (IS_VOID(only_words)) + Add_Binder_Index(&binder, canon, n); + else { + if (Try_Get_Binder_Index(&binder, canon) != 0) { + Remove_Binder_Index(&binder, canon); + Add_Binder_Index(&binder, canon, n); + } + } + } + + // Foreach word in target, copy the correct value from source: + // + var = i != 0 ? CTX_VAR(target, i) : CTX_VARS_HEAD(target); + key = i != 0 ? CTX_KEY(target, i) : CTX_KEYS_HEAD(target); + for (; NOT_END(key); key++, var++) { + REBINT m = Try_Remove_Binder_Index(&binder, VAL_KEY_CANON(key)); + if (m != 0) { + // "the remove succeeded, so it's marked as set now" (old comment) + if ( + NOT_VAL_FLAG(var, VALUE_FLAG_PROTECTED) + && (all || IS_VOID(var)) + ) { + if (m < 0) Init_Void(var); // no value in source context + else { + Move_Value(var, CTX_VAR(source, m)); + + // Need to also copy if the binding is lookahead (e.g. + // would be an infix call). + // + if (GET_VAL_FLAG(CTX_VAR(source, m), VALUE_FLAG_ENFIXED)) + SET_VAL_FLAG(var, VALUE_FLAG_ENFIXED); + } + } + } + } + + // Add any new words and values: + if (expand) { + key = CTX_KEYS_HEAD(source); + for (n = 1; NOT_END(key); n++, key++) { + REBSTR *canon = VAL_KEY_CANON(key); + if (Try_Remove_Binder_Index(&binder, canon) != 0) { + // + // Note: no protect check is needed here + // + var = Append_Context(target, 0, canon); + Move_Value(var, CTX_VAR(source, n)); + + // Need to also copy if the binding is lookahead (e.g. + // would be an infix call). + // + if (GET_VAL_FLAG(CTX_VAR(source, n), VALUE_FLAG_ENFIXED)) + SET_VAL_FLAG(var, VALUE_FLAG_ENFIXED); + } + } + } + else { + // Reset bind table (do not use Collect_End): + if (i != 0) { + for (key = CTX_KEY(target, i); NOT_END(key); key++) + Try_Remove_Binder_Index(&binder, VAL_KEY_CANON(key)); + } + else if (IS_BLOCK(only_words)) { + RELVAL *word = VAL_ARRAY_AT(only_words); + for (; NOT_END(word); word++) { + if (IS_WORD(word) || IS_SET_WORD(word)) + Try_Remove_Binder_Index(&binder, VAL_WORD_CANON(word)); + } + } + else { + for (key = CTX_KEYS_HEAD(source); NOT_END(key); key++) + Try_Remove_Binder_Index(&binder, VAL_KEY_CANON(key)); + } + } + + // !!! Note we explicitly do *not* use Collect_Keys_End(). See warning + // about errors, out of memory issues, etc. at Collect_Keys_Start() + // + SET_SERIES_LEN(SER(BUF_COLLECT), 0); // allow reuse, no terminator + + SHUTDOWN_BINDER(&binder); +} + + +// +// Find_Canon_In_Context: C +// +// Search a context looking for the given canon symbol. Return the index or +// 0 if not found. +// +REBCNT Find_Canon_In_Context(REBCTX *context, REBSTR *canon, REBOOL always) +{ + assert(GET_SER_INFO(canon, STRING_INFO_CANON)); + + REBVAL *key = CTX_KEYS_HEAD(context); + REBCNT len = CTX_LEN(context); + + REBCNT n; + for (n = 1; n <= len; n++, key++) { + if (canon == VAL_KEY_CANON(key)) + return (!always && GET_VAL_FLAG(key, TYPESET_FLAG_HIDDEN)) ? 0 : n; + } + + // !!! Should this be changed to NOT_FOUND? + return 0; +} + + +// +// Select_Canon_In_Context: C +// +// Search a frame looking for the given word symbol and +// return the value for the word. Locate it by matching +// the canon word identifiers. Return NULL if not found. +// +REBVAL *Select_Canon_In_Context(REBCTX *context, REBSTR *sym) +{ + REBCNT n = Find_Canon_In_Context(context, sym, FALSE); + if (n == 0) return NULL; + + return CTX_VAR(context, n); +} + + +// +// Find_Word_In_Array: C +// +// Find word (of any type) in an array of values with linear search. +// +REBCNT Find_Word_In_Array(REBARR *array, REBCNT index, REBSTR *sym) +{ + RELVAL *value; + + for (; index < ARR_LEN(array); index++) { + value = ARR_AT(array, index); + if (ANY_WORD(value) && sym == VAL_WORD_CANON(value)) + return index; + } + + return NOT_FOUND; +} + + +// +// Obj_Value: C +// +// Return pointer to the nth VALUE of an object. +// Return zero if the index is not valid. +// +REBVAL *Obj_Value(REBVAL *value, REBCNT index) +{ + REBCTX *context = VAL_CONTEXT(value); + + if (index > CTX_LEN(context)) return 0; + return CTX_VAR(context, index); +} + + +// +// Startup_Collector: C +// +void Startup_Collector(void) +{ + // Temporary block used while scanning for frame words: + // "just holds typesets, no GC behavior" (!!! until typeset symbols or + // embedded tyeps are GC'd...!) + // + // Note that the logic inside Collect_Keylist managed assumes it's at + // least 2 long to hold the rootkey (SYM_0) and a possible SYM_SELF + // hidden actual key. + // + Init_Block(TASK_BUF_COLLECT, Make_Array_Core(2 + 98, 0)); +} + + +#ifndef NDEBUG + +// +// Assert_Context_Core: C +// +void Assert_Context_Core(REBCTX *c) +{ + REBARR *varlist = CTX_VARLIST(c); + + if (NOT_SER_FLAG(varlist, ARRAY_FLAG_VARLIST)) + panic (varlist); + + REBARR *keylist = CTX_KEYLIST(c); + + if (!CTX_KEYLIST(c)) + panic (c); + + if (GET_SER_INFO(keylist, CONTEXT_INFO_STACK)) + panic (keylist); + + REBVAL *rootvar = CTX_VALUE(c); + if (!ANY_CONTEXT(rootvar)) + panic (rootvar); + + REBCNT keys_len = ARR_LEN(keylist); + REBCNT vars_len = ARR_LEN(varlist); + + if (keys_len < 1) + panic (keylist); + + if (GET_SER_INFO(CTX_VARLIST(c), CONTEXT_INFO_STACK)) { + if (vars_len != 1) + panic (varlist); + } + else { + if (keys_len != vars_len) + panic (c); + } + + if (rootvar->payload.any_context.varlist != varlist) + panic (rootvar); + + if (CTX_VARS_UNAVAILABLE(c)) { + // + // !!! For the moment, don't check inaccessible stack frames any + // further. This includes varless reified frames and those reified + // frames that are no longer on the stack. + // + return; + } + + REBVAL *rootkey = CTX_ROOTKEY(c); + if (IS_BLANK_RAW(rootkey)) { + // + // Note that in the future the rootkey for ordinary OBJECT! or ERROR! + // PORT! etc. may be more interesting than BLANK. But it uses that + // for now--unreadable. + // + if (IS_FRAME(rootvar)) + panic (c); + } + else if (IS_FUNCTION(rootkey)) { + // + // At the moment, only FRAME! is able to reuse a FUNCTION!'s keylist. + // There may be reason to relax this, if you wanted to make an + // ordinary object that was a copy of a FRAME! but not a FRAME!. + // + if (!IS_FRAME(rootvar)) + panic (rootvar); + + // !!! Temporary disablement of an important check! + // + // Currently MAKE FRAME! of a FUNCTION! makes the keylist for the + // function itself, and not the underlying one. This is buggy, and + // needs to be fixed. It will require some major changes, though. + // + /*REBFRM *f = CTX_FRAME_IF_ON_STACK(c); + if (f != NULL) { + REBFUN *rootkey_fun = VAL_FUNC(rootkey); + REBFUN *frame_fun = FRM_UNDERLYING(f); + + if (rootkey_fun != frame_fun) { + printf("FRAME! context function doesn't match its REBFRM"); + panic (frame_fun); + } + }*/ + } + else + panic (rootkey); + + REBVAL *key = CTX_KEYS_HEAD(c); + REBVAL *var = CTX_VARS_HEAD(c); + + REBCNT n; + for (n = 1; n < keys_len; n++, var++, key++) { + if (IS_END(key)) { + printf("** Early key end at index: %d\n", cast(int, n)); + panic (c); + } + + if (!IS_TYPESET(key)) + panic (key); + + if (IS_END(var)) { + printf("** Early var end at index: %d\n", cast(int, n)); + panic (c); + } + } + + if (NOT_END(key)) { + printf("** Missing key end at index: %d\n", cast(int, n)); + panic (key); + } + + if (NOT_END(var)) { + printf("** Missing var end at index: %d\n", cast(int, n)); + panic (var); + } +} + +#endif diff --git a/src/core/c-do.c b/src/core/c-do.c old mode 100644 new mode 100755 index 481476d891..66064543f5 --- a/src/core/c-do.c +++ b/src/core/c-do.c @@ -1,2218 +1,38 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: c-do.c -** Summary: the core interpreter - the heart of REBOL -** Section: core -** Author: Carl Sassenrath -** Notes: -** WARNING WARNING WARNING -** This is highly tuned code that should only be modified by experts -** who fully understand its design. It is very easy to create odd -** side effects so please be careful and extensively test all changes! -** -***********************************************************************/ +// +// File: %c-do.c +// Summary: "DO Evaluator Wrappers" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// These are the "slightly more user-friendly" interfaces to the evaluator +// from %c-eval.c. These routines will do the setup of the Reb_Frame state +// for you. +// +// Even "friendlier" interfaces are available as macros on top of these. +// See %sys-do.h for Do_Any_Array_At_Throws() and similar macros. +// #include "sys-core.h" -#include -#include "sys-state.h" -enum Eval_Types { - ET_INVALID, // not valid to evaluate - ET_WORD, - ET_SELF, // returns itself - ET_FUNCTION, - ET_OPERATOR, - ET_PAREN, - ET_SET_WORD, - ET_LIT_WORD, - ET_GET_WORD, - ET_PATH, - ET_LIT_PATH, - ET_END // end of block -}; - -static jmp_buf *Halt_State = 0; //!!!!!!!!!! global? - -/* -void T_Error(REBCNT n) {;} - -// Deferred: -void T_Series(REBCNT n) {;} // image -void T_List(REBCNT n) {;} // list -*/ - -void Do_Rebcode(REBVAL *v) {;} - -#include "tmp-evaltypes.h" - -#define EVAL_TYPE(val) (Eval_Type_Map[VAL_TYPE(val)]) - -#define PUSH_ERROR(v, a) -#define PUSH_FUNC(v, w, s) -#define PUSH_BLOCK(b) - -static REBVAL *Func_Word(REBINT dsf) -{ - static REBVAL val; // Safe: Lifetime is limited to passage to error object. - Init_Word(&val, VAL_WORD_SYM(DSF_WORD(dsf))); - return &val; -} - - -/*********************************************************************** -** -*/ void Do_Op(REBVAL *func) -/* -** A trampoline. -** -***********************************************************************/ -{ - Func_Dispatch[VAL_GET_EXT(func) - REB_NATIVE](func); -} - - -/*********************************************************************** -** -*/ void Expand_Stack(REBCNT amount) -/* -** Expand the datastack. Invalidates any references to stack -** values, so code should generally use stack index integers, -** not pointers into the stack. -** -***********************************************************************/ -{ - if (SERIES_REST(DS_Series) >= STACK_LIMIT) Trap0(RE_STACK_OVERFLOW); - DS_Series->tail = DSP+1; - Extend_Series(DS_Series, amount); - DS_Base = BLK_HEAD(DS_Series); - Debug_Fmt(BOOT_STR(RS_STACK, 0), DSP, SERIES_REST(DS_Series)); -} - - -/*********************************************************************** -** -*/ void DS_Ret_Int(REBINT n) -/* -** Memsaver: set integer as return result on data stack. -** -***********************************************************************/ -{ - DS_RET_INT(n); -} - - -/*********************************************************************** -** -*/ void DS_Ret_Val(REBVAL *value) -/* -** Memsaver: set any value as return result on data stack. -** -***********************************************************************/ -{ - *DS_RETURN = *value; -} - - -/*********************************************************************** -** -*/ REBINT Eval_Depth() -/* -***********************************************************************/ -{ - REBINT depth = 0; - REBINT dsf; - - for (dsf = DSF; dsf > 0; dsf = PRIOR_DSF(dsf), depth++); - return depth; -} - - -/*********************************************************************** -** -*/ REBVAL *Stack_Frame(REBCNT n) -/* -***********************************************************************/ -{ - REBCNT dsf = DSF; - - for (dsf = DSF; dsf > 0; dsf = PRIOR_DSF(dsf)) { - if (n-- <= 0) return DS_VALUE(dsf); - } - - return 0; -} - - -/*********************************************************************** -** -*/ REBNATIVE(trace) -/* -***********************************************************************/ -{ - REBVAL *arg = D_ARG(1); - - Check_Security(SYM_DEBUG, POL_READ, 0); - - // The /back option: ON and OFF, or INTEGER! for # of lines: - if (D_REF(2)) { // /back - if (IS_LOGIC(arg)) { - Enable_Backtrace(IS_TRUE(arg)); - } - else if (IS_INTEGER(arg)) { - Trace_Flags = 0; - Display_Backtrace(Int32(arg)); - return R_UNSET; - } - } - else Enable_Backtrace(FALSE); - - // Set the trace level: - if (IS_LOGIC(arg)) { - Trace_Level = IS_TRUE(arg) ? 100000 : 0; - } - else Trace_Level = Int32(arg); - - if (Trace_Level) { - Trace_Flags = 1; - if (D_REF(3)) SET_FLAG(Trace_Flags, 1); // function - Trace_Depth = Eval_Depth() - 1; // subtract current TRACE frame - } - else Trace_Flags = 0; - - return R_UNSET; -} - -static REBINT Init_Depth(void) -{ - // Check the trace depth is ok: - int depth = Eval_Depth() - Trace_Depth; - if (depth < 0 || depth >= Trace_Level) return -1; - if (depth > 10) depth = 10; - Debug_Space(4 * depth); - return depth; -} - -#define CHECK_DEPTH(d) if ((d = Init_Depth()) < 0) return;\ - -void Trace_Line(REBSER *block, REBINT index, REBVAL *value) -{ - int depth; - - if (GET_FLAG(Trace_Flags, 1)) return; // function - if (ANY_FUNC(value)) return; - - CHECK_DEPTH(depth); - - Debug_Fmt_(BOOT_STR(RS_TRACE,1), index+1, value); - if (IS_WORD(value) || IS_GET_WORD(value)) { - value = Get_Var(value); - if (VAL_TYPE(value) < REB_NATIVE) - Debug_Fmt_(BOOT_STR(RS_TRACE,2), value); - else if (VAL_TYPE(value) >= REB_NATIVE && VAL_TYPE(value) <= REB_FUNCTION) - Debug_Fmt_(BOOT_STR(RS_TRACE,3), Get_Type_Name(value), List_Func_Words(value)); - else - Debug_Fmt_(BOOT_STR(RS_TRACE,4), Get_Type_Name(value)); - } - /*if (ANY_WORD(value)) { - word = value; - if (IS_WORD(value)) value = Get_Var(word); - Debug_Fmt_(BOOT_STR(RS_TRACE,2), VAL_WORD_FRAME(word), VAL_WORD_INDEX(word), Get_Type_Name(value)); - } - if (Trace_Stack) Debug_Fmt(BOOT_STR(RS_TRACE,3), DSP, DSF); - else - */ - Debug_Line(); -} - -void Trace_Func(REBVAL *word, REBVAL *value) -{ - int depth; - CHECK_DEPTH(depth); - Debug_Fmt_(BOOT_STR(RS_TRACE,5), Get_Word_Name(word), Get_Type_Name(value)); - if (GET_FLAG(Trace_Flags, 1)) Debug_Values(DS_GET(DS_ARG_BASE+1), DS_ARGC, 20); - else Debug_Line(); -} - -void Trace_Return(REBVAL *word, REBVAL *value) -{ - int depth; - CHECK_DEPTH(depth); - Debug_Fmt_(BOOT_STR(RS_TRACE,6), Get_Word_Name(word)); - Debug_Values(value, 1, 50); -} - -void Trace_Arg(REBINT num, REBVAL *arg, REBVAL *path) -{ - int depth; - if (IS_REFINEMENT(arg) && (!path || IS_END(path))) return; - CHECK_DEPTH(depth); - Debug_Fmt(BOOT_STR(RS_TRACE,6), num+1, arg); -} - - -/*********************************************************************** -** -*/ void Trace_Value(REBINT n, REBVAL *value) -/* -***********************************************************************/ -{ - int depth; - CHECK_DEPTH(depth); - Debug_Fmt(BOOT_STR(RS_TRACE,n), value); -} - -/*********************************************************************** -** -*/ void Trace_String(REBINT n, REBYTE *str, REBINT limit) -/* -***********************************************************************/ -{ - static char tracebuf[64]; - int depth; - CHECK_DEPTH(depth); - memcpy(tracebuf, str, MIN(60, limit)); - Debug_Fmt(BOOT_STR(RS_TRACE,n), tracebuf); -} - - -/*********************************************************************** -** -*/ void Trace_Error(REBVAL *value) -/* -***********************************************************************/ -{ - int depth; - CHECK_DEPTH(depth); - Debug_Fmt(BOOT_STR(RS_TRACE, 10), &VAL_ERR_VALUES(value)->type, &VAL_ERR_VALUES(value)->id); -} - - -/*********************************************************************** -** -*/ REBCNT Push_Func(REBFLG keep, REBSER *block, REBCNT index, REBCNT word, REBVAL *func) -/* -** Push on stack a function call frame as defined in stack.h. -** Optimized to reduce usage of thread globals (TLS). -** Block value must not be NULL (otherwise will cause GC fault). -** -** keep: use current top of stack as the return value; do not push -** a new value for the return. -** -** returns: the stack index for the return value. -** -***********************************************************************/ -{ - REBCNT dsp = DSP; - REBVAL *tos = DS_VALUE(dsp); - REBVAL *ret; - - // Set RETURN slot to its default value: - if (keep) ret = 0, dsp--; - else ret = ++tos; // don't unset it until bottom of this func - - // Save BLOCK current evaluation position and prior DSF; - tos++; - VAL_SET(tos, REB_BLOCK); - VAL_SERIES(tos) = block; - VAL_INDEX(tos) = index; - VAL_BACK(tos) = DSF; - - // Save WORD for function and fake frame for relative arg lookup: - tos++; - VAL_SET(tos, REB_HANDLE); // Was REB_WORD, but GC does not like bad fields. - VAL_WORD_SYM(tos) = word ? word : SYM__APPLY_; - VAL_WORD_INDEX(tos) = -1; // avoid GC access to invalid FRAME above - if (func) { - VAL_WORD_FRAME(tos) = VAL_FUNC_ARGS(func); - // Save FUNC value for safety (spec, args, code): - tos++; - *tos = *func; // the DSF_FUNC - } else { - VAL_WORD_FRAME(tos) = 0; - tos++; - SET_NONE(tos); // the DSF_FUNC - } - - if (ret) SET_UNSET(ret); - - DSP = dsp + DSF_BIAS; - return dsp + 1; -} - - -/*********************************************************************** -** -*/ void Next_Path(REBPVS *pvs) -/* -** Evaluate next part of a path. -** -***********************************************************************/ -{ - REBVAL *path; - REBPEF func; - - // Path must have dispatcher, else return: - func = Path_Dispatch[VAL_TYPE(pvs->value)]; - if (!func) return; // unwind, then check for errors - - pvs->path++; - - //Debug_Fmt("Next_Path: %r/%r", pvs->path-1, pvs->path); - - // object/:field case: - if (IS_GET_WORD(path = pvs->path)) { - pvs->select = Get_Var(path); - if (IS_UNSET(pvs->select)) Trap1(RE_NO_VALUE, path); - } - // object/(expr) case: - else if (IS_PAREN(path)) { - // ?? GC protect stuff !!!!!! stack could expand! - pvs->select = Do_Blk(VAL_SERIES(path), 0); - } - else // object/word and object/value case: - pvs->select = path; - - // Uses selector on the value. - // .path - must be advanced as path is used (modified by func) - // .value - holds currently evaluated path value (modified by func) - // .select - selector on value - // .store - storage (usually TOS) for constructed values - // .setval - non-zero for SET-PATH (set to zero after SET is done) - // .orig - original path for error messages - switch (func(pvs)) { - case PE_OK: - break; - case PE_SET: // only sets if end of path - if (pvs->setval && IS_END(pvs->path+1)) { - *pvs->value = *pvs->setval; - pvs->setval = 0; - } - break; - case PE_NONE: - SET_NONE(pvs->store); - case PE_USE: - pvs->value = pvs->store; - break; - case PE_BAD_SELECT: - Trap2(RE_INVALID_PATH, pvs->orig, pvs->path); - case PE_BAD_SET: - Trap2(RE_BAD_PATH_SET, pvs->orig, pvs->path); - case PE_BAD_RANGE: - Trap_Range(pvs->path); - case PE_BAD_SET_TYPE: - Trap2(RE_BAD_FIELD_SET, pvs->path, Of_Type(pvs->setval)); - } - - if (NOT_END(pvs->path+1)) Next_Path(pvs); -} - - -/*********************************************************************** -** -*/ REBVAL *Do_Path(REBVAL **path_val, REBVAL *val) -/* -** Evaluate a path value. Path_val is updated so -** result can be used for function refinements. -** If val is not zero, then this is a SET-PATH. -** Returns value only if result is a function, -** otherwise the result is on TOS. -** -***********************************************************************/ -{ - REBPVS pvs; - - if (val && THROWN(val)) { - // If unwind/throw value is not coming from TOS, push it. - if (val != DS_TOP) DS_PUSH(val); - return 0; - } - - pvs.setval = val; // Set to this new value - DS_PUSH_NONE; - pvs.store = DS_TOP; // Temp space for constructed results - - // Get first block value: - pvs.path = VAL_BLK_DATA(pvs.orig = *path_val); - - // Lookup the value of the variable: - if (IS_WORD(pvs.path)) { - pvs.value = Get_Var(pvs.path); - if (IS_UNSET(pvs.value)) Trap1(RE_NO_VALUE, pvs.path); - } else pvs.value = pvs.path; //Trap2(RE_INVALID_PATH, pvs.orig, pvs.path); - - // Start evaluation of path: - if (Path_Dispatch[VAL_TYPE(pvs.value)]) { - Next_Path(&pvs); - // Check for errors: - if (NOT_END(pvs.path+1) && !ANY_FUNC(pvs.value)) { - // Only function refinements should get by this line: - Trap2(RE_INVALID_PATH, pvs.orig, pvs.path); - } - } - else if (NOT_END(pvs.path+1) && !ANY_FUNC(pvs.value)) - Trap2(RE_BAD_PATH_TYPE, pvs.orig, Of_Type(pvs.value)); - - // If SET then we can drop result storage created above. - if (val) { - DS_DROP; // on SET, we do not care about returned value - return 0; - } else { - //if (ANY_FUNC(pvs.value) && IS_GET_PATH(pvs.orig)) Debug_Fmt("FUNC %r %r", pvs.orig, pvs.path); - // If TOS was not used, then copy final value back to it: - if (pvs.value != pvs.store) *pvs.store = *pvs.value; - // Return 0 if not function or is :path/word... - if (!ANY_FUNC(pvs.value) || IS_GET_PATH(pvs.orig)) return 0; - *path_val = pvs.path; // return new path (for func refinements) - return pvs.value; // only used for functions - } -} - - -/*********************************************************************** -** -*/ void Pick_Path(REBVAL *value, REBVAL *selector, REBVAL *val) -/* -** Lightweight version of Do_Path used for A_PICK actions. -** Result on TOS. -** -***********************************************************************/ -{ - REBPVS pvs; - REBPEF func; - - pvs.value = value; - pvs.path = 0; - pvs.select = selector; - pvs.setval = val; - DS_PUSH_NONE; - pvs.store = DS_TOP; // Temp space for constructed results - - // Path must have dispatcher, else return: - func = Path_Dispatch[VAL_TYPE(value)]; - if (!func) return; // unwind, then check for errors - - switch (func(&pvs)) { - case PE_OK: - break; - case PE_SET: // only sets if end of path - if (pvs.setval) *pvs.value = *pvs.setval; - break; - case PE_NONE: - SET_NONE(pvs.store); - case PE_USE: - pvs.value = pvs.store; - break; - case PE_BAD_SELECT: - Trap2(RE_INVALID_PATH, pvs.value, pvs.select); - case PE_BAD_SET: - Trap2(RE_BAD_PATH_SET, pvs.value, pvs.select); - break; - } -} - - -#ifdef removed_func -/*********************************************************************** -** -x*/ static REBINT Do_Args_Light(REBVAL *func, REBVAL *path, REBSER *block, REBCNT index) -/* -** Evaluate code block according to the function arg spec. -** Args are pushed onto the data stack in the same order -** as the function frame. Args not evaluated. -** -***********************************************************************/ -{ - REBVAL *args; - REBSER *words; - REBINT ds = 0; // stack argument position - REBINT dsp = DSP + 1; // stack base - REBINT dsf = dsp - DSF_BIAS; - REBVAL *tos; - REBVAL *val; - - if ((dsp + 100) > (REBINT)SERIES_REST(DS_Series)) - Trap0(RE_STACK_OVERFLOW); //Expand_Stack(); - - // If closure, get args from object context: - words = VAL_FUNC_WORDS(func); - //if (IS_CLOSURE(func)) words = FRM_WORD_SERIES(words); - args = BLK_SKIP(words, 1); - - // Fill stack variables with default values: - ds = SERIES_TAIL(words)-1; // length of stack fill below - tos = DS_NEXT; - DSP += ds; - for (; ds > 0; ds--) SET_NONE(tos++); - - // Go thru the word list args: - ds = dsp; - for (; NOT_END(args); args++, ds++) { - - if (index >= BLK_LEN(block)) { - if (!IS_REFINEMENT(args)) - Trap2(RE_NO_ARG, Func_Word(dsf), args); - break; - } - - // Process each argument according to the argument block: - switch (VAL_TYPE(args)) { - case REB_WORD: - case REB_LIT_WORD: - case REB_GET_WORD: - DS_Base[ds] = *BLK_SKIP(block, index); - index++; - break; - case REB_REFINEMENT: - val = BLK_SKIP(block, index); - index++; - if (IS_NONE(val) || IS_FALSE(val)) SET_NONE(&DS_Base[ds]); - else if (IS_LOGIC(val) && VAL_LOGIC(val)) SET_TRUE(&DS_Base[ds]); - else Trap1(RE_BAD_REFINE, args); - break; - } - - // If word words is typed, verify correct argument datatype: - if (!TYPE_CHECK(args, VAL_TYPE(DS_VALUE(ds)))) - Trap3(RE_EXPECT_ARG, Func_Word(dsf), args, Of_Type(DS_VALUE(ds))); - } - - if (path && NOT_END(path)) - Trap2(RE_NO_REFINE, Func_Word(dsf), path); - - return index; -} -#endif - - -/*********************************************************************** -** -*/ static REBINT Do_Args(REBVAL *func, REBVAL *path, REBSER *block, REBCNT index) -/* -** Evaluate code block according to the function arg spec. -** Args are pushed onto the data stack in the same order -** as the function frame. -** -** func: function or path value -** path: refinements or object/function path -** block: current evaluation block -** index: current evaluation index -** -***********************************************************************/ -{ - REBVAL *value; - REBVAL *args; - REBSER *words; - REBINT ds = 0; // stack argument position - REBINT dsp = DSP + 1; // stack base - REBINT dsf = dsp - DSF_BIAS; - REBVAL *tos; - - if (IS_OP(func)) dsf--; // adjust for extra arg - - if ((dsp + 100) > (REBINT)SERIES_REST(DS_Series)) - Trap0(RE_STACK_OVERFLOW); //Expand_Stack(); - - // Get list of words: - words = VAL_FUNC_WORDS(func); - args = BLK_SKIP(words, 1); - ds = SERIES_TAIL(words)-1; // length of stack fill below - //Debug_Fmt("Args: %z", VAL_FUNC_ARGS(func)); - - // If func is operator, first arg is already on stack: - if (IS_OP(func)) { - //if (!TYPE_CHECK(args, VAL_TYPE(DS_VALUE(DSP)))) - // Trap3(RE_EXPECT_ARG, Func_Word(dsf), args, Of_Type(DS_VALUE(ds))); - args++; // skip evaluation, but continue with type check - ds--; // shorten stack fill below - } - - // Fill stack variables with default values: - tos = DS_NEXT; - DSP += ds; - for (; ds > 0; ds--) SET_NONE(tos++); - - // Go thru the word list args: - ds = dsp; - for (; NOT_END(args); args++, ds++) { - - //if (Trace_Flags) Trace_Arg(ds - dsp, args, path); - - // Process each formal argument: - switch (VAL_TYPE(args)) { - - case REB_WORD: // WORD - Evaluate next value - index = Do_Next(block, index, IS_OP(func)); - // THROWN is handled after the switch. - if (index == END_FLAG) Trap2(RE_NO_ARG, Func_Word(dsf), args); - DS_Base[ds] = *DS_POP; - break; - - case REB_LIT_WORD: // 'WORD - Just get next value - if (index < BLK_LEN(block)) { - value = BLK_SKIP(block, index); - if (IS_PAREN(value) || IS_GET_WORD(value) || IS_GET_PATH(value)) { - index = Do_Next(block, index, IS_OP(func)); - // THROWN is handled after the switch. - DS_Base[ds] = *DS_POP; - } - else { - index++; - DS_Base[ds] = *value; - } - } else - SET_UNSET(&DS_Base[ds]); // allowed to be none - break; - - case REB_GET_WORD: // :WORD - Get value - if (index < BLK_LEN(block)) { - DS_Base[ds] = *BLK_SKIP(block, index); - index++; - } else - SET_UNSET(&DS_Base[ds]); // allowed to be none - break; -/* - value = BLK_SKIP(block, index); - index++; - if (IS_WORD(value) && VAL_WORD_FRAME(value)) value = Get_Var(value); - DS_Base[ds] = *value; -*/ - case REB_REFINEMENT: // /WORD - Function refinement - if (!path || IS_END(path)) return index; - if (IS_WORD(path)) { - // Optimize, if the refinement is the next arg: - if (SAME_SYM(path, args)) { - SET_TRUE(DS_VALUE(ds)); // set refinement stack value true - path++; // remove processed refinement - continue; - } - // Refinement out of sequence, resequence arg order: -more_path: - ds = dsp; - args = BLK_SKIP(words, 1); - for (; NOT_END(args); args++, ds++) { - if (IS_REFINEMENT(args) && VAL_WORD_CANON(args) == VAL_WORD_CANON(path)) { - SET_TRUE(DS_VALUE(ds)); // set refinement stack value true - path++; // remove processed refinement - break; - } - } - // Was refinement found? If not, error: - if (IS_END(args)) Trap2(RE_NO_REFINE, Func_Word(dsf), path); - continue; - } - else Trap1(RE_BAD_REFINE, path); - break; - - case REB_SET_WORD: // WORD: - reserved for special features - default: - Trap_Arg(args); - } - - if (THROWN(DS_VALUE(ds))) { - // Store THROWN value in TOS, so that Do_Next can handle it. - *DS_TOP = *DS_VALUE(ds); - return index; - } - - // If word is typed, verify correct argument datatype: - if (!TYPE_CHECK(args, VAL_TYPE(DS_VALUE(ds)))) - Trap3(RE_EXPECT_ARG, Func_Word(dsf), args, Of_Type(DS_VALUE(ds))); - } - - // Hack to process remaining path: - if (path && NOT_END(path)) goto more_path; - // Trap2(RE_NO_REFINE, Func_Word(dsf), path); - - return index; -} - - -/*********************************************************************** -** -*/ void Do_Signals(void) -/* -** Special events to process during evaluation. -** Search for SET_SIGNAL to find them. -** -***********************************************************************/ -{ - REBCNT sigs; - REBCNT mask; - - // Accumulate evaluation counter and reset countdown: - if (Eval_Count <= 0) { - //Debug_Num("Poll:", (REBINT) Eval_Cycles); - Eval_Cycles += Eval_Dose - Eval_Count; - Eval_Count = Eval_Dose; - if (Eval_Limit != 0 && Eval_Cycles > Eval_Limit) - Check_Security(SYM_EVAL, POL_EXEC, 0); - } - - if (!(Eval_Signals & Eval_Sigmask)) return; - - // Be careful of signal loops! EG: do not PRINT from here. - sigs = Eval_Signals & (mask = Eval_Sigmask); - Eval_Sigmask = 0; // avoid infinite loop - //Debug_Num("Signals:", Eval_Signals); - - // Check for recycle signal: - if (GET_FLAG(sigs, SIG_RECYCLE)) { - CLR_SIGNAL(SIG_RECYCLE); - Recycle(); - } - -#ifdef NOT_USED_INVESTIGATE - if (GET_FLAG(sigs, SIG_EVENT_PORT)) { // !!! Why not used? - CLR_SIGNAL(SIG_EVENT_PORT); - Awake_Event_Port(); - } -#endif - - // Escape only allowed after MEZZ boot (no handlers): - if (GET_FLAG(sigs, SIG_ESCAPE) && PG_Boot_Phase >= BOOT_MEZZ) { - CLR_SIGNAL(SIG_ESCAPE); - Eval_Sigmask = mask; - Halt_Code(RE_HALT, 0); // Throws! - } - - Eval_Sigmask = mask; -} - - -/*********************************************************************** -** -*/ REBCNT Do_Next(REBSER *block, REBCNT index, REBFLG op) -/* -** Evaluate the code block until we have: -** 1. An irreducible value (return next index) -** 2. Reached the end of the block (return END_FLAG) -** 3. Encountered an error -** -** Index is a zero-based index into the block. -** Op indicates infix operator is being evaluated (precedence); -** The value (or error) is placed on top of the data stack. -** -***********************************************************************/ -{ - REBVAL *value; - REBVAL *word = 0; - REBINT ftype; - REBCNT dsf; - - //CHECK_MEMORY(1); - CHECK_STACK(&value); - if ((DSP + 20) > (REBINT)SERIES_REST(DS_Series)) Expand_Stack(STACK_MIN); //Trap0(RE_STACK_OVERFLOW); - if (--Eval_Count <= 0 || Eval_Signals) Do_Signals(); - - value = BLK_SKIP(block, index); - //if (Trace_Flags) Trace_Eval(block, index); - -reval: - if (Trace_Flags) Trace_Line(block, index, value); - - //getchar(); - switch (EVAL_TYPE(value)) { - - case ET_WORD: - value = Get_Var(word = value); - if (IS_UNSET(value)) Trap1(RE_NO_VALUE, word); - if (VAL_TYPE(value) >= REB_NATIVE && VAL_TYPE(value) <= REB_FUNCTION) goto reval; // || IS_LIT_PATH(value) - DS_PUSH(value); - if (IS_LIT_WORD(value)) VAL_SET(DS_TOP, REB_WORD); - if (IS_FRAME(value)) Init_Obj_Value(DS_TOP, VAL_WORD_FRAME(word)); - index++; - break; - - case ET_SELF: - DS_PUSH(value); - index++; - break; - - case ET_SET_WORD: - word = value; - //if (!VAL_WORD_FRAME(word)) Trap1(RE_NOT_DEFINED, word); (checked in set_var) - index = Do_Next(block, index+1, 0); - // THROWN is handled in Set_Var. - if (index == END_FLAG || VAL_TYPE(DS_TOP) <= REB_UNSET) Trap1(RE_NEED_VALUE, word); - Set_Var(word, DS_TOP); - //Set_Word(word, DS_TOP); // (value stays on stack) - //Dump_Frame(Main_Frame); - break; - - case ET_FUNCTION: -eval_func0: - ftype = VAL_TYPE(value) - REB_NATIVE; // function type - if (!word) word = ROOT_NONAME; - dsf = Push_Func(FALSE, block, index, VAL_WORD_SYM(word), value); -eval_func: - value = DSF_FUNC(dsf); // a safe copy of function - if (VAL_TYPE(value) < REB_NATIVE) { - Debug_Value(word, 4, 0); - Dump_Values(value, 4); - } - index = Do_Args(value, 0, block, index+1); // uses old DSF, updates DSP -eval_func2: - // Evaluate the function: - DSF = dsf; // Set new DSF - if (!THROWN(DS_TOP)) { - if (Trace_Flags) Trace_Func(word, value); - Func_Dispatch[ftype](value); - } - else { - *DS_RETURN = *DS_TOP; - } - - // Reset the stack to prior function frame, but keep the - // return value (function result) on the top of the stack. - DSP = dsf; - DSF = PRIOR_DSF(dsf); - if (Trace_Flags) Trace_Return(word, DS_TOP); - - // The return value is a FUNC that needs to be re-evaluated. - if (VAL_GET_OPT(DS_TOP, OPTS_REVAL) && ANY_FUNC(DS_TOP)) { - value = DS_POP; // WARNING: value is volatile on TOS1 ! - word = Get_Type_Word(VAL_TYPE(value)); - index--; // Backup block index to re-evaluate. - if (IS_OP(value)) Trap_Type(value); // not allowed - goto eval_func0; - } - break; - - case ET_OPERATOR: - // An operator can be native or function, so its true evaluation - // datatype is stored in the extended flags part of the value. - if (!word) word = ROOT_NONAME; - if (DSP <= 0 || index == 0) Trap1(RE_NO_OP_ARG, word); - ftype = VAL_GET_EXT(value) - REB_NATIVE; - dsf = Push_Func(TRUE, block, index, VAL_WORD_SYM(word), value); // TOS has first arg - DS_PUSH(DS_VALUE(dsf)); // Copy prior to first argument - goto eval_func; - - case ET_PATH: // PATH, SET_PATH - ftype = VAL_TYPE(value); - word = value; // a path - //index++; // now done below with +1 - - //Debug_Fmt("t: %r", value); - if (ftype == REB_SET_PATH) { - index = Do_Next(block, index+1, 0); - // THROWN is handled in Do_Path. - if (index == END_FLAG || VAL_TYPE(DS_TOP) <= REB_UNSET) Trap1(RE_NEED_VALUE, word); - Do_Path(&word, DS_TOP); - } else { - // Can be a path or get-path: - value = Do_Path(&word, 0); // returns in word the path item, DS_TOP has value - //Debug_Fmt("v: %r", value); - // Value returned only for functions that need evaluation (but not GET_PATH): - if (value && ANY_FUNC(value)) { - if (IS_OP(value)) Trap_Type(value); // (because prior value is wiped out above) - // Can be object/func or func/refinements or object/func/refinement: - dsf = Push_Func(TRUE, block, index, VAL_WORD_SYM(word), value); // Do not unset TOS1 (it is the value) - value = DS_TOP; - index = Do_Args(value, word+1, block, index+1); - ftype = VAL_TYPE(value)-REB_NATIVE; - goto eval_func2; - } else - index++; - } - break; - - case ET_PAREN: - DO_BLK(value); - DSP++; // keep it on top - index++; - break; - - case ET_LIT_WORD: - DS_PUSH(value); - VAL_SET(DS_TOP, REB_WORD); - index++; - break; - - case ET_GET_WORD: - DS_PUSH(Get_Var(value)); - index++; - break; - - case ET_LIT_PATH: - DS_PUSH(value); - VAL_SET(DS_TOP, REB_PATH); - index++; - break; - - case ET_END: - return END_FLAG; - - default: - //Debug_Fmt("Bad eval: %d %s", VAL_TYPE(value), Get_Type_Name(value)); - Crash(RP_BAD_EVALTYPE, VAL_TYPE(value)); - //return -index; - } - - // If normal eval (not higher precedence of infix op), check for op: - if (!op) { - value = BLK_SKIP(block, index); - if (IS_WORD(value) && VAL_WORD_FRAME(value) && IS_OP(Get_Var(value))) - goto reval; - } - - return index; -} - - -/*********************************************************************** -** -*/ REBVAL *Do_Blk(REBSER *block, REBCNT index) -/* -** Evaluate a block from the index position specified. -** Return the result (a pointer to TOS+1). -** -***********************************************************************/ -{ - REBVAL *tos = 0; -#if (ALEVEL>1) - REBINT start = DSP; -// REBCNT gcd = GC_Disabled; -#endif - - CHECK_MEMORY(4); // Be sure we don't go far with a problem. - - ASSERT1(block->info, RP_GC_OF_BLOCK); - - while (index < BLK_LEN(block)) { - index = Do_Next(block, index, 0); - tos = DS_POP; - if (THROWN(tos)) break; - } - // If block was empty: - if (!tos) {tos = DS_NEXT; SET_UNSET(tos);} - - if (start != DSP || tos != &DS_Base[start+1]) Trap0(RE_MISSING_ARG); - -// ASSERT2(gcd == GC_Disabled, RP_GC_STUCK); - - // Restore data stack and return value: -// ASSERT2((tos == 0 || (start == DSP && tos == &DS_Base[start+1])), RP_TOS_DRIFT); -// if (!tos) {tos = DS_NEXT; SET_UNSET(tos);} - return tos; -} - - -/*********************************************************************** -** -*/ REBVAL *Do_Block_Value_Throw(REBVAL *block) -/* -** A common form of Do_Blk(). Takes block value. Handles throw. -** -***********************************************************************/ -{ - REBSER *series = VAL_SERIES(block); - REBCNT index = VAL_INDEX(block); - REBVAL *tos = 0; - REBINT start = DSP; - - while (index < BLK_LEN(series)) { - index = Do_Next(series, index, 0); - tos = DS_POP; - if (THROWN(tos)) Throw_Break(tos); - } - // If series was empty: - if (!tos) {tos = DS_NEXT; SET_UNSET(tos);} - - if (start != DSP || tos != &DS_Base[start+1]) Trap0(RE_MISSING_ARG); - - return tos; -} - - -/*********************************************************************** -** -*/ REBFLG Try_Block(REBSER *block, REBCNT index) -/* -** Evaluate a block from the index position specified in the value. -** TOS+1 holds the result. -** -***********************************************************************/ -{ - REBOL_STATE state; - REBVAL *tos; - - PUSH_STATE(state, Saved_State); - if (SET_JUMP(state)) { - POP_STATE(state, Saved_State); - Catch_Error(DS_NEXT); // Stores error value here - return TRUE; - } - SET_STATE(state, Saved_State); - - tos = 0; - while (index < BLK_LEN(block)) { - index = Do_Next(block, index, 0); - tos = DS_POP; - if (THROWN(tos)) break; - } - if (!tos) {tos = DS_NEXT; SET_UNSET(tos);} - - // Restore data stack and return value at TOS+1: - DS_Base[state.dsp+1] = *tos; - POP_STATE(state, Saved_State); - - return FALSE; -} - - -/*********************************************************************** -** -*/ void Reduce_Block(REBSER *block, REBCNT index, REBVAL *into) -/* -** Reduce block from the index position specified in the value. -** Collect all values from stack and make them a block. -** -***********************************************************************/ -{ - REBINT start = DSP + 1; - - while (index < BLK_LEN(block)) { - index = Do_Next(block, index, 0); - if (THROWN(DS_TOP)) return; - } - - Copy_Stack_Values(start, into); -} - - -/*********************************************************************** -** -*/ void Reduce_Only(REBSER *block, REBCNT index, REBVAL *words, REBVAL *into) -/* -** Reduce only words and paths not found in word list. -** -***********************************************************************/ -{ - REBINT start = DSP + 1; - REBVAL *val; - REBVAL *v; - REBSER *ser = 0; - REBCNT idx = 0; - - if (IS_BLOCK(words)) { - ser = VAL_SERIES(words); - idx = VAL_INDEX(words); - } - - for (val = BLK_SKIP(block, index); NOT_END(val); val++) { - if (IS_WORD(val)) { - // Check for keyword: - if (ser && NOT_FOUND != Find_Word(ser, idx, VAL_WORD_CANON(val))) { - DS_PUSH(val); - continue; - } - v = Get_Var(val); - DS_PUSH(v); - } - else if (IS_PATH(val)) { - if (ser) { - // Check for keyword/path: - v = VAL_BLK_DATA(val); - if (IS_WORD(v)) { - if (NOT_FOUND != Find_Word(ser, idx, VAL_WORD_CANON(v))) { - DS_PUSH(val); - continue; - } - } - } - v = val; - Do_Path(&v, 0); // pushes val on stack - } - else DS_PUSH(val); - // No need to check for unwinds (THROWN) here, because unwinds should - // never be accessible via words or paths. - } - - Copy_Stack_Values(start, into); -} - - -/*********************************************************************** -** -*/ void Reduce_Block_No_Set(REBSER *block, REBCNT index, REBVAL *into) -/* -***********************************************************************/ -{ - REBINT start = DSP + 1; - REBVAL *val; - - while (index < BLK_LEN(block)) { - if (IS_SET_WORD(val = BLK_SKIP(block, index))) { - DS_PUSH(val); - index++; - } else - index = Do_Next(block, index, 0); - if (THROWN(DS_TOP)) return; - } - - Copy_Stack_Values(start, into); -} - - -/*********************************************************************** -** -*/ void Reduce_Type_Stack(REBSER *block, REBCNT index, REBCNT type) -/* -** Reduce a block of words/paths that are of the specified type. -** Return them on the stack. The change in TOS is the length. -** -***********************************************************************/ -{ - //REBINT start = DSP + 1; - REBVAL *val; - REBVAL *v; - - // Lookup words and paths and push values on stack: - for (val = BLK_SKIP(block, index); NOT_END(val); val++) { - if (IS_WORD(val)) { - v = Get_Var(val); - if (VAL_TYPE(v) == type) DS_PUSH(v); - } - else if (IS_PATH(val)) { - v = val; - if (!Do_Path(&v, 0)) { // pushes val on stack - if (VAL_TYPE(DS_TOP) != type) DS_DROP; - } - } - else if (VAL_TYPE(val) == type) DS_PUSH(val); - // !!! check stack size - } - SET_END(&DS_Base[++DSP]); // in case caller needs it - - //block = Copy_Values(DS_Base + start, DSP - start + 1); - //DSP = start; - //return block; -} - - -/*********************************************************************** -** -*/ void Reduce_In_Frame(REBSER *frame, REBVAL *values) -/* -** Reduce a block with simple lookup in the context. -** Only words in that context are valid (e.g. error object). -** All values are left on the stack. No copy is made. -** -***********************************************************************/ -{ - REBVAL *val; - - for (; NOT_END(values); values++) { - switch (VAL_TYPE(values)) { - case REB_WORD: - case REB_SET_WORD: - case REB_GET_WORD: - if (NZ(val = Find_Word_Value(frame, VAL_WORD_SYM(values)))) { - DS_PUSH(val); - break; - } // Unknown in context, fall below, use word as value. - case REB_LIT_WORD: - DS_PUSH(values); - VAL_SET(DS_TOP, REB_WORD); - break; - default: - DS_PUSH(values); - } - } -} - - -/*********************************************************************** -** -*/ void Compose_Block(REBVAL *block, REBFLG deep, REBFLG only, REBVAL *into) -/* -** Compose a block from a block of un-evaluated values and -** paren blocks that are evaluated. Stack holds temp values, -** which also protects them from GC along the way. -** -** deep - recurse into sub-blocks -** only - parens that return blocks are kept as blocks -** -** Returns result as a block on top of stack. -** -***********************************************************************/ -{ - REBVAL *value; - REBINT start = DSP + 1; - - for (value = VAL_BLK_DATA(block); NOT_END(value); value++) { - if (IS_PAREN(value)) { - // Eval the paren, and leave result on the stack: - DO_BLK(value); - DSP++; // !!!DSP temp - if (THROWN(DS_TOP)) return; - - // If result is a block, and not /only, insert its contents: - if (IS_BLOCK(DS_TOP) && !only) { - // Append series to the stack: - SERIES_TAIL(DS_Series) = DSP; // overwrites TOP value - Append_Series(DS_Series, (REBYTE *)VAL_BLK_DATA(DS_TOP), VAL_BLK_LEN(DS_TOP)); - DSP = SERIES_TAIL(DS_Series) - 1; - // Note: stack may have moved - } - else if (IS_UNSET(DS_TOP)) DS_DROP; // remove unset values - } - else if (deep) { - if (IS_BLOCK(value)) Compose_Block(value, TRUE, only, 0); - else { - DS_PUSH(value); - if (ANY_BLOCK(value)) // Include PATHS - VAL_SERIES(DS_TOP) = Copy_Block(VAL_SERIES(value), 0); - } - } - else DS_PUSH(value); - } - - Copy_Stack_Values(start, into); -} - - -/*********************************************************************** -** -*/ void Apply_Block(REBVAL *func, REBVAL *args, REBFLG reduce) -/* -** Result is on top of stack. -** -***********************************************************************/ -{ - REBINT ftype = VAL_TYPE(func) - REB_NATIVE; // function type - REBSER *block = VAL_SERIES(args); - REBCNT index = VAL_INDEX(args); - REBCNT dsf; - - REBSER *words; - REBINT len; - REBINT n; - REBINT start; - REBVAL *val; - - if (index > SERIES_TAIL(block)) index = SERIES_TAIL(block); - - // Push function frame: - dsf = Push_Func(0, block, index, 0, func); - func = DSF_FUNC(dsf); // for safety - - // Determine total number of args: - words = VAL_FUNC_WORDS(func); - len = words ? SERIES_TAIL(words)-1 : 0; - start = DSP+1; - - // Gather arguments: - if (reduce) { - // Reduce block contents to stack: - n = 0; - while (index < BLK_LEN(block)) { - index = Do_Next(block, index, 0); - if (THROWN(DS_TOP)) return; - n++; - } - if (n > len) DSP = start + len; - } - else { - // Copy block contents to stack: - n = VAL_BLK_LEN(args); - if (len < n) n = len; - memcpy(&DS_Base[start], BLK_SKIP(block, index), n * sizeof(REBVAL)); - DSP = start + n - 1; - } - - // Pad out missing args: - for (; n < len; n++) DS_PUSH_NONE; - - // Validate arguments: - if (words) { - val = DS_Base + start; - for (args = BLK_SKIP(words, 1); NOT_END(args);) { - // If arg is refinement, determine its state: - if (IS_REFINEMENT(args)) { - if (IS_FALSE(val)) { - SET_NONE(val); // ++ ok for none - while (TRUE) { - val++; - args++; - if (IS_END(args) || IS_REFINEMENT(args)) break; - SET_NONE(val); - } - continue; - } - SET_TRUE(val); - } - // If arg is typed, verify correct argument datatype: - if (!TYPE_CHECK(args, VAL_TYPE(val))) - Trap3(RE_EXPECT_ARG, Func_Word(dsf), args, Of_Type(val)); - args++; - val++; - } - } - - // Evaluate the function: - DSF = dsf; - Func_Dispatch[ftype](func); - DSP = dsf; - DSF = PRIOR_DSF(dsf); -} - - -/*********************************************************************** -** -*/ REBVAL *Apply_Function(REBSER *wblk, REBCNT widx, REBVAL *func, va_list args) -/* -** Applies function from args provided by C call. Zero terminated. -** Result is EXTREMELY VOLATILE - a stack value above the DSP. -** -** wblk - where block (where we were called) -** widx - where index (position in above block) -** func - function to call -** args - list of function args (null terminated) -** -***********************************************************************/ -{ - REBCNT dsf; - REBSER *words; - REBCNT ds; - REBVAL *arg; - - dsf = Push_Func(0, wblk, widx, 0, func); - func = DSF_FUNC(dsf); // for safety - words = VAL_FUNC_WORDS(func); - ds = SERIES_TAIL(words)-1; // length of stack fill below - - // Gather arguments from C stack: - for (; ds > 0; ds--) { - arg = va_arg(args, REBVAL*); // get value - if (arg) DS_PUSH(arg); // push it; no type check - else break; - } - for (; ds > 0; ds--) DS_PUSH_NONE; // unused slots - - // Evaluate the function: - DSF = dsf; - Func_Dispatch[VAL_TYPE(func) - REB_NATIVE](func); - DSF = PRIOR_DSF(dsf); - DSP = dsf-1; - - // Return resulting value from TOS1. But note: - // EXTREMELY VOLATILE - use or copy quickly - // before next evaluation, GC, or anything else! - return DS_VALUE(dsf); -} - - -/*********************************************************************** -** -*/ REBVAL *Apply_Func(REBSER *where, REBVAL *func, ...) -/* -** Applies function from args provided by C call. Zero terminated. -** Result is EXTREMELY VOLATILE - a stack value above the DSP. -** -***********************************************************************/ -{ - REBVAL *value; - va_list args; - - if (!ANY_FUNC(func)) Trap_Arg(func); - if (!where) where = VAL_FUNC_BODY(func); // something/anything ?!! - - va_start(args, func); - value = Apply_Function(where, 0, func, args); - va_end(args); - - return value; -} - - -/*********************************************************************** -** -*/ REBVAL *Do_Sys_Func(REBCNT inum, ...) -/* -** Evaluates a SYS function and TOS1 contains -** the result (VOLATILE). Uses current stack frame location -** as the next location (e.g. for error output). -** -***********************************************************************/ -{ - REBVAL *value; - va_list args; - REBSER *blk = 0; - REBCNT idx = 0; - - if (DSF) { - value = DSF_BACK(DSF); - blk = VAL_SERIES(value); - idx = VAL_INDEX(value); - } - - value = FRM_VALUE(Sys_Context, inum); - if (!ANY_FUNC(value)) Trap1(RE_BAD_SYS_FUNC, value); - if (!DSF) blk = VAL_FUNC_BODY(value); - - va_start(args, inum); - value = Apply_Function(blk, idx, value, args); - va_end(args); - - return value; -} - - -/*********************************************************************** -** -*/ void Do_Construct(REBVAL *value) -/* -** Do a block with minimal evaluation and no evaluation of -** functions. Used for things like script headers where security -** is important. -** -** Handles cascading set words: word1: word2: value -** -***********************************************************************/ -{ - REBVAL *temp; - REBINT ssp; // starting stack pointer - - DS_PUSH_NONE; - temp = DS_TOP; - ssp = DSP; - - for (; NOT_END(value); value++) { - if (IS_SET_WORD(value)) { - // Next line not needed, because SET words are ALWAYS in frame. - //if (VAL_WORD_INDEX(value) > 0 && VAL_WORD_FRAME(value) == frame) - DS_PUSH(value); - } else { - // Get value: - if (IS_WORD(value)) { - switch (VAL_WORD_CANON(value)) { - case SYM_NONE: - SET_NONE(temp); - break; - case SYM_TRUE: - case SYM_ON: - case SYM_YES: - SET_TRUE(temp); - break; - case SYM_FALSE: - case SYM_OFF: - case SYM_NO: - SET_FALSE(temp); - break; - default: - *temp = *value; - VAL_SET(temp, REB_WORD); - } - } - else if (IS_LIT_WORD(value)) { - *temp = *value; - VAL_SET(temp, REB_WORD); - } - else if (IS_LIT_PATH(value)) { - *temp = *value; - VAL_SET(temp, REB_PATH); - } - else if (VAL_TYPE(value) >= REB_NONE) { // all valid values - *temp = *value; - } - else - SET_NONE(temp); - - // Set prior set-words: - while (DSP > ssp) { - Set_Var(DS_TOP, temp); - DS_DROP; - } - } - } - DS_DROP; // temp -} - - -/*********************************************************************** -** -*/ void Do_Min_Construct(REBVAL *value) -/* -** Do no evaluation of the set values. -** -***********************************************************************/ -{ - REBVAL *temp; - REBINT ssp; // starting stack pointer - - DS_PUSH_NONE; - temp = DS_TOP; - ssp = DSP; - - for (; NOT_END(value); value++) { - if (IS_SET_WORD(value)) { - // Next line not needed, because SET words are ALWAYS in frame. - //if (VAL_WORD_INDEX(value) > 0 && VAL_WORD_FRAME(value) == frame) - DS_PUSH(value); - } else { - // Get value: - *temp = *value; - // Set prior set-words: - while (DSP > ssp) { - Set_Var(DS_TOP, temp); - DS_DROP; - } - } - } - DS_DROP; // temp -} - - -/*********************************************************************** -** -*/ REBVAL *Do_Bind_Block(REBSER *frame, REBVAL *block) -/* -** Bind deep and evaluate a block value in a given context. -** Result is left on top of data stack (may be an error). -** -***********************************************************************/ -{ - Bind_Block(frame, VAL_BLK_DATA(block), BIND_DEEP); - return DO_BLK(block); -} - - -/*********************************************************************** -** -*/ void Reduce_Bind_Block(REBSER *frame, REBVAL *block, REBCNT binding) -/* -** Bind deep and reduce a block value in a given context. -** Result is left on top of data stack (may be an error). -** -***********************************************************************/ -{ - Bind_Block(frame, VAL_BLK_DATA(block), binding); - Reduce_Block(VAL_SERIES(block), VAL_INDEX(block), 0); -} - - -/*********************************************************************** -** -*/ REBOOL Try_Block_Halt(REBSER *block, REBCNT index) -/* -** Evaluate a block from the index position specified in the value, -** with a handler for quit conditions (QUIT, HALT) set up. -** -***********************************************************************/ -{ - REBOL_STATE state; - REBVAL *val; -// static D = 0; -// int depth = D++; - -// Debug_Fmt("Set Halt %d", depth); - - PUSH_STATE(state, Halt_State); - if (SET_JUMP(state)) { -// Debug_Fmt("Throw Halt %d", depth); - POP_STATE(state, Halt_State); - Catch_Error(DS_NEXT); // Stores error value here - return TRUE; - } - SET_STATE(state, Halt_State); - - SAVE_SERIES(block); - val = Do_Blk(block, index); - UNSAVE_SERIES(block); - - DS_Base[state.dsp+1] = *val; - POP_STATE(state, Halt_State); - -// Debug_Fmt("Ret Halt %d", depth); - - return FALSE; -} - - -/*********************************************************************** -** -*/ REBVAL *Do_String(REBYTE *text, REBCNT flags) -/* -** Do a string. Convert it to code, then evaluate it with -** the ability to catch errors and also alow HALT if needed. -** -***********************************************************************/ -{ - REBOL_STATE state; - REBSER *code; - REBVAL *val; - REBSER *rc; - REBCNT len; - REBVAL vali; - - PUSH_STATE(state, Halt_State); - if (SET_JUMP(state)) { - POP_STATE(state, Halt_State); - Saved_State = Halt_State; - Catch_Error(DS_NEXT); // Stores error value here - val = Get_System(SYS_STATE, STATE_LAST_ERROR); // Save it for EXPLAIN - *val = *DS_NEXT; - if (VAL_ERR_NUM(val) == RE_QUIT) { - OS_EXIT(VAL_INT32(VAL_ERR_VALUE(DS_NEXT))); // console quit - } - return val; - } - SET_STATE(state, Halt_State); - // Use this handler for both, halt conditions (QUIT, HALT) and error - // conditions. As this is a top-level handler, simply overwriting - // Saved_State is safe. - Saved_State = Halt_State; - - code = Scan_Source(text, LEN_BYTES(text)); - SAVE_SERIES(code); - - // Bind into lib or user spaces? - if (flags) { - // Top words will be added to lib: - Bind_Block(Lib_Context, BLK_HEAD(code), BIND_SET); - Bind_Block(Lib_Context, BLK_HEAD(code), BIND_DEEP); - } - else { - rc = VAL_OBJ_FRAME(Get_System(SYS_CONTEXTS, CTX_USER)); - len = rc->tail; - Bind_Block(rc, BLK_HEAD(code), BIND_ALL | BIND_DEEP); - SET_INTEGER(&vali, len); - Resolve_Context(rc, Lib_Context, &vali, FALSE, 0); - } - - Do_Blk(code, 0); - UNSAVE_SERIES(code); - - POP_STATE(state, Halt_State); - Saved_State = Halt_State; - - return DS_NEXT; // result is volatile -} - - -/*********************************************************************** -** -*/ void Halt_Code(REBINT kind, REBVAL *arg) -/* -** Halts execution by throwing back to the above Do_String. -** Kind is RE_HALT or RE_QUIT -** Arg is the optional return value. -** -** Future versions may not reset the stack, but leave it as is -** to allow for examination and a RESUME operation. -** -***********************************************************************/ -{ - REBVAL *err = TASK_THIS_ERROR; - - if (!Halt_State) return; - - if (arg) { - if (IS_NONE(arg)) { - SET_INTEGER(TASK_THIS_VALUE, 0); - } else - *TASK_THIS_VALUE = *arg; // save the value - } else { - SET_NONE(TASK_THIS_VALUE); - } - - VAL_SET(err, REB_ERROR); - VAL_ERR_NUM(err) = kind; - VAL_ERR_VALUE(err) = TASK_THIS_VALUE; - VAL_ERR_SYM(err) = 0; - - longjmp(*Halt_State, 1); -} - - -/*********************************************************************** -** -*/ void Call_Func(REBVAL *func_val) -/* -** Calls a REBOL function from C code. -** -** Setup: -** Before calling this, the caller must setup the stack and -** provide the function arguments on the stack. Any missing -** args will be set to NONE. -** -** Return: -** On return, the stack remains as-is. The caller must reset -** the DSP and DSF values. -** -***********************************************************************/ -{ - REBINT n; - - // Caller must: Prep_Func + Args above - VAL_WORD_FRAME(DSF_WORD(DSF)) = VAL_FUNC_ARGS(func_val); - n = DS_ARGC - (SERIES_TAIL(VAL_FUNC_WORDS(func_val)) - 1); - for (; n > 0; n--) DS_PUSH_NONE; - Func_Dispatch[VAL_TYPE(func_val)-REB_NATIVE](func_val); - // Caller must: pop stack back -} - - -/*********************************************************************** -** -*/ void Redo_Func(REBVAL *func_val) -/* -** Trampoline a function, restacking arguments as needed. -** -** Setup: -** The source for arguments is the existing stack frame, -** or a prior stack frame. (Prep_Func + Args) -** -** Return: -** On return, the stack remains as-is. The caller must reset -** the DSP and DSF values. -** -***********************************************************************/ -{ - REBSER *wsrc; // words of source func - REBSER *wnew; // words of target func - REBCNT isrc; // index position in source frame - REBCNT inew; // index position in target frame - REBVAL *word; - REBVAL *word2; - - //!!! NEEDS to check stack for overflow - //!!! Should check datatypes for new arg passing! - - wsrc = VAL_FUNC_WORDS(DSF_FUNC(DSF)); - wnew = VAL_FUNC_WORDS(func_val); - - // Foreach arg of the target, copy to source until refinement. - for (isrc = inew = 1; inew < BLK_LEN(wnew); inew++, isrc++) { - word = BLK_SKIP(wnew, inew); - if (isrc > BLK_LEN(wsrc)) isrc = BLK_LEN(wsrc); - - switch (VAL_TYPE(word)) { - case REB_WORD: - case REB_LIT_WORD: - case REB_GET_WORD: - if (VAL_TYPE(word) == VAL_TYPE(BLK_SKIP(wsrc, isrc))) break; - DS_PUSH_NONE; - continue; - //Trap_Arg(word); - - // At refinement, search for it in source, then continue with words. - case REB_REFINEMENT: - // Are we aligned on the refinement already? (a common case) - word2 = BLK_SKIP(wsrc, isrc); - if (!(IS_REFINEMENT(word2) && VAL_BIND_CANON(word2) == VAL_BIND_CANON(word))) { - // No, we need to search for it: - for (isrc = 1; isrc < BLK_LEN(wsrc); isrc++) { - word2 = BLK_SKIP(wsrc, isrc); - if (IS_REFINEMENT(word2) && VAL_BIND_CANON(word2) == VAL_BIND_CANON(word)) goto push_arg; - } - DS_PUSH_NONE; - continue; - //if (isrc >= BLK_LEN(wsrc)) Trap_Arg(word); - } - break; - - default: - ASSERT1(FALSE, RP_ASSERTS); - } -push_arg: - DS_PUSH(DSF_ARGS(DSF, isrc)); - //Debug_Fmt("Arg %d -> %d", isrc, inew); - } - - // Copy values to prior location: - inew--; - // memory areas may overlap, so use memmove and not memcpy! - memmove(DS_ARG(1), DS_TOP-(inew-1), inew * sizeof(REBVAL)); - DSP = DS_ARG_BASE + inew; // new TOS - //Dump_Block(DS_ARG(1), inew); - VAL_WORD_FRAME(DSF_WORD(DSF)) = VAL_FUNC_ARGS(func_val); - *DSF_FUNC(DSF) = *func_val; - Func_Dispatch[VAL_TYPE(func_val)-REB_NATIVE](func_val); -} - - -/*********************************************************************** -** -*/ REBVAL *Get_Simple_Value(REBVAL *val) -/* -** Does easy lookup, else just returns the value as is. -** Note for paths value is left on stack. -** -***********************************************************************/ -{ - if (IS_WORD(val) || IS_GET_WORD(val)) - val = Get_Var(val); - else if (IS_PATH(val) || IS_GET_PATH(val)) { //val = Get_Path_Var(val); - REBVAL *v = val; - DS_PUSH_NONE; - Do_Path(&v, 0); - val = DS_TOP; - } - - return val; -} - - -/*********************************************************************** -** -*/ REBSER *Resolve_Path(REBVAL *path, REBCNT *index) -/* -** Given a path, return a context and index for its terminal. -** -***********************************************************************/ -{ - REBVAL *sel; // selector - REBVAL *val; - REBSER *blk; - REBCNT i; - - if (VAL_TAIL(path) < 2) return 0; - blk = VAL_SERIES(path); - sel = BLK_HEAD(blk); - if (!ANY_WORD(sel)) return 0; - val = Get_Var(sel); - - sel = BLK_SKIP(blk, 1); - while (TRUE) { - if (!ANY_OBJECT(val) || !IS_WORD(sel)) return 0; - i = Find_Word_Index(VAL_OBJ_FRAME(val), VAL_WORD_SYM(sel), FALSE); - sel++; - if (IS_END(sel)) { - *index = i; - return VAL_OBJ_FRAME(val); - } - } - - return 0; // never happens -} - - -#ifdef obsolete -/*********************************************************************** -** -xx*/ REBVAL *Call_Action(REBVAL *word, REBVAL *data, REBCNT act, REBSER *blk, REBCNT i) -/* -** Calls datatype action with a value and argument. -** Value is saved on top of stack. -** -***********************************************************************/ -{ - REBVAL *ds; - - DSF = Push_Func(TRUE, blk, i, VAL_WORD_SYM(word), 0); - DS_RELOAD(ds); - *D_ARG(1) = *data; - *D_ARG(2) = *BLK_SKIP(blk, i); - Do_Act(D_RET, VAL_TYPE(data), act); // (properly handles returns) - DSP = DSF; - DSF = VAL_BACK(DS_NEXT); - return DS_TOP; -} - - -/*********************************************************************** -** -xx*/ REBVAL *Get_Path_Var(REBVAL *path) -/* -** Leaves result on top of stack. -** -***********************************************************************/ -{ - REBVAL *word; - REBVAL *val; - REBSER *blk; - REBCNT i; - - if (VAL_TAIL(path) < 2) Trap1(RE_INVALID_PATH, path); // empty path - - blk = VAL_SERIES(path); - word = BLK_HEAD(blk); - if (!ANY_WORD(word)) Trap1(RE_INVALID_PATH, path); - - val = Get_Var(word); - - for (i = 1; i < blk->tail; i++) { - val = Call_Action(word, val, A_PATH, blk, i); // result is on TOS - } - - return val; -} - - -/*********************************************************************** -** -x*/ REBVAL *Do_Path(REBVAL **ppath, REBSER *block, REBCNT *index) -/* -** Evaluate a path. -** -***********************************************************************/ -{ - REBVAL *path = *ppath; - REBVAL *orig = *ppath; - REBVAL *value; - REBVAL *selector; - REBINT dsp; - REBINT act; - REBVAL *ds; - - if (VAL_TAIL(path) == 0) Trap1(RE_INVALID_PATH, path); // empty path - path = *ppath = VAL_BLK_DATA(path); - if (!ANY_WORD(path)) Trap1(RE_INVALID_PATH, orig); - value = Get_Var(path); - if (IS_UNSET(value)) Trap1(RE_NO_VALUE, path); - if (ANY_FUNC(value)) { - DS_PUSH(value); - value = DS_TOP; - return value; - } - - // Call the PATH action for the datatype. - if (!index) act = 0, index = &act; // Fudge a reference - DSF = Push_Func(FALSE, block ? block : VAL_SERIES(*ppath), *index, VAL_WORD_SYM(path), 0); - DS_PUSH_NONE; // Path dispatch value (from earlier evaluation) - DS_PUSH_NONE; // Argument to A_PATH action - dsp = DSP; - - // Note: above, the backtrace word should not be bound, or it could - // cause Get_Var() to use the wrong frame for relative values. So, - // to avoid that, we remove the frame from that word. - - for (path++; NOT_END(path); path++) { - - DSP = dsp; // Be sure stack does not grow - act = A_PATH; - - // word/:field case - if (IS_GET_WORD(path)) { - // !!! need to add word/:field: case - selector = Get_Var(path); // The object/:word case - } - // word/field: case - else if (IS_SET_PATH(orig) && IS_END(path+1)) { - if (!block) break; // evaluating singular path (e.g. in PARSE) - //if (!IS_END(path+1)) Trap1(RE_INVALID_PATH, orig); - selector = path; - act = A_PATH_SET; - *index = Do_Next(block, *index, 0); - if (IS_UNSET(DS_TOP)) Trap1(RE_NEED_VALUE, orig); - if (THROWN(DS_TOP)) { - value = DS_TOP; // stop, return !!! protected? - break; - } - // Leave value on top of stack for PATH SET. - } - // word/(expr) case - else if (IS_PAREN(path)) { - if (!block) Trap1(RE_NEED_VALUE, orig); // !!! not correct error - selector = DO_BLK(path); - } - // The object/word and object/value default case: - else selector = path; - - // Special case for BLOCK access: - DS_RELOAD(ds); - if (ANY_BLOCK(value)) { - value = Pick_Block(value, selector); - if (!value) { - if (act == A_PATH_SET) Trap_Range(selector); - SET_NONE(D_RET); - value = D_RET; - } - if (act == A_PATH_SET) { - *D_RET = *value = *D_ARG(3); // !!!! must check PROTECT flag!!!!!!!!!! - value = D_RET; - } - } - else { - // Call the A_PATH action for the given value: - *D_ARG(1) = *value; - *D_ARG(2) = *selector; - // D_ARG(3) too when PATH SET. - Do_Act(ds, VAL_TYPE(value), act); // (properly handles returns) - DS_RELOAD(ds); - // All scalars must be written back to their storage areas: - if (IS_SET_PATH(orig) && (IS_SCALAR(value) || IS_EVENT(value))) { //act == A_PATH_SET - *value = *D_RET; // !!!! must check PROTECT flag!!!!!!!!!! - *D_RET = *D_ARG(3); // return the value of the set - } - value = D_RET; // not GC protected after stack restore - } - if (ANY_FUNC(value)) break; // evaluate - } - - //ASSERT(DSF == dsf); - DSP = DSF; - DSF = VAL_BACK(DS_NEXT); - - *ppath = path; - *DS_TOP = *value; // should not be needed! - return value; -} - -/*********************************************************************** -** -xx*/ REBVAL *Do_Path(REBVAL **path_val, REBVAL *val) -/* -** Evaluate a path value. -** If val is not zero, set path to that new value. -** -** Args to PD_* func: -** Data: refernce value - can be modified -** Sel: any value -** Val: 0 or temp value on stack -** -** Stack: -** TOS-1: val (or not used) -** TOS: none -** -** Call the PD_function -** -** Result code: -** tos (tos has value) -** none -** error - invalid path -** -***********************************************************************/ -{ - REBVAL *orig = *path_val; - REBVAL *path; - REBVAL *value; - REBVAL *selector; - REBPEF func; - - // Get first block value: - *path_val = path = VAL_BLK_DATA(orig); - if (!ANY_WORD(path)) Trap1(RE_INVALID_PATH, orig, path); - - // Lookup the value of the variable: - value = Get_Var(path); - if (IS_UNSET(value)) Trap1(RE_NO_VALUE, path); - *DS_TOP = *value; - - // Foreach value in path: - while (TRUE) { - - // It is a function, return now: - if (ANY_FUNC(value)) { - if (IS_GET_PATH(orig)) return 0; - *path_val = path; // return the function name - return DS_TOP; - } - - if (IS_END(++path)) return 0; - - // object/:field case: - if (IS_GET_WORD(path)) { - selector = Get_Var(path); - if (IS_UNSET(selector)) Trap1(RE_NO_VALUE, path); - } - // object/(expr) case: - else if (IS_PAREN(path)) { - selector = Do_Blk(VAL_SERIES(path), 0); - } - else // object/word and object/value case: - selector = path; - - func = Path_Dispatch[VAL_TYPE(value)]; - if (!func) Trap1(RE_BAD_PATH, orig); - - if (NOT_END(path+1)) { - // Call datatype value with the given selector. - // It may return an object to continue. - value = func(value, selector, 0); // can modify DS_TOP - if (!value) Trap1(RE_INVALID_PATH, orig, path); - } else { - // This is the last field of the path. If val is - // given then the func sets the value of the field. - value = func(value, selector, val); // val can be zero - if (!value) break; // done (field was set) - *DS_TOP = *value; - if (!ANY_FUNC(value)) break; // done - // Now, use code at top of loop for FUNC - } - } - - return 0; -} - -#endif - -/*********************************************************************** -** -*/ REBINT Init_Mezz(REBINT reserved) -/* -***********************************************************************/ -{ - REBINT result = 0; - //REBVAL *val; - REBOL_STATE state; - REBVAL *val; - int MERGE_WITH_Do_String; -// static D = 0; -// int depth = D++; - - //Debug_Fmt("Set Halt"); - - if (PG_Boot_Level >= BOOT_LEVEL_MODS) { - - PUSH_STATE(state, Halt_State); - if (SET_JUMP(state)) { - //Debug_Fmt("Throw Halt"); - POP_STATE(state, Halt_State); - Saved_State = Halt_State; - Catch_Error(val = DS_NEXT); // Stores error value here - if (IS_ERROR(val)) { // (what else could it be?) - val = Get_System(SYS_STATE, STATE_LAST_ERROR); // Save it for EXPLAIN - *val = *DS_NEXT; - if (VAL_ERR_NUM(val) == RE_QUIT) { - //Debug_Fmt("Quit(init)"); - OS_EXIT(VAL_INT32(VAL_ERR_VALUE(val))); // console quit - } - if (VAL_ERR_NUM(val) >= RE_THROW_MAX) - Print_Value(val, 1000, FALSE); - } - return -1; - } - SET_STATE(state, Halt_State); - // Use this handler for both, halt conditions (QUIT, HALT) and error - // conditions. As this is a top-level handler, simply overwriting - // Saved_State is safe. - Saved_State = Halt_State; - - val = Do_Sys_Func(SYS_CTX_START, 0); // what if script contains a HALT? - - if (IS_INTEGER(val)) result = VAL_INT32(val); - //if (Try_Block_Halt(VAL_SERIES(ROOT_SCRIPT), 0)) { - - //DS_Base[state.dsp+1] = *val; - POP_STATE(state, Halt_State); - Saved_State = Halt_State; - } - - // Cleanup stack and memory: - DS_RESET; - Recycle(); - return 0; //result; -} diff --git a/src/core/c-error.c b/src/core/c-error.c index d2acdee33a..166854275a 100644 --- a/src/core/c-error.c +++ b/src/core/c-error.c @@ -1,835 +1,1692 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: c-error.c -** Summary: error handling -** Section: core -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ -/* - The Trap() functions are used for errors within the C code. - - TrapN() provides simple trampoline to the var-arg Make_Error() - that constructs a new error object. - - The Make_Error function uses the error category objects to - convert from an error code (integer) to error words and strings. - Other important state information such as location of error - and function context are also saved at this point. - - Throw_Error is called to throw the error back to a prior catch. - A catch is defined using a set of C-macros. During the throw - the error object is stored in a global: This_Error (because we - cannot be sure that the longjmp value is able to hold a pointer - on 64bit CPUs.) - - On the catch side, the Catch_Error function takes the error - object and stores it into the value provided (normally on the - DStack). - - Catch_Error can be extended to provide a debugging breakpoint - for examining the call trace and context frames on the stack. -*/ -/* - - Error Handling - - Errors occur in two places: - - 1. evaluation of natives and actions - 2. evaluation of a block - - When an error occurs, an error object is built and thrown back to - the nearest prior catch function. The catch is a longjmp that was - set by a TRY or similar native. At that point the interpreter stack - can be either examined (for debugging) or restored to the current - catch state. - - The error is returned from the catch as a disarmed error object. At - that point, the error can be passed around and accessed as a normal - object (although its datatype is ERROR!). The DISARM function - becomes unnecessary and will simply copy the fields to a normal - OBJECT! type. - - Using the new CAUSE native with the error object will re-activate - the error and throw the error back further to the prior catch. - - The error object will include a new TRACE field that provides a back - trace of the interpreter stack. This is a block of block pointers - and may be clipped at some reasonable size (perhaps 10). - - When C code hits an error condition, it calls Trap(id, arg1, arg2, ...). - This function takes a variable number of arguments. - - BREAK and RETURN - - TRY/RECOVER/EXCEPT. - - try [block] - try/recover [block] [block] - - TRACE f1, :path/f1, or [f1 f2 f3] - foo: func [[trace] ...] - -*/ +// +// File: %c-error.c +// Summary: "error handling" +// Section: core +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -#include "sys-state.h" -// Globals or Threaded??? -static REBOL_STATE Top_State; // Boot var: holds error state during boot +// +// Snap_State_Core: C +// +// Used by SNAP_STATE, PUSH_TRAP, and PUSH_UNHALTABLE_TRAP. +// +// **Note:** Modifying this routine likely means a necessary modification to +// both `Assert_State_Balanced_Debug()` and `Trapped_Helper_Halted()`. +// +void Snap_State_Core(struct Reb_State *s) +{ + s->dsp = DSP; + s->top_chunk = TG_Top_Chunk; + // There should not be a Collect_Keys in progress. (We use a non-zero + // length of the collect buffer to tell if a later fail() happens in + // the middle of a Collect_Keys.) + // + assert(ARR_LEN(BUF_COLLECT) == 0); -/*********************************************************************** -** -*/ void Check_Stack(void) -/* -***********************************************************************/ -{ - if ((DSP + 100) > (REBINT)SERIES_REST(DS_Series)) - Trap0(RE_STACK_OVERFLOW); + s->guarded_len = SER_LEN(GC_Guarded); + s->frame = FS_TOP; + + s->manuals_len = SER_LEN(GC_Manuals); + s->uni_buf_len = SER_LEN(UNI_BUF); + s->mold_loop_tail = ARR_LEN(MOLD_STACK); + + // !!! Is this initialization necessary? + s->error = NULL; } -/*********************************************************************** -** -*/ void Catch_Error(REBVAL *value) -/* -** Gets the current error and stores it as a value. -** Normally the value is on the stack and is returned. -** -***********************************************************************/ +#if !defined(NDEBUG) + +// +// Assert_State_Balanced_Debug: C +// +// Check that all variables in `state` have returned to what they were at +// the time of snapshot. +// +void Assert_State_Balanced_Debug( + struct Reb_State *s, + const char *file, + int line +) { + if (s->dsp != DSP) { + printf( + "DS_PUSH()x%d without DS_POP/DS_DROP\n", + DSP - s->dsp + ); + panic_at (NULL, file, line); + } + + assert(s->top_chunk == TG_Top_Chunk); + + assert(s->frame == FS_TOP); + + assert(ARR_LEN(BUF_COLLECT) == 0); + + if (s->guarded_len != SER_LEN(GC_Guarded)) { + printf( + "PUSH_GUARD()x%d without DROP_GUARD()\n", + cast(int, SER_LEN(GC_Guarded) - s->guarded_len) + ); + REBNOD *guarded = *SER_AT( + REBNOD*, + GC_Guarded, + SER_LEN(GC_Guarded) - 1 + ); + panic_at (guarded, file, line); + } + + // !!! Note that this inherits a test that uses GC_Manuals->content.xxx + // instead of SER_LEN(). The idea being that although some series + // are able to fit in the series node, the GC_Manuals wouldn't ever + // pay for that check because it would always be known not to. Review + // this in general for things that may not need "series" overhead, + // e.g. a contiguous pointer stack. + // + if (s->manuals_len > SER_LEN(GC_Manuals)) { + // + // Note: Should this ever actually happen, panic() on the series won't + // do any real good in helping debug it. You'll probably need to + // add additional checking in the Manage_Series and Free_Series + // routines that checks against the caller's manuals_len. + // + panic_at ("manual series freed outside checkpoint", file, line); + } + else if (s->manuals_len < SER_LEN(GC_Manuals)) { + printf( + "Make_Series()x%d without Free_Series or MANAGE_SERIES\n", + cast(int, SER_LEN(GC_Manuals) - s->manuals_len) + ); + REBSER *manual = *(SER_AT( + REBSER*, + GC_Manuals, + SER_LEN(GC_Manuals) - 1 + )); + panic_at (manual, file, line); + } + + assert(s->uni_buf_len == SER_LEN(UNI_BUF)); + assert(s->mold_loop_tail == ARR_LEN(MOLD_STACK)); + + assert(s->error == NULL); // !!! necessary? +} + +#endif + + +// +// Trapped_Helper_Halted: C +// +// This is used by both PUSH_TRAP and PUSH_UNHALTABLE_TRAP to do the work of +// responding to a longjmp. (Hence it is run when setjmp returns TRUE.) Its +// job is to safely recover from a sudden interruption, though the list of +// things which can be safely recovered from is finite. +// +// (Among the countless things that are not handled automatically would be a +// memory allocation via malloc().) +// +// Note: This is a crucial difference between C and C++, as C++ will walk up +// the stack at each level and make sure any constructors have their +// associated destructors run. *Much* safer for large systems, though not +// without cost. Rebol's greater concern is not so much the cost of setup for +// stack unwinding, but being written without requiring a C++ compiler. +// +// Returns whether the trapped error was a RE_HALT or not. +// +REBOOL Trapped_Helper_Halted(struct Reb_State *s) { - if (IS_NONE(TASK_THIS_ERROR)) Crash(RP_ERROR_CATCH); - *value = *TASK_THIS_ERROR; -// Print("CE: %r", value); - SET_NONE(TASK_THIS_ERROR); - //!!! Reset or ENABLE_GC; + ASSERT_CONTEXT(s->error); + assert(CTX_TYPE(s->error) == REB_ERROR); + + REBOOL halted = LOGICAL(ERR_NUM(s->error) == RE_HALT); + + // Restore Rebol data stack pointer at time of Push_Trap + // + DS_DROP_TO(s->dsp); + + // Drop to the chunk state at the time of Push_Trap + // + while (TG_Top_Chunk != s->top_chunk) + Drop_Chunk_Of_Values(NULL); + + // If we were in the middle of a Collect_Keys and an error occurs, then + // the binding lookup table has entries in it that need to be zeroed out. + // We can tell if that's necessary by whether there is anything + // accumulated in the collect buffer. + // + if (ARR_LEN(BUF_COLLECT) != 0) + Collect_Keys_End(NULL); // !!! No binder, review implications + + // Free any manual series that were extant at the time of the error + // (that were created since this PUSH_TRAP started). This includes + // any arglist series in call frames that have been wiped off the stack. + // (Closure series will be managed.) + // + assert(SER_LEN(GC_Manuals) >= s->manuals_len); + while (SER_LEN(GC_Manuals) != s->manuals_len) { + // Freeing the series will update the tail... + Free_Series( + *SER_AT(REBSER*, GC_Manuals, SER_LEN(GC_Manuals) - 1) + ); + } + + SET_SERIES_LEN(GC_Guarded, s->guarded_len); + TG_Frame_Stack = s->frame; + TERM_SEQUENCE_LEN(UNI_BUF, s->uni_buf_len); + +#if !defined(NDEBUG) + // + // Because reporting errors in the actual Push_Mold process leads to + // recursion, this debug flag helps make it clearer what happens if + // that does happen... and can land on the right comment. But if there's + // a fail of some kind, the flag for the warning needs to be cleared. + // + TG_Pushing_Mold = FALSE; +#endif + + TERM_ARRAY_LEN(MOLD_STACK, s->mold_loop_tail); + + Saved_State = s->last_state; + + return halted; } -/*********************************************************************** -** -*/ void Throw_Error(REBSER *err) -/* -** Throw the C stack. -** -***********************************************************************/ +// +// Fail_Core: C +// +// Cause a "trap" of an error by longjmp'ing to the enclosing PUSH_TRAP (or +// PUSH_UNHALTABLE_TRAP). Note that these failures interrupt code mid-stream, +// so if a Rebol function is running it will not make it to the point of +// returning the result value. This distinguishes the "fail" mechanic from +// the "throw" mechanic, which has to bubble up a THROWN() value through +// D_OUT (used to implement BREAK, CONTINUE, RETURN, LEAVE...) +// +// The function will auto-detect if the pointer it is given is an ERROR!'s +// REBCTX*, a REBVAL*, or a UTF-8 string. If it's a string, an error will be +// created from it automatically. If it's a value, then it is turned into +// the ubiquitous (and kind of lame) "Invalid Arg" error. +// +// Note: Over the long term, one does not want to hard-code error strings in +// the executable. That makes them more difficult to hook with translations, +// or to identify systemically with some kind of "error code". However, +// it's a realistic quick-and-dirty way of delivering a more meaningful +// error than just using a RE_MISC error code, and can be found just as easily +// to clean up later. +// +ATTRIBUTE_NO_RETURN void Fail_Core(const void *p) { - if (!Saved_State) Crash(RP_NO_SAVED_STATE); - SET_ERROR(TASK_THIS_ERROR, ERR_NUM(err), err); - if (Trace_Level) Trace_Error(TASK_THIS_ERROR); - longjmp(*Saved_State, 1); + REBCTX *error; + + switch (Detect_Rebol_Pointer(p)) { + case DETECTED_AS_UTF8: { + DECLARE_LOCAL (string); + Init_String(string, Make_UTF8_May_Fail(cast(const char*, p))); + error = Error(RE_USER, string, END); + break; } + + case DETECTED_AS_SERIES: { + REBSER *s = m_cast(REBSER*, cast(const REBSER*, p)); // don't mutate + if (NOT_SER_FLAG(s, ARRAY_FLAG_VARLIST)) + panic (s); + error = CTX(s); + break; } + + case DETECTED_AS_VALUE: { + const REBVAL *v = cast(const REBVAL*, p); + error = Error(RE_INVALID_ARG, v, END); + break; } + + default: + panic (p); // suppress compiler error from non-smart compilers + } + + ASSERT_CONTEXT(error); + assert(CTX_TYPE(error) == REB_ERROR); + +#if !defined(NDEBUG) + // + // All calls to Fail_Core should originate from the `fail` macro, + // which in the debug build sets TG_Erroring_C_File and TG_Erroring_C_Line. + // Any error creations as arguments to that fail should have picked + // it up, and we now need to NULL it out so other Make_Error calls + // that are not inside of a fail invocation don't get confused and + // have the wrong information + // + assert(TG_Erroring_C_File != NULL); + TG_Erroring_C_File = NULL; +#endif + + // If we raise the error we'll lose the stack, and if it's an early + // error we always want to see it (do not use ATTEMPT or TRY on + // purpose in Startup_Core()...) + // + if (PG_Boot_Phase < BOOT_DONE) + panic (error); + + // There should be a PUSH_TRAP of some kind in effect if a `fail` can + // ever be run. + // + if (Saved_State == NULL) + panic (error); + + // The information for the Rebol call frames generally is held in stack + // variables, so the data will go bad in the longjmp. We have to free + // the data *before* the jump. Be careful not to let this code get too + // recursive or do other things that would be bad news if we're responding + // to C_STACK_OVERFLOWING. (See notes on the sketchiness in general of + // the way R3-Alpha handles stack overflows, and alternative plans.) + // + REBFRM *f = FS_TOP; + while (f != Saved_State->frame) { + if (Is_Any_Function_Frame(f)) + Drop_Function_Args_For_Frame_Core(f, FALSE); // don't drop chunks + + // See notes in Do_Va_Core() about how it is required by C standard + // to call va_end() after va_start(). If we longjmp past the point + // that called va_start(), we have to clean up the va_list else there + // could be undefined behavior. + // + if (FRM_IS_VALIST(f)) + va_end(*f->source.vaptr); + + REBFRM *prior = f->prior; + Drop_Frame_Core(f); + f = prior; + } + + TG_Frame_Stack = f; // TG_Frame_Stack is writable FS_TOP + + Saved_State->error = error; + + // If a THROWN() was being processed up the stack when the error was + // raised, then it had the thrown argument set. Trash it in debug + // builds. (The value will not be kept alive, it is not seen by GC) + // + SET_UNREADABLE_BLANK(&TG_Thrown_Arg); + + LONG_JUMP(Saved_State->cpu_state, 1); } -/*********************************************************************** -** -*/ void Throw_Break(REBVAL *val) -/* -** Throw a break or return style error (for special cases -** where we do not want to unwind the stack). -** -***********************************************************************/ +// +// Stack_Depth: C +// +REBCNT Stack_Depth(void) { - if (!Saved_State) Crash(RP_NO_SAVED_STATE); - *TASK_THIS_ERROR = *val; - longjmp(*Saved_State, 1); + REBCNT depth = 0; + + REBFRM *f = FS_TOP; + while (f) { + if (Is_Any_Function_Frame(f)) + if (NOT(Is_Function_Frame_Fulfilling(f))) { + // + // We only count invoked functions (not group or path + // evaluations or "pending" functions that are building their + // arguments but have not been formally invoked yet) + // + ++depth; + } + + f = FRM_PRIOR(f); + } + + return depth; } -/*********************************************************************** -** -*/ void Throw_Return_Series(REBCNT type, REBSER *series) -/* -** Throws a series value using error temp values. -** -***********************************************************************/ +// +// Find_Error_For_Code: C +// +// Find the id word, the error type (category) word, and the error +// message template block-or-string for a given error number. +// +// This scans the data which is loaded into the boot file by +// processing %errors.r +// +// If the message is not found, return NULL. Will not write to +// `id_out` or `type_out` unless returning a non-NULL pointer. +// +const REBVAL *Find_Error_For_Code(REBVAL *id_out, REBVAL *type_out, REBCNT code) { - REBVAL *val; - REBVAL *err; - REBSER *blk = VAL_SERIES(TASK_ERR_TEMPS); + REBCNT n; + + // See %errors.r for the list of data which is loaded into the boot + // file as objects for the "error catalog" + // + REBCTX *categories = VAL_CONTEXT(Get_System(SYS_CATALOG, CAT_ERRORS)); + assert(CTX_KEY_SYM(categories, 1) == SYM_SELF); + + // Find the correct catalog category + n = code / RE_CATEGORY_SIZE; // 0 for Special, 1 for Internal... + if (SELFISH(n + 1) > CTX_LEN(categories)) // 1-based, not 0 based + return NULL; + + // Get context of object representing the elements of the category itself + if (!IS_OBJECT(CTX_VAR(categories, SELFISH(n + 1)))) { + assert(FALSE); + return NULL; + } + + REBCTX *category = VAL_CONTEXT(CTX_VAR(categories, SELFISH(n + 1))); + assert(CTX_KEY_SYM(category, 1) == SYM_SELF); + + // Find the correct template in the catalog category (see %errors.r) + n = code % RE_CATEGORY_SIZE; // 0-based order within category + if (SELFISH(n + 2) > CTX_LEN(category)) // 1-based (CODE: TYPE:) + return NULL; + + // Sanity check CODE: field of category object + if (!IS_INTEGER(CTX_VAR(category, SELFISH(1)))) { + assert(FALSE); + return NULL; + } + assert( + (code / RE_CATEGORY_SIZE) * RE_CATEGORY_SIZE + == cast(REBCNT, VAL_INT32(CTX_VAR(category, SELFISH(1)))) + ); + + // Sanity check TYPE: field of category object + // !!! Same spelling as what we set in VAL_WORD_SYM(type_out))? + if (!IS_STRING(CTX_VAR(category, SELFISH(2)))) { + assert(FALSE); + return NULL; + } + + REBVAL *message = CTX_VAR(category, SELFISH(n + 3)); + + // Error message template must be string or block + assert(IS_BLOCK(message) || IS_STRING(message)); + + // Success! Write category word from the category list context key sym, + // and specific error ID word from the context key sym within category + // + Init_Word( + type_out, + CTX_KEY_SPELLING(categories, SELFISH((code / RE_CATEGORY_SIZE) + 1)) + ); + Init_Word( + id_out, + CTX_KEY_SPELLING(category, SELFISH((code % RE_CATEGORY_SIZE) + 3)) + ); + + return message; +} - RESET_SERIES(blk); - val = Append_Value(blk); - Set_Series(type, val, series); - err = Append_Value(blk); - SET_THROW(err, RE_RETURN, val); - VAL_ERR_SYM(err) = SYM_RETURN; // indicates it is "virtual" (parse return) - Throw_Break(err); + +// +// Set_Location_Of_Error: C +// +// Since errors are generally raised to stack levels above their origin, the +// stack levels causing the error are no longer running by the time the +// error object is inspected. A limited snapshot of context information is +// captured in the WHERE and NEAR fields, and some amount of file and line +// information may be captured as well. +// +// The information is derived from the current execution position and stack +// depth of a running frame. Also, if running from a C fail() call, the +// file and line information can be captured in the debug build. +// +void Set_Location_Of_Error( + REBCTX *error, + REBFRM *where // must be valid and executing on the stack +) { + assert(where != NULL); + + REBDSP dsp_orig = DSP; + + ERROR_VARS *vars = ERR_VARS(error); + + // WHERE is a backtrace in the form of a block of label words, that start + // from the top of stack and go downward. + // + REBFRM *f = where; + for (; f != NULL; f = f->prior) { + // + // Only invoked functions (not pending functions, groups, etc.) + // + if (NOT(Is_Any_Function_Frame(f))) + continue; + if (Is_Function_Frame_Fulfilling(f)) + continue; + + DS_PUSH_TRASH; + Init_Word(DS_TOP, FRM_LABEL(f)); + } + Init_Block(&vars->where, Pop_Stack_Values(dsp_orig)); + + // Nearby location of the error. Reify any valist that is running, + // so that the error has an array to present. + // + if (FRM_IS_VALIST(where)) { + const REBOOL truncated = TRUE; + Reify_Va_To_Array_In_Frame(where, truncated); + } + + // Get at most 6 values out of the array. Ideally 3 before and after + // the error point. If truncating either the head or tail of the + // values, put ellipses. + + REBINT start = FRM_INDEX(where) - 3; + if (start < 0) { + DS_PUSH_TRASH; + Init_Word(DS_TOP, Canon(SYM_ELLIPSIS)); + + start = 0; + } + + REBCNT count = 0; + RELVAL *item = ARR_AT(FRM_ARRAY(where), start); + while (NOT_END(item) && count++ < 6) { + DS_PUSH_RELVAL(item, where->specifier); + if (count == FRM_INDEX(where) - start) { + // + // Leave a marker at the point of the error (currently `??`) + // + // Note: something like `=>ERROR=>` would be better, but have to + // insert a today-legal WORD! + // + DS_PUSH_TRASH; + Init_Word(DS_TOP, Canon(SYM__Q_Q)); + } + ++item; + } + + if (NOT_END(item)) { + DS_PUSH_TRASH; + Init_Word(DS_TOP, Canon(SYM_ELLIPSIS)); + } + + Init_Block(&vars->nearest, Pop_Stack_Values(dsp_orig)); + +#if !defined(NDEBUG) + if (TG_Erroring_C_File) { + // + // !!! Note that a WORD! is used because FILE! strings cannot be + // interned at this time, and the general mechanism for storing + // filenames in usermode blocks wants to avoid generating a lot + // of copies of the same string, given that the total number of + // files one is working with is probably a limited set. + // + Init_Word( + &vars->file, + Intern_UTF8_Managed( + cb_cast(TG_Erroring_C_File), strlen(TG_Erroring_C_File) + ) + ); + Init_Integer(&vars->line, TG_Erroring_C_Line); + } + else +#endif + { // ^-- mind the ELSE + // Try to fill in the file and line information of the error from the + // stack, looking for arrays with SERIES_FLAG_FILE_LINE. + // + f = where; + for (; f != NULL; f = f->prior) { + if (FRM_IS_VALIST(f)) + continue; + if (NOT(GET_SER_FLAG(f->source.array, SERIES_FLAG_FILE_LINE))) + continue; + break; + } + if (f != NULL) { + Init_Word(&vars->file, SER(f->source.array)->link.filename); + Init_Integer(&vars->line, SER(f->source.array)->misc.line); + } + } } -/*********************************************************************** -** -*/ void Throw_Return_Value(REBVAL *value) -/* -** Throws a series value using error temp values. -** -***********************************************************************/ +// +// Make_Error_Object_Throws: C +// +// Creates an error object from arg and puts it in value. +// The arg can be a string or an object body block. +// +// Returns TRUE if a THROWN() value is made during evaluation. +// +// This function is called by MAKE ERROR!. Note that most often +// system errors from %errors.r are thrown by C code using +// Make_Error(), but this routine accommodates verification of +// errors created through user code...which may be mezzanine +// Rebol itself. A goal is to not allow any such errors to +// be formed differently than the C code would have made them, +// and to cross through the point of R3-Alpha error compatibility, +// which makes this a rather tortured routine. However, it +// maps out the existing landscape so that if it is to be changed +// then it can be seen exactly what is changing. +// +REBOOL Make_Error_Object_Throws( + REBVAL *out, // output location **MUST BE GC SAFE**! + const REBVAL *arg +) { + // Frame from the error object template defined in %sysobj.r + // + REBCTX *root_error = VAL_CONTEXT(Get_System(SYS_STANDARD, STD_ERROR)); + + REBCTX *error; + ERROR_VARS *vars; // C struct mirroring fixed portion of error fields + + if (IS_ERROR(arg) || IS_OBJECT(arg)) { + // Create a new error object from another object, including any + // non-standard fields. WHERE: and NEAR: will be overridden if + // used. If ID:, TYPE:, or CODE: were used in a way that would + // be inconsistent with a Rebol system error, an error will be + // raised later in the routine. + + error = Merge_Contexts_Selfish(root_error, VAL_CONTEXT(arg)); + vars = ERR_VARS(error); + } + else if (IS_BLOCK(arg)) { + // If a block, then effectively MAKE OBJECT! on it. Afterward, + // apply the same logic as if an OBJECT! had been passed in above. + + // Bind and do an evaluation step (as with MAKE OBJECT! with A_MAKE + // code in REBTYPE(Context) and code in REBNATIVE(construct)) + + error = Make_Selfish_Context_Detect( + REB_ERROR, // type + VAL_ARRAY_AT(arg), // values to scan for toplevel set-words + root_error // parent + ); + + // Protect the error from GC by putting into out, which must be + // passed in as a GC-protecting value slot. + // + Init_Error(out, error); + + Rebind_Context_Deep(root_error, error, NULL); // NULL=>no more binds + Bind_Values_Deep(VAL_ARRAY_AT(arg), error); + + DECLARE_LOCAL (evaluated); + if (Do_Any_Array_At_Throws(evaluated, arg)) { + Move_Value(out, evaluated); + return TRUE; + } + + vars = ERR_VARS(error); + } + else if (IS_STRING(arg)) { + // + // String argument to MAKE ERROR! makes a custom error from user: + // + // code: _ ;-- default is blank + // type: _ + // id: _ + // message: "whatever the string was" + // + // Minus the message, this is the default state of root_error. + + error = Copy_Context_Shallow(root_error); + + // !!! fix in Startup_Errors()? + // + VAL_RESET_HEADER(CTX_VALUE(error), REB_ERROR); + + vars = ERR_VARS(error); + assert(IS_BLANK(&vars->code)); + assert(IS_BLANK(&vars->type)); + assert(IS_BLANK(&vars->id)); + + Init_String(&vars->message, Copy_Sequence_At_Position(arg)); + } + else { + // No other argument types are handled by this routine at this time. + + fail (Error_Invalid_Error_Raw(arg)); + } + + // Validate the error contents, and reconcile message template and ID + // information with any data in the object. Do this for the IS_STRING + // creation case just to make sure the rules are followed there too. + + // !!! Note that this code is very cautious because the goal isn't to do + // this as efficiently as possible, rather to put up lots of alarms and + // traffic cones to make it easy to pick and choose what parts to excise + // or tighten in an error enhancement upgrade. + + if (IS_INTEGER(&vars->code)) { + assert(VAL_INT32(&vars->code) != RE_USER); // not real code, use blank + + // Users can make up anything for error codes allocated to them, + // but Rebol's historical default is to "own" error codes less + // than RE_USER. If a code is used in the sub-RE_USER range then + // make sure any id or type provided do not conflict. + + if (!IS_BLANK(&vars->message)) // assume a MESSAGE: is wrong + fail (Error_Invalid_Error_Raw(arg)); + + DECLARE_LOCAL (id); + DECLARE_LOCAL (type); + const REBVAL *message = Find_Error_For_Code( + id, + type, + cast(REBCNT, VAL_INT32(&vars->code)) + ); + + if (message == NULL) + fail (Error_Invalid_Error_Raw(arg)); + + Move_Value(&vars->message, message); + + if (!IS_BLANK(&vars->id)) { + if ( + !IS_WORD(&vars->id) + || VAL_WORD_CANON(&vars->id) != VAL_WORD_CANON(id) + ){ + fail (Error_Invalid_Error_Raw(arg)); + } + } + Move_Value(&vars->id, id); // binding and case normalized + + if (!IS_BLANK(&vars->type)) { + if ( + !IS_WORD(&vars->id) + || VAL_WORD_CANON(&vars->type) != VAL_WORD_CANON(type) + ){ + fail (Error_Invalid_Error_Raw(arg)); + } + } + Move_Value(&vars->type, type); // binding and case normalized + + // !!! TBD: Check that all arguments were provided! + } + else if (IS_WORD(&vars->type) && IS_WORD(&vars->id)) { + // If there was no CODE: supplied but there was a TYPE: and ID: then + // this may overlap a combination used by Rebol where we wish to + // fill in the code. (No fast lookup for this, must search.) + + REBCTX *categories = VAL_CONTEXT(Get_System(SYS_CATALOG, CAT_ERRORS)); + + assert(IS_BLANK(&vars->code)); + + // Find correct category for TYPE: (if any) + REBVAL *category + = Select_Canon_In_Context(categories, VAL_WORD_CANON(&vars->type)); + + if (category) { + assert(IS_OBJECT(category)); + assert(VAL_CONTEXT_KEY_SYM(category, 1) == SYM_SELF); + assert(VAL_CONTEXT_KEY_SYM(category, SELFISH(1)) == SYM_CODE); + assert(IS_INTEGER(VAL_CONTEXT_VAR(category, SELFISH(1)))); + + REBCNT code = cast(REBCNT, + VAL_INT32(VAL_CONTEXT_VAR(category, SELFISH(1))) + ); + + assert(VAL_CONTEXT_KEY_SYM(category, SELFISH(2)) == SYM_TYPE); + assert(IS_STRING(VAL_CONTEXT_VAR(category, SELFISH(2)))); + + // Find correct message for ID: (if any) + + REBVAL *message = Select_Canon_In_Context( + VAL_CONTEXT(category), VAL_WORD_CANON(&vars->id) + ); + + if (message) { + assert(IS_STRING(message) || IS_BLOCK(message)); + + if (!IS_BLANK(&vars->message)) + fail (Error_Invalid_Error_Raw(arg)); + + Move_Value(&vars->message, message); + + Init_Integer(&vars->code, + code + + Find_Canon_In_Context( + error, VAL_WORD_CANON(&vars->id), FALSE + ) + - Find_Canon_In_Context(error, Canon(SYM_TYPE), FALSE) + - 1 + ); + } + else { + // At the moment, we don't let the user make a user-ID'd + // error using a category from the internal list just + // because there was no id from that category. In effect + // all the category words have been "reserved" + + // !!! Again, remember this is all here just to show compliance + // with what the test suite tested for, it disallowed e.g. + // it expected the following to be an illegal error because + // the `script` category had no `set-self` error ID. + // + // make error! [type: 'script id: 'set-self] + + fail (Error_Invalid_Error_Raw(arg)); + } + assert(IS_INTEGER(&vars->code)); + } + else { + // The type and category picked did not overlap any existing one + // so let it be a user error. + // + assert(IS_BLANK(&vars->code)); + Init_Blank(&vars->code); + } + } + else { + // It's either a user-created error or otherwise. It may + // have bad ID, TYPE, or message fields, or a completely + // strange code #. The question of how non-standard to + // tolerate is an open one. + + // For now we just write blank into the error code field, if that was + // not already there. + + if (NOT(IS_BLANK(&vars->code))) + fail (Error_Invalid_Error_Raw(arg)); + + // !!! Because we will experience crashes in the molding logic, + // we put some level of requirement besides "code # not 0". + // This is conservative logic and not good for general purposes. + + if ( + !(IS_WORD(&vars->id) || IS_BLANK(&vars->id)) + || !(IS_WORD(&vars->type) || IS_BLANK(&vars->type)) + || !( + IS_BLOCK(&vars->message) + || IS_STRING(&vars->message) + || IS_BLANK(&vars->message) + ) + ) { + fail (Error_Invalid_Error_Raw(arg)); + } + } + + // There might be no Rebol code running when the error is created (e.g. + // the static creation of the stack overflow error before any code runs) + // + if (FS_TOP != NULL) + Set_Location_Of_Error(error, FS_TOP); + + Init_Error(out, error); + return FALSE; +} + + +// +// Make_Error_Managed_Core: C +// +// (WARNING va_list by pointer: http://stackoverflow.com/a/3369762/211160) +// +// Create and init a new error object based on a C va_list and an error code. +// It knows how many arguments the error particular error ID requires based +// on the templates defined in %errors.r. +// +// If the error code RE_USER is used, then the error will have +// +// This routine should either succeed and return to the caller, or panic() +// and crash if there is a problem (such as running out of memory, or that +// %errors.r has not been loaded). Hence the caller can assume it will +// regain control to properly call va_end with no longjmp to skip it. +// +REBCTX *Make_Error_Managed_Core(REBCNT code, va_list *vaptr) +{ + assert(code != 0); + + if (PG_Boot_Phase < BOOT_ERRORS) { // no STD_ERROR or template table yet + #if !defined(NDEBUG) + printf( + "fail() before object table initialized, code = %d\n", + cast(int, code) + ); + #endif + + DECLARE_LOCAL (code_value); + Init_Integer(code_value, code); + + panic (code_value); + } + + REBCTX *root_error = VAL_CONTEXT(Get_System(SYS_STANDARD, STD_ERROR)); + + DECLARE_LOCAL (id); + DECLARE_LOCAL (type); + const REBVAL *message; + if (code == RE_USER) { + Init_Blank(id); + Init_Blank(type); + message = va_arg(*vaptr, const REBVAL*); + } + else + message = Find_Error_For_Code(id, type, code); + + assert(message != NULL); + + REBCNT expected_args = 0; + if (IS_BLOCK(message)) { // GET-WORD!s in template should match va_list + RELVAL *temp = VAL_ARRAY_HEAD(message); + for (; NOT_END(temp); ++temp) { + if (IS_GET_WORD(temp)) + ++expected_args; + else + assert(IS_STRING(temp)); + } + } + else // Just a string, no arguments expected. + assert(IS_STRING(message)); + + REBCTX *error; + if (expected_args == 0) { + // If there are no arguments, we don't need to make a new keylist... + // just a new varlist to hold this instance's settings. (root + // error keylist is already managed) + + error = Copy_Context_Shallow(root_error); + + // !!! Should tweak root error during boot so it actually is an ERROR! + // (or use literal error construction syntax, if it worked?) + // + VAL_RESET_HEADER(CTX_VALUE(error), REB_ERROR); + } + else { + // !!! See remarks on how the modern way to handle this may be to + // put error arguments in the error object, and then have the META-OF + // hold the generic error parameters. Investigate how this ties in + // with user-defined types. + + REBCNT root_len = CTX_LEN(root_error); + + // Should the error be well-formed, we'll need room for the new + // expected values *and* their new keys in the keylist. + // + error = Copy_Context_Shallow_Extra(root_error, expected_args); + + // !!! Should tweak root error during boot so it actually is an ERROR! + // (or use literal error construction syntax, if it worked?) + // + VAL_RESET_HEADER(CTX_VALUE(error), REB_ERROR); + + // Fix up the tail first so CTX_KEY and CTX_VAR don't complain + // in the debug build that they're accessing beyond the error length + // + TERM_ARRAY_LEN(CTX_VARLIST(error), root_len + expected_args + 1); + TERM_ARRAY_LEN(CTX_KEYLIST(error), root_len + expected_args + 1); + + REBVAL *key = CTX_KEY(error, root_len) + 1; + REBVAL *value = CTX_VAR(error, root_len) + 1; + + #ifdef NDEBUG + const RELVAL *temp = VAL_ARRAY_HEAD(message); + #else + // Will get here even for a parameterless string due to throwing in + // the extra "arguments" of the __FILE__ and __LINE__ + // + const RELVAL *temp = + IS_STRING(message) + ? cast(const RELVAL*, END) // needed by gcc/g++ 2.95 (bug) + : VAL_ARRAY_HEAD(message); + #endif + + for (; NOT_END(temp); ++temp) { + if (IS_GET_WORD(temp)) { + const REBVAL *arg = va_arg(*vaptr, const REBVAL*); + + // NULL is 0 in C, and so passing NULL to a va_arg list and + // reading it as a pointer is not legal (because it will just + // be an integer). One would have to use `(REBVAL*)NULL`, so + // END is used instead (consistent w/variadic Do_XXX) + // + assert(arg != NULL); + + if (IS_END(arg)) { + // Terminating with an end marker is optional but can help + // catch errors here of too few args passed when the + // template expected more substitutions. + + #ifdef NDEBUG + // If the C code passed too few args in a debug build, + // prevent a crash in the release build by filling it. + // No perfect answer if you're going to keep running... + // something like ISSUE! #404 could be an homage: + // + // http://www.room404.com/page.php?pg=homepage + // + // But we'll just use NONE. Debug build asserts here. + + arg = BLANK_VALUE; + #else + printf( + "too few args passed for error code %d at %s line %d", + cast(int, code), + TG_Erroring_C_File ? TG_Erroring_C_File : "", + TG_Erroring_C_File ? TG_Erroring_C_Line : -1 + ); + assert(FALSE); + + // !!! Note that we have no way of checking for too *many* + // args with C's va_list machinery + #endif + } + + #if !defined(NDEBUG) + if (GET_VAL_FLAG(arg, VALUE_FLAG_RELATIVE)) { + // + // Make_Error doesn't have any way to pass in a specifier, + // so only specific values should be used. + // + printf("Relative value passed to Make_Error()\n"); + panic (arg); + } + #endif + + ASSERT_VALUE_MANAGED(arg); + + Init_Typeset(key, ALL_64, VAL_WORD_SPELLING(temp)); + Move_Value(value, arg); + + key++; + value++; + } + } + + assert(IS_END(key)); // set above by TERM_ARRAY_LEN + assert(IS_END(value)); // ...same + } + + // C struct mirroring fixed portion of error fields + // + ERROR_VARS *vars = ERR_VARS(error); + + if (code == RE_USER) + assert(IS_BLANK(&vars->code)); // no error number + else + Init_Integer(&vars->code, code); + + Move_Value(&vars->message, message); + Move_Value(&vars->id, id); + Move_Value(&vars->type, type); + + // There might be no Rebol code running when the error is created (e.g. + // the static creation of the stack overflow error before any code runs) + // + if (FS_TOP != NULL) + Set_Location_Of_Error(error, FS_TOP); + + // !!! We create errors and then fail() on them without ever putting them + // into a REBVAL. This means that if left unmanaged, they would count as + // manual memory that the fail() needed to clean up...but the fail() + // plans on reporting this error (!). In these cases the GC doesn't run + // but the cleanup does, so for now manage the error in the hopes it + // will be used up quickly. + // + MANAGE_ARRAY(CTX_VARLIST(error)); + return error; +} + + +// +// Error: C +// +// This variadic function takes a number of REBVAL* arguments appropriate for +// the error number passed. It is commonly used with fail(): +// +// fail (Error(RE_SOMETHING, arg1, arg2, ...)); +// +// Note that in C, variadic functions don't know how many arguments they were +// passed. Make_Error_Managed_Core() knows how many arguments are in an +// error's template in %errors.r for a given error id, so that is the number +// of arguments it will *attempt* to use--reading invalid memory if wrong. +// +// (All C variadics have this problem, e.g. `printf("%d %d", 12);`) +// +// But the risk of mistakes is reduced by creating wrapper functions, with a +// fixed number of arguments specific to each error...and the wrappers can +// also do additional argument processing: +// +// fail (Error_Something(arg1, thing_processed_to_make_arg2)); +// +// But to make variadic calls *slightly* safer, a caller can pass END +// after the last argument for a double-check that won't try reading invalid +// memory if too few arguments are given: +// +// fail (Error(RE_SOMETHING, arg1, arg2, END)); +// +REBCTX *Error(REBCNT num, ... /* REBVAL *arg1, REBVAL *arg2, ... */) { - REBVAL *val; - REBVAL *err; - REBSER *blk = VAL_SERIES(TASK_ERR_TEMPS); + va_list va; + REBCTX *error; - RESET_SERIES(blk); - val = Append_Value(blk); - *val = *value; - err = Append_Value(blk); - SET_THROW(err, RE_RETURN, val); - VAL_ERR_SYM(err) = SYM_RETURN; // indicates it is "virtual" (parse return) - Throw_Break(err); + va_start(va, num); + error = Make_Error_Managed_Core(num, &va); + va_end(va); + + return error; } -/*********************************************************************** -** -*/ void Trap_Stack() -/* -***********************************************************************/ +// +// Error_Lookback_Quote_Too_Late: C +// +REBCTX *Error_Lookback_Quote_Too_Late(const RELVAL *word, REBSPC *specifier) { + assert(IS_WORD(word)); + + DECLARE_LOCAL (specific); + Derelativize(specific, word, specifier); + + fail (Error_Enfix_Quote_Late_Raw(specific)); +} + + +// +// Error_Non_Logic_Refinement: C +// +// Ren-C allows functions to be specialized, such that a function's frame can +// be filled (or partially filled) by an example frame. The variables +// corresponding to refinements must be canonized to either TRUE or FALSE +// by these specializations, because that's what the called function expects. +// +REBCTX *Error_Non_Logic_Refinement(REBFRM *f) { + DECLARE_LOCAL (word); + Init_Word(word, VAL_PARAM_SPELLING(f->param)); + fail (Error_Non_Logic_Refine_Raw(word, Type_Of(f->arg))); +} + + +// +// Error_Bad_Func_Def: C +// +REBCTX *Error_Bad_Func_Def(const REBVAL *spec, const REBVAL *body) { - if (IS_INTEGER(TASK_THIS_ERROR)) return; // composing prior error. + // !!! Improve this error; it's simply a direct emulation of arity-1 + // error that existed before refactoring code out of MAKE_Function(). - if (!Saved_State) Crash(RP_NO_SAVED_STATE); + REBARR *array = Make_Array(2); + Append_Value(array, spec); + Append_Value(array, body); - *TASK_THIS_ERROR = *TASK_STACK_ERROR; // pre-allocated + DECLARE_LOCAL (def); - longjmp(*Saved_State, 1); + Init_Block(def, array); + return Error_Bad_Func_Def_Raw(def); } -/*********************************************************************** -** -*/ REBCNT Stack_Depth() -/* -***********************************************************************/ +// +// Error_No_Arg: C +// +REBCTX *Error_No_Arg(REBSTR *label, const RELVAL *param) { - REBCNT dsf = DSF; - REBCNT count = 0; + assert(IS_TYPESET(param)); + + DECLARE_LOCAL (param_word); + Init_Word(param_word, VAL_PARAM_SPELLING(param)); - for (dsf = DSF; dsf > 0; dsf = PRIOR_DSF(dsf)) { - count++; - } + DECLARE_LOCAL (label_word); + Init_Word(label_word, label); - return count; + return Error_No_Arg_Raw(label_word, param_word); } -/*********************************************************************** -** -*/ REBSER *Make_Backtrace(REBINT start) -/* -** Return a block of backtrace words. -** -***********************************************************************/ +// +// Error_Invalid_Datatype: C +// +REBCTX *Error_Invalid_Datatype(REBCNT id) { - REBCNT depth = Stack_Depth(); - REBSER *blk = Make_Block(depth-start); - REBINT dsf; - REBVAL *val; + DECLARE_LOCAL (id_value); + + Init_Integer(id_value, id); + return Error_Invalid_Datatype_Raw(id_value); +} + + +// +// Error_No_Memory: C +// +REBCTX *Error_No_Memory(REBCNT bytes) +{ + DECLARE_LOCAL (bytes_value); + + Init_Integer(bytes_value, bytes); + return Error_No_Memory_Raw(bytes_value); +} + - for (dsf = DSF; dsf > 0; dsf = PRIOR_DSF(dsf)) { - if (start-- <= 0) { - val = Append_Value(blk); - Init_Word(val, VAL_WORD_SYM(DSF_WORD(dsf))); - } - } +// +// Error_Invalid_Arg_Core: C +// +// This error is pretty vague...it's just "invalid argument" +// and the value with no further commentary or context. It +// becomes a catch all for "unexpected input" when a more +// specific error would be more useful. +// +// Note that just `fail (value)` on REBVAL* will generate this error, this +// variant is used on RELVAL*. +// +REBCTX *Error_Invalid_Arg_Core(const RELVAL *value, REBSPC *specifier) +{ + DECLARE_LOCAL (specific); + Derelativize(specific, value, specifier); - return blk; + return Error_Invalid_Arg_Raw(specific); } - -/*********************************************************************** -** -*/ void Set_Error_Type(ERROR_OBJ *error) -/* -** Sets error type and id fields based on code number. -** -***********************************************************************/ -{ - REBSER *cats; // Error catalog object - REBSER *cat; // Error category object - REBCNT n; // Word symbol number - REBCNT code; - - code = VAL_INT32(&error->code); - - // Set error category: - n = code / 100 + 1; - cats = VAL_OBJ_FRAME(Get_System(SYS_CATALOG, CAT_ERRORS)); - - if (code >= 0 && n < SERIES_TAIL(cats) && - NZ(cat = VAL_SERIES(BLK_SKIP(cats, n))) - ) { - Set_Word(&error->type, FRM_WORD_SYM(cats, n), cats, n); - - // Find word related to the error itself: - - n = code % 100 + 3; - if (n < SERIES_TAIL(cat)) - Set_Word(&error->id, FRM_WORD_SYM(cat, n), cat, n); - } -} - - -/*********************************************************************** -** -*/ REBVAL *Find_Error_Info(ERROR_OBJ *error, REBINT *num) -/* -** Return the error message needed to print an error. -** Must scan the error catalog and its error lists. -** Note that the error type and id words no longer need -** to be bound to the error catalog context. -** If the message is not found, return null. -** -***********************************************************************/ -{ - REBSER *frame; - REBVAL *obj1; - REBVAL *obj2; - - if (!IS_WORD(&error->type) || !IS_WORD(&error->id)) return 0; - - // Find the correct error type object in the catalog: - frame = VAL_OBJ_FRAME(Get_System(SYS_CATALOG, CAT_ERRORS)); - obj1 = Find_Word_Value(frame, VAL_WORD_SYM(&error->type)); - if (!obj1) return 0; - - // Now find the correct error message for that type: - frame = VAL_OBJ_FRAME(obj1); - obj2 = Find_Word_Value(frame, VAL_WORD_SYM(&error->id)); - if (!obj2) return 0; - - if (num) { - obj1 = Find_Word_Value(frame, SYM_CODE); - *num = VAL_INT32(obj1) - + Find_Word_Index(frame, VAL_WORD_SYM(&error->id), FALSE) - - Find_Word_Index(frame, SYM_TYPE, FALSE) - 1; - } - - return obj2; -} - - -/*********************************************************************** -** -*/ void Make_Error_Object(REBVAL *arg, REBVAL *value) -/* -** Creates an error object from arg and puts it in value. -** The arg can be a string or an object body block. -** This function is called by MAKE ERROR!. -** -***********************************************************************/ -{ - REBSER *err; // Error object - ERROR_OBJ *error; // Error object values - REBINT code = 0; - - // Create a new error object from another object, including any non-standard fields: - if (IS_ERROR(arg) || IS_OBJECT(arg)) { - err = Merge_Frames(VAL_OBJ_FRAME(ROOT_ERROBJ), - IS_ERROR(arg) ? VAL_OBJ_FRAME(arg) : VAL_ERR_OBJECT(arg)); - error = ERR_VALUES(err); -// if (!IS_INTEGER(&error->code)) { - if (!Find_Error_Info(error, &code)) code = RE_INVALID_ERROR; - SET_INTEGER(&error->code, code); -// } - SET_ERROR(value, VAL_INT32(&error->code), err); - return; - } - - // Make a copy of the error object template: - err = CLONE_OBJECT(VAL_OBJ_FRAME(ROOT_ERROBJ)); - error = ERR_VALUES(err); - SET_NONE(&error->id); - SET_ERROR(value, 0, err); - - // If block arg, evaluate object values (checking done later): - // If user set error code, use it to setup type and id fields. - if (IS_BLOCK(arg)) { - DISABLE_GC; - Do_Bind_Block(err, arg); // GC-OK (disabled) - ENABLE_GC; - if (IS_INTEGER(&error->code) && VAL_INT64(&error->code)) { - Set_Error_Type(error); - } else { - if (Find_Error_Info(error, &code)) { - SET_INTEGER(&error->code, code); - } - } - // The error code is not valid: - if (IS_NONE(&error->id)) { - SET_INTEGER(&error->code, RE_INVALID_ERROR); - Set_Error_Type(error); - } - if (VAL_INT64(&error->code) < 100 || VAL_INT64(&error->code) > 1000) - Trap_Arg(arg); - } - - // If string arg, setup other fields - else if (IS_STRING(arg)) { - SET_INTEGER(&error->code, RE_USER); // user error - Set_String(&error->arg1, Copy_Series_Value(arg)); - Set_Error_Type(error); - } - -// No longer allowed: -// else if (IS_INTEGER(arg)) { -// error->code = *arg; -// Set_Error_Type(error); -// } - else - Trap_Arg(arg); - - if (!(VAL_ERR_NUM(value) = VAL_INT32(&error->code))) { - Trap_Arg(arg); - } + +// +// Error_Bad_Func_Def_Core: C +// +REBCTX *Error_Bad_Func_Def_Core(const RELVAL *item, REBSPC *specifier) +{ + DECLARE_LOCAL (specific); + Derelativize(specific, item, specifier); + return Error_Bad_Func_Def_Raw(specific); } -/*********************************************************************** -** -*/ REBSER *Make_Error(REBINT code, REBVAL *arg1, REBVAL *arg2, REBVAL *arg3) -/* -** Create and init a new error object. -** -***********************************************************************/ +// +// Error_Bad_Refine_Revoke: C +// +// We may have to search for the refinement, so we always do (speed of error +// creation not considered that relevant to the evaluator, being overshadowed +// by the error handling). See the remarks about the state of f->refine in +// the Reb_Frame definition. +// +REBCTX *Error_Bad_Refine_Revoke(REBFRM *f) { - REBSER *err; // Error object - ERROR_OBJ *error; // Error object values + assert(IS_TYPESET(f->param)); - if (PG_Boot_Phase < BOOT_ERRORS) Crash(RP_EARLY_ERROR, code); // Not far enough! + DECLARE_LOCAL (param_name); + Init_Word(param_name, VAL_PARAM_SPELLING(f->param)); - // Make a copy of the error object template: - err = CLONE_OBJECT(VAL_OBJ_FRAME(ROOT_ERROBJ)); - error = ERR_VALUES(err); + while (VAL_PARAM_CLASS(f->param) != PARAM_CLASS_REFINEMENT) + --f->param; - // Set error number: - SET_INTEGER(&error->code, (REBINT)code); - Set_Error_Type(error); + DECLARE_LOCAL (refine_name); + Init_Refinement(refine_name, VAL_PARAM_SPELLING(f->param)); - // Set error argument values: - if (arg1) error->arg1 = *arg1; - if (arg2) error->arg2 = *arg2; - if (arg3) error->arg3 = *arg3; + if (IS_VOID(f->arg)) // was void and shouldn't have been + return Error_Bad_Refine_Revoke_Raw(refine_name, param_name); + + // wasn't void and should have been + // + return Error_Argument_Revoked_Raw(refine_name, param_name); +} - // Set backtrace and location information: - if (DSF > 0) { - // Where (what function) is the error: - Set_Block(&error->where, Make_Backtrace(0)); - // Nearby location of the error (in block being evaluated): - error->nearest = *DSF_BACK(DSF); - } - return err; +// +// Error_No_Value_Core: C +// +REBCTX *Error_No_Value_Core(const RELVAL *target, REBSPC *specifier) { + DECLARE_LOCAL (specified); + Derelativize(specified, target, specifier); + + return Error_No_Value_Raw(specified); } -/*********************************************************************** -** -*/ void Trap0(REBCNT num) -/* -***********************************************************************/ +// +// Error_Partial_Lookback: C +// +REBCTX *Error_Partial_Lookback(REBFRM *f) { - Throw_Error(Make_Error(num, 0, 0, 0)); + DECLARE_LOCAL (label); + Init_Word(label, FRM_LABEL(f)); + + DECLARE_LOCAL (param_name); + Init_Word(param_name, VAL_PARAM_SPELLING(f->param)); + + return Error_Partial_Lookback_Raw(label, param_name); } -/*********************************************************************** -** -*/ void Trap1(REBCNT num, REBVAL *arg1) -/* -***********************************************************************/ +// +// Error_No_Value: C +// +REBCTX *Error_No_Value(const REBVAL *target) { + return Error_No_Value_Core(target, SPECIFIED); +} + + +// +// Error_No_Catch_For_Throw: C +// +REBCTX *Error_No_Catch_For_Throw(REBVAL *thrown) { - Throw_Error(Make_Error(num, arg1, 0, 0)); + DECLARE_LOCAL (arg); + + assert(THROWN(thrown)); + CATCH_THROWN(arg, thrown); // clears bit + + if (IS_BLANK(thrown)) + return Error_No_Catch_Raw(arg); + + return Error_No_Catch_Named_Raw(arg, thrown); } -/*********************************************************************** -** -*/ void Trap2(REBCNT num, REBVAL *arg1, REBVAL *arg2) -/* -***********************************************************************/ +// +// Error_Invalid_Type: C +// +// type is not allowed here. +// +REBCTX *Error_Invalid_Type(enum Reb_Kind kind) { - Throw_Error(Make_Error(num, arg1, arg2, 0)); + return Error_Invalid_Type_Raw(Get_Type(kind)); } -/*********************************************************************** -** -*/ void Trap3(REBCNT num, REBVAL *arg1, REBVAL *arg2, REBVAL *arg3) -/* -***********************************************************************/ +// +// Error_Out_Of_Range: C +// +// value out of range: +// +REBCTX *Error_Out_Of_Range(const REBVAL *arg) { - Throw_Error(Make_Error(num, arg1, arg2, arg3)); + return Error_Out_Of_Range_Raw(arg); } -/*********************************************************************** -** -*/ void Trap_Arg(REBVAL *arg) -/* -***********************************************************************/ +// +// Error_Protected_Key: C +// +REBCTX *Error_Protected_Key(REBVAL *key) { - Trap1(RE_INVALID_ARG, arg); + assert(IS_TYPESET(key)); + + DECLARE_LOCAL (key_name); + Init_Word(key_name, VAL_KEY_SPELLING(key)); + + return Error_Protected_Word_Raw(key_name); } -/*********************************************************************** -** -*/ void Trap_Type(REBVAL *arg) -/* -** type is not allowed here -** -***********************************************************************/ +// +// Error_Illegal_Action: C +// +REBCTX *Error_Illegal_Action(enum Reb_Kind type, REBSYM action) { - Trap1(RE_INVALID_TYPE, Of_Type(arg)); + DECLARE_LOCAL (action_word); + Init_Word(action_word, Canon(action)); + + return Error_Cannot_Use_Raw(action_word, Get_Type(type)); } -/*********************************************************************** -** -*/ void Trap_Range(REBVAL *arg) -/* -** value out of range: -** -***********************************************************************/ +// +// Error_Math_Args: C +// +REBCTX *Error_Math_Args(enum Reb_Kind type, REBSYM action) { - Trap1(RE_OUT_OF_RANGE, arg); + DECLARE_LOCAL (action_word); + Init_Word(action_word, Canon(action)); + + return Error_Not_Related_Raw(action_word, Get_Type(type)); } -/*********************************************************************** -** -*/ void Trap_Word(REBCNT num, REBCNT sym, REBVAL *arg) -/* -***********************************************************************/ +// +// Error_Unexpected_Type: C +// +REBCTX *Error_Unexpected_Type(enum Reb_Kind expected, enum Reb_Kind actual) { - Init_Word(DS_TOP, sym); - if (arg) Trap2(num, DS_TOP, arg); - else Trap1(num, DS_TOP); + assert(expected < REB_MAX); + assert(actual < REB_MAX); + + return Error_Expect_Val_Raw( + Get_Type(expected), + Get_Type(actual) + ); +} + + +// +// Error_Arg_Type: C +// +// Function in frame of `call` expected parameter `param` to be +// a type different than the arg given (which had `arg_type`) +// +REBCTX *Error_Arg_Type( + REBSTR *label, + const RELVAL *param, + enum Reb_Kind kind +) { + assert(IS_TYPESET(param)); + + DECLARE_LOCAL (param_word); + Init_Word(param_word, VAL_PARAM_SPELLING(param)); + + DECLARE_LOCAL (label_word); + Init_Word(label_word, label); + + if (kind != REB_MAX_VOID) { + assert(kind != REB_0); + REBVAL *datatype = Get_Type(kind); + assert(IS_DATATYPE(datatype)); + + return Error_Expect_Arg_Raw( + label_word, + datatype, + param_word + ); + } + + // Although REB_MAX_VOID is not a type, the typeset bits are used + // to check it. Since Get_Type() will fail, use another error. + // + return Error_Arg_Required_Raw( + label_word, + param_word + ); +} + + +// +// Error_Bad_Return_Type: C +// +REBCTX *Error_Bad_Return_Type(REBSTR *label, enum Reb_Kind kind) { + DECLARE_LOCAL (label_word); + Init_Word(label_word, label); + + if (kind == REB_MAX_VOID) + return Error_Needs_Return_Value_Raw(label_word); + + REBVAL *datatype = Get_Type(kind); + assert(IS_DATATYPE(datatype)); + return Error_Bad_Return_Type_Raw(label_word, datatype); } -/*********************************************************************** -** -*/ void Trap_Action(REBCNT type, REBCNT action) -/* -***********************************************************************/ +// +// Error_Bad_Make: C +// +REBCTX *Error_Bad_Make(enum Reb_Kind type, const REBVAL *spec) { - Trap2(RE_CANNOT_USE, Get_Action_Word(action), Get_Type(type)); + return Error_Bad_Make_Arg_Raw(Get_Type(type), spec); } -/*********************************************************************** -** -*/ void Trap_Math_Args(REBCNT type, REBCNT action) -/* -***********************************************************************/ +// +// Error_Cannot_Reflect: C +// +REBCTX *Error_Cannot_Reflect(enum Reb_Kind type, const REBVAL *arg) { - Trap2(RE_NOT_RELATED, Get_Action_Word(action), Get_Type(type)); + return Error_Cannot_Use_Raw(arg, Get_Type(type)); } -/*********************************************************************** -** -*/ void Trap_Types(REBCNT errnum, REBCNT type1, REBCNT type2) -/* -***********************************************************************/ +// +// Error_On_Port: C +// +REBCTX *Error_On_Port(REBCNT errnum, REBCTX *port, REBINT err_code) { - if (type2 != 0) Trap2(errnum, Get_Type(type1), Get_Type(type2)); - Trap1(errnum, Get_Type(type1)); + REBVAL *spec = CTX_VAR(port, STD_PORT_SPEC); + if (!IS_OBJECT(spec)) + fail (Error_Invalid_Port_Raw()); + + REBVAL *val = VAL_CONTEXT_VAR(spec, STD_PORT_SPEC_HEAD_REF); // informative + if (IS_BLANK(val)) + val = VAL_CONTEXT_VAR(spec, STD_PORT_SPEC_HEAD_TITLE); // less info + + DECLARE_LOCAL (err_code_value); + Init_Integer(err_code_value, err_code); + + return Error(errnum, val, err_code_value, END); } -/*********************************************************************** -** -*/ void Trap_Expect(REBVAL *object, REBCNT index, REBCNT type) -/* -** Object field is not of expected type. -** PORT expected SCHEME of OBJECT type -** -***********************************************************************/ +// +// Exit_Status_From_Value: C +// +// This routine's job is to turn an arbitrary value into an +// operating system exit status: +// +// https://en.wikipedia.org/wiki/Exit_status +// +int Exit_Status_From_Value(REBVAL *value) { - Trap3(RE_EXPECT_TYPE, Of_Type(object), Obj_Word(object, index), Get_Type(type)); + assert(!THROWN(value)); + + if (IS_INTEGER(value)) { + // Fairly obviously, an integer should return an integer + // result. But Rebol integers are 64 bit and signed, while + // exit statuses don't go that large. + // + return VAL_INT32(value); + } + else if (IS_VOID(value) || IS_BLANK(value)) { + // An unset would happen with just QUIT or EXIT and no /WITH, + // so treating that as a 0 for success makes sense. A NONE! + // seems like nothing to report as well, for instance: + // + // exit/with if badthing [badthing-code] + // + return 0; + } + else if (IS_ERROR(value)) { + // Rebol errors do have an error number in them, and if your + // program tries to return a Rebol error it seems it wouldn't + // hurt to try using that. They may be out of range for + // platforms using byte-sized error codes, however...but if + // that causes bad things OS_EXIT() should be graceful about it. + // + return VAL_ERR_NUM(value); + } + + // Just 1 otherwise. + // + return 1; } -/*********************************************************************** -** -*/ void Trap_Make(REBCNT type, REBVAL *spec) -/* -***********************************************************************/ +// +// Startup_Errors: C +// +// Create error objects and error type objects +// +REBCTX *Startup_Errors(REBARR *boot_errors) { - Trap2(RE_BAD_MAKE_ARG, Get_Type(type), spec); + REBCTX *catalog = Construct_Context( + REB_OBJECT, + ARR_HEAD(boot_errors), + SPECIFIED, // we're confident source array isn't in a function body + NULL + ); + + // Create objects for all error types (CAT_ERRORS is "selfish", currently + // so self is in slot 1 and the actual errors start at context slot 2) + // + REBVAL *val; + for (val = CTX_VAR(catalog, SELFISH(1)); NOT_END(val); val++) { + REBCTX *error = Construct_Context( + REB_OBJECT, + VAL_ARRAY_HEAD(val), + SPECIFIED, // source array not in a function body + NULL + ); + Init_Object(val, error); + } + + return catalog; } -/*********************************************************************** -** -*/ void Trap_Num(REBCNT err, REBCNT num) -/* -***********************************************************************/ +// +// Security_Policy: C +// +// Given a security symbol (like FILE) and a value (like the file +// path) returns the security policy (RWX) allowed for it. +// +// Args: +// +// sym: word that represents the type ['file 'net] +// name: file or path value +// +// Returns BTYE array of flags for the policy class: +// +// flags: [rrrr wwww xxxx ----] +// +// Where each byte is: +// 0: SEC_ALLOW +// 1: SEC_ASK +// 2: SEC_THROW +// 3: SEC_QUIT +// +// The secuity is defined by the system/state/policies object, that +// is of the form: +// +// [ +// file: [%file1 tuple-flags %file2 ... default tuple-flags] +// net: [...] +// call: tuple-flags +// stack: tuple-flags +// eval: integer (limit) +// ] +// +REBYTE *Security_Policy(REBSTR *spelling, REBVAL *name) { - DS_PUSH_INTEGER(num); - Trap1(err, DS_TOP); + REBVAL *policy = Get_System(SYS_STATE, STATE_POLICIES); + REBYTE *flags; + REBCNT len; + REBCNT errcode = RE_SECURITY_ERROR; + + if (!IS_OBJECT(policy)) goto error; + + // Find the security class in the block: (file net call...) + policy = Select_Canon_In_Context(VAL_CONTEXT(policy), STR_CANON(spelling)); + if (!policy) goto error; + + // Obtain the policies for it: + // Check for a master tuple: [file rrrr.wwww.xxxx] + if (IS_TUPLE(policy)) return VAL_TUPLE(policy); // non-aligned + // removed A90: if (IS_INTEGER(policy)) return (REBYTE*)VAL_INT64(policy); // probably not used + + // Only other form is detailed block: + if (!IS_BLOCK(policy)) goto error; + + // Scan block of policies for the class: [file [allow read quit write]] + len = 0; // file or url length + flags = 0; // policy flags + + policy = KNOWN(VAL_ARRAY_HEAD(policy)); // no relatives in STATE_POLICIES + + for (; NOT_END(policy); policy += 2) { + + // Must be a policy tuple: + if (!IS_TUPLE(policy+1)) goto error; + + // Is it a policy word: + if (IS_WORD(policy)) { // any word works here + // If no strings found, use the default: + if (len == 0) flags = VAL_TUPLE(policy+1); // non-aligned + } + + // Is it a string (file or URL): + else if (ANY_BINSTR(policy) && name) { + if (Match_Sub_Path(VAL_SERIES(policy), VAL_SERIES(name))) { + // Is the match adequate? + if (VAL_LEN_HEAD(name) >= len) { + len = VAL_LEN_HEAD(name); + flags = VAL_TUPLE(policy+1); // non-aligned + } + } + } + else goto error; + } + + if (!flags) { + errcode = RE_SECURITY; + policy = name ? name : 0; + + error: + ; // need statement + DECLARE_LOCAL (temp); + if (!policy) { + Init_Word(temp, spelling); + policy = temp; + } + fail (Error(errcode, policy)); + } + + return flags; } -/*********************************************************************** -** -*/ void Trap_Reflect(REBCNT type, REBVAL *arg) -/* -***********************************************************************/ +// +// Trap_Security: C +// +// Take action on the policy flags provided. The sym and value +// are provided for error message purposes only. +// +void Trap_Security(REBCNT flag, REBSTR *sym, REBVAL *value) { - Trap_Arg(arg); + if (flag == SEC_THROW) { + if (!value) { + Init_Word(DS_TOP, sym); + value = DS_TOP; + } + fail (Error_Security_Raw(value)); + } + else if (flag == SEC_QUIT) OS_EXIT(101); } -/*********************************************************************** -** -*/ void Trap_Port(REBCNT errnum, REBSER *port, REBINT err_code) -/* -***********************************************************************/ +// +// Check_Security: C +// +// A helper function that fetches the security flags for +// a given symbol (FILE) and value (path), and then tests +// that they are allowed. +// +void Check_Security(REBSTR *sym, REBCNT policy, REBVAL *value) { - REBVAL *spec = OFV(port, STD_PORT_SPEC); - REBVAL *val; + REBYTE *flags; + + flags = Security_Policy(sym, value); + Trap_Security(flags[policy], sym, value); +} - if (!IS_OBJECT(spec)) Trap0(RE_INVALID_PORT); - val = Get_Object(spec, STD_PORT_SPEC_HEAD_REF); // most informative - if (IS_NONE(val)) val = Get_Object(spec, STD_PORT_SPEC_HEAD_TITLE); +// +// Make_OS_Error: C +// +void Make_OS_Error(REBVAL *out, int errnum) +{ + REBCHR str[100]; - DS_PUSH_INTEGER(err_code); - Trap2(errnum, val, DS_TOP); + OS_FORM_ERROR(errnum, str, 100); + Init_String(out, Copy_OS_Str(str, OS_STRLEN(str))); } -/*********************************************************************** -** -*/ REBINT Check_Error(REBVAL *val) -/* -** Process a loop exceptions. Pass in the TOS value, returns: -** -** 2 - if break/return, change val to that set by break -** 1 - if break -** -1 - if continue, change val to unset -** 0 - if not break or continue -** else: error if not an ERROR value -** -***********************************************************************/ +// +// Find_Next_Error_Base_Code: C +// +// Find in system/catalog/errors the next error base (used by extensions) +// +REBINT Find_Next_Error_Base_Code(void) { - // It's UNSET, not an error: - if (!IS_ERROR(val)) - Trap0(RE_NO_RETURN); //!!! change to special msg - - // If it's a BREAK, check for /return value: - if (IS_BREAK(val)) { - if (VAL_ERR_VALUE(val)) { - *val = *VAL_ERR_VALUE(val); - return 2; - } else { - SET_UNSET(val); - return 1; - } - } - - if (IS_CONTINUE(val)) { - SET_UNSET(val); - return -1; - } - - return 0; - // Else: Let all other errors return as values. -} - - -/*********************************************************************** -** -*/ void Init_Errors(REBVAL *errors) -/* -***********************************************************************/ -{ - REBSER *errs; - REBVAL *val; - - // Create error objects and error type objects: - *ROOT_ERROBJ = *Get_System(SYS_STANDARD, STD_ERROR); - errs = Construct_Object(0, VAL_BLK(errors), 0); - Set_Object(Get_System(SYS_CATALOG, CAT_ERRORS), errs); - - Set_Root_Series(TASK_ERR_TEMPS, Make_Block(3), "task errors"); - - // Create objects for all error types: - for (val = BLK_SKIP(errs, 1); NOT_END(val); val++) { - errs = Construct_Object(0, VAL_BLK(val), 0); - SET_OBJECT(val, errs); - } - - // Catch top level errors, to provide decent output: - PUSH_STATE(Top_State, Saved_State); - if (SET_JUMP(Top_State)) { - POP_STATE(Top_State, Saved_State); - DSP++; // Room for return value - Catch_Error(DS_TOP); // Stores error value here - Print_Value(DS_TOP, 0, FALSE); - Crash(RP_NO_CATCH); - } - SET_STATE(Top_State, Saved_State); -} - - -/*********************************************************************** -** -*/ REBYTE *Security_Policy(REBCNT sym, REBVAL *name) -/* -** Given a security symbol (like FILE) and a value (like the file -** path) returns the security policy (RWX) allowed for it. -** -** Args: -** -** sym: word that represents the type ['file 'net] -** name: file or path value -** -** Returns BTYE array of flags for the policy class: -** -** flags: [rrrr wwww xxxx ----] -** -** Where each byte is: -** 0: SEC_ALLOW -** 1: SEC_ASK -** 2: SEC_THROW -** 3: SEC_QUIT -** -** The secuity is defined by the system/state/policies object, that -** is of the form: -** -** [ -** file: [%file1 tuple-flags %file2 ... default tuple-flags] -** net: [...] -** call: tuple-flags -** stack: tuple-flags -** eval: integer (limit) -** ] -** -***********************************************************************/ -{ - REBVAL *policy = Get_System(SYS_STATE, STATE_POLICIES); - REBYTE *flags; - REBCNT len; - REBCNT errcode = RE_SECURITY_ERROR; - - if (!IS_OBJECT(policy)) goto error; - - // Find the security class in the block: (file net call...) - policy = Find_Word_Value(VAL_OBJ_FRAME(policy), sym); - if (!policy) goto error; - - // Obtain the policies for it: - // Check for a master tuple: [file rrrr.wwww.xxxx] - if (IS_TUPLE(policy)) return VAL_TUPLE(policy); // non-aligned - // removed A90: if (IS_INTEGER(policy)) return (REBYTE*)VAL_INT64(policy); // probably not used - - // Only other form is detailed block: - if (!IS_BLOCK(policy)) goto error; - - // Scan block of policies for the class: [file [allow read quit write]] - len = 0; // file or url length - flags = 0; // policy flags - for (policy = VAL_BLK(policy); NOT_END(policy); policy += 2) { - - // Must be a policy tuple: - if (!IS_TUPLE(policy+1)) goto error; - - // Is it a policy word: - if (IS_WORD(policy)) { // any word works here - // If no strings found, use the default: - if (len == 0) flags = VAL_TUPLE(policy+1); // non-aligned - } - - // Is it a string (file or URL): - else if (ANY_BINSTR(policy) && name) { - //Debug_Fmt("sec: %r %r", policy, name); - if (Match_Sub_Path(VAL_SERIES(policy), VAL_SERIES(name))) { - // Is the match adequate? - if (VAL_TAIL(name) >= len) { - len = VAL_TAIL(name); - flags = VAL_TUPLE(policy+1); // non-aligned - } - } - } - else goto error; - } - - if (!flags) { - errcode = RE_SECURITY; - policy = name ? name : 0; -error: - if (!policy) { - Init_Word(DS_TOP, sym); - policy = DS_TOP; - } - Trap1(errcode, policy); - } - - return flags; -} - - -/*********************************************************************** -** -*/ void Trap_Security(REBCNT flag, REBCNT sym, REBVAL *value) -/* -** Take action on the policy flags provided. The sym and value -** are provided for error message purposes only. -** -***********************************************************************/ -{ - if (flag == SEC_THROW) { - if (!value) { - Init_Word(DS_TOP, sym); - value = DS_TOP; - } - Trap1(RE_SECURITY, value); - } - else if (flag == SEC_QUIT) OS_EXIT(101); -} - - -/*********************************************************************** -** -*/ void Check_Security(REBCNT sym, REBCNT policy, REBVAL *value) -/* -** A helper function that fetches the security flags for -** a given symbol (FILE) and value (path), and then tests -** that they are allowed. -** -***********************************************************************/ -{ - REBYTE *flags; - - flags = Security_Policy(sym, value); - Trap_Security(flags[policy], sym, value); + REBCTX * categories = VAL_CONTEXT(Get_System(SYS_CATALOG, CAT_ERRORS)); + if (CTX_LEN(categories) > RE_USER / RE_CATEGORY_SIZE) + fail (Error_Out_Of_Error_Numbers_Raw()); + return (CTX_LEN(categories) - 1) * RE_CATEGORY_SIZE; } diff --git a/src/core/c-eval.c b/src/core/c-eval.c new file mode 100644 index 0000000000..0ab889a9c4 --- /dev/null +++ b/src/core/c-eval.c @@ -0,0 +1,2087 @@ +// +// File: %c-eval.c +// Summary: "Central Interpreter Evaluator" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This file contains `Do_Core()`, which is the central evaluator which +// is behind DO. It can execute single evaluation steps (e.g. a DO/NEXT) +// or it can run the array to the end of its content. A flag controls that +// behavior, and there are DO_FLAG_XXX for controlling other behaviors. +// +// For comprehensive notes on the input parameters, output parameters, and +// internal state variables...see %sys-rebfrm.h. +// +// NOTES: +// +// * Do_Core() is a very long routine. That is largely on purpose, because it +// doesn't contain repeated portions. If it were broken into functions that +// would add overhead for little benefit, and prevent interesting tricks +// and optimizations. Note that it is separated into sections, and +// the invariants in each section are made clear with comments and asserts. +// +// * The evaluator only moves forward, and it consumes exactly one element +// from the input at a time. This input may be a source where the index +// needs to be tracked and care taken to contain the index within its +// boundaries in the face of change (e.g. a mutable ARRAY). Or it may be +// an entity which tracks its own position on each fetch (e.g. a C va_list) +// + +#include "sys-core.h" + + +#if !defined(NDEBUG) + // + // The `do_count` should be visible in the C debugger watchlist as a + // local variable in Do_Core() for each stack level. So if a fail() + // happens at a deterministic moment in a run, capture the number from + // the level of interest and recompile with it here to get a breakpoint + // at that tick. + // + // Notice also that in debug builds, frames carry this value in them. + // *Plus* you can get the initialization tick for void cells, BLANK!s, + // LOGIC!s, and most end markers by looking at the `track` payload of + // the REBVAL cell. And series contain the do_count where they were + // created as well. + // + // *** DON'T COMMIT THIS v-- KEEP IT AT ZERO! *** + #define DO_COUNT_BREAKPOINT 0 + // *** DON'T COMMIT THIS --^ KEEP IT AT ZERO! *** + // + // Note: Taking this number on the command line sounds convenient, though + // with command line processing in usermode it would throw the number off + // between runs. + // + // Note also there is `Dump_Frame_Location()` if there's a trouble spot + // and you want to see what the state is. It will reify C va_list + // input for you, so you can see what the C caller passed as an array. + // +#endif + + +static inline REBOOL Start_New_Expression_Throws(REBFRM *f) { + assert(Eval_Count >= 0); + if (--Eval_Count == 0) { + // + // Note that Do_Signals_Throws() may do a recycle step of the GC, or + // it may spawn an entire interactive debugging session via + // breakpoint before it returns. It may also FAIL and longjmp out. + // + SET_END(&f->cell); + if (Do_Signals_Throws(&f->cell)) { + Move_Value(f->out, &f->cell); + return TRUE; + } + + assert(IS_END(&f->cell)); + } + + UPDATE_EXPRESSION_START(f); // !!! See FRM_INDEX() for caveats + if (Trace_Flags) + Trace_Line(f); + + return FALSE; +} + +#define START_NEW_EXPRESSION_MAY_THROW_COMMON(f,g) \ + if (Start_New_Expression_Throws(f)) \ + g; \ + args_evaluate = NOT((f)->flags.bits & DO_FLAG_NO_ARGS_EVALUATE); \ + +#ifdef NDEBUG + #define START_NEW_EXPRESSION_MAY_THROW(f,g) \ + START_NEW_EXPRESSION_MAY_THROW_COMMON(f, g) +#else + // Macro is used to mutate local do_count variable in Do_Core (for easier + // browsing in the watchlist) as well as to not be in a deeper stack level + // than Do_Core when a DO_COUNT_BREAKPOINT is hit. + // + #define START_NEW_EXPRESSION_MAY_THROW(f,g) \ + do { \ + START_NEW_EXPRESSION_MAY_THROW_COMMON(f, g); \ + do_count = Do_Core_Expression_Checks_Debug(f); \ + if (do_count == DO_COUNT_BREAKPOINT) { \ + Debug_Fmt("DO_COUNT_BREAKPOINT at %d", f->do_count_debug); \ + Dump_Frame_Location(f); \ + debug_break(); /* see %debug_break.h */ \ + } \ + } while (FALSE) +#endif + +static inline void Drop_Function_Args_For_Frame(REBFRM *f) { + Drop_Function_Args_For_Frame_Core(f, TRUE); +} + +static inline void Abort_Function_Args_For_Frame(REBFRM *f) { + Drop_Function_Args_For_Frame(f); + + // If a function call is aborted, there may be pending refinements (if + // in the gathering phase) or functions (if running a chainer) on the + // data stack. They must be dropped to balance. + // + DS_DROP_TO(f->dsp_orig); +} + +static inline void Link_Vararg_Param_To_Frame(REBFRM *f, REBOOL make) { + assert(GET_VAL_FLAG(f->param, TYPESET_FLAG_VARIADIC)); + + // Note that this varlist is to a context with bad cells in + // any unfilled arg slots. Because of this, there needs to + // be special handling in the GC that knows *not* to try + // and walk these incomplete arrays sitting in the argument + // slots if they're not ready... + // + if ( + f->varlist == NULL + || NOT_SER_FLAG(f->varlist, ARRAY_FLAG_VARLIST) + ){ + // Don't use ordinary call to Context_For_Frame_May_Reify + // because this special case allows reification even + // though the frame is pending. + // + Reify_Frame_Context_Maybe_Fulfilling(f); + } + f->arg->extra.binding = f->varlist; + + // Store the offset so that both the f->arg and f->param locations can + // be quickly recovered, while using only a single slot in the REBVAL. + // + f->arg->payload.varargs.param_offset = f->arg - f->args_head; + + // The data feed doesn't necessarily come from the frame + // that has the parameter and the argument. A varlist may be + // chained such that its data came from another frame, or just + // an ordinary array. + // + if (make) { + VAL_RESET_HEADER(f->arg, REB_VARARGS); + f->arg->payload.varargs.feed = f->varlist; + } + else + assert(VAL_TYPE(f->arg) == REB_VARARGS); + + assert(GET_SER_FLAG(f->arg->payload.varargs.feed, SERIES_FLAG_ARRAY)); +} + + +// +// f->refine is a bit tricky. If it IS_LOGIC() and TRUE, then this means that +// a refinement is active but revokable, having its arguments gathered. So +// it actually points to the f->arg of the active refinement slot. If +// evaluation of an argument in this state produces no value, the refinement +// must be revoked, and its value mutated to be FALSE. +// +// But all the other values that f->refine can hold are read-only pointers +// that signal something about the argument gathering state: +// +// * If VOID_CELL, then refinements are being skipped and the arguments +// that follow should not be written to. +// +// * If BLANK_VALUE, this is an arg to a refinement that was not used in +// the invocation. No consumption should be performed, arguments should +// be written as unset, and any non-unset specializations of arguments +// should trigger an error. +// +// * If FALSE_VALUE, this is an arg to a refinement that was used in the +// invocation but has been *revoked*. It still consumes expressions +// from the callsite for each remaining argument, but those expressions +// must not evaluate to any value. +// +// * If EMPTY_BLOCK, it's an ordinary arg...and not a refinement. It will +// be evaluated normally but is not involved with revocation. +// +// * If EMPTY_STRING, the evaluator's next argument fulfillment is the +// left-hand argument of a lookback operation. After that fulfillment, +// it will be transitioned to EMPTY_BLOCK. +// +// Because of how this lays out, IS_CONDITIONAL_TRUE() can be used to +// determine if an argument should be type checked normally...while +// IS_CONDITIONAL_FALSE() means that the arg's bits must be set to void. +// +// These special values are all pointers to read-only cells, but are cast to +// mutable in order to be held in the same pointer that might write to a +// refinement to revoke it. Note that since literal pointers are used, tests +// like `f->refine == BLANK_VALUE` are faster than `IS_BLANK(f->refine)`. +// +#define SKIPPING_REFINEMENT_ARGS m_cast(REBVAL*, VOID_CELL) +#define ARG_TO_UNUSED_REFINEMENT m_cast(REBVAL*, BLANK_VALUE) +#define ARG_TO_REVOKED_REFINEMENT m_cast(REBVAL*, FALSE_VALUE) +#define ORDINARY_ARG m_cast(REBVAL*, EMPTY_BLOCK) +#define LOOKBACK_ARG m_cast(REBVAL*, EMPTY_STRING) + + +// +// Do_Core: C +// +// While this routine looks very complex, it's actually not that difficult +// to step through. A lot of it is assertions, debug tracking, and comments. +// +// Comments on the definition of Reb_Frame are a good place to start looking +// to understand what's going on. See %sys-rebfrm.h for full details. +// +// These fields are required upon initialization: +// +// f->out* +// REBVAL pointer to which the evaluation's result should be written, +// must point to initialized bits, and that needs to be an END marker, +// unless it's in lookback mode, in which case it must be the REBVAL to +// use as first argument (infix/postfix/"enfixed" functions) +// +// f->value +// Fetched first value to execute (cannot be an END marker) +// +// f->source +// Contains the REBARR* or C va_list of subsequent values to fetch +// +// f->index +// Needed if f->source is an array (can be garbage if it's a C va_list) +// +// f->pending +// Must be VA_LIST_PENDING if source is a va_list, else starts out NULL +// +// f->specifier +// Resolver for bindings of values in f->source, SPECIFIED if all resolved +// +// f->gotten +// Must be either be the Get_Var() lookup of f->value, or NULL +// +// More detailed assertions of the preconditions, postconditions, and state +// at each evaluation step are contained in %d-eval.c +// +void Do_Core(REBFRM * const f) +{ +#if !defined(NDEBUG) + REBUPT do_count = f->do_count_debug = TG_Do_Count; // snapshot start tick +#endif + + REBOOL args_evaluate; // set on every iteration (varargs do, EVAL/ONLY...) + + // APPLY and a DO of a FRAME! both use this same code path. + // + if (f->flags.bits & DO_FLAG_APPLYING) { + f->eval_type = REB_FUNCTION; + args_evaluate = NOT(f->flags.bits & DO_FLAG_NO_ARGS_EVALUATE); + f->refine = ORDINARY_ARG; // "APPLY infix" not supported + goto do_function_arglist_in_progress; + } + + // Some initialized bit pattern is needed to check to see if a + // function call is actually in progress, or if eval_type is just + // REB_FUNCTION but doesn't have valid args/state. The label is a + // good choice because it is only affected by the function call case, + // see Is_Function_Frame_Fulfilling(). + // + f->label = NULL; + f->eval_type = VAL_TYPE(f->value); + +#if !defined(NDEBUG) + SNAP_STATE(&f->state_debug); // to make sure stack balances, etc. + Do_Core_Entry_Checks_Debug(f); // run once per Do_Core() +#endif + + // This is an important guarantee...the out slot needs to have some form + // of initialization to allow GC. END is chosen because that is what + // natives can count on the f->out slot to be, but lookback arguments + // also are passed by way of the out slot. + // + assert(NOT(IS_TRASH_DEBUG(f->out))); + + // Capture the data stack pointer on entry (used by debug checks, but + // also refinements are pushed to stack and need to be checked if there + // are any that are not processed) + // + f->dsp_orig = DSP; + +do_next:; + + START_NEW_EXPRESSION_MAY_THROW(f, goto finished); + // ^-- sets args_evaluate, do_count, Ctrl-C may abort + +reevaluate:; + // + // ^-- doesn't advance expression index, so `eval x` starts with `eval` + // also EVAL/ONLY may change args_evaluate to FALSE for a cycle + + //==////////////////////////////////////////////////////////////////==// + // + // LOOKAHEAD TO ENABLE ENFIXED FUNCTIONS THAT QUOTE THEIR LEFT ARG + // + //==////////////////////////////////////////////////////////////////==// + + // Ren-C has an additional lookahead step *before* an evaluation in order + // to take care of this scenario. To do this, it pre-emptively feeds the + // frame one unit that f->value is the *next* value, and a local variable + // called "current" holds the current head of the expression that the + // switch will be processing. + // + // Additionally, it attempts to reuse any lookahead fetching done with + // Get_Var. In the general case, this is not going to be possible, e.g.: + // + // obj: make object! [x: 10] + // foo: does [append obj [y: 20]] + // do in obj [foo x] + // + // Consider the lookahead fetch for `foo x`. It will get x to f->gotten, + // and see that it is not a lookback function. But then when it runs foo, + // the memory location where x had been found before may have moved due + // to expansion. Basically any function call invalidates f->gotten, as + // does obviously any Fetch_Next_In_Frame (because the position changes) + // + // !!! Review how often gotten has hits vs. misses, and what the benefit + // of the feature actually is. + + const RELVAL *current; + const REBVAL *current_gotten; + + current = f->value; // <-- DO_COUNT_BREAKPOINT landing spot + current_gotten = f->gotten; + f->gotten = END; + Fetch_Next_In_Frame(f); + + // !!! We never want to do infix processing if the args aren't evaluating + // (e.g. arguments in a va_list from a C function calling into Rebol) + // But this is distinct from DO_FLAG_NO_LOOKAHEAD (which Apply_Only also + // sets), which really controls the after lookahead step. Consider this + // edge case. + // + if (NOT_END(f->value) && IS_WORD(f->value) && args_evaluate) { + // + // While the next item may be a WORD! that looks up to an enfixed + // function, and it may want to quote what's on its left...there + // could be a conflict. This happens if the current item is also + // a WORD!, but one that looks up to a prefix function that wants + // to quote what's on its right! + // + if (f->eval_type == REB_WORD) { + if (current_gotten == END) + current_gotten = Get_Opt_Var_Else_End(current, f->specifier); + else + assert( + current_gotten + == Get_Opt_Var_Else_End(current, f->specifier) + ); + + if ( + VAL_TYPE_OR_0(current_gotten) == REB_FUNCTION // END is REB_0 + && NOT_VAL_FLAG(current_gotten, VALUE_FLAG_ENFIXED) + && GET_VAL_FLAG(current_gotten, FUNC_FLAG_QUOTES_FIRST_ARG) + ){ + // Yup, it quotes. We could look for a conflict and call + // it an error, but instead give the left hand side precedence + // over the right. This means something like: + // + // foo: quote -> [print quote] + // + // Would be interpreted as: + // + // foo: (quote ->) [print quote] + // + // This is a good argument for not making enfixed operations + // that hard-quote things that can dispatch functions. A + // soft-quote would give more flexibility to override the + // left hand side's precedence, e.g. the user writes: + // + // foo: ('quote) -> [print quote] + // + f->eval_type = REB_FUNCTION; + SET_FRAME_LABEL(f, VAL_WORD_SPELLING(current)); + f->refine = ORDINARY_ARG; + goto do_function_in_current_gotten; + } + } + + f->gotten = Get_Opt_Var_Else_End(f->value, f->specifier); + + if ( + VAL_TYPE_OR_0(f->gotten) == REB_FUNCTION // END is REB_0 + && ALL_VAL_FLAGS( + f->gotten, VALUE_FLAG_ENFIXED | FUNC_FLAG_QUOTES_FIRST_ARG + ) + ){ + f->eval_type = REB_FUNCTION; + SET_FRAME_LABEL(f, VAL_WORD_SPELLING(f->value)); + + // The protocol for lookback is that the lookback argument is + // consumed from the f->out slot. It will ultimately wind up + // moved into the frame, so having the quoting cases get + // it there by way of the f->out is *slightly* inefficient. But + // since evaluative cases do wind up with the value in f->out, + // and are much more common, it's not worth worrying about. + // + f->refine = LOOKBACK_ARG; + Derelativize(f->out, current, f->specifier); + + #if !defined(NDEBUG) + // + // Since the value is going to be copied into an arg slot anyway, + // setting the unevaluated flag here isn't necessary. However, + // it allows for an added debug check that if an enfixed parameter + // is hard or soft quoted, it *probably* came from here. + // + SET_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED); + #endif + + current_gotten = f->gotten; // the function + + // We don't want the WORD! that invoked the function to act like + // an argument, so we have to advance the frame once more. + // + f->gotten = END; + Fetch_Next_In_Frame(f); + + goto do_function_in_current_gotten; + } + } + + //==////////////////////////////////////////////////////////////////==// + // + // BEGIN MAIN SWITCH STATEMENT + // + //==////////////////////////////////////////////////////////////////==// + + // This switch is done via contiguous REB_XXX values, in order to + // facilitate use of a "jump table optimization": + // + // http://stackoverflow.com/questions/17061967/c-switch-and-jump-tables + + switch (f->eval_type) { + + case REB_0: + assert(FALSE); // internal type. + break; + +//==//////////////////////////////////////////////////////////////////////==// +// +// [FUNCTION!] (lookback or non-lookback) +// +// If a function makes it to the SWITCH statement, that means it is either +// literally a function value in the array (`do compose [(:+) 1 2]`) or is +// being retriggered via EVAL +// +// Most function evaluations are triggered from a SWITCH on a WORD! or PATH!, +// which jumps in at the `do_function_in_current_gotten` label. +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_FUNCTION: // literal function in a block + current_gotten = const_KNOWN(current); + SET_FRAME_LABEL(f, Canon(SYM___ANONYMOUS__)); // nameless literal + + if (GET_VAL_FLAG(current_gotten, VALUE_FLAG_ENFIXED)) { + // + // f->out can't be trash, but it can be an END. + // + f->refine = LOOKBACK_ARG; + } + else { + SET_END(f->out); // clear out previous result (needs GC-safe data) + f->refine = ORDINARY_ARG; + } + + do_function_in_current_gotten: + assert(IS_FUNCTION(current_gotten)); + assert(f->eval_type == REB_FUNCTION); + TRASH_POINTER_IF_DEBUG(current); // shouldn't be used below + + // There may be refinements pushed to the data stack to process, if + // the call originated from a path dispatch. + // + assert(DSP >= f->dsp_orig); + + //==////////////////////////////////////////////////////////////////==// + // + // FUNCTION! NORMAL ARGUMENT FULFILLMENT PROCESS + // + //==////////////////////////////////////////////////////////////////==// + + // We assume you can enumerate both the formal parameters (in the + // spec) and the actual arguments (in the call frame) using pointer + // incrementation, that they are both terminated by END, and + // that there are an equal number of values in both. + // + // Push_Or_Alloc_Args sets the frame's function, sets args_head... + // + Push_Or_Alloc_Args_For_Underlying_Func(f, current_gotten); + + do_function_arglist_in_progress: + + assert(f->label != NULL); // must be something (even "anonymous") + #if !defined(NDEBUG) + assert(f->label_debug != NULL); // SET_FRAME_LABEL sets (C debugging) + #endif + + Eval_Functions++; // this isn't free...is it worth tracking? + + // Now that we have extracted f->phase, we do not have to worry that + // f->value might have lived in f->cell.eval. We can't overwrite + // f->out during the argument evaluations, in case that is holding the + // first argument to an infix function, so f->cell gets used for + // temporary evaluations up until the point the function gets called. + + assert(f->refine == ORDINARY_ARG || f->refine == LOOKBACK_ARG); + + f->arg = f->args_head; + f->param = FUNC_FACADE_HEAD(f->phase); + // f->special is END, f->args_head, or first specialized value + + // Same as check before switch. (do_function_arglist_in_progress: + // might have a goto from another point, so we check it again here) + // + assert(IS_END(f->out) || f->refine == LOOKBACK_ARG); + + //==////////////////////////////////////////////////////////////////==// + // + // FUNCTION! NORMAL ARGUMENT FULFILLMENT LOOP + // + //==////////////////////////////////////////////////////////////////==// + + // This loop goes through the parameter and argument slots. Though + // the argument slots must be protected from garbage collection once + // they are filled, they start out uninitialized. (The GC has access + // to the frame list, so it can examine f->arg and avoid trying to + // protect slots that come after it.) + // + // Based on the parameter type, it may be necessary to "consume" an + // expression from values that come after the invocation point. But + // not all params will consume arguments for all calls. + // + // This one body of code to is able to handle both function + // specialization and ordinary invocation. f->special is used to + // either step through a list of specialized values (with void as a + // signal of no specialization), to step through the arguments if + // they are just being type checked, or END otherwise. + + enum Reb_Param_Class pclass; // gotos would cross it if inside loop + + f->doing_pickups = FALSE; // still looking for way to encode in refine + + while (NOT_END(f->param)) { + pclass = VAL_PARAM_CLASS(f->param); + + //=//// A /REFINEMENT ARG /////////////////////////////////////////////=// + + // Refinements are checked first for a reason. This is to + // short-circuit based on the `doing_pickups` flag before redoing + // fulfillments on arguments that have already been handled. + // + // The reason an argument might have already been handled is + // that some refinements have to reach back and be revisited after + // the original parameter walk. They can't be fulfilled in a + // single pass because these two calls mean different things: + // + // foo: func [a /b c /d e] [...] + // + // foo/b/d (1 + 2) (3 + 4) (5 + 6) + // foo/d/b (1 + 2) (3 + 4) (5 + 6) + // + // The order of refinements in the definition (b d) may not match + // what order the refinements are invoked in the path. This means + // the "visitation order" of the parameters while walking across + // parameters in the array might not match the "consumption order" + // of the expressions that are being fetched from the callsite. + // + // Hence refinements are targeted to be revisited by "pickups" + // after the initial parameter walk. An out-of-order refinement + // makes a note in the stack about a parameter and arg position + // it sees that it will need to come back to. A REB_0_PICKUP + // is used to track this (it holds a cache of the parameter and + // argument position). + + if (pclass == PARAM_CLASS_REFINEMENT) { + + if (f->doing_pickups) { + f->param = END; // !Is_Function_Frame_Fulfilling + #if !defined(NDEBUG) + f->arg = m_cast(REBVAL*, END); // checked after + #endif + break; + } + + if (f->special != END) { + if (f->special == f->arg) { + // + // We're just checking the values already in the + // frame, so fall through and test the arg slot. + // However, offer a special tolerance here for void + // since MAKE FRAME! fills all arg slots with void. + // + if (IS_VOID(f->arg)) + Init_Logic(f->arg, FALSE); + } + else { + // Voids in specializations mean something different, + // that the refinement is left up to the caller. + // + if (IS_VOID(f->special)) { + ++f->special; + goto unspecialized_refinement; + } + + Move_Value(f->arg, f->special); + } + + if (!IS_LOGIC(f->arg)) + fail (Error_Non_Logic_Refinement(f)); + + if (IS_CONDITIONAL_TRUE(f->arg)) + f->refine = f->arg; // remember so we can revoke! + else + f->refine = ARG_TO_UNUSED_REFINEMENT; // (read-only) + + ++f->special; + goto continue_arg_loop; + } + + //=//// UNSPECIALIZED REFINEMENT SLOT (no consumption) ////////////////=// + + unspecialized_refinement: + + if (f->dsp_orig == DSP) { // no refinements left on stack + Init_Logic(f->arg, FALSE); + f->refine = ARG_TO_UNUSED_REFINEMENT; // "don't consume" + goto continue_arg_loop; + } + + f->refine = DS_TOP; + + if ( + IS_WORD(f->refine) && + ( + VAL_WORD_SPELLING(f->refine) // canon when pushed + == VAL_PARAM_CANON(f->param) // #2258 + ) + ){ + DS_DROP; // we're lucky: this was next refinement used + + Init_Logic(f->arg, TRUE); // marks refinement used + f->refine = f->arg; // "consume args (can be revoked)" + goto continue_arg_loop; + } + + --f->refine; // not lucky: if in use, this is out of order + + for (; f->refine > DS_AT(f->dsp_orig); --f->refine) { + if (!IS_WORD(f->refine)) continue; // non-refinement + if ( + VAL_WORD_SPELLING(f->refine) // canon when pushed + == VAL_PARAM_CANON(f->param) // #2258 + ){ + // The call uses this refinement but we'll have to + // come back to it when the expression index to + // consume lines up. Make a note of the param + // and arg and poke them into the stack value. + // + f->refine->header.bits &= CELL_MASK_RESET; + f->refine->header.bits |= HEADERIZE_KIND(REB_0_PICKUP); + f->refine->payload.pickup.param + = const_KNOWN(f->param); + f->refine->payload.pickup.arg = f->arg; + + Init_Logic(f->arg, TRUE); // marks refinement used + // "consume args later" (promise not to change) + f->refine = SKIPPING_REFINEMENT_ARGS; + goto continue_arg_loop; + } + } + + // Wasn't in the path and not specialized, so not present + // + Init_Logic(f->arg, FALSE); + f->refine = ARG_TO_UNUSED_REFINEMENT; // "don't consume" + goto continue_arg_loop; + } + + //=//// "PURE" LOCAL: ARG /////////////////////////////////////////////=// + + // This takes care of locals, including "magic" RETURN and LEAVE + // cells that need to be pre-filled. Notice that although the + // parameter list may have RETURN and LEAVE slots, that parameter + // list may be reused by an "adapter" or "hijacker" which would + // technically happen *before* the "magic" (if the user had + // implemented the definitinal returns themselves inside the + // function body). Hence they are not always filled. + // + // Also note that while it might seem intuitive to take care of + // these "easy" fills before refinement checking--checking for + // refinement pickups ending prevents double-doing this work. + + switch (pclass) { + case PARAM_CLASS_LOCAL: + Init_Void(f->arg); // faster than checking bad specializations + if (f->special != END) + ++f->special; + goto continue_arg_loop; + + case PARAM_CLASS_RETURN: + assert(VAL_PARAM_SYM(f->param) == SYM_RETURN); + + if (NOT_VAL_FLAG(FUNC_VALUE(f->phase), FUNC_FLAG_RETURN)) { + Init_Void(f->arg); + goto continue_arg_loop; + } + + Move_Value(f->arg, NAT_VALUE(return)); + + if (f->varlist) // !!! in specific binding, always for Plain + f->arg->extra.binding = f->varlist; + else + f->arg->extra.binding = FUNC_PARAMLIST(FRM_UNDERLYING(f)); + + if (f->special != END) + ++f->special; // specialization being overwritten is right + goto continue_arg_loop; + + case PARAM_CLASS_LEAVE: + assert(VAL_PARAM_SYM(f->param) == SYM_LEAVE); + + if (NOT_VAL_FLAG(FUNC_VALUE(f->phase), FUNC_FLAG_LEAVE)) { + Init_Void(f->arg); + goto continue_arg_loop; + } + + Move_Value(f->arg, NAT_VALUE(leave)); + + if (f->varlist) // !!! in specific binding, always for Plain + f->arg->extra.binding = f->varlist; + else + f->arg->extra.binding = FUNC_PARAMLIST(FRM_UNDERLYING(f)); + + if (f->special != END) + ++f->special; // specialization being overwritten is right + goto continue_arg_loop; + + default: + break; + } + + //=//// IF COMING BACK TO REFINEMENT ARGS LATER, MOVE ON FOR NOW //////=// + + if (f->refine == SKIPPING_REFINEMENT_ARGS) { + // + // The GC will protect values up through how far we have + // enumerated, so the argument slot cannot be uninitialized + // bits once we pass it. Use a safe trash so that the debug + // build will be able to tell if we don't come back and + // overwrite it correctly during the pickups phase. + // + SET_UNREADABLE_BLANK(f->arg); + + if (f->special != END) + ++f->special; + goto continue_arg_loop; + } + + if (f->special != END) { + if (f->special == f->arg) { + // + // Just running the loop to verify arguments/refinements... + // + ++f->special; + goto check_arg; + } + + //=//// SPECIALIZED ARG (already filled, so does not consume) /////////=// + + if (IS_VOID(f->special)) { + // + // A void specialized value means this particular argument + // is not specialized. Still must increment the pointer + // before falling through to ordinary fulfillment. + // + ++f->special; + } + else { + Move_Value(f->arg, f->special); + + ++f->special; + goto check_arg; // normal checking, handles errors also + } + } + + //=//// IF UNSPECIALIZED ARG IS INACTIVE, SET VOID AND MOVE ON ////////=// + + // Unspecialized arguments that do not consume do not need any + // further processing or checking. void will always be fine. + // + if (f->refine == ARG_TO_UNUSED_REFINEMENT) { + Init_Void(f->arg); + goto continue_arg_loop; + } + + //=//// IF LOOKBACK, THEN USE PREVIOUS EXPRESSION RESULT FOR ARG //////=// + + if (f->refine == LOOKBACK_ARG) { + // + // Switch to ordinary arg up front, so gotos below are good to + // go for the next argument + // + f->refine = ORDINARY_ARG; + + // !!! Can a variadic lookback argument be meaningful? + // Arguably, if you have an arity-1 function which is variadic + // and you enfix it, then giving it a feed of either 0 or 1 + // values and only letting it take from the left would make + // sense. But if it's arity-2 (e.g. multiple variadic taps) + // does that make any sense? + // + // It may be too wacky to worry about, and SET/LOOKBACK should + // just prohibit it. + // + assert(NOT_VAL_FLAG(f->param, TYPESET_FLAG_VARIADIC)); + + if (IS_END(f->out)) { + // + // Seeing an END in the output slot could mean that there + // was really "nothing" to the left, or it could be a + // consequence of a frame being in an argument gathering + // mode, e.g. + // + // if 1 then [2] ;-- error, THEN can't complete `if 1` + // + // The difference can be told by the frame flag. + + if (f->flags.bits & DO_FLAG_FULFILLING_ARG) + fail (Error_Partial_Lookback(f)); + + if (NOT_VAL_FLAG(f->param, TYPESET_FLAG_ENDABLE)) + fail (Error_No_Arg(FRM_LABEL(f), f->param)); + + Init_Void(f->arg); + goto continue_arg_loop; + } + + switch (pclass) { + case PARAM_CLASS_NORMAL: + // + // The deferment of arguments for normal parameters means + // this situation should not happen--only an END marker + // should be in f->out if fulfilling an argument. + // + assert(NOT(f->flags.bits & DO_FLAG_FULFILLING_ARG)); + Move_Value(f->arg, f->out); + break; + + case PARAM_CLASS_TIGHT: + Move_Value(f->arg, f->out); + break; + + case PARAM_CLASS_HARD_QUOTE: + #if !defined(NDEBUG) + // + // Only in debug builds, the before-switch lookahead sets + // this flag to help indicate that's where it came from. + // + assert(GET_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED)); + #endif + + Move_Value(f->arg, f->out); + SET_VAL_FLAG(f->arg, VALUE_FLAG_UNEVALUATED); + break; + + case PARAM_CLASS_SOFT_QUOTE: + #if !defined(NDEBUG) + // + // Only in debug builds, the before-switch lookahead sets + // this flag to help indicate that's where it came from. + // + assert(GET_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED)); + #endif + + if (IS_QUOTABLY_SOFT(f->out)) { + if (Eval_Value_Throws(f->arg, f->out)) { + Move_Value(f->out, f->arg); + Abort_Function_Args_For_Frame(f); + goto finished; + } + } + else { + Move_Value(f->arg, f->out); + SET_VAL_FLAG(f->arg, VALUE_FLAG_UNEVALUATED); + } + break; + + default: + assert(FALSE); + } + + SET_END(f->out); + goto check_arg; + } + + //=//// VARIADIC ARG (doesn't consume anything *yet*) /////////////////=// + + // Evaluation argument "hook" parameters (marked in MAKE FUNCTION! + // by a `[[]]` in the spec, and in FUNC by `<...>`). They point + // back to this call through a reified FRAME!, and are able to + // consume additional arguments during the function run. + // + if (GET_VAL_FLAG(f->param, TYPESET_FLAG_VARIADIC)) { + const REBOOL make = TRUE; + Link_Vararg_Param_To_Frame(f, make); + goto continue_arg_loop; // new value, type guaranteed correct + } + + //=//// AFTER THIS, PARAMS CONSUME FROM CALLSITE IF NOT APPLY ////////=// + + assert( + f->refine == ORDINARY_ARG + || (IS_LOGIC(f->refine) && IS_CONDITIONAL_TRUE(f->refine)) + ); + + //=//// ERROR ON END MARKER, BAR! IF APPLICABLE //////////////////////=// + + if (IS_END(f->value)) { + if (NOT_VAL_FLAG(f->param, TYPESET_FLAG_ENDABLE)) + fail (Error_No_Arg(FRM_LABEL(f), f->param)); + + Init_Void(f->arg); + goto continue_arg_loop; + } + + //=//// IF EVAL/ONLY SEMANTICS, TAKE NEXT ARG WITHOUT EVALUATION //////=// + + if (NOT(args_evaluate)) { + Quote_Next_In_Frame(f->arg, f); // has VALUE_FLAG_UNEVALUATED + goto check_arg; + } + + //=//// IF EVAL SEMANTICS, DISALLOW LITERAL EXPRESSION BARRIERS ///////=// + + if (IS_BAR(f->value) && pclass != PARAM_CLASS_HARD_QUOTE) { + // + // Only legal if arg is *hard quoted*. Else, it must come via + // other means (e.g. literal as `'|` or `first [|]`) + + if (NOT_VAL_FLAG(f->param, TYPESET_FLAG_ENDABLE)) + fail (Error_Expression_Barrier_Raw()); + + Init_Void(f->arg); + goto continue_arg_loop; + } + + switch (pclass) { + + //=//// REGULAR ARG-OR-REFINEMENT-ARG (consumes a DO/NEXT's worth) ////=// + + case PARAM_CLASS_NORMAL: + if (Do_Next_In_Subframe_Throws( + f->arg, + f, + DO_FLAG_FULFILLING_ARG + )){ + Move_Value(f->out, f->arg); + Abort_Function_Args_For_Frame(f); + goto finished; + } + break; + + case PARAM_CLASS_TIGHT: + // + // The default for evaluated parameters is to do normal + // infix lookahead, e.g. `square 1 + 2` would pass 3 + // to a single-arity function "square". But if the + // argument to square is declared #tight, it will act as + // `(square 1) + 2`, by not applying lookahead to + // see the + during the argument evaluation. + // + if (Do_Next_In_Subframe_Throws( + f->arg, + f, + DO_FLAG_NO_LOOKAHEAD | DO_FLAG_FULFILLING_ARG + )){ + Move_Value(f->out, f->arg); + Abort_Function_Args_For_Frame(f); + goto finished; + } + break; + + //=//// HARD QUOTED ARG-OR-REFINEMENT-ARG /////////////////////////////=// + + case PARAM_CLASS_HARD_QUOTE: { + Quote_Next_In_Frame(f->arg, f); // has VALUE_FLAG_UNEVALUATED + break; } + + //=//// SOFT QUOTED ARG-OR-REFINEMENT-ARG ////////////////////////////=// + + case PARAM_CLASS_SOFT_QUOTE: + if (!IS_QUOTABLY_SOFT(f->value)) { + Quote_Next_In_Frame(f->arg, f); // VALUE_FLAG_UNEVALUATED + goto check_arg; + } + + if (Eval_Value_Core_Throws(f->arg, f->value, f->specifier)) { + Move_Value(f->out, f->arg); + Abort_Function_Args_For_Frame(f); + goto finished; + } + + Fetch_Next_In_Frame(f); + break; + + default: + assert(FALSE); + } + + //=//// TYPE CHECKING FOR (MOST) ARGS AT END OF ARG LOOP //////////////=// + + check_arg:; + + // Some arguments can be fulfilled and skip type checking or + // take care of it themselves. But normal args pass through + // this code which checks the typeset and also handles it when + // a void arg signals the revocation of a refinement usage. + + ASSERT_VALUE_MANAGED(f->arg); + assert(pclass != PARAM_CLASS_REFINEMENT); + assert(pclass != PARAM_CLASS_LOCAL); + + // f->refine may point to the applicable refinement slot for the + // current arg being fulfilled, or it might just be a signal of + // information about the mode (see `Reb_Frame.refine` in %sys-do.h) + // + assert( + f->refine == ORDINARY_ARG || + f->refine == LOOKBACK_ARG || + f->refine == ARG_TO_UNUSED_REFINEMENT || + f->refine == ARG_TO_REVOKED_REFINEMENT || + (IS_LOGIC(f->refine) && IS_CONDITIONAL_TRUE(f->refine)) // used + ); + + if (IS_VOID(f->arg)) { + if (IS_LOGIC(f->refine)) { + // + // We can only revoke the refinement if this is the 1st + // refinement arg. If it's a later arg, then the first + // didn't trigger revocation, or refine wouldn't be logic. + // + if (f->refine + 1 != f->arg) + fail (Error_Bad_Refine_Revoke(f)); + + Init_Logic(f->refine, FALSE); // can't re-enable... + f->refine = ARG_TO_REVOKED_REFINEMENT; + goto continue_arg_loop; // don't type check for optionality + } + else if (IS_CONDITIONAL_FALSE(f->refine)) { + // + // FALSE means the refinement has already been revoked so + // the void is okay. BLANK! means the refinement was + // never in use in the first place. Don't type check. + // + goto continue_arg_loop; + } + else { + // fall through to check arg for if is ok + // + assert( + f->refine == ORDINARY_ARG + || f->refine == LOOKBACK_ARG + ); + } + } + else { + // If the argument is set, then the refinement shouldn't be + // in a revoked or unused state. + // + if (IS_CONDITIONAL_FALSE(f->refine)) + fail (Error_Bad_Refine_Revoke(f)); + } + + if (NOT_VAL_FLAG(f->param, TYPESET_FLAG_VARIADIC)) { + if (NOT(TYPE_CHECK(f->param, VAL_TYPE(f->arg)))) + fail (Error_Arg_Type( + FRM_LABEL(f), f->param, VAL_TYPE(f->arg)) + ); + } + else { + // Varargs are odd, because the type checking doesn't + // actually check the type of the parameter--it's always + // a VARARGS!. Also since the "types accepted" are a lie + // (an [integer! <...>] takes VARARGS!, not INTEGER!) then + // an "honest" parameter has to be made to give the error. + // + if (!IS_VARARGS(f->arg)) { + DECLARE_LOCAL (honest_param); + Init_Typeset( + honest_param, + FLAGIT_KIND(REB_VARARGS), // actually expected + VAL_PARAM_SPELLING(f->param) + ); + + fail (Error_Arg_Type( + FRM_LABEL(f), honest_param, VAL_TYPE(f->arg)) + ); + } + + // While "checking" the variadic argument we actually re-stamp + // it with this parameter and frame's signature. It reuses + // whatever the original data feed was (this frame, another + // frame, or just an array from MAKE VARARGS!) + // + const REBOOL make = FALSE; // reuse feed in f->arg + Link_Vararg_Param_To_Frame(f, make); + } + + continue_arg_loop: // `continue` might bind to the wrong scope + ++f->param; + ++f->arg; + // f->special is incremented while already testing it for END + } + + // If there was a specialization of the arguments, it should have + // been marched to an end cell...or just be the unwritable canon END + // node to start with + // + assert(IS_END(f->special)); + + // While having the rule that arg terminates isn't strictly necessary, + // it is a useful tool...and implicit termination makes it as cheap + // as not doing it. + // + assert(IS_END(f->arg)); + + // There may have been refinements that were skipped because the + // order of definition did not match the order of usage. They were + // left on the stack with a pointer to the `param` and `arg` after + // them for later fulfillment. + // + // Note that there may be functions on the stack if this is the + // second time through, and we were just jumping up to check the + // parameters in response to a R_REDO_CHECKED; if so, skip this. + // + if (DSP != f->dsp_orig) { + if (IS_WORD(DS_TOP)) { + // + // The walk through the arguments didn't fill in any + // information for this word, so it was either a duplicate of + // one that was fulfilled or not a refinement the function + // has at all. + // + assert(IS_WORD(DS_TOP)); + fail (Error_Bad_Refine_Raw(DS_TOP)); + } + + if (VAL_TYPE(DS_TOP) == REB_0_PICKUP) { + assert(f->special == END); // no specialization "pickups" + f->param = DS_TOP->payload.pickup.param; + f->refine = f->arg = DS_TOP->payload.pickup.arg; + assert(IS_LOGIC(f->refine) && VAL_LOGIC(f->refine)); + DS_DROP; + f->doing_pickups = TRUE; + goto continue_arg_loop; // leaves refine, but bumps param+arg + } + + // chains push functions, and R_REDO_CHECKED + assert(IS_FUNCTION(DS_TOP)); + } + + #if !defined(NDEBUG) + if (GET_VAL_FLAG(FUNC_VALUE(f->phase), FUNC_FLAG_LEGACY_DEBUG)) + Legacy_Convert_Function_Args(f); // BLANK!+NONE! vs. FALSE+UNSET! + #endif + + //==////////////////////////////////////////////////////////////////==// + // + // FUNCTION! ARGUMENTS NOW GATHERED, DISPATCH CALL + // + //==////////////////////////////////////////////////////////////////==// + + redo_unchecked: + assert(IS_END(f->param)); + // refine can be anything. + assert( + IS_END(f->value) + || (f->flags.bits & DO_FLAG_VA_LIST) + || IS_VALUE_IN_ARRAY_DEBUG(f->source.array, f->value) + ); + + if (Trace_Flags) + Trace_Func(FRM_LABEL(f)); + + // The out slot needs initialization for GC safety during the function + // run. Choosing an END marker should be legal because places that + // you can use as output targets can't be visible to the GC (that + // includes argument arrays being fulfilled). This offers extra + // perks, because it means a recycle/torture will catch you if you + // try to Do_Core into movable memory...*and* a native can tell if it + // has written the out slot yet or not (e.g. WHILE/? refinement). + // + assert(IS_END(f->out)); + + // Running arbitrary native code can manipulate the bindings or cache + // of a variable. It's very conservative to say this, but any word + // fetches that were done for lookahead are potentially invalidated + // by every function call. + // + f->gotten = END; + + // Cases should be in enum order for jump-table optimization + // (R_FALSE first, R_TRUE second, etc.) + // + // The dispatcher may push functions to the data stack which will be + // used to process the return result after the switch. + // + REBNAT dispatcher; // goto would cross initialization + dispatcher = FUNC_DISPATCHER(f->phase); + switch (dispatcher(f)) { + case R_FALSE: + Init_Logic(f->out, FALSE); // no VALUE_FLAG_UNEVALUATED + break; + + case R_TRUE: + Init_Logic(f->out, TRUE); // no VALUE_FLAG_UNEVALUATED + break; + + case R_VOID: + Init_Void(f->out); // no VALUE_FLAG_UNEVALUATED + break; + + case R_BLANK: + Init_Blank(f->out); // no VALUE_FLAG_UNEVALUATED + break; + + case R_BAR: + Init_Bar(f->out); // no VALUE_FLAG_UNEVALUATED + break; + + case R_OUT: + CLEAR_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED); + break; // checked as NOT_END() after switch() + + case R_OUT_UNEVALUATED: // returned by QUOTE and SEMIQUOTE + SET_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED); + break; + + case R_OUT_IS_THROWN: { + assert(THROWN(f->out)); + + if (!IS_FUNCTION(f->out) || VAL_FUNC(f->out) != NAT_FUNC(exit)) { + // + // Do_Core only catches "definitional exits" to current frame + // + Abort_Function_Args_For_Frame(f); + goto finished; + } + + ASSERT_ARRAY(VAL_BINDING(f->out)); + + if (VAL_BINDING(f->out) == FUNC_PARAMLIST(FRM_UNDERLYING(f))) { + // + // The most recent instance of a function on the stack (if + // any) will catch a FUNCTION! style exit. + // + CATCH_THROWN(f->out, f->out); + assert(NOT_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED)); + } + else if (VAL_BINDING(f->out) == f->varlist) { + // + // This identifies an exit from a *specific* function + // invocation. We'll only match it if we have a reified + // frame context. (Note f->varlist may be null here.) + // + CATCH_THROWN(f->out, f->out); + } + else { + Abort_Function_Args_For_Frame(f); + goto finished; // stay THROWN and try to exit frames above... + } + CLEAR_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED); + break; } + + case R_OUT_TRUE_IF_WRITTEN: + if (IS_END(f->out)) + Init_Logic(f->out, FALSE); // no VALUE_FLAG_UNEVALUATED + else + Init_Logic(f->out, TRUE); // no VALUE_FLAG_UNEVALUATED + break; + + case R_OUT_VOID_IF_UNWRITTEN: + if (IS_END(f->out)) + Init_Void(f->out); // no VALUE_FLAG_UNEVALUATED + else + CLEAR_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED); + break; + + case R_OUT_VOID_IF_UNWRITTEN_TRUTHIFY: + if (IS_END(f->out)) + Init_Void(f->out); + else if (IS_VOID(f->out) || IS_CONDITIONAL_FALSE(f->out)) + Init_Bar(f->out); + else + CLEAR_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED); + break; + + case R_OUT_BLANK_IF_VOID: + if (IS_VOID(f->out)) + Init_Blank(f->out); + else + CLEAR_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED); + break; + + case R_OUT_VOID_IF_UNWRITTEN_BLANK_IF_VOID: + if (IS_END(f->out)) + Init_Void(f->out); + else if (IS_VOID(f->out)) + Init_Blank(f->out); + else + CLEAR_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED); + break; + + case R_REDO_CHECKED: + SET_END(f->out); + f->special = f->args_head; + f->refine = ORDINARY_ARG; // no gathering, but need for assert + goto do_function_arglist_in_progress; + + case R_REDO_UNCHECKED: + // + // This instruction represents the idea that it is desired to + // run the f->phase again. The dispatcher may have changed the + // value of what f->phase is, for instance. + // + SET_END(f->out); + goto redo_unchecked; + + case R_REEVALUATE: + args_evaluate = TRUE; // unnecessary? + Drop_Function_Args_For_Frame(f); + CLEAR_FRAME_LABEL(f); + goto reevaluate; // we don't move index! + + case R_REEVALUATE_ONLY: + args_evaluate = FALSE; + Drop_Function_Args_For_Frame(f); + CLEAR_FRAME_LABEL(f); + goto reevaluate; // we don't move index! + + case R_UNHANDLED: // internal use only, shouldn't be returned + assert(FALSE); + + default: + assert(FALSE); + } + + assert(NOT_END(f->out)); // should have overwritten + assert(NOT(THROWN(f->out))); // throws must be R_OUT_IS_THROWN + + assert(f->eval_type == REB_FUNCTION); // shouldn't have changed + + //==////////////////////////////////////////////////////////////////==// + // + // DEBUG CHECK RETURN OF ALL FUNCTIONS (not just user functions) + // + //==////////////////////////////////////////////////////////////////==// + + // Here we know the function finished and did not throw or exit. + // Generally the return type is validated by the Returner_Dispatcher() + // with everything else assumed to return the correct type. But this + // double checks any function marked with RETURN in the debug build. + +#if !defined(NDEBUG) + if (GET_VAL_FLAG(FUNC_VALUE(f->phase), FUNC_FLAG_RETURN)) { + REBVAL *typeset = FUNC_PARAM(f->phase, FUNC_NUM_PARAMS(f->phase)); + assert(VAL_PARAM_SYM(typeset) == SYM_RETURN); + if (!TYPE_CHECK(typeset, VAL_TYPE(f->out))) + fail (Error_Bad_Return_Type(f->label, VAL_TYPE(f->out))); + } +#endif + + //==////////////////////////////////////////////////////////////////==// + // + // FUNCTION! CALL COMPLETION + // + //==////////////////////////////////////////////////////////////////==// + + // If we have functions pending to run on the outputs, then do so. + // + while (DSP != f->dsp_orig) { + assert(IS_FUNCTION(DS_TOP)); + + Move_Value(&f->cell, f->out); + + // Data stack values cannot be used directly in an apply, because + // the evaluator uses DS_PUSH, which could relocate the stack + // and invalidate the pointer. + // + DECLARE_LOCAL (fun); + Move_Value(fun, DS_TOP); + + if (Apply_Only_Throws(f->out, TRUE, fun, &f->cell, END)) { + Abort_Function_Args_For_Frame(f); + goto finished; + } + + DS_DROP; + } + + if (Trace_Flags) + Trace_Return(FRM_LABEL(f), f->out); + + // !!! It would technically be possible to drop the arguments before + // running chains... and if the chained function were to run *in* + // this frame that could be even more optimal. However, having the + // original function still on the stack helps make errors clearer. + // + Drop_Function_Args_For_Frame(f); + + CLEAR_FRAME_LABEL(f); + break; + +//==//////////////////////////////////////////////////////////////////////==// +// +// [WORD!] +// +// A plain word tries to fetch its value through its binding. It will fail +// and longjmp out of this stack if the word is unbound (or if the binding is +// to a variable which is not set). Should the word look up to a function, +// then that function will be called by jumping to the ANY-FUNCTION! case. +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_WORD: + if (current_gotten == END) { + current_gotten = Get_Opt_Var_May_Fail(current, f->specifier); + goto do_word_in_current_unchecked; + } + + do_word_in_current: + assert(current_gotten == Get_Opt_Var_May_Fail(current, f->specifier)); + + do_word_in_current_unchecked: + if (IS_FUNCTION(current_gotten)) { // before IS_VOID() is common case + f->eval_type = REB_FUNCTION; + SET_FRAME_LABEL(f, VAL_WORD_SPELLING(current)); + + if (GET_VAL_FLAG(current_gotten, VALUE_FLAG_ENFIXED)) { + f->refine = LOOKBACK_ARG; + goto do_function_in_current_gotten; + } + + SET_END(f->out); + f->refine = ORDINARY_ARG; + goto do_function_in_current_gotten; + } + + if (IS_VOID(current_gotten)) // need `:x` if `x` is unset + fail (Error_No_Value_Core(current, f->specifier)); + + Move_Value(f->out, current_gotten); // no copy VALUE_FLAG_UNEVALUATED + + #if !defined(NDEBUG) + if (LEGACY(OPTIONS_LIT_WORD_DECAY) && IS_LIT_WORD(f->out)) + VAL_SET_TYPE_BITS(f->out, REB_WORD); // don't reset full header! + #endif + break; + +//==//////////////////////////////////////////////////////////////////////==// +// +// [SET-WORD!] +// +// A chain of `x: y: z: ...` may happen, so there could be any number of +// SET-WORD!s before the value to assign is found. Some kind of list needs to +// be maintained. +// +// Recursion into Do_Core() is used, but a new frame is not created. Instead +// it reuses `f` with a lighter-weight approach. Do_Next_Mid_Frame_Throws() +// has remarks on how this is done. +// +// !!! Note that `10 = 5 + 5` would be an error due to lookahead suppression +// from `=`, so it reads as `(10 = 5) + 5`. However `10 = x: 5 + 5` will not +// be an error, as the SET-WORD! causes a recursion in the evaluator. This +// is unusual, but there are advantages to seeing SET-WORD! as a kind of +// single-arity function. +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_SET_WORD: + assert(IS_SET_WORD(current)); + + if (IS_END(f->value)) { + DECLARE_LOCAL (specific); + Derelativize(specific, current, f->specifier); + fail (Error_Need_Value_Raw(specific)); // `do [a:]` is illegal + } + + if (NOT(args_evaluate)) { // e.g. `eval/only quote x: 1 + 2`, x => 1 + Derelativize(f->out, f->value, f->specifier); + Move_Value(Sink_Var_May_Fail(current, f->specifier), f->out); + } + else { + // f->value is guarded implicitly by the frame, but `current` is a + // transient local pointer that might be to a va_list REBVAL* that + // has already been fetched. The bits will stay live until + // va_end(), but a GC wouldn't see it. + // + DS_PUSH_RELVAL(current, f->specifier); + + if (Do_Next_Mid_Frame_Throws(f)) { // lightweight reuse of `f` + DS_DROP; + goto finished; + } + + Move_Value(Sink_Var_May_Fail(DS_TOP, SPECIFIED), f->out); + + DS_DROP; + } + break; + +//==//////////////////////////////////////////////////////////////////////==// +// +// [GET-WORD!] +// +// A GET-WORD! does no checking for unsets, no dispatch on functions, and +// will return void if the variable is not set. +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_GET_WORD: + // + // Note: copying values does not copy VALUE_FLAG_UNEVALUATED + // + Copy_Opt_Var_May_Fail(f->out, current, f->specifier); + break; + +//==/////////////////////////////////////////////////////////////////////==// +// +// [LIT-WORD!] +// +// Note we only want to reset the type bits in the header, not the whole +// header--because header bits contain information like WORD_FLAG_BOUND. +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_LIT_WORD: + // + // Derelativize will clear VALUE_FLAG_UNEVALUATED + // + Derelativize(f->out, current, f->specifier); + VAL_SET_TYPE_BITS(f->out, REB_WORD); + break; + +//==//// INERT WORD AND STRING TYPES /////////////////////////////////////==// + + case REB_REFINEMENT: + case REB_ISSUE: + // ^-- ANY-WORD! + goto inert; + +//==//////////////////////////////////////////////////////////////////////==// +// +// [GROUP!] +// +// If a GROUP! is seen then it generates another call into Do_Core(). The +// resulting value for this step will be the outcome of that evaluation. +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_GROUP: { + // + // If the source array we are processing that is yielding values is + // part of the deep copy of a function body, it's possible that this + // GROUP! is a "relative ANY-ARRAY!" that needs the specifier to + // resolve the relative any-words and other any-arrays inside it... + // + REBSPC *derived = Derive_Specifier(f->specifier, current); + if (Do_At_Throws( + f->out, + VAL_ARRAY(current), // the GROUP!'s array + VAL_INDEX(current), // index in group's REBVAL (may not be head) + derived + )){ + goto finished; + } + + CLEAR_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED); + break; } + +//==//////////////////////////////////////////////////////////////////////==// +// +// [PATH!] +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_PATH: { + REBSTR *label; + if (Do_Path_Throws_Core( + f->out, + &label, // requesting label says we run functions (not GET-PATH!) + current, + f->specifier, + NULL // `setval`: null means don't treat as SET-PATH! + )){ + goto finished; + } + + if (IS_VOID(f->out)) // need `:x/y` if `y` is unset + fail (Error_No_Value_Core(current, f->specifier)); + + if (IS_FUNCTION(f->out)) { + f->eval_type = REB_FUNCTION; + if (label == NULL) + SET_FRAME_LABEL(f, Canon(SYM___ANONYMOUS__)); + else + SET_FRAME_LABEL(f, label); + + // object/func or func/refinements or object/func/refinement + // + // Because we passed in a label symbol, the path evaluator was + // willing to assume we are going to invoke a function if it + // is one. Hence it left any potential refinements on data stack. + // + assert(DSP >= f->dsp_orig); + + Move_Value(&f->cell, f->out); + current_gotten = &f->cell; + SET_END(f->out); + f->refine = ORDINARY_ARG; // paths are never enfixed (for now) + goto do_function_in_current_gotten; + } + + CLEAR_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED); + break; + } + +//==//////////////////////////////////////////////////////////////////////==// +// +// [SET-PATH!] +// +// See notes on SET-WORD! SET-PATH!s are handled in a similar way, by +// pushing them to the stack, continuing the evaluation via a lightweight +// reuse of the current frame. +// +// !!! The evaluation ordering is dictated by the fact that there isn't a +// separate "evaluate path to target location" and "set target' step. This +// is because some targets of assignments (e.g. gob/size/x:) do not correspond +// to a cell that can be returned; the path operation "encodes as it goes" +// and requires the value to set as a parameter to Do_Path. Yet it is +// counterintuitive given the "left-to-right" nature of the language: +// +// >> foo: make object! [[bar][bar: 10]] +// +// >> foo/(print "left" 'bar): (print "right" 20) +// right +// left +// == 20 +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_SET_PATH: { + assert(IS_SET_PATH(current)); + + if (IS_END(f->value)) { + DECLARE_LOCAL (specific); + Derelativize(specific, current, f->specifier); + fail (Error_Need_Value_Raw(specific)); // `do [a/b:]` is illegal + } + + if (NOT(args_evaluate)) { + Derelativize(f->out, f->value, f->specifier); + + // !!! Due to the way this is currently designed, throws need to + // be written to a location distinct from the path and also + // distinct from the value being set. Review. + // + DECLARE_LOCAL (temp); + + if (Do_Path_Throws_Core( + temp, // output location if thrown + NULL, // not requesting symbol means refinements not allowed + current, // still holding SET-PATH! we got in + f->specifier, // specifier for current + f->out // value to set (already in f->out) + )) { + fail (Error_No_Catch_For_Throw(temp)); + } + } + else { + // f->value is guarded implicitly by the frame, but `current` is a + // transient local pointer that might be to a va_list REBVAL* that + // has already been fetched. The bits will stay live until + // va_end(), but a GC wouldn't see it. + // + DS_PUSH_RELVAL(current, f->specifier); + + if (Do_Next_Mid_Frame_Throws(f)) { // lighweight reuse of `f` + DS_DROP; + goto finished; + } + + // The path cannot be executed directly from the data stack, so + // it has to be popped. This could be changed by making the core + // Do_Path_Throws take a VAL_ARRAY, index, and kind. By moving + // it into the f->cell, it is guaranteed garbage collected. + // + Move_Value(&f->cell, DS_TOP); + DS_DROP; + + // !!! Due to the way this is currently designed, throws need to + // be written to a location distinct from the path and also + // distinct from the value being set. Review. + // + DECLARE_LOCAL (temp); + + if (Do_Path_Throws_Core( + temp, // output location if thrown + NULL, // not requesting symbol means refinements not allowed + &f->cell, // still holding SET-PATH! we got in + SPECIFIED, // current derelativized when pushed to DS_TOP + f->out // value to set (already in f->out) + )) { + fail (Error_No_Catch_For_Throw(temp)); + } + } + + break; } + +//==//////////////////////////////////////////////////////////////////////==// +// +// [GET-PATH!] +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_GET_PATH: + // + // !!! Should a GET-PATH! be able to call into the evaluator, by + // evaluating GROUP!s in the path? It's clear that `get path` + // shouldn't be able to evaluate (a GET should not have side effects). + // But perhaps source-level GET-PATH!s can be more liberal, as one can + // visibly see the GROUP!s. + // + if (Do_Path_Throws_Core( + f->out, + NULL, // not requesting symbol means refinements not allowed + current, + f->specifier, + NULL // `setval`: null means don't treat as SET-PATH! + )){ + goto finished; + } + + CLEAR_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED); + break; + +//==//////////////////////////////////////////////////////////////////////==// +// +// [LIT-PATH!] +// +// We only set the type, in order to preserve the header bits... (there +// currently aren't any for ANY-PATH!, but there might be someday.) +// +// !!! Aliases a REBSER under two value types, likely bad, see #2233 +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_LIT_PATH: + // + // Derelativize will leave VALUE_FLAG_UNEVALUATED clear + // + Derelativize(f->out, current, f->specifier); + VAL_SET_TYPE_BITS(f->out, REB_PATH); + break; + +//==//////////////////////////////////////////////////////////////////////==// +// +// Treat all the other Is_Bindable() types as inert +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_BLOCK: + // + case REB_BINARY: + case REB_STRING: + case REB_FILE: + case REB_EMAIL: + case REB_URL: + case REB_TAG: + // + case REB_BITSET: + case REB_IMAGE: + case REB_VECTOR: + // + case REB_MAP: + // + case REB_VARARGS: + // + case REB_OBJECT: + case REB_FRAME: + case REB_MODULE: + case REB_ERROR: + case REB_PORT: + goto inert; + +//==//////////////////////////////////////////////////////////////////////==// +// +// [BAR!] +// +// If an expression barrier is seen in-between expressions (as it will always +// be if hit in this switch), it evaluates to void. It only errors in +// argument fulfillment during the switch case for ANY-FUNCTION!. +// +// Note that `DO/NEXT [| | | | 1 + 2]` will skip the bars and yield 3. This +// helps give BAR!s their lightweight character. It also means that code +// doing DO/NEXTs will not see them as generating voids, which might have +// a specific meaning to the caller. (They can check for BAR!s explicitly +// if they want to give BAR!s a meaning.) +// +// Note also that natives and dialects frequently do their own interpretation +// of BAR!--rather than just evaluate it and let it mean something equivalent +// to an unset. For instance: +// +// case [false [print "F"] | true [print ["T"]] +// +// If CASE did not specially recognize BAR!, it would complain that the +// "second condition" had no value. So if you are looking for a BAR! behavior +// and it's not passing through here, check the construct you are using. +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_BAR: + assert(IS_BAR(current)); + + if (NOT_END(f->value)) { + SET_END(f->out); // skipping the post loop where this is done + f->eval_type = VAL_TYPE(f->value); + goto do_next; // quickly process next item, no infix test needed + } + + Init_Void(f->out); // no VALUE_FLAG_UNEVALUATED + break; + +//==//////////////////////////////////////////////////////////////////////==// +// +// [LIT-BAR!] +// +// LIT-BAR! decays into an ordinary BAR! if seen here by the evaluator. +// +// !!! Considerations of the "lit-bit" proposal would add a literal form +// for every type, which would make this datatype unnecssary. +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_LIT_BAR: + assert(IS_LIT_BAR(current)); + + Init_Bar(f->out); // no VALUE_FLAG_UNEVALUATED + break; + +//==//////////////////////////////////////////////////////////////////////==// +// +// Treat all the other NOT(Is_Bindable()) types as inert +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_BLANK: + // + case REB_LOGIC: + case REB_INTEGER: + case REB_DECIMAL: + case REB_PERCENT: + case REB_MONEY: + case REB_CHAR: + case REB_PAIR: + case REB_TUPLE: + case REB_TIME: + case REB_DATE: + // + case REB_DATATYPE: + case REB_TYPESET: + // + case REB_GOB: + case REB_EVENT: + case REB_HANDLE: + case REB_STRUCT: + case REB_LIBRARY: + // + inert: + Derelativize(f->out, current, f->specifier); + SET_VAL_FLAG(f->out, VALUE_FLAG_UNEVALUATED); + break; + +//==//////////////////////////////////////////////////////////////////////==// +// +// [void] +// +// Void is not an ANY-VALUE!, and void cells are not allowed in ANY-ARRAY! +// exposed to the user. So usually, a DO shouldn't be able to see them, +// unless they are un-evaluated...e.g. `Apply_Only_Throws()` passes in a +// VOID_CELL as an evaluation-already-accounted-for parameter to a function. +// +// The exception case is something like `eval ()`, which is the user +// deliberately trying to invoke the evaluator on a void. (Not to be confused +// with `eval quote ()`, which is the evaluation of an empty GROUP!, which +// produces void, and that's fine). We choose to deliver an error in the void +// case, which provides a consistency: +// +// :foo/bar => pick* foo 'bar (void if not present) +// foo/bar => eval :foo/bar (should be an error if not present) +// +//==//////////////////////////////////////////////////////////////////////==// + + case REB_MAX_VOID: + if (NOT(args_evaluate)) { + Init_Void(f->out); + } + else { + // must be EVAL, so the value must be living in the frame cell + // + assert(current == &f->cell); + fail (Error_Evaluate_Void_Raw()); + } + break; + +//==//////////////////////////////////////////////////////////////////////==// +// +// If garbage, panic on the value to generate more debug information about +// its origins (what series it lives in, where the cell was assigned...) +// +//==//////////////////////////////////////////////////////////////////////==// + + default: + panic (current); + } + + //==////////////////////////////////////////////////////////////////==// + // + // END MAIN SWITCH STATEMENT + // + //==////////////////////////////////////////////////////////////////==// + + assert(!THROWN(f->out)); // should have jumped to exit sooner + + if (IS_END(f->value)) + goto finished; + + f->eval_type = VAL_TYPE(f->value); + + if (f->flags.bits & DO_FLAG_NO_LOOKAHEAD) { + // + // Don't do infix lookahead if asked *not* to look. See the + // PARAM_CLASS_TIGHT parameter convention for the use of this + // + assert(NOT(f->flags.bits & DO_FLAG_TO_END)); + } + else if (f->eval_type == REB_WORD) { + + if (f->gotten == END) + f->gotten = Get_Opt_Var_Else_End(f->value, f->specifier); + else + assert( + f->gotten == Get_Opt_Var_Else_End(f->value, f->specifier) + ); + + //=//// DO/NEXT WON'T RUN MORE CODE UNLESS IT'S AN INFIX FUNCTION /////=// + + if ( + NOT(f->flags.bits & DO_FLAG_TO_END) + && ( + f->gotten == END // could fold the END check in with masking + || NOT_VAL_FLAG(f->gotten, VALUE_FLAG_ENFIXED) + ) + ){ + goto finished; + } + + //=//// IT'S INFIX OR WE'RE DOING TO THE END...DISPATCH LIKE WORD /////=// + + START_NEW_EXPRESSION_MAY_THROW(f, goto finished); + // ^-- sets args_evaluate, do_count, Ctrl-C may abort + + if (VAL_TYPE_OR_0(f->gotten) != REB_FUNCTION) { // END is REB_0 + current = f->value; + current_gotten = f->gotten; // if END, the word will error + f->gotten = END; + Fetch_Next_In_Frame(f); + goto do_word_in_current; + } + + f->eval_type = REB_FUNCTION; + + if (GET_VAL_FLAG(f->gotten, VALUE_FLAG_ENFIXED)) { + if ( + GET_VAL_FLAG(f->gotten, FUNC_FLAG_DEFERS_LOOKBACK) + && (f->flags.bits & DO_FLAG_FULFILLING_ARG) + ){ + // This is the special case; we have a lookback function + // pending but it wants to defer its first argument as + // long as possible--and we're on the last parameter of + // some function. Skip the "lookahead" and let whoever + // is gathering arguments (or whoever's above them) finish + // the expression before taking the pending operation. + // + assert(NOT(f->flags.bits & DO_FLAG_TO_END)); + } + else if (GET_VAL_FLAG(f->gotten, FUNC_FLAG_QUOTES_FIRST_ARG)) { + // + // Left-quoting by enfix needs to be done in the lookahead + // before an evaluation, not this one that's after. This + // error happens in cases like: + // + // left-quote: enfix func [:value] [:value] + // quote left-quote + // + // !!! Is this the ideal place to be delivering the error? + // + fail (Error_Lookback_Quote_Too_Late(f->value, f->specifier)); + } + else { + // This is a case for an evaluative lookback argument we + // don't want to defer, e.g. a #tight argument or a normal + // one which is not being requested in the context of + // parameter fulfillment. We want to reuse the f->out + // + SET_FRAME_LABEL(f, VAL_WORD_SPELLING(f->value)); + f->refine = LOOKBACK_ARG; + current = f->value; + current_gotten = f->gotten; + f->gotten = END; + Fetch_Next_In_Frame(f); + goto do_function_in_current_gotten; + } + } + else { + SET_END(f->out); + SET_FRAME_LABEL(f, VAL_WORD_SPELLING(f->value)); + f->refine = ORDINARY_ARG; + current = f->value; + current_gotten = f->gotten; + f->gotten = END; + Fetch_Next_In_Frame(f); + goto do_function_in_current_gotten; + } + } + + // Continue evaluating rest of block if not just a DO/NEXT + // + if (f->flags.bits & DO_FLAG_TO_END) + goto do_next; + +finished:; + +#if !defined(NDEBUG) + Do_Core_Exit_Checks_Debug(f); // will get called unless a fail() longjmps +#endif + + // All callers must inspect for THROWN(f->out), and most should also + // inspect for IS_END(f->value) +} diff --git a/src/core/c-frame.c b/src/core/c-frame.c deleted file mode 100644 index fd571d3fe6..0000000000 --- a/src/core/c-frame.c +++ /dev/null @@ -1,1415 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: c-frame.c -** Summary: frame management -** Section: core -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ -/* - This structure is used for: - - 1. Modules - 2. Objects - 3. Function frame (arguments) - 4. Closures - - A frame is a block that begins with a special FRAME! value - (a datatype that links to the frame word list). That value - (SELF) is followed by the values of the words for the frame. - - FRAME BLOCK: WORD LIST: - +----------------------------+ +----------------------------+ - | Frame Datatype Value |--Series->| SELF word | - +----------------------------+ +----------------------------+ - | Value 1 | | Word 1 | - +----------------------------+ +----------------------------+ - | Value 2 | | Word 2 | - +----------------------------+ +----------------------------+ - | Value ... | | Word ... | - +----------------------------+ +----------------------------+ - - The word list holds word datatype values of the structure: - - Type: word, 'word, :word, word:, /word - Symbol: actual symbol - Canon: canonical symbol - Typeset: index of the value's typeset, or zero - - This list is used for binding, evaluation, type checking, and - can also be used for molding. - - When a frame is cloned, only the value block itself need be - created. The word list remains the same. For functions, the - value block can be pushed on the stack. - - Frame creation patterns: - - 1. Function specification to frame. Spec is scanned for - words and datatypes, from which the word list is created. - Closures are identical. - - 2. Object specification to frame. Spec is scanned for - word definitions and merged with parent defintions. An - option is to allow the words to be typed. - - 3. Module words to frame. They are not normally known in - advance, they are collected during the global binding of a - newly loaded block. This requires either preallocation of - the module frame, or some kind of special scan to track - the new words. - - 4. Special frames, such as system natives and actions - may be created by specific block scans and appending to - a given frame. -*/ - -#include "sys-core.h" - -#define CHECK_BIND_TABLE - -/*********************************************************************** -** -*/ void Check_Bind_Table() -/* -***********************************************************************/ -{ - REBCNT n; - REBINT *binds = WORDS_HEAD(Bind_Table); - - //Debug_Fmt("Bind Table (Size: %d)", SERIES_TAIL(Bind_Table)); - for (n = 0; n < SERIES_TAIL(Bind_Table); n++) { - if (binds[n]) { - Debug_Fmt("Bind table fault: %3d to %3d (%s)", n, binds[n], Get_Sym_Name(n)); - } - } -} - -/*********************************************************************** -** -*/ REBSER *Make_Frame(REBINT len) -/* -** Create a frame of a given size, allocating space for both -** words and values. Normally used for global frames. -** -** selfless means do not set SELF word -** -***********************************************************************/ -{ - REBSER *frame; - REBSER *words; - REBVAL *value; - - //DISABLE_GC; - words = Make_Block(len + 1); // size + room for SELF - BARE_SERIES(words); - frame = Make_Block(len + 1); - //ENABLE_GC; - // Note: cannot use Append_Frame for first word. - value = Append_Value(frame); - SET_FRAME(value, 0, words); - value = Append_Value(words); - Init_Frame_Word(value, SYM_SELF); // may get unset by selfless frames - - return frame; -} - - -/*********************************************************************** -** -*/ void Expand_Frame(REBSER *frame, REBCNT delta, REBCNT copy) -/* -** Expand a frame. Copy words if flagged. -** -***********************************************************************/ -{ - REBSER *words = FRM_WORD_SERIES(frame); - - Extend_Series(frame, delta); - BLK_TERM(frame); - - // Expand or copy WORDS block: - if (copy) { - FRM_WORD_SERIES(frame) = Copy_Expand_Block(words, delta); - BARE_SERIES(FRM_WORD_SERIES(frame)); - } else { - Extend_Series(words, delta); - BLK_TERM(words); - } -} - - -/*********************************************************************** -** -*/ REBVAL *Append_Frame(REBSER *frame, REBVAL *word, REBCNT sym) -/* -** Append a word to the frame word list. Expands the list -** if necessary. Returns the value cell for the word. (Set to -** UNSET by default to avoid GC corruption.) -** -** If word is not NULL, use the word sym and bind the word value, -** otherwise use sym. -** -** WARNING: Invalidates pointers to values within the frame -** because the frame block may get expanded. (Use indexes.) -** -***********************************************************************/ -{ - REBSER *words = FRM_WORD_SERIES(frame); - REBVAL *value; - - // Add to word list: - EXPAND_SERIES_TAIL(words, 1); - value = BLK_LAST(words); - if (word) Init_Frame_Word(value, VAL_WORD_SYM(word)); - else Init_Frame_Word(value, sym); - BLK_TERM(words); - - // Bind the word to this frame: - if (word) { - VAL_WORD_FRAME(word) = frame; - VAL_WORD_INDEX(word) = frame->tail; - } - - // Add unset value to frame: - EXPAND_SERIES_TAIL(frame, 1); - word = BLK_LAST(frame); - SET_UNSET(word); - BLK_TERM(frame); - - return word; // The value cell for word. -} - - -/*********************************************************************** -** -*/ void Collect_Start(REBCNT modes) -/* -** Use the Bind_Table to start collecting new words for -** a frame. Use Collect_End() when done. -** -** WARNING: Do not call code that might call BIND or otherwise -** make use of the Bind_Table or the Word cache array (BUF_WORDS). -** -***********************************************************************/ -{ - REBINT *binds = WORDS_HEAD(Bind_Table); // GC safe to do here - - CHECK_BIND_TABLE; - - // Reuse a global word list block because length of block cannot - // be known until all words are scanned. Then copy this block. - if (SERIES_TAIL(BUF_WORDS)) Crash(RP_WORD_LIST); // still in use - - // Add the SELF word to slot zero. - if ((modes = (modes & BIND_NO_SELF)?0:SYM_SELF)) - binds[modes] = -1; // (cannot use zero here) - Init_Frame_Word(BLK_HEAD(BUF_WORDS), modes); - SERIES_TAIL(BUF_WORDS) = 1; -} - - -/*********************************************************************** -** -*/ REBSER *Collect_End(REBSER *prior) -/* -** Finish collecting words, and free the Bind_Table for reuse. -** -***********************************************************************/ -{ - REBVAL *words; - REBINT *binds = WORDS_HEAD(Bind_Table); // GC safe to do here - - // Reset binding table (note BUF_WORDS may have expanded): - for (words = BLK_HEAD(BUF_WORDS); NOT_END(words); words++) - binds[VAL_WORD_CANON(words)] = 0; - - // If no new words, prior frame: - if (prior && SERIES_TAIL(BUF_WORDS) == SERIES_TAIL(prior)) { - RESET_TAIL(BUF_WORDS); // allow reuse - return FRM_WORD_SERIES(prior); - } - - prior = Copy_Series(BUF_WORDS); - RESET_TAIL(BUF_WORDS); // allow reuse - BARE_SERIES(prior); // No GC ever needed for word list - - CHECK_BIND_TABLE; - - return prior; -} - - -/*********************************************************************** -** -*/ void Collect_Object(REBSER *prior) -/* -** Collect words from a prior object. -** -***********************************************************************/ -{ - REBVAL *words = FRM_WORDS(prior); - REBINT *binds = WORDS_HEAD(Bind_Table); - REBINT n; - - // this is necessary for COPY_VALUES below - // to not overwrite memory BUF_WORDS does not own - RESIZE_SERIES(BUF_WORDS, SERIES_TAIL(prior)); - COPY_VALUES(words, BLK_HEAD(BUF_WORDS), SERIES_TAIL(prior)); - SERIES_TAIL(BUF_WORDS) = SERIES_TAIL(prior); - for (n = 1, words++; NOT_END(words); words++) // skips first = SELF - binds[VAL_WORD_CANON(words)] = n++; -} - - -/*********************************************************************** -** -*/ void Collect_Words(REBVAL *block, REBFLG modes) -/* -** The inner recursive loop used for Collect_Frame function below. -** -***********************************************************************/ -{ - REBINT *binds = WORDS_HEAD(Bind_Table); - REBVAL *word; - REBVAL *value; - - for (; NOT_END(block); block++) { - value = block; - //if (modes & BIND_GET && IS_GET_WORD(block)) value = Get_Var(block); - if (ANY_WORD(value)) { - if (!binds[VAL_WORD_CANON(value)]) { // only once per word - if (IS_SET_WORD(value) || modes & BIND_ALL) { - binds[VAL_WORD_CANON(value)] = SERIES_TAIL(BUF_WORDS); - EXPAND_SERIES_TAIL(BUF_WORDS, 1); - word = BLK_LAST(BUF_WORDS); - VAL_SET(word, VAL_TYPE(value)); - VAL_SET_OPT(word, OPTS_UNWORD); - VAL_BIND_SYM(word) = VAL_WORD_SYM(value); - // Allow all datatypes (to start): - VAL_BIND_TYPESET(word) = ~((TYPESET(REB_END) | TYPESET(REB_UNSET))); // not END or UNSET - } - } else { - // If word duplicated: - if (modes & BIND_NO_DUP) { - // Reset binding table (note BUF_WORDS may have expanded): - for (word = BLK_HEAD(BUF_WORDS); NOT_END(word); word++) - binds[VAL_WORD_CANON(word)] = 0; - RESET_TAIL(BUF_WORDS); // allow reuse - Trap1(RE_DUP_VARS, value); - } - } - continue; - } - // Recurse into sub-blocks: - if (ANY_EVAL_BLOCK(value) && (modes & BIND_DEEP)) - Collect_Words(VAL_BLK_DATA(value), modes); - // In this mode (foreach native), do not allow non-words: - //else if (modes & BIND_GET) Trap_Arg(value); - } - BLK_TERM(BUF_WORDS); -} - - -/*********************************************************************** -** -*/ REBSER *Collect_Frame(REBFLG modes, REBSER *prior, REBVAL *block) -/* -** Scans a block for words to use in the frame. The list of -** words can then be used to create a frame. The Bind_Table is -** used to quickly determine duplicate entries. -** -** Returns: -** A block of words that can be used for a frame word list. -** If no new words, the prior list is returned. -** -** Modes: -** BIND_ALL - scan all words, or just set words -** BIND_DEEP - scan sub-blocks too -** BIND_GET - substitute :word with actual word -** BIND_NO_SELF - do not add implicit SELF to the frame -** -***********************************************************************/ -{ - Collect_Start(modes); - - // Setup binding table with existing words: - if (prior) Collect_Object(prior); - - // Scan for words, adding them to BUF_WORDS and bind table: - Collect_Words(block, modes); - - return Collect_End(prior); -} - - -/*********************************************************************** -** -*/ void Collect_Simple_Words(REBVAL *block, REBCNT modes) -/* -** Used for Collect_Block_Words(). -** -***********************************************************************/ -{ - REBINT *binds = WORDS_HEAD(Bind_Table); // GC safe to do here - REBVAL *val; - - for (; NOT_END(block); block++) { - if (ANY_WORD(block) - && !binds[VAL_WORD_CANON(block)] - && (modes & BIND_ALL || IS_SET_WORD(block)) - ) { - binds[VAL_WORD_CANON(block)] = 1; - val = Append_Value(BUF_WORDS); - Init_Word(val, VAL_WORD_SYM(block)); - } - else if (ANY_EVAL_BLOCK(block) && (modes & BIND_DEEP)) - Collect_Simple_Words(VAL_BLK_DATA(block), modes); - } -} - - -/*********************************************************************** -** -*/ REBSER *Collect_Block_Words(REBVAL *block, REBVAL *prior, REBCNT modes) -/* -** Collect words from a prior block and new block. -** -***********************************************************************/ -{ - REBSER *series; - REBCNT start; - REBINT *binds = WORDS_HEAD(Bind_Table); // GC safe to do here - CHECK_BIND_TABLE; - - if (SERIES_TAIL(BUF_WORDS)) Crash(RP_WORD_LIST); // still in use - - if (prior) - Collect_Simple_Words(prior, BIND_ALL); - - start = SERIES_TAIL(BUF_WORDS); - Collect_Simple_Words(block, modes); - - // Reset word markers: - for (block = BLK_HEAD(BUF_WORDS); NOT_END(block); block++) - binds[VAL_WORD_CANON(block)] = 0; - - series = Copy_Series_Part(BUF_WORDS, start, SERIES_TAIL(BUF_WORDS)-start); - RESET_TAIL(BUF_WORDS); // allow reuse - - CHECK_BIND_TABLE; - return series; -} - - -/*********************************************************************** -** -*/ REBSER *Create_Frame(REBSER *words, REBSER *spec) -/* -** Create a new frame from a word list. -** The values of the frame are initialized to NONE. -** -***********************************************************************/ -{ - REBINT len = SERIES_TAIL(words); - REBSER *frame = Make_Block(len); - REBVAL *value = BLK_HEAD(frame); - - SET_FRAME(value, spec, words); - - SERIES_TAIL(frame) = len; - for (value++, len--; len > 0; len--, value++) SET_NONE(value); // skip first value (self) - SET_END(value); - - return frame; -} - - -/*********************************************************************** -** -*/ void Rebind_Frame(REBSER *src_frame, REBSER *dst_frame) -/* -** Clone old src_frame to new dst_frame knowing -** which types of values need to be copied, deep copied, and rebound. -** -***********************************************************************/ -{ - // Rebind all values: - Rebind_Block(src_frame, dst_frame, BLK_SKIP(dst_frame, 1), REBIND_FUNC); -} - - -/*********************************************************************** -** -*/ REBSER *Make_Object(REBSER *parent, REBVAL *block) -/* -** Create an object from a parent object and a spec block. -** The words within the resultant object are not bound. -** -***********************************************************************/ -{ - REBSER *words; - REBSER *object; - - PG_Reb_Stats->Objects++; - - if (!block || IS_END(block)) { - object = parent ? Copy_Block_Values(parent, 0, SERIES_TAIL(parent), TS_CLONE) : Make_Frame(0); - } else { - words = Collect_Frame(BIND_ONLY, parent, block); // GC safe - object = Create_Frame(words, 0); // GC safe - if (parent) { - if (Reb_Opts->watch_obj_copy) - Debug_Fmt(BOOT_STR(RS_WATCH, 2), SERIES_TAIL(parent) - 1, FRM_WORD_SERIES(object)); - // Copy parent values and deep copy blocks and strings: - COPY_VALUES(FRM_VALUES(parent)+1, FRM_VALUES(object)+1, SERIES_TAIL(parent) - 1); - Copy_Deep_Values(object, 1, SERIES_TAIL(object), TS_CLONE); - } - } - - //Dump_Frame(object); - return object; -} - - -/*********************************************************************** -** -*/ REBSER *Construct_Object(REBSER *parent, REBVAL *block, REBFLG asis) -/* -** Construct an object (partial evaluation of block). -** Parent can be null. Block is rebound. -** -***********************************************************************/ -{ - REBSER *frame; - - frame = Make_Object(parent, block); - if (NOT_END(block)) Bind_Block(frame, block, BIND_ONLY); - if (asis) Do_Min_Construct(block); - else Do_Construct(block); - return frame; -} - - -/*********************************************************************** -** -*/ REBSER *Make_Object_Block(REBSER *frame, REBINT mode) -/* -** Return a block containing words, values, or set-word: value -** pairs for the given object. Note: words are bound to original -** object. -** -** Modes: -** 1 for word -** 2 for value -** 3 for words and values -** -***********************************************************************/ -{ - REBVAL *words = FRM_WORDS(frame); - REBVAL *values = FRM_VALUES(frame); - REBSER *block; - REBVAL *value; - REBCNT n; - - n = (mode & 4) ? 0 : 1; - block = Make_Block(SERIES_TAIL(frame) * (n + 1)); - - for (; n < SERIES_TAIL(frame); n++) { - if (!VAL_GET_OPT(words+n, OPTS_HIDE)) { - if (mode & 1) { - value = Append_Value(block); - if (mode & 2) { - VAL_SET(value, REB_SET_WORD); - VAL_SET_LINE(value); - } - else VAL_SET(value, REB_WORD); //VAL_TYPE(words+n)); - VAL_WORD_SYM(value) = VAL_BIND_SYM(words+n); - VAL_WORD_INDEX(value) = n; - VAL_WORD_FRAME(value) = frame; - } - if (mode & 2) { - Append_Val(block, values+n); - } - } - } - - return block; -} - - -/*********************************************************************** -** -*/ void Assert_Public_Object(REBVAL *value) -/* -***********************************************************************/ -{ - REBVAL *word = BLK_HEAD(VAL_OBJ_WORDS(value)); - - for (; NOT_END(word); word++) - if (VAL_GET_OPT(word, OPTS_HIDE)) Trap0(RE_HIDDEN); -} - - -/*********************************************************************** -** -*/ REBVAL *Make_Module(REBVAL *spec) -/* -** Create a module from a spec and an init block. -** Call the Make_Module function in the system/intrinsic object. -** -***********************************************************************/ -{ - REBVAL *value; - - value = Do_Sys_Func(SYS_CTX_MAKE_MODULE_P, spec, 0); // volatile - if (IS_NONE(value)) Trap1(RE_INVALID_SPEC, spec); - - return value; -} - - -/*********************************************************************** -** -*/ REBSER *Make_Module_Spec(REBVAL *block) -/* -** Create a module spec object. Holds module name, version, -** exports, locals, and more. See system/standard/module. -** -***********************************************************************/ -{ - REBSER *obj; - REBSER *frame; - - // Build standard module header object: - obj = VAL_OBJ_FRAME(Get_System(SYS_STANDARD, STD_SCRIPT)); - if (block && IS_BLOCK(block)) frame = Construct_Object(obj, VAL_BLK_DATA(block), 0); - else frame = CLONE_OBJECT(obj); - - return frame; -} - - -/*********************************************************************** -** -*/ REBSER *Merge_Frames(REBSER *parent1, REBSER *parent2) -/* -** Create a child frame from two parent frames. Merge common fields. -** Values from the second parent take precedence. -** -** Deep copy and rebind the child. -** -***********************************************************************/ -{ - REBSER *wrds; - REBSER *child; - REBVAL *words; - REBVAL *value; - REBCNT n; - REBINT *binds = WORDS_HEAD(Bind_Table); - - // Merge parent1 and parent2 words. - // Keep the binding table. - Collect_Start(BIND_ALL); - // Setup binding table and BUF_WORDS with parent1 words: - if (parent1) Collect_Object(parent1); - // Add parent2 words to binding table and BUF_WORDS: - Collect_Words(BLK_SKIP(FRM_WORD_SERIES(parent2), 1), BIND_ALL); - - // Allocate child (now that we know the correct size): - wrds = Copy_Series(BUF_WORDS); - child = Make_Block(SERIES_TAIL(wrds)); - value = Append_Value(child); - VAL_SET(value, REB_FRAME); - VAL_FRM_WORDS(value) = wrds; - VAL_FRM_SPEC(value) = 0; - - // Copy parent1 values: - COPY_VALUES(FRM_VALUES(parent1)+1, FRM_VALUES(child)+1, SERIES_TAIL(parent1)-1); - - // Copy parent2 values: - words = FRM_WORDS(parent2)+1; - value = FRM_VALUES(parent2)+1; - for (; NOT_END(words); words++, value++) { - // no need to search when the binding table is available - n = binds[VAL_WORD_CANON(words)]; - BLK_HEAD(child)[n] = *value; - } - - // Terminate the child frame: - SERIES_TAIL(child) = SERIES_TAIL(wrds); - BLK_TERM(child); - - // Deep copy the child - Copy_Deep_Values(child, 1, SERIES_TAIL(child), TS_CLONE); - - // Rebind the child - Rebind_Block(parent1, child, BLK_SKIP(child, 1), REBIND_FUNC); - Rebind_Block(parent2, child, BLK_SKIP(child, 1), REBIND_FUNC | REBIND_TABLE); - - // release the bind table - Collect_End(child); - - return child; -} - - -/*********************************************************************** -** -*/ void Resolve_Context(REBSER *target, REBSER *source, REBVAL *only_words, REBFLG all, REBFLG expand) -/* -** Only_words can be a block of words or an index in the target -** (for new words). -** -***********************************************************************/ -{ - REBINT *binds = WORDS_HEAD(Bind_Table); // GC safe to do here - REBVAL *words; - REBVAL *vals; - REBINT n; - REBINT m; - REBCNT i = 0; - - CHECK_BIND_TABLE; - - if (IS_PROTECT_SERIES(target)) Trap0(RE_PROTECTED); - - if (IS_INTEGER(only_words)) { // Must be: 0 < i <= tail - i = VAL_INT32(only_words); // never <= 0 - if (i == 0) i = 1; - if (i >= target->tail) return; - } - - Collect_Start(BIND_NO_SELF); // DO NOT TRAP IN THIS SECTION - - n = 0; - - // If limited resolve, tag the word ids that need to be copied: - if (i) { - // Only the new words of the target: - for (words = FRM_WORD(target, i); NOT_END(words); words++) - binds[VAL_BIND_CANON(words)] = -1; - n = SERIES_TAIL(target) - 1; - } - else if (IS_BLOCK(only_words)) { - // Limit exports to only these words: - for (words = VAL_BLK_DATA(only_words); NOT_END(words); words++) { - if (IS_WORD(words) || IS_SET_WORD(words)) { - binds[VAL_WORD_CANON(words)] = -1; - n++; - } - } - } - - // Expand target as needed: - if (expand && n > 0) { - // Determine how many new words to add: - for (words = FRM_WORD(target, 1); NOT_END(words); words++) - if (binds[VAL_BIND_CANON(words)]) n--; - // Expand frame by the amount required: - if (n > 0) Expand_Frame(target, n, 0); - else expand = 0; - } - - // Maps a word to its value index in the source context. - // Done by marking all source words (in bind table): - words = FRM_WORDS(source)+1; - for (n = 1; NOT_END(words); n++, words++) { - if (IS_NONE(only_words) || binds[VAL_BIND_CANON(words)]) - binds[VAL_WORD_CANON(words)] = n; - } - - // Foreach word in target, copy the correct value from source: - n = i ? i : 1; - vals = FRM_VALUE(target, n); - for (words = FRM_WORD(target, n); NOT_END(words); words++, vals++) { - if ((m = binds[VAL_BIND_CANON(words)])) { - binds[VAL_BIND_CANON(words)] = 0; // mark it as set - if (!VAL_PROTECTED(words) && (all || IS_UNSET(vals))) { - if (m < 0) SET_UNSET(vals); // no value in source context - else *vals = *FRM_VALUE(source, m); - //Debug_Num("type:", VAL_TYPE(vals)); - //Debug_Str(Get_Word_Name(words)); - } - } - } - - // Add any new words and values: - if (expand) { - REBVAL *val; - words = FRM_WORDS(source)+1; - for (n = 1; NOT_END(words); n++, words++) { - if (binds[VAL_BIND_CANON(words)]) { - // Note: no protect check is needed here - binds[VAL_BIND_CANON(words)] = 0; - val = Append_Frame(target, 0, VAL_BIND_SYM(words)); - *val = *FRM_VALUE(source, n); - } - } - } - else { - // Reset bind table (do not use Collect_End): - if (i) { - for (words = FRM_WORD(target, i); NOT_END(words); words++) - binds[VAL_BIND_CANON(words)] = 0; - } - else if (IS_BLOCK(only_words)) { - for (words = VAL_BLK_DATA(only_words); NOT_END(words); words++) { - if (IS_WORD(words) || IS_SET_WORD(words)) binds[VAL_WORD_CANON(words)] = 0; - } - } - else { - for (words = FRM_WORDS(source)+1; NOT_END(words); words++) - binds[VAL_BIND_CANON(words)] = 0; - } - } - - CHECK_BIND_TABLE; - - RESET_TAIL(BUF_WORDS); // allow reuse, trapping ok now -} - - -/*********************************************************************** -** -*/ static void Bind_Block_Words(REBSER *frame, REBVAL *value, REBCNT mode) -/* -** Inner loop of bind block. Modes are: -** -** BIND_ONLY Only bind the words found in the frame. -** BIND_SET Add set-words to the frame during the bind. -** BIND_ALL Add words to the frame during the bind. -** BIND_DEEP Recurse into sub-blocks. -** -** NOTE: BIND_SET must be used carefully, because it does not -** bind prior instances of the word before the set-word. That is -** forward references are not allowed. -** -***********************************************************************/ -{ - REBINT *binds = WORDS_HEAD(Bind_Table); // GC safe to do here - REBCNT n; - REBFLG selfish = !IS_SELFLESS(frame); - - for (; NOT_END(value); value++) { - if (ANY_WORD(value)) { - //Print("Word: %s", Get_Sym_Name(VAL_WORD_CANON(value))); - // Is the word found in this frame? - if (NZ(n = binds[VAL_WORD_CANON(value)])) { - if (n == NO_RESULT) n = 0; // SELF word - ASSERT1(n < SERIES_TAIL(frame), RP_BIND_BOUNDS); - // Word is in frame, bind it: - VAL_WORD_INDEX(value) = n; - VAL_WORD_FRAME(value) = frame; - } - else if (selfish && VAL_WORD_CANON(value) == SYM_SELF) { - VAL_WORD_INDEX(value) = 0; - VAL_WORD_FRAME(value) = frame; - } - else { - // Word is not in frame. Add it if option is specified: - if ((mode & BIND_ALL) || ((mode & BIND_SET) && (IS_SET_WORD(value)))) { - Append_Frame(frame, value, 0); - binds[VAL_WORD_CANON(value)] = VAL_WORD_INDEX(value); - } - } - } - else if (ANY_BLOCK(value) && (mode & BIND_DEEP)) - Bind_Block_Words(frame, VAL_BLK_DATA(value), mode); - else if ((IS_FUNCTION(value) || IS_CLOSURE(value)) && (mode & BIND_FUNC)) - Bind_Block_Words(frame, BLK_HEAD(VAL_FUNC_BODY(value)), mode); - } -} - - -/*********************************************************************** -** -*/ void Bind_Block(REBSER *frame, REBVAL *block, REBCNT mode) -/* -** Bind the words of a block to a specified frame. -** Different modes may be applied: -** BIND_ONLY - Only bind words found in the frame. -** BIND_ALL - Add words to the frame during the bind. -** BIND_SET - Add set-words to the frame during the bind. -** (note: word must not occur before the SET) -** BIND_DEEP - Recurse into sub-blocks. -** -***********************************************************************/ -{ - REBVAL *words; - REBCNT index; - REBINT *binds = WORDS_HEAD(Bind_Table); // GC safe to do here - - CHECK_MEMORY(4); - - CHECK_BIND_TABLE; - - // Note about optimization: it's not a big win to avoid the - // binding table for short blocks (size < 4), because testing - // every block for the rare case adds up. - - // Setup binding table: - index = 1; - for (index = 1; index < frame->tail; index++) { - words = FRM_WORD(frame, index); - if (!VAL_GET_OPT(words, OPTS_HIDE)) - binds[VAL_BIND_CANON(words)] = index; - } - - Bind_Block_Words(frame, block, mode); - - // Reset binding table: - for (words = FRM_WORDS(frame)+1; NOT_END(words); words++) - binds[VAL_BIND_CANON(words)] = 0; - - CHECK_BIND_TABLE; -} - - -/*********************************************************************** -** -*/ void Unbind_Block(REBVAL *val, REBCNT deep) -/* -***********************************************************************/ -{ - for (; NOT_END(val); val++) { - if (ANY_WORD(val)) { - UNBIND(val); - } - if (ANY_BLOCK(val) && deep) { - Unbind_Block(VAL_BLK_DATA(val), TRUE); - } - } -} - - -/*********************************************************************** -** -*/ REBCNT Bind_Word(REBSER *frame, REBVAL *word) -/* -** Binds a word to a frame. If word is not part of the -** frame, ignore it. -** -***********************************************************************/ -{ - REBCNT n; - - n = Find_Word_Index(frame, VAL_WORD_SYM(word), FALSE); - if (n) { - VAL_WORD_FRAME(word) = frame; - VAL_WORD_INDEX(word) = n; - } - return n; -} - - -/*********************************************************************** -** -*/ static void Bind_Relative_Words(REBSER *frame, REBSER *block) -/* -** Recursive function for relative function word binding. -** -** Note: frame arg points to an identifying series of the function, -** not a normal frame. This will be used to verify the word fetch. -** -***********************************************************************/ -{ - REBVAL *value = BLK_HEAD(block); - REBINT n; - - for (; NOT_END(value); value++) { - if (ANY_WORD(value)) { - // Is the word (canon sym) found in this frame? - if (NZ(n = WORDS_HEAD(Bind_Table)[VAL_WORD_CANON(value)])) { - // Word is in frame, bind it: - VAL_WORD_INDEX(value) = n; - VAL_WORD_FRAME(value) = frame; // func body - } - } - else if (ANY_BLOCK(value)) - Bind_Relative_Words(frame, VAL_SERIES(value)); - } -} - - -/*********************************************************************** -** -*/ void Bind_Relative(REBSER *words, REBSER *frame, REBSER *block) -/* -** Bind the words of a function block to a stack frame. -** To indicate the relative nature of the index, it is set to -** a negative offset. -** -** words: VAL_FUNC_ARGS(func) -** frame: VAL_FUNC_ARGS(func) -** block: block to bind -** -***********************************************************************/ -{ - REBVAL *args; - REBINT index; - REBINT *binds = WORDS_HEAD(Bind_Table); // GC safe to do here - - args = BLK_SKIP(words, 1); - - CHECK_BIND_TABLE; - - //Dump_Block(words); - - // Setup binding table from the argument word list: - for (index = 1; NOT_END(args); args++, index++) - binds[VAL_BIND_CANON(args)] = -index; - - Bind_Relative_Words(frame, block); - - // Reset binding table: - for (args = BLK_SKIP(words, 1); NOT_END(args); args++) - binds[VAL_BIND_CANON(args)] = 0; - - CHECK_BIND_TABLE; -} - - -/*********************************************************************** -** -*/ void Bind_Stack_Block(REBSER *frame, REBSER *block) -/* -***********************************************************************/ -{ - Bind_Relative(frame, frame, block); -} - - -/*********************************************************************** -** -*/ void Bind_Stack_Word(REBSER *frame, REBVAL *word) -/* -***********************************************************************/ -{ - REBINT index; - - index = Find_Arg_Index(frame, VAL_WORD_SYM(word)); - if (!index) Trap1(RE_NOT_IN_CONTEXT, word); - VAL_WORD_FRAME(word) = frame; - VAL_WORD_INDEX(word) = -index; -} - - -/*********************************************************************** -** -*/ void Rebind_Block(REBSER *src_frame, REBSER *dst_frame, REBVAL *data, REBFLG modes) -/* -** Rebind all words that reference src frame to dst frame. -** Rebind is always deep. -** -** There are two types of frames: relative frames and normal frames. -** When frame_src type and frame_dst type differ, -** modes must have REBIND_TYPE. -** -***********************************************************************/ -{ - REBINT *binds = WORDS_HEAD(Bind_Table); - - for (; NOT_END(data); data++) { - if (ANY_BLOCK(data)) - Rebind_Block(src_frame, dst_frame, VAL_BLK_DATA(data), modes); - else if (ANY_WORD(data) && VAL_WORD_FRAME(data) == src_frame) { - VAL_WORD_FRAME(data) = dst_frame; - if (modes & REBIND_TABLE) VAL_WORD_INDEX(data) = binds[VAL_WORD_CANON(data)]; - if (modes & REBIND_TYPE) VAL_WORD_INDEX(data) = - VAL_WORD_INDEX(data); - } else if ((modes & REBIND_FUNC) && (IS_FUNCTION(data) || IS_CLOSURE(data))) - Rebind_Block(src_frame, dst_frame, BLK_HEAD(VAL_FUNC_BODY(data)), modes); - } -} - - -/*********************************************************************** -** -*/ REBCNT Find_Arg_Index(REBSER *args, REBCNT sym) -/* -** Find function arg word in function arg "frame". -** -***********************************************************************/ -{ - REBCNT n; - REBCNT s; - REBVAL *word; - REBCNT len; - - s = SYMBOL_TO_CANON(sym); // always compare to CANON sym - - word = BLK_SKIP(args, 1); - len = SERIES_TAIL(args); - - for (n = 1; n < len; n++, word++) - if (sym == VAL_BIND_SYM(word) || s == VAL_BIND_CANON(word)) return n; - - return 0; -} - - -/*********************************************************************** -** -*/ REBCNT Find_Word_Index(REBSER *frame, REBCNT sym, REBFLG always) -/* -** Search a frame looking for the given word symbol. -** Return the frame index for a word. Locate it by matching -** the canon word identifiers. Return 0 if not found. -** -***********************************************************************/ -{ - REBCNT len = SERIES_TAIL(FRM_WORD_SERIES(frame)); - REBVAL *word = FRM_WORDS(frame) + 1; - REBCNT n; - REBCNT s; - - s = SYMBOL_TO_CANON(sym); // always compare to CANON sym - - for (n = 1; n < len; n++, word++) - if (sym == VAL_BIND_SYM(word) || s == VAL_BIND_CANON(word)) - return (!always && VAL_GET_OPT(word, OPTS_HIDE)) ? 0 : n; - - return 0; -} - - -/*********************************************************************** -** -*/ REBVAL *Find_Word_Value(REBSER *frame, REBCNT sym) -/* -** Search a frame looking for the given word symbol and -** return the value for the word. Locate it by matching -** the canon word identifiers. Return NULL if not found. -** -***********************************************************************/ -{ - REBINT n; - - if (!frame) return 0; - n = Find_Word_Index(frame, sym, FALSE); - if (!n) return 0; - return BLK_SKIP(frame, n); -} - - -/*********************************************************************** -** -*/ REBVAL *Find_In_Contexts(REBCNT sym, REBVAL *where) -/* -** Search a block of objects for a given word symbol and -** return the value for the word. NULL if not found. -** -***********************************************************************/ -{ - REBVAL *val; - - for (; NOT_END(where); where++) { - if (IS_WORD(where)) { - val = Get_Var(where); - } - else if (IS_PATH(where)) { - Do_Path(&where, 0); - val = DS_TOP; // only safe for short time! - } - else - val = where; - - if (IS_OBJECT(val)) { - val = Find_Word_Value(VAL_OBJ_FRAME(val), sym); - if (val) return val; - } - } - return 0; -} - - -/*********************************************************************** -** -*/ REBCNT Find_Word(REBSER *series, REBCNT index, REBCNT sym) -/* -** Find word (of any type) in a block... quickly. -** -***********************************************************************/ -{ - REBVAL *value; - - for (; index < SERIES_TAIL(series); index++) { - value = BLK_SKIP(series, index); - if (ANY_WORD(value) && sym == VAL_WORD_CANON(value)) - return index; - } - - return NOT_FOUND; -} - - -/*********************************************************************** -** -*/ REBVAL *Get_Var(REBVAL *word) -/* -** Get the word (variable) value. (Use macro when possible). -** -***********************************************************************/ -{ - REBINT index = VAL_WORD_INDEX(word); - REBSER *frame = VAL_WORD_FRAME(word); - REBINT dsf; - - if (!frame) Trap1(RE_NOT_DEFINED, word); - if (index >= 0) return FRM_VALUES(frame)+index; - - // A negative index indicates that the value is in a frame on - // the data stack, so now we must find it by walking back the - // stack looking for the function that the word is bound to. - dsf = DSF; - while (frame != VAL_WORD_FRAME(DSF_WORD(dsf))) { - dsf = PRIOR_DSF(dsf); - if (dsf <= 0) Trap1(RE_NOT_DEFINED, word); // change error !!! - } -// if (Trace_Level) Dump_Stack_Frame(dsf); - return DSF_ARGS(dsf, -index); -} - - -/*********************************************************************** -** -*/ REBVAL *Get_Var_Safe(REBVAL *word) -/* -** Get the word, but check if it will be safe to modify. -** -***********************************************************************/ -{ - REBINT index = VAL_WORD_INDEX(word); - REBSER *frame = VAL_WORD_FRAME(word); - REBINT dsf; - - if (!frame) Trap1(RE_NOT_DEFINED, word); - - if (index >= 0) { - if (VAL_PROTECTED(FRM_WORDS(frame) + index)) - Trap1(RE_LOCKED_WORD, word); - return FRM_VALUES(frame) + index; - } - - // A negative index indicates that the value is in a frame on - // the data stack, so now we must find it by walking back the - // stack looking for the function that the word is bound to. - dsf = DSF; - while (frame != VAL_WORD_FRAME(DSF_WORD(dsf))) { - dsf = PRIOR_DSF(dsf); - if (dsf <= 0) Trap1(RE_NOT_DEFINED, word); // change error !!! - } -// if (Trace_Level) Dump_Stack_Frame(dsf); - return DSF_ARGS(dsf, -index); -} - - -/*********************************************************************** -** -*/ REBVAL *Get_Var_No_Trap(REBVAL *word) -/* -** Same as above, but returns 0 rather than error. -** -***********************************************************************/ -{ - REBINT index = VAL_WORD_INDEX(word); - REBSER *frame = VAL_WORD_FRAME(word); - REBINT dsf; - - if (!frame) return 0; - if (index >= 0) return FRM_VALUES(frame)+index; - dsf = DSF; - while (frame != VAL_WORD_FRAME(DSF_WORD(dsf))) { - dsf = PRIOR_DSF(dsf); - if (dsf <= 0) return 0; - } - return DSF_ARGS(dsf, -index); -} - - -/*********************************************************************** -** -*/ REBVAL *Get_Any_Var(REBVAL *item) -/* -** Works for words and paths. For paths, return value is -** volatile on top of stack. -** -***********************************************************************/ -{ - if (IS_WORD(item)) return Get_Var(item); - if (IS_PATH(item)) { - REBVAL *path = item; - if (Do_Path(&path, 0)) return item; // found a function - item = DS_TOP; - } - return item; -} - - -/*********************************************************************** -** -*/ void Set_Var(REBVAL *word, REBVAL *value) -/* -** Set the word (variable) value. (Use macro when possible). -** -***********************************************************************/ -{ - REBINT index = VAL_WORD_INDEX(word); - REBINT dsf; - REBSER *frm; - - if (THROWN(value)) return; - - if (!HAS_FRAME(word)) Trap1(RE_NOT_DEFINED, word); - -// ASSERT(index, RP_BAD_SET_INDEX); - ASSERT(VAL_WORD_FRAME(word), RP_BAD_SET_CONTEXT); -// Print("Set %s to %s [frame: %x idx: %d]", Get_Word_Name(word), Get_Type_Name(value), VAL_WORD_FRAME(word), VAL_WORD_INDEX(word)); - - if (index > 0) { - frm = VAL_WORD_FRAME(word); - if (VAL_PROTECTED(FRM_WORDS(frm)+index)) - Trap1(RE_LOCKED_WORD, word); - FRM_VALUES(frm)[index] = *value; - return; - } - if (index == 0) Trap0(RE_SELF_PROTECTED); - - // Find relative value: - dsf = DSF; - while (VAL_WORD_FRAME(word) != VAL_WORD_FRAME(DSF_WORD(dsf))) { - dsf = PRIOR_DSF(dsf); - if (dsf <= 0) Trap1(RE_NOT_DEFINED, word); // change error !!! - } - *DSF_ARGS(dsf, -index) = *value; -} - - -/*********************************************************************** -** -*/ void Set_Var_Series(REBVAL *var, REBCNT type, REBSER *series, REBCNT index) -/* -** A commonly used helper function to set a variable -** to a series value. -** -***********************************************************************/ -{ - REBVAL value; - - VAL_SET(&value, type); - VAL_SERIES(&value) = series; - VAL_INDEX(&value) = index; - VAL_SERIES_SIDE(&value) = 0; - - Set_Var(var, &value); -} - - -/*********************************************************************** -** -*/ void Set_Var_Basic(REBVAL *var, REBCNT type, ...) -/* -** A commonly used helper function to set a variable -** to a basic value. -** -***********************************************************************/ -{ - REBVAL value = {0}; - - VAL_SET(&value, type); - - Set_Var(var, &value); -} - - -/*********************************************************************** -** -*/ REBVAL *Obj_Word(REBVAL *value, REBCNT index) -/* -** Return pointer to the nth WORD of an object. -** -***********************************************************************/ -{ - REBSER *obj = VAL_OBJ_WORDS(value); - return BLK_SKIP(obj, index); -} - - -/*********************************************************************** -** -*/ REBVAL *Obj_Value(REBVAL *value, REBCNT index) -/* -** Return pointer to the nth VALUE of an object. -** Return zero if the index is not valid. -** -***********************************************************************/ -{ - REBSER *obj = VAL_OBJ_FRAME(value); - - if (index >= SERIES_TAIL(obj)) return 0; - return BLK_SKIP(obj, index); -} - - -/*********************************************************************** -** -*/ void Init_Obj_Value(REBVAL *value, REBSER *frame) -/* -***********************************************************************/ -{ - ASSERT(frame, RP_BAD_SET_CONTEXT); - CLEARS(value); - SET_OBJECT(value, frame); -} - -/*********************************************************************** -** -*/ void Check_Frame(REBSER *frame) -/* -***********************************************************************/ -{ - REBINT n; - REBVAL *values = FRM_VALUES(frame); - REBVAL *words = FRM_WORDS(frame); - REBINT tail = SERIES_TAIL(frame); - - for (n = 0; n < tail; n++, values++, words++) { - if (IS_END(words) || IS_END(values)) { - Debug_Fmt("** Early %s end at index: %d", IS_END(words) ? "words" : "values", n); - } - } - - if (NOT_END(words) || NOT_END(values)) - Debug_Fmt("** Missing %s end at index: %d type: %d", NOT_END(words) ? "words" : "values", n, VAL_TYPE(words)); -} - - -/*********************************************************************** -** -*/ void Init_Frame(void) -/* -***********************************************************************/ -{ - // Temporary block used while scanning for frame words: - Set_Root_Series(TASK_BUF_WORDS, Make_Block(100), "word cache"); // just holds words, no GC -} diff --git a/src/core/c-function.c b/src/core/c-function.c index 293ee21c24..0538a9ce3f 100644 --- a/src/core/c-function.c +++ b/src/core/c-function.c @@ -1,471 +1,2163 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: c-function.c -** Summary: support for functions, actions, and closures -** Section: core -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ -/* - Structure of functions: - - spec - interface spec block - body - body code - args - args list (see below) - - Args list is a block of word+values: - - word - word, 'word, :word, /word - value - typeset! or none (valid datatypes) - - Args list provides: - - 1. specifies arg order, arg kind (e.g. 'word) - 2. specifies valid datatypes (typesets) - 3. used for word and type in error output - 4. used for debugging tools (stack dumps) - 5. not used for MOLD (spec is used) - 6. used as a (pseudo) frame of function variables - -*/ +// +// File: %c-function.c +// Summary: "support for functions, actions, and routines" +// Section: core +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/*********************************************************************** -** -*/ REBSER *List_Func_Words(REBVAL *func) -/* -** Return a block of function words, unbound. -** Note: skips 0th entry. -** -***********************************************************************/ +// +// List_Func_Words: C +// +// Return a block of function words, unbound. +// Note: skips 0th entry. +// +REBARR *List_Func_Words(const RELVAL *func, REBOOL pure_locals) { - REBSER *block; - REBSER *words = VAL_FUNC_WORDS(func); - REBCNT n; - REBVAL *value; - REBVAL *word; - - block = Make_Block(SERIES_TAIL(words)); - word = BLK_SKIP(words, 1); - - for (n = 1; n < SERIES_TAIL(words); word++, n++) { - value = Append_Value(block); - VAL_SET(value, VAL_TYPE(word)); - VAL_WORD_SYM(value) = VAL_BIND_SYM(word); - UNBIND(value); - } - - return block; + REBARR *array = Make_Array(VAL_FUNC_NUM_PARAMS(func)); + REBVAL *param = VAL_FUNC_PARAMS_HEAD(func); + + for (; NOT_END(param); param++) { + enum Reb_Kind kind; + + switch (VAL_PARAM_CLASS(param)) { + case PARAM_CLASS_NORMAL: + kind = REB_WORD; + break; + + case PARAM_CLASS_TIGHT: + kind = REB_ISSUE; + break; + + case PARAM_CLASS_REFINEMENT: + kind = REB_REFINEMENT; + break; + + case PARAM_CLASS_HARD_QUOTE: + kind = REB_GET_WORD; + break; + + case PARAM_CLASS_SOFT_QUOTE: + kind = REB_LIT_WORD; + break; + + case PARAM_CLASS_LOCAL: + case PARAM_CLASS_RETURN: // "magic" local - prefilled invisibly + case PARAM_CLASS_LEAVE: // "magic" local - prefilled invisibly + if (!pure_locals) + continue; // treat as invisible, e.g. for WORDS-OF + + kind = REB_SET_WORD; + break; + + default: + assert(FALSE); + DEAD_END; + } + + Init_Any_Word( + Alloc_Tail_Array(array), kind, VAL_PARAM_SPELLING(param) + ); + } + + return array; } -/*********************************************************************** -** -*/ REBSER *List_Func_Types(REBVAL *func) -/* -** Return a block of function arg types. -** Note: skips 0th entry. -** -***********************************************************************/ +// +// List_Func_Typesets: C +// +// Return a block of function arg typesets. +// Note: skips 0th entry. +// +REBARR *List_Func_Typesets(REBVAL *func) { - REBSER *block; - REBSER *words = VAL_FUNC_WORDS(func); - REBCNT n; - REBVAL *value; - REBVAL *word; - - block = Make_Block(SERIES_TAIL(words)); - word = BLK_SKIP(words, 1); - - for (n = 1; n < SERIES_TAIL(words); word++, n++) { - value = Append_Value(block); - VAL_SET(value, VAL_TYPE(word)); - VAL_WORD_SYM(value) = VAL_BIND_SYM(word); - UNBIND(value); - } - - return block; + REBARR *array = Make_Array(VAL_FUNC_NUM_PARAMS(func)); + REBVAL *typeset = VAL_FUNC_PARAMS_HEAD(func); + + for (; NOT_END(typeset); typeset++) { + assert(IS_TYPESET(typeset)); + + REBVAL *value = Alloc_Tail_Array(array); + Move_Value(value, typeset); + + // !!! It's already a typeset, but this will clear out the header + // bits. This may not be desirable over the long run (what if + // a typeset wishes to encode hiddenness, protectedness, etc?) + // + VAL_RESET_HEADER(value, REB_TYPESET); + } + + return array; +} + + +enum Reb_Spec_Mode { + SPEC_MODE_NORMAL, // words are arguments + SPEC_MODE_LOCAL, // words are locals + SPEC_MODE_WITH // words are "extern" +}; + + +// +// Make_Paramlist_Managed_May_Fail: C +// +// Check function spec of the form: +// +// ["description" arg "notes" [type! type2! ...] /ref ...] +// +// !!! The spec language was not formalized in R3-Alpha. Strings were left +// in and it was HELP's job (and any other clients) to make sense of it, e.g.: +// +// [foo [type!] {doc string :-)}] +// [foo {doc string :-/} [type!]] +// [foo {doc string1 :-/} {doc string2 :-(} [type!]] +// +// Ren-C breaks this into two parts: one is the mechanical understanding of +// MAKE FUNCTION! for parameters in the evaluator. Then it is the job +// of a generator to tag the resulting function with a "meta object" with any +// descriptions. As a proxy for the work of a usermode generator, this +// routine tries to fill in FUNCTION-META (see %sysobj.r) as well as to +// produce a paramlist suitable for the function. +// +// Note a "true local" (indicated by a set-word) is considered to be tacit +// approval of wanting a definitional return by the generator. This helps +// because Red's model for specifying returns uses a SET-WORD! +// +// func [return: [integer!] {returns an integer}] +// +// In Ren/C's case it just means you want a local called return, but the +// generator will be "initializing it with a definitional return" for you. +// You don't have to use it if you don't want to...and may overwrite the +// variable. But it won't be a void at the start. +// +REBARR *Make_Paramlist_Managed_May_Fail( + const REBVAL *spec, + REBFLGS flags +) { + assert(ANY_ARRAY(spec)); + + REBUPT header_bits = 0; + +#if !defined(NDEBUG) + // + // Debug builds go ahead and include a RETURN field and hang onto the + // typeset for fake returns (e.g. natives). But they make a note that + // they are doing this, which helps know what the actual size of the + // frame would be in a release build (e.g. for a FRM_CELL() assert) + // + if (flags & MKF_FAKE_RETURN) { + header_bits |= FUNC_FLAG_RETURN_DEBUG; + flags &= ~MKF_FAKE_RETURN; + assert(NOT(flags & MKF_RETURN)); + flags |= MKF_RETURN; + } +#endif + + REBOOL durable = FALSE; + + REBDSP dsp_orig = DSP; + assert(DS_TOP == DS_AT(dsp_orig)); + + REBDSP definitional_return_dsp = 0; + REBDSP definitional_leave_dsp = 0; + + // As we go through the spec block, we push TYPESET! BLOCK! STRING! triples. + // These will be split out into separate arrays after the process is done. + // The first slot of the paramlist needs to be the function canon value, + // while the other two first slots need to be rootkeys. Get the process + // started right after a BLOCK! so it's willing to take a string for + // the function description--it will be extracted from the slot before + // it is turned into a rootkey for param_notes. + // + DS_PUSH_TRASH; // paramlist[0] (will become FUNCTION! canon value) + SET_UNREADABLE_BLANK(DS_TOP); + DS_PUSH(EMPTY_BLOCK); // param_types[0] (to be OBJECT! canon value, if any) + DS_PUSH(EMPTY_STRING); // param_notes[0] (holds description, then canon) + + REBOOL has_description = FALSE; + REBOOL has_types = FALSE; + REBOOL has_notes = FALSE; + + enum Reb_Spec_Mode mode = SPEC_MODE_NORMAL; + + REBOOL refinement_seen = FALSE; + + DECLARE_FRAME (f); + Push_Frame(f, spec); + + while (NOT_END(f->value)) { + const RELVAL *item = f->value; // "faked", e.g. => RETURN: + Fetch_Next_In_Frame(f); // go ahead and consume next + + //=//// STRING! FOR FUNCTION DESCRIPTION OR PARAMETER NOTE ////////////=// + + if (IS_STRING(item)) { + // + // Consider `[ some-extern "description of that extern"]` to + // be purely commentary for the implementation, and don't include + // it in the meta info. + // + if (mode == SPEC_MODE_WITH) + continue; + + if (IS_TYPESET(DS_TOP)) + DS_PUSH(EMPTY_BLOCK); // need a block to be in position + + if (IS_BLOCK(DS_TOP)) { // we're in right spot to push notes/title + DS_PUSH_TRASH; + Init_String( + DS_TOP, + Copy_String_Slimming(VAL_SERIES(item), VAL_INDEX(item), -1) + ); + } + else { + assert(IS_STRING(DS_TOP)); + + // !!! A string was already pushed. Should we append? + // + Init_String( + DS_TOP, + Copy_String_Slimming(VAL_SERIES(item), VAL_INDEX(item), -1) + ); + } + + if (DS_TOP == DS_AT(dsp_orig + 3)) + has_description = TRUE; + else + has_notes = TRUE; + + continue; + } + + //=//// TOP-LEVEL SPEC TAGS LIKE , etc. /////////////////=// + + if (IS_TAG(item) && (flags & MKF_KEYWORDS)) { + if (0 == Compare_String_Vals(item, ROOT_WITH_TAG, TRUE)) { + mode = SPEC_MODE_WITH; + } + else if (0 == Compare_String_Vals(item, ROOT_LOCAL_TAG, TRUE)) { + mode = SPEC_MODE_LOCAL; + } + else if (0 == Compare_String_Vals(item, ROOT_DURABLE_TAG, TRUE)) { + // + // is currently a lesser version of what it + // hopes to be, but signals what R3-Alpha called CLOSURE! + // semantics. Indicating that a typeset is durable in + // the low-level will need to be done with some notation + // that doesn't use "keywords"--perhaps a #[true] or a + // #[false] picked up on by the typeset. + // + // !!! Enforce only at the head, if it's going to be + // applying to everything?? + // + durable = TRUE; + } + else + fail (Error_Bad_Func_Def_Core(item, VAL_SPECIFIER(spec))); + + continue; + } + + //=//// BLOCK! OF TYPES TO MAKE TYPESET FROM (PLUS PARAMETER TAGS) ////=// + + if (IS_BLOCK(item)) { + if (IS_BLOCK(DS_TOP)) // two blocks of types! + fail (Error_Bad_Func_Def_Core(item, VAL_SPECIFIER(spec))); + + // You currently can't say ` x [integer!]`, because they + // are always void when the function runs. You can't say + // ` x [integer!]` because "externs" don't have param slots + // to store the type in. + // + // !!! A type constraint on a parameter might be useful, + // though--and could be achieved by adding a type checker into + // the body of the function. However, that would be more holistic + // than this generation of just a paramlist. Consider for future. + // + if (mode != SPEC_MODE_NORMAL) + fail (Error_Bad_Func_Def_Core(item, VAL_SPECIFIER(spec))); + + // Save the block for parameter types. + // + REBVAL *typeset; + if (IS_TYPESET(DS_TOP)) { + REBSPC *derived = Derive_Specifier(VAL_SPECIFIER(spec), item); + DS_PUSH_TRASH; + Init_Block( + DS_TOP, + Copy_Array_At_Deep_Managed( + VAL_ARRAY(item), + VAL_INDEX(item), + derived + ) + ); + + typeset = DS_TOP - 1; // volatile if you DS_PUSH! + } + else { + assert(IS_STRING(DS_TOP)); // !!! are blocks after notes good? + + if (IS_BLANK_RAW(DS_TOP - 2)) { + // + // No typesets pushed yet, so this is a block before any + // parameters have been named. This was legal in Rebol2 + // for e.g. `func [[catch] x y][...]`, and R3-Alpha + // ignored it. Ren-C only tolerates this in , + // (with the tolerance implemented in compatibility FUNC) + // + fail (Error_Bad_Func_Def_Core(item, VAL_SPECIFIER(spec))); + } + + assert(IS_TYPESET(DS_TOP - 2)); + typeset = DS_TOP - 2; + + assert(IS_BLOCK(DS_TOP - 1)); + if (VAL_ARRAY(DS_TOP - 1) != EMPTY_ARRAY) + fail (Error_Bad_Func_Def_Core(item, VAL_SPECIFIER(spec))); + + REBSPC *derived = Derive_Specifier(VAL_SPECIFIER(spec), item); + Init_Block( + DS_TOP - 1, + Copy_Array_At_Deep_Managed( + VAL_ARRAY(item), + VAL_INDEX(item), + derived + ) + ); + } + + // Turn block into typeset for parameter at current index. + // Leaves VAL_TYPESET_SYM as-is. + // + REBSPC *derived = Derive_Specifier(VAL_SPECIFIER(spec), item); + Update_Typeset_Bits_Core( + typeset, + VAL_ARRAY_HEAD(item), + derived + ); + + // Refinements and refinement arguments cannot be specified as + // . Although refinement arguments may be void, they are + // not "passed in" that way...the refinement is inactive. + // + if (refinement_seen) { + if (TYPE_CHECK(typeset, REB_MAX_VOID)) + fail (Error_Refinement_Arg_Opt_Raw()); + } + + + // A hard quote can only get a void if it is an , and that + // is not reflected in the typeset but in TYPESET_FLAG_ENDABLE + // + if (VAL_PARAM_CLASS(typeset) == PARAM_CLASS_HARD_QUOTE) { + if (TYPE_CHECK(typeset, REB_MAX_VOID)) { + DECLARE_LOCAL (param_name); + Init_Word(param_name, VAL_PARAM_SPELLING(typeset)); + fail (Error_Hard_Quote_Void_Raw(param_name)); + } + } + + has_types = TRUE; + continue; + } + + //=//// ANY-WORD! PARAMETERS THEMSELVES (MAKE TYPESETS w/SYMBOL) //////=// + + if (!ANY_WORD(item)) + fail (Error_Bad_Func_Def_Core(item, VAL_SPECIFIER(spec))); + + // !!! If you say [ x /foo y] the terminates and a + // refinement is started. Same w/. Is this a good idea? + // Note that historically, help hides any refinements that appear + // behind a /local, but this feature has no parallel in Ren-C. + // + if (mode != SPEC_MODE_NORMAL) { + if (IS_REFINEMENT(item)) { + mode = SPEC_MODE_NORMAL; + } + else if (!IS_WORD(item) && !IS_SET_WORD(item)) + fail (Error_Bad_Func_Def_Core(item, VAL_SPECIFIER(spec))); + } + + REBSTR *canon = VAL_WORD_CANON(item); + + // In rhythm of TYPESET! BLOCK! STRING! we want to be on a string spot + // at the time of the push of each new typeset. + // + if (IS_TYPESET(DS_TOP)) + DS_PUSH(EMPTY_BLOCK); + if (IS_BLOCK(DS_TOP)) + DS_PUSH(EMPTY_STRING); + assert(IS_STRING(DS_TOP)); + + // By default allow "all datatypes but function and void". Note that + // since void isn't a "datatype" the use of the REB_MAX_VOID bit is for + // expedience. Also that there are two senses of void signal...the + // typeset REB_MAX_VOID represents sense, not the sense, + // which is encoded by TYPESET_FLAG_ENDABLE. + // + // We do not canonize the saved symbol in the paramlist, see #2258. + // + DS_PUSH_TRASH; + REBVAL *typeset = DS_TOP; // volatile if you DS_PUSH! + Init_Typeset( + typeset, + (flags & MKF_ANY_VALUE) + ? ALL_64 + : ALL_64 & ~(FLAGIT_KIND(REB_MAX_VOID) | FLAGIT_KIND(REB_FUNCTION)), + VAL_WORD_SPELLING(item) + ); + + // All these would cancel a definitional return (leave has same idea): + // + // func [return [integer!]] + // func [/refinement return] + // func [ return] + // func [ return] + // + // ...although `return:` is explicitly tolerated ATM for compatibility + // (despite violating the "pure locals are NULL" premise) + // + if (STR_SYMBOL(canon) == SYM_RETURN && NOT(flags & MKF_LEAVE)) { + assert(definitional_return_dsp == 0); + if (IS_SET_WORD(item)) + definitional_return_dsp = DSP; // RETURN: explicitly tolerated + else + flags &= ~(MKF_RETURN | MKF_FAKE_RETURN); + } + else if ( + STR_SYMBOL(canon) == SYM_LEAVE + && NOT(flags & (MKF_RETURN | MKF_FAKE_RETURN)) + ) { + assert(definitional_leave_dsp == 0); + if (IS_SET_WORD(item)) + definitional_leave_dsp = DSP; // LEAVE: explicitly tolerated + else + flags &= ~MKF_LEAVE; + } + + if (mode == SPEC_MODE_WITH && !IS_SET_WORD(item)) { + // + // Because FUNC does not do any locals gathering by default, the + // main purpose of is for instructing it not to do the + // definitional returns. However, it also makes changing between + // FUNC and FUNCTION more fluid. + // + // !!! If you write something like `func [x x] [...]` that + // should be sanity checked with an error...TBD. + // + DS_DROP; // forge the typeset, used in `definitional_return` case + continue; + } + + switch (VAL_TYPE(item)) { + case REB_WORD: + assert(mode != SPEC_MODE_WITH); // should have continued... + INIT_VAL_PARAM_CLASS( + typeset, + (mode == SPEC_MODE_LOCAL) + ? PARAM_CLASS_LOCAL + : PARAM_CLASS_NORMAL + ); + break; + + case REB_GET_WORD: + assert(mode == SPEC_MODE_NORMAL); + INIT_VAL_PARAM_CLASS(typeset, PARAM_CLASS_HARD_QUOTE); + break; + + case REB_LIT_WORD: + assert(mode == SPEC_MODE_NORMAL); + INIT_VAL_PARAM_CLASS(typeset, PARAM_CLASS_SOFT_QUOTE); + break; + + case REB_REFINEMENT: + refinement_seen = TRUE; + INIT_VAL_PARAM_CLASS(typeset, PARAM_CLASS_REFINEMENT); + + // !!! The typeset bits of a refinement are not currently used. + // They are checked for TRUE or FALSE but this is done literally + // by the code. This means that every refinement has some spare + // bits available in it for another purpose. + break; + + case REB_SET_WORD: + // tolerate as-is if in or mode... + INIT_VAL_PARAM_CLASS(typeset, PARAM_CLASS_LOCAL); + // + // !!! Typeset bits of pure locals also not currently used, + // though definitional return should be using it for the return + // type of the function. + // + break; + + case REB_ISSUE: + // + // !!! Because of their role in the preprocessor in Red, and a + // likely need for a similar behavior in Rebol, ISSUE! might not + // be the ideal choice to mark tight parameters. + // + assert(mode == SPEC_MODE_NORMAL); + INIT_VAL_PARAM_CLASS(typeset, PARAM_CLASS_TIGHT); + break; + + default: + fail (Error_Bad_Func_Def_Core(item, VAL_SPECIFIER(spec))); + } + + // !!! This is a lame way of setting the durability, because it means + // that there's no way a user with just `make function!` could do it. + // However, it's a step closer to the solution and eliminating the + // FUNCTION!/CLOSURE! distinction. + // + if (durable) + SET_VAL_FLAG(typeset, TYPESET_FLAG_DURABLE); + } + + Drop_Frame(f); + + // Go ahead and flesh out the TYPESET! BLOCK! STRING! triples. + // + if (IS_TYPESET(DS_TOP)) + DS_PUSH(EMPTY_BLOCK); + if (IS_BLOCK(DS_TOP)) + DS_PUSH(EMPTY_STRING); + assert((DSP - dsp_orig) % 3 == 0); // must be a multiple of 3 + + // Definitional RETURN and LEAVE slots must have their argument values + // fulfilled with FUNCTION! values specific to the function being called + // on *every instantiation*. They are marked with special parameter + // classes to avoid needing to separately do canon comparison of their + // symbols to find them. In addition, since RETURN's typeset holds + // types that need to be checked at the end of the function run, it + // is moved to a predictable location: last slot of the paramlist. + // + // Note: Trying to take advantage of the "predictable first position" + // by swapping is not legal, as the first argument's position matters + // in the ordinary arity of calling. + + if (flags & MKF_LEAVE) { + if (definitional_leave_dsp == 0) { // no LEAVE: pure local explicit + REBSTR *canon_leave = Canon(SYM_LEAVE); + + DS_PUSH_TRASH; + Init_Typeset(DS_TOP, FLAGIT_KIND(REB_MAX_VOID), canon_leave); + INIT_VAL_PARAM_CLASS(DS_TOP, PARAM_CLASS_LEAVE); + definitional_leave_dsp = DSP; + + DS_PUSH(EMPTY_BLOCK); + DS_PUSH(EMPTY_STRING); + } + else { + REBVAL *definitional_leave = DS_AT(definitional_leave_dsp); + assert(VAL_PARAM_CLASS(definitional_leave) == PARAM_CLASS_LOCAL); + INIT_VAL_PARAM_CLASS(definitional_leave, PARAM_CLASS_LEAVE); + } + header_bits |= FUNC_FLAG_LEAVE; + } + + if (flags & MKF_RETURN) { + if (definitional_return_dsp == 0) { // no RETURN: pure local explicit + REBSTR *canon_return = Canon(SYM_RETURN); + + // !!! The current experiment for dealing with default type + // checking on definitional returns is to be somewhat restrictive + // if there are *any* documentation notes or typesets on the + // function. Hence: + // + // >> foo: func [x] [] ;-- no error, void return allowed + // >> foo: func [{a} x] [] ;-- will error, can't return void + // + // The idea is that if any effort has been expended on documenting + // the interface at all, it has some "public" component...so + // problems like leaking arbitrary values (vs. using PROC) are + // more likely to be relevant. Whereas no effort indicates a + // likely more ad-hoc experimentation. + // + // (A "strict" mode, selectable per module, could control this and + // other settings. But the goal is to attempt to define something + // that is as broadly usable as possible.) + // + DS_PUSH_TRASH; + Init_Typeset( + DS_TOP, + (flags & MKF_ANY_VALUE) + || NOT(has_description || has_types || has_notes) + ? ALL_64 + : ALL_64 & ~( + FLAGIT_KIND(REB_MAX_VOID) | FLAGIT_KIND(REB_FUNCTION) + ), + canon_return + ); + INIT_VAL_PARAM_CLASS(DS_TOP, PARAM_CLASS_RETURN); + definitional_return_dsp = DSP; + + DS_PUSH(EMPTY_BLOCK); + DS_PUSH(EMPTY_STRING); + // no need to move it--it's already at the tail position + } + else { + REBVAL *definitional_return = DS_AT(definitional_return_dsp); + assert(VAL_PARAM_CLASS(definitional_return) == PARAM_CLASS_LOCAL); + INIT_VAL_PARAM_CLASS(definitional_return, PARAM_CLASS_RETURN); + + // definitional_return handled specially when paramlist copied + // off of the stack... + } + header_bits |= FUNC_FLAG_RETURN; + } + + // Slots, which is length +1 (includes the rootvar or rootparam) + // + REBCNT num_slots = (DSP - dsp_orig) / 3; + + // If we pushed a typeset for a return and it's a native, it actually + // doesn't want a RETURN: key in the frame in release builds. We'll omit + // from the copy. + // + if (definitional_return_dsp != 0 && (flags & MKF_FAKE_RETURN)) + --num_slots; + + // There should be no more pushes past this point, so a stable pointer + // into the stack for the definitional return can be found. + // + REBVAL *definitional_return = + definitional_return_dsp == 0 + ? NULL + : DS_AT(definitional_return_dsp); + + // Must make the function "paramlist" even if "empty", for identity. + // Also make sure the parameter list does not expand. + // + // !!! Expanding the parameter list might be part of an advanced feature + // under the hood in the future, but users should not themselves grow + // function frames by appending to them. + // + REBARR *paramlist = Make_Array_Core( + num_slots, + ARRAY_FLAG_PARAMLIST | SERIES_FLAG_FIXED_SIZE + ); + + if (TRUE) { + RELVAL *dest = ARR_HEAD(paramlist); // canon function value + VAL_RESET_HEADER(dest, REB_FUNCTION); + SET_VAL_FLAGS(dest, header_bits); + dest->payload.function.paramlist = paramlist; + dest->extra.binding = NULL; + ++dest; + + // We want to check for duplicates and a Binder can be used for that + // purpose--but note that a fail() cannot happen while binders are + // in effect UNLESS the BUF_COLLECT contains information to undo it! + // There's no BUF_COLLECT here, so don't fail while binder in effect. + // + // (This is why we wait until the parameter list gathering process + // is over to do the duplicate checks--it can fail.) + // + struct Reb_Binder binder; + INIT_BINDER(&binder); + + REBSTR *duplicate = NULL; + + REBVAL *src = DS_AT(dsp_orig + 1) + 3; + + for (; src <= DS_TOP; src += 3) { + assert(IS_TYPESET(src)); + if (!Try_Add_Binder_Index(&binder, VAL_PARAM_CANON(src), 1020)) + duplicate = VAL_PARAM_SPELLING(src); + + if (definitional_return && src == definitional_return) + continue; + + Move_Value(dest, src); + ++dest; + } + + if (definitional_return) { + if (flags & MKF_FAKE_RETURN) { + // + // This is where you don't actually want a RETURN key in the + // function frame (e.g. because it's native code and would be + // wasteful and unused). + // + // !!! The debug build uses real returns, not fake ones. + // This means actions and natives have an extra slot. + // + } + else { + assert(flags & MKF_RETURN); + *dest = *definitional_return; + ++dest; + } + } + + // Must remove binder indexes for all words, even if about to fail + // + src = DS_AT(dsp_orig + 1) + 3; + for (; src <= DS_TOP; src += 3, ++dest) { + if (!Try_Remove_Binder_Index(&binder, VAL_PARAM_CANON(src))) + assert(duplicate != NULL); + } + + SHUTDOWN_BINDER(&binder); + + if (duplicate != NULL) { + DECLARE_LOCAL (word); + Init_Word(word, duplicate); + fail (Error_Dup_Vars_Raw(word)); + } + + TERM_ARRAY_LEN(paramlist, num_slots); + MANAGE_ARRAY(paramlist); + } + + //=///////////////////////////////////////////////////////////////////=// + // + // BUILD META INFORMATION OBJECT (IF NEEDED) + // + //=///////////////////////////////////////////////////////////////////=// + + // !!! See notes on FUNCTION-META in %sysobj.r + + REBCTX *meta = NULL; + + if (has_description || has_types || has_notes) { + meta = Copy_Context_Shallow(VAL_CONTEXT(ROOT_FUNCTION_META)); + MANAGE_ARRAY(CTX_VARLIST(meta)); + } + + SER(paramlist)->link.meta = meta; + + // If a description string was gathered, it's sitting in the first string + // slot, the third cell we pushed onto the stack. Extract it if so. + // + if (has_description) { + assert(IS_STRING(DS_AT(dsp_orig + 3))); + Move_Value( + CTX_VAR(meta, STD_FUNCTION_META_DESCRIPTION), + DS_AT(dsp_orig + 3) + ); + } + else if (meta) + Init_Void(CTX_VAR(meta, STD_FUNCTION_META_DESCRIPTION)); + + // Only make `parameter-types` if there were blocks in the spec + // + if (NOT(has_types)) { + if (meta) { + Init_Void(CTX_VAR(meta, STD_FUNCTION_META_PARAMETER_TYPES)); + Init_Void(CTX_VAR(meta, STD_FUNCTION_META_RETURN_TYPE)); + } + } + else { + REBARR *types_varlist = Make_Array_Core( + num_slots, ARRAY_FLAG_VARLIST + ); + INIT_CTX_KEYLIST_SHARED(CTX(types_varlist), paramlist); + + REBVAL *dest = SINK(ARR_HEAD(types_varlist)); // "rootvar" + VAL_RESET_HEADER(dest, REB_FRAME); + dest->payload.any_context.varlist = types_varlist; // canon FRAME! + dest->payload.any_context.phase = AS_FUNC(paramlist); + dest->extra.binding = NULL; + ++dest; + + REBVAL *src = DS_AT(dsp_orig + 2); + src += 3; + for (; src <= DS_TOP; src += 3) { + assert(IS_BLOCK(src)); + if (definitional_return && src == definitional_return + 1) + continue; + + if (VAL_ARRAY_LEN_AT(src) == 0) + Init_Void(dest); + else + Move_Value(dest, src); + ++dest; + } + + if (definitional_return) { + // + // We put the return note in the top-level meta information, not + // on the local itself (the "return-ness" is a distinct property + // of the function from what word is used for RETURN:, and it + // is possible to use the word RETURN for a local or refinement + // argument while having nothing to do with the exit value of + // the function.) + // + if (VAL_ARRAY_LEN_AT(definitional_return + 1) == 0) + Init_Void(CTX_VAR(meta, STD_FUNCTION_META_RETURN_TYPE)); + else { + Move_Value( + CTX_VAR(meta, STD_FUNCTION_META_RETURN_TYPE), + &definitional_return[1] + ); + } + + if (NOT(flags & MKF_FAKE_RETURN)) { + Init_Void(dest); // clear the local RETURN: var's description + ++dest; + } + } + + TERM_ARRAY_LEN(types_varlist, num_slots); + MANAGE_ARRAY(types_varlist); + + Init_Any_Context( + CTX_VAR(meta, STD_FUNCTION_META_PARAMETER_TYPES), + REB_FRAME, + CTX(types_varlist) + ); + } + + // Only make `parameter-notes` if there were strings (besides description) + // + if (NOT(has_notes)) { + if (meta) { + Init_Void(CTX_VAR(meta, STD_FUNCTION_META_PARAMETER_NOTES)); + Init_Void(CTX_VAR(meta, STD_FUNCTION_META_RETURN_NOTE)); + } + } + else { + REBARR *notes_varlist = Make_Array_Core( + num_slots, ARRAY_FLAG_VARLIST + ); + INIT_CTX_KEYLIST_SHARED(CTX(notes_varlist), paramlist); + + REBVAL *dest = SINK(ARR_HEAD(notes_varlist)); // "rootvar" + VAL_RESET_HEADER(dest, REB_FRAME); + dest->payload.any_context.varlist = notes_varlist; // canon FRAME! + dest->payload.any_context.phase = AS_FUNC(paramlist); + dest->extra.binding = NULL; + ++dest; + + REBVAL *src = DS_AT(dsp_orig + 3); + src += 3; + for (; src <= DS_TOP; src += 3) { + assert(IS_STRING(src)); + if (definitional_return && src == definitional_return + 2) + continue; + + if (SER_LEN(VAL_SERIES(src)) == 0) + Init_Void(dest); + else + Move_Value(dest, src); + ++dest; + } + + if (definitional_return) { + // + // See remarks on the return type--the RETURN is documented in + // the top-level META-OF, not the "incidentally" named RETURN + // parameter in the list + // + if (SER_LEN(VAL_SERIES(definitional_return + 2)) == 0) + Init_Void(CTX_VAR(meta, STD_FUNCTION_META_RETURN_NOTE)); + else { + Move_Value( + CTX_VAR(meta, STD_FUNCTION_META_RETURN_NOTE), + &definitional_return[2] + ); + } + + if (NOT(flags & MKF_FAKE_RETURN)) { + Init_Void(dest); + ++dest; + } + } + + TERM_ARRAY_LEN(notes_varlist, num_slots); + MANAGE_ARRAY(notes_varlist); + + Init_Any_Context( + CTX_VAR(meta, STD_FUNCTION_META_PARAMETER_NOTES), + REB_FRAME, + CTX(notes_varlist) + ); + } + + // With all the values extracted from stack to array, restore stack pointer + // + DS_DROP_TO(dsp_orig); + + return paramlist; +} + + + +// +// Find_Param_Index: C +// +// Find function param word in function "frame". +// +// !!! This is semi-redundant with similar functions for Find_Word_In_Array +// and key finding for objects, review... +// +REBCNT Find_Param_Index(REBARR *paramlist, REBSTR *spelling) +{ + REBSTR *canon = STR_CANON(spelling); // don't recalculate each time + + RELVAL *param = ARR_AT(paramlist, 1); + REBCNT len = ARR_LEN(paramlist); + + REBCNT n; + for (n = 1; n < len; ++n, ++param) { + if ( + spelling == VAL_PARAM_SPELLING(param) + || canon == VAL_PARAM_CANON(param) + ) { + return n; + } + } + + return 0; +} + + +// +// Make_Function: C +// +// Create an archetypal form of a function, given C code implementing a +// dispatcher that will be called by Do_Core. Dispatchers are of the form: +// +// REB_R Dispatcher(REBFRM *f) {...} +// +// The REBFUN returned is "archetypal" because individual REBVALs which hold +// the same REBFUN may differ in a per-REBVAL piece of "instance" data. +// (This is how one RETURN is distinguished from another--the instance +// data stored in the REBVAL identifies the pointer of the FRAME! to exit). +// +// Functions have an associated REBVAL-sized cell of data, accessible via +// FUNC_BODY(). This is where they can store information that will be +// available when the dispatcher is called. Despite being called "body", it +// doesn't have to be an array--it can be any REBVAL. +// +REBFUN *Make_Function( + REBARR *paramlist, + REBNAT dispatcher, // native C function called by Do_Core + REBFUN *opt_underlying, // function which has size of actual frame to push + REBCTX *opt_exemplar // specialization (or inherit from underlying) +) { + ASSERT_ARRAY_MANAGED(paramlist); + + RELVAL *rootparam = ARR_HEAD(paramlist); + assert(IS_FUNCTION(rootparam)); // !!! body not fully formed... + assert(rootparam->payload.function.paramlist == paramlist); + assert(rootparam->extra.binding == NULL); // archetype + + // Precalculate FUNC_FLAG_DEFERS_LOOKBACK + // + // Note that this flag is only relevant for *un-refined-calls*. There + // are no lookback function calls via PATH! and brancher dispatch is done + // from a raw function value. HOWEVER: specialization does come into play + // because it may change what the first "real" argument is. But again, + // we're only interested in specialization's removal of *non-refinement* + // arguments. Looking at the surface interface is good enough--that is + // what will be relevant after the specializations are accounted for. + + REBVAL *param = KNOWN(rootparam) + 1; + for (; NOT_END(param); ++param) { + switch (VAL_PARAM_CLASS(param)) { + case PARAM_CLASS_LOCAL: + case PARAM_CLASS_RETURN: + case PARAM_CLASS_LEAVE: + break; // skip. + + case PARAM_CLASS_REFINEMENT: + // + // hit before hitting any basic args, so not a brancher, and not + // a candidate for deferring lookback arguments. + // + goto done_caching; + + case PARAM_CLASS_NORMAL: + // + // First argument is not tight, cache flag to report it. + // + SET_VAL_FLAG(rootparam, FUNC_FLAG_DEFERS_LOOKBACK); + goto done_caching; + + // Otherwise, at least one argument but not one that requires the + // deferring of lookback. + + case PARAM_CLASS_TIGHT: + // + // First argument is tight, no flag needed + // + goto done_caching; + + case PARAM_CLASS_HARD_QUOTE: + case PARAM_CLASS_SOFT_QUOTE: + SET_VAL_FLAG(rootparam, FUNC_FLAG_QUOTES_FIRST_ARG); + goto done_caching; + + default: + assert(FALSE); + } + } + +done_caching:; + + // The "body" for a function can be any REBVAL. It doesn't have to be + // a block--it's anything that the dispatcher might wish to interpret. + + REBARR *body_holder = Alloc_Singular_Array(); + Init_Blank(ARR_HEAD(body_holder)); + MANAGE_ARRAY(body_holder); + + rootparam->payload.function.body_holder = body_holder; + + // The C function pointer is stored inside the REBSER node for the body. + // Hence there's no need for a `switch` on a function class in Do_Core, + // Having a level of indirection from the REBVAL bits themself also + // facilitates the "Hijacker" to change multiple REBVALs behavior. + + SER(body_holder)->misc.dispatcher = dispatcher; + + // When this function is run, it needs to push a stack frame with a + // certain number of arguments, and do type checking and parameter class + // conventions based on that. This frame must be compatible with the + // number of arguments expected by the underlying function, and must not + // allow any types to be passed to that underlying function it is not + // expecting (e.g. natives written to only take INTEGER! may crash if + // they get BLOCK!). But beyond those constraints, the outer function + // may have new parameter classes through a "facade". This facade is + // initially just the underlying function's paramlist, but may change. + // + if (opt_underlying) { + SER(paramlist)->misc.facade = + SER(FUNC_PARAMLIST(opt_underlying))->misc.facade; + } + else { + // To avoid NULL checking when a function is called and looking for + // the underlying function, the functions own pointer in if needed + // + SER(paramlist)->misc.facade = paramlist; + } + + if (opt_exemplar) { + assert( + CTX_LEN(opt_exemplar) + == ARR_LEN(SER(paramlist)->misc.facade) - 1 + ); + + SER(body_holder)->link.exemplar = opt_exemplar; + } + else if (opt_underlying) + SER(body_holder)->link.exemplar = + SER( + FUNC_VALUE(opt_underlying)->payload.function.body_holder + )->link.exemplar; + else + SER(body_holder)->link.exemplar = NULL; + + // The meta information may already be initialized, since the native + // version of paramlist construction sets up the FUNCTION-META information + // used by HELP. If so, it must be a valid REBCTX*. Otherwise NULL. + // + assert( + SER(paramlist)->link.meta == NULL + || GET_SER_FLAG( + CTX_VARLIST(SER(paramlist)->link.meta), ARRAY_FLAG_VARLIST + ) + ); + + // Note: used to set the keys of natives as read-only so that the debugger + // couldn't manipulate the values in a native frame out from under it, + // potentially crashing C code (vs. just causing userspace code to + // error). That protection is now done to the frame series on reification + // in order to be able to MAKE FRAME! and reuse the native's paramlist. + + assert(NOT_SER_FLAG(paramlist, SERIES_FLAG_FILE_LINE)); + assert(NOT_SER_FLAG(body_holder, SERIES_FLAG_FILE_LINE)); + + return AS_FUNC(paramlist); +} + + +// +// Make_Expired_Frame_Ctx_Managed: C +// +// Function bodies contain relative words and relative arrays. Arrays from +// this relativized body may only be put into a specified REBVAL once they +// have been combined with a frame. +// +// Reflection asks for function body data, when no instance is called. Hence +// a REBVAL must be produced somehow. If the body is being copied, then the +// option exists to convert all the references to unbound...but this isn't +// representative of the actual connections in the body. +// +// There could be an additional "archetype" state for the relative binding +// machinery. But making a one-off expired frame is an inexpensive option, +// at least while the specific binding is coming online. +// +// !!! To be written...was started for MOLD of function, and realized it's +// really only needed for the BODY-OF reflector that gives back REBVAL* +// +REBCTX *Make_Expired_Frame_Ctx_Managed(REBFUN *func) +{ + REBARR *varlist = Alloc_Singular_Array_Core(ARRAY_FLAG_VARLIST); + SET_SER_INFO(varlist, CONTEXT_INFO_STACK); + Init_Blank(ARR_HEAD(varlist)); + MANAGE_ARRAY(varlist); + + SET_SER_INFO(varlist, SERIES_INFO_INACCESSIBLE); + + REBCTX *expired = CTX(varlist); + + INIT_CTX_KEYLIST_SHARED(expired, FUNC_PARAMLIST(func)); + + CTX_VALUE(expired)->payload.any_context.varlist = varlist; + + // A NULL stored by the misc field of a REB_FRAME context's varlist which + // indicates that the frame has finished running. If it is stack-based, + // then that also means the data values are unavailable. + // + SER(varlist)->misc.f = NULL; + + return expired; } -/*********************************************************************** -** -*/ REBSER *Check_Func_Spec(REBSER *block) -/* -** Check function spec of the form: -** -** ["description" arg "notes" [type! type2! ...] /ref ...] -** -** Throw an error for invalid values. -** -***********************************************************************/ +// +// Get_Maybe_Fake_Func_Body: C +// +// The FUNC_FLAG_LEAVE and FUNC_FLAG_RETURN tricks used for definitional +// scoping make it seem like a generator authored more code in the function's +// body...but the code isn't *actually* there and an optimized internal +// trick is used. +// +// If the body is fake, it needs to be freed by the caller with +// Free_Series. This means that the body must currently be shallow +// copied, and the splicing slot must be in the topmost series. +// +REBARR *Get_Maybe_Fake_Func_Body(REBOOL *is_fake, const REBVAL *func) { - REBVAL *blk; - REBSER *words; - REBINT n = 0; - REBVAL *value; - - blk = BLK_HEAD(block); - words = Collect_Frame(BIND_ALL | BIND_NO_DUP | BIND_NO_SELF, 0, blk); - - // !!! needs more checks - for (; NOT_END(blk); blk++) { - switch (VAL_TYPE(blk)) { - case REB_BLOCK: - // Skip the SPEC block as an arg. Use other blocks as datatypes: - if (n > 0) Make_Typeset(VAL_BLK(blk), BLK_SKIP(words, n), 0); - break; - case REB_STRING: - case REB_INTEGER: // special case used by datatype test actions - break; - case REB_WORD: - case REB_GET_WORD: - case REB_LIT_WORD: - n++; - break; - case REB_REFINEMENT: - // Refinement only allows logic! and none! for its datatype: - n++; - value = BLK_SKIP(words, n); - VAL_TYPESET(value) = (TYPESET(REB_LOGIC) | TYPESET(REB_NONE)); - break; - case REB_SET_WORD: - default: - Trap1(RE_BAD_FUNC_DEF, blk); - } - } - - return words; //Create_Frame(words, 0); + REBARR *fake_body; + REBVAL *example = NULL; + + assert(IS_FUNCTION(func) && IS_FUNCTION_INTERPRETED(func)); + + REBCNT body_index; + if (GET_VAL_FLAG(func, FUNC_FLAG_RETURN)) { + if (GET_VAL_FLAG(func, FUNC_FLAG_LEAVE)) { + example = Get_System(SYS_STANDARD, STD_FUNC_BODY); + body_index = 8; + } + else { + example = Get_System(SYS_STANDARD, STD_FUNC_NO_LEAVE_BODY); + body_index = 4; + } + *is_fake = TRUE; + } + else if (GET_VAL_FLAG(func, FUNC_FLAG_LEAVE)) { + example = Get_System(SYS_STANDARD, STD_PROC_BODY); + body_index = 4; + *is_fake = TRUE; + } + else { + *is_fake = FALSE; + return VAL_ARRAY(VAL_FUNC_BODY(func)); + } + + // See comments in sysobj.r on standard/func-body and standard/proc-body + // + fake_body = Copy_Array_Shallow(VAL_ARRAY(example), VAL_SPECIFIER(example)); + + // Index 5 (or 4 in zero-based C) should be #BODY, a "real" body. Since + // the body has relative words and relative arrays and this is not pairing + // that with a frame from any specific invocation, the value must be + // marked as relative. + { + RELVAL *slot = ARR_AT(fake_body, body_index); // #BODY + assert(IS_ISSUE(slot)); + + VAL_RESET_HEADER(slot, REB_GROUP); + SET_VAL_FLAGS(slot, VALUE_FLAG_RELATIVE | VALUE_FLAG_LINE); + INIT_VAL_ARRAY(slot, VAL_ARRAY(VAL_FUNC_BODY(func))); + VAL_INDEX(slot) = 0; + INIT_RELATIVE(slot, VAL_FUNC(func)); + } + + return fake_body; +} + + +// +// Make_Interpreted_Function_May_Fail: C +// +// This is the support routine behind `MAKE FUNCTION!`, FUNC, and PROC. +// +// Ren/C's schematic for the FUNC and PROC generators is *very* different +// from R3-Alpha, whose definition of FUNC was simply: +// +// make function! copy/deep reduce [spec body] +// +// Ren/C's `make function!` doesn't need to copy the spec (it does not save +// it--parameter descriptions are in a meta object). It also copies the body +// by virtue of the need to relativize it. They also have "definitional +// return" constructs so that the body introduces RETURN and LEAVE constructs +// specific to each function invocation, so the body acts more like: +// +// return: make function! [ +// [{Returns a value from a function.} value [ any-value!]] +// [exit/from/with (context-of 'return) :value] +// ] +// (body goes here) +// +// This pattern addresses "Definitional Return" in a way that does not +// technically require building RETURN or LEAVE in as a language keyword in +// any specific form (in the sense that MAKE FUNCTION! does not itself +// require it, and one can pretend FUNC and PROC don't exist). +// +// FUNC and PROC optimize by not internally building or executing the +// equivalent body, but giving it back from BODY-OF. This is another benefit +// of making a copy--since the user cannot access the new root, it makes it +// possible to "lie" about what the body "above" is. This gives FUNC and PROC +// the edge to pretend to add containing code and simulate its effects, while +// really only holding onto the body the caller provided. +// +// While MAKE FUNCTION! has no RETURN, all functions still have EXIT as a +// non-definitional alternative. Ren/C adds a /WITH refinement so it can +// behave equivalently to old-non-definitonal return. There is even a way to +// identify specific points up the call stack to exit from via EXIT/FROM, so +// not having definitional return has several alternate options for generators +// that wish to use them. +// +REBFUN *Make_Interpreted_Function_May_Fail( + const REBVAL *spec, + const REBVAL *code, + REBFLGS mkf_flags // MKF_RETURN, MKF_LEAVE, etc. +) { + assert(IS_BLOCK(spec)); + assert(IS_BLOCK(code)); + + REBFUN *fun = Make_Function( + Make_Paramlist_Managed_May_Fail(spec, mkf_flags), + &Noop_Dispatcher, // will be overwritten if non-NULL body + NULL, // no underlying function, this is fundamental + NULL // not providing a specialization + ); + + // We look at the *actual* function flags; e.g. the person may have used + // the FUNC generator (with MKF_RETURN) but then named a parameter RETURN + // which overrides it, so the value won't have FUNC_FLAG_RETURN. + // + REBVAL *value = FUNC_VALUE(fun); + + REBARR *body_array; + if (VAL_ARRAY_LEN_AT(code) == 0) { + if (GET_VAL_FLAG(value, FUNC_FLAG_RETURN)) { + // + // Since we're bypassing type checking in the dispatcher for + // speed, we need to make sure that the return type allows void + // (which is all the Noop dispatcher will return). If not, we + // don't want to fail here (it would reveal the optimization)... + // just fall back on the Returner_Dispatcher instead. + // + REBVAL *typeset = FUNC_PARAM(fun, FUNC_NUM_PARAMS(fun)); + assert(VAL_PARAM_SYM(typeset) == SYM_RETURN); + if (!TYPE_CHECK(typeset, REB_MAX_VOID)) + FUNC_DISPATCHER(fun) = &Returner_Dispatcher; + } + + body_array = EMPTY_ARRAY; // just reuse empty array if empty, no copy + } + else { + // Body is not empty, so we need to pick the right dispatcher based + // on how the output value is to be handled. + // + if (GET_VAL_FLAG(value, FUNC_FLAG_RETURN)) + FUNC_DISPATCHER(fun) = &Returner_Dispatcher; // type checks f->out + else if (GET_VAL_FLAG(value, FUNC_FLAG_LEAVE)) + FUNC_DISPATCHER(fun) = &Voider_Dispatcher; // forces f->out void + else + FUNC_DISPATCHER(fun) = &Unchecked_Dispatcher; // leaves f->out + + // We need to copy the body in order to relativize its references to + // args and locals to refer to the parameter list. Future work + // might be able to "image" the bindings virtually, and not require + // this to be copied if the input code is read-only. + // + body_array = Copy_And_Bind_Relative_Deep_Managed( + code, + FUNC_PARAMLIST(fun), + TS_ANY_WORD + ); + } + + // We need to do a raw initialization of this block RELVAL because it is + // relative to a function. (Init_Block assumes all specific values) + // + RELVAL *body = FUNC_BODY(fun); + VAL_RESET_HEADER_EXTRA(body, REB_BLOCK, VALUE_FLAG_RELATIVE); + INIT_VAL_ARRAY(body, body_array); + VAL_INDEX(body) = 0; + INIT_RELATIVE(body, fun); + +#if !defined(NDEBUG) + // + // If FUNC or MAKE FUNCTION! are being invoked from an array of code that + // has been flagged "legacy" (e.g. the body of a function created after + // `do ` has been run) then mark the function with the setting + // to make refinements and args blank instead of FALSE/void...if that + // option is on. + // + if ( + LEGACY_RUNNING(OPTIONS_REFINEMENTS_BLANK) + || GET_SER_INFO(VAL_ARRAY(spec), SERIES_INFO_LEGACY_DEBUG) + || GET_SER_INFO(VAL_ARRAY(code), SERIES_INFO_LEGACY_DEBUG) + ) { + SET_VAL_FLAG(FUNC_VALUE(fun), FUNC_FLAG_LEGACY_DEBUG); + } +#endif + + // All the series inside of a function body are "relatively bound". This + // means that there's only one copy of the body, but the series handle + // is "viewed" differently based on which call it represents. Though + // each of these views compares uniquely, there's only one series behind + // it...hence the series must be read only to keep modifying a view + // that seems to have one identity but then affecting another. + // +#if defined(NDEBUG) + Deep_Freeze_Array(VAL_ARRAY(body)); +#else + if (!LEGACY(OPTIONS_UNLOCKED_SOURCE)) + Deep_Freeze_Array(VAL_ARRAY(body)); +#endif + + return fun; } -/*********************************************************************** -** -*/ void Make_Native(REBVAL *value, REBSER *spec, REBFUN func, REBINT type) -/* -***********************************************************************/ +// +// Make_Frame_For_Function: C +// +// This creates a *non-stack-allocated* FRAME!, which can be used in function +// applications or specializations. It reuses the keylist of the function +// but makes a new varlist. +// +REBCTX *Make_Frame_For_Function(const REBVAL *value) { + // + // Note that this cannot take just a REBFUN* directly, because definitional + // RETURN and LEAVE only have their unique `binding` bits in the REBVAL. + // + REBFUN *func = VAL_FUNC(value); + + // In order to have the frame survive the call to MAKE and be + // returned to the user it can't be stack allocated, because it + // would immediately become useless. Allocate dynamically. + // + REBARR *varlist = Make_Array_Core( + ARR_LEN(FUNC_PARAMLIST(func)), + ARRAY_FLAG_VARLIST | SERIES_FLAG_FIXED_SIZE + ); + + // Fill in the rootvar information for the context canon REBVAL + // + REBVAL *var = SINK(ARR_HEAD(varlist)); + VAL_RESET_HEADER(var, REB_FRAME); + var->payload.any_context.varlist = varlist; + var->extra.binding = value->extra.binding; + var->payload.any_context.phase = func; + + // We can reuse the paramlist we're given, but note in the case of + // definitional RETURN and LEAVE we have to stow the `binding` field + // in the context, since the single archetype paramlist does not hold + // enough information to know where to return *to*. + // + INIT_CTX_KEYLIST_SHARED(CTX(varlist), FUNC_PARAMLIST(func)); + ASSERT_ARRAY_MANAGED(CTX_KEYLIST(CTX(varlist))); + + // The current implementation allows that `do frame` of the result + // of a `make frame! :fun` will not make a copy of the frame, but use + // its values. See notes in DO of FRAME! regarding this. + // + SER(varlist)->misc.f = NULL; + + ++var; + + // A FRAME! defaults all args and locals to not being set. If the frame + // is then used as the storage for a function specialization, unset + // vars indicate *unspecialized* arguments...not ones. (This is + // a good argument for not making have meaning that is interesting + // to APPLY or SPECIALIZE cases, but to revoke the function's effects. + // + REBCNT n; + for (n = 1; n <= FUNC_NUM_PARAMS(func); ++n, ++var) + Init_Void(var); + + TERM_ARRAY_LEN(varlist, ARR_LEN(FUNC_PARAMLIST(func))); + + return CTX(varlist); +} + + +// +// Specialize_Function_Throws: C +// +// This produces a new REBVAL for a function that specializes another. It +// uses a FRAME! to do this, where the frame intrinsically stores the +// reference to the function it is specializing. +// +REBOOL Specialize_Function_Throws( + REBVAL *out, + REBVAL *specializee, + REBSTR *opt_specializee_name, + REBVAL *block // !!! REVIEW: gets binding modified directly (not copied) +) { + assert(out != specializee); + + REBCTX *exemplar = FUNC_EXEMPLAR(VAL_FUNC(specializee)); + if (exemplar == NULL) { + // + // An initial specialization is responsible for making a frame out + // of the function's paramlist. Frame vars default void. + // + REBFUN *underlying = FUNC_UNDERLYING(VAL_FUNC(specializee)); + exemplar = Make_Frame_For_Function(FUNC_VALUE(underlying)); + MANAGE_ARRAY(CTX_VARLIST(exemplar)); + } + else { + // Specializing a specialization is ultimately just a specialization + // of the innermost function being specialized. (Imagine specializing + // a specialization of APPEND, to the point where it no longer takes + // any parameters. Nevertheless, the frame being stored and invoked + // needs to have as many parameters as APPEND has. The frame must be + // be built for the code ultimately being called--and specializations + // have no code of their own.) + + REBARR *varlist = Copy_Array_Deep_Managed( + CTX_VARLIST(exemplar), SPECIFIED + ); + SET_SER_FLAG(varlist, ARRAY_FLAG_VARLIST); + INIT_CTX_KEYLIST_SHARED(CTX(varlist), CTX_KEYLIST(exemplar)); + + exemplar = CTX(varlist); // okay, now make exemplar our copy + CTX_VALUE(exemplar)->payload.any_context.varlist = varlist; + } + + // Archetypal frame values can't have exit bindings (would write paramlist) + // + assert(VAL_BINDING(CTX_VALUE(exemplar)) == NULL); + + // Bind all the SET-WORD! in the body that match params in the frame + // into the frame. This means `value: value` can very likely have + // `value:` bound for assignments into the frame while `value` refers + // to whatever value was in the context the specialization is running + // in, but this is likely the more useful behavior. Review. + // + // !!! This binds the actual arg data, not a copy of it--following + // OBJECT!'s lead. However, ordinary functions make a copy of the body + // they are passed before rebinding. Rethink. + // + Bind_Values_Core( + VAL_ARRAY_AT(block), + exemplar, + FLAGIT_KIND(REB_SET_WORD), // types to bind (just set-word!) + 0, // types to "add midstream" to binding as we go (nothing) + BIND_DEEP + ); + + // Do the block into scratch space--we ignore the result (unless it is + // thrown, in which case it must be returned.) + { + PUSH_GUARD_ARRAY(CTX_VARLIST(exemplar)); + + if (Do_Any_Array_At_Throws(out, block)) { + DROP_GUARD_ARRAY(CTX_VARLIST(exemplar)); + return TRUE; + } + + DROP_GUARD_ARRAY(CTX_VARLIST(exemplar)); + } + + // Generate paramlist by way of the data stack. Push inherited value (to + // become the function value afterward), then all the args that remain + // unspecialized (indicated by being void... is not supported) + // + REBDSP dsp_orig = DSP; + DS_PUSH(FUNC_VALUE(VAL_FUNC(specializee))); // !!! is inheriting good? + + REBVAL *param = CTX_KEYS_HEAD(exemplar); + REBVAL *arg = CTX_VARS_HEAD(exemplar); + for (; NOT_END(param); ++param, ++arg) { + if (IS_VOID(arg)) + DS_PUSH(param); + } + + REBARR *paramlist = Pop_Stack_Values_Core( + dsp_orig, + ARRAY_FLAG_PARAMLIST | SERIES_FLAG_FIXED_SIZE + ); + MANAGE_ARRAY(paramlist); + + RELVAL *rootparam = ARR_HEAD(paramlist); + rootparam->payload.function.paramlist = paramlist; + + // See %sysobj.r for `specialized-meta:` object template + + REBVAL *example = Get_System(SYS_STANDARD, STD_SPECIALIZED_META); + + REBCTX *meta = Copy_Context_Shallow(VAL_CONTEXT(example)); + + Init_Void(CTX_VAR(meta, STD_SPECIALIZED_META_DESCRIPTION)); // default + Move_Value( + CTX_VAR(meta, STD_SPECIALIZED_META_SPECIALIZEE), + specializee + ); + if (opt_specializee_name == NULL) + Init_Void(CTX_VAR(meta, STD_SPECIALIZED_META_SPECIALIZEE_NAME)); + else + Init_Word( + CTX_VAR(meta, STD_SPECIALIZED_META_SPECIALIZEE_NAME), + opt_specializee_name + ); + + MANAGE_ARRAY(CTX_VARLIST(meta)); + SER(paramlist)->link.meta = meta; + + REBFUN *fun = Make_Function( + paramlist, + &Specializer_Dispatcher, + VAL_FUNC(specializee), // cache underlying function's facade + exemplar // also provide a context of specialization values + ); + + // !!! The full story behind how FRAME!s remember the function they were + // made for is not completely told yet, but it comes from the "phase" + // field. System-wide this needs to be honored--all FRAME!s must have + // one, eventually. In order to make sure specialization dispatches the + // function you specialized and not the one tied to its underlying frame, + // we just patch this one in here. + // + CTX_VALUE(exemplar)->payload.any_context.phase = VAL_FUNC(specializee); + + // The "body" is the FRAME! value of the specialization. Though we may + // not be able to touch the keylist of that frame to update the "archetype" + // binding, we can patch this cell in the "body array" to hold it. + // + Move_Value(FUNC_BODY(fun), CTX_VALUE(exemplar)); + assert(VAL_BINDING(FUNC_BODY(fun)) == VAL_BINDING(specializee)); + + Move_Value(out, FUNC_VALUE(fun)); + assert(VAL_BINDING(out) == NULL); + + return FALSE; +} + + +// +// Clonify_Function: C +// +// (A "Clonify" interface takes in a raw duplicate value that one wishes to +// mutate in-place into a full-fledged copy of the value it is a clone of. +// This interface can be more efficient than a "source in, dest out" copy... +// and clarifies the dangers when the source and destination are the same.) +// +// !!! Function bodies in R3-Alpha were mutable. This meant that you could +// effectively have static data in cases like: +// +// foo: does [static: [] | append static 1] +// +// Hence, it was meaningful to be able to COPY a function; because that copy +// would get any such static state snapshotted at wherever it was in time. +// +// Ren-C eliminated this idea. But functions are still copied in the special +// case of object "member functions", so that each "derived" object will +// have functions with bindings to its specific context variables. Some +// plans are in the work to use function REBVAL's `binding` parameter to +// make a lighter-weight way of connecting methods to objects without actually +// needing to mutate the archetypal REBFUN to do so ("virtual binding"). +// +void Clonify_Function(REBVAL *value) { - //Print("Make_Native: %s spec %d", Get_Sym_Name(type+1), SERIES_TAIL(spec)); - VAL_FUNC_SPEC(value) = spec; - VAL_FUNC_ARGS(value) = Check_Func_Spec(spec); - VAL_FUNC_CODE(value) = func; - VAL_SET(value, type); + assert(IS_FUNCTION(value)); + + // Function compositions point downwards through their layers in a linked + // list. Each step in the chain has identity, and we need a copied + // identity for all steps that require a copy and everything *above* it. + // So for instance, although R3-Alpha did not see a need to copy natives, + // if you ADAPT a native with code, the adapting Rebol code may need to + // take into account new bindings to a derived object...just as the body + // to an interpreted function would. + // + // !!! For the moment, this work is not done...and only functions that + // are raw interpreted functions are cloned. That means old code will + // stay compatible but new features won't necessarily work the same way + // with object binding. All of this needs to be rethought in light of + // "virtual binding" anyway! + // + if (!IS_FUNCTION_INTERPRETED(value)) + return; + + REBFUN *original_fun = VAL_FUNC(value); + REBARR *paramlist = Copy_Array_Shallow( + FUNC_PARAMLIST(original_fun), + SPECIFIED + ); + SET_SER_FLAG(paramlist, ARRAY_FLAG_PARAMLIST); + MANAGE_ARRAY(paramlist); + ARR_HEAD(paramlist)->payload.function.paramlist = paramlist; + + // !!! Meta: copy, inherit? + // + SER(paramlist)->link.meta = FUNC_META(original_fun); + + REBFUN *new_fun = Make_Function( + paramlist, + FUNC_DISPATCHER(original_fun), + NULL, // no underlying function, this is fundamental + NULL // not providing a specialization + ); + + RELVAL *body = FUNC_BODY(new_fun); + + // Since we rebind the body, we need to instruct the interpreted dispatcher + // that it's o.k. to tell the frame lookup that it can find variables + // under the "new paramlist". + // + VAL_RESET_HEADER_EXTRA(body, REB_BLOCK, VALUE_FLAG_RELATIVE); + INIT_VAL_ARRAY( + body, + Copy_Rerelativized_Array_Deep_Managed( + VAL_ARRAY(FUNC_BODY(original_fun)), + original_fun, + AS_FUNC(paramlist) + ) + ); + VAL_INDEX(body) = 0; + + // Remap references in the body from the original function to new + + INIT_RELATIVE(body, AS_FUNC(paramlist)); + + Move_Value(value, FUNC_VALUE(new_fun)); } -/*********************************************************************** -** -*/ REBFLG Make_Function(REBCNT type, REBVAL *value, REBVAL *def) -/* -***********************************************************************/ +// +// REBTYPE: C +// +// This handler is used to fail for a type which cannot handle actions. +// +// !!! Currently all types have a REBTYPE() handler for either themselves or +// their class. But having a handler that could be "swapped in" from a +// default failing case is an idea that could be used as an interim step +// to allow something like REB_GOB to fail by default, but have the failing +// type handler swapped out by an extension. +// +REBTYPE(Fail) { - REBVAL *spec; - REBVAL *body; - REBCNT len; + UNUSED(frame_); + UNUSED(action); - if ( - !IS_BLOCK(def) - || (len = VAL_LEN(def)) < 2 - || !IS_BLOCK(spec = VAL_BLK(def)) - ) return FALSE; + fail ("Datatype does not have a dispatcher registered."); +} - body = VAL_BLK_SKIP(def, 1); - VAL_FUNC_SPEC(value) = VAL_SERIES(spec); - VAL_FUNC_ARGS(value) = Check_Func_Spec(VAL_SERIES(spec)); +// +// Action_Dispatcher: C +// +// "actions" are historically a kind of dispatch based on the first argument's +// type, and then calling a common function for that type parameterized with +// a word for the action. e.g. APPEND X [...] would look at the type of X, +// and call a function based on that parameterized with APPEND and the list +// of arguments. +// +REB_R Action_Dispatcher(REBFRM *f) +{ + enum Reb_Kind type = VAL_TYPE(FRM_ARG(f, 1)); + assert(type < REB_MAX); // actions should not allow void first arguments + REBSYM sym = STR_SYMBOL(VAL_WORD_SPELLING(FUNC_BODY(f->phase))); + assert(sym != SYM_0); - if (type != REB_COMMAND) { - if (len != 2 || !IS_BLOCK(body)) return FALSE; - VAL_FUNC_BODY(value) = VAL_SERIES(body); - } - else - Make_Command(value, def); + REBACT subdispatch = Value_Dispatch[type]; + return subdispatch(f, sym); +} - VAL_SET(value, type); - if (type == REB_FUNCTION || type == REB_CLOSURE) - Bind_Relative(VAL_FUNC_ARGS(value), VAL_FUNC_ARGS(value), VAL_FUNC_BODY(value)); +// +// Noop_Dispatcher: C +// +// If a function's body is an empty block, rather than bother running the +// equivalent of `DO []` and generating a frame for specific binding, this +// just returns void. What makes this a semi-interesting optimization is +// for functions like ASSERT whose default implementation is an empty block, +// but intended to be hijacked in "debug mode" with an implementation. So +// you can minimize the cost of instrumentation hooks. +// +REB_R Noop_Dispatcher(REBFRM *f) +{ + UNUSED(f); + return R_VOID; +} - return TRUE; + +// +// Datatype_Checker_Dispatcher: C +// +// Dispatcher used by TYPECHECKER generator for when argument is a datatype. +// +REB_R Datatype_Checker_Dispatcher(REBFRM *f) +{ + RELVAL *datatype = FUNC_BODY(f->phase); + assert(IS_DATATYPE(datatype)); + if (VAL_TYPE(FRM_ARG(f, 1)) == VAL_TYPE_KIND(datatype)) + return R_TRUE; + return R_FALSE; } -/*********************************************************************** -** -*/ REBFLG Copy_Function(REBVAL *value, REBVAL *args) -/* -***********************************************************************/ +// +// Typeset_Checker_Dispatcher: C +// +// Dispatcher used by TYPECHECKER generator for when argument is a typeset. +// +REB_R Typeset_Checker_Dispatcher(REBFRM *f) { - REBVAL *spec; - REBVAL *body; - - if (!args || ((spec = VAL_BLK(args)) && IS_END(spec))) { - body = 0; - if (IS_FUNCTION(value) || IS_CLOSURE(value)) - VAL_FUNC_ARGS(value) = Copy_Block(VAL_FUNC_ARGS(value), 0); - } else { - body = VAL_BLK_SKIP(args, 1); - // Spec given, must be block or * - if (IS_BLOCK(spec)) { - VAL_FUNC_SPEC(value) = VAL_SERIES(spec); - VAL_FUNC_ARGS(value) = Check_Func_Spec(VAL_SERIES(spec)); - } else { - if (!IS_STAR(spec)) return FALSE; - VAL_FUNC_ARGS(value) = Copy_Block(VAL_FUNC_ARGS(value), 0); - } - } - - if (body && !IS_END(body)) { - if (!IS_FUNCTION(value) && !IS_CLOSURE(value)) return FALSE; - // Body must be block: - if (!IS_BLOCK(body)) return FALSE; - VAL_FUNC_BODY(value) = VAL_SERIES(body); - } - // No body, use prototype: - else if (IS_FUNCTION(value) || IS_CLOSURE(value)) - VAL_FUNC_BODY(value) = Clone_Block(VAL_FUNC_BODY(value)); - - // Rebind function words: - if (IS_FUNCTION(value) || IS_CLOSURE(value)) - Bind_Relative(VAL_FUNC_ARGS(value), VAL_FUNC_ARGS(value), VAL_FUNC_BODY(value)); - - return TRUE; + RELVAL *typeset = FUNC_BODY(f->phase); + assert(IS_TYPESET(typeset)); + if (TYPE_CHECK(typeset, VAL_TYPE(FRM_ARG(f, 1)))) + return R_TRUE; + return R_FALSE; } -/*********************************************************************** -** -*/ void Clone_Function(REBVAL *value, REBVAL *func) -/* -***********************************************************************/ +// +// Unchecked_Dispatcher: C +// +// This is the default MAKE FUNCTION! dispatcher for interpreted functions +// (whose body is a block that runs through DO []). There is no return type +// checking done on these simple functions. +// +REB_R Unchecked_Dispatcher(REBFRM *f) { - REBSER *src_frame = VAL_FUNC_ARGS(func); - - VAL_FUNC_SPEC(value) = VAL_FUNC_SPEC(func); - VAL_FUNC_BODY(value) = Clone_Block(VAL_FUNC_BODY(func)); - VAL_FUNC_ARGS(value) = Copy_Block(src_frame, 0); - // VAL_FUNC_BODY(value) = Clone_Block(VAL_FUNC_BODY(func)); - VAL_FUNC_BODY(value) = Copy_Block_Values(VAL_FUNC_BODY(func), 0, SERIES_TAIL(VAL_FUNC_BODY(func)), TS_CLONE); - Rebind_Block(src_frame, VAL_FUNC_ARGS(value), BLK_HEAD(VAL_FUNC_BODY(value)), 0); + RELVAL *body = FUNC_BODY(f->phase); + assert(IS_BLOCK(body) && IS_RELATIVE(body) && VAL_INDEX(body) == 0); + + if (Do_At_Throws( + f->out, + VAL_ARRAY(body), + VAL_INDEX(body), + AS_SPECIFIER(Context_For_Frame_May_Reify_Managed(f)) + )){ + return R_OUT_IS_THROWN; + } + + return R_OUT; } -/*********************************************************************** -** -*/ void Do_Native(REBVAL *func) -/* -***********************************************************************/ +// +// Voider_Dispatcher: C +// +// Variant of Unchecked_Dispatcher, except sets the output value to void. +// Pushing that code into the dispatcher means there's no need to do flag +// testing in the main loop. +// +REB_R Voider_Dispatcher(REBFRM *f) { - REBVAL *ds; - REBINT n; -#ifdef DEBUGGING - REBYTE *fname = Get_Word_Name(DSF_WORD(DSF)); // for DEBUG - Debug_Str(fname); -#endif + RELVAL *body = FUNC_BODY(f->phase); + assert(IS_BLOCK(body) && IS_RELATIVE(body) && VAL_INDEX(body) == 0); + + if (Do_At_Throws( + f->out, + VAL_ARRAY(body), + VAL_INDEX(body), + AS_SPECIFIER(Context_For_Frame_May_Reify_Managed(f)) + )){ + return R_OUT_IS_THROWN; + } + + return R_VOID; +} - Eval_Natives++; - - if (NZ(n = VAL_FUNC_CODE(func)(DS_RETURN))) { - ds = DS_RETURN; - switch (n) { - case R_RET: // for compiler opt - break; - case R_TOS: - *ds = *DS_TOP; - break; - case R_TOS1: - *ds = *DS_NEXT; - break; - case R_NONE: - SET_NONE(ds); - break; - case R_UNSET: - SET_UNSET(ds); - break; - case R_TRUE: - SET_TRUE(ds); - break; - case R_FALSE: - SET_FALSE(ds); - break; - case R_ARG1: - *ds = *D_ARG(1); - break; - case R_ARG2: - *ds = *D_ARG(2); - break; - case R_ARG3: - *ds = *D_ARG(3); - break; - } - } + +// +// Returner_Dispatcher: C +// +// Contrasts with the Unchecked_Dispatcher since it ensures the return type is +// correct. (Note that natives do not get this type checking, and they +// probably shouldn't pay for it except in the debug build.) +// +REB_R Returner_Dispatcher(REBFRM *f) +{ + RELVAL *body = FUNC_BODY(f->phase); + assert(IS_BLOCK(body) && IS_RELATIVE(body) && VAL_INDEX(body) == 0); + + if (Do_At_Throws( + f->out, + VAL_ARRAY(body), + VAL_INDEX(body), + AS_SPECIFIER(Context_For_Frame_May_Reify_Managed(f)) + )){ + return R_OUT_IS_THROWN; + } + + REBVAL *typeset = FUNC_PARAM(f->phase, FUNC_NUM_PARAMS(f->phase)); + assert(VAL_PARAM_SYM(typeset) == SYM_RETURN); + + // The type bits of the definitional return are not applicable + // to the `return` word being associated with a FUNCTION! + // vs. an INTEGER! (for instance). It is where the type + // information for the non-existent return function specific + // to this call is hidden. + // + if (!TYPE_CHECK(typeset, VAL_TYPE(f->out))) + fail (Error_Bad_Return_Type(f->label, VAL_TYPE(f->out))); + + return R_OUT; } -/*********************************************************************** -** -*/ void Do_Act(REBVAL *ds, REBCNT type, REBCNT act) -/* -***********************************************************************/ +// +// Specializer_Dispatcher: C +// +// The evaluator does not do any special "running" of a specialized frame. +// All of the contribution that the specialization has to make was taken care +// of at the time of generating the arguments to the underlying function. +// +// Though an attempt is made to use the work of "digging" past specialized +// frames, some exist deep as chains of specializations etc. These have +// to just be peeled off when the chain runs. +// +REB_R Specializer_Dispatcher(REBFRM *f) { - REBACT action; - REBINT ret; - - action = Value_Dispatch[type]; - //ASSERT2(action != 0, RP_NO_ACTION); - if (!action) Trap_Action(type, act); - ret = action(ds, act); - if (ret > 0) { - ds = DS_RETURN; - switch (ret) { - case R_RET: // for compiler opt - break; - case R_TOS: - *ds = *DS_TOP; - break; - case R_TOS1: - *ds = *DS_NEXT; - break; - case R_NONE: - SET_NONE(ds); - break; - case R_UNSET: - SET_UNSET(ds); - break; - case R_TRUE: - SET_TRUE(ds); - break; - case R_FALSE: - SET_FALSE(ds); - break; - case R_ARG1: - *ds = *D_ARG(1); - break; - case R_ARG2: - *ds = *D_ARG(2); - break; - case R_ARG3: - *ds = *D_ARG(3); - break; - } - } + REBVAL *exemplar = KNOWN(FUNC_BODY(f->phase)); + f->phase = exemplar->payload.any_context.phase; + f->binding = VAL_BINDING(exemplar); + + return R_REDO_UNCHECKED; } -/*********************************************************************** -** -*/ void Do_Action(REBVAL *func) -/* -***********************************************************************/ +// +// Hijacker_Dispatcher: C +// +// A hijacker takes over another function's identity, replacing it with its +// own implementation, injecting directly into the paramlist and body_holder +// nodes held onto by all the victim's references. +// +// Sometimes the hijacking function has the same underlying function +// as the victim, in which case there's no need to insert a new dispatcher. +// The hijacker just takes over the identity. But otherwise it cannot, +// and a "shim" is needed...since something like an ADAPT or SPECIALIZE +// or a MAKE FRAME! might depend on the existing paramlist shape. +// +REB_R Hijacker_Dispatcher(REBFRM *f) { - REBVAL *ds = DS_RETURN; - REBCNT type = VAL_TYPE(D_ARG(1)); + RELVAL *hijacker = FUNC_BODY(f->phase); - Eval_Natives++; + // We need to build a new frame compatible with the hijacker, and + // transform the parameters we've gathered to be compatible with it. + // + if (Redo_Func_Throws(f, VAL_FUNC(hijacker))) + return R_OUT_IS_THROWN; - ASSERT1(type < REB_MAX, RP_BAD_TYPE_ACTION); + return R_OUT; +} - // Handle special datatype test cases (eg. integer?) - if (VAL_FUNC_ACT(func) == 0) { - VAL_SET(D_RET, REB_LOGIC); - VAL_LOGIC(D_RET) = (type == VAL_INT64(BLK_LAST(VAL_FUNC_SPEC(func)))); - return; - } - Do_Act(D_RET, type, VAL_FUNC_ACT(func)); +// +// Adapter_Dispatcher: C +// +// Dispatcher used by ADAPT. +// +REB_R Adapter_Dispatcher(REBFRM *f) +{ + RELVAL *adaptation = FUNC_BODY(f->phase); + assert(ARR_LEN(VAL_ARRAY(adaptation)) == 2); + + RELVAL* prelude = VAL_ARRAY_AT_HEAD(adaptation, 0); + REBVAL* adaptee = KNOWN(VAL_ARRAY_AT_HEAD(adaptation, 1)); + + // The first thing to do is run the prelude code, which may throw. If it + // does throw--including a RETURN--that means the adapted function will + // not be run. + // + // (Note that when the adapter was created, the prelude code was bound to + // the paramlist of the *underlying* function--because that's what a + // compatible frame gets pushed for.) + // + if (Do_At_Throws( + f->out, + VAL_ARRAY(prelude), + VAL_INDEX(prelude), + AS_SPECIFIER(Context_For_Frame_May_Reify_Managed(f)) + )){ + return R_OUT_IS_THROWN; + } + + f->phase = VAL_FUNC(adaptee); + f->binding = VAL_BINDING(adaptee); + return R_REDO_CHECKED; // Have Do_Core run the adaptee updated into f->phase } -/*********************************************************************** -** -*/ void Do_Function(REBVAL *func) -/* -***********************************************************************/ +// +// Chainer_Dispatcher: C +// +// Dispatcher used by CHAIN. +// +REB_R Chainer_Dispatcher(REBFRM *f) { - REBVAL *result; - REBVAL *ds; -#ifdef DEBUGGING - REBYTE *name = Get_Word_Name(DSF_WORD(DSF)); -#endif + REBVAL *pipeline = KNOWN(FUNC_BODY(f->phase)); // array of functions + + // Before skipping off to find the underlying non-chained function + // to kick off the execution, the post-processing pipeline has to + // be "pushed" so it is not forgotten. Go in reverse order so + // the function to apply last is at the bottom of the stack. + // + REBVAL *value = KNOWN(ARR_LAST(VAL_ARRAY(pipeline))); + while (value != VAL_ARRAY_HEAD(pipeline)) { + assert(IS_FUNCTION(value)); + DS_PUSH(KNOWN(value)); + --value; + } + + // Extract the first function, itself which might be a chain. + // + f->phase = VAL_FUNC(value); + f->binding = VAL_BINDING(value); + + return R_REDO_UNCHECKED; // signatures should match +} + - Eval_Functions++; - - //Dump_Block(VAL_FUNC_BODY(func)); - result = Do_Blk(VAL_FUNC_BODY(func), 0); - ds = DS_RETURN; - - if (IS_ERROR(result) && IS_RETURN(result)) { - // Value below is kept safe from GC because no-allocation is - // done between point of SET_THROW and here. - if (VAL_ERR_VALUE(result)) - *ds = *VAL_ERR_VALUE(result); - else - SET_UNSET(ds); - } - else *ds = *result; // Set return value (atomic) +// +// Get_If_Word_Or_Path_Arg: C +// +// Some routines like APPLY and SPECIALIZE are willing to take a WORD! or +// PATH! instead of just the value type they are looking for, and perform +// the GET for you. By doing the GET inside the function, they are able +// to preserve the symbol: +// +// >> apply 'append [value: 'c] +// ** Script error: append is missing its series argument +// +void Get_If_Word_Or_Path_Arg( + REBVAL *out, + REBSTR **opt_name_out, + const REBVAL *value +) { + DECLARE_LOCAL (adjusted); + Move_Value(adjusted, value); + + if (ANY_WORD(value)) { + *opt_name_out = VAL_WORD_SPELLING(value); + VAL_SET_TYPE_BITS(adjusted, REB_GET_WORD); + } + else if (ANY_PATH(value)) { + // + // In theory we could get a symbol here, assuming we only do non + // evaluated GETs. Not implemented at the moment. + // + *opt_name_out = NULL; + VAL_SET_TYPE_BITS(adjusted, REB_GET_PATH); + } + else { + *opt_name_out = NULL; + Move_Value(out, value); + return; + } + + if (Eval_Value_Throws(out, adjusted)) { + // + // !!! GET_PATH should not evaluate GROUP!, and hence shouldn't be + // able to throw. TBD. + // + fail (Error_No_Catch_For_Throw(out)); + } } -/*********************************************************************** -** -*/ void Do_Closure(REBVAL *func) -/* -** Do a closure by cloning its body and rebinding it to -** a new frame of words/values. -** -***********************************************************************/ +// +// Apply_Frame_Core: C +// +// Work in progress to factor out common code used by DO and APPLY. Needs +// to be streamlined. +// +// Expects the following Reb_Frame fields to be preloaded: +// +// f->out (just valid pointer, pointed-to value can be garbage) +// f->phase +// f->binding +// +// If opt_def is NULL, then f->varlist.context must be set +// +// !!! Because APPLY is being written as a regular native (and not a +// special exception case inside of Do_Core) it has to "re-enter" Do_Core +// and jump to the argument processing. This is the first example of +// such a re-entry, and is not particularly streamlined yet. +// +// This could also be accomplished if function dispatch were a subroutine +// that would be called both here and from the evaluator loop. But if +// the subroutine were parameterized with the frame state, it would be +// basically equivalent to a re-entry. And re-entry is interesting to +// experiment with for other reasons (e.g. continuations), so that is what +// is used here. +// +REB_R Apply_Frame_Core(REBFRM *f, REBSTR *label, REBVAL *opt_def) { - REBSER *body; - REBSER *frame; - REBVAL *result; - REBVAL *ds; - - Eval_Functions++; - //DISABLE_GC; - - // Clone the body of the function to allow rebinding to it: - body = Clone_Block(VAL_FUNC_BODY(func)); - - // Copy stack frame args as the closure object (one extra at head) - frame = Copy_Values(BLK_SKIP(DS_Series, DS_ARG_BASE), SERIES_TAIL(VAL_FUNC_ARGS(func))); - SET_FRAME(BLK_HEAD(frame), 0, VAL_FUNC_ARGS(func)); - - // Rebind the body to the new context (deeply): - Rebind_Block(VAL_FUNC_ARGS(func), frame, BLK_HEAD(body), REBIND_TYPE); - - ds = DS_RETURN; - SET_OBJECT(ds, body); // keep it GC safe - result = Do_Blk(body, 0); // GC-OK - also, result returned on DS stack - ds = DS_RETURN; - - if (IS_ERROR(result) && IS_RETURN(result)) { - // Value below is kept safe from GC because no-allocation is - // done between point of SET_THROW and here. - if (VAL_ERR_VALUE(result)) - *ds = *VAL_ERR_VALUE(result); - else - SET_UNSET(ds); - } - else *ds = *result; // Set return value (atomic) + assert(IS_FUNCTION(f->gotten)); + + f->eval_type = REB_FUNCTION; + SET_FRAME_LABEL(f, label); + + // We pretend our "input source" has ended. + // + SET_FRAME_VALUE(f, END); + f->index = 0; + f->source.array = EMPTY_ARRAY; + f->specifier = SPECIFIED; + f->pending = NULL; + + f->dsp_orig = DSP; + + Init_Endlike_Header(&f->flags, DO_FLAG_APPLYING); + + // !!! We have to push a call here currently because prior to specific + // binding, the stack gets walked to resolve variables. Hence in the + // apply case, Do_Core doesn't do its own push to the frame stack. + // + Push_Frame_Core(f); + +#if !defined(NDEBUG) + // + // We may push a data chunk, which is one of the things the snapshot state + // checks. It also checks the top of stack, so that has to be set as well. + // So this has to come before Push_Or_Alloc_Vars + // + SNAP_STATE(&f->state_debug); +#endif + + f->refine = m_cast(REBVAL*, END); + + if (opt_def) + Push_Or_Alloc_Args_For_Underlying_Func(f, f->gotten); + else { + ASSERT_CONTEXT(CTX(f->varlist)); // underlying must be set + + f->args_head = CTX_VARS_HEAD(CTX(f->varlist)); + + REBCTX *exemplar = FUNC_EXEMPLAR(f->phase); + if (exemplar) + f->special = CTX_VARS_HEAD(exemplar); + else + f->special = m_cast(REBVAL*, END); // literal pointer tested + + SET_END(&f->cell); // needed for GC safety + } + + // Ordinary function dispatch does not pre-fill the arguments; they + // are left as garbage until the parameter enumeration gets to them. + // (The GC can see f->param to know how far the enumeration has + // gotten, and avoid tripping on the garbage.) This helps avoid + // double-walking and double-writing. + // + // However, the user code being run by the APPLY can't get garbage + // if it looks at variables in the frame. Also, it's necessary to + // know if the user writes them or not...so making them "write-only" + // isn't an option either. One has to + // + f->param = FUNC_FACADE_HEAD(f->phase); + f->arg = f->args_head; + while (NOT_END(f->param)) { + if (f->special != END && !IS_VOID(f->special)) { + // + // !!! Specialized arguments *should* be invisible to the + // binding process of the apply. They have been set and should + // not be reset. Removing them from the binding process is + // TBD, so for now if you apply a specialization and change + // arguments you shouldn't that is a client error. + // + assert(!THROWN(f->special)); + Move_Value(f->arg, f->special); + ++f->special; + } + else if (opt_def) + Init_Void(f->arg); + else { + // just leave it alone + } + + ++f->arg; + ++f->param; + } + assert(IS_END(f->param)); + + if (opt_def) { + // In today's implementation, the body must be rebound to the frame. + // Ideally if it were read-only (at least), then the opt_def value + // should be able to carry a virtual binding into the new context. + // That feature is not currently implemented, so this mutates the + // bindings on the passed in block...as OBJECTs and other things do + // + Bind_Values_Core( + VAL_ARRAY_AT(opt_def), + Context_For_Frame_May_Reify_Managed(f), + FLAGIT_KIND(REB_SET_WORD), // types to bind (just set-word!) + 0, // types to "add midstream" to binding as we go (nothing) + BIND_DEEP + ); + + // Do the block into scratch space--we ignore the result (unless it is + // thrown, in which case it must be returned.) + // + if (Do_Any_Array_At_Throws(f->out, opt_def)) { + Drop_Frame_Core(f); + return R_OUT_IS_THROWN; + } + } + else { + // !!! This form of execution raises a ton of open questions about + // what to do if a frame is used more than once. Function calls + // are allowed to destroy their arguments and will contaminate the + // pure locals. We need to treat this as a "non-specializing + // specialization", and push a frame. The narrow case of frame + // reuse needs to be contained to something that a function can only + // do to itself--e.g. to facilitate tail recursion, because no caller + // but the function itself understands the state of its locals in situ. + } + + f->special = f->args_head; // do type/refinement checks on existing data + + SET_END(f->out); + + Do_Core(f); + + Drop_Frame_Core(f); + + if (THROWN(f->out)) + return R_OUT_IS_THROWN; // prohibits recovery from exits + + assert(IS_END(f->value)); // we started at END_FLAG, can only throw + + return R_OUT; } diff --git a/src/core/c-path.c b/src/core/c-path.c new file mode 100644 index 0000000000..7f1d687fbf --- /dev/null +++ b/src/core/c-path.c @@ -0,0 +1,635 @@ +// +// File: %c-path.h +// Summary: "Core Path Dispatching and Chaining" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// !!! See notes in %sys-path.h regarding the R3-Alpha path dispatch concept +// and regarding areas that need improvement. +// + +#include "sys-core.h" + + +// +// PD_Fail: C +// +// In order to avoid having to pay for a check for NULL in the path dispatch +// table for types with no path dispatch, a failing handler is in the slot. +// +REBINT PD_Fail(REBPVS *pvs) +{ + DECLARE_LOCAL (specified_orig); + Derelativize(specified_orig, pvs->orig, pvs->item_specifier); + + DECLARE_LOCAL (specified_item); + Derelativize(specified_item, pvs->item, pvs->item_specifier); + + fail (Error_Invalid_Path_Raw(specified_orig, specified_item)); +} + + +// +// Next_Path_Throws: C +// +// Evaluate next part of a path. +// +// !!! This is done as a recursive function instead of iterating in a loop due +// to the unusual nature of some path dispatches that call Next_Path_Throws() +// inside their implementation. +// +REBOOL Next_Path_Throws(REBPVS *pvs) +{ + if (IS_VOID(pvs->value)) + fail (Error_No_Value_Core(pvs->orig, pvs->item_specifier)); + + REBPEF dispatcher = Path_Dispatch[VAL_TYPE(pvs->value)]; + assert(dispatcher != NULL); // &PD_Fail is used instead of NULL + + pvs->item++; + + // Calculate the "picker" into the GC guarded cell. + // + assert(pvs->picker == &pvs->picker_cell); + + if (IS_GET_WORD(pvs->item)) { // e.g. object/:field + Copy_Opt_Var_May_Fail( + &pvs->picker_cell, pvs->item, pvs->item_specifier + ); + + if (IS_VOID(pvs->picker)) + fail (Error_No_Value_Core(pvs->item, pvs->item_specifier)); + } + else if (IS_GROUP(pvs->item)) { // object/(expr) case: + REBSPC *derived = Derive_Specifier(pvs->item_specifier, pvs->item); + if (Do_At_Throws( + &pvs->picker_cell, + VAL_ARRAY(pvs->item), + VAL_INDEX(pvs->item), + derived + )) { + Move_Value(pvs->store, &pvs->picker_cell); + return TRUE; + } + } + else { // object/word and object/value case: + Derelativize(&pvs->picker_cell, pvs->item, pvs->item_specifier); + } + + // Disallow voids from being used in path dispatch. This rule seems like + // common sense for safety, and also corresponds to voids being illegal + // to use in SELECT. + // + if (IS_VOID(pvs->picker)) + fail (Error_No_Value_Core(pvs->item, pvs->item_specifier)); + + switch (dispatcher(pvs)) { + case PE_OK: + break; + + case PE_SET_IF_END: + if (pvs->opt_setval && IS_END(pvs->item + 1)) { + *pvs->value = *pvs->opt_setval; + pvs->opt_setval = NULL; + } + break; + + case PE_NONE: + Init_Blank(pvs->store); + // falls through + case PE_USE_STORE: + pvs->value = pvs->store; + pvs->value_specifier = SPECIFIED; + break; + + default: + assert(FALSE); + } + + // A function being refined does not actually update pvs->value with + // a "more refined" function value, it holds the original function and + // accumulates refinement state on the stack. The label should only + // be captured the first time the function is seen, otherwise it would + // capture the last refinement's name, so check label for non-NULL. + // + if (IS_FUNCTION(pvs->value) && IS_WORD(pvs->item)) + if (pvs->label_out != NULL && *pvs->label_out == NULL) + *pvs->label_out = VAL_WORD_SPELLING(pvs->item); + + if (NOT_END(pvs->item + 1)) + return Next_Path_Throws(pvs); + + return FALSE; +} + + +// +// Do_Path_Throws_Core: C +// +// Evaluate an ANY_PATH! REBVAL, starting from the index position of that +// path value and continuing to the end. +// +// The evaluator may throw because GROUP! is evaluated, e.g. `foo/(throw 1020)` +// +// If label_sym is passed in as being non-null, then the caller is implying +// readiness to process a path which may be a function with refinements. +// These refinements will be left in order on the data stack in the case +// that `out` comes back as IS_FUNCTION(). +// +// If `opt_setval` is given, the path operation will be done as a "SET-PATH!" +// if the path evaluation did not throw or error. HOWEVER the set value +// is NOT put into `out`. This provides more flexibility on performance in +// the evaluator, which may already have the `val` where it wants it, and +// so the extra assignment would just be overhead. +// +// !!! Path evaluation is one of the parts of R3-Alpha that has not been +// vetted very heavily by Ren-C, and needs a review and overhaul. +// +REBOOL Do_Path_Throws_Core( + REBVAL *out, + REBSTR **label_out, + const RELVAL *path, + REBSPC *specifier, + const REBVAL *opt_setval +) { + // The pvs contains a cell for the picker into which evaluations are + // done, e.g. `foo/(1 + 2)`. Because Next_Path() doesn't commit to not + // performing any evaluations this cell must be guarded. In the case of + // a fail() this guard will be released automatically, but to return + // normally use `return_thrown` and `return_not_thrown` which drops guard. + // + // !!! There was also a strange requirement in some more quirky path + // evaluation (GOB!, STRUCT!) that the cell survive between Next_Path() + // calls, which may still be relevant to why this can't be a C local. + // + REBPVS pvs; + Prep_Global_Cell(&pvs.picker_cell); + SET_END(&pvs.picker_cell); + PUSH_GUARD_VALUE(&pvs.picker_cell); + pvs.picker = &pvs.picker_cell; + + REBDSP dsp_orig = DSP; + + assert(ANY_PATH(path)); + + // !!! There is a bug in the dispatch such that if you are running a + // set path, it does not always assign the output, because it "thinks you + // aren't going to look at it". This presumably originated from before + // parens were allowed in paths, and neglects cases like: + // + // foo/(throw 1020): value + // + // We always have to check to see if a throw occurred. Until this is + // streamlined, we have to at minimum set it to something that is *not* + // thrown so that we aren't testing uninitialized memory. A safe trash + // will do, which is unset in release builds. + // + if (opt_setval) + SET_UNREADABLE_BLANK(out); + + // None of the values passed in can live on the data stack, because + // they might be relocated during the path evaluation process. + // + assert(!IN_DATA_STACK_DEBUG(out)); + assert(!IN_DATA_STACK_DEBUG(path)); + assert(!opt_setval || !IN_DATA_STACK_DEBUG(opt_setval)); + + // Not currently robust for reusing passed in path or value as the output + assert(out != path && out != opt_setval); + + assert(!opt_setval || !THROWN(opt_setval)); + + // Initialize REBPVS -- see notes in %sys-do.h + // + pvs.opt_setval = opt_setval; + pvs.store = out; + pvs.orig = path; + pvs.item = VAL_ARRAY_AT(pvs.orig); // may not be starting at head of PATH! + pvs.label_out = label_out; + if (label_out != NULL) + *label_out = NULL; // initial value if no function label found + + // The path value that's coming in may be relative (in which case it + // needs to use the specifier passed in). Or it may be specific already, + // in which case we should use the specifier in the value to process + // its array contents. + // + pvs.item_specifier = Derive_Specifier(specifier, path); + + // Seed the path evaluation process by looking up the first item (to + // get a datatype to dispatch on for the later path items) + // + if (IS_WORD(pvs.item)) { + pvs.value = Get_Mutable_Var_May_Fail(pvs.item, pvs.item_specifier); + pvs.value_specifier = SPECIFIED; + + if (IS_VOID(pvs.value)) + fail (Error_No_Value_Core(pvs.item, pvs.item_specifier)); + + if (IS_FUNCTION(pvs.value) && pvs.label_out != NULL) + *pvs.label_out = VAL_WORD_SPELLING(pvs.item); + } + else { + // !!! Ideally there would be some way to deal with writes to + // temporary locations, like this pvs.value...if a set-path sets + // it, then it will be discarded. + + Derelativize(pvs.store, VAL_ARRAY_AT(pvs.orig), pvs.item_specifier); + pvs.value = pvs.store; + pvs.value_specifier = SPECIFIED; + } + + // Start evaluation of path: + if (IS_END(pvs.item + 1)) { + // If it was a single element path, return the value rather than + // try to dispatch it (would cause a crash at time of writing) + // + // !!! Is this the desired behavior, or should it be an error? + } + else { + REBOOL threw = Next_Path_Throws(&pvs); + + // !!! See comments about why the initialization of out is necessary. + // Without it this assertion can change on some things: + // + // t: now + // t/time: 10:20:03 + // + // (It thinks pvs.value has its THROWN bit set when it completed + // successfully. It was a PE_USE_STORE case where pvs.value was reset to + // pvs.store, and pvs.store has its thrown bit set. Valgrind does not + // catch any uninitialized variables.) + // + // There are other cases that do trip valgrind when omitting the + // initialization, though not as clearly reproducible. + // + assert(threw == THROWN(pvs.value)); + + if (threw) + goto return_thrown; + } + + if (opt_setval) { + // If SET then we don't return anything + assert(IS_END(pvs.item) + 1); + goto return_not_thrown; + } + + // If storage was not used, then copy final value back to it: + if (pvs.value != pvs.store) + Derelativize(pvs.store, pvs.value, pvs.value_specifier); + + assert(!THROWN(out)); + + assert(IS_END(pvs.item) + 1); + + // To make things easier for processing, reverse any refinements + // pushed to the data stack (we needed to evaluate them + // in forward order). This way we can just pop them as we go, + // and know if they weren't all consumed if it doesn't get + // back to `dsp_orig` by the end. + // + if (dsp_orig != DSP) { + assert(IS_FUNCTION(pvs.store)); + + // !!! It should be technically possible to do something like + // :append/dup and return a "refined" variant of a function. That + // feature is not currently implemented. So if a label wasn't + // requested, assume a function is not being run and deliver an + // error for that case. + // + if (label_out == NULL) + fail (Error_Too_Long_Raw()); + + REBVAL *bottom = DS_AT(dsp_orig + 1); + REBVAL *top = DS_TOP; + while (top > bottom) { + DECLARE_LOCAL (temp); + Move_Value(temp, bottom); + Move_Value(bottom, top); + Move_Value(top, temp); + + top--; + bottom++; + } + } + +return_not_thrown: + DROP_GUARD_VALUE(&pvs.picker_cell); + return FALSE; + +return_thrown: + DROP_GUARD_VALUE(&pvs.picker_cell); + return TRUE; +} + + +// +// Error_Bad_Path_Select: C +// +REBCTX *Error_Bad_Path_Select(REBPVS *pvs) +{ + DECLARE_LOCAL (orig); + Derelativize(orig, pvs->orig, pvs->item_specifier); + + DECLARE_LOCAL (item); + Derelativize(item, pvs->item, pvs->item_specifier); + + return Error_Invalid_Path_Raw(orig, item); +} + + +// +// Error_Bad_Path_Set: C +// +REBCTX *Error_Bad_Path_Set(REBPVS *pvs) +{ + DECLARE_LOCAL (orig); + Derelativize(orig, pvs->orig, pvs->item_specifier); + + DECLARE_LOCAL (item); + Derelativize(item, pvs->item, pvs->item_specifier); + + return Error_Bad_Path_Set_Raw(orig, item); +} + + +// +// Error_Bad_Path_Range: C +// +REBCTX *Error_Bad_Path_Range(REBPVS *pvs) +{ + DECLARE_LOCAL (item); + Derelativize(item, pvs->item, pvs->item_specifier); + + return Error_Out_Of_Range(item); +} + + +// +// Error_Bad_Path_Field_Set: C +// +REBCTX *Error_Bad_Path_Field_Set(REBPVS *pvs) +{ + DECLARE_LOCAL (item); + Derelativize(item, pvs->item, pvs->item_specifier); + + return Error_Bad_Field_Set_Raw(item, Type_Of(pvs->opt_setval)); +} + + +// +// Get_Simple_Value_Into: C +// +// Does easy lookup, else just returns the value as is. +// +void Get_Simple_Value_Into(REBVAL *out, const RELVAL *val, REBSPC *specifier) +{ + if (IS_WORD(val) || IS_GET_WORD(val)) { + Copy_Opt_Var_May_Fail(out, val, specifier); + } + else if (IS_PATH(val) || IS_GET_PATH(val)) { + if (Do_Path_Throws_Core(out, NULL, val, specifier, NULL)) + fail (Error_No_Catch_For_Throw(out)); + } + else { + Derelativize(out, val, specifier); + } +} + + +// +// Resolve_Path: C +// +// Given a path, determine if it is ultimately specifying a selection out +// of a context...and if it is, return that context. So `a/obj/key` would +// return the object assocated with obj, while `a/str/1` would return +// NULL if `str` were a string as it's not an object selection. +// +// !!! This routine overlaps the logic of Do_Path, and should potentially +// be a mode of that instead. It is not very complete, considering that it +// does not execute GROUP! (and perhaps shouldn't?) and only supports a +// path that picks contexts out of other contexts, via word selection. +// +REBCTX *Resolve_Path(const REBVAL *path, REBCNT *index_out) +{ + REBARR *array = VAL_ARRAY(path); + RELVAL *picker = ARR_HEAD(array); + + if (IS_END(picker) || !ANY_WORD(picker)) + return NULL; // !!! only handles heads of paths that are ANY-WORD! + + const RELVAL *var = Get_Opt_Var_May_Fail(picker, VAL_SPECIFIER(path)); + + ++picker; + if (IS_END(picker)) + return NULL; // !!! does not handle single-element paths + + while (ANY_CONTEXT(var) && IS_WORD(picker)) { + REBCNT i = Find_Canon_In_Context( + VAL_CONTEXT(var), VAL_WORD_CANON(picker), FALSE + ); + ++picker; + if (IS_END(picker)) { + *index_out = i; + return VAL_CONTEXT(var); + } + + var = CTX_VAR(VAL_CONTEXT(var), i); + } + + return NULL; +} + + +// +// pick*: native [ +// +// {Perform a path picking operation, same as `:(:location)/(:picker)`} +// +// return: [ any-value!] +// {Picked value, or void if picker can't fulfill the request} +// location [any-value!] +// picker [any-value!] +// {Index offset, symbol, or other value to use as index} +// ] +// +REBNATIVE(pick_p) +// +// In R3-Alpha, PICK was an "action", which dispatched on types through the +// "action mechanic" for the following types: +// +// [any-series! map! gob! pair! date! time! tuple! bitset! port! varargs!] +// +// In Ren-C, PICK is rethought to use the same dispatch mechanic as paths, +// to cut down on the total number of operations the system has to define. +{ + INCLUDE_PARAMS_OF_PICK_P; + + REBVAL *location = ARG(location); + REBVAL *picker = ARG(picker); + + // PORT!s are kind of a "user defined type" which historically could + // react to PICK and POKE, but which could not override path dispatch. + // Use a symbol-based call to bounce the frame to the port, which should + // be a compatible frame with the historical "action". + // + if (IS_PORT(location)) + return Do_Port_Action(frame_, VAL_CONTEXT(location), SYM_PICK_P); + + REBPVS pvs_decl; + REBPVS *pvs = &pvs_decl; + + Prep_Global_Cell(&pvs->picker_cell); + TRASH_CELL_IF_DEBUG(&pvs->picker_cell); // not used + pvs->picker = picker; + pvs->store = D_OUT; + + // !!! Sometimes path dispatchers check the item to see if it's at the + // end of the path. The entire thing needs review. In the meantime, + // take advantage of the implicit termination of the frame cell. + // + Move_Value(D_CELL, picker); + assert(IS_END(D_CELL + 1)); + + pvs->item = D_CELL; + pvs->item_specifier = SPECIFIED; + pvs->value = location; + pvs->value_specifier = SPECIFIED; + + pvs->label_out = NULL; // applies to e.g. :append/only returning APPEND + pvs->orig = location; // expected to be a PATH! for errors, but tolerant + pvs->opt_setval = NULL; + + REBPEF dispatcher = Path_Dispatch[VAL_TYPE(location)]; + assert(dispatcher != NULL); // &PD_Fail is used instead of NULL + switch (dispatcher(pvs)) { + case PE_OK: + break; + + case PE_SET_IF_END: + break; + + case PE_NONE: + Init_Blank(pvs->store); + // falls through + case PE_USE_STORE: + pvs->value = pvs->store; + pvs->value_specifier = SPECIFIED; + break; + + default: + assert(FALSE); + } + + if (pvs->value != pvs->store) + Derelativize(D_OUT, pvs->value, pvs->value_specifier); + + return R_OUT; +} + + +// +// poke: native [ +// +// {Perform a path poking operation, same as `(:location)/(:picker): :value`} +// +// return: [ any-value!] +// {Same as value} +// location [any-value!] +// {(modified)} +// picker +// {Index offset, symbol, or other value to use as index} +// value [ any-value!] +// {The new value} +// ] +// +REBNATIVE(poke) +// +// As with PICK*, POKE is changed in Ren-C from its own action to "whatever +// path-setting (now path-poking) would do". +{ + INCLUDE_PARAMS_OF_POKE; + + REBVAL *location = ARG(location); + REBVAL *picker = ARG(picker); + REBVAL *value = ARG(value); + + // PORT!s are kind of a "user defined type" which historically could + // react to PICK and POKE, but which could not override path dispatch. + // Use a symbol-based call to bounce the frame to the port, which should + // be a compatible frame with the historical "action". + // + if (IS_PORT(location)) + return Do_Port_Action(frame_, VAL_CONTEXT(location), SYM_POKE); + + REBPVS pvs_decl; + REBPVS *pvs = &pvs_decl; + + Prep_Global_Cell(&pvs->picker_cell); + TRASH_CELL_IF_DEBUG(&pvs->picker_cell); // not used + pvs->picker = picker; + pvs->store = D_OUT; + + // !!! Sometimes the path mechanics do the writes for a poke inside their + // dispatcher, vs. delegating via PE_SET_IF_END. They check to see if + // the current pvs->item is at the end. All of path dispatch was ad hoc + // and needs a review. In the meantime, take advantage of the implicit + // termination of the frame cell. + // + Move_Value(D_CELL, picker); + assert(IS_END(D_CELL + 1)); + + pvs->item = D_CELL; + pvs->item_specifier = SPECIFIED; + pvs->value = location; + pvs->value_specifier = SPECIFIED; + + pvs->label_out = NULL; // applies to e.g. :append/only returning APPEND + pvs->orig = location; // expected to be a PATH! for errors, but tolerant + pvs->opt_setval = value; + + REBPEF dispatcher = Path_Dispatch[VAL_TYPE(location)]; + assert(dispatcher != NULL); // &PD_Fail is used instead of NULL + switch (dispatcher(pvs)) { + case PE_SET_IF_END: + *pvs->value = *pvs->opt_setval; + break; + + case PE_OK: + // !!! Trust that it wrote? See above notes about D_CELL. + break; + + case PE_NONE: + case PE_USE_STORE: + fail (picker); // Invalid argument + + default: + assert(FALSE); + } + + Move_Value(D_OUT, value); + return R_OUT; +} diff --git a/src/core/c-port.c b/src/core/c-port.c old mode 100644 new mode 100755 index d6a94104db..1113ca74af --- a/src/core/c-port.c +++ b/src/core/c-port.c @@ -1,631 +1,607 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: c-port.c -** Summary: support for I/O ports -** Section: core -** Author: Carl Sassenrath -** Notes: -** See comments in Init_Ports for startup. -** See www.rebol.net/wiki/Event_System for full details. -** -***********************************************************************/ +// +// File: %c-port.c +// Summary: "support for I/O ports" +// Section: core +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// See comments in Init_Ports for startup. +// See www.rebol.net/wiki/Event_System for full details. +// #include "sys-core.h" #define MAX_WAIT_MS 64 // Maximum millsec to sleep -/*********************************************************************** -** -*/ REBVAL *Make_Port(REBVAL *spec) -/* -** Create a new port. This is done by calling the MAKE_PORT -** function stored in the system/intrinsic object. -** -***********************************************************************/ -{ - REBVAL *value; - - value = Do_Sys_Func(SYS_CTX_MAKE_PORT_P, spec, 0); // volatile - if (IS_NONE(value)) Trap1(RE_INVALID_SPEC, spec); - - return value; -} - -/*********************************************************************** -** -*/ REBFLG Is_Port_Open(REBSER *port) -/* -** Standard method for checking if port is open. -** A convention. Not all ports use this method. -** -***********************************************************************/ +// +// Is_Port_Open: C +// +// Standard method for checking if port is open. +// A convention. Not all ports use this method. +// +REBOOL Is_Port_Open(REBCTX *port) { - REBVAL *state = BLK_SKIP(port, STD_PORT_STATE); - if (!IS_BINARY(state)) return FALSE; - return IS_OPEN(VAL_BIN_DATA(state)); + REBVAL *state = CTX_VAR(port, STD_PORT_STATE); + if (!IS_BINARY(state)) return FALSE; + return IS_OPEN(VAL_BIN_AT(state)); } -/*********************************************************************** -** -*/ void Set_Port_Open(REBSER *port, REBFLG flag) -/* -** Standard method for setting a port open/closed. -** A convention. Not all ports use this method. -** -***********************************************************************/ +// +// Set_Port_Open: C +// +// Standard method for setting a port open/closed. +// A convention. Not all ports use this method. +// +void Set_Port_Open(REBCTX *port, REBOOL open) { - REBVAL *state = BLK_SKIP(port, STD_PORT_STATE); - if (IS_BINARY(state)) { - if (flag) SET_OPEN(VAL_BIN_DATA(state)); - else SET_CLOSED(VAL_BIN_DATA(state)); - } + REBVAL *state = CTX_VAR(port, STD_PORT_STATE); + if (IS_BINARY(state)) { + if (open) SET_OPEN(VAL_BIN_AT(state)); + else SET_CLOSED(VAL_BIN_AT(state)); + } } -/*********************************************************************** -** -*/ void *Use_Port_State(REBSER *port, REBCNT device, REBCNT size) -/* -** Use private state area in a port. Create if necessary. -** The size is that of a binary structure used by -** the port for storing internal information. -** -***********************************************************************/ +// +// Ensure_Port_State: C +// +// Use private state area in a port. Create if necessary. +// The size is that of a binary structure used by +// the port for storing internal information. +// +REBREQ *Ensure_Port_State(REBCTX *port, REBCNT device) { - REBVAL *state = BLK_SKIP(port, STD_PORT_STATE); - - // If state is not a binary structure, create it: - if (!IS_BINARY(state)) { - REBSER *data = Make_Binary(size); - REBREQ *req = (REBREQ*)STR_HEAD(data); - Guard_Series(data); // GC safe if no other references - req->clen = size; - CLEAR(STR_HEAD(data), size); - //data->tail = size; // makes it easier for ACCEPT to clone the port - SET_FLAG(req->flags, RRF_ALLOC); // not on stack - req->port = port; - req->device = device; - Set_Binary(state, data); - } - - return (void *)VAL_BIN(state); + REBVAL *state = CTX_VAR(port, STD_PORT_STATE); + REBCNT req_size = OS_DEVREQ_SIZE(device); + + if (!IS_BINARY(state)) { + assert(IS_BLANK(state)); + REBSER *data = Make_Binary(req_size); + CLEAR(BIN_HEAD(data), req_size); + TERM_BIN_LEN(data, req_size); + + REBREQ *req = cast(REBREQ*, BIN_HEAD(data)); + SET_FLAG(req->flags, RRF_ALLOC); // not on stack + req->port = port; + req->device = device; + Init_Binary(state, data); + } + else { + assert(VAL_INDEX(state) == 0); // should always be at head + assert(VAL_LEN_HEAD(state) == req_size); // should be right size + } + + return cast(REBREQ*, VAL_BIN(state)); } -/*********************************************************************** -** -*/ void Free_Port_State(REBSER *port) -/* -***********************************************************************/ +// +// Pending_Port: C +// +// Return TRUE if port value is pending a signal. +// Not valid for all ports - requires request struct!!! +// +REBOOL Pending_Port(REBVAL *port) { - REBVAL *state = BLK_SKIP(port, STD_PORT_STATE); - - // ??? check that this is the binary we think it is? !!! - - if (IS_BINARY(state)) { - Loose_Series(VAL_SERIES(state)); - VAL_SET(state, REB_NONE); - } + REBVAL *state; + REBREQ *req; + + if (IS_PORT(port)) { + state = CTX_VAR(VAL_CONTEXT(port), STD_PORT_STATE); + if (IS_BINARY(state)) { + req = (REBREQ*)VAL_BIN(state); + if (!GET_FLAG(req->flags, RRF_PENDING)) return FALSE; + } + } + return TRUE; } -/*********************************************************************** -** -*/ REBFLG Pending_Port(REBVAL *port) -/* -** Return TRUE if port value is pending a signal. -** Not valid for all ports - requires request struct!!! -** -***********************************************************************/ +// +// Awake_System: C +// +// Returns: +// -1 for errors +// 0 for nothing to do +// 1 for wait is satisifed +// +REBINT Awake_System(REBARR *ports, REBOOL only) { - REBVAL *state; - REBREQ *req; - - if (IS_PORT(port)) { - state = BLK_SKIP(VAL_PORT(port), STD_PORT_STATE); - if (IS_BINARY(state)) { - req = (REBREQ*)VAL_BIN(state); - if (!GET_FLAG(req->flags, RRF_PENDING)) return FALSE; - } - } - return TRUE; + // Get the system port object: + REBVAL *port = Get_System(SYS_PORTS, PORTS_SYSTEM); + if (!IS_PORT(port)) + return -10; // verify it is a port object + + // Get wait queue block (the state field): + REBVAL *state = VAL_CONTEXT_VAR(port, STD_PORT_STATE); + if (!IS_BLOCK(state)) + return -10; + + // Get waked queue block: + REBVAL *waked = VAL_CONTEXT_VAR(port, STD_PORT_DATA); + if (!IS_BLOCK(waked)) + return -10; + + // If there is nothing new to do, return now: + if (VAL_LEN_HEAD(state) == 0 && VAL_LEN_HEAD(waked) == 0) + return -1; + + // Get the system port AWAKE function: + REBVAL *awake = VAL_CONTEXT_VAR(port, STD_PORT_AWAKE); + if (!IS_FUNCTION(awake)) + return -1; + + DECLARE_LOCAL (tmp); + if (ports) + Init_Block(tmp, ports); + else + Init_Blank(tmp); + + DECLARE_LOCAL (awake_only); + if (only) { + // + // If we're using /ONLY, we need path AWAKE/ONLY to call. (Ren-C's + // va_list API does not support positionally-provided refinements.) + // + REBARR *array = Make_Array(2); + Append_Value(array, awake); + Init_Word(Alloc_Tail_Array(array), Canon(SYM_ONLY)); + + Init_Path(awake_only, array); + } + + // Call the system awake function: + // + DECLARE_LOCAL (result); + if (Apply_Only_Throws( + result, + TRUE, + only ? awake_only : awake, + port, + tmp, + END + )) { + fail (Error_No_Catch_For_Throw(result)); + } + + // Awake function returns 1 for end of WAIT: + // + return (IS_LOGIC(result) && VAL_LOGIC(result)) ? 1 : 0; } -/*********************************************************************** -** -*/ REBINT Awake_System(REBSER *ports) -/* -** Returns: -** -1 for errors -** 0 for nothing to do -** 1 for wait is satisifed -** -***********************************************************************/ -{ - REBVAL *port; - REBVAL *state; - REBVAL *waked; - REBVAL *awake; - REBVAL tmp; - REBVAL *v; - - // Get the system port object: - port = Get_System(SYS_PORTS, PORTS_SYSTEM); - if (!IS_PORT(port)) return -10; // verify it is a port object - - // Get wait queue block (the state field): - state = VAL_BLK_SKIP(port, STD_PORT_STATE); - if (!IS_BLOCK(state)) return -10; - //Debug_Num("S", VAL_TAIL(state)); - - // Get waked queue block: - waked = VAL_BLK_SKIP(port, STD_PORT_DATA); - if (!IS_BLOCK(waked)) return -10; - - // If there is nothing new to do, return now: - if (VAL_TAIL(state) == 0 && VAL_TAIL(waked) == 0) return -1; - - //Debug_Num("A", VAL_TAIL(waked)); - // Get the system port AWAKE function: - awake = VAL_BLK_SKIP(port, STD_PORT_AWAKE); - if (!ANY_FUNC(awake)) return -1; - if (ports) Set_Block(&tmp, ports); - else SET_NONE(&tmp); - - // Call the system awake function: - v = Apply_Func(0, awake, port, &tmp, 0); // ds is return value - - // Awake function returns 1 for end of WAIT: - return (IS_LOGIC(v) && VAL_LOGIC(v)) ? 1 : 0; -} - - -/*********************************************************************** -** -*/ REBINT Wait_Ports(REBSER *ports, REBCNT timeout) -/* -** Inputs: -** Ports: a block of ports or zero (on stack to avoid GC). -** Timeout: milliseconds to wait -** -** Returns: -** TRUE when port action happened, or FALSE for timeout. -** -***********************************************************************/ -{ - REBI64 base = OS_DELTA_TIME(0, 0); - REBCNT time; - REBINT result; - REBCNT wt = 1; - REBCNT res = (timeout >= 1000) ? 0 : 16; // OS dependent? - - while (wt) { - if (GET_SIGNAL(SIG_ESCAPE)) { - CLR_SIGNAL(SIG_ESCAPE); - Halt_Code(RE_HALT, 0); // Throws! - } - - // Process any waiting events: - if ((result = Awake_System(ports)) > 0) return TRUE; - - // If activity, use low wait time, otherwise increase it: - if (result == 0) wt = 1; - else { - wt *= 2; - if (wt > MAX_WAIT_MS) wt = MAX_WAIT_MS; - } - - if (timeout != ALL_BITS) { - // Figure out how long that (and OS_WAIT) took: - time = (REBCNT)(OS_DELTA_TIME(base, 0)/1000); - if (time >= timeout) break; // done (was dt = 0 before) - else if (wt > timeout - time) // use smaller residual time - wt = timeout - time; - } - - //printf("%d %d %d\n", dt, time, timeout); - - // Wait for events or time to expire: - //Debug_Num("OSW", wt); - OS_WAIT(wt, res); - } - - //time = (REBCNT)OS_DELTA_TIME(base, 0); - //Print("dt: %d", time); - - return FALSE; // timeout -} - -#ifdef NDEF -/*********************************************************************** -** -xx*/ REBINT Wait_Device(REBREQ *req, REBCNT timeout) -/* -** Utility function for waiting on specific device. -** (Near the main WAIT code above to keep in-sync.) -** This still lets any GUI events continue. -** Returns 0 when event occurs, else -1 for error. -** -***********************************************************************/ -{ - REBI64 base = OS_DELTA_TIME(0); - REBCNT time; - REBCNT dt = DT; - - while (dt) { - // Process any waiting events: - Awake_System(0); - if (!GET_FLAG(req->flags, RRF_PENDING)) return TRUE; - - // Figure out how long that (and OS_WAIT) took: - time = (REBCNT)OS_DELTA_TIME(base); - - Use above method! - - // Did we use all our time? - if (timeout == ALL_BITS) dt = DT; // infinite time - else if (time >= timeout) dt = 0; // done - else if (dt > timeout - time) // residual time - dt = timeout - time; - - // Wait for events or time to expire: - OS_WAIT(dt); - } - - return FALSE; // timeout -} -#endif - -/*********************************************************************** -** -*/ void Sieve_Ports(REBSER *ports) -/* -** Remove all ports not found in the WAKE list. -** -***********************************************************************/ +// +// Wait_Ports: C +// +// Inputs: +// Ports: a block of ports or zero (on stack to avoid GC). +// Timeout: milliseconds to wait +// +// Returns: +// TRUE when port action happened, or FALSE for timeout. +// +REBOOL Wait_Ports(REBARR *ports, REBCNT timeout, REBOOL only) { - REBVAL *port; - REBVAL *waked; - REBVAL *val; - REBCNT n; - - port = Get_System(SYS_PORTS, PORTS_SYSTEM); - if (!IS_PORT(port)) return; - waked = VAL_BLK_SKIP(port, STD_PORT_DATA); - if (!IS_BLOCK(waked)) return; - - for (n = 0; n < SERIES_TAIL(ports);) { - val = BLK_SKIP(ports, n); - if (IS_PORT(val)) { - if (VAL_TAIL(waked) != Find_Block_Simple(VAL_SERIES(waked), 0, val)) { - Remove_Series(VAL_SERIES(waked), n, 1); - continue; - } - } - n++; - } + REBI64 base = OS_DELTA_TIME(0, 0); + REBCNT time; + REBCNT wt = 1; + REBCNT res = (timeout >= 1000) ? 0 : 16; // OS dependent? + + // Waiting opens the doors to pressing Ctrl-C, which may get this code + // to throw an error. There needs to be a state to catch it. + // + assert(Saved_State != NULL); + + while (wt) { + if (GET_SIGNAL(SIG_HALT)) { + CLR_SIGNAL(SIG_HALT); + fail (VAL_CONTEXT(TASK_HALT_ERROR)); + } + + if (GET_SIGNAL(SIG_INTERRUPT)) { + CLR_SIGNAL(SIG_INTERRUPT); + + DECLARE_LOCAL (result); + if (Do_Breakpoint_Throws(result, TRUE, VOID_CELL, FALSE)) { + // + // !!! Consider Wait_Ports() callsites being re-engineered + // to be able to gracefully accept a throw generated by + // a RESUME from a breakpoint, e.g. `resume/do [throw 10]`. + // This would require having a return result. + // + fail (Error_No_Catch_For_Throw(result)); + } + if (!IS_VOID(result)) { + // + // !!! Same as above... if `resume/with 10` is to have any + // meaning then there must be a way to deliver that result + // up the stack. + // + fail ("Cannot deliver non-void result from Wait_Ports()"); + } + } + + REBINT ret; + + // Process any waiting events: + if ((ret = Awake_System(ports, only)) > 0) return TRUE; + + // If activity, use low wait time, otherwise increase it: + if (ret == 0) wt = 1; + else { + wt *= 2; + if (wt > MAX_WAIT_MS) wt = MAX_WAIT_MS; + } + + if (timeout != ALL_BITS) { + // Figure out how long that (and OS_WAIT) took: + time = (REBCNT)(OS_DELTA_TIME(base, 0)/1000); + if (time >= timeout) break; // done (was dt = 0 before) + else if (wt > timeout - time) // use smaller residual time + wt = timeout - time; + } + + //printf("%d %d %d\n", dt, time, timeout); + + // Wait for events or time to expire: + OS_WAIT(wt, res); + } + + //time = (REBCNT)OS_DELTA_TIME(base, 0); + //Print("dt: %d", time); + + return FALSE; // timeout } -#ifdef not_used -/*********************************************************************** -** -*/ REBVAL *Form_Write(REBVAL *arg, REBYTE *newline) -/* -** Converts REBOL values to strings to use as data in WRITE. -** Will also add newlines for conversions of blocks of lines. -** -***********************************************************************/ +// +// Sieve_Ports: C +// +// Remove all ports not found in the WAKE list. +// ports could be NULL, in which case the WAKE list is cleared. +// +void Sieve_Ports(REBARR *ports) { - REBSER *series; - REBVAL *val; - REBCNT n = 0; - //REB_MOLD mo = {0 --- more here needed}; - - if (IS_BLOCK(arg)) { - - if (newline) n = LEN_BYTES(newline); - - mo.series = series = Make_Binary(VAL_BLK_LEN(arg) * 10); - - for (val = VAL_BLK_DATA(arg); NOT_END(val); val++) { - Mold_Value(&mo, val, 0); - if (newline) Append_Series(series, newline, n); - } - - Set_String(arg, series); - } - - if (!ANY_STRING(arg)) { - Set_String(arg, Copy_Form_Value(arg, 0)); - } - - return arg; + REBVAL *port; + REBVAL *waked; + REBCNT n; + + port = Get_System(SYS_PORTS, PORTS_SYSTEM); + if (!IS_PORT(port)) return; + waked = VAL_CONTEXT_VAR(port, STD_PORT_DATA); + if (!IS_BLOCK(waked)) return; + + for (n = 0; ports && n < ARR_LEN(ports);) { + RELVAL *val = ARR_AT(ports, n); + if (IS_PORT(val)) { + assert(VAL_LEN_HEAD(waked) != 0); + if ( + Find_In_Array_Simple(VAL_ARRAY(waked), 0, val) + == VAL_LEN_HEAD(waked) // `=len` means not found + ) { + Remove_Series(SER(ports), n, 1); + continue; + } + } + n++; + } + //clear waked list + RESET_ARRAY(VAL_ARRAY(waked)); } -#endif -/*********************************************************************** -** -*/ REBCNT Find_Action(REBVAL *object, REBCNT action) -/* -** Given an action number, return the action's index in -** the specified object. If not found, a zero is returned. -** -***********************************************************************/ +// +// Find_Action: C +// +// Given an action number, return the action's index in +// the specified object. If not found, a zero is returned. +// +REBCNT Find_Action(REBVAL *object, REBSYM action) { - return Find_Word_Index(VAL_OBJ_FRAME(object), VAL_BIND_SYM(Get_Action_Word(action)), FALSE); + return Find_Canon_In_Context(VAL_CONTEXT(object), Canon(action), FALSE); } -/*********************************************************************** -** -*/ int Do_Port_Action(REBSER *port, REBCNT action) -/* -** Call a PORT actor (action) value. Search PORT actor -** first. If not found, search the PORT scheme actor. -** -** NOTE: stack must already be setup correctly for action, and -** the caller must cleanup the stack. -** -***********************************************************************/ +// +// Redo_Func_Throws: C +// +// This code takes a running call frame that has been built for one function +// and then tries to map its parameters to another call. It is used to +// dispatch some ACTION!s (an archetypal function spec with no implementation) +// from a native C invocation to be "bounced" out into user code. +// +// In the origins of this function's active usage in R3-Alpha, it was allowed +// for the target function to have a parameterization that was a superset of +// the original frame's function (adding refinements, etc.) The greater +// intentions of how it was supposed to work are not known--as there was +// little error checking, given there were few instances. +// +// !!! Due to the historical brittleness of this function, very rare calls, +// and need for an additional repetition of dispatch logic from Do_Core, +// this code has been replaced with a straightforward implementation. It +// builds a PATH! of the target function and refinements from the original +// frame. Then it uses this in the DO_FLAG_EVAL_ONLY mode to suppress +// re-evaluation of the frame's "live" args. +// +// !!! This won't stand up in the face of targets that are "adversarial" +// to the archetype: +// +// foo: func [a /b c] [...] => bar: func [/b d e] [...] +// foo/b 1 2 => bar/b 1 2 +// +// However, it is still *much* better than the R3-Alpha situation for error +// checking, and significantly less confusing. A real solution to this kind +// of dispatch--if it is to be used--seems like it should be a language +// feature available to users themselves. So leaning on the evaluator in +// one way or another is the best course to keep this functionality going. +// +REBOOL Redo_Func_Throws(REBFRM *f, REBFUN *func_new) { - REBVAL *actor; - REBCNT n = 0; - - ASSERT2(action < A_MAX_ACTION, RP_BAD_PORT_ACTION); - - // Verify valid port (all of these must be false): - if ( - // Must be = or larger than std port: - (SERIES_TAIL(port) < STD_PORT_MAX) || - // Must be an object series: - !IS_FRAME(BLK_HEAD(port)) || - // Must have a spec object: - !IS_OBJECT(BLK_SKIP(port, STD_PORT_SPEC)) - ) - Trap0(RE_INVALID_PORT); - - // Get actor for port, if it has one: - actor = BLK_SKIP(port, STD_PORT_ACTOR); - - if (IS_NONE(actor)) return R_NONE; - - // If actor is a native function: - if (IS_NATIVE(actor)) - return ((REBPAF)VAL_FUNC_CODE(actor))(DS_RETURN, port, action); - - // actor must be an object: - if (!IS_OBJECT(actor)) Trap0(RE_INVALID_ACTOR); - - // Dispatch object function: - n = Find_Action(actor, action); - actor = Obj_Value(actor, n); - if (!n || !actor || !ANY_FUNC(actor)) { - Trap1(RE_NO_PORT_ACTION, Get_Action_Word(action)); - } - Redo_Func(actor); - return R_RET; - - // If not in PORT actor, use the SCHEME actor: -#ifdef no_longer_used - if (n == 0) { - actor = Obj_Value(scheme, STD_SCHEME_actor); - if (!actor) goto err; - if (IS_NATIVE(actor)) goto fun; - if (!IS_OBJECT(actor)) goto err; //Trap_Expect(value, STD_PORT_actor, REB_OBJECT); - n = Find_Action(actor, action); - if (n == 0) goto err; - } -#endif - + // Upper bound on the length of the args we might need for a redo + // invocation is the total number of parameters to the *old* function's + // invocation (if it had no refinements or locals). + // + REBARR *code_array = Make_Array(FUNC_NUM_PARAMS(f->phase)); + RELVAL *code = ARR_HEAD(code_array); + + // We'll walk through the original functions param and arglist only, and + // accept the error-checking the evaluator provides at this time (types, + // refinement presence or absence matching). + // + // !!! See note in function description about arity mismatches. + // + f->param = FUNC_FACADE_HEAD(f->phase); + f->arg = f->args_head; + REBOOL ignoring = FALSE; + + // The first element of our path will be the function, followed by its + // refinements. It has an upper bound on length that is to consider the + // opposite case where it had only refinements and then the function + // at the head... + // + REBARR *path_array = Make_Array(FUNC_NUM_PARAMS(f->phase) + 1); + RELVAL *path = ARR_HEAD(path_array); + + Move_Value(path, FUNC_VALUE(func_new)); + ++path; + + for (; NOT_END(f->param); ++f->param, ++f->arg) { + enum Reb_Param_Class pclass = VAL_PARAM_CLASS(f->param); + + if ( + pclass == PARAM_CLASS_LOCAL + || pclass == PARAM_CLASS_LEAVE + || pclass == PARAM_CLASS_RETURN + ) { + continue; // don't add a callsite expression for it (can't)! + } + + if (pclass == PARAM_CLASS_REFINEMENT) { + if (IS_CONDITIONAL_FALSE(f->arg)) { + // + // If the refinement is not in use, do not add it and ignore + // args until the next refinement. + // + ignoring = TRUE; + continue; + } + + // In use--and used refinements must be added to the PATH! + // + ignoring = FALSE; + Init_Word(path, VAL_PARAM_SPELLING(f->param)); + ++path; + continue; + } + + // Otherwise it should be a quoted or normal argument. If ignoring + // then pass on it, otherwise add the arg to the code as-is. + // + if (ignoring) continue; + + Move_Value(code, f->arg); + ++code; + } + + TERM_ARRAY_LEN(code_array, code - ARR_HEAD(code_array)); + MANAGE_ARRAY(code_array); + + DECLARE_LOCAL (first); + TERM_ARRAY_LEN(path_array, path - ARR_HEAD(path_array)); + Init_Path(first, path_array); + + // Invoke DO with the special mode requesting non-evaluation on all + // args, as they were evaluated the first time around. + // + REBIXO indexor = Do_Array_At_Core( + f->out, + first, // path not in array, will be "virtual" first element + code_array, + 0, // index + SPECIFIED, // reusing existing REBVAL arguments, no relative values + DO_FLAG_NO_ARGS_EVALUATE + ); + + if (indexor != THROWN_FLAG && indexor != END_FLAG) { + // + // We may not have stopped the invocation by virtue of the args + // all not getting consumed, but we can raise an error now that it + // did not. + // + fail ("Function frame proxying did not consume all arguments"); + } + + return LOGICAL(indexor == THROWN_FLAG); } -/*********************************************************************** -** -*/ void Secure_Port(REBCNT kind, REBREQ *req, REBVAL *name, REBSER *path) -/* -** kind: word that represents the type (e.g. 'file) -** req: I/O request -** name: value that holds the original user spec -** path: the local path to compare with -** -***********************************************************************/ +// +// Do_Port_Action: C +// +// Call a PORT actor (action) value. Search PORT actor +// first. If not found, search the PORT scheme actor. +// +// NOTE: stack must already be setup correctly for action, and +// the caller must cleanup the stack. +// +REB_R Do_Port_Action(REBFRM *frame_, REBCTX *port, REBSYM action) { - REBYTE *flags; - REBVAL val; - - Set_String(&val, path); - flags = Security_Policy(kind, &val); // policy flags - - // Check policy integer: - // Mask is [xxxx wwww rrrr] - each holds the action - if (GET_FLAG(req->modes, RFM_READ)) Trap_Security(flags[POL_READ], kind, name); - if (GET_FLAG(req->modes, RFM_WRITE)) Trap_Security(flags[POL_WRITE], kind, name); + FAIL_IF_BAD_PORT(port); + + REBVAL *actor = CTX_VAR(port, STD_PORT_ACTOR); + + REB_R r; + + // If actor is a HANDLE!, it should be a PAF + // + // !!! Review how user-defined types could make this better/safer, as if + // it's some other kind of handle value this could crash. + // + if (Is_Native_Port_Actor(actor)) { + r = cast(REBPAF, VAL_HANDLE_CFUNC(actor))(frame_, port, action); + goto post_process_output; + } + + // actor must be an object: + if (!IS_OBJECT(actor)) + fail (Error_Invalid_Actor_Raw()); + + // Dispatch object function: + + REBCNT n; // goto would cross initialization + n = Find_Action(actor, action); + actor = Obj_Value(actor, n); + if (!n || !actor || !IS_FUNCTION(actor)) { + DECLARE_LOCAL (action_word); + Init_Word(action_word, Canon(action)); + + fail (Error_No_Port_Action_Raw(action_word)); + } + + if (Redo_Func_Throws(frame_, VAL_FUNC(actor))) { + // The throw name will be in D_OUT, with thrown value in task vars + return R_OUT_IS_THROWN; + } + + r = R_OUT; // result should be in frame_->out + + // !!! READ's /LINES and /STRING refinements are something that should + // work regardless of data source. But R3-Alpha only implemented it in + // %p-file.c, so it got ignored. Ren-C caught that it was being ignored, + // so the code was moved to here as a quick fix. + // + // !!! Note this code is incorrect for files read in chunks!!! + +post_process_output: + if (action == SYM_READ) { + INCLUDE_PARAMS_OF_READ; + + UNUSED(PAR(source)); + UNUSED(PAR(part)); + UNUSED(PAR(limit)); + UNUSED(PAR(seek)); + UNUSED(PAR(index)); + + assert(r == R_OUT); + + if ((REF(string) || REF(lines)) && !IS_STRING(D_OUT)) { + if (NOT(IS_BINARY(D_OUT))) + fail ("/STRING or /LINES used on a non-BINARY!/STRING! read"); + + REBSER *decoded = Decode_UTF_String( + VAL_BIN_AT(D_OUT), + VAL_LEN_AT(D_OUT), + -1 + ); + if (decoded == NULL) + fail (Error_Bad_Utf8_Raw()); + Init_String(D_OUT, decoded); + } + + if (REF(lines)) { // caller wants a BLOCK! of STRING!s, not one string + assert(IS_STRING(D_OUT)); + + DECLARE_LOCAL (temp); + Move_Value(temp, D_OUT); + Init_Block(D_OUT, Split_Lines(temp)); + } + } + + return r; } -/*********************************************************************** -** -*/ void Validate_Port(REBSER *port, REBCNT action) -/* -** Because port actors are exposed to the user level, we must -** prevent them from being called with invalid values. -** -***********************************************************************/ +// +// Secure_Port: C +// +// kind: word that represents the type (e.g. 'file) +// req: I/O request +// name: value that holds the original user spec +// path: the local path to compare with +// +void Secure_Port(REBSYM sym_kind, REBREQ *req, REBVAL *name, REBSER *path) { - if ( - action >= A_MAX_ACTION - || port->tail > 50 - || SERIES_WIDE(port) != sizeof(REBVAL) - || !IS_FRAME(BLK_HEAD(port)) - || !IS_OBJECT(BLK_SKIP(port, STD_PORT_SPEC)) - ) - Trap0(RE_INVALID_PORT); -} + DECLARE_LOCAL (val); + Init_String(val, path); -/*********************************************************************** -** -** Scheme Native Action Support -** -** This array is used to associate a scheme word with its -** native action functions. -** -** Each native port scheme must be listed here. This list is -** created by each native scheme calling Register_Scheme() -** during initialization. -** -** Example of defining actions: -** -** static const PORT_ACTION File_Actions[] = { -** A_OPEN, P_open, -** A_CLOSE, P_close, -** 0, 0 -** } -** -** Register_Scheme(SYM_FILE, &File_Actions[0], 0); -** -** -***********************************************************************/ - -#define MAX_SCHEMES 10 // max native schemes - -typedef struct rebol_scheme_actions { - REBCNT sym; - const PORT_ACTION *map; - REBPAF fun; -} SCHEME_ACTIONS; - -SCHEME_ACTIONS *Scheme_Actions; // Initial Global (not threaded) - - -/*********************************************************************** -** -*/ void Register_Scheme(REBCNT sym, const PORT_ACTION *map, REBPAF fun) -/* -** Associate a scheme word (e.g. FILE) with a set of native -** scheme actions. This will be used by the Set_Scheme native -** -***********************************************************************/ -{ - REBINT n; + REBYTE *flags = Security_Policy(Canon(sym_kind), val); // policy flags - for (n = 0; n < MAX_SCHEMES && Scheme_Actions[n].sym; n++); - ASSERT2(n < MAX_SCHEMES, RP_MAX_SCHEMES); + // Check policy integer: + // Mask is [xxxx wwww rrrr] - each holds the action + if (GET_FLAG(req->modes, RFM_READ)) + Trap_Security(flags[POL_READ], Canon(sym_kind), name); - Scheme_Actions[n].sym = sym; - Scheme_Actions[n].map = map; - Scheme_Actions[n].fun = fun; -} - - -/*********************************************************************** -** -*/ REBNATIVE(set_scheme) -/* -***********************************************************************/ -{ - REBVAL *scheme; - REBVAL *actor; - REBVAL *func; - REBVAL *act; - REBCNT n; - const PORT_ACTION *map = 0; - - scheme = D_ARG(1); - - act = Obj_Value(scheme, STD_SCHEME_NAME); - if (!IS_WORD(act)) return R_NONE; - actor = Obj_Value(scheme, STD_SCHEME_ACTOR); - if (!actor) return R_NONE; - - // Does this scheme have native actor or actions? - for (n = 0; Scheme_Actions[n].sym; n++) { - if (Scheme_Actions[n].sym == VAL_WORD_SYM(act)) break; - } - if (!Scheme_Actions[n].sym) return R_NONE; - - // The scheme uses a native actor: - if (Scheme_Actions[n].fun) { - //Make_Native(actor, Make_Block(0), (REBFUN)(Scheme_Actions[n].fun), REB_NATIVE); - // Hand build a native function that will be used to reach native scheme actors. - REBSER *ser = Make_Block(1); - act = Append_Value(ser); - Init_Word(act, REB_PORT+1); // any word will do - VAL_TYPESET(act) = TYPESET(REB_END); // don't let it get called normally - VAL_FUNC_SPEC(actor) = ser; - VAL_FUNC_ARGS(actor) = ser; - VAL_FUNC_CODE(actor) = (REBFUN)(Scheme_Actions[n].fun); - VAL_SET(actor, REB_NATIVE); - return R_TRUE; - } - - // The scheme has an array of action natives: - if (!IS_OBJECT(actor)) return R_NONE; - - // Map action natives to scheme actor words: - for (; map->func; map++) { - // Find the action in the scheme actor: - n = Find_Action(actor, map->action); - if (n) { - // Get standard action's spec block: - act = Get_Action_Value(map->action); - - // Make native function for action: - func = Obj_Value(actor, n); // function - Make_Native(func, VAL_FUNC_SPEC(act), (REBFUN)(map->func), REB_NATIVE); - } - } - return R_TRUE; + if (GET_FLAG(req->modes, RFM_WRITE)) + Trap_Security(flags[POL_WRITE], Canon(sym_kind), name); } -/*********************************************************************** -** -*/ void Init_Ports(void) -/* -** Initialize port scheme related subsystems. -** -** In order to add a port scheme: -** -** In mezz-ports.r add a make-scheme. -** Add an Init_*_Scheme() here. -** Be sure host-devices.c has the device enabled. -** -***********************************************************************/ +// +// Make_Port_Actor_Handle: C +// +// When users write a "port scheme", they provide an actor...which contains +// a block of functions with the names of the "verbs" that can be applied to +// ports. When the name of a port action matches the name of a supplied +// function, then the matching function is called. Each of these functions +// may have different numbers and types of arguments and refinements. +// +// R3-Alpha provided some native code to handle port actions, but all the +// port actions were folded into a single function that was able to interpret +// different function frames. This was similar to how datatypes handled +// various "action" verbs. +// +// In Ren-C, this distinction is taken care of such that when the actor is +// a HANDLE!, it is assumed to be a pointer to a "REBPAF". But since the +// registration is done in user code, these handles have to be exposed to +// that code. In order to make this more distributed, each port action +// function is exposed through a native that returns it. This is the shared +// routine used to make a handle out of a REBPAF. +// +void Make_Port_Actor_Handle(REBVAL *out, REBPAF paf) { - Scheme_Actions = Make_Mem(sizeof(SCHEME_ACTIONS) * MAX_SCHEMES); - - Init_Console_Scheme(); - Init_File_Scheme(); - Init_Dir_Scheme(); - Init_Event_Scheme(); - Init_TCP_Scheme(); - Init_DNS_Scheme(); -#ifndef MIN_OS - Init_Clipboard_Scheme(); -#endif + Init_Handle_Cfunc(out, cast(CFUNC*, paf), 0); } diff --git a/src/core/c-signal.c b/src/core/c-signal.c new file mode 100644 index 0000000000..04f2b9b161 --- /dev/null +++ b/src/core/c-signal.c @@ -0,0 +1,168 @@ +// +// File: %c-signal.c +// Summary: "Evaluator Interrupt Signal Handling" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// "Signal" refers to special events to process periodically during +// evaluation. Search for SET_SIGNAL to find them. +// +// (Note: Not to be confused with SIGINT and unix "signals", although on +// unix an evaluator signal can be triggered by a unix signal.) +// +// Note in signal dispatch that R3-Alpha did not have a policy articulated on +// dealing with the interrupt nature of the SIGINT signals sent by Ctrl-C: +// +// https://en.wikipedia.org/wiki/Unix_signal +// +// Guarding against errors being longjmp'd when an evaluation is in effect +// isn't the only time these signals are processed. Rebol's Process_Signals +// currently happens during I/O, such as printing output. As a consequence, +// a Ctrl-C can be picked up and then triggered during an Out_Value, jumping +// the stack from there. +// +// This means a top-level trap must always be in effect, even though no eval +// is running. This trap's job is to handle errors that happen *while +// reporting another error*, with Ctrl-C triggering a HALT being the most +// likely example if not running an evaluation (though any fail() could +// cause it) +// + +#include "sys-core.h" + + +// +// Do_Signals_Throws: C +// +// !!! R3-Alpha's evaluator loop had a countdown (Eval_Count) which was +// decremented on every step. When this counter reached zero, it would call +// this routine to process any "signals"...which could be requests for +// garbage collection, network-related, Ctrl-C being hit, etc. +// +// It also would check the Eval_Signals mask to see if it was non-zero on +// every step. If it was, then it would always call this routine--regardless +// of the Eval_Count. +// +// While a broader review of how signals would work in Ren-C is pending, it +// seems best to avoid checking two things each step. So only the Eval_Count +// is checked, and places that set Eval_Signals set it to 1...to have the +// same effect as if it were being checked. Then if the Eval_Signals are +// not cleared by the end of this routine, it resets the Eval_Count to 1 +// rather than giving it the full EVAL_DOSE of counts until next call. +// +// Currently the ability of a signal to THROW comes from the processing of +// breakpoints. The RESUME instruction is able to execute code with /DO, +// and that code may escape from a debug interrupt signal (like Ctrl-C). +// +REBOOL Do_Signals_Throws(REBVAL *out) +{ + assert(IS_END(out)); // incoming must be END, will be END if no throw + + // !!! When it was the case that the only way Do_Signals_Throws would run + // due to the Eval_Count reaching the end of an Eval_Dose, this way of + // doing "CPU quota" would work. Currently, however, it is inaccurate, + // due to the fact that Do_Signals_Throws can be queued to run by setting + // the Eval_Count to 1 for a specific signal. Review. + // + Eval_Cycles += Eval_Dose - Eval_Count; + if (Eval_Limit != 0 && Eval_Cycles > Eval_Limit) + Check_Security(Canon(SYM_EVAL), POL_EXEC, 0); + + Eval_Count = Eval_Dose; + + REBOOL thrown = FALSE; + + // The signal mask allows the system to disable processing of some + // signals. It defaults to ALL_BITS, but during signal processing + // itself, the mask is set to 0 to avoid recursion. + // + // !!! This seems overdesigned considering SIG_EVENT_PORT isn't used. + // + REBCNT filtered_sigs = Eval_Signals & Eval_Sigmask; + REBCNT saved_mask = Eval_Sigmask; + Eval_Sigmask = 0; + + // "Be careful of signal loops! EG: do not PRINT from here." + + if (GET_FLAG(filtered_sigs, SIG_RECYCLE)) { + CLR_SIGNAL(SIG_RECYCLE); + Recycle(); + } + +#ifdef NOT_USED_INVESTIGATE + if (GET_FLAG(filtered_sigs, SIG_EVENT_PORT)) { // !!! Why not used? + CLR_SIGNAL(SIG_EVENT_PORT); + Awake_Event_Port(); + } +#endif + + if (GET_FLAG(filtered_sigs, SIG_HALT)) { + // + // Early in the booting process, it's not possible to handle Ctrl-C + // because the error machinery has not been initialized. There must + // be at least one PUSH_UNHALTABLE_TRAP() before fail() can work. + // + if (Saved_State == NULL) + panic ("Ctrl-C or other HALT signal with no trap to process it"); + + CLR_SIGNAL(SIG_HALT); + Eval_Sigmask = saved_mask; + + fail (VAL_CONTEXT(TASK_HALT_ERROR)); + } + + if (GET_FLAG(filtered_sigs, SIG_INTERRUPT)) { + // + // Similar to the Ctrl-C halting, the "breakpoint" interrupt request + // can't be processed early on. The throw mechanics should panic + // all right, but it might make more sense to wait. + // + CLR_SIGNAL(SIG_INTERRUPT); + + // !!! This can recurse, which may or may not be a bad thing. But + // if the garbage collector and such are going to run during this + // execution, the signal mask has to be turned back on. Review. + // + Eval_Sigmask = saved_mask; + if (Do_Breakpoint_Throws(out, TRUE, VOID_CELL, FALSE)) + return TRUE; + + // !!! What to do with something like a Ctrl-C-based breakpoint + // session that does something like `resume/with 10`? This gets + // called "in-between" evaluations, so that 10 really has no meaning + // and is just going to get discarded. FAIL for now to alert the + // user that something is off, but perhaps the failure should be + // contained in a sandbox and restart the break? + // + if (NOT(IS_VOID(out))) + fail ("Interrupt-based debug session used RESUME/WITH"); + + SET_END(out); + return FALSE; + } + + Eval_Sigmask = saved_mask; + return thrown; +} diff --git a/src/core/c-task.c b/src/core/c-task.c deleted file mode 100644 index 2f7f2d0440..0000000000 --- a/src/core/c-task.c +++ /dev/null @@ -1,88 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: c-task.c -** Summary: sub-task support -** Section: core -** Author: Carl Sassenrath -** Notes: INCOMPLETE IMPLEMENTATION (partially operational) -** -***********************************************************************/ - -/* - Making a Task: - - 1. Local copies of: - Main globals - For data stack - Interpreter flags - Memory management - Root series (all or part?) - Data stack - System object (for module) - C stack (thread provided) - - 2. Share copies of: - Boot strings and values - System functions (natives and mezzanine) - Word table - Various sub-objects of system object - - Task Spec is a module definition. Needs new context. - - Questions: - System object is already copied for local user context - System blocks might hold references to local series (how to GC) - Can system values (objects and functions) be modified by other - tasks? How are they protected? Is it good enough that our local - references to functions refer to the older ones? How can we - "update" our references? -*/ - -#include "sys-core.h" - -/*********************************************************************** -** -*/ static void Launch_Task(REBVAL *task) -/* -***********************************************************************/ -{ - REBSER *body; - - Debug_Str("Begin Task"); - - Init_Task(); - body = Clone_Block(VAL_MOD_BODY(task)); - OS_TASK_READY(0); - Do_Blk(body, 0); - - Debug_Str("End Task"); -} - - -/*********************************************************************** -** -*/ void Do_Task(REBVAL *task) -/* -***********************************************************************/ -{ - OS_CREATE_THREAD((void*)Launch_Task, task, 50000); -} diff --git a/src/core/c-value.c b/src/core/c-value.c new file mode 100644 index 0000000000..165fdf3c8e --- /dev/null +++ b/src/core/c-value.c @@ -0,0 +1,237 @@ +// +// File: %c-value.c +// Summary: "Generic REBVAL Support Services and Debug Routines" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2016 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// These are mostly DEBUG-build routines to support the macros and definitions +// in %sys-value.h. +// +// These are not specific to any given type. For the type-specific REBVAL +// code, see files with names like %t-word.c, %t-logic.c, %t-integer.c... +// + +#include "sys-core.h" + + +#if !defined(NDEBUG) + +// +// Panic_Value_Debug: C +// +// This is a debug-only "error generator", which will hunt through all the +// series allocations and panic on the series that contains the value (if +// it can find it). This will allow those using Address Sanitizer or +// Valgrind to know a bit more about where the value came from. +// +// Additionally, if it happens to be a void or trash, LOGIC!, BAR!, or NONE! +// it will dump out where the initialization happened if that information +// was stored. +// +ATTRIBUTE_NO_RETURN void Panic_Value_Debug(const RELVAL *v) { + fflush(stdout); + fflush(stderr); + + REBSER *containing = Try_Find_Containing_Series_Debug(v); + + switch (VAL_TYPE_RAW(v)) { + case REB_MAX_VOID: + case REB_BLANK: + case REB_LOGIC: + case REB_BAR: + printf( + "REBVAL init on tick #%d at %s:%d\n", + cast(unsigned int, v->extra.do_count), + v->payload.track.filename, + v->payload.track.line + ); + fflush(stdout); + break; + + default: + break; + } + + printf("Kind=%d\n", cast(int, VAL_TYPE_RAW(v))); + fflush(stdout); + + if (containing != NULL) { + printf("Containing series for value pointer found, panicking it:\n"); + Panic_Series_Debug(containing); + } + + printf("No containing series for value...panicking to make stack dump:\n"); + Panic_Series_Debug(SER(EMPTY_ARRAY)); +} + + +// +// VAL_SPECIFIC_Debug: C +// +REBCTX *VAL_SPECIFIC_Debug(const REBVAL *v) +{ + assert(NOT_VAL_FLAG(v, VALUE_FLAG_RELATIVE)); + assert( + ANY_WORD(v) + || ANY_ARRAY(v) + || IS_VARARGS(v) + || IS_FUNCTION(v) + || ANY_CONTEXT(v) + ); + + REBCTX *specific = VAL_SPECIFIC_COMMON(v); + + if (specific != SPECIFIED) { + // + // Basic sanity check: make sure it's a context at all + // + if (NOT_SER_FLAG(CTX_VARLIST(specific), ARRAY_FLAG_VARLIST)) { + printf("Non-CONTEXT found as specifier in specific value\n"); + panic (specific); // may not be a series, either + } + + // While an ANY-WORD! can be bound specifically to an arbitrary + // object, an ANY-ARRAY! only becomes bound specifically to frames. + // The keylist for a frame's context should come from a function's + // paramlist, which should have a FUNCTION! value in keylist[0] + // + if (ANY_ARRAY(v)) + assert(IS_FUNCTION(CTX_ROOTKEY(specific))); + } + + return specific; +} + + +// +// Assert_No_Relative: C +// +// Check to make sure there are no relative values in an array, maybe deeply. +// +// !!! What if you have an ANY-ARRAY! inside your array at a position N, +// but there is a relative value in the VAL_ARRAY() of that value at an +// index earlier than N? This currently considers that an error since it +// checks the whole array...which is more conservative (asserts on more +// cases). But should there be a flag to ask to honor the index? +// +void Assert_No_Relative(REBARR *array, REBOOL deep) +{ + RELVAL *item = ARR_HEAD(array); + while (NOT_END(item)) { + if (IS_RELATIVE(item)) { + printf("Array contained relative item and wasn't supposed to\n"); + panic (item); + } + if (!IS_UNREADABLE_IF_DEBUG(item) && ANY_ARRAY(item) && deep) + Assert_No_Relative(VAL_ARRAY(item), deep); + ++item; + } +} + + +// +// Probe_Core_Debug: C +// +void Probe_Core_Debug( + const void *p, + const char *file, + int line +) { + const struct Reb_Header *h = cast(const struct Reb_Header*, p); + + printf("\n** PROBE() "); + printf("tick %d %s:%d\n", cast(int, TG_Do_Count), file, line); + + fflush(stdout); + fflush(stderr); + + if (h->bits & NODE_FLAG_CELL) + Debug_Fmt("%r\n", cast(const REBVAL*, p)); + else { + REBSER *s = m_cast(REBSER*, cast(const REBSER*, p)); + + // Invalid series would possibly (but not necessarily) crash the print + // routines--which are the same ones used to output a series normally. + // Hence don't attempt to print a known malformed series. A more + // pointed message will probably come from ASSERT_SERIES, saying + // what is wrong rather than just crashing the print code... + // + ASSERT_SERIES(s); + + if (GET_SER_FLAG(s, ARRAY_FLAG_VARLIST)) { + REBCTX *c = CTX(s); + + // Don't use Init_Any_Context, because that can implicitly manage + // the context...which we don't want a debug dump routine to do. + // + DECLARE_LOCAL (temp); + VAL_RESET_HEADER(temp, CTX_TYPE(c)); + temp->extra.binding = NULL; + temp->payload.any_context.varlist = CTX_VARLIST(c); + temp->payload.any_context.phase = NULL; + Debug_Fmt("%r\n", temp); + } + else { + REBOOL disabled = GC_Disabled; + GC_Disabled = TRUE; + + // This routine is also a little catalog of the outlying series + // types in terms of sizing, just to know what they are. + + if (BYTE_SIZE(s)) + Debug_Str(s_cast(BIN_HEAD(s))); + else if (GET_SER_FLAG(s, SERIES_FLAG_ARRAY)) { + // + // May not actually be a REB_BLOCK, but we put it in a value + // container for now saying it is so we can output it. May + // not want to Manage_Series here, so we use a raw + // initialization instead of Init_Block. + // + DECLARE_LOCAL (value); + VAL_RESET_HEADER(value, REB_BLOCK); + INIT_VAL_ARRAY(value, ARR(s)); + VAL_INDEX(value) = 0; + + Debug_Fmt("%r", value); + } + else if (SER_WIDE(s) == sizeof(REBUNI)) + Debug_Uni(s); + else if (s == PG_Canons_By_Hash) { + printf("can't probe PG_Canons_By_Hash\n"); + panic (s); + } + else if (s == GC_Guarded) { + printf("can't probe GC_Guarded\n"); + panic (s); + } + else + panic (s); + + assert(GC_Disabled == TRUE); + GC_Disabled = disabled; + } + } +} + +#endif diff --git a/src/core/c-word.c b/src/core/c-word.c old mode 100644 new mode 100755 index 7bbbc1a8d8..864e7188d1 --- a/src/core/c-word.c +++ b/src/core/c-word.c @@ -1,423 +1,641 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: c-word.c -** Summary: symbol table and word related functions -** Section: core -** Author: Carl Sassenrath -** Notes: -** Word table is a block composed of symbols, each of which contain -** a canon word number, alias word number (if it exists), and an -** index that refers to the string for the text itself. -** -** The canon number for a word is unique and is used to compare -** words. The word table is independent of context frames and -** words are never garbage collected. -** -** The alias is used mainly for upper and lower case equality, -** but can also be used to create ALIASes. -** -** The word strings are stored as a single large string series. -** NEVER CACHE A WORD NAME POINTER if new words may be added (e.g. -** LOAD), because the series may get moved in memory. -** -***********************************************************************/ +// +// File: %c-word.c +// Summary: "symbol table and word related functions" +// Section: core +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// In R3-Alpha, words were not garbage collected, and their UTF-8 data was +// kept in a separate table from the REBSERs. In Ren-C, words use REBSERs, +// and are merely *indexed* by hashes of their canon forms via an external +// table. This table grows and shrinks as canons are added and removed. +// #include "sys-core.h" -#include #define WORD_TABLE_SIZE 1024 // initial size in words -/*********************************************************************** -** -*/ static REBCNT const Primes[] = -/* -** Prime numbers used for hash table sizes. Divide by 2 for -** number of words that can be held in the symbol table. -** -***********************************************************************/ +// +// Prime numbers used for hash table sizes. Divide by 2 for +// number of words that can be held in the symbol table. +// +static REBCNT const Primes[] = { - 251, - 509, - 1021, - 2039, - 4093, - 8191, - 16381, - 32749, - 65521, - 131071, - 262139, - 524287, - 1048573, - 2097143, - 4194301, - 8388593, - 16777213, - 33554393, - 67108859, - 134217689, - 268435399, - 0 + 7, + 13, + 31, + 61, + 127, + 251, + 509, + 1021, + 2039, + 4093, + 8191, + 16381, + 32749, + 65521, + 131071, + 262139, + 524287, + 1048573, + 2097143, + 4194301, + 8388593, + 16777213, + 33554393, + 67108859, + 134217689, + 268435399, + 536870909, + 1073741789, + 2147483647, + 0xFFFFFFFB, // 4294967291 = 2^32 - 5 (C89) + 0 +// see https://primes.utm.edu/lists/2small/0bit.html }; -/*********************************************************************** -** -*/ REBINT Get_Hash_Prime(REBCNT size) -/* -** Given a size, return a prime number that is larger. -** -***********************************************************************/ +// +// Get_Hash_Prime: C +// +// Given a size, return a prime number that is larger. +// +REBINT Get_Hash_Prime(REBCNT size) { - REBINT n; + REBINT n; - for (n = 0; Primes[n] && size > Primes[n]; n++); + for (n = 0; Primes[n] && size > Primes[n]; n++); - if (!Primes[n]) return 0; + if (!Primes[n]) return 0; - return Primes[n]; + return Primes[n]; } - -/*********************************************************************** -** -*/ void Expand_Hash(REBSER *ser) -/* -** Expand hash series. Clear it but set its tail. -** -***********************************************************************/ -{ - REBSER oser; - REBSER *nser; - REBINT pnum; - - pnum = Get_Hash_Prime(ser->tail+1); - if (!pnum) Trap_Num(RE_SIZE_LIMIT, ser->tail+1); - - nser = Make_Series(pnum+1, sizeof(REBCNT), TRUE); - LABEL_SERIES(nser, "hash series"); - oser = *ser; - *ser = *nser; - ser->info = oser.info; - *nser = oser; - - Clear_Series(ser); - ser->tail = pnum; - - Free_Series(nser); -} - - -/*********************************************************************** -** -*/ static void Expand_Word_Table(void) -/* -** Expand the hash table part of the word_table by allocating -** the next larger table size and rehashing all the words of -** the current table. Free the old hash array. -** -***********************************************************************/ +// Removals from linear probing lists can be complex, because the same +// overflow slot may be visited through different initial hashes: +// +// http://stackoverflow.com/a/279812/211160 +// +// Since it's not enough to simply NULL out the spot when an interned string +// is GC'd, a special pointer signaling "deletedness" is used. It does not +// cause a linear probe to terminate, but it is reused on insertions. +// +static REBSTR PG_Deleted_Canon; +#define DELETED_CANON &PG_Deleted_Canon + + +// +// Expand_Word_Table: C +// +// Expand the hash table part of the word_table by allocating +// the next larger table size and rehashing all the words of +// the current table. Free the old hash array. +// +static void Expand_Word_Table(void) { - REBCNT *hashes; - REBVAL *word; - REBINT hash; - REBCNT size; - REBINT skip; - REBCNT n; - - // Allocate a new hash table: - Expand_Hash(PG_Word_Table.hashes); - // Debug_Fmt("WORD-TABLE: expanded (%d symbols, %d slots)", PG_Word_Table.series->tail, PG_Word_Table.hashes->tail); - - // Rehash all the symbols: - word = BLK_SKIP(PG_Word_Table.series, 1); - hashes = (REBCNT *)PG_Word_Table.hashes->data; - size = PG_Word_Table.hashes->tail; - for (n = 1; n < PG_Word_Table.series->tail; n++, word++) { - hash = Hash_Word(VAL_SYM_NAME(word), -1); - skip = (hash & 0x0000FFFF) % size; - if (skip == 0) skip = 1; - hash = (hash & 0x00FFFF00) % size; - while (hashes[hash]) { - hash += skip; - if (hash >= (REBINT)size) hash -= size; - } - hashes[hash] = n; - } + // The only full list of canon words available is the old hash table. + // Hold onto it while creating the new hash table. + + REBCNT old_size = SER_LEN(PG_Canons_By_Hash); + REBSTR* *old_canons_by_hash = SER_HEAD(REBSER*, PG_Canons_By_Hash); + + REBCNT new_size = Get_Hash_Prime(old_size + 1); + if (new_size == 0) { + DECLARE_LOCAL (temp); + Init_Integer(temp, old_size + 1); + fail (Error_Size_Limit_Raw(temp)); + } + + assert(SER_WIDE(PG_Canons_By_Hash) == sizeof(REBSTR*)); + + REBSER *ser = Make_Series_Core( + new_size, sizeof(REBSTR*), SERIES_FLAG_POWER_OF_2 + ); + Clear_Series(ser); + SET_SERIES_LEN(ser, new_size); + + // Rehash all the symbols: + + REBSTR **new_canons_by_hash = SER_HEAD(REBSER*, ser); + + REBCNT n; + for (n = 0; n < old_size; ++n) { + REBSTR *canon = old_canons_by_hash[n]; + + if (canon == NULL) continue; + + if (canon == DELETED_CANON) { // clean out any deleted canon entries + --PG_Num_Canon_Slots_In_Use; + #if !defined(NDEBUG) + --PG_Num_Canon_Deleteds; // keep track for shutdown assert + #endif + continue; + } + + REBINT hash = Hash_Word(STR_HEAD(canon), STR_NUM_BYTES(canon)); + REBINT skip = (hash & 0x0000FFFF) % new_size; + if (skip == 0) skip = 1; + hash = (hash & 0x00FFFF00) % new_size; + + while (new_canons_by_hash[hash] != NULL) { + hash += skip; + if (hash >= cast(REBINT, new_size)) + hash -= new_size; + } + new_canons_by_hash[hash] = canon; + } + + Free_Series(PG_Canons_By_Hash); + PG_Canons_By_Hash = ser; } -/*********************************************************************** -** -*/ static REBCNT Make_Word_Name(REBYTE *str, REBCNT len) -/* -** Allocates and copies the text string of the word. -** -***********************************************************************/ +// +// Intern_UTF8_Managed: C +// +// This will "intern" a UTF-8 string, which is to store only one copy of each +// distinct string value: +// +// https://en.wikipedia.org/wiki/String_interning +// +// The interning is case-sensitive. But a relationship is set up between +// instances that are just differently upper-or-lower-"cased". This allows +// those instances to agree on a single "canon" interning that can be used for +// fast comparison between them. +// +// Interned UTF8 strings are stored as series, and are implicitly managed +// by the GC (because they are shared). Individual synonyms can be GC'd, +// including canon forms--in which case the agreed-upon canon for the +// group will get bumped to one of the other synonyms. +// +REBSTR *Intern_UTF8_Managed(const REBYTE *utf8, REBCNT len) { - REBCNT pos = SERIES_TAIL(PG_Word_Names); - - Append_Mem_Extra(PG_Word_Names, str, len, 1); // so we can do next line... - PG_Word_Names->tail++; // keep terminator for each string - return pos; + // The hashing technique used is called "linear probing": + // + // https://en.wikipedia.org/wiki/Linear_probing + // + // For the hash search to be guaranteed to terminate, the table must be + // large enough that we are able to find a NULL if there's a miss. (It's + // actually kept larger than that, but to be on the right side of theory, + // the table is always checked for expansion needs *before* the search.) + // + REBCNT size = SER_LEN(PG_Canons_By_Hash); + if (PG_Num_Canon_Slots_In_Use > size / 2) { + Expand_Word_Table(); + size = SER_LEN(PG_Canons_By_Hash); // got larger + } + + REBSTR* *canons_by_hash = SER_HEAD(REBSER*, PG_Canons_By_Hash); + + // Calculate the starting hash slot to try--and the amount to skip to by + // each time a slot is found that is occupied by a non-match. + // + REBCNT hash = Hash_Word(utf8, len); + REBCNT skip = (hash & 0x0000FFFF) % size; + if (skip == 0) + skip = 1; + hash = (hash & 0x00FFFF00) % size; + + REBSTR **deleted_slot = NULL; + + // The hash table only indexes the canon form of each spelling. So when + // testing a slot to see if it's a match (or a collision that needs to + // be skipped to try again) the search uses a comparison that is + // case-insensitive...and returns a value > 0 for a match. + // + // However, the result also indicates whether it was an *exact* match, by + // returning 0 if it is. + // + REBSTR* canon; + while ((canon = canons_by_hash[hash]) != NULL) { + if (canon == DELETED_CANON) { + deleted_slot = &canons_by_hash[hash]; + hash += skip; + if (hash >= size) hash -= size; + continue; + } + + assert(GET_SER_INFO(canon, STRING_INFO_CANON)); + + // Compare_UTF8 returns 0 when the spelling is a case-sensitive match, + // and is the exact interning to return. + // + REBINT cmp = Compare_UTF8(STR_HEAD(canon), utf8, len); + if (cmp == 0) return canon; + + if (cmp < 0) { + // + // Compare_UTF8 returns less than zero when the canon value in the + // slot isn't the same at all. Since it's not a match, skip ahead + // to the next candidate slot--wrapping around if necessary + // + hash += skip; + if (hash >= size) hash -= size; + continue; + } + + // The > 0 result means that the canon word that was found is an + // alternate casing ("synonym") for the string we're interning. The + // synonyms are attached to the canon form with a circularly linked + // list. Walk the list to see if any of the synonyms are a match. + // + REBSTR *synonym = canon->link.synonym; + while (synonym != canon) { + assert(synonym->misc.canon == canon); + assert(NOT_SER_INFO(synonym, STRING_INFO_CANON)); + + // Exact match for a synonym also means no new allocation needed. + // + cmp = Compare_UTF8(STR_HEAD(synonym), utf8, len); + if (cmp == 0) return synonym; + + // Comparison should at least be a synonym, if in this list. + // Keep checking for an exact match until a cycle is found. + // + assert(cmp > 0); + synonym = synonym->link.synonym; + } + + // If none of the synonyms matched, then this case variation needs + // to get its own interning, and point to the canon found. + + assert(canon != NULL); + goto new_interning; // break loop, make a new synonym + } + + // normal loop fallthrough at canon == NULL - make a new canon form + assert(canon == NULL); + +new_interning: ; // semicolon needed for statement + + // If possible, the allocation should be fit into a REBSER node with no + // separate allocation. Because automatically doing this is a new + // feature, double check with an assert that the behavior matches. + // + REBSTR *intern = Make_Series_Core( + len + 1, + sizeof(REBYTE), + SERIES_FLAG_UTF8_STRING | SERIES_FLAG_FIXED_SIZE + ); + +#if !defined(NDEBUG) + if (len + 1 > sizeof(intern->content)) + assert(GET_SER_INFO(intern, SERIES_INFO_HAS_DYNAMIC)); + else + assert(NOT_SER_INFO(intern, SERIES_INFO_HAS_DYNAMIC)); +#endif + + // The incoming string isn't always null terminated, e.g. if you are + // interning `foo` in `foo: bar + 1` it would be colon-terminated. + // + memcpy(BIN_HEAD(intern), utf8, len); + TERM_SEQUENCE_LEN(intern, len); + + if (canon == NULL) { + // + // There was no canon symbol found, so this interning will be canon. + // Add it to the hash table and mark it, reuse deleted slot (if any) + // + if (deleted_slot) { + *deleted_slot = intern; // slot "usage" count stays constant + + #if !defined(NDEBUG) + --PG_Num_Canon_Deleteds; + #endif + } + else { + canons_by_hash[hash] = intern; + ++PG_Num_Canon_Slots_In_Use; + } + + SET_SER_INFO(intern, STRING_INFO_CANON); + + intern->link.synonym = intern; // circularly linked list, empty state + + // Canon symbols don't need to cache a canon pointer to themselves. + // So instead that slot is reserved for tracking associated information + // for the canon word, e.g. the current bind index. Because this + // may be used by several threads, it would likely have to be an + // atomic pointer that would "pop out" to a structure, but for now + // it is just randomized to keep its information in high bits or low + // bits as a poor-man's demo that there is an infrastructure in place + // for sharing (start with 2, grow to N based on the functions for + // 2 being in place) + // + intern->misc.bind_index.high = 0; + intern->misc.bind_index.low = 0; + + // leave header.bits as 0 for SYM_0 as answer to VAL_WORD_SYM() + // Startup_Symbols() tags values from %words.r after the fact. + } + else { + // This is a synonym for an existing canon. Link it into the synonyms + // circularly linked list, and direct link the canon form. + // + intern->misc.canon = canon; + intern->link.synonym = canon->link.synonym; + canon->link.synonym = intern; + + // If the canon form had a SYM_XXX for quick comparison of %words.r + // words in C switch statements, the synonym inherits that number. + // + assert(RIGHT_16_BITS(intern->header.bits) == 0); + intern->header.bits |= FLAGUINT16_RIGHT(STR_SYMBOL(canon)); + } + +#if !defined(NDEBUG) + REBUPT sym_canon = cast(REBUPT, STR_SYMBOL(STR_CANON(intern))); + REBUPT sym = cast(REBUPT, STR_SYMBOL(intern)); + assert(sym == sym_canon); +#endif + + // Created series must be managed, because if they were not there could + // be no clear contract on the return result--as it wouldn't be possible + // to know if a shared instance had been managed by someone else or not. + // + MANAGE_SERIES(intern); + assert(LEFT_N_BITS(intern->header.bits, 4) != 0); + return intern; } -/*********************************************************************** -** -*/ REBCNT Make_Word(REBYTE *str, REBCNT len) -/* -** Given a string and its length, compute its hash value, -** search for a match, and if not found, add it to the table. -** Length of zero indicates you provided a zero terminated string. -** Return the table index for the word (whether found or new). -** -***********************************************************************/ +// +// GC_Kill_Interning: C +// +// Unlink this spelling out of the circularly linked list of synonyms. +// Further, if it happens to be canon, we need to re-point everything in the +// chain to a new entry. Choose the synonym as a new canon if so. +// +void GC_Kill_Interning(REBSTR *intern) { - REBINT hash; - REBINT size; - REBINT skip; - REBINT n; - REBCNT h; - REBCNT *hashes; - REBVAL *words; - REBVAL *w; - - //REBYTE *sss = Get_Sym_Name(1); // (Debugging method) - - if (len == 0) len = LEN_BYTES(str); - - // If hash part of word table is too dense, expand it: - if (PG_Word_Table.series->tail > PG_Word_Table.hashes->tail/2) - Expand_Word_Table(); - - ASSERT((SERIES_TAIL(PG_Word_Table.series) == SERIES_TAIL(Bind_Table)), RP_BIND_TABLE_SIZE); - - // If word symbol part of word table is full, expand it: - if (SERIES_FULL(PG_Word_Table.series)) { - Extend_Series(PG_Word_Table.series, 256); - } - if (SERIES_FULL(Bind_Table)) { - Extend_Series(Bind_Table, 256); - CLEAR_SERIES(Bind_Table); - } - - size = (REBINT)PG_Word_Table.hashes->tail; - words = BLK_HEAD(PG_Word_Table.series); - hashes = (REBCNT *)PG_Word_Table.hashes->data; - - // Hash the word, including a skip factor for lookup: - hash = Hash_Word(str, len); - skip = (hash & 0x0000FFFF) % size; - if (skip == 0) skip = 1; - hash = (hash & 0x00FFFF00) % size; - //Debug_Fmt("%s hash %d skip %d", str, hash, skip); - - // Search hash table for word match: - while (NZ(h = hashes[hash])) { - while ((n = Compare_UTF8(VAL_SYM_NAME(words+h), str, len)) >= 0) { - //if (Match_String("script", str, len)) - // Debug_Fmt("---- %s %d %d\n", VAL_SYM_NAME(&words[h]), n, h); - if (n == 0) return h; // direct hit - if (VAL_SYM_ALIAS(words+h)) h = VAL_SYM_ALIAS(words+h); - else goto make_sym; // Create new alias for word - } - hash += skip; - if (hash >= size) hash -= size; - } - -make_sym: - n = PG_Word_Table.series->tail; - w = words + n; - if (h) { - // Alias word (h = canon word) - VAL_SYM_ALIAS(words+h) = n; - VAL_SYM_CANON(w) = VAL_SYM_CANON(words+h); - } else { - // Canon (base version of) word (h == 0) - hashes[hash] = n; - VAL_SYM_CANON(w) = n; - } - VAL_SYM_ALIAS(w) = 0; - VAL_SYM_NINDEX(w) = Make_Word_Name(str, len); - VAL_SET(w, REB_HANDLE); - - // These are allowed because of the SERIES_FULL checks above which - // add one extra to the TAIL check comparision. However, their - // termination values (nulls) will be missing. - PG_Word_Table.series->tail++; - Bind_Table->tail++; - - return n; + REBSER *synonym = intern->link.synonym; + + // Note synonym and intern may be the same here. + // + REBSER *temp = synonym; + while (temp->link.synonym != intern) { + if (GET_SER_INFO(intern, STRING_INFO_CANON)) + temp->misc.canon = synonym; + temp = temp->link.synonym; + } + temp->link.synonym = synonym; // cut intern out of chain (or no-op) + + if (NOT_SER_INFO(intern, STRING_INFO_CANON)) + return; // for non-canon forms, removing from chain is all you need + + assert(intern->misc.bind_index.high == 0); // shouldn't GC during binds? + assert(intern->misc.bind_index.low == 0); + + REBCNT size = SER_LEN(PG_Canons_By_Hash); + REBSTR* *canons_by_hash = SER_HEAD(REBSER*, PG_Canons_By_Hash); + assert(canons_by_hash != NULL); + + REBCNT len = STR_NUM_BYTES(intern); + assert(len == LEN_BYTES(STR_HEAD(intern))); + + REBCNT hash = Hash_Word(STR_HEAD(intern), len); + REBCNT skip = (hash & 0x0000FFFF) % size; + if (skip == 0) skip = 1; + hash = (hash & 0x00FFFF00) % size; + + // We *will* find the canon form in the hash table. + // + while (canons_by_hash[hash] != intern) { + hash += skip; + if (hash >= size) hash -= size; + } + + if (synonym != intern) { + // + // If there was a synonym in the circularly linked list distinct from + // the canon form, then it gets a promotion to being the canon form. + // It should hash the same, and be able to take over the hash slot. + // + #ifdef SLOW_INTERN_HASH_DOUBLE_CHECK + assert(hash == Hash_Word(STR_HEAD(synonym))); + #endif + canons_by_hash[hash] = synonym; + SET_SER_INFO(synonym, STRING_INFO_CANON); + synonym->misc.bind_index.low = 0; + synonym->misc.bind_index.high = 0; + } + else { + // This canon form must be removed from the hash table. Ripple the + // collision slots back until a NULL is found, to reduce search times. + // + REBCNT previous_hash = hash; + while (canons_by_hash[hash] != NULL) { + hash += skip; + if (hash >= size) hash -= size; + canons_by_hash[previous_hash] = canons_by_hash[hash]; + } + + // Signal that the hash slot is "deleted" via a special pointer. + // See notes on DELETED_SLOT for why the final slot in the collision + // chain can't just be left NULL: + // + // http://stackoverflow.com/a/279812/211160 + // + canons_by_hash[previous_hash] = DELETED_CANON; + + #if !defined(NDEBUG) + ++PG_Num_Canon_Deleteds; // total use same (PG_Num_Canons_Or_Deleteds) + #endif + } } -/*********************************************************************** -** -*/ REBCNT Last_Word_Num(void) -/* -** Return the number of the last word created. Used to -** mark a range of canon-words (e.g. operators). -** -***********************************************************************/ +// +// Get_Type_Name: C +// +const REBYTE *Get_Type_Name(const RELVAL *value) { - return PG_Word_Table.series->tail - 1; + return STR_HEAD(Canon(SYM_FROM_KIND(VAL_TYPE(value)))); } -/*********************************************************************** -** -*/ void Set_Word(REBVAL *value, REBINT sym, REBSER *frame, REBCNT index) -/* -***********************************************************************/ +// +// Compare_Word: C +// +// Compare the names of two words and return the difference. +// Note that words are kept UTF8 encoded. +// Positive result if s > t and negative if s < t. +// +REBINT Compare_Word(const RELVAL *s, const RELVAL *t, REBOOL strict) { - VAL_SET(value, REB_WORD); - VAL_WORD_SYM(value) = sym; - VAL_WORD_FRAME(value) = frame; - VAL_WORD_INDEX(value) = index; -} + const REBYTE *sp = STR_HEAD(VAL_WORD_SPELLING(s)); + const REBYTE *tp = STR_HEAD(VAL_WORD_SPELLING(t)); + if (strict) + return COMPARE_BYTES(sp, tp); // must match byte-for-byte -/*********************************************************************** -** -*/ void Init_Word(REBVAL *value, REBCNT sym) -/* -** Initialize a value as a word. Set frame as unbound (no context). -** -***********************************************************************/ -{ - VAL_SET(value, REB_WORD); - VAL_WORD_INDEX(value) = 0; - VAL_WORD_FRAME(value) = 0; - VAL_WORD_SYM(value) = sym; -} - - -/*********************************************************************** -** -*/ void Init_Frame_Word(REBVAL *value, REBCNT sym) -/* -** Initialize as a word list word. -** -***********************************************************************/ -{ - VAL_SET(value, REB_WORD); - VAL_SET_OPT(value, OPTS_UNWORD); - VAL_BIND_SYM(value) = sym; - VAL_BIND_TYPESET(value) = ALL_64; -} + if (VAL_WORD_CANON(s) == VAL_WORD_CANON(t)) + return 0; // equivalent canon forms are considered equal - -/*********************************************************************** -** -*/ REBYTE *Get_Sym_Name(REBCNT num) -/* -***********************************************************************/ -{ - if (num == 0 || num >= PG_Word_Table.series->tail) return (REBYTE*)"???"; - return VAL_SYM_NAME(BLK_SKIP(PG_Word_Table.series, num)); + // They must differ by case.... + return Compare_UTF8(sp, tp, LEN_BYTES(tp)) + 2; } -/*********************************************************************** -** -*/ REBYTE *Get_Word_Name(REBVAL *value) -/* -***********************************************************************/ +// +// Startup_Interning: C +// +// Get the engine ready to do Intern_UTF8_Managed(), which is required to +// get REBSTR* pointers generated during a scan of ANY-WORD!s. Words of the +// same spelling currently look up and share the same REBSTR*, this process +// is referred to as "string interning": +// +// https://en.wikipedia.org/wiki/String_interning +// +void Startup_Interning(void) { - if (value) return Get_Sym_Name(VAL_WORD_SYM(value)); - return (REBYTE*)"(unnamed)"; + PG_Num_Canon_Slots_In_Use = 0; +#if !defined(NDEBUG) + PG_Num_Canon_Deleteds = 0; +#endif + + // Start hash table out at a fixed size. When collisions occur, it + // causes a skipping pattern that continues until it finds the desired + // slot. The method is known as linear probing: + // + // https://en.wikipedia.org/wiki/Linear_probing + // + // It must always be at least as big as the total number of words, in order + // for it to uniquely be able to locate each symbol pointer. But to + // reduce long probing chains, it should be significantly larger than that. + // R3-Alpha used a heuristic of 4 times as big as the number of words. + + REBCNT n; +#if defined(NDEBUG) + n = Get_Hash_Prime(WORD_TABLE_SIZE * 4); // extra reduces rehashing +#else + n = 1; // forces exercise of rehashing logic in debug build +#endif + + PG_Canons_By_Hash = Make_Series_Core( + n, sizeof(REBSTR*), SERIES_FLAG_POWER_OF_2 + ); + Clear_Series(PG_Canons_By_Hash); // all slots start at NULL + SET_SERIES_LEN(PG_Canons_By_Hash, n); } -/*********************************************************************** -** -*/ REBYTE *Get_Type_Name(REBVAL *value) -/* -***********************************************************************/ +// +// Startup_Symbols: C +// +// By this point in the boot, the canon words have already been interned for +// everything in %words.r. +// +// This goes through the name series for %words.r words and tags them with +// SYM_XXX constants. This allows the small number to be quickly extracted to +// use with VAL_WORD_SYM() in C switch statements. These are the only words +// that have fixed symbol numbers--others are only managed and compared +// through their pointers. +// +// It also creates a table for mapping from SYM_XXX => REBSTR series. This +// is used e.g. by Canon(SYM_XXX) to get the string name for a symbol. +// +void Startup_Symbols(REBARR *words) { - return Get_Sym_Name(VAL_TYPE(value)+1); + PG_Symbol_Canons = Make_Series_Core( + ARR_LEN(words) + 1, // extra NULL at head for SYM_0 + sizeof(REBSTR*), + SERIES_FLAG_FIXED_SIZE // can't ever add more SYM_XXX lookups + ); + + // All words that not in %words.r will get back VAL_WORD_SYM(w) == SYM_0 + // Hence, SYM_0 cannot be canonized. Allowing Canon(SYM_0) to return NULL + // and try and use that meaningfully is too risky, so it is simply + // prohibited to canonize SYM_0, and trash the REBSTR* in the [0] slot. + // + REBSYM sym = SYM_0; + TRASH_POINTER_IF_DEBUG( + *SER_AT(REBSTR*, PG_Symbol_Canons, cast(REBCNT, sym)) + ); + + RELVAL *word = ARR_HEAD(words); + for (; NOT_END(word); ++word) { + REBSTR *canon = VAL_WORD_CANON(word); + assert(GET_SER_INFO(canon, STRING_INFO_CANON)); + + sym = cast(REBSYM, cast(REBCNT, sym) + 1); + *SER_AT(REBSTR*, PG_Symbol_Canons, cast(REBCNT, sym)) = canon; + + // More code was loaded than just the word list, and it might have + // included alternate-case forms of the %words.r words. Walk any + // aliases and make sure they have the header bits too. + + REBSTR *name = canon; + do { + // The low 8 bits of the header are reserved for flags, including + // those common between REBSER nodes and REBVALs. The high 8 bits + // are used for the size if the series has no dynamic content, + // and reserved otherwise. So the shifted-left-by-8 16 bits of + // the header are free for the symbol number (could probably use + // less than 16 bits, but 8 is insufficient, length %words.r > 256) + // + assert(RIGHT_16_BITS(name->header.bits) == 0); + name->header.bits |= FLAGUINT16_RIGHT(sym); + assert(SAME_SYM_NONZERO(STR_SYMBOL(name), sym)); + + name = name->link.synonym; + } while (name != canon); // circularly linked list, stop on a cycle + } + + *SER_AT(REBSTR*, PG_Symbol_Canons, cast(REBCNT, sym)) = NULL; // terminate + sym = cast(REBSYM, cast(REBCNT, sym) + 1); + + SET_SERIES_LEN(PG_Symbol_Canons, cast(REBCNT, sym)); + assert(SER_LEN(PG_Symbol_Canons) == ARR_LEN(words) + 1); + + // Do some sanity checks + + if (COMPARE_BYTES(cb_cast("blank!"), STR_HEAD(Canon(SYM_BLANK_X))) != 0) + panic (Canon(SYM_BLANK_X)); + if (COMPARE_BYTES(cb_cast("true"), STR_HEAD(Canon(SYM_TRUE))) != 0) + panic (Canon(SYM_TRUE)); } -/*********************************************************************** -** -*/ REBINT Compare_Word(REBVAL *s, REBVAL *t, REBFLG is_case) -/* -** Compare the names of two words and return the difference. -** Note that words are kept UTF8 encoded. -** Positive result if s > t and negative if s < t. -** -***********************************************************************/ +// +// Shutdown_Symbols: C +// +void Shutdown_Symbols(void) { - REBYTE *sp = VAL_WORD_NAME(s); - REBYTE *tp = VAL_WORD_NAME(t); - - // Use a more strict comparison than normal: - if (is_case) return CMP_BYTES(sp, tp); - - // They are the equivalent words: - if (VAL_WORD_CANON(s) == VAL_WORD_CANON(t)) return 0; - - // They must be differ by case: - return Compare_UTF8(sp, tp, LEN_BYTES(tp)) + 2; + Free_Series(PG_Symbol_Canons); } -/*********************************************************************** -** -*/ void Init_Words(REBFLG only) -/* -** Only flags BIND_Table creation only (for threads). -** -***********************************************************************/ +// +// Shutdown_Interning: C +// +void Shutdown_Interning(void) { - REBCNT n = Get_Hash_Prime(WORD_TABLE_SIZE * 4); // extra to reduce rehashing - - if (!only) { - // Create the hash for locating words quickly: - // Note that the TAIL is never changed for this series. - PG_Word_Table.hashes = Make_Series(n+1, sizeof(REBCNT *), FALSE); - KEEP_SERIES(PG_Word_Table.hashes, "word hashes"); // pointer array - Clear_Series(PG_Word_Table.hashes); - PG_Word_Table.hashes->tail = n; - - // The word (symbol) table itself: - PG_Word_Table.series = Make_Block(WORD_TABLE_SIZE); - SET_NONE(BLK_HEAD(PG_Word_Table.series)); // Put a NONE at head. - KEEP_SERIES(PG_Word_Table.series, "word table"); // words are never GC'd - BARE_SERIES(PG_Word_Table.series); // don't bother to GC scan it - PG_Word_Table.series->tail = 1; // prevent the zero case - - // A normal char array to hold symbol names: - PG_Word_Names = Make_Binary(6 * WORD_TABLE_SIZE); // average word size - KEEP_SERIES(PG_Word_Names, "word names"); - } - - // The bind table. Used to cache context indexes for given symbols. - Bind_Table = Make_Series(SERIES_REST(PG_Word_Table.series), 4, FALSE); - KEEP_SERIES(Bind_Table, "bind table"); // numeric table - CLEAR_SERIES(Bind_Table); - Bind_Table->tail = PG_Word_Table.series->tail; + assert(PG_Num_Canon_Slots_In_Use - PG_Num_Canon_Deleteds == 0); + Free_Series(PG_Canons_By_Hash); } diff --git a/src/core/d-break.c b/src/core/d-break.c new file mode 100644 index 0000000000..4bec92d447 --- /dev/null +++ b/src/core/d-break.c @@ -0,0 +1,494 @@ +// +// Rebol 3 Language Interpreter and Run-time Environment +// "Ren-C" branch @ https://github.com/metaeducation/ren-c +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Summary: Debug Breaking and Resumption +// File: %d-break.h +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2015-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This file contains interactive debugging support for breaking and +// resuming. The instructions BREAKPOINT and PAUSE are natives which will +// call a host environment hook which can then begin an interactive debugging +// session. During that time Rebol functions may continue to be called, +// though there is a sandbox which prevents the code from throwing or causing +// errors which will propagate past the breakpoint. The only way to +// resume normal operation is with a "resume instruction". +// +// !!! Interactive debugging is a work in progress, and comments are in the +// functions below. +// + +#include "sys-core.h" + + +// +// Index values for the properties in a "resume instruction" (see notes on +// REBNATIVE(resume)) +// +enum { + RESUME_INST_MODE = 0, // FALSE if /WITH, TRUE if /DO, NONE! if default + RESUME_INST_PAYLOAD, // code block to /DO or value of /WITH + RESUME_INST_TARGET, // unwind target, NONE! to return from breakpoint + RESUME_INST_MAX +}; + + +// +// Do_Breakpoint_Throws: C +// +// A call to Do_Breakpoint_Throws does delegation to a hook in the host, which +// (if registered) will generally start an interactive session for probing the +// environment at the break. The RESUME native cooperates by being able to +// give back a value (or give back code to run to produce a value) that the +// call to breakpoint returns. +// +// RESUME has another feature, which is to be able to actually unwind and +// simulate a return /AT a function *further up the stack*. (This may be +// switched to a feature of a "step out" command at some point.) +// +REBOOL Do_Breakpoint_Throws( + REBVAL *out, + REBOOL interrupted, // Ctrl-C (as opposed to a BREAKPOINT) + const REBVAL *default_value, + REBOOL do_default +) { + const REBVAL *target = BLANK_VALUE; + + if (!PG_Breakpoint_Quitting_Hook) { + // + // Host did not register any breakpoint handler, so raise an error + // about this as early as possible. + // + fail (Error_Host_No_Breakpoint_Raw()); + } + + DECLARE_LOCAL (temp); + + // We call the breakpoint hook in a loop, in order to keep running if any + // inadvertent FAILs or THROWs occur during the interactive session. + // Only a conscious call of RESUME speaks the protocol to break the loop. + // + while (TRUE) { + struct Reb_State state; + REBCTX *error; + + push_trap: + PUSH_TRAP(&error, &state); + + // The host may return a block of code to execute, but cannot + // while evaluating do a THROW or a FAIL that causes an effective + // "resumption". Halt is the exception, hence we PUSH_TRAP and + // not PUSH_UNHALTABLE_TRAP. QUIT is also an exception, but a + // desire to quit is indicated by the return value of the breakpoint + // hook (which may or may not decide to request a quit based on the + // QUIT command being run). + // + // The core doesn't want to get involved in presenting UI, so if + // an error makes it here and wasn't trapped by the host first that + // is a bug in the host. It should have done its own PUSH_TRAP. + // + if (error) { + #if !defined(NDEBUG) + printf("Error not trapped during breakpoint\n"); + panic (error); + #endif + + // In release builds, if an error managed to leak out of the + // host's breakpoint hook somehow...just re-push the trap state + // and try it again. + // + goto push_trap; + } + + // Call the host's breakpoint hook. + // + // The DECLARE_LOCAL is here and not outside the loop + // due to wanting to avoid "longjmp clobbering" warnings + // (seen in optimized builds on Android). + // + DECLARE_LOCAL (inst); + if (PG_Breakpoint_Quitting_Hook(inst, interrupted)) { + // + // If a breakpoint hook returns TRUE that means it wants to quit. + // The value should be the /WITH value (as in QUIT/WITH), so + // not actually a "resume instruction" in this case. + // + assert(!THROWN(inst)); + Move_Value(out, NAT_VALUE(quit)); + CONVERT_NAME_TO_THROWN(out, inst); + return TRUE; // TRUE = threw + } + + // If a breakpoint handler returns FALSE, then it should have passed + // back a "resume instruction" triggered by a call like: + // + // resume/do [fail "This is how to fail from a breakpoint"] + // + // So now that the handler is done, we will allow any code handed back + // to do whatever FAIL it likes vs. trapping that here in a loop. + // + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + + // Decode and process the "resume instruction" + + #if !defined(NDEBUG) + REBOOL found = FALSE; + #endif + + assert(IS_GROUP(inst)); + assert(VAL_LEN_HEAD(inst) == RESUME_INST_MAX); + + // The instruction was built from raw material, non-relative + // + REBVAL *mode = KNOWN(VAL_ARRAY_AT_HEAD(inst, RESUME_INST_MODE)); + REBVAL *payload + = KNOWN(VAL_ARRAY_AT_HEAD(inst, RESUME_INST_PAYLOAD)); + target = KNOWN(VAL_ARRAY_AT_HEAD(inst, RESUME_INST_TARGET)); + + assert(IS_FRAME(target)); + + // The first thing we need to do is determine if the target we + // want to return to has another breakpoint sandbox blocking + // us. If so, what we need to do is actually retransmit the + // resume instruction so it can break that wall, vs. transform + // it into an EXIT/FROM that would just get intercepted. + // + REBFRM *frame; + for (frame = FS_TOP; frame != NULL; frame = frame->prior) { + if (NOT(Is_Any_Function_Frame(frame))) + continue; + if (Is_Function_Frame_Fulfilling(frame)) + continue; + + if ( + frame != FS_TOP + && ( + FUNC_DISPATCHER(frame->phase) == &N_pause + || FUNC_DISPATCHER(frame->phase) == &N_breakpoint + ) + ) { + // We hit a breakpoint (that wasn't this call to + // breakpoint, at the current FS_TOP) before finding + // the sought after target. Retransmit the resume + // instruction so that level will get it instead. + // + Move_Value(out, NAT_VALUE(resume)); + CONVERT_NAME_TO_THROWN(out, inst); + return TRUE; // TRUE = thrown + } + + // If the frame were the one we were looking for, it would be + // reified (so it would have a context to match) + // + if (frame->varlist == NULL) + continue; + + if (VAL_CONTEXT(target) == CTX(frame->varlist)) { + // Found a match before hitting any breakpoints, so no + // need to retransmit. + // + #if !defined(NDEBUG) + found = TRUE; + #endif + break; + } + } + + // RESUME should not have been willing to use a target that + // is not on the stack. + // + #if !defined(NDEBUG) + assert(found); + #endif + + if (IS_BLANK(mode)) { + // + // If the resume instruction had no /DO or /WITH of its own, + // then it doesn't override whatever the breakpoint provided + // as a default. (If neither the breakpoint nor the resume + // provided a /DO or a /WITH, result will be void.) + // + goto return_default; // heeds `target` + } + + assert(IS_LOGIC(mode)); + + if (VAL_LOGIC(mode)) { + if (Do_Any_Array_At_Throws(temp, payload)) { + // + // Throwing is not compatible with /AT currently. + // + if (!IS_BLANK(target)) + fail (Error_No_Catch_For_Throw(temp)); + + // Just act as if the BREAKPOINT call itself threw + // + Move_Value(out, temp); + return TRUE; // TRUE = thrown + } + + // Ordinary evaluation result... + } + else + Move_Value(temp, payload); + + // The resume instruction will be GC'd. + // + goto return_temp; + } + + DEAD_END; + +return_default: + + if (do_default) { + if (Do_Any_Array_At_Throws(temp, default_value)) { + // + // If the code throws, we're no longer in the sandbox...so we + // bubble it up. Note that breakpoint runs this code at its + // level... so even if you request a higher target, any throws + // will be processed as if they originated at the BREAKPOINT + // frame. To do otherwise would require the EXIT/FROM protocol + // to add support for DO-ing at the receiving point. + // + Move_Value(out, temp); + return TRUE; // TRUE = thrown + } + } + else + Move_Value(temp, default_value); // generally void if no /WITH + +return_temp: + // + // If the target is a function, then we're looking to simulate a return + // from something up the stack. This uses the same mechanic as + // definitional returns--a throw named by the function or closure frame. + // + // !!! There is a weak spot in definitional returns for FUNCTION! that + // they can only return to the most recent invocation; which is a weak + // spot of FUNCTION! in general with stack relative variables. Also, + // natives do not currently respond to definitional returns...though + // they can do so just as well as FUNCTION! can. + // + Make_Thrown_Exit_Value(out, target, temp, NULL); + return TRUE; // TRUE = thrown +} + + +// +// breakpoint: native [ +// +// "Signal breakpoint to the host (simple variant of PAUSE dialect)" +// +// return: [ any-value!] +// "Returns the value passed to RESUME/WITH (or void by default)" +// ] +// +REBNATIVE(breakpoint) +// +// The reason BREAKPOINT needs to exist as a native is to be recognized by +// BACKTRACE as being a "0" stack level (e.g. probably not interesting to be +// where you are probing variables). Backtrace should not *always* skip the +// most recent stack level however, because of a "Ctrl-C"-like debugging +// break, where the most recent stack level *is* the one to inspect. +{ + if (Do_Breakpoint_Throws( + D_OUT, + FALSE, // not a Ctrl-C, it's an actual BREAKPOINT + VOID_CELL, // default result if RESUME does not override + FALSE // !execute (don't try to evaluate the VOID_CELL) + )) { + return R_OUT_IS_THROWN; + } + + return R_OUT; +} + + +// +// pause: native [ +// +// "Pause in the debugger before running the provided code" +// +// return: [ any-value!] +// "Result of the code evaluation, or RESUME/WITH value if override" +// :code [group!] ;-- or LIT-WORD! name or BLOCK! for dialect +// "Run the given code if breakpoint does not override" +// ] +// +REBNATIVE(pause) +{ + INCLUDE_PARAMS_OF_PAUSE; + + if (Do_Breakpoint_Throws( + D_OUT, + FALSE, // not a Ctrl-C, it's an actual BREAKPOINT + ARG(code), // default result if RESUME does not override + TRUE // execute (run the GROUP! as code, don't return as-is) + )) { + return R_OUT_IS_THROWN; + } + + return R_OUT; +} + + +// +// resume: native [ +// +// {Resume after a breakpoint, can evaluate code in the breaking context.} +// +// /with +// "Return the given value as return value from BREAKPOINT" +// value [any-value!] +// "Value to use" +// /do +// "Evaluate given code as return value from BREAKPOINT" +// code [block!] +// "Code to evaluate" +// /at +// "Return from another call up stack besides the breakpoint" +// level [frame! function! integer!] +// "Stack level to target in unwinding (can be BACKTRACE #)" +// ] +// +REBNATIVE(resume) +// +// The host breakpoint hook makes a wall to prevent arbitrary THROWs and +// FAILs from ending the interactive inspection. But RESUME is special, and +// it makes a very specific instruction (with a throw /NAME of the RESUME +// native) to signal a desire to end the interactive session. +// +// When the BREAKPOINT native gets control back from the hook, it interprets +// and executes the instruction. This offers the additional benefit that +// each host doesn't have to rewrite interpretation in the hook--they only +// need to recognize a RESUME throw and pass the argument back. +{ + INCLUDE_PARAMS_OF_RESUME; + + if (REF(with) && REF(do)) { + // + // /WITH and /DO both dictate a default return result, (/DO evaluates + // and /WITH does not) They are mutually exclusive. + // + fail (Error_Bad_Refines_Raw()); + } + + // We don't actually want to run the code for a /DO here. If we tried + // to run code from this stack level--and it failed or threw without + // some special protocol--we'd stay stuck in the breakpoint's sandbox. + // + // The /DO code we received needs to actually be run by the host's + // breakpoint hook, once it knows that non-local jumps to above the break + // level (throws, returns, fails) actually intended to be "resuming". + + REBARR *instruction = Make_Array(RESUME_INST_MAX); + + if (REF(with)) { + Init_Logic(ARR_AT(instruction, RESUME_INST_MODE), FALSE); // don't DO + Move_Value( + SINK(ARR_AT(instruction, RESUME_INST_PAYLOAD)), ARG(value) + ); + } + else if (REF(do)) { + Init_Logic(ARR_AT(instruction, RESUME_INST_MODE), TRUE); // DO value + Move_Value( + SINK(ARR_AT(instruction, RESUME_INST_PAYLOAD)), ARG(code) + ); + } + else { + Init_Blank(ARR_AT(instruction, RESUME_INST_MODE)); // use default + + // Even though this slot should be ignored, use BAR! to try and make + // any attempts to use it more conspicuous (an unset wouldn't be) + // + Init_Bar(ARR_AT(instruction, RESUME_INST_PAYLOAD)); + } + + // We want BREAKPOINT to resume /AT a higher stack level (using the + // same machinery that definitionally-scoped return would to do it). + // Frames will be reified as necessary. + // + REBFRM *frame; + + if (REF(at)) { + // + // `level` is currently allowed to be anything that backtrace can + // handle (integers, functions for most recent call, literal FRAME!) + + if (!(frame = Frame_For_Stack_Level(NULL, ARG(level), TRUE))) + fail (ARG(level)); + + // !!! It's possible to specify a context to return at which is + // "underneath" a breakpoint. So being at a breakpoint and doing + // `if true [resume/at :if]` would try and specify the IF running + // in the interactive breakpoint session. The instruction will + // error with no breakpoint to catch the resume...but a better error + // could be given here if the case were detected early. + } + else { + // We just want a BREAKPOINT or PAUSE themselves to return, so find + // the most recent one (if any, error if none found). + + frame = FS_TOP; + for (; frame != NULL; frame = frame->prior) { + if (NOT(Is_Any_Function_Frame(frame))) continue; + if (Is_Function_Frame_Fulfilling(frame)) continue; + + if ( + FUNC_DISPATCHER(frame->phase) == &N_pause + || FUNC_DISPATCHER(frame->phase) == &N_breakpoint + ) { + break; + } + } + + if (frame == NULL) + fail (Error_No_Current_Pause_Raw()); + } + + Init_Any_Context( + ARR_AT(instruction, RESUME_INST_TARGET), + REB_FRAME, + Context_For_Frame_May_Reify_Managed(frame) + ); + + TERM_ARRAY_LEN(instruction, RESUME_INST_MAX); + + // We put the resume instruction into a GROUP! just to make it a little + // bit more unusual than a BLOCK!. More hardened approaches might put + // a special symbol as a "magic number" or somehow version the protocol, + // but for now we'll assume that the only decoder is BREAKPOINT and it + // will be kept in sync. + // + DECLARE_LOCAL (cell); + Init_Group(cell, instruction); + + // Throw the instruction with the name of the RESUME function + // + Move_Value(D_OUT, NAT_VALUE(resume)); + CONVERT_NAME_TO_THROWN(D_OUT, cell); + return R_OUT_IS_THROWN; +} diff --git a/src/core/d-crash.c b/src/core/d-crash.c index 32d8b44b6c..f2acff671d 100644 --- a/src/core/d-crash.c +++ b/src/core/d-crash.c @@ -1,131 +1,175 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: d-crash.c -** Summary: low level crash output -** Section: debug -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %d-crash.c +// Summary: "low level crash output" +// Section: debug +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -#define CRASH_BUF_SIZE 512 // space for crash print string - -extern const REBYTE * const Crash_Msgs[]; - -enum Crash_Msg_Nums { - // Must align with Crash_Msgs[] array. - CM_ERROR, - CM_BOOT, - CM_INTERNAL, - CM_ASSERT, - CM_DATATYPE, - CM_DEBUG, - CM_CONTACT -}; - - -/*********************************************************************** -** -*/ void Crash(REBINT id, ...) -/* -** Print a failure message and abort. -** -** LATIN1 ONLY!! (For now) -** -** The error is identified by id number, which can reference an -** error message string in the boot strings block. -** -** Note that lower level error messages should not attempt to -** use the %r (mold value) format (uses higher level functions). -** -** See panics.h for list of crash errors. -** -***********************************************************************/ -{ - va_list args; - REBYTE buf[CRASH_BUF_SIZE]; - REBYTE *msg; - REBINT n = 0; - - va_start(args, id); - - DISABLE_GC; - if (Reb_Opts->crash_dump) { - Dump_Info(); - Dump_Stack(0, 0); - } - - // "REBOL PANIC #nnn:" - COPY_BYTES(buf, Crash_Msgs[CM_ERROR], CRASH_BUF_SIZE); - APPEND_BYTES(buf, " #", CRASH_BUF_SIZE); - Form_Int(buf + LEN_BYTES(buf), id); - APPEND_BYTES(buf, ": ", CRASH_BUF_SIZE); - - // "REBOL PANIC #nnn: put error message here" - // The first few error types only print general error message. - // Those errors > RP_STR_BASE have specific error messages (from boot.r). - if (id < RP_BOOT_DATA) n = CM_DEBUG; - else if (id < RP_INTERNAL) n = CM_BOOT; - else if (id < RP_ASSERTS) n = CM_INTERNAL; - else if (id < RP_DATATYPE) n = CM_ASSERT; - else if (id < RP_STR_BASE) n = CM_DATATYPE; - else if (id > RP_STR_BASE + RS_MAX - RS_ERROR) n = CM_DEBUG; - - // Use the above string or the boot string for the error (in boot.r): - msg = (REBYTE*)(n >= 0 ? Crash_Msgs[n] : BOOT_STR(RS_ERROR, id - RP_STR_BASE - 1)); - Form_Var_Args(buf + LEN_BYTES(buf), CRASH_BUF_SIZE - 1 - LEN_BYTES(buf), msg, args); - - APPEND_BYTES(buf, Crash_Msgs[CM_CONTACT], CRASH_BUF_SIZE); - - // Convert to OS-specific char-type: -#ifdef disable_for_now //OS_WIDE_CHAR /// win98 does not support it - { - REBCHR s1[512]; - REBCHR s2[2000]; - - n = TO_OS_STR(s1, Crash_Msgs[CM_ERROR], LEN_BYTES(Crash_Msgs[CM_ERROR])); - if (n > 0) s1[n] = 0; // terminate - else OS_EXIT(200); // bad conversion - - n = TO_OS_STR(s2, buf, LEN_BYTES(buf)); - if (n > 0) s2[n] = 0; - else OS_EXIT(200); - - OS_CRASH(s1, s2); - } + +// Size of crash buffers +#define PANIC_TITLE_BUF_SIZE 80 +#define PANIC_BUF_SIZE 512 + + +// +// Panic_Core: C +// +// See comments on `panic (...)` macro, which calls this routine. +// +ATTRIBUTE_NO_RETURN void Panic_Core( + const void *p, // REBSER* (array, context, etc), REBVAL*, or UTF-8 char* + const char *file, + int line +) { + if (p == NULL) + p = "panic (...) was passed NULL"; // avoid later NULL tests + + // We are crashing, so a legitimate time to be disabling the garbage + // collector. (It won't be turned back on.) + // + GC_Disabled = TRUE; + +#if defined(NDEBUG) + UNUSED(file); + UNUSED(line); #else - OS_CRASH(Crash_Msgs[CM_ERROR], buf); + // + // First thing's first in the debug build, make sure the file and the + // line are printed out. + // + printf("C Source File %s, Line %d\n", file, line); + + // Generally Rebol does not #include , but the debug build does. + // It's often used for debug spew--as opposed to Debug_Fmt()--when there + // is a danger of causing recursive errors if the problem is being caused + // by I/O in the first place. So flush anything lingering in the + // standard output or error buffers + // + fflush(stdout); + fflush(stderr); #endif -} -/*********************************************************************** -** -*/ void NA(void) -/* -** Feature not available. -** -***********************************************************************/ -{ - Crash(RP_NA); + // Because the release build of Rebol does not link to printf or its + // support functions, the crash buf is assembled into a buffer for + // raw output through the host. + // + char title[PANIC_TITLE_BUF_SIZE + 1]; // account for null terminator + char buf[PANIC_BUF_SIZE + 1]; // " + + title[0] = '\0'; + buf[0] = '\0'; + +#if !defined(NDEBUG) + if (Reb_Opts && Reb_Opts->crash_dump) { + Dump_Info(); + Dump_Stack(NULL, 0); + } +#endif + + strncat(title, "PANIC()", PANIC_TITLE_BUF_SIZE - 0); + + strncat(buf, Str_Panic_Directions, PANIC_BUF_SIZE - 0); + + strncat(buf, "\n", PANIC_BUF_SIZE - strlen(buf)); + + switch (Detect_Rebol_Pointer(p)) { + case DETECTED_AS_UTF8: // string might be empty...handle specially? + strncat( + buf, + cast(const char*, p), + PANIC_BUF_SIZE - strlen(buf) + ); + break; + + case DETECTED_AS_SERIES: { + REBSER *s = m_cast(REBSER*, cast(const REBSER*, p)); // don't mutate + #if !defined(NDEBUG) + #if 0 + // + // It can sometimes be useful to probe here if the series is + // valid, but if it's not valid then that could result in a + // recursive call to panic and a stack overflow. + // + PROBE(s); + #endif + + if (GET_SER_FLAG(s, ARRAY_FLAG_VARLIST)) { + printf("Series VARLIST detected.\n"); + REBCTX *context = CTX(s); + if (CTX_TYPE(context) == REB_ERROR) { + printf("...and that VARLIST is of an ERROR!..."); + PROBE(context); + } + } + Panic_Series_Debug(cast(REBSER*, s)); + #else + UNUSED(s); + strncat(buf, "valid series", PANIC_BUF_SIZE - strlen(buf)); + #endif + break; } + + case DETECTED_AS_FREED_SERIES: + #if !defined(NDEBUG) + Panic_Series_Debug(m_cast(REBSER*, cast(const REBSER*, p))); + #endif + strncat(buf, "freed series", PANIC_BUF_SIZE - strlen(buf)); + break; + + case DETECTED_AS_VALUE: + case DETECTED_AS_END: + #if !defined(NDEBUG) + Panic_Value_Debug(cast(const REBVAL*, p)); + #else + strncat(buf, "value", PANIC_BUF_SIZE - strlen(buf)); + #endif + break; + + case DETECTED_AS_TRASH_CELL: + #if !defined(NDEBUG) + Panic_Value_Debug(cast(const RELVAL*, p)); + #endif + strncat(buf, "trash cell", PANIC_BUF_SIZE - strlen(buf)); + break; + } + +#if !defined(NDEBUG) + // + // In a debug build, we'd like to try and cause a break so as not to lose + // the state of the panic, which would happen if we called out to the + // host kit's exit routine... + // + printf("%s\n", Str_Panic_Title); + printf("%s\n", buf); + fflush(stdout); + debug_break(); // see %debug_break.h +#endif + + OS_CRASH(cb_cast(Str_Panic_Title), cb_cast(buf)); + + DEAD_END; } diff --git a/src/core/d-dump.c b/src/core/d-dump.c index 3e879e0172..d5a5b8905b 100644 --- a/src/core/d-dump.c +++ b/src/core/d-dump.c @@ -1,399 +1,320 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: d-dump.c -** Summary: various debug output functions -** Section: debug -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %d-dump.c +// Summary: "various debug output functions" +// Section: debug +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Most of these low-level debug routines were leftovers from R3-Alpha, which +// had no DEBUG build (and was perhaps frequently debugged without an IDE +// debugger). After the open source release, Ren-C's reliance is on a +// more heavily checked debug build...so these routines were not used. +// +// They're being brought up to date to be included in the debug build only +// version of panic(). That should keep them in working shape. +// +// Note: These routines use `printf()`, which is only linked in DEBUG builds. +// Higher-level Rebol formatting should ultimately be using BLOCK! dialects, +// as opposed to strings with %s and %d. Bear in mind the "z" modifier in +// printf is unavailable in C89, so if something might be 32-bit or 64-bit +// depending, it must be cast to unsigned long: +// +// http://stackoverflow.com/q/2125845 +// #include "sys-core.h" +#include "mem-series.h" // low-level series memory access +#if !defined(NDEBUG) -/*********************************************************************** -** -*/ void Dump_Series(REBSER *series, REBYTE *memo) -/* -***********************************************************************/ -{ - if (!series) return; - Debug_Fmt( - Str_Dump[0], //"%s Series %x %s: Wide: %2d Size: %6d - Bias: %d Tail: %d Rest: %d Flags: %x" - memo, - series, - (SERIES_LABEL(series) ? SERIES_LABEL(series) : "-"), - SERIES_WIDE(series), - SERIES_TOTAL(series), - SERIES_BIAS(series), - SERIES_TAIL(series), - SERIES_REST(series), - SERIES_FLAGS(series) - ); - if (SERIES_WIDE(series) == sizeof(REBVAL)) - Dump_Values(BLK_HEAD(series), SERIES_TAIL(series)); - else - Dump_Bytes(series->data, (SERIES_TAIL(series)+1) * SERIES_WIDE(series)); -} +#ifdef _MSC_VER +#define snprintf _snprintf +#endif -/*********************************************************************** -** -*/ void Dump_Bytes(REBYTE *bp, REBCNT limit) -/* -***********************************************************************/ -{ - const max_lines = 120; - REBYTE buf[2048]; - REBYTE str[40]; - REBYTE *cp, *tp; - REBYTE c; - REBCNT l, n; - REBCNT cnt = 0; - - cp = buf; - for (l = 0; l < max_lines; l++) { - cp = Form_Hex_Pad(cp, (REBCNT) bp, 8); - - *cp++ = ':'; - *cp++ = ' '; - tp = str; - - for (n = 0; n < 16; n++) { - if (cnt++ >= limit) break; - c = *bp++; - cp = Form_Hex2(cp, c); - if ((n & 3) == 3) *cp++ = ' '; - if ((c < 32) || (c > 126)) c = '.'; - *tp++ = c; - } - - for (; n < 16; n++) { - c = ' '; - *cp++ = c; - *cp++ = c; - if ((n & 3) == 3) *cp++ = ' '; - if ((c < 32) || (c > 126)) c = '.'; - *tp++ = c; - } - *tp++ = 0; - - for (tp = str; *tp;) *cp++ = *tp++; - - *cp = 0; - Debug_Str(buf); - if (cnt >= limit) break; - cp = buf; - } -} -/*********************************************************************** -** -*/ void Dump_Values(REBVAL *vp, REBCNT count) -/* -** Print out values in raw hex; If memory is corrupted -** this function still needs to work. -** -***********************************************************************/ +// +// Dump_Bytes: C +// +void Dump_Bytes(REBYTE *bp, REBCNT limit) { - REBYTE buf[2048]; - REBYTE *cp; - REBCNT l, n; - REBCNT *bp = (REBCNT*)vp; - REBYTE *type; - - cp = buf; - for (l = 0; l < count; l++) { - cp = Form_Hex_Pad(cp, (REBCNT) l, 4); - *cp++ = ':'; - *cp++ = ' '; - - type = Get_Type_Name((REBVAL*)bp); - for (n = 0; n < 11; n++) { - if (*type) *cp++ = *type++; - else *cp++ = ' '; - } - *cp++ = ' '; - for (n = 0; n < 4; n++) { - cp = Form_Hex_Pad(cp, *bp++, 8); - *cp++ = ' '; - } - - *cp = 0; - Debug_Str(buf); - cp = buf; - } -} + const REBCNT max_lines = 120; -#ifdef not_used -/*********************************************************************** -** -xx*/ void Dump_Block_Raw(REBSER *series, int depth, int max_depth) -/* -***********************************************************************/ -{ - REBVAL *val; - REBCNT n; - REBYTE *str; - - if (!IS_BLOCK_SERIES(series) || depth > max_depth) return; - - for (n = 0, val = BLK_HEAD(series); NOT_END(val); val++, n++) { - Debug_Chars(' ', depth * 4); - if (IS_BLOCK(val)) { - Debug_Fmt("%3d: [%s] len: %d", n, Get_Type_Name(val), VAL_TAIL(val)); - Dump_Block_Raw(VAL_SERIES(val), depth + 1, max_depth); - } else { - str = ""; - if (ANY_WORD(val)) str = Get_Word_Name(val); - Debug_Fmt("%3d: [%s] %s", n, Get_Type_Name(val), str); - } - } - //if (depth == 2) Input_Str(); -} + REBCNT total = 0; -/*********************************************************************** -** -xx*/ REBSER *Dump_Value(REBVAL *block, REBSER *series) -/* -** Dump a values's contents for debugging purposes. -** -***********************************************************************/ -{ - REB_MOLD mo = {0}; - mo.digits = 17; // max digits + REBYTE buf[2048]; - if (VAL_TYPE(block) >= REB_MAX) Crash(RP_DATATYPE+7, VAL_TYPE(block)); + REBCNT l = 0; + for (; l < max_lines; l++) { + REBYTE *cp = buf; - ASSERT2(series, 9997); - mo.series = series; - Emit(&mo, "T: ", block); + cp = Form_Hex_Pad(cp, cast(REBUPT, bp), 8); - Mold_Value(&mo, block, TRUE); + *cp++ = ':'; + *cp++ = ' '; - if (ANY_WORD(block)) { - if (!VAL_WORD_FRAME(block)) Append_Bytes(series, " - unbound"); - else if (VAL_WORD_INDEX(block) < 0) Append_Bytes(series, " - relative"); - else Append_Bytes(series, " - absolute"); - } - return series; -} + REBYTE str[40]; + REBYTE *tp = str; + REBCNT n = 0; + for (; n < 16; n++) { + if (total++ >= limit) + break; -/*********************************************************************** -** -xx*/ void Print_Dump_Value(REBVAL *value, REBYTE *label) -/* -** Dump a value's contents for debugging purposes. -** -***********************************************************************/ -{ - REBSER *series; - series = Copy_Bytes(label, -1); - SAVE_SERIES(series); - series = Dump_Value(value, series); - Debug_Str(STR_HEAD(series)); - UNSAVE_SERIES(series); -} + REBYTE c = *bp++; + cp = Form_Hex2(cp, c); + if ((n & 3) == 3) + *cp++ = ' '; + if ((c < 32) || (c > 126)) + c = '.'; + *tp++ = c; + } + for (; n < 16; n++) { + REBYTE c = ' '; + *cp++ = c; + *cp++ = c; + if ((n & 3) == 3) + *cp++ = ' '; + if ((c < 32) || (c > 126)) + c = '.'; + *tp++ = c; + } -/*********************************************************************** -** -xx*/ void Dump_Block(REBVAL *blk, REBINT len) -/* -** Dump a block's contents for debugging purposes. -** -***********************************************************************/ -{ - REBSER *series; - //REBVAL *blk = BLK_HEAD(block); - - //Print("BLOCK: %x Tail: %d Size: %d", block, block->tail, block->rest); - // change to a make string!!! no need to append to a series, this is a debug function - series = Make_Binary(100); - Append_Bytes(series, "[\n"); - while (NOT_END(blk) && len-- > 0) { - Append_Byte(series, '\t'); - Dump_Value(blk, series); - Append_Byte(series, '\n'); - blk++; - } - Append_Byte(series, ']'); - *STR_TAIL(series) = 0; - Debug_Str(STR_HEAD(series)); -} + *tp++ = 0; + for (tp = str; *tp;) + *cp++ = *tp++; -/*********************************************************************** -** -xx*/ void Dump_Frame(REBSER *frame, REBINT limit) -/* -***********************************************************************/ -{ - REBINT n; - REBVAL *values = FRM_VALUES(frame); - REBVAL *words = FRM_WORDS(frame); - - if (limit == -1 || limit > (REBINT)SERIES_TAIL(frame)) - limit = SERIES_TAIL(frame); - - Debug_Fmt("Frame: %x len = %d", frame, SERIES_TAIL(frame)); - for (n = 0; n < limit; n++, values++, words++) { - Debug_Fmt(" %02d: %s (%s) [%s]", - n, - Get_Sym_Name(VAL_BIND_SYM(words)), - Get_Sym_Name(VAL_BIND_CANON(words)), - Get_Type_Name(values) - ); - } - - if (limit >= (REBINT)SERIES_TAIL(frame) && NOT_END(words)) - Debug_Fmt("** Word list not terminated! Type: %d, Tail: %d", VAL_TYPE(words), SERIES_TAIL(frame)); + *cp = 0; + printf("%s\n", s_cast(buf)); + fflush(stdout); + + if (total >= limit) + break; + } } -/*********************************************************************** -** -xx*/ void Dump_Word_Value(REBVAL *word) -/* -***********************************************************************/ +// +// Dump_Series: C +// +void Dump_Series(REBSER *s, const char *memo) { - Debug_Fmt("Word: %s (Symbol %d Frame %x Index %d)", Get_Word_Name(word), - VAL_WORD_SYM(word), VAL_WORD_FRAME(word), VAL_WORD_INDEX(word)); + printf("Dump_Series(%s) @ %p\n", memo, cast(void*, s)); + fflush(stdout); + + if (s == NULL) + return; + + printf(" wide: %d\n", SER_WIDE(s)); + printf(" size: %ld\n", cast(unsigned long, SER_TOTAL_IF_DYNAMIC(s))); + if (GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC)) + printf(" bias: %d\n", cast(int, SER_BIAS(s))); + printf(" tail: %d\n", cast(int, SER_LEN(s))); + printf(" rest: %d\n", cast(int, SER_REST(s))); + + // flags includes len if non-dynamic + printf(" flags: %lx\n", cast(unsigned long, s->header.bits)); + + // info includes width + printf(" info: %lx\n", cast(unsigned long, s->info.bits)); + + fflush(stdout); + + if (GET_SER_FLAG(s, SERIES_FLAG_ARRAY)) + Dump_Values(ARR_HEAD(ARR(s)), SER_LEN(s)); + else + Dump_Bytes(SER_DATA_RAW(s), (SER_LEN(s) + 1) * SER_WIDE(s)); + + fflush(stdout); } -/*********************************************************************** -** -xx*/ void Dump_Word_Table() -/* -***********************************************************************/ +// +// Dump_Values: C +// +// Print values in raw hex; If memory is corrupted this still needs to work. +// +void Dump_Values(RELVAL *vp, REBCNT count) { - REBCNT n; - REBVAL *words = BLK_HEAD(PG_Word_Table.series); - - for (n = 1; n < PG_Word_Table.series->tail; n++) { - Debug_Fmt("%03d: %s = %d (%d)", n, VAL_SYM_NAME(words+n), - VAL_SYM_CANON(words+n), VAL_SYM_ALIAS(words+n)); -// if ((n % 40) == 0) getchar(); - } + REBYTE buf[2048]; + REBYTE *cp; + REBCNT l, n; + REBCNT *bp = (REBCNT*)vp; + const REBYTE *type; + + cp = buf; + for (l = 0; l < count; l++) { + REBVAL *val = cast(REBVAL*, bp); + if (IS_END(val)) { + break; + } + if (IS_BLANK_RAW(val) || IS_VOID(val)) { + bp = cast(REBCNT*, val + 1); + continue; + } + + cp = Form_Hex_Pad(cp, l, 8); + + *cp++ = ':'; + *cp++ = ' '; + + type = Get_Type_Name(val); + for (n = 0; n < 11; n++) { + if (*type) *cp++ = *type++; + else *cp++ = ' '; + } + *cp++ = ' '; + for (n = 0; n < sizeof(REBVAL) / sizeof(REBCNT); n++) { + cp = Form_Hex_Pad(cp, *bp++, 8); + *cp++ = ' '; + } + n = 0; + if (IS_WORD(val) || IS_GET_WORD(val) || IS_SET_WORD(val)) { + const REBYTE *name = STR_HEAD(VAL_WORD_SPELLING(val)); + n = snprintf( + s_cast(cp), sizeof(buf) - (cp - buf), " (%s)", cs_cast(name) + ); + } + + *(cp + n) = 0; + Debug_Str(s_cast(buf)); + cp = buf; + } } -/*********************************************************************** -** -xx*/ void Dump_Bind_Table() -/* -***********************************************************************/ +// +// Dump_Info: C +// +void Dump_Info(void) { - REBCNT n; - REBINT *binds = WORDS_HEAD(Bind_Table); - - Debug_Fmt("Bind Table (Size: %d)", SERIES_TAIL(Bind_Table)); - for (n = 1; n < SERIES_TAIL(Bind_Table); n++) { - if (binds[n]) - Debug_Fmt("Bind: %3d to %3d (%s)", n, binds[n], Get_Sym_Name(n)); - } + printf("^/--REBOL Kernel Dump--\n"); + + printf("Evaluator:\n"); + printf(" Cycles: %ld\n", cast(unsigned long, Eval_Cycles)); + printf(" Counter: %d\n", cast(int, Eval_Count)); + printf(" Dose: %d\n", cast(int, Eval_Dose)); + printf(" Signals: %lx\n", cast(unsigned long, Eval_Signals)); + printf(" Sigmask: %lx\n", cast(unsigned long, Eval_Sigmask)); + printf(" DSP: %d\n", DSP); + + printf("Memory/GC:\n"); + + printf(" Ballast: %d\n", cast(int, GC_Ballast)); + printf(" Disable: %s\n", GC_Disabled ? "yes" : "no"); + printf(" Guarded Nodes: %d\n", cast(int, SER_LEN(GC_Guarded))); + fflush(stdout); } -#endif -/*********************************************************************** -** -*/ void Dump_Info(void) -/* -***********************************************************************/ +// +// Dump_Stack: C +// +// Prints stack counting levels from the passed in number. Pass 0 to start. +// +void Dump_Stack(REBFRM *f, REBCNT level) { - REBINT n; - REBINT nums [] = { - 0, - 0, - (REBINT)Eval_Cycles, - Eval_Count, - Eval_Dose, - Eval_Signals, - Eval_Sigmask, - DSP, - DSF, - 0, - GC_Ballast, - GC_Disabled, - SERIES_TAIL(GC_Protect), - GC_Last_Infant, - }; - - DISABLE_GC; - for (n = 0; n < 14; n++) Debug_Fmt(BOOT_STR(RS_DUMP, n), nums[n]); - ENABLE_GC; + printf("\n"); + + if (f == NULL) + f = FS_TOP; + + if (f == NULL) { + printf("*STACK[] - NO FRAMES*\n"); + fflush(stdout); + return; + } + + printf( + "STACK[%d](%s) - %d\n", + cast(int, level), + STR_HEAD(FRM_LABEL(f)), + f->eval_type // note: this is now an ordinary Reb_Kind, stringify it + ); + + if (NOT(Is_Any_Function_Frame(f))) { + printf("(no function call pending or in progress)\n"); + fflush(stdout); + return; + } + + // !!! This is supposed to be a low-level debug routine, but it is + // effectively molding arguments. If the stack is known to be in "good + // shape" enough for that, it should be dumped by routines using the + // Rebol backtrace API. + + fflush(stdout); + + REBINT n = 1; + REBVAL *arg = FRM_ARG(f, 1); + REBVAL *param = FUNC_PARAMS_HEAD(f->phase); + + for (; NOT_END(param); ++param, ++arg, ++n) { + Debug_Fmt( + " %s: %72r", + STR_HEAD(VAL_PARAM_SPELLING(param)), + arg + ); + } + + if (f->prior) + Dump_Stack(f->prior, level + 1); } -/*********************************************************************** -** -*/ void Dump_Stack(REBINT dsf, REBINT dsp) -/* -***********************************************************************/ + +#endif // DUMP is picked up by scan regardless of #ifdef, must be defined + + +// +// dump: native [ +// +// "Temporary debug dump" +// +// value [ any-value!] +// ] +// +REBNATIVE(dump) { - REBINT n; - REBINT m; - REBVAL *args; - - if (dsf == 0) { - dsf = DSF; - dsp = DSP; - } - - m = dsp - dsf - DSF_SIZE; - Debug_Fmt(BOOT_STR(RS_STACK, 1), dsp, Get_Word_Name(DSF_WORD(dsf)), m, Get_Type_Name(DSF_FUNC(dsf))); - - if (dsf > 0) { - if (ANY_FUNC(DSF_FUNC(dsf))) { - args = BLK_HEAD(VAL_FUNC_ARGS(DSF_FUNC(dsf))); - m = SERIES_TAIL(VAL_FUNC_ARGS(DSF_FUNC(dsf))); - for (n = 1; n < m; n++) - Debug_Fmt("\t%s: %72r", Get_Word_Name(args+n), DSF_ARGS(dsf, n)); - } - //Debug_Fmt(Str_Stack[2], PRIOR_DSF(dsf)); - if (PRIOR_DSF(dsf) > 0) Dump_Stack(PRIOR_DSF(dsf), dsf-1); - } - - //for (n = 1; n <= 2; n++) { - // Debug_Fmt(" ARG%d: %s %r", n, Get_Type_Name(DSF_ARGS(dsf, n)), DSF_ARGS(dsf, n)); - //} -} + INCLUDE_PARAMS_OF_DUMP; -#ifdef TEST_PRINT - // Simple low-level tests: - Print("%%d %d", 1234); - Print("%%d %d", -1234); - Print("%%d %d", 12345678); - Print("%%d %d", 0); - Print("%%6d %6d", 1234); - Print("%%10d %10d", 123456789); - Print("%%x %x", 0x1234ABCD); - Print("%%x %x", -1); - Print("%%4x %x", 0x1234); - Print("%%s %s", "test"); - Print("%%s %s", 0); - Print("%%c %c", (REBINT)'X'); - Print("%s %d %x", "test", 1234, 1234); - getchar(); +#ifdef NDEBUG + UNUSED(ARG(value)); + fail (Error_Debug_Only_Raw()); +#else + REBVAL *value = ARG(value); + + Dump_Stack(frame_, 0); + + if (ANY_SERIES(value)) + Dump_Series(VAL_SERIES(value), "=>"); + else + Dump_Values(value, 1); + + Move_Value(D_OUT, value); + return R_OUT; #endif +} diff --git a/src/core/d-eval.c b/src/core/d-eval.c new file mode 100644 index 0000000000..93ba42fd4b --- /dev/null +++ b/src/core/d-eval.c @@ -0,0 +1,397 @@ +// +// File: %d-eval.c +// Summary: "Debug-Build Checks for the Evaluator" +// Section: debug +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Due to the length of Do_Core() and how many debug checks it already has, +// three debug-only routines are separated out: +// +// * Do_Core_Entry_Checks_Debug() runs once at the beginning of a Do_Core() +// call. It verifies that the fields of the frame the caller has to +// provide have been pre-filled correctly, and snapshots bits of the +// interpreter state that are supposed to "balance back to zero" by the +// end of a run (assuming it completes, and doesn't longjmp from fail()ing) +// +// * Do_Core_Expression_Checks_Debug() runs before each full "expression" +// is evaluated, e.g. before each DO/NEXT step. It makes sure the state +// balanced completely--so no DS_PUSH that wasn't balanced by a DS_POP +// or DS_DROP (for example). It also trashes variables in the frame which +// might accidentally carry over from one step to another, so that there +// will be a crash instead of a casual reuse. +// +// * Do_Core_Exit_Checks_Debug() runs if the Do_Core() call makes it to the +// end without a fail() longjmping out from under it. It also checks to +// make sure the state has balanced, and that the return result is +// consistent with the state being returned. +// +// Because none of these routines are in the release build, they cannot have +// any side-effects that affect the interpreter's ordinary operation. +// + +#include "sys-core.h" + +#if !defined(NDEBUG) + + +// +// Dump_Frame_Location: C +// +void Dump_Frame_Location(REBFRM *f) +{ + DECLARE_LOCAL (dump); + Derelativize(dump, f->value, f->specifier); + + printf("Dump_Frame_Location() value\n"); + PROBE(dump); + + if (f->flags.bits & DO_FLAG_VA_LIST) { + // + // NOTE: This reifies the va_list in the frame, and hence has + // side effects. It may need to be commented out if the + // problem you are trapping with DO_COUNT_BREAKPOINT was + // specifically with va_list frame processing. + // + const REBOOL truncated = TRUE; + Reify_Va_To_Array_In_Frame(f, truncated); + } + + if (f->pending && NOT_END(f->pending)) { + assert(IS_SPECIFIC(f->pending)); + printf("EVAL in progress, so next will be...\n"); + PROBE(const_KNOWN(f->pending)); + } + + if (IS_END(f->value)) { + printf("...then Dump_Frame_Location() at end of array\n"); + } + else { + Init_Any_Series_At_Core( + dump, + REB_BLOCK, + SER(f->source.array), + cast(REBCNT, f->index), + f->specifier + ); + + printf("Dump_Frame_Location() next input\n"); + PROBE(dump); + } +} + + +// +// Do_Core_Entry_Checks_Debug: C +// +void Do_Core_Entry_Checks_Debug(REBFRM *f) +{ + // Though we can protect the value written into the target pointer 'out' + // from GC during the course of evaluation, we can't protect the + // underlying value from relocation. Technically this would be a problem + // for any series which might be modified while this call is running, but + // most notably it applies to the data stack--where output used to always + // be returned. + // + // !!! A non-contiguous data stack which is not a series is a possibility. + // +#ifdef STRESS_CHECK_DO_OUT_POINTER + REBSER *containing = Try_Find_Containing_Series_Debug(f->out); + + if (containing) { + if (GET_SER_FLAG(containing, SERIES_FLAG_FIXED_SIZE)) { + // + // Currently it's considered OK to be writing into a fixed size + // series, for instance the durable portion of a function's + // arg storage. It's assumed that the memory will not move + // during the course of the argument evaluation. + // + } + else { + printf("Request for ->out location in movable series memory\n"); + panic (containing); + } + } +#else + assert(!IN_DATA_STACK_DEBUG(f->out)); +#endif + + Assert_Cell_Writable(f->out, __FILE__, __LINE__); + + // Caller should have pushed the frame, such that it is the topmost. + // This way, repeated calls to Do_Core(), e.g. by routines like ANY [] + // don't keep pushing and popping on each call. + // + assert(f == FS_TOP); + + // The arguments to functions in their frame are exposed via FRAME!s + // and through WORD!s. This means that if you try to do an evaluation + // directly into one of those argument slots, and run arbitrary code + // which also *reads* those argument slots...there could be trouble with + // reading and writing overlapping locations. So unless a function is + // in the argument fulfillment stage (before the variables or frame are + // accessible by user code), it's not legal to write directly into an + // argument slot. :-/ Note the availability of D_CELL for any functions + // that have more than one argument, during their run. + // + REBFRM *ftemp = FS_TOP->prior; + for (; ftemp != NULL; ftemp = ftemp->prior) { + if (!Is_Any_Function_Frame(ftemp)) + continue; + if (Is_Function_Frame_Fulfilling(ftemp)) + continue; + assert( + f->out < ftemp->args_head || + f->out >= ftemp->args_head + FRM_NUM_ARGS(ftemp) + ); + } + + // The caller must preload ->value with the first value to process. It + // may be resident in the array passed that will be used to fetch further + // values, or it may not. + // + assert(f->value); + + assert(f->flags.bits & NODE_FLAG_END); + assert(NOT(f->flags.bits & NODE_FLAG_CELL)); + + // f->label is set to NULL by Do_Core() + +#if !defined(NDEBUG) + f->label_debug = NULL; + + if ( + NOT(FRM_IS_VALIST(f)) + && GET_SER_FLAG(f->source.array, SERIES_FLAG_FILE_LINE) + ){ + f->file_debug = cast( + const char*, STR_HEAD(SER(f->source.array)->link.filename) + ); + f->line_debug = SER(f->source.array)->misc.line; + } + else { + f->file_debug = "(no file info)"; + f->line_debug = 0; + } +#endif + + // All callers should ensure that the type isn't an END marker before + // bothering to invoke Do_Core(). + // + assert(NOT_END(f->value)); +} + + +// These are checks common to Expression and Exit checks (hence also common +// to the "end of Start" checks, since that runs on the first expression) +// +static void Do_Core_Shared_Checks_Debug(REBFRM *f) { + // + // There shouldn't have been any "accumulated state", in the sense that + // we should be back where we started in terms of the data stack, the + // mold buffer position, the outstanding manual series allocations, etc. + // + // Because this check is a bit expensive it is lightened up and used in + // the exit case only. But re-enable it to help narrowing down an + // imbalanced state discovered on an exit. + // +#ifdef BALANCE_CHECK_EVERY_EVALUATION_STEP + ASSERT_STATE_BALANCED(&f->state_debug); +#endif + + assert(f == FS_TOP); + assert(f->state_debug.top_chunk == TG_Top_Chunk); + /* assert(DSP == f->dsp_orig); */ // !!! not true now with push SET-WORD! + + if (f->flags.bits & DO_FLAG_VA_LIST) + assert(f->index == TRASHED_INDEX); + else { + assert( + f->index != TRASHED_INDEX + && f->index != END_FLAG + && f->index != THROWN_FLAG + && f->index != VA_LIST_FLAG + ); // END, THROWN, VA_LIST only used by wrappers + } + + // If this fires, it means that Flip_Series_To_White was not called an + // equal number of times after Flip_Series_To_Black, which means that + // the custom marker on series accumulated. + // + assert(TG_Num_Black_Series == 0); + + if (f->gotten != END) { + assert(IS_WORD(f->value)); // may not match eval_type at this point + assert(Get_Opt_Var_May_Fail(f->value, f->specifier) == f->gotten); + } + + //=//// ^-- ABOVE CHECKS *ALWAYS* APPLY ///////////////////////////////=// + + if (IS_END(f->value)) + return; + + if (NOT_END(f->out) && THROWN(f->out)) + return; + + assert(f->kind_debug == VAL_TYPE(f->value)); + + //=//// v-- BELOW CHECKS ONLY APPLY IN EXITS CASE WITH MORE CODE //////=// + + // The eval_type is expected to be calculated already. Should match + // f->value, with special exemption for optimized lookback calls + // coming from Do_Next_In_Subframe_Throws() + // + assert( + ( + f->eval_type == REB_FUNCTION + && (IS_WORD(f->value) || IS_FUNCTION(f->value)) + ) + || f->eval_type == VAL_TYPE(f->value) + ); + + assert(f->value); + assert(NOT_END(f->value)); + assert(NOT(THROWN(f->value))); + ASSERT_VALUE_MANAGED(f->value); + assert(f->value != f->out); + + //=//// ^-- ADD CHECKS EARLIER THAN HERE IF THEY SHOULD ALWAYS RUN ////=// +} + + +// +// Do_Core_Expression_Checks_Debug: C +// +// The iteration preamble takes care of clearing out variables and preparing +// the state for a new "/NEXT" evaluation. It's a way of ensuring in the +// debug build that one evaluation does not leak data into the next, and +// making the code shareable allows code paths that jump to later spots +// in the switch (vs. starting at the top) to reuse the work. +// +REBUPT Do_Core_Expression_Checks_Debug(REBFRM *f) { + + assert(f == FS_TOP); // should be topmost frame, still + + Do_Core_Shared_Checks_Debug(f); + + // Once a throw is started, no new expressions may be evaluated until + // that throw gets handled. + // + assert(IS_UNREADABLE_IF_DEBUG(&TG_Thrown_Arg)); + + assert(f->label == NULL); // release build initializes this + +#if !defined(NDEBUG) + assert(f->label_debug == NULL); // marked debug to point out debug only +#endif + + // Make sure `cell` is trash in debug build if not doing a `reevaluate`. + // It does not have to be GC safe (for reasons explained below). We + // also need to reset evaluation to normal vs. a kind of "inline quoting" + // in case EVAL/ONLY had enabled that. + // +#if !defined(NDEBUG) + if (f->value != &f->cell) + TRASH_CELL_IF_DEBUG(&f->cell); +#endif + + // Trash call variables in debug build to make sure they're not reused. + // Note that this call frame will *not* be seen by the GC unless it gets + // chained in via a function execution, so it's okay to put "non-GC safe" + // trash in at this point...though by the time of that call, they must + // hold valid values. + + TRASH_POINTER_IF_DEBUG(f->param); + TRASH_POINTER_IF_DEBUG(f->arg); + TRASH_POINTER_IF_DEBUG(f->refine); + + TRASH_POINTER_IF_DEBUG(f->args_head); + TRASH_POINTER_IF_DEBUG(f->varlist); + + TRASH_POINTER_IF_DEBUG(f->original); + TRASH_POINTER_IF_DEBUG(f->phase); + TRASH_POINTER_IF_DEBUG(f->binding); + + // Mutate va_list sources into arrays at fairly random moments in the + // debug build. It should be able to handle it at any time. + // + if ((f->flags.bits & DO_FLAG_VA_LIST) && SPORADICALLY(50)) { + const REBOOL truncated = TRUE; + Reify_Va_To_Array_In_Frame(f, truncated); + } + + // We bound the count at the max unsigned 32-bit, since otherwise it would + // roll over to zero and print a message that wasn't asked for, which + // is annoying even in a debug build. (It's actually a REBUPT, so this + // wastes possible bits in the 64-bit build, but there's no MAX_REBUPT.) + // + if (TG_Do_Count < MAX_U32) + f->do_count_debug = ++TG_Do_Count; + + return f->do_count_debug; +} + + +// +// Do_Core_Exit_Checks_Debug: C +// +void Do_Core_Exit_Checks_Debug(REBFRM *f) { + // + // To keep from slowing down the debug build too much, this is not put in + // the shared checks. But if it fires and it's hard to figure out which + // exact cycle caused the problem, re-add it in the shared checks. + // + ASSERT_STATE_BALANCED(&f->state_debug); + + Do_Core_Shared_Checks_Debug(f); + + if (NOT_END(f->value) && NOT(f->flags.bits & DO_FLAG_VA_LIST)) { + assert( + (f->index <= ARR_LEN(f->source.array)) + || ( + ( + (f->pending && IS_END(f->pending)) + || THROWN(f->out) + ) + && f->index == ARR_LEN(f->source.array) + 1 + ) + ); + } + + if (f->flags.bits & DO_FLAG_TO_END) + assert(THROWN(f->out) || IS_END(f->value)); + + // Function execution should have written *some* actual output value. + // checking the VAL_TYPE() is enough to make sure it's not END or trash + // + assert(VAL_TYPE(f->out) <= REB_MAX_VOID); + + if (NOT(THROWN(f->out))) { + assert(f->label == NULL); + ASSERT_VALUE_MANAGED(f->out); + } +} + +#endif diff --git a/src/core/d-legacy.c b/src/core/d-legacy.c new file mode 100644 index 0000000000..a5ea5302a6 --- /dev/null +++ b/src/core/d-legacy.c @@ -0,0 +1,137 @@ +// +// File: %d-legacy.h +// Summary: "Legacy Support Routines for Debug Builds" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2016 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// In order to make porting code from R3-Alpha or Rebol2 easier, Ren-C set +// up several LEGACY() switches and a mode. The switches are +// intended to only be available in debug builds, so that compatibility for +// legacy code will not be a runtime cost in the release build. However, +// they could be enabled by any sufficiently motivated individual who +// wished to build a version of the interpreter with the old choices in an +// optimized build as well. +// +// Support routines for legacy mode are quarantined here when possible. +// + +#include "sys-core.h" + + +#if !defined(NDEBUG) + +// +// In_Legacy_Function_Debug: C +// +// Determine if a legacy function is "in effect" currently. To the extent +// that compatibility in debug builds or legacy mode with R3-Alpha is +// "important" this should be used sparingly, because code can be bound and +// passed around in blocks. So you might be running a legacy function passed +// new code or new code passed legacy code (e.g. a mezzanine that uses DO) +// +REBOOL In_Legacy_Function_Debug(void) +{ + // Find the first bit of code that's actually running ordinarily in + // the evaluator, and not just dispatching. + // + REBFRM *f = FS_TOP; + for (; f != NULL; f = f->prior) { + if (f->flags.bits & DO_FLAG_VA_LIST) + return FALSE; // no source array to look at + + break; // whatever's dispatching here, there is a source array + } + + if (f == NULL) + return FALSE; + + // Check the flag on the source series + // + if (GET_SER_INFO(f->source.array, SERIES_INFO_LEGACY_DEBUG)) + return TRUE; + + return FALSE; +} + + +// +// Legacy_Convert_Function_Args: C +// +// R3-Alpha and Rebol2 used BLANK for unused refinements and arguments to +// a refinement which is not present. Ren-C uses FALSE for unused refinements +// and arguments to unused refinements are not set. +// +// Could be woven in efficiently, but as it's a debug build only feature it's +// better to isolate it into a post-phase. This improves the readability of +// the mainline code. +// +// Trigger is when OPTIONS_REFINEMENTS_TRUE is set during function creation, +// which will give it FUNC_FLAG_LEGACY_DEBUG--leading to this being used. +// +void Legacy_Convert_Function_Args(REBFRM *f) +{ + REBVAL *param = FUNC_FACADE_HEAD(f->phase); + REBVAL *arg = f->args_head; + + REBOOL set_blank = FALSE; + + for (; NOT_END(param); ++param, ++arg) { + switch (VAL_PARAM_CLASS(param)) { + case PARAM_CLASS_REFINEMENT: + if (IS_LOGIC(arg)) { + if (VAL_LOGIC(arg)) + set_blank = FALSE; + else { + Init_Blank(arg); + set_blank = TRUE; + } + } + else assert(FALSE); + break; + + case PARAM_CLASS_LOCAL: + assert(IS_VOID(arg)); // keep *pure* locals as void, even in legacy + break; + + case PARAM_CLASS_RETURN: + case PARAM_CLASS_LEAVE: + assert(IS_FUNCTION(arg) || IS_VOID(arg)); + break; + + case PARAM_CLASS_NORMAL: + case PARAM_CLASS_HARD_QUOTE: + case PARAM_CLASS_SOFT_QUOTE: + if (set_blank) { + assert(IS_VOID(arg)); + Init_Blank(arg); + } + break; + + default: + assert(FALSE); + } + } +} + +#endif diff --git a/src/core/d-print.c b/src/core/d-print.c index 06a6137fd0..0004236a79 100644 --- a/src/core/d-print.c +++ b/src/core/d-print.c @@ -1,39 +1,41 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: d-print.c -** Summary: low-level console print interface -** Section: debug -** Author: Carl Sassenrath -** Notes: -** R3 is intended to run on fairly minimal devices, so this code may -** duplicate functions found in a typical C lib. That's why output -** never uses standard clib printf functions. -** -***********************************************************************/ - -/* - Print_OS... - low level OS output functions - Out_... - general console output functions - Debug_... - debug mode (trace) output functions +// +// File: %d-print.c +// Summary: "low-level console print interface" +// Section: debug +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// R3 is intended to run on fairly minimal devices, so this code may +// duplicate functions found in a typical C lib. That's why output +// never uses standard clib printf functions. +// + +/* + Print_OS... - low level OS output functions + Out_... - general console output functions + Debug_... - debug mode (trace) output functions */ #include "sys-core.h" @@ -43,831 +45,752 @@ static REBREQ *Req_SIO; /*********************************************************************** ** -** Lower Level Print Interface +** Lower Level Print Interface ** ***********************************************************************/ -/*********************************************************************** -** -*/ void Init_StdIO(void) -/* -***********************************************************************/ +// +// Startup_StdIO: C +// +void Startup_StdIO(void) { - //OS_CALL_DEVICE(RDI_STDIO, RDC_INIT); - Req_SIO = OS_MAKE_DEVREQ(RDI_STDIO); - if (!Req_SIO) Crash(RP_IO_ERROR); + //OS_CALL_DEVICE(RDI_STDIO, RDC_INIT); + Req_SIO = OS_MAKE_DEVREQ(RDI_STDIO); + if (!Req_SIO) + fail (Error_Io_Error_Raw()); - // The device is already open, so this call will just setup - // the request fields properly. - OS_DO_DEVICE(Req_SIO, RDC_OPEN); + // The device is already open, so this call will just setup + // the request fields properly. + OS_DO_DEVICE(Req_SIO, RDC_OPEN); } -/*********************************************************************** -** -*/ static void Print_OS_Line(void) -/* -** Print a new line. -** -***********************************************************************/ +// +// Shutdown_StdIO: C +// +void Shutdown_StdIO(void) { - Req_SIO->data = BYTES("\n"); - Req_SIO->length = 1; - Req_SIO->actual = 0; - - OS_DO_DEVICE(Req_SIO, RDC_WRITE); - - if (Req_SIO->error) Crash(RP_IO_ERROR); + // !!! There is no OS_FREE_DEVREQ. Should there be? Should this + // include an OS_ABORT_DEVICE? + OS_FREE(Req_SIO); } -/*********************************************************************** -** -*/ static void Prin_OS_String(REBYTE *bp, REBINT len, REBOOL uni) -/* -** Print a string, but no line terminator or space. -** -** The width of the input is specified by UNI. -** -***********************************************************************/ +// +// Print_OS_Line: C +// +// Print a new line. +// +void Print_OS_Line(void) { - #define BUF_SIZE 1024 - REBYTE buffer[BUF_SIZE]; // on stack - REBYTE *buf = &buffer[0]; - REBINT n; - REBCNT len2; - REBUNI *up = (REBUNI*)bp; - - if (!bp) Crash(RP_NO_PRINT_PTR); - - // Determine length if not provided: - if (len == UNKNOWN) len = uni ? wcslen(up) : LEN_BYTES(bp); - - SET_FLAG(Req_SIO->flags, RRF_FLUSH); - - Req_SIO->actual = 0; - Req_SIO->data = buf; - buf[0] = 0; // for debug tracing - - while ((len2 = len) > 0) { - - Do_Signals(); - - // returns # of chars, size returns buf bytes output - n = Encode_UTF8(buf, BUF_SIZE-4, uni ? (void*)up : (void*)bp, &len2, uni, OS_CRLF); - if (n == 0) break; + // !!! Don't put const literal directly into mutable Req_SIO->data + static REBYTE newline[] = "\n"; - Req_SIO->length = len2; // byte size of buffer + Req_SIO->common.data = newline; + Req_SIO->length = 1; + Req_SIO->actual = 0; - if (uni) up += n; else bp += n; - len -= n; + OS_DO_DEVICE(Req_SIO, RDC_WRITE); - OS_DO_DEVICE(Req_SIO, RDC_WRITE); - if (Req_SIO->error) Crash(RP_IO_ERROR); - } + if (Req_SIO->error) + panic ("IO error in Print_OS_Line"); // !!! could/should this fail()? } -/*********************************************************************** -** -*/ void Out_Value(REBVAL *value, REBCNT limit, REBOOL mold, REBINT lines) -/* -***********************************************************************/ -{ - Print_Value(value, limit, mold); // higher level! - for (; lines > 0; lines--) Print_OS_Line(); -} - - -/*********************************************************************** -** -*/ void Out_Str(REBYTE *bp, REBINT lines) -/* -***********************************************************************/ -{ - Prin_OS_String(bp, UNKNOWN, 0); - for (; lines > 0; lines--) Print_OS_Line(); -} - - -/*********************************************************************** -** -** Debug Print Interface -** -** If the Trace_Buffer exists, then output goes there, -** otherwise output goes to OS output. -** -***********************************************************************/ - - -/*********************************************************************** -** -*/ void Enable_Backtrace(REBFLG on) -/* -***********************************************************************/ +// +// Prin_OS_String: C +// +// Print a string (with no line terminator). +// +// The encoding options are OPT_ENC_XXX flags OR'd together. +// +void Prin_OS_String(const void *p, REBCNT len, REBFLGS opts) { - if (on) { - if (Trace_Limit == 0) { - Trace_Limit = 100000; - Trace_Buffer = Make_Binary(Trace_Limit); - KEEP_SERIES(Trace_Buffer, "trace-buffer"); // !!! use better way - } - } - else { - if (Trace_Limit) Free_Series(Trace_Buffer); - Trace_Limit = 0; - Trace_Buffer = 0; - } -} + #define BUF_SIZE 1024 + REBYTE buffer[BUF_SIZE]; // on stack + REBYTE *buf = &buffer[0]; + REBCNT len2; + const REBOOL unicode = LOGICAL(opts & OPT_ENC_UNISRC); + const REBYTE *bp = unicode ? NULL : cast(const REBYTE *, p); + const REBUNI *up = unicode ? cast(const REBUNI *, p) : NULL; -/*********************************************************************** -** -*/ void Display_Backtrace(REBCNT lines) -/* -***********************************************************************/ -{ - REBCNT tail; - REBCNT i; - - if (Trace_Limit > 0) { - tail = Trace_Buffer->tail; - i = tail - 1; - for (lines++ ;lines > 0; lines--, i--) { - i = Find_Str_Char(Trace_Buffer, 0, i, tail, -1, LF, 0); - if (i == NOT_FOUND || i == 0) { - i = 0; - break; - } - } - - if (lines == 0) i += 2; // start of next line - Prin_OS_String(BIN_SKIP(Trace_Buffer, i), tail-i, 0); - //RESET_SERIES(Trace_Buffer); - } - else { - Out_Str("backtrace not enabled", 1); - } -} + if (p == NULL) + fail (Error_No_Print_Ptr_Raw()); + // Determine length if not provided: + if (len == UNKNOWN) len = unicode ? Strlen_Uni(up) : LEN_BYTES(bp); -/*********************************************************************** -** -*/ void Debug_String(REBYTE *bp, REBINT len, REBOOL uni, REBINT lines) -/* -***********************************************************************/ -{ - REBUNI *up = (REBUNI*)bp; - REBUNI uc; - - if (Trace_Limit > 0) { - if (Trace_Buffer->tail >= Trace_Limit) - Remove_Series(Trace_Buffer, 0, 2000); - if (len == UNKNOWN) len = uni ? wcslen(up) : LEN_BYTES(bp); - // !!! account for unicode! - for (; len > 0; len--) { - uc = uni ? *up++ : *bp++; - Append_Byte(Trace_Buffer, uc); - } - //Append_Bytes_Len(Trace_Buffer, bp, len); - for (; lines > 0; lines--) Append_Byte(Trace_Buffer, LF); - } - else { - Prin_OS_String(bp, len, uni); - for (; lines > 0; lines--) Print_OS_Line(); - } -} + SET_FLAG(Req_SIO->flags, RRF_FLUSH); + Req_SIO->actual = 0; + Req_SIO->common.data = buf; + buffer[0] = 0; // for debug tracing -/*********************************************************************** -** -*/ void Debug_Line(void) -/* -***********************************************************************/ -{ - Debug_String("", UNKNOWN, 0, 1); -} + DECLARE_LOCAL (result); + SET_END(result); + if (opts & OPT_ENC_RAW) { + if (Do_Signals_Throws(result)) + fail (Error_No_Catch_For_Throw(result)); -/*********************************************************************** -** -*/ void Debug_Str(REBYTE *str) -/* -** Print a string followed by a newline. -** -***********************************************************************/ -{ - Debug_String(str, UNKNOWN, 0, 1); -} + assert(IS_END(result)); + // Used by verbatim terminal output, e.g. print of a BINARY! + assert(!unicode); + Req_SIO->length = len; -/*********************************************************************** -** -*/ void Debug_Uni(REBSER *ser) -/* -** Print debug unicode string followed by a newline. -** -***********************************************************************/ -{ - REBCNT ul; - REBCNT bl; - REBYTE buf[1024]; - REBUNI *up = UNI_HEAD(ser); - REBINT size = Length_As_UTF8(up, SERIES_TAIL(ser), TRUE, OS_CRLF); - - while (size > 0) { - ul = Encode_UTF8(buf, MIN(size, 1020), up, &bl, TRUE, OS_CRLF); - Debug_String(buf, bl, 0, 0); - size -= ul; - up += ul; - } - - Debug_Line(); -} + // Mutability cast, but RDC_WRITE should not be modifying the buffer + // (doing so could yield undefined behavior) + Req_SIO->common.data = m_cast(REBYTE *, bp); + OS_DO_DEVICE(Req_SIO, RDC_WRITE); + if (Req_SIO->error) + fail (Error_Io_Error_Raw()); + } + else { + while ((len2 = len) > 0) { + if (Do_Signals_Throws(result)) + fail (Error_No_Catch_For_Throw(result)); -/*********************************************************************** -** -*/ void Debug_Series(REBSER *ser) -/* -***********************************************************************/ -{ - if (BYTE_SIZE(ser)) Debug_Str(BIN_HEAD(ser)); - else Debug_Uni(ser); -} + assert(IS_END(result)); + Req_SIO->length = Encode_UTF8( + buf, + BUF_SIZE - 4, + unicode ? cast(const void *, up) : cast(const void *, bp), + &len2, + opts + ); -/*********************************************************************** -** -*/ void Debug_Num(REBYTE *str, REBINT num) -/* -** Print a string followed by a number. -** -***********************************************************************/ -{ - REBYTE buf[40]; + if (unicode) up += len2; else bp += len2; + len -= len2; - Debug_String(str, UNKNOWN, 0, 0); - Debug_String(" ", 1, 0, 0); - Form_Hex_Pad(buf, num, 8); - Debug_Str(buf); + OS_DO_DEVICE(Req_SIO, RDC_WRITE); + if (Req_SIO->error) + fail (Error_Io_Error_Raw()); + } + } } -/*********************************************************************** -** -*/ void Debug_Chars(REBYTE chr, REBCNT num) -/* -** Print a number of spaces. -** -***********************************************************************/ -{ - REBYTE spaces[100]; - - memset(spaces, chr, MIN(num, 99)); - spaces[num] = 0; - Debug_String(spaces, num, 0, 0); -} - /*********************************************************************** ** -*/ void Debug_Space(REBCNT num) -/* -** Print a number of spaces. +** Debug Print Interface ** -***********************************************************************/ -{ - if (num > 0) Debug_Chars(' ', num); -} - - -/*********************************************************************** -** -*/ void Debug_Word(REBVAL *word) -/* -** Print a REBOL word. +** If the Trace_Buffer exists, then output goes there, +** otherwise output goes to OS output. ** ***********************************************************************/ -{ - Debug_Str(Get_Word_Name(word)); -} -/*********************************************************************** -** -*/ void Debug_Type(REBVAL *value) -/* -** Print a REBOL datatype name. -** -***********************************************************************/ +// +// Enable_Backtrace: C +// +void Enable_Backtrace(REBOOL on) { - if (VAL_TYPE(value) < REB_MAX) Debug_Str(Get_Type_Name(value)); - else Debug_Str("TYPE?!"); + if (on) { + if (Trace_Limit == 0) { + Trace_Limit = 100000; + Trace_Buffer = Make_Binary(Trace_Limit); + } + } + else { + if (Trace_Limit) Free_Series(Trace_Buffer); + Trace_Limit = 0; + Trace_Buffer = 0; + } } -/*********************************************************************** -** -*/ void Debug_Value(REBVAL *value, REBCNT limit, REBOOL mold) -/* -***********************************************************************/ +// +// Display_Backtrace: C +// +void Display_Backtrace(REBCNT lines) { - Print_Value(value, limit, mold); // higher level! -} + REBCNT tail; + REBCNT i; + if (Trace_Limit > 0) { + tail = SER_LEN(Trace_Buffer); + i = tail - 1; + for (lines++ ;lines > 0; lines--, i--) { + i = Find_Str_Char(LF, Trace_Buffer, 0, i, tail, -1, 0); + if (i == NOT_FOUND || i == 0) { + i = 0; + break; + } + } -/*********************************************************************** -** -*/ void Debug_Values(REBVAL *value, REBCNT count, REBCNT limit) -/* -***********************************************************************/ -{ - REBSER *out; - REBCNT i1; - REBCNT i2; - REBUNI uc, pc = ' '; - REBCNT n; - - for (n = 0; n < count; n++, value++) { - Debug_Space(1); - if (n > 0 && VAL_TYPE(value) <= REB_NONE) Debug_Chars('.', 1); - else { - out = Mold_Print_Value(value, limit, TRUE); // shared mold buffer - for (i1 = i2 = 0; i1 < out->tail; i1++) { - uc = GET_ANY_CHAR(out, i1); - if (uc < ' ') uc = ' '; - if (uc > ' ' || pc > ' ') SET_ANY_CHAR(out, i2++, uc); - pc = uc; - } - SET_ANY_CHAR(out, i2, 0); - Debug_String(out->data, i2, TRUE, 0); - } - } - Debug_Line(); + if (lines == 0) i += 2; // start of next line + Prin_OS_String(BIN_AT(Trace_Buffer, i), tail - i, OPT_ENC_CRLF_MAYBE); + } + else { + Debug_Fmt(RM_BACKTRACE_NOT_ENABLED); + } } -/*********************************************************************** -** -*/ void Debug_Buf(const REBYTE *fmt, va_list args) -/* -** Lower level formatted print for debugging purposes. -** -** 1. Does not support UNICODE. -** 2. Does not auto-expand the output buffer. -** 3. No termination buffering (limited length). -** -** Print using a format string and variable number -** of arguments. All args must be long word aligned -** (no short or char sized values unless recast to long). -** -** Output will be held in series print buffer and -** will not exceed its max size. No line termination -** is supplied after the print. -** -***********************************************************************/ +// +// Debug_String: C +// +void Debug_String(const void *p, REBCNT len, REBOOL unicode, REBINT lines) { - REBSER *buf = BUF_PRINT; - REBCNT len; - REBCNT n; - REBYTE *bp; - REBCNT tail; + REBUNI uni; + const REBYTE *bp = unicode ? NULL : cast(const REBYTE *, p); + const REBUNI *up = unicode ? cast(const REBUNI *, p) : NULL; - if (!buf) Crash(RP_NO_BUFFER); + REBOOL disabled = GC_Disabled; + GC_Disabled = TRUE; - RESET_SERIES(buf); + if (Trace_Limit > 0) { + if (SER_LEN(Trace_Buffer) >= Trace_Limit) + Remove_Series(Trace_Buffer, 0, 2000); - // Limits output to size of buffer, will not expand it: - bp = Form_Var_Args(STR_HEAD(buf), SERIES_REST(buf)-1, fmt, args); - tail = bp - STR_HEAD(buf); + if (len == UNKNOWN) len = unicode ? Strlen_Uni(up) : LEN_BYTES(bp); - for (n = 0; n < tail; n += len) { - len = LEN_BYTES(STR_SKIP(buf, n)); - if (len > 1024) len = 1024; - Debug_String(STR_SKIP(buf, n), len, 0, 0); - } -} + for (; len > 0; len--) { + uni = unicode ? *up++ : *bp++; + Append_Codepoint_Raw(Trace_Buffer, uni); + } + for (; lines > 0; lines--) Append_Codepoint_Raw(Trace_Buffer, LF); + /* Append_Unencoded_Len(Trace_Buffer, bp, len); */ // !!! alternative? + } + else { + Prin_OS_String( + p, len, (unicode ? OPT_ENC_UNISRC : 0) | OPT_ENC_CRLF_MAYBE + ); + for (; lines > 0; lines--) Print_OS_Line(); + } -/*********************************************************************** -** -*/ void Debug_Fmt_(REBYTE *fmt, ...) -/* -** Print using a format string and variable number -** of arguments. All args must be long word aligned -** (no short or char sized values unless recast to long). -** Output will be held in series print buffer and -** will not exceed its max size. No line termination -** is supplied after the print. -** -***********************************************************************/ -{ - va_list args; - va_start(args, fmt); - Debug_Buf(fmt, args); - va_end(args); + assert(GC_Disabled == TRUE); + GC_Disabled = disabled; } -/*********************************************************************** -** -*/ void Debug_Fmt(const REBYTE *fmt, ...) -/* -** Print using a formatted string and variable number -** of arguments. All args must be long word aligned -** (no short or char sized values unless recast to long). -** Output will be held in a series print buffer and -** will not exceed its max size. A line termination -** is supplied after the print. -** -***********************************************************************/ +// +// Debug_Line: C +// +void Debug_Line(void) { - va_list args; - va_start(args, fmt); - Debug_Buf(fmt, args); - Debug_Line(); - va_end(args); + Debug_String(cb_cast(""), UNKNOWN, FALSE, 1); } -/*********************************************************************** -** -*/ REBFLG Echo_File(REBCHR *file) -/* -***********************************************************************/ -{ - Req_SIO->file.path = file; - return (DR_ERROR != OS_DO_DEVICE(Req_SIO, RDC_CREATE)); +// +// Debug_Str: C +// +// Print a string followed by a newline. +// +void Debug_Str(const char *str) +{ + Debug_String(cb_cast(str), UNKNOWN, FALSE, 1); } -#ifdef unused -/*********************************************************************** -** -*/ REBYTE *Input_Str(void) -/* -** Very simple string input, limited to 255 chars. -** -***********************************************************************/ +// +// Debug_Uni: C +// +// Print debug unicode string followed by a newline. +// +void Debug_Uni(REBSER *ser) { - static REBYTE buffer[256]; - REBINT res; - - Req_SIO->data = buffer; - Req_SIO->length = 255; - Req_SIO->actual = 0; - res = OS_DO_DEVICE(Req_SIO, RDC_READ); - if (Req_SIO->error) Crash(RP_IO_ERROR); - //if (res > 0) Wait_Device(Req_SIO, 1000); // pending - //if (res < 0) return 0; // error - - return buffer; -} -#endif - + const REBFLGS encopts = OPT_ENC_UNISRC | OPT_ENC_CRLF_MAYBE; + REBCNT ul; + REBCNT bl; + REBYTE buf[1024]; + REBUNI *up = UNI_HEAD(ser); + REBCNT size = SER_LEN(ser); -/*********************************************************************** -** -*/ REBYTE *Form_Hex_Pad(REBYTE *buf, REBU64 val, REBINT len) -/* -** Form an integer hex string in the given buffer with a -** width padded out with zeros. -** If len = 0 and val = 0, a null string is formed. -** Does not insert a #. -** Make sure you have room in your buffer before calling this! -** -***********************************************************************/ + REBOOL disabled = GC_Disabled; + GC_Disabled = TRUE; + + while (size > 0) { + ul = size; + bl = Encode_UTF8(buf, 1020, up, &ul, encopts); + Debug_String(buf, bl, FALSE, 0); + size -= ul; + up += ul; + } + + Debug_Line(); + + assert(GC_Disabled == TRUE); + GC_Disabled = disabled; +} + + +// +// Debug_Chars: C +// +// Print a number of spaces. +// +void Debug_Chars(REBYTE chr, REBCNT num) { - REBYTE buffer[MAX_HEX_LEN+4]; - REBYTE *bp = (REBYTE*)(buffer + MAX_HEX_LEN + 1); - REBU64 sgn; - - sgn = (val < 0) ? -1 : 0; - - len = MIN(len, MAX_HEX_LEN); - *bp-- = 0; - while (val != sgn && len > 0) { - *bp-- = Hex_Digits[val & 0xf]; - val >>= 4; - len--; - } - for (; len > 0; len--) *bp-- = (REBYTE)(sgn ? 'F' : '0'); - bp++; - while (NZ(*buf++ = *bp++)); - return buf-1; -} + REBYTE spaces[100]; + memset(spaces, chr, MIN(num, 99)); + spaces[num] = 0; + Debug_String(spaces, num, FALSE, 0); +} + + +// +// Debug_Space: C +// +// Print a number of spaces. +// +void Debug_Space(REBCNT num) +{ + if (num > 0) Debug_Chars(' ', num); +} + + +// +// Debug_Values: C +// +void Debug_Values(const RELVAL *value, REBCNT count, REBCNT limit) +{ + REBCNT i1; + REBCNT i2; + REBUNI uc, pc = ' '; + REBCNT n; + + for (n = 0; n < count; n++, value++) { + Debug_Space(1); + if (n > 0 && VAL_TYPE(value) <= REB_BLANK) Debug_Chars('.', 1); + else { + REB_MOLD mo; + CLEARS(&mo); + if (limit != 0) { + SET_FLAG(mo.opts, MOPT_LIMIT); + mo.limit = limit; + } + Push_Mold(&mo); + + Mold_Value(&mo, value, TRUE); + Throttle_Mold(&mo); // not using Pop_Mold(), must do explicitly + + for (i1 = i2 = mo.start; i1 < SER_LEN(mo.series); i1++) { + uc = GET_ANY_CHAR(mo.series, i1); + if (uc < ' ') uc = ' '; + if (uc > ' ' || pc > ' ') SET_ANY_CHAR(mo.series, i2++, uc); + pc = uc; + } + SET_ANY_CHAR(mo.series, i2, '\0'); + assert(SER_WIDE(mo.series) == sizeof(REBUNI)); + Debug_String( + UNI_AT(mo.series, mo.start), + i2 - mo.start, + TRUE, + 0 + ); + + Drop_Mold(&mo); + } + } + Debug_Line(); +} + + +// +// Debug_Buf: C +// +// (va_list by pointer: http://stackoverflow.com/a/3369762/211160) +// +// Lower level formatted print for debugging purposes. +// +// 1. Does not support UNICODE. +// 2. Does not auto-expand the output buffer. +// 3. No termination buffering (limited length). +// +// Print using a format string and variable number +// of arguments. All args must be long word aligned +// (no short or char sized values unless recast to long). +// +// Output will be held in series print buffer and +// will not exceed its max size. No line termination +// is supplied after the print. +// +void Debug_Buf(const char *fmt, va_list *vaptr) +{ + REBOOL disabled = GC_Disabled; + GC_Disabled = TRUE; + + REB_MOLD mo; + CLEARS(&mo); + Push_Mold(&mo); + + Form_Args_Core(&mo, fmt, vaptr); + + REBSER *bytes = Pop_Molded_UTF8(&mo); + + // Don't send the Debug_String routine more than 1024 bytes at a time, + // but chunk it to 1024 byte sections. + // + // !!! What's the rationale for this? + // + REBCNT len = SER_LEN(bytes); + + REBCNT n = 0; + while (n < len) { + REBCNT sub = len - n; + if (sub > 1024) + sub = 1024; + Debug_String(BIN_AT(bytes, n), sub, FALSE, 0); + n += sub; + } + + Free_Series(bytes); + + assert(GC_Disabled == TRUE); + GC_Disabled = disabled; +} + + +// +// Debug_Fmt_: C +// +// Print using a format string and variable number +// of arguments. All args must be long word aligned +// (no short or char sized values unless recast to long). +// Output will be held in series print buffer and +// will not exceed its max size. No line termination +// is supplied after the print. +// +void Debug_Fmt_(const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + Debug_Buf(fmt, &va); + va_end(va); +} + + +// +// Debug_Fmt: C +// +// Print using a formatted string and variable number +// of arguments. All args must be long word aligned +// (no short or char sized values unless recast to long). +// Output will be held in a series print buffer and +// will not exceed its max size. A line termination +// is supplied after the print. +// +void Debug_Fmt(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Debug_Buf(fmt, &args); + Debug_Line(); + va_end(args); +} + + +// +// Form_Hex_Pad: C +// +// Form an integer hex string in the given buffer with a +// width padded out with zeros. +// If len = 0 and val = 0, a null string is formed. +// Does not insert a #. +// Make sure you have room in your buffer before calling this! +// +REBYTE *Form_Hex_Pad(REBYTE *buf, REBI64 val, REBINT len) +{ + REBYTE buffer[MAX_HEX_LEN+4]; + REBYTE *bp = buffer + MAX_HEX_LEN + 1; + REBI64 sgn; + + // !!! val parameter was REBI64 at one point; changed to REBI64 + // as this does signed comparisons (val < 0 was never true...) + sgn = (val < 0) ? -1 : 0; + + len = MIN(len, MAX_HEX_LEN); + *bp-- = 0; + while (val != sgn && len > 0) { + *bp-- = Hex_Digits[val & 0xf]; + val >>= 4; + len--; + } + for (; len > 0; len--) *bp-- = (sgn != 0) ? 'F' : '0'; + bp++; + while ((*buf++ = *bp++)); + return buf-1; +} + + +// +// Form_Hex2: C +// +// Convert byte-sized int to xx format. Very fast. +// +REBYTE *Form_Hex2(REBYTE *bp, REBCNT val) +{ + bp[0] = Hex_Digits[(val & 0xf0) >> 4]; + bp[1] = Hex_Digits[val & 0xf]; + bp[2] = 0; + return bp+2; +} + + +// +// Form_Hex2_Uni: C +// +// Convert byte-sized int to unicode xx format. Very fast. +// +REBUNI *Form_Hex2_Uni(REBUNI *up, REBCNT val) +{ + up[0] = Hex_Digits[(val & 0xf0) >> 4]; + up[1] = Hex_Digits[val & 0xf]; + up[2] = 0; + return up+2; +} + + +// +// Form_Hex_Esc_Uni: C +// +// Convert byte int to %xx format (in unicode destination) +// +REBUNI *Form_Hex_Esc_Uni(REBUNI *up, REBUNI c) +{ + up[0] = '%'; + up[1] = Hex_Digits[(c & 0xf0) >> 4]; + up[2] = Hex_Digits[c & 0xf]; + up[3] = 0; + return up+3; +} + + +// +// Form_RGB_Uni: C +// +// Convert 24 bit RGB to xxxxxx format. +// +REBUNI *Form_RGB_Uni(REBUNI *up, REBCNT val) +{ +#ifdef ENDIAN_LITTLE + up[0] = Hex_Digits[(val >> 4) & 0xf]; + up[1] = Hex_Digits[val & 0xf]; + up[2] = Hex_Digits[(val >> 12) & 0xf]; + up[3] = Hex_Digits[(val >> 8) & 0xf]; + up[4] = Hex_Digits[(val >> 20) & 0xf]; + up[5] = Hex_Digits[(val >> 16) & 0xf]; +#else + up[0] = Hex_Digits[(val >> 28) & 0xf]; + up[1] = Hex_Digits[(val >> 24) & 0xf]; + up[2] = Hex_Digits[(val >> 20) & 0xf]; + up[3] = Hex_Digits[(val >> 16) & 0xf]; + up[4] = Hex_Digits[(val >> 12) & 0xf]; + up[5] = Hex_Digits[(val >> 8) & 0xf]; +#endif + up[6] = 0; -/*********************************************************************** -** -*/ REBYTE *Form_Hex2(REBYTE *bp, REBCNT val) -/* -** Convert byte-sized int to xx format. Very fast. -** -***********************************************************************/ -{ - bp[0] = Hex_Digits[(val & 0xf0) >> 4]; - bp[1] = Hex_Digits[val & 0xf]; - bp[2] = 0; - return bp+2; + return up+6; } -/*********************************************************************** -** -*/ REBUNI *Form_Hex2_Uni(REBUNI *up, REBCNT val) -/* -** Convert byte-sized int to unicode xx format. Very fast. -** -***********************************************************************/ +// +// Form_Uni_Hex: C +// +// Fast var-length hex output for uni-chars. +// Returns next position (just past the insert). +// +REBUNI *Form_Uni_Hex(REBUNI *out, REBCNT n) { - up[0] = Hex_Digits[(val & 0xf0) >> 4]; - up[1] = Hex_Digits[val & 0xf]; - up[2] = 0; - return up+2; -} - + REBUNI buffer[10]; + REBUNI *up = &buffer[10]; -/*********************************************************************** -** -*/ REBUNI *Form_Hex_Esc_Uni(REBUNI *up, REBUNI c) -/* -** Convert byte int to %xx format (in unicode destination) -** -***********************************************************************/ -{ - up[0] = '%'; - up[1] = Hex_Digits[(c & 0xf0) >> 4]; - up[2] = Hex_Digits[c & 0xf]; - up[3] = 0; - return up+3; -} + while (n != 0) { + *(--up) = Hex_Digits[n & 0xf]; + n >>= 4; + } + while (up < &buffer[10]) *out++ = *up++; -/*********************************************************************** -** -*/ REBUNI *Form_RGB_Uni(REBUNI *up, REBCNT val) -/* -** Convert 24 bit RGB to xxxxxx format. -** -***********************************************************************/ -{ - up[0] = Hex_Digits[(val >> 20) & 0xf]; - up[1] = Hex_Digits[(val >> 16) & 0xf]; - up[2] = Hex_Digits[(val >> 12) & 0xf]; - up[3] = Hex_Digits[(val >> 8) & 0xf]; - up[4] = Hex_Digits[(val >> 4) & 0xf]; - up[5] = Hex_Digits[val & 0xf]; - up[6] = 0; - return up+6; + return out; } -/*********************************************************************** -** -*/ REBUNI *Form_Uni_Hex(REBUNI *out, REBCNT n) -/* -** Fast var-length hex output for uni-chars. -** Returns next position (just past the insert). -** -***********************************************************************/ +// +// Form_Args_Core: C +// +// (va_list by pointer: http://stackoverflow.com/a/3369762/211160) +// +// This is an internal routine used for debugging, which is something like +// `printf` (it understands %d, %s, %c) but stripped down in features. +// It also knows how to show REBVAL* values FORMed (%v) or MOLDed (%r), +// as well as REBSER* or REBARR* series molded (%m). +// +// Initially it was considered to be for low-level debug output only. It +// was strictly ASCII, and it only supported a fixed-size output destination +// buffer. The buffer that it used was reused by other routines, and +// nested calls would erase the content. The choice was made to use the +// implementation techniques of MOLD and the "mold stack"...allowing nested +// calls and unicode support. It simplified the code, at the cost of +// becoming slightly more "bootstrapped". +// +void Form_Args_Core(REB_MOLD *mo, const char *fmt, va_list *vaptr) { - REBUNI buffer[10]; - REBUNI *up = &buffer[10]; - - while (n != 0) { - *(--up) = Hex_Digits[n & 0xf]; - n >>= 4; - } - - while (up < &buffer[10]) *out++ = *up++; + REBYTE *cp; + REBINT pad; + REBYTE desc; + REBYTE padding; + REBSER *ser = mo->series; + REBYTE buf[MAX_SCAN_DECIMAL]; - return out; -} - - -/*********************************************************************** -** -*/ REBYTE *Form_Var_Args(REBYTE *bp, REBCNT max, const REBYTE *fmt, va_list args) -/* -** Lower level (debugging) value formatter. -** Can restrict to max char size. -** -***********************************************************************/ -{ - REBYTE *cp; - REBCNT len = 0; - REBINT pad; - REBVAL *vp; - REBYTE desc; - REBSER *ser; - REBVAL value; - REBYTE padding; - REBINT l; + DECLARE_LOCAL (value); - max--; // adjust for the fact that it adds a NULL at the end. + // buffer used for making byte-oriented renderings to add to the REBUNI + // mold series. Should be more formally checked as it's used for + // integers, hex, eventually perhaps other things. + // + assert(MAX_SCAN_DECIMAL >= MAX_HEX_LEN); - //*bp++ = '!'; len++; + for (; *fmt != '\0'; fmt++) { - for (; *fmt && len < max; fmt++) { + // Copy format string until next % escape + // + while ((*fmt != '\0') && (*fmt != '%')) + Append_Codepoint_Raw(ser, *fmt++); - // Copy string until next % escape: - for (; *fmt && *fmt != '%' && len < max; len++) *bp++ = *fmt++; - if (*fmt != '%') break; + if (*fmt != '%') break; - pad = 1; - padding = ' '; - fmt++; // skip % + pad = 1; + padding = ' '; + fmt++; // skip % pick: - switch (desc = *fmt) { - - case '0': - padding = '0'; - case '-': - case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - fmt = Grab_Int((REBYTE*)fmt, &pad); - goto pick; - - case 'd': - l = va_arg(args, REBINT); - cp = Form_Int_Pad(bp, (REBI64)l, max-len, pad, padding); - len += (REBCNT)(cp - bp); - bp = cp; - break; - - case 'D': - cp = Form_Int_Pad(bp, va_arg(args, REBI64), max-len, pad, padding); - len += (REBCNT)(cp - bp); - bp = cp; - break; - - case 's': - cp = va_arg(args, REBYTE *); - if ((REBCNT)cp < 100) cp = (REBYTE*)Bad_Ptr; - if (pad == 1) pad = LEN_BYTES(cp); - if (pad < 0) { - pad = -pad; - pad -= LEN_BYTES(cp); - for (; pad > 0 && len < max; len++, pad--) *bp++ = ' '; - } - for (; *cp && len < max && pad > 0; pad--, len++) *bp++ = *cp++; - for (; pad > 0 && len < max; len++, pad--) *bp++ = ' '; - break; - - case 'r': // use Mold - case 'v': // use Form - vp = va_arg(args, REBVAL *); -mold_value: - // Form the REBOL value into a reused buffer: - ser = Mold_Print_Value(vp, 0, desc != 'v'); - - l = Length_As_UTF8(UNI_HEAD(ser), SERIES_TAIL(ser), TRUE, OS_CRLF); - if (pad != 1 && l > pad) l = pad; - if (l+len >= max) l = max-len-1; - - Encode_UTF8(bp, l, UNI_HEAD(ser), 0, TRUE, OS_CRLF); - - // Filter out CTRL chars: - for (; l > 0; l--, bp++) if (*bp < ' ') *bp = ' '; - break; - - case 'm': // Mold a series - ser = va_arg(args, REBSER *); - Set_Block(&value, ser); - vp = &value; - goto mold_value; - - case 'c': - if (len < max) { - *bp++ = (REBYTE)va_arg(args, REBINT); - len++; - } - break; - - case 'x': - if (len + MAX_HEX_LEN + 1 < max) { // A cheat, but it is safe. - *bp++ = '#'; - if (pad == 1) pad = 8; - cp = Form_Hex_Pad(bp, (REBCNT)(va_arg(args, REBYTE*)), pad); - len += 1 + (REBCNT)(cp - bp); - bp = cp; - } - break; - - default: - *bp++ = *fmt; - len++; - } - } - *bp = 0; - return bp; -} - - -/*********************************************************************** -** -** User Output Print Interface -** -***********************************************************************/ - -/*********************************************************************** -** -*/ void Prin_Value(REBVAL *value, REBCNT limit, REBOOL mold) -/* -** Print a value or block's contents for user viewing. -** Can limit output to a given size. Set limit to 0 for full size. -** -***********************************************************************/ -{ - REBSER *out = Mold_Print_Value(value, limit, mold); - Prin_OS_String(out->data, out->tail, TRUE); -} - - -/*********************************************************************** -** -*/ void Print_Value(REBVAL *value, REBCNT limit, REBOOL mold) -/* -** Print a value or block's contents for user viewing. -** Can limit output to a given size. Set limit to 0 for full size. -** -***********************************************************************/ -{ - Prin_Value(value, limit, mold); - Print_OS_Line(); -} - - -#ifdef unused -/*********************************************************************** -** -*/ static void Prin_Mold_Block(REBVAL *block, REBCNT limit) -/* -** Can limit output to a given size. Set limit to 0 for full size. -** -***********************************************************************/ -{ - REBCNT n; - -// Reset_Mold_Buffer(); - old_Block_Series(block, BUF_MOLD, 0, 0); - - // Note: do not need to protect BUF_MOLD - if (limit != 0 && STR_LEN(BUF_MOLD) > limit) { - SERIES_TAIL(BUF_MOLD) = limit; - Append_Bytes(BUF_MOLD, "..."); - } - - for (n = 0; n < SERIES_TAIL(BUF_MOLD);) { - n = Encode_Uni_UTF8(BUF_MOLD, n, BUF_PRINT); - Prin_OS_String(STR_HEAD(BUF_PRINT), SERIES_TAIL(BUF_PRINT)); - } -} - - -/*********************************************************************** -** -*/ void Print_Mold_Block(REBVAL *block, REBCNT limit) -/* -** Print a block contents for user viewing. -** Can limit output to a given size. Set limit to 0 for full size. -** -***********************************************************************/ -{ - Prin_Mold_Block(block, limit); - Out_Line(); -} - -/*********************************************************************** -** -*/ REBYTE *Form_Args(REBYTE *cp, REBCNT limit, REBYTE *fmt, ...) -/* -** Format a string into a string buffer up to a maximum length. -** Used mostly for debugging output. -** -***********************************************************************/ -{ - va_list args; - - va_start(args, fmt); - cp = Form_Var_Args(cp, limit, fmt, args); - va_end(args); - return cp; -} -#endif - - -/*********************************************************************** -** -*/ void Init_Raw_Print(void) -/* -** Initialize print module. -** -***********************************************************************/ -{ - Set_Root_Series(TASK_BUF_PRINT, Make_Binary(1000), "print buffer"); - Set_Root_Series(TASK_BUF_FORM, Make_Binary(64), "form buffer"); + switch (desc = *fmt) { + + case '0': + padding = '0'; + // falls through + case '-': + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + fmt = cs_cast(Grab_Int(cb_cast(fmt), &pad)); + goto pick; + + case 'D': + assert(FALSE); // !!! was identical code to "d"...why "D"? + case 'd': + // All va_arg integer arguments will be coerced to platform 'int' + cp = Form_Int_Pad( + buf, + cast(REBI64, va_arg(*vaptr, int)), + MAX_SCAN_DECIMAL, + pad, + padding + ); + Append_Unencoded_Len(ser, s_cast(buf), cast(REBCNT, cp - buf)); + break; + + case 's': + cp = va_arg(*vaptr, REBYTE *); + if (pad == 1) pad = LEN_BYTES(cp); + if (pad < 0) { + pad = -pad; + pad -= LEN_BYTES(cp); + for (; pad > 0; pad--) Append_Codepoint_Raw(ser, ' '); + } + Append_Unencoded(ser, s_cast(cp)); + + // !!! see R3-Alpha for original pad logic, this is an attempt + // to make the output somewhat match without worrying heavily + // about the padding features of this debug routine. + // + pad -= LEN_BYTES(cp); + + for (; pad > 0; pad--) Append_Codepoint_Raw(ser, ' '); + break; + + case 'r': // use Mold + case 'v': // use Form + Mold_Value( + mo, + va_arg(*vaptr, const REBVAL*), + LOGICAL(desc != 'v') + ); + + // !!! This used to "filter out ctrl chars", which isn't a bad + // idea as a mold option (MOPT_FILTER_CTRL) but it would involve + // some doing, as molding doesn't have a real "moment" that + // it can always filter...since sometimes the buffer is examined + // directly by clients vs. getting handed back. + // + /* for (; l > 0; l--, bp++) if (*bp < ' ') *bp = ' '; */ + break; + + case 'm': { // Mold a series + // Init_Block would Ensure_Series_Managed, we use a raw + // VAL_SET instead. + // + // !!! Better approach? Can the series be passed directly? + // + REBSER* temp = va_arg(*vaptr, REBSER*); + if (GET_SER_FLAG(temp, SERIES_FLAG_ARRAY)) { + VAL_RESET_HEADER(value, REB_BLOCK); + INIT_VAL_ARRAY(value, ARR(temp)); + } + else { + VAL_RESET_HEADER(value, REB_STRING); + INIT_VAL_SERIES(value, temp); + } + VAL_INDEX(value) = 0; + Mold_Value(mo, value, TRUE); + break; + } + + case 'c': + Append_Codepoint_Raw( + ser, + cast(REBYTE, va_arg(*vaptr, REBINT)) + ); + break; + + case 'x': + Append_Codepoint_Raw(ser, '#'); + if (pad == 1) pad = 8; + cp = Form_Hex_Pad( + buf, + cast(REBU64, cast(REBUPT, va_arg(*vaptr, REBYTE*))), + pad + ); + Append_Unencoded_Len(ser, s_cast(buf), cp - buf); + break; + + default: + Append_Codepoint_Raw(ser, *fmt); + } + } + + TERM_SERIES(ser); +} + + +// +// Form_Args: C +// +void Form_Args(REB_MOLD *mo, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + Form_Args_Core(mo, fmt, &args); + va_end(args); +} + + +// +// Startup_Raw_Print: C +// +// Initialize print module. +// +void Startup_Raw_Print(void) +{ + Init_String(TASK_BYTE_BUF, Make_Binary(1000)); } diff --git a/src/core/d-stack.c b/src/core/d-stack.c new file mode 100644 index 0000000000..3ca9129ed1 --- /dev/null +++ b/src/core/d-stack.c @@ -0,0 +1,779 @@ +// +// Rebol 3 Language Interpreter and Run-time Environment +// "Ren-C" branch @ https://github.com/metaeducation/ren-c +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Summary: Debug Stack Reflection and Querying +// File: %d-stack.h +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2015-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This file contains interactive debugging support for examining and +// interacting with the stack. +// +// !!! Interactive debugging is a work in progress, and comments are in the +// functions below. +// + +#include "sys-core.h" + + +// +// Collapsify_Array: C +// +// This will replace "long" nested blocks with collapsed versions with +// ellipses to show they have been cut off. It does not change the arrays +// in question, but replaces them with copies. +// +void Collapsify_Array(REBARR *array, REBSPC *specifier, REBCNT limit) +{ + RELVAL *item = ARR_HEAD(array); + for (; NOT_END(item); ++item) { + if (ANY_ARRAY(item) && VAL_LEN_AT(item) > limit) { + REBSPC *derived = Derive_Specifier(specifier, item); + REBARR *copy = Copy_Array_At_Max_Shallow( + VAL_ARRAY(item), + VAL_INDEX(item), + derived, + limit + 1 + ); + + Init_Word(ARR_AT(copy, limit), Canon(SYM_ELLIPSIS)); + + Collapsify_Array( + copy, + SPECIFIED, + limit + ); + + enum Reb_Kind kind = VAL_TYPE(item); + Init_Any_Array_At(item, kind, copy, 0); // at 0 now + assert(IS_SPECIFIC(item)); + assert(NOT_VAL_FLAG(item, VALUE_FLAG_LINE)); // should be cleared + } + } +} + + +// +// Make_Where_For_Frame: C +// +// Each call frame maintains the array it is executing in, the current index +// in that array, and the index of where the current expression started. +// This can be deduced into a segment of code to display in the debug views +// to indicate roughly "what's running" at that stack level. The code is +// a shallow copy of the array content. +// +// The resulting WHERE information only includes the range of the array being +// executed up to the point of currently relevant evaluation. It does not +// go all the way to the tail of the block (where future potential evaluation +// should be. +// +// !!! Unfortunately, Rebol doesn't formalize this very well. There is no +// lock on segments of blocks during their evaluation (should there be?). +// It's possible for self-modifying code to scramble the blocks being executed. +// The DO evaluator is robust in terms of not *crashing*, but the semantics +// may well suprise users. +// +// !!! DO also offers a feature whereby values can be supplied at the start +// of an evaluation which are not resident in the array. It also can run +// on an irreversible C va_list of REBVAL*, where these disappear as the +// evaluation proceeds. A special debug setting would be needed to hang +// onto these values for the purposes of better error messages (at the cost +// of performance). +// +REBARR *Make_Where_For_Frame(REBFRM *f) +{ + if (FRM_IS_VALIST(f)) { + // + // Traversing a C va_arg, so reify into a (truncated) array. + // + const REBOOL truncated = TRUE; + Reify_Va_To_Array_In_Frame(f, truncated); + } + + + // WARNING: MIN is a C macro and repeats its arguments. + // + REBCNT start = MIN(ARR_LEN(FRM_ARRAY(f)), FRM_EXPR_INDEX(f)); + REBCNT end = MIN(ARR_LEN(FRM_ARRAY(f)), FRM_INDEX(f)); + + assert(end >= start); + + assert(Is_Any_Function_Frame(f)); + REBOOL pending = Is_Function_Frame_Fulfilling(f); + + REBCNT dsp_start = DSP; + + // !!! We may be running a function where the value for the function was a + // "head" value not in the array. These cases could substitute the symbol + // for the currently executing function. Reconsider when such cases + // appear and can be studied. + /* + DS_PUSH_TRASH; + Init_Word(DS_TOP, FRM_LABEL(f)); + */ + + REBCNT n; + for (n = start; n < end; ++n) { + DS_PUSH_TRASH; + if (IS_VOID(ARR_AT(FRM_ARRAY(f), n))) { + // + // If a va_list is used to do a non-evaluative call (something + // like R3-Alpha's APPLY/ONLY) then void cells are currently + // allowed. Reify_Va_To_Array_In_Frame() may come along and + // make a special block containing voids, which we don't want + // to expose in a user-visible block. Since this array is just + // for display purposes and is "lossy" (as evidenced by the ...) + // substitute a placeholder to avoid crashing the GC. + // + assert(GET_SER_FLAG(FRM_ARRAY(f), ARRAY_FLAG_VOIDS_LEGAL)); + Init_Word(DS_TOP, Canon(SYM___VOID__)); + } + else + Derelativize(DS_TOP, ARR_AT(FRM_ARRAY(f), n), f->specifier); + + if (n == start) { + // + // Get rid of any newline marker on the first element, + // that would visually disrupt the backtrace for no reason. + // + CLEAR_VAL_FLAG(DS_TOP, VALUE_FLAG_LINE); + } + } + + // We add an ellipsis to a pending frame to make it a little bit + // clearer what is going on. If someone sees a where that looks + // like just `* [print]` the asterisk alone doesn't quite send + // home the message that print is not running and it is + // argument fulfillment that is why it's not "on the stack" + // yet, so `* [print ...]` is an attempt to say that better. + // + // !!! This is in-band, which can be mixed up with literal usage + // of ellipsis. Could there be a better "out-of-band" conveyance? + // Might the system use colorization in a value option bit? + // + if (pending) { + DS_PUSH_TRASH; + Init_Word(DS_TOP, Canon(SYM_ELLIPSIS)); + } + + REBARR *where = Pop_Stack_Values(dsp_start); + + // Simplify overly-deep blocks embedded in the where so they show (...) + // instead of printing out fully. + // + Collapsify_Array(where, SPECIFIED, 3); + + return where; +} + + +// +// where-of: native [ +// +// "Get execution point summary for a function call (if still on stack)" +// +// level [frame! function! integer! blank!] +// ] +// +REBNATIVE(where_of) +// +// !!! This routine should probably be used to get the information for the +// where of an error, which should likely be out-of-band. +{ + INCLUDE_PARAMS_OF_WHERE_OF; + + REBFRM *frame = Frame_For_Stack_Level(NULL, ARG(level), TRUE); + if (frame == NULL) + fail (ARG(level)); + + Init_Block(D_OUT, Make_Where_For_Frame(frame)); + return R_OUT; +} + + +// +// label-of: native [ +// +// "Get word label used to invoke a function call (if still on stack)" +// +// level [frame! function! integer!] +// ] +// +REBNATIVE(label_of) +{ + INCLUDE_PARAMS_OF_LABEL_OF; + + REBFRM *frame = Frame_For_Stack_Level(NULL, ARG(level), TRUE); + + // Make it slightly easier by returning a NONE! instead of giving an + // error for a frame that isn't on the stack. + // + // !!! Should a function that was invoked by something other than a WORD! + // return something like TRUE instead of a fake symbol? + // + if (frame == NULL) + return R_BLANK; + + Init_Word(D_OUT, FRM_LABEL(frame)); + return R_OUT; +} + + +// +// file-of: native [ +// +// "Get filename of origin for any series" +// +// return: [file! url! blank!] +// series [any-series!] +// ] +// +REBNATIVE(file_of) +{ + INCLUDE_PARAMS_OF_FILE_OF; + + REBSER *s = VAL_SERIES(ARG(series)); + + if (NOT_SER_FLAG(s, SERIES_FLAG_FILE_LINE)) + return R_BLANK; + + // !!! How to tell whether it's a URL! or a FILE! ? + // + Scan_File(D_OUT, STR_HEAD(s->link.filename), SER_LEN(s->link.filename)); + return R_OUT; +} + + +// +// line-of: native [ +// +// "Get line of origin for any series" +// +// return: [integer! blank!] +// series [any-series!] +// ] +// +REBNATIVE(line_of) +{ + INCLUDE_PARAMS_OF_LINE_OF; + + REBSER *s = VAL_SERIES(ARG(series)); + + if (NOT_SER_FLAG(s, SERIES_FLAG_FILE_LINE)) + return R_BLANK; + + Init_Integer(D_OUT, s->misc.line); + return R_OUT; +} + + +// +// function-of: native [ +// +// "Get the FUNCTION! for a stack level or frame" +// +// return: [function!] +// level [frame! integer!] +// ] +// +REBNATIVE(function_of) +{ + INCLUDE_PARAMS_OF_FUNCTION_OF; + + REBVAL *level = ARG(level); + + if (IS_FRAME(level)) { + // + // If a FRAME!, then the keylist *should* be the function params, + // which should be coercible to a function even when the call is + // no longer on the stack. + // + REBCTX *context = VAL_CONTEXT(level); + Move_Value(D_OUT, CTX_FRAME_FUNC_VALUE(context)); + } + else { + REBFRM *frame = Frame_For_Stack_Level(NULL, level, TRUE); + if (!frame) + fail (level); + + Move_Value(D_OUT, FUNC_VALUE(frame->phase)); + } + + return R_OUT; +} + + +// +// backtrace-index: native [ +// +// "Get the index of a given frame or function as BACKTRACE shows it" +// +// level [function! frame!] +// {The function or frame to get an index for (NONE! if not running)} +// ] +// +REBNATIVE(backtrace_index) +{ + INCLUDE_PARAMS_OF_BACKTRACE_INDEX; + + REBCNT number; + + if (NULL != Frame_For_Stack_Level(&number, ARG(level), TRUE)) { + Init_Integer(D_OUT, number); + return R_OUT; + } + + return R_BLANK; +} + + +// +// backtrace: native [ +// +// "Backtrace to find a specific FRAME!, or other queried property." +// +// return: [ block! frame!] +// "Nothing if printing, if specific level a frame! else block" +// level [ blank! integer! function!] +// "Stack level to return frame for (blank to list)" +// /limit +// "Limit the length of the backtrace" +// frames [blank! integer!] +// "Max number of frames (pending and active), blank for no limit" +// /brief +// "Do not list depths, just function labels on one line" +// ] +// +REBNATIVE(backtrace) +{ + INCLUDE_PARAMS_OF_BACKTRACE; + + Check_Security(Canon(SYM_DEBUG), POL_READ, 0); + + // Note: Running this code path is *intentionally* redundant with + // Frame_For_Stack_Level, as a way of keeping the numbers listed in a + // backtrace lined up with what that routine returns. This isn't a very + // performance-critical routine, so it's good to have the doublecheck. + // + REBVAL *level = ARG(level); + REBOOL get_frame = NOT(IS_VOID(level) || IS_BLANK(level)); + if (get_frame) { + // + // /LIMIT assumes that you are returning a list of backtrace items, + // while specifying a level gives one. They are mutually exclusive. + // + if (REF(limit) || REF(brief)) + fail (Error_Bad_Refines_Raw()); + + // See notes on handling of breakpoint below for why 0 is accepted. + // + if (IS_INTEGER(level) && VAL_INT32(level) < 0) + fail (level); + } + + REBCNT max_rows; // The "frames" from /LIMIT, plus one (for ellipsis) + if (REF(limit)) { + if (IS_BLANK(ARG(frames))) + max_rows = MAX_U32; // NONE is no limit--as many frames as possible + else { + if (VAL_INT32(ARG(frames)) < 0) + fail (ARG(frames)); + max_rows = VAL_INT32(ARG(frames)) + 1; // + 1 for ellipsis + } + } + else + max_rows = 20; // On an 80x25 terminal leaves room to type afterward + + REBDSP dsp_orig = DSP; // original stack pointer (for gathered backtrace) + + REBCNT row = 0; // row we're on (incl. pending frames and maybe ellipsis) + REBCNT number = 0; // level label number in the loop(no pending frames) + REBOOL first = TRUE; // special check of first frame for "breakpoint 0" + + REBFRM *f; + for (f = FS_TOP->prior; f != NULL; f = f->prior) { + // + // Only consider invoked or pending functions in the backtrace. + // + // !!! The pending functions aren't actually being "called" yet, + // their frames are in a partial state of construction. However it + // gives a fuller picture to see them in the backtrace. It may + // be interesting to see GROUP! stack levels that are being + // executed as well (as they are something like DO). + // + if (NOT(Is_Any_Function_Frame(f))) + continue; + + REBOOL pending = Is_Function_Frame_Fulfilling(f); + if (NOT(pending)) { + if ( + first + && ( + FUNC_DISPATCHER(f->phase) == &N_pause + || FUNC_DISPATCHER(f->phase) == &N_breakpoint + ) + ) { + // Omitting breakpoints from the list entirely presents a + // skewed picture of what's going on. But giving them + // "index 1" means that inspecting the frame you're actually + // interested in (the one where you put the breakpoint) bumps + // to 2, which feels unnatural. + // + // Compromise by not incrementing the stack numbering for + // this case, leaving a leading breakpoint frame at index 0. + } + else + ++number; + } + + first = FALSE; + + ++row; + + #if !defined(NDEBUG) + // + // Try and keep the numbering in sync with query used by host to get + // function frames to do binding in the REPL with. + // + if (!pending) { + DECLARE_LOCAL (temp_val); + Init_Integer(temp_val, number); + + REBCNT temp_num; + if ( + Frame_For_Stack_Level(&temp_num, temp_val, TRUE) != f + || temp_num != number + ) { + printf( + "%d != Frame_For_Stack_Level %d", + cast(int, number), + cast(int, temp_num) + ); + fflush(stdout); + assert(FALSE); + } + } + #endif + + if (get_frame) { + if (IS_INTEGER(level)) { + if (number != cast(REBCNT, VAL_INT32(level))) // is positive + continue; + } + else { + assert(IS_FUNCTION(level)); + if (f->phase != VAL_FUNC(level)) + continue; + } + } + else { + if (row >= max_rows) { + // + // If there's more stack levels to be shown than we were asked + // to show, then put an `+ ...` in the list and break. + // + DS_PUSH_TRASH; + Init_Word(DS_TOP, Canon(SYM_PLUS)); + + if (NOT(REF(brief))) { + // + // In the non-/ONLY backtrace, the pairing of the ellipsis + // with a plus is used in order to keep the "record size" + // of the list at an even 2. Asterisk might have been + // used but that is taken for "pending frames". + // + // !!! Review arbitrary symbolic choices. + // + DS_PUSH_TRASH; + Init_Word(DS_TOP, Canon(SYM_ASTERISK)); + SET_VAL_FLAG(DS_TOP, VALUE_FLAG_LINE); // put on own line + } + break; + } + } + + if (get_frame) { + // + // If we were fetching a single stack level, then our result will + // be a FRAME! (which can be queried for further properties via + // `where-of`, `label-of`, `function-of`, etc.) + // + Init_Any_Context( + D_OUT, + REB_FRAME, + Context_For_Frame_May_Reify_Managed(f) + ); + return R_OUT; + } + + // !!! Should /BRIEF omit pending frames? Should it have a less + // "loaded" name for the refinement? + // + if (REF(brief)) { + DS_PUSH_TRASH; + Init_Word(DS_TOP, FRM_LABEL(f)); + continue; + } + + DS_PUSH_TRASH; + Init_Block(DS_TOP, Make_Where_For_Frame(f)); + + // If building a backtrace, we just keep accumulating results as long + // as there are stack levels left and the limit hasn't been hit. + + // The integer identifying the stack level (used to refer to it + // in other debugging commands). Since we're going in reverse, we + // add it after the props so it will show up before, and give it + // the newline break marker. + // + DS_PUSH_TRASH; + if (pending) { + // + // You cannot (or should not) switch to inspect a pending frame, + // as it is partially constructed. It gets a "*" in the list + // instead of a number. + // + // !!! This may be too restrictive; though it is true you can't + // resume/from or exit/from a pending frame (due to the index + // not knowing how many values it would have consumed if a + // call were to complete), inspecting the existing args could + // be okay. Disallowing it offers more flexibility in the + // dealings with the arguments, however (for instance: not having + // to initialize not-yet-filled args could be one thing). + // + Init_Word(DS_TOP, Canon(SYM_ASTERISK)); + } + else + Init_Integer(DS_TOP, number); + + SET_VAL_FLAG(DS_TOP, VALUE_FLAG_LINE); + } + + // If we ran out of stack levels before finding the single one requested + // via /AT, return a NONE! + // + // !!! Would it be better to give an error? + // + if (get_frame) + return R_BLANK; + + // Return accumulated backtrace otherwise, in the reverse order pushed + // + Init_Block(D_OUT, Pop_Stack_Values_Reversed(dsp_orig)); + return R_OUT; +} + + +// +// Frame_For_Stack_Level: C +// +// Level can be a void, an INTEGER!, an ANY-FUNCTION!, or a FRAME!. If +// level is void then it means give whatever the first call found is. +// +// Returns NULL if the given level number does not correspond to a running +// function on the stack. +// +// Can optionally give back the index number of the stack level (counting +// where the most recently pushed stack level is the lowest #) +// +// !!! Unfortunate repetition of logic inside of BACKTRACE. Assertions +// are used to try and keep them in sync, by noticing during backtrace +// if the stack level numbers being handed out don't line up with what +// would be given back by this routine. But it would be nice to find a way +// to unify the logic for omitting things like breakpoint frames, or either +// considering pending frames or not. +// +REBFRM *Frame_For_Stack_Level( + REBCNT *number_out, + const REBVAL *level, + REBOOL skip_current +) { + REBFRM *frame = FS_TOP; + REBOOL first = TRUE; + REBINT num = 0; + + if (IS_INTEGER(level)) { + if (VAL_INT32(level) < 0) { + // + // !!! fail() here, or just return NULL? + // + return NULL; + } + } + + // We may need to skip some number of frames, if there have been stack + // levels added since the numeric reference point that "level" was + // supposed to refer to has changed. For now that's only allowed to + // be one level, because it's rather fuzzy which stack levels to + // omit otherwise (pending? parens?) + // + if (skip_current) + frame = frame->prior; + + for (; frame != NULL; frame = frame->prior) { + if (NOT(Is_Any_Function_Frame(frame))) { + // + // Don't consider pending calls, or GROUP!, or any non-invoked + // function as a candidate to target. + // + // !!! The inability to target a GROUP! by number is an artifact + // of implementation, in that there's no hook in Do_Core() at + // the point of group evaluation to process the return. The + // matter is different with a pending function call, because its + // arguments are only partially processed--hence something + // like a RESUME/AT or an EXIT/FROM would not know which array + // index to pick up running from. + // + continue; + } + + REBOOL pending = Is_Function_Frame_Fulfilling(frame); + if (NOT(pending)) { + if (first) { + if ( + FUNC_DISPATCHER(frame->phase) == &N_pause + || FUNC_DISPATCHER(frame->phase) == N_breakpoint + ) { + // this is considered the "0". Return it only if 0 was requested + // specifically (you don't "count down to it"); + // + if (IS_INTEGER(level) && num == VAL_INT32(level)) + goto return_maybe_set_number_out; + else { + first = FALSE; + continue; + } + } + else { + ++num; // bump up from 0 + } + } + } + + first = FALSE; + + if (pending) continue; + + if (IS_INTEGER(level) && num == VAL_INT32(level)) + goto return_maybe_set_number_out; + + if (IS_VOID(level) || IS_BLANK(level)) { + // + // Take first actual frame if void or blank + // + goto return_maybe_set_number_out; + } + else if (IS_INTEGER(level)) { + ++num; + if (num == VAL_INT32(level)) + goto return_maybe_set_number_out; + } + else if (IS_FRAME(level)) { + if (frame->varlist == CTX_VARLIST(VAL_CONTEXT(level))) { + goto return_maybe_set_number_out; + } + } + else { + assert(IS_FUNCTION(level)); + if (VAL_FUNC(level) == frame->phase) + goto return_maybe_set_number_out; + } + } + + // Didn't find it... + // + return NULL; + +return_maybe_set_number_out: + if (number_out) + *number_out = num; + return frame; +} + + +// +// Is_Context_Running_Or_Pending: C +// +REBOOL Is_Context_Running_Or_Pending(REBCTX *frame_ctx) +{ + REBFRM *f = CTX_FRAME_IF_ON_STACK(frame_ctx); + if (f == NULL) + return FALSE; + + if (Is_Function_Frame_Fulfilling(f)) + return FALSE; + + return TRUE; +} + + +// +// running?: native [ +// +// "Returns TRUE if a FRAME! is on the stack and executing (arguments done)." +// +// frame [frame!] +// ] +// +REBNATIVE(running_q) +{ + INCLUDE_PARAMS_OF_RUNNING_Q; + + REBCTX *frame_ctx = VAL_CONTEXT(ARG(frame)); + + REBFRM *f = CTX_FRAME_IF_ON_STACK(frame_ctx); + if (f == NULL) + return R_FALSE; + + if (Is_Function_Frame_Fulfilling(f)) + return R_FALSE; + + return R_TRUE; +} + + +// +// pending?: native [ +// +// "Returns TRUE if a FRAME! is on the stack, but is gathering arguments." +// +// frame [frame!] +// ] +// +REBNATIVE(pending_q) +{ + INCLUDE_PARAMS_OF_PENDING_Q; + + REBCTX *frame_ctx = VAL_CONTEXT(ARG(frame)); + + REBFRM *f = CTX_FRAME_IF_ON_STACK(frame_ctx); + if (f == NULL) + return R_FALSE; + + if (Is_Function_Frame_Fulfilling(f)) + return R_TRUE; + + return R_FALSE; +} diff --git a/src/core/d-trace.c b/src/core/d-trace.c new file mode 100644 index 0000000000..fa2cd01ace --- /dev/null +++ b/src/core/d-trace.c @@ -0,0 +1,282 @@ +// +// File: %d-trace.c +// Summary: "Tracing Debug Routines" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// TRACE is functionality that was in R3-Alpha for doing low-level tracing. +// It could be turned on with `trace on` and off with `trace off`. While +// it was on, it would print out information about the current execution step. +// +// Ren-C's goal is to have a fully-featured debugger that should allow a +// TRACE-like facility to be written and customized by the user. They would +// be able to get access on each step to the call frame, and control the +// evaluator from within. +// +// A lower-level trace facility may still be interesting even then, for +// "debugging the debugger". Either way, the routines have been extracted +// from %c-do.c in order to reduce the total length of that very long file. +// + +#include "sys-core.h" + + +// +// Eval_Depth: C +// +REBINT Eval_Depth(void) +{ + REBINT depth = 0; + REBFRM *frame = FS_TOP; + + for (; frame != NULL; frame = FRM_PRIOR(frame), depth++) + NOOP; + + return depth; +} + + +// +// Frame_At_Depth: C +// +REBFRM *Frame_At_Depth(REBCNT n) +{ + REBFRM *frame = FS_TOP; + + while (frame) { + if (n == 0) return frame; + + --n; + frame = FRM_PRIOR(frame); + } + + return NULL; +} + + +static REBINT Init_Depth(void) +{ + // Check the trace depth is ok: + REBINT depth = Eval_Depth() - Trace_Depth; + if (depth < 0 || depth >= Trace_Level) return -1; + if (depth > 10) depth = 10; + Debug_Space(cast(REBCNT, 4 * depth)); + return depth; +} + + +#define CHECK_DEPTH(d) if ((d = Init_Depth()) < 0) return;\ + + +// +// Trace_Line: C +// +void Trace_Line(REBFRM *f) +{ + int depth; + + if (GET_FLAG(Trace_Flags, 1)) return; // function + if (IS_FUNCTION(f->value)) return; + + CHECK_DEPTH(depth); + + if (IS_END(f->value)) { + Debug_Fmt_("END"); + } + else if (f->flags.bits & DO_FLAG_VA_LIST) { + Debug_Fmt_("VA_LIST_FLAG..."); + } + else { + Debug_Fmt_("%-02d: %50r", cast(REBINT, f->index), f->value); + } + + if (IS_WORD(f->value) || IS_GET_WORD(f->value)) { + const RELVAL *var = Get_Opt_Var_May_Fail(f->value, f->specifier); + if (VAL_TYPE(var) < REB_FUNCTION) + Debug_Fmt_(" : %50r", var); + else if (VAL_TYPE(var) == REB_FUNCTION) { + REBARR *words = List_Func_Words(var, FALSE); // no locals + Debug_Fmt_(" : %s %50m", Get_Type_Name(var), words); + Free_Array(words); + } + else + Debug_Fmt_(" : %s", Get_Type_Name(var)); + } + Debug_Line(); +} + + +// +// Trace_Func: C +// +void Trace_Func(REBSTR *label) +{ + int depth; + CHECK_DEPTH(depth); + Debug_Fmt_(RM_TRACE_FUNCTION, STR_HEAD(label)); + if (GET_FLAG(Trace_Flags, 1)) + Debug_Values(FRM_ARG(FS_TOP, 1), FRM_NUM_ARGS(FS_TOP), 20); + else Debug_Line(); +} + + +// +// Trace_Return: C +// +void Trace_Return(REBSTR *label, const REBVAL *value) +{ + int depth; + CHECK_DEPTH(depth); + Debug_Fmt_(RM_TRACE_RETURN, STR_HEAD(label)); + Debug_Values(value, 1, 50); +} + + +// +// Trace_Value: C +// +void Trace_Value( + const char* label, // currently "match" or "input" + const RELVAL *value +) { + int depth; + CHECK_DEPTH(depth); + Debug_Fmt(RM_TRACE_PARSE_VALUE, label, value); +} + + +// +// Trace_String: C +// +void Trace_String(const REBYTE *str, REBINT limit) +{ + static char tracebuf[64]; + int depth; + int len = MIN(60, limit); + CHECK_DEPTH(depth); + memcpy(tracebuf, str, len); + tracebuf[len] = '\0'; + Debug_Fmt(RM_TRACE_PARSE_INPUT, tracebuf); +} + + +// +// Trace_Error: C +// +void Trace_Error(const REBVAL *value) +{ + int depth; + CHECK_DEPTH(depth); + Debug_Fmt( + RM_TRACE_ERROR, + &VAL_ERR_VARS(value)->type, + &VAL_ERR_VARS(value)->id + ); +} + + +// +// trace: native [ +// +// {Enables and disables evaluation tracing and backtrace.} +// +// return: [] +// mode [integer! logic!] +// /back +// {Set mode ON to enable or integer for lines to display} +// /function +// "Traces functions only (less output)" +// ] +// +REBNATIVE(trace) +{ + INCLUDE_PARAMS_OF_TRACE; + + REBVAL *mode = ARG(mode); + + Check_Security(Canon(SYM_DEBUG), POL_READ, 0); + + // The /back option: ON and OFF, or INTEGER! for # of lines: + if (REF(back)) { + if (IS_LOGIC(mode)) { + Enable_Backtrace(VAL_LOGIC(mode)); + } + else if (IS_INTEGER(mode)) { + REBINT lines = Int32(mode); + Trace_Flags = 0; + if (lines < 0) + fail (mode); + + Display_Backtrace(cast(REBCNT, lines)); + return R_VOID; + } + } + else + Enable_Backtrace(FALSE); + + // Set the trace level: + if (IS_LOGIC(mode)) + Trace_Level = VAL_LOGIC(mode) ? 100000 : 0; + else + Trace_Level = Int32(mode); + + if (Trace_Level) { + Trace_Flags = 1; + if (REF(function)) + SET_FLAG(Trace_Flags, 1); + Trace_Depth = Eval_Depth() - 1; // subtract current TRACE frame + } + else + Trace_Flags = 0; + + return R_VOID; +} + + +#if !defined(NDEBUG) + +// +// Trace_Fetch_Debug: C +// +// When down to the wire and wanting to debug the evaluator, it can be very +// useful to see the steps of the states it's going through to see what is +// wrong. This routine hooks the individual fetch and writes at a more +// fine-grained level than a breakpoint at each DO/NEXT point. +// +void Trace_Fetch_Debug(const char* msg, REBFRM *f, REBOOL after) { + Debug_Fmt( + "%d - %s : %s", + cast(REBCNT, f->index), + msg, + after ? "AFTER" : "BEFORE" + ); + + if (IS_END(f->value)) + Debug_Fmt("f->value is END"); + else + PROBE(f->value); +} + +#endif diff --git a/src/core/f-blocks.c b/src/core/f-blocks.c old mode 100644 new mode 100755 index a6040a9ef6..9cb868ddbd --- a/src/core/f-blocks.c +++ b/src/core/f-blocks.c @@ -1,441 +1,567 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: f-blocks.c -** Summary: primary block series support functions -** Section: functional -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %f-blocks.c +// Summary: "primary block series support functions" +// Section: functional +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/*********************************************************************** -** -*/ REBSER *Make_Block(REBCNT length) -/* -** Make a block series. Add 1 extra for the terminator. -** Set TAIL to zero and set terminator. -** -***********************************************************************/ -{ - REBSER *series = Make_Series(length + 1, sizeof(REBVAL), FALSE); - SET_END(BLK_HEAD(series)); - PG_Reb_Stats->Blocks++; - return series; -} - - -/*********************************************************************** -** -*/ REBSER *Copy_Block(REBSER *block, REBCNT index) -/* -** Shallow copy a block from the given index thru the tail. -** -***********************************************************************/ -{ - REBCNT len = SERIES_TAIL(block); - REBSER *series; - - if (index > len) return Make_Block(0); - - len -= index; - series = Make_Series(len + 1, sizeof(REBVAL), FALSE); - COPY_BLK_PART(series, BLK_SKIP(block, index), len); - - PG_Reb_Stats->Blocks++; - - return series; -} - - -/*********************************************************************** -** -*/ REBSER *Copy_Block_Len(REBSER *block, REBCNT index, REBCNT len) -/* -** Shallow copy a block from the given index for given length. -** -***********************************************************************/ -{ - REBSER *series; - - if (index > SERIES_TAIL(block)) return Make_Block(0); - if (index + len > SERIES_TAIL(block)) len = SERIES_TAIL(block) - index; - - series = Make_Series(len + 1, sizeof(REBVAL), FALSE); - COPY_BLK_PART(series, BLK_SKIP(block, index), len); - - PG_Reb_Stats->Blocks++; - - return series; +// !!! Currently, callers don't specify if they are copying an array to turn +// it into a paramlist or varlist, or to use as the kind of array the user +// might see. If we used plain Make_Array() then it would add a flag saying +// there were line numbers available, which may compete with the usage of the +// ->misc and ->link fields of the series node for internal arrays. Pass 0. +// +#define Make_Array_For_Copy(a) \ + Make_Array_Core((a), 0) + + +// +// Copy_Array_At_Extra_Shallow: C +// +// Shallow copy an array from the given index thru the tail. +// Additional capacity beyond what is required can be added +// by giving an `extra` count of how many value cells one needs. +// +REBARR *Copy_Array_At_Extra_Shallow( + REBARR *original, + REBCNT index, + REBSPC *specifier, + REBCNT extra +) { + REBCNT len = ARR_LEN(original); + + if (index > len) + return Make_Array_For_Copy(0); + + len -= index; + + REBARR *copy = Make_Array_For_Copy(len + extra + 1); + + if (specifier == SPECIFIED) { + // + // We can just bit-copy a fully specified array. By its definition + // it may not contain any RELVALs. But in the debug build, double + // check that... + // + #if !defined(NDEBUG) + RELVAL *check = ARR_AT(original, index); + REBCNT count = 0; + for (; count < len; ++count) + assert(IS_SPECIFIC(check)); + #endif + + memcpy(ARR_HEAD(copy), ARR_AT(original, index), len * sizeof(REBVAL)); + } + else { + // Any RELVALs will have to be handled. Review if a memcpy with + // a touch-up phase is faster, or if there is any less naive way. + // + RELVAL *src = ARR_AT(original, index); + REBVAL *dest = KNOWN(ARR_HEAD(copy)); + REBCNT count = 0; + for (; count < len; ++count, ++dest, ++src) + Derelativize(dest, src, specifier); + } + + TERM_ARRAY_LEN(copy, len); + + return copy; } -/*********************************************************************** -** -*/ REBSER *Copy_Values(REBVAL *blk, REBCNT length) -/* -** Shallow copy a block from current value for length values. -** -***********************************************************************/ -{ - REBSER *series; - - series = Make_Series(length + 1, sizeof(REBVAL), FALSE); - COPY_BLK_PART(series, blk, length); - - PG_Reb_Stats->Blocks++; - - return series; +// +// Copy_Array_At_Max_Shallow: C +// +// Shallow copy an array from the given index for given maximum +// length (clipping if it exceeds the array length) +// +REBARR *Copy_Array_At_Max_Shallow( + REBARR *original, + REBCNT index, + REBSPC *specifier, + REBCNT max +) { + if (index > ARR_LEN(original)) + return Make_Array_For_Copy(0); + + if (index + max > ARR_LEN(original)) + max = ARR_LEN(original) - index; + + REBARR *copy = Make_Array_For_Copy(max + 1); + + if (specifier == SPECIFIED) { + #if !defined(NDEBUG) + REBCNT count = 0; + const RELVAL *check = ARR_AT(original, index); + for (; count < max; ++count, ++check) { + assert(IS_SPECIFIC(check)); + } + #endif + memcpy(ARR_HEAD(copy), ARR_AT(original, index), max * sizeof(REBVAL)); + } + else { + REBCNT count = 0; + const RELVAL *src = ARR_AT(original, index); + RELVAL *dest = ARR_HEAD(copy); + for (; count < max; ++count, ++src, ++dest) + Derelativize(dest, src, specifier); + } + + TERM_ARRAY_LEN(copy, max); + + return copy; } -/*********************************************************************** -** -*/ void Copy_Deep_Values(REBSER *block, REBCNT index, REBCNT tail, REBU64 types) -/* -** Copy the contents of values specified by types. If the -** DEEP flag is set, recurse into sub-blocks and objects. -** -***********************************************************************/ -{ - REBVAL *val; - - for (; index < tail; index++) { - - val = BLK_SKIP(block, index); - - if ((types & TYPESET(VAL_TYPE(val)) & TS_SERIES_OBJ) != 0) { - // Replace just the series field of the value - // Note that this should work for objects too (the frame). - VAL_SERIES(val) = Copy_Series(VAL_SERIES(val)); - if ((types & TYPESET(VAL_TYPE(val)) & TS_BLOCKS_OBJ) != 0) { - PG_Reb_Stats->Blocks++; - // If we need to copy recursively (deep): - if ((types & CP_DEEP) != 0) - Copy_Deep_Values(VAL_SERIES(val), 0, VAL_TAIL(val), types); - } - } else if (types & TYPESET(VAL_TYPE(val)) & TS_FUNCLOS) - Clone_Function(val, val); - } +// +// Copy_Values_Len_Extra_Skip_Shallow: C +// +// Shallow copy the first 'len' values of `head` into a new +// series created to hold exactly that many entries. +// +REBARR *Copy_Values_Len_Extra_Skip_Shallow_Core( + const RELVAL head[], + REBSPC *specifier, + REBCNT len, + REBCNT extra, + REBINT skip, + REBUPT flags +) { + REBARR *array = Make_Array_Core(len + extra + 1, flags); + + if (specifier == SPECIFIED && skip == 1) { + #if !defined(NDEBUG) + REBCNT count = 0; + const RELVAL *check = head; + for (; count < len; ++count, ++check) { + assert(IS_SPECIFIC(check)); + } + #endif + memcpy(ARR_HEAD(array), head, len * sizeof(REBVAL)); + } + else { + REBCNT count = 0; + const RELVAL *src = head; + RELVAL *dest = ARR_HEAD(array); + for (; count < len; ++count, src += skip, ++dest) + Derelativize(dest, src, specifier); + } + + TERM_ARRAY_LEN(array, len); + + return array; } -/*********************************************************************** -** -*/ REBSER *Copy_Block_Values(REBSER *block, REBCNT index, REBCNT tail, REBU64 types) -/* -** Copy a block, copy specified values, deeply if indicated. -** -***********************************************************************/ -{ - REBSER *series; - - if (index > tail) index = tail; - if (index > SERIES_TAIL(block)) return Make_Block(0); - - series = Copy_Values(BLK_SKIP(block, index), tail - index); - - if (types != 0) Copy_Deep_Values(series, 0, SERIES_TAIL(series), types); - - return series; +// +// Clonify_Values_Len_Managed: C +// +// Update the first `len` elements of `head[]` to clone the series +// embedded in them *if* they are in the given set of types (and +// if "cloning" makes sense for them, e.g. they are not simple +// scalars). If the `deep` flag is set, recurse into subseries +// and objects when that type is matched for clonifying. +// +// Note: The resulting clones will be managed. The model for +// lists only allows the topmost level to contain unmanaged +// values...and we *assume* the values we are operating on here +// live inside of an array. (We also assume the source values +// are in an array, and assert that they are managed.) +// +void Clonify_Values_Len_Managed( + RELVAL head[], + REBSPC *specifier, + REBCNT len, + REBOOL deep, + REBU64 types +) { + if (C_STACK_OVERFLOWING(&len)) Trap_Stack_Overflow(); + + RELVAL *value = head; + + REBCNT index; + for (index = 0; index < len; index++, value++) { + // + // By the rules, if we need to do a deep copy on the source + // series then the values inside it must have already been + // marked managed (because they *might* delve another level deep) + // + ASSERT_VALUE_MANAGED(value); + + if (types & FLAGIT_KIND(VAL_TYPE(value)) & TS_SERIES_OBJ) { + #if !defined(NDEBUG) + REBOOL legacy = FALSE; + #endif + + // Objects and series get shallow copied at minimum + // + REBSER *series; + if (ANY_CONTEXT(value)) { + #if !defined(NDEBUG) + legacy = GET_SER_INFO( + CTX_VARLIST(VAL_CONTEXT(value)), + SERIES_INFO_LEGACY_DEBUG + ); + #endif + + assert(!IS_FRAME(value)); // !!! Don't exist yet... + value->payload.any_context.varlist = + CTX_VARLIST(Copy_Context_Shallow(VAL_CONTEXT(value))); + series = SER(CTX_VARLIST(VAL_CONTEXT(value))); + } + else { + if (GET_SER_FLAG(VAL_SERIES(value), SERIES_FLAG_ARRAY)) { + #if !defined(NDEBUG) + legacy = GET_SER_INFO( + VAL_ARRAY(value), SERIES_INFO_LEGACY_DEBUG + ); + #endif + + REBSPC *derived = Derive_Specifier(specifier, value); + series = SER( + Copy_Array_Shallow( + VAL_ARRAY(value), + derived + ) + ); + + INIT_VAL_ARRAY(value, ARR(series)); // copies args + + // If it was relative, then copying with a specifier + // means it isn't relative any more. + // + INIT_SPECIFIC(value, SPECIFIED); + } + else { + series = Copy_Sequence(VAL_SERIES(value)); + INIT_VAL_SERIES(value, series); + } + } + + #if !defined(NDEBUG) + if (legacy) // propagate legacy + SET_SER_INFO(series, SERIES_INFO_LEGACY_DEBUG); + #endif + + MANAGE_SERIES(series); + + if (!deep) continue; + + // If we're going to copy deeply, we go back over the shallow + // copied series and "clonify" the values in it. + // + // Since we had to get rid of the relative bindings in the + // shallow copy, we can pass in SPECIFIED here...but the recursion + // in Clonify_Values will be threading through any updated specificity + // through to the new values. + // + if (types & FLAGIT_KIND(VAL_TYPE(value)) & TS_ARRAYS_OBJ) { + REBSPC *derived = Derive_Specifier(specifier, value); + Clonify_Values_Len_Managed( + ARR_HEAD(ARR(series)), + derived, + VAL_LEN_HEAD(value), + deep, + types + ); + } + } + else if ( + types & FLAGIT_KIND(VAL_TYPE(value)) & FLAGIT_KIND(REB_FUNCTION) + ) { + Clonify_Function(KNOWN(value)); // functions never "relative" + } + else { + // The value is not on our radar as needing to be processed, + // so leave it as-is. + } + + // Value shouldn't be relative after the above processing. + // + assert(!IS_RELATIVE(value)); + } } -/*********************************************************************** -** -*/ REBSER *Clone_Block(REBSER *block) -/* -** Deep copy block, including all series (strings and blocks), -** but not images, bitsets, maps, etc. -** -***********************************************************************/ -{ - return Copy_Block_Values(block, 0, SERIES_TAIL(block), TS_CODE); -} - - -/*********************************************************************** -** -*/ REBSER *Clone_Block_Value(REBVAL *code) -/* -** Same as above, but uses a value. -** -***********************************************************************/ -{ - // Note: TAIL will be clipped to correct size if INDEX is not zero. - return Copy_Block_Values(VAL_SERIES(code), VAL_INDEX(code), VAL_TAIL(code), TS_CODE); -} - - -#ifdef obsolete -/*********************************************************************** -** -x*/ REBSER *Copy_Block_Deep(REBSER *block, REBCNT index, REBINT len, REBCNT mode) -/* -** A useful function for copying a block and its contents. -** -** index - used to indicate the start of the copy. -** length - can be zero, which means use the series length - index, -** or it can be any length, which if its less than the length -** of the series will clip it, or if it's longer will allocate -** extra space for it. -** mode - indicates what to copy, how deep to copy. -** -***********************************************************************/ -{ - REBSER *series; - REBVAL *val; - REBINT maxlen = (REBINT)SERIES_TAIL(block) - index; - - CHECK_STACK(&series); - - if (mode & COPY_OBJECT) mode |= COPY_STRINGS; - - //DISABLE_GC; // Copy deep may trigger recycle - - if (maxlen < 0) maxlen = 0; - if (len == 0 || len > maxlen) len = maxlen; // (clip size) - - series = (mode & COPY_SAME) ? block : Copy_Values(BLK_SKIP(block, index), len); - - val = BLK_HEAD(series); - if (mode & COPY_SAME) { - val += index; - mode &= ~COPY_SAME; - } - - for (; len > 0; len--, val++) { - if ( - ((mode & COPY_DEEP) && (ANY_BLOCK(val) || IS_OBJECT(val) || IS_PORT(val))) - || - ((mode & COPY_OBJECT) && ANY_BLOCK(val)) - ) { - VAL_SERIES(val) = Copy_Block_Deep(VAL_SERIES(val), 0, 0, mode); - } - if ((mode & COPY_STRINGS) && ANY_BINSTR(val)) { - VAL_SERIES(val) = Copy_Series(VAL_SERIES(val)); - } - } - //ENABLE_GC; - - return series; -} +// +// Copy_Array_Core_Managed: C +// +// Copy a block, copy specified values, deeply if indicated. +// +// The resulting series will already be under GC management, +// and hence cannot be freed with Free_Series(). +// +REBARR *Copy_Array_Core_Managed( + REBARR *original, + REBCNT index, + REBSPC *specifier, + REBCNT tail, + REBCNT extra, + REBOOL deep, + REBU64 types +) { + REBARR *copy; + + if (index > tail) index = tail; + + if (index > ARR_LEN(original)) { + copy = Make_Array_For_Copy(extra); + MANAGE_ARRAY(copy); + } + else { + copy = Copy_Values_Len_Extra_Shallow( + ARR_AT(original, index), specifier, tail - index, extra + ); + MANAGE_ARRAY(copy); + + if (types != 0) // the copy above should have specified top level + Clonify_Values_Len_Managed( + ARR_HEAD(copy), SPECIFIED, ARR_LEN(copy), deep, types + ); + } + +#if !defined(NDEBUG) + // + // Propagate legacy flag, hence if a legacy array was loaded with + // `[switch 1 [2]]` in it (for instance) then when that code is used to + // make a function body, the `[switch 1 [2]]` in that body will also + // be marked legacy. Then if it runs, the SWITCH can dispatch to return + // blank instead of the Ren-C behavior of returning `2`. + // + if (GET_SER_INFO(original, SERIES_INFO_LEGACY_DEBUG)) + SET_SER_INFO(copy, SERIES_INFO_LEGACY_DEBUG); #endif - -/*********************************************************************** -** -*/ REBSER *Copy_Expand_Block(REBSER *block, REBCNT extra) -/* -** Create an expanded copy of the block, but with same tail. -** -***********************************************************************/ -{ - REBCNT len = SERIES_TAIL(block); - REBSER *series = Make_Series(len + extra + 1, sizeof(REBVAL), FALSE); - COPY_BLK_PART(series, BLK_HEAD(block), len); - PG_Reb_Stats->Blocks++; - return series; + ASSERT_NO_RELATIVE(copy, deep); + return copy; } -/*********************************************************************** -** -*/ void Copy_Stack_Values(REBINT start, REBVAL *into) -/* -** Copy computed values from the stack into the series -** specified by "into", or if into is NULL then store it as a -** block on top of the stack. (Also checks to see if into -** is protected, and will trigger a trap if that is the case.) -** -***********************************************************************/ -{ - REBSER *series; - REBVAL *blk = DS_Base + start; - REBCNT len = DSP - start + 1; - REBCNT type; - - if (into) { - type = VAL_TYPE(into); - series = VAL_SERIES(into); - if (IS_PROTECT_SERIES(series)) Trap0(RE_PROTECTED); - len = Insert_Series(series, VAL_INDEX(into), (REBYTE*)blk, len); - } else { - series = Make_Series(len + 1, sizeof(REBVAL), FALSE); - COPY_BLK_PART(series, blk, len); - len = 0; - type = REB_BLOCK; - PG_Reb_Stats->Blocks++; - } - - DSP = start; - blk = DS_TOP; - VAL_SET(blk, type); - VAL_SERIES(blk) = series; - VAL_INDEX(blk) = len; - VAL_SERIES_SIDE(blk) = 0; +// +// Copy_Array_At_Extra_Deep_Managed: C +// +// Deep copy an array, including all series (strings, blocks, +// parens, objects...) excluding images, bitsets, maps, etc. +// The set of exclusions is the typeset TS_NOT_COPIED. +// +// The resulting array will already be under GC management, +// and hence cannot be freed with Free_Series(). +// +// Note: If this were declared as a macro it would use the +// `array` parameter more than once, and have to be in all-caps +// to warn against usage with arguments that have side-effects. +// +REBARR *Copy_Array_At_Extra_Deep_Managed( + REBARR *original, + REBCNT index, + REBSPC *specifier, + REBCNT extra +) { + REBARR *copy = Copy_Array_Core_Managed( + original, + index, // at + specifier, + ARR_LEN(original), // tail + extra, // extra + TRUE, // deep + TS_SERIES & ~TS_NOT_COPIED // types + ); + + return copy; } -/*********************************************************************** -** -*/ REBVAL *Append_Value(REBSER *block) -/* -** Append a value to a block series at its tail. -** Expand it if necessary. Update the termination and tail. -** Returns the new value for you to initialize. -** -***********************************************************************/ -{ - REBVAL *value; - - EXPAND_SERIES_TAIL(block, 1); - value = BLK_TAIL(block); - SET_END(value); - value--; - SET_NONE(value); // Expand_Series leaves a hole here to be filled - return value; +// +// Copy_Rerelativized_Array_Deep_Managed: C +// +// The invariant of copying in general is that when you are done with the +// copy, there are no relative values in that copy. One exception to this +// is the deep copy required to make a relative function body in the first +// place (which it currently does in two passes--a normal deep copy followed +// by a relative binding). The other exception is when a relativized +// function body is copied to make another relativized function body. +// +// This is specialized logic for the latter case. It's constrained enough +// to be simple (all relative values are known to be relative to the same +// function), and the feature is questionable anyway. So it's best not to +// further complicate ordinary copying with a parameterization to copy +// and change all the relative binding information from one function's +// paramlist to another. +// +REBARR *Copy_Rerelativized_Array_Deep_Managed( + REBARR *original, + REBFUN *before, // references to `before` will be changed to `after` + REBFUN *after +) { + REBARR *copy = Make_Array_For_Copy(ARR_LEN(original)); + RELVAL *src = ARR_HEAD(original); + RELVAL *dest = ARR_HEAD(copy); + + for (; NOT_END(src); ++src, ++dest) { + if (!IS_RELATIVE(src)) { + *dest = *src; + continue; + } + + assert(VAL_RELATIVE(src) == before); + if (ANY_ARRAY(src)) { + *dest = *src; // !!! could copy just fields not overwritten + dest->payload.any_series.series = SER( + Copy_Rerelativized_Array_Deep_Managed( + VAL_ARRAY(src), before, after + ) + ); + INIT_RELATIVE(dest, after); + } + else { + assert(ANY_WORD(src)); + *dest = *src; // !!! could copy just fields not overwritten + INIT_WORD_FUNC(dest, after); + } + } + + TERM_ARRAY_LEN(copy, ARR_LEN(original)); + MANAGE_ARRAY(copy); + + return copy; } -#ifdef ndef -/*********************************************************************** -** -*/ void Append_Block(REBSER *block, REBSER *added) -/* -** Append a block to the tail of a block. -** Expand it if necessary. Update the termination and tail. -** -***********************************************************************/ -{ - Insert_Series(block, block->tail, (void*)BLK_HEAD(added), added->tail); -} -#endif -/*********************************************************************** -** -*/ void Append_Val(REBSER *block, REBVAL *val) -/* -** Append a value to a block series at its tail. -** Expand it if necessary. Update the termination and tail. -** -***********************************************************************/ +// +// Alloc_Tail_Array: C +// +// Append a REBVAL-size slot to Rebol Array series at its tail. +// Will use existing memory capacity already in the series if it +// is available, but will expand the series if necessary. +// Returns the new value for you to initialize. +// +// Note: Updates the termination and tail. +// +REBVAL *Alloc_Tail_Array(REBARR *a) { - REBVAL *value; - - EXPAND_SERIES_TAIL(block, 1); - value = BLK_TAIL(block); - SET_END(value); - value--; - *value = *val; + EXPAND_SERIES_TAIL(SER(a), 1); + TERM_ARRAY_LEN(a, ARR_LEN(a)); + return SINK(ARR_LAST(a)); } -/*********************************************************************** -** -*/ REBINT Find_Same_Block(REBSER *blk, REBVAL *val) -/* -** Scan a block for any values that reference blocks related -** to the value provided. -** -** Defect: only checks certain kinds of values. -** -***********************************************************************/ +// +// Find_Same_Array: C +// +// Scan a block for any values that reference blocks related +// to the value provided. +// +// !!! This was used for detection of cycles during MOLD. The idea is that +// while it is outputting a series, it doesn't want to see that series +// again. For the moment the only places to worry about with that are +// context varlists and block series or maps. (Though a function contains +// series for the spec, body, and paramlist...the spec and body are blocks, +// and so recursion would be found when the blocks were output.) +// +REBCNT Find_Same_Array(REBARR *search_values, const RELVAL *value) { - REBVAL *bp; - REBINT index = 0; - - for (bp = BLK_HEAD(blk); NOT_END(bp); bp++, index++) { - - if (VAL_TYPE(bp) >= REB_BLOCK && - VAL_TYPE(bp) <= REB_MAP && - VAL_BLK(bp) == VAL_BLK(val) - ) return index+1; - - if ( - VAL_TYPE(bp) >= REB_OBJECT && - VAL_TYPE(bp) <= REB_PORT && - VAL_OBJ_FRAME(bp) == VAL_OBJ_FRAME(val) - ) return index+1; - } - return -1; + REBCNT index = 0; + REBARR *array; + RELVAL *other; + + if (ANY_ARRAY(value)) + array = VAL_ARRAY(value); + else if (IS_MAP(value)) + array = MAP_PAIRLIST(VAL_MAP(value)); + else if (ANY_CONTEXT(value)) + array = CTX_VARLIST(VAL_CONTEXT(value)); + else { + // Value being worked with is not a candidate for containing an + // array that could form a loop with one of the search_list values + // + return NOT_FOUND; + } + + other = ARR_HEAD(search_values); + for (; NOT_END(other); other++, index++) { + if (ANY_ARRAY(other)) { + if (array == VAL_ARRAY(other)) + return index; + } + else if (IS_MAP(other)) { + if (array == MAP_PAIRLIST(VAL_MAP(other))) + return index; + } + else if (ANY_CONTEXT(other)) { + if (array == CTX_VARLIST(VAL_CONTEXT(other))) + return index; + } + } + + return NOT_FOUND; } -#ifdef ndef -/*********************************************************************** -** -*/ REBSER *Copy_Side_Series(REBSER *ser) -/* -** Copy a hash or list side series -** -***********************************************************************/ -{ - REBSER *ret; - - ret = Make_Series(ser->tail, SERIES_WIDE(ser), FALSE); - ret->tail = ser->tail; - memcpy(ret->data, ser->data, ret->tail * SERIES_WIDE(ret)); - return ret; -} -#endif -/*********************************************************************** -** -*/ void Clear_Value_Opts(REBSER *ser) -/* -** Clear all options for values of a block series. -** -***********************************************************************/ +// +// Uncolor_Array: C +// +void Uncolor_Array(REBARR *a) { - REBVAL *val = BLK_HEAD(ser); + if (Is_Series_White(SER(a))) + return; // avoid loop + + Flip_Series_To_White(SER(a)); - for (; NOT_END(val); val++) { - VAL_OPTS(val) = 0; - } + RELVAL *val; + for (val = ARR_HEAD(a); NOT_END(val); ++val) + if (ANY_ARRAY(val) || ANY_CONTEXT(val)) + Uncolor(val); } -/*********************************************************************** -** -*/ void Unmark(REBVAL *val) -/* -** Clear the recusion markers for series and object trees. -** -** Note: these markers are also used for GC. Functions that -** call this must not be able to trigger GC! -** -***********************************************************************/ +// +// Uncolor: C +// +// Clear the recusion markers for series and object trees. +// +void Uncolor(RELVAL *val) { - // The next line works because VAL_OBJ_FRAME(val) == VAL_SERIES(val) - REBSER *series = VAL_SERIES(val); - - if (!IS_MARK_SERIES(series)) return; // avoid loop - - UNMARK_SERIES(series); - - for (val = VAL_BLK(val); NOT_END(val); val++) { - if (ANY_SERIES(val) || IS_OBJECT(val) || IS_MODULE(val) - || IS_ERROR(val) || IS_PORT(val)) - Unmark(val); - } + REBARR *array; + + if (ANY_ARRAY(val)) + array = VAL_ARRAY(val); + else if (ANY_CONTEXT(val)) + array = CTX_VARLIST(VAL_CONTEXT(val)); + else { + // Shouldn't have marked recursively any non-array series (no need) + // + assert( + !ANY_SERIES(val) + || Is_Series_White(VAL_SERIES(val)) + ); + return; + } + + Uncolor_Array(array); } diff --git a/src/core/f-deci.c b/src/core/f-deci.c index f2fb6e974c..9ca430207a 100644 --- a/src/core/f-deci.c +++ b/src/core/f-deci.c @@ -1,51 +1,54 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: f-deci.c -** Summary: extended precision arithmetic functions -** Section: functional -** Author: Ladislav Mecir for REBOL Technologies -** Notes: -** Deci significands are 87-bit long, unsigned, unnormalized, stored in -** little endian order. (Maximal deci significand is 1e26 - 1, i.e. 26 -** nines) -** -** Sign is one-bit, 1 means nonpositive, 0 means nonnegative. -** -** Exponent is 8-bit, unbiased. -** -** Functions may be inlined (especially the ones marked by INLINE). -** 64-bit and/or double arithmetic used where they bring advantage. -** -***********************************************************************/ +// +// File: %f-deci.c +// Summary: "extended precision arithmetic functions" +// Section: functional +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Deci significands are 87-bit long, unsigned, unnormalized, stored in +// little endian order. (Maximal deci significand is 1e26 - 1, i.e. 26 +// nines) +// +// Sign is one-bit, 1 means nonpositive, 0 means nonnegative. +// +// Exponent is 8-bit, unbiased. +// +// 64-bit and/or double arithmetic used where they bring advantage. +// +// !!! Inlining was once hinted here, and it may be possible to use +// the hint to speed up this code. But for the moment, inlining +// decisions are being left up to the compiler due to it not being +// a standard feature in C89 and numerous quirks in both C and C++ +// regarding how inline works. A broader review of inline for +// the whole codebase is required at some later date. --@HF +// #include "sys-core.h" #include "sys-deci-funcs.h" #include "sys-dec-to-char.h" -#ifndef TEST_MODE -#define OVERFLOW_ERROR Trap0(RE_OVERFLOW) -#define DIVIDE_BY_ZERO_ERROR Trap0(RE_ZERO_DIVIDE) -#endif - #define IS_DIGIT(c) ((c) >= '0' && (c) <= '9') #define MASK32(i) (REBCNT)(i) @@ -62,53 +65,53 @@ static const deci deci_minus_one = {1u, 0u, 0u, 1u, 0}; static const REBCNT min_int64_t_as_deci[] = {0u, 0x80000000u, 0u}; /* - Compare significand a and significand b; - -1 means a < b; - 0 means a = b; - 1 means a > b; + Compare significand a and significand b; + -1 means a < b; + 0 means a = b; + 1 means a > b; */ -INLINE REBINT m_cmp (REBINT n, const REBCNT a[], const REBCNT b[]) { - REBINT i; - for (i = n - 1; i >= 0; i--) - if (a[i] != b[i]) return a[i] < b[i] ? -1 : 1; - return 0; +REBINT m_cmp (REBINT n, const REBCNT a[], const REBCNT b[]) { + REBINT i; + for (i = n - 1; i >= 0; i--) + if (a[i] != b[i]) return a[i] < b[i] ? -1 : 1; + return 0; } -INLINE REBFLG m_is_zero (REBINT n, const REBCNT a[]) { - REBINT i; - for (i = 0; (i < n) && (a[i] == 0); i++); - return i == n; +REBOOL m_is_zero (REBINT n, const REBCNT a[]) { + REBINT i; + for (i = 0; (i < n) && (a[i] == 0); i++); + return LOGICAL(i == n); } /* unnormalized powers of ten */ static const REBCNT P[][3] = { - {1u, 0u, 0u}, /* 1e0 */ - {10u, 0u, 0u}, /* 1e1 */ - {100u, 0u, 0u}, /* 1e2 */ - {1000u, 0u, 0u}, /* 1e3 */ - {10000u, 0u, 0u}, /* 1e4 */ - {100000u, 0u, 0u}, /* 1e5 */ - {1000000u, 0u, 0u}, /* 1e6 */ - {10000000u, 0u, 0u}, /* 1e7 */ - {100000000u, 0u, 0u}, /* 1e8 */ - {1000000000u, 0u, 0u}, /* 1e9 */ - {1410065408u, 2u, 0u}, /* 1e10 */ - {1215752192u, 23u, 0u}, /* 1e11 */ - {3567587328u, 232u, 0u}, /* 1e12 */ - {1316134912u, 2328u, 0u}, /* 1e13 */ - {276447232u, 23283u, 0u}, /* 1e14 */ - {2764472320u, 232830u, 0u}, /* 1e15 */ - {1874919424u, 2328306u, 0u}, /* 1e16 */ - {1569325056u, 23283064u, 0u}, /* 1e17 */ - {2808348672u, 232830643u, 0u}, /* 1e18 */ - {2313682944u, 2328306436u, 0u}, /* 1e19 */ - {1661992960u, 1808227885u, 5u}, /* 1e20 */ - {3735027712u, 902409669u, 54u}, /* 1e21 */ - {2990538752u, 434162106u, 542u}, /* 1e22 */ - {4135583744u, 46653770u, 5421u}, /* 1e23 */ - {2701131776u, 466537709u, 54210u}, /* 1e24 */ - {1241513984u, 370409800u, 542101u}, /* 1e25 */ - {3825205248u, 3704098002u, 5421010u} /* 1e26 */ + {1u, 0u, 0u}, /* 1e0 */ + {10u, 0u, 0u}, /* 1e1 */ + {100u, 0u, 0u}, /* 1e2 */ + {1000u, 0u, 0u}, /* 1e3 */ + {10000u, 0u, 0u}, /* 1e4 */ + {100000u, 0u, 0u}, /* 1e5 */ + {1000000u, 0u, 0u}, /* 1e6 */ + {10000000u, 0u, 0u}, /* 1e7 */ + {100000000u, 0u, 0u}, /* 1e8 */ + {1000000000u, 0u, 0u}, /* 1e9 */ + {1410065408u, 2u, 0u}, /* 1e10 */ + {1215752192u, 23u, 0u}, /* 1e11 */ + {3567587328u, 232u, 0u}, /* 1e12 */ + {1316134912u, 2328u, 0u}, /* 1e13 */ + {276447232u, 23283u, 0u}, /* 1e14 */ + {2764472320u, 232830u, 0u}, /* 1e15 */ + {1874919424u, 2328306u, 0u}, /* 1e16 */ + {1569325056u, 23283064u, 0u}, /* 1e17 */ + {2808348672u, 232830643u, 0u}, /* 1e18 */ + {2313682944u, 2328306436u, 0u}, /* 1e19 */ + {1661992960u, 1808227885u, 5u}, /* 1e20 */ + {3735027712u, 902409669u, 54u}, /* 1e21 */ + {2990538752u, 434162106u, 542u}, /* 1e22 */ + {4135583744u, 46653770u, 5421u}, /* 1e23 */ + {2701131776u, 466537709u, 54210u}, /* 1e24 */ + {1241513984u, 370409800u, 542101u}, /* 1e25 */ + {3825205248u, 3704098002u, 5421010u} /* 1e26 */ }; /* 1e26 as double significand */ @@ -117,863 +120,947 @@ static const REBCNT P26[] = {3825205248u, 3704098002u, 5421010u, 0u, 0u, 0u}; static const REBCNT P26_1[] = {3825205247u, 3704098002u, 5421010u}; /* - Computes max decimal shift left for nonzero significand a with length 3; - using double arithmetic; + Computes max decimal shift left for nonzero significand a with length 3; + using double arithmetic; */ -INLINE REBINT max_shift_left (const REBCNT a[]) { +REBINT max_shift_left (const REBCNT a[]) { REBINT i; - i = (REBINT)(log10((a[2] * two_to_32 + a[1]) * two_to_32 + a[0]) + 0.5); + i = (REBINT)(log10((a[2] * two_to_32 + a[1]) * two_to_32 + a[0]) + 0.5); return m_cmp (3, P[i], a) <= 0 ? 25 - i : 26 - i; } /* limits for "double significand" right shift */ static const REBCNT Q[][6] = { - {3892314107u, 2681241660u, 54210108u, 0u, 0u, 0u}, /* 1e27-5e0 */ - {268435406u, 1042612833u, 542101086u, 0u, 0u, 0u}, /* 1e28-5e1 */ - {2684354060u, 1836193738u, 1126043566u, 1u, 0u, 0u}, /* 1e29-5e2 */ - {1073736824u, 1182068202u, 2670501072u, 12u, 0u, 0u}, /* 1e30-5e3 */ - {2147433648u, 3230747430u, 935206946u, 126u, 0u, 0u}, /* 1e31-5e4 */ - {4294467296u, 2242703232u, 762134875u, 1262u, 0u, 0u}, /* 1e32-5e5 */ - {4289967296u, 952195849u, 3326381459u, 12621u, 0u, 0u}, /* 1e33-5e6 */ - {4244967296u, 932023907u, 3199043520u, 126217u, 0u, 0u}, /* 1e34-5e7 */ - {3794967296u, 730304487u, 1925664130u, 1262177u, 0u, 0u}, /* 1e35-5e8 */ - {3589934592u, 3008077582u, 2076772117u, 12621774u, 0u, 0u}, /* 1e36-5e9 */ - {1539607552u, 16004756u, 3587851993u, 126217744u, 0u, 0u}, /* 1e37-5e10 */ - {2511173632u, 160047563u, 1518781562u, 1262177448u, 0u, 0u}, /* 1e38-5e11 */ - {3636899840u, 1600475635u, 2302913732u, 4031839891u, 2u, 0u}, /* 1e39-5e12 */ - {2009260032u, 3119854470u, 1554300843u, 1663693251u, 29u, 0u}, /* 1e40-5e13*/ - {2912731136u, 1133773632u, 2658106549u, 3752030625u, 293u, 0u}, /* 1e41-5e14 */ - {3357507584u, 2747801734u, 811261716u, 3160567888u, 2938u, 0u}, /* 1e42-5e15 */ - {3510304768u, 1708213571u, 3817649870u, 1540907809u, 29387u, 0u}, /* 1e43-5e16 */ - {743309312u, 4197233830u, 3816760335u, 2524176210u, 293873u, 0u}, /* 1e44-5e17 */ - {3138125824u, 3317632637u, 3807864991u, 3766925628u, 2938735u, 0u}, /* 1e45-5e18 */ - {1316487168u, 3111555305u, 3718911549u, 3309517920u, 29387358u, 0u}, /* 1e46-5e19 */ - {279969792u, 1050781981u, 2829377129u, 3030408136u, 293873587u, 0u}, /* 1e47-5e20 */ - {2799697920u, 1917885218u, 2523967516u, 239310294u, 2938735877u, 0u}, /* 1e48-5e21 */ - {2227175424u, 1998983002u, 3764838684u, 2393102945u, 3617554994u, 6u}, /* 1e49-5e22 */ - {796917760u, 2809960841u, 3288648476u, 2456192978u, 1815811577u, 68u}, /* 1e50-5e23 */ - {3674210304u, 2329804635u, 2821713694u, 3087093307u, 978246591u, 684u}, /* 1e51-5e24 */ - {2382364672u, 1823209878u, 2447333169u, 806162004u, 1192531325u, 6842u} /* 1e52-5e25 */ + {3892314107u, 2681241660u, 54210108u, 0u, 0u, 0u}, /* 1e27-5e0 */ + {268435406u, 1042612833u, 542101086u, 0u, 0u, 0u}, /* 1e28-5e1 */ + {2684354060u, 1836193738u, 1126043566u, 1u, 0u, 0u}, /* 1e29-5e2 */ + {1073736824u, 1182068202u, 2670501072u, 12u, 0u, 0u}, /* 1e30-5e3 */ + {2147433648u, 3230747430u, 935206946u, 126u, 0u, 0u}, /* 1e31-5e4 */ + {4294467296u, 2242703232u, 762134875u, 1262u, 0u, 0u}, /* 1e32-5e5 */ + {4289967296u, 952195849u, 3326381459u, 12621u, 0u, 0u}, /* 1e33-5e6 */ + {4244967296u, 932023907u, 3199043520u, 126217u, 0u, 0u}, /* 1e34-5e7 */ + {3794967296u, 730304487u, 1925664130u, 1262177u, 0u, 0u}, /* 1e35-5e8 */ + {3589934592u, 3008077582u, 2076772117u, 12621774u, 0u, 0u}, /* 1e36-5e9 */ + {1539607552u, 16004756u, 3587851993u, 126217744u, 0u, 0u}, /* 1e37-5e10 */ + {2511173632u, 160047563u, 1518781562u, 1262177448u, 0u, 0u}, /* 1e38-5e11 */ + {3636899840u, 1600475635u, 2302913732u, 4031839891u, 2u, 0u}, /* 1e39-5e12 */ + {2009260032u, 3119854470u, 1554300843u, 1663693251u, 29u, 0u}, /* 1e40-5e13*/ + {2912731136u, 1133773632u, 2658106549u, 3752030625u, 293u, 0u}, /* 1e41-5e14 */ + {3357507584u, 2747801734u, 811261716u, 3160567888u, 2938u, 0u}, /* 1e42-5e15 */ + {3510304768u, 1708213571u, 3817649870u, 1540907809u, 29387u, 0u}, /* 1e43-5e16 */ + {743309312u, 4197233830u, 3816760335u, 2524176210u, 293873u, 0u}, /* 1e44-5e17 */ + {3138125824u, 3317632637u, 3807864991u, 3766925628u, 2938735u, 0u}, /* 1e45-5e18 */ + {1316487168u, 3111555305u, 3718911549u, 3309517920u, 29387358u, 0u}, /* 1e46-5e19 */ + {279969792u, 1050781981u, 2829377129u, 3030408136u, 293873587u, 0u}, /* 1e47-5e20 */ + {2799697920u, 1917885218u, 2523967516u, 239310294u, 2938735877u, 0u}, /* 1e48-5e21 */ + {2227175424u, 1998983002u, 3764838684u, 2393102945u, 3617554994u, 6u}, /* 1e49-5e22 */ + {796917760u, 2809960841u, 3288648476u, 2456192978u, 1815811577u, 68u}, /* 1e50-5e23 */ + {3674210304u, 2329804635u, 2821713694u, 3087093307u, 978246591u, 684u}, /* 1e51-5e24 */ + {2382364672u, 1823209878u, 2447333169u, 806162004u, 1192531325u, 6842u} /* 1e52-5e25 */ }; /* Computes minimal decimal shift right for "double significand" a with length 6 to fit length 3; - using double arithmetic; + using double arithmetic; */ -INLINE REBINT min_shift_right (const REBCNT a[6]) { +REBINT min_shift_right (const REBCNT a[6]) { REBINT i; if (m_cmp (6, a, P26) < 0) return 0; i = (REBINT) (log10 ( - ((((a[5] * two_to_32 + a[4]) * two_to_32 + a[3]) * two_to_32 + a[2]) * two_to_32 + a[1]) * two_to_32 + a[0] - ) + 0.5); - if (i == 26) return 1; + ((((a[5] * two_to_32 + a[4]) * two_to_32 + a[3]) * two_to_32 + a[2]) * two_to_32 + a[1]) * two_to_32 + a[0] + ) + 0.5); + if (i == 26) return 1; return (m_cmp (6, Q[i - 27], a) <= 0) ? i - 25 : i - 26; } /* Finds out if deci a is zero */ -REBFLG deci_is_zero (const deci a) { - return (a.m0 == 0) && (a.m1 == 0) && (a.m2 == 0); +REBOOL deci_is_zero (const deci a) { + return LOGICAL((a.m0 == 0) && (a.m1 == 0) && (a.m2 == 0)); } /* Changes the sign of a deci value */ deci deci_negate (deci a) { - a.s = !a.s; - return a; + a.s = !a.s; + return a; } /* Returns the absolute value of deci a */ deci deci_abs (deci a) { - a.s = 0; - return a; + a.s = 0; + return a; } /* - Adds unsigned 32-bit value b to significand a; - a must be "large enough" to contain the sum; - using 64-bit arithmetic; + Adds unsigned 32-bit value b to significand a; + a must be "large enough" to contain the sum; + using 64-bit arithmetic; */ -INLINE void m_add_1 (REBCNT *a, const REBCNT b) { - REBU64 c = (REBU64) b; - while (c) { - c += (REBU64) *a; - *(a++) = (REBCNT)c; - c >>= 32; - } +void m_add_1 (REBCNT *a, const REBCNT b) { + REBU64 c = (REBU64) b; + while (c) { + c += (REBU64) *a; + *(a++) = (REBCNT)c; + c >>= 32; + } } /* - Subtracts unsigned 32-bit value b from significand a; - using 64-bit arithmetic; + Subtracts unsigned 32-bit value b from significand a; + using 64-bit arithmetic; */ -INLINE void m_subtract_1 (REBCNT *a, const REBCNT b) { - REBI64 c = - (REBI64) b; - while (c) { - c += 0xffffffffu + (REBI64)*a + 1; - *(a++) = MASK32(c); - c = (c >> 32) - 1; - } +void m_subtract_1 (REBCNT *a, const REBCNT b) { + REBI64 c = - (REBI64) b; + while (c) { + c += 0xffffffffu + (REBI64)*a + 1; + *(a++) = MASK32(c); + c = (c >> 32) - 1; + } } /* - Adds significand b to significand a yielding sum s; - using 64-bit arithmetic; + Adds significand b to significand a yielding sum s; + using 64-bit arithmetic; */ -INLINE void m_add (REBINT n, REBCNT s[], const REBCNT a[], const REBCNT b[]) { - REBU64 c = (REBU64) 0; - REBINT i; - for (i = 0; i < n; i++) { - c += (REBU64) a[i] + (REBU64) b[i]; - s[i] = MASK32(c); - c >>= 32; - } - s[n] = (REBCNT)c; +void m_add (REBINT n, REBCNT s[], const REBCNT a[], const REBCNT b[]) { + REBU64 c = (REBU64) 0; + REBINT i; + for (i = 0; i < n; i++) { + c += (REBU64) a[i] + (REBU64) b[i]; + s[i] = MASK32(c); + c >>= 32; + } + s[n] = (REBCNT)c; } /* - Subtracts significand b from significand a yielding difference d; - returns carry flag to signal whether the result is negative; - using 64-bit arithmetic; + Subtracts significand b from significand a yielding difference d; + returns carry flag to signal whether the result is negative; + using 64-bit arithmetic; */ -INLINE REBINT m_subtract (REBINT n, REBCNT d[], const REBCNT a[], const REBCNT b[]) { - REBU64 c = (REBU64) 1; - REBINT i; - for (i = 0; i < n; i++) { - c += (REBU64) 0xffffffffu + (REBU64) a[i] - (REBU64) b[i]; - d[i] = MASK32(c); - c >>= 32; - } - return (REBINT) c - 1; +REBINT m_subtract (REBINT n, REBCNT d[], const REBCNT a[], const REBCNT b[]) { + REBU64 c = (REBU64) 1; + REBINT i; + for (i = 0; i < n; i++) { + c += (REBU64) 0xffffffffu + (REBU64) a[i] - (REBU64) b[i]; + d[i] = MASK32(c); + c >>= 32; + } + return (REBINT) c - 1; } /* - Negates significand a; - using 64-bit arithmetic; + Negates significand a; + using 64-bit arithmetic; */ -INLINE void m_negate (REBINT n, REBCNT a[]) { - REBU64 c = (REBU64) 1; - REBINT i; - for (i = 0; i < n; i++) { - c += (REBU64) 0xffffffffu - (REBU64) a[i]; - a[i] = MASK32(c); - c >>= 32; - } +void m_negate (REBINT n, REBCNT a[]) { + REBU64 c = (REBU64) 1; + REBINT i; + for (i = 0; i < n; i++) { + c += (REBU64) 0xffffffffu - (REBU64) a[i]; + a[i] = MASK32(c); + c >>= 32; + } } -/* - Multiplies significand a by b storing the product to p; - p and a may be the same; - using 64-bit arithmetic; +/* + Multiplies significand a by b storing the product to p; + p and a may be the same; + using 64-bit arithmetic; */ -INLINE void m_multiply_1 (REBINT n, REBCNT p[], const REBCNT a[], REBCNT b) { - REBINT j; - REBU64 f = b, g = (REBU64) 0; - for (j = 0; j < n; j++) { - g += f * (REBU64) a[j]; - p[j] = MASK32(g); - g >>= 32; - } - p[n] = (REBCNT) g; +void m_multiply_1 (REBINT n, REBCNT p[], const REBCNT a[], REBCNT b) { + REBINT j; + REBU64 f = b, g = (REBU64) 0; + for (j = 0; j < n; j++) { + g += f * (REBU64) a[j]; + p[j] = MASK32(g); + g >>= 32; + } + p[n] = (REBCNT) g; } /* - Decimally shifts significand a to the "left"; - a must be longer than the complete result; - n is the initial length of a; + Decimally shifts significand a to the "left"; + a must be longer than the complete result; + n is the initial length of a; */ -INLINE void dsl (REBINT n, REBCNT a[], REBINT shift) { - REBINT shift1; - for (; shift > 0; shift -= shift1) { - shift1 = 9 <= shift ? 9 : shift; - m_multiply_1 (n, a, a, P[shift1][0]); - if (a[n] != 0) n++; - } +void dsl (REBINT n, REBCNT a[], REBINT shift) { + REBINT shift1; + for (; shift > 0; shift -= shift1) { + shift1 = 9 <= shift ? 9 : shift; + m_multiply_1 (n, a, a, P[shift1][0]); + if (a[n] != 0) n++; + } } /* - Multiplies significand a by significand b yielding the product p; - using 64-bit arithmetic; + Multiplies significand a by significand b yielding the product p; + using 64-bit arithmetic; */ -INLINE void m_multiply (REBCNT p[/* n + m */], REBINT n, const REBCNT a[], REBINT m, const REBCNT b[]) { - REBINT i, j; - REBU64 f, g; - memset (p, 0, (n + m) * sizeof (REBCNT)); - for (i = 0; i < m; i++) { - f = (REBU64) b[i]; - g = (REBU64) 0; - for (j = 0; j < n; j++) { - g += f * (REBU64) a[j] + p[i + j]; - p[i + j] = MASK32(g); - g >>= 32; - } - m_add_1 (p + i + j, (REBCNT) g); - } +void m_multiply (REBCNT p[/* n + m */], REBINT n, const REBCNT a[], REBINT m, const REBCNT b[]) { + REBINT i, j; + REBU64 f, g; + memset (p, 0, (n + m) * sizeof (REBCNT)); + for (i = 0; i < m; i++) { + f = (REBU64) b[i]; + g = (REBU64) 0; + for (j = 0; j < n; j++) { + g += f * (REBU64) a[j] + p[i + j]; + p[i + j] = MASK32(g); + g >>= 32; + } + m_add_1 (p + i + j, (REBCNT) g); + } } -/* - Divides significand a by b yielding quotient q; - returns the remainder; - b must be nonzero! - using 64-bit arithmetic; +/* + Divides significand a by b yielding quotient q; + returns the remainder; + b must be nonzero! + using 64-bit arithmetic; */ -INLINE REBCNT m_divide_1 (REBINT n, REBCNT q[], const REBCNT a[], REBCNT b) { - REBINT i; - REBU64 f = 0, g = b; - for (i = n - 1; i >= 0; i--) { - f = (f << 32) + (REBU64) a[i]; - q[i] = (REBCNT)(f / g); - f %= g; - } - return (REBCNT) f; +REBCNT m_divide_1 (REBINT n, REBCNT q[], const REBCNT a[], REBCNT b) { + REBINT i; + REBU64 f = 0, g = b; + for (i = n - 1; i >= 0; i--) { + f = (f << 32) + (REBU64) a[i]; + q[i] = (REBCNT)(f / g); + f %= g; + } + return (REBCNT) f; } /* - Decimally shifts significand a to the "right"; - truncate flag t_flag is an I/O value with the following meaning: - 0 - result is exact - 1 - less than half of the least significant unit truncated - 2 - exactly half of the least significant unit truncated - 3 - more than half of the least significant unit truncated + Decimally shifts significand a to the "right"; + truncate flag t_flag is an I/O value with the following meaning: + 0 - result is exact + 1 - less than half of the least significant unit truncated + 2 - exactly half of the least significant unit truncated + 3 - more than half of the least significant unit truncated */ -INLINE void dsr (REBINT n, REBCNT a[], REBINT shift, REBINT *t_flag) { - REBCNT remainder, divisor; - REBINT shift1; - for (; shift > 0; shift -= shift1) { - shift1 = 9 <= shift ? 9 : shift; - remainder = m_divide_1 (n, a, a, divisor = P[shift1][0]); - if (remainder < divisor / 2) { - if (remainder || *t_flag) *t_flag = 1; - } else if ((remainder > divisor / 2) || *t_flag) *t_flag = 3; - else *t_flag = 2; - } -} +void dsr (REBINT n, REBCNT a[], REBINT shift, REBINT *t_flag) { + REBCNT remainder, divisor; + REBINT shift1; + for (; shift > 0; shift -= shift1) { + shift1 = 9 <= shift ? 9 : shift; + remainder = m_divide_1 (n, a, a, divisor = P[shift1][0]); + if (remainder < divisor / 2) { + if (remainder || *t_flag) *t_flag = 1; + } else if ((remainder > divisor / 2) || *t_flag) *t_flag = 3; + else *t_flag = 2; + } +} /* - Decimally shifts significands a and b to make them comparable; - ea and eb are exponents; - ta and tb are truncate flags like above; + Decimally shifts significands a and b to make them comparable; + ea and eb are exponents; + ta and tb are truncate flags like above; */ -INLINE void make_comparable (REBCNT a[4], REBINT *ea, REBINT *ta, REBCNT b[4], REBINT *eb, REBINT *tb) { - REBCNT *c; - REBINT *p; - REBINT shift, shift1; - - /* set truncate flags to zero */ - *ta = 0; - *tb = 0; - - if (*ea == *eb) return; /* no work needed */ - - if (*ea < *eb) { - /* swap a and b to fulfill the condition below */ - c = a; - a = b; - b = c; - - p = ea; - ea = eb; - eb = p; - - p = ta; - ta = tb; - tb = p; - } - /* (*ea > *eb) */ - - /* decimally shift a to the left */ - if (m_is_zero (3, a)) { - *ea = *eb; - return; - } - shift1 = max_shift_left (a) + 1; - shift = *ea - *eb; +void make_comparable (REBCNT a[4], REBINT *ea, REBINT *ta, REBCNT b[4], REBINT *eb, REBINT *tb) { + REBCNT *c; + REBINT *p; + REBINT shift, shift1; + + /* set truncate flags to zero */ + *ta = 0; + *tb = 0; + + if (*ea == *eb) return; /* no work needed */ + + if (*ea < *eb) { + /* swap a and b to fulfill the condition below */ + c = a; + a = b; + b = c; + + p = ea; + ea = eb; + eb = p; + + p = ta; + ta = tb; + tb = p; + } + /* (*ea > *eb) */ + + /* decimally shift a to the left */ + if (m_is_zero (3, a)) { + *ea = *eb; + return; + } + shift1 = max_shift_left (a) + 1; + shift = *ea - *eb; dsl (3, a, shift1 = shift1 < shift ? shift1 : shift); *ea -= shift1; - - /* decimally shift b to the right if necessary */ - shift = *ea - *eb; - if (!shift) return; - if (shift > 26) { - /* significand underflow */ - if (!m_is_zero (3, b)) *tb = 1; - memset (b, 0, 3 * sizeof (REBCNT)); - *eb = *ea; - return; - } - dsr (3, b, shift, tb); - *eb = *ea; + + /* decimally shift b to the right if necessary */ + shift = *ea - *eb; + if (!shift) return; + if (shift > 26) { + /* significand underflow */ + if (!m_is_zero (3, b)) *tb = 1; + memset (b, 0, 3 * sizeof (REBCNT)); + *eb = *ea; + return; + } + dsr (3, b, shift, tb); + *eb = *ea; } -REBFLG deci_is_equal (deci a, deci b) { - REBINT ea = a.e, eb = b.e, ta, tb; - REBCNT sa[] = {a.m0, a.m1, a.m2, 0}, sb[] = {b.m0, b.m1, b.m2, 0}; - - make_comparable (sa, &ea, &ta, sb, &eb, &tb); - - /* round */ - if ((ta == 3) || ((ta == 2) && (sa[0] % 2 == 1))) m_add_1 (sa, 1); - else if ((tb == 3) || ((tb == 2) && (sb[0] % 2 == 1))) m_add_1 (sb, 1); - - return (m_cmp (3, sa, sb) == 0) && ((a.s == b.s) || m_is_zero (3, sa)); +REBOOL deci_is_equal (deci a, deci b) { + REBINT ea = a.e, eb = b.e, ta, tb; + + // Must be compile-time const for '= {...}' style init (-Wc99-extensions) + REBCNT sa[4]; + REBCNT sb[4]; + + sa[0] = a.m0; + sa[1] = a.m1; + sa[2] = a.m2; + sa[3] = 0; + + sb[0] = b.m0; + sb[1] = b.m1; + sb[2] = b.m2; + sb[3] = 0; + + make_comparable (sa, &ea, &ta, sb, &eb, &tb); + + /* round */ + if ((ta == 3) || ((ta == 2) && (sa[0] % 2 == 1))) m_add_1 (sa, 1); + else if ((tb == 3) || ((tb == 2) && (sb[0] % 2 == 1))) m_add_1 (sb, 1); + + return LOGICAL( + (m_cmp (3, sa, sb) == 0) && ((a.s == b.s) || m_is_zero (3, sa)) + ); } -REBFLG deci_is_lesser_or_equal (deci a, deci b) { - REBINT ea = a.e, eb = b.e, ta, tb; - REBCNT sa[] = {a.m0, a.m1, a.m2, 0}, sb[] = {b.m0, b.m1, b.m2, 0}; +REBOOL deci_is_lesser_or_equal (deci a, deci b) { + REBINT ea = a.e, eb = b.e, ta, tb; + + // Must be compile-time const for '= {...}' style init (-Wc99-extensions) + REBCNT sa[4]; + REBCNT sb[4]; + + sa[0] = a.m0; + sa[1] = a.m1; + sa[2] = a.m2; + sa[3] = 0; - if (a.s && !b.s) return 1; - if (!a.s && b.s) return m_is_zero (3, sa) && m_is_zero (3, sb); - make_comparable (sa, &ea, &ta, sb, &eb, &tb); + sb[0] = b.m0; + sb[1] = b.m1; + sb[2] = b.m2; + sb[3] = 0; - /* round */ - if ((ta == 3) || ((ta == 2) && (sa[0] % 2 == 1))) m_add_1 (sa, 1); - else if ((tb == 3) || ((tb == 2) && (sb[0] % 2 == 1))) m_add_1 (sb, 1); + if (a.s && !b.s) return TRUE; + if (!a.s && b.s) return LOGICAL(m_is_zero (3, sa) && m_is_zero (3, sb)); + make_comparable (sa, &ea, &ta, sb, &eb, &tb); - return a.s ? (m_cmp (3, sa, sb) >= 0) : (m_cmp (3, sa, sb) <= 0); + /* round */ + if ((ta == 3) || ((ta == 2) && (sa[0] % 2 == 1))) m_add_1 (sa, 1); + else if ((tb == 3) || ((tb == 2) && (sb[0] % 2 == 1))) m_add_1 (sb, 1); + + return LOGICAL( + a.s ? (m_cmp (3, sa, sb) >= 0) : (m_cmp (3, sa, sb) <= 0) + ); } deci deci_add (deci a, deci b) { - deci c; - REBCNT sc[4]; - REBINT ea = a.e, eb = b.e, ta, tb, tc, test; - REBCNT sa[] = {a.m0, a.m1, a.m2, 0}, sb[] = {b.m0, b.m1, b.m2, 0}; - - make_comparable (sa, &ea, &ta, sb, &eb, &tb); - - c.s = a.s; - if (a.s == b.s) { - /* addition */ - m_add (3, sc, sa, sb); - tc = ta + tb; - - /* significand normalization */ - test = m_cmp (3, sc, P26_1); - if ((test > 0) || ((test == 0) && ((tc == 3) || ((tc == 2) && (sc[0] % 2 == 1))))) { - if (ea == 127) OVERFLOW_ERROR; - ea++; - dsr (3, sc, 1, &tc); - /* the shift may be needed once again */ - test = m_cmp (3, sc, P26_1); - if ((test > 0) || ((test == 0) && ((tc == 3) || ((tc == 2) && (sc[0] % 2 == 1))))) { - if (ea == 127) OVERFLOW_ERROR; - ea++; - dsr (3, sc, 1, &tc); - } - } - - /* round */ - if ((tc == 3) || ((tc == 2) && (sc[0] % 2 == 1))) m_add_1 (sc, 1); - - } else { - /* subtraction */ - tc = ta - tb; - if (m_subtract (3, sc, sa, sb)) { - m_negate (3, sc); - c.s = b.s; - tc = -tc; - } - /* round */ - if ((tc == 3) || ((tc == 2) && (sc[0] % 2 == 1))) m_add_1 (sc, 1); - else if ((tc == -3) || ((tc == -2) && (sc[0] % 2 == 1))) m_subtract_1 (sc, 1); - } - c.m0 = sc[0]; - c.m1 = sc[1]; - c.m2 = sc[2]; - c.e = ea; - return c; + deci c; + REBCNT sc[4]; + REBINT ea = a.e, eb = b.e, ta, tb, tc, test; + + // Must be compile-time const for '= {...}' style init (-Wc99-extensions) + REBCNT sa[4]; + REBCNT sb[4]; + + sa[0] = a.m0; + sa[1] = a.m1; + sa[2] = a.m2; + sa[3] = 0; + + sb[0] = b.m0; + sb[1] = b.m1; + sb[2] = b.m2; + sb[3] = 0; + + make_comparable (sa, &ea, &ta, sb, &eb, &tb); + + c.s = a.s; + if (a.s == b.s) { + /* addition */ + m_add (3, sc, sa, sb); + tc = ta + tb; + + /* significand normalization */ + test = m_cmp (3, sc, P26_1); + if ((test > 0) || ((test == 0) && ((tc == 3) || ((tc == 2) && (sc[0] % 2 == 1))))) { + if (ea == 127) fail (Error_Overflow_Raw()); + ea++; + dsr (3, sc, 1, &tc); + /* the shift may be needed once again */ + test = m_cmp (3, sc, P26_1); + if ((test > 0) || ((test == 0) && ((tc == 3) || ((tc == 2) && (sc[0] % 2 == 1))))) { + if (ea == 127) fail (Error_Overflow_Raw()); + ea++; + dsr (3, sc, 1, &tc); + } + } + + /* round */ + if ((tc == 3) || ((tc == 2) && (sc[0] % 2 == 1))) m_add_1 (sc, 1); + + } else { + /* subtraction */ + tc = ta - tb; + if (m_subtract (3, sc, sa, sb)) { + m_negate (3, sc); + c.s = b.s; + tc = -tc; + } + /* round */ + if ((tc == 3) || ((tc == 2) && (sc[0] % 2 == 1))) m_add_1 (sc, 1); + else if ((tc == -3) || ((tc == -2) && (sc[0] % 2 == 1))) m_subtract_1 (sc, 1); + } + c.m0 = sc[0]; + c.m1 = sc[1]; + c.m2 = sc[2]; + c.e = ea; + return c; } deci deci_subtract (deci a, deci b) {return deci_add (a, deci_negate (b));} /* using 64-bit arithmetic */ deci int_to_deci (REBI64 a) { - deci c; - c.e = 0; - if (0 <= a) c.s = 0; else {c.s = 1; a = -a;} - c.m0 = (REBCNT)a; - c.m1 = (REBCNT)(a >> 32); - c.m2 = 0; - return c; + deci c; + c.e = 0; + if (0 <= a) c.s = 0; else {c.s = 1; a = -a;} + c.m0 = (REBCNT)a; + c.m1 = (REBCNT)(a >> 32); + c.m2 = 0; + return c; } /* using 64-bit arithmetic */ REBI64 deci_to_int (const deci a) { - REBCNT sa[] = {a.m0, a.m1, a.m2, 0}; - REBINT ta; - REBI64 result; - - /* handle zero and small numbers */ - if (m_is_zero (3, sa) || (a.e < -26)) return (REBI64) 0; - - /* handle exponent */ - if (a.e >= 20) OVERFLOW_ERROR; - if (a.e > 0) - if (m_cmp (3, P[20 - a.e], sa) <= 0) OVERFLOW_ERROR; - else dsl (3, sa, a.e); - else if (a.e < 0) dsr (3, sa, -a.e, &ta); - - /* convert significand to integer */ - if (m_cmp (3, sa, min_int64_t_as_deci) > 0) OVERFLOW_ERROR; - result = ((REBI64) sa[1] << 32) | (REBI64) sa[0]; - - /* handle sign */ - if (a.s) result = -result; - if (!a.s && (result < 0)) OVERFLOW_ERROR; - - return result; + REBINT ta; + REBI64 result; + + // Must be compile-time const for '= {...}' style init (-Wc99-extensions) + REBCNT sa[4]; + + sa[0] = a.m0; + sa[1] = a.m1; + sa[2] = a.m2; + sa[3] = 0; + + /* handle zero and small numbers */ + if (m_is_zero (3, sa) || (a.e < -26)) return (REBI64) 0; + + /* handle exponent */ + if (a.e >= 20) fail (Error_Overflow_Raw()); + if (a.e > 0) + if (m_cmp (3, P[20 - a.e], sa) <= 0) fail (Error_Overflow_Raw()); + else dsl (3, sa, a.e); + else if (a.e < 0) dsr (3, sa, -a.e, &ta); + + /* convert significand to integer */ + if (m_cmp (3, sa, min_int64_t_as_deci) > 0) fail (Error_Overflow_Raw()); + result = cast(REBI64, (cast(REBU64, sa[1]) << 32) | cast(REBU64, sa[0])); + + /* handle sign */ + if (a.s && result > MIN_I64) result = -result; + if (!a.s && (result < 0)) fail (Error_Overflow_Raw()); + + return result; } REBDEC deci_to_decimal (const deci a) { - /* use STRTOD */ - char *se; + /* use STRTOD */ + const char *se; REBYTE b [34]; - deci_to_string(b, a, 0, '.'); - return STRTOD((char *)b, &se); + deci_to_string(b, a, 0, '.'); + return STRTOD(s_cast(b), &se); } #define DOUBLE_DIGITS 17 /* using the dtoa function */ deci decimal_to_deci (REBDEC a) { - deci result; - REBI64 d; /* decimal significand */ - int e; /* decimal exponent */ - int s; /* sign */ - REBYTE *c; - REBYTE *rve; + deci result; + REBI64 d; /* decimal significand */ + int e; /* decimal exponent */ + int s; /* sign */ + REBYTE *c; + REBYTE *rve; /* convert a to string */ - c = (REBYTE *) dtoa (a, 0, DOUBLE_DIGITS, &e, &s, (char **) &rve); + c = (REBYTE *) dtoa (a, 0, DOUBLE_DIGITS, &e, &s, (char **) &rve); - e -= (rve - c); + e -= (rve - c); - d = CHR_TO_INT(c); + d = CHR_TO_INT(c); - result.s = s; - result.m2 = 0; - result.m1 = (REBCNT)(d >> 32); - result.m0 = (REBCNT)d; - result.e = 0; + result.s = s; + result.m2 = 0; + result.m1 = (REBCNT)(d >> 32); + result.m0 = (REBCNT)d; + result.e = 0; - return deci_ldexp(result, e); + return deci_ldexp(result, e); } /* - Calculates a * (10 ** (*f + e)); + Calculates a * (10 ** (*f + e)); returns zero when underflow occurs; - ta is a truncate flag as described above; - *f is supposed to be in range [-128; 127]; + ta is a truncate flag as described above; + *f is supposed to be in range [-128; 127]; */ -INLINE void m_ldexp (REBCNT a[4], REBINT *f, REBINT e, REBINT ta) { - /* take care of zero significand */ - if (m_is_zero (3, a)) { - *f = 0; - return; - } - - /* take care of exponent overflow */ - if (e >= 281) OVERFLOW_ERROR; - if (e < -281) e = -282; - - *f += e; - - /* decimally shift the significand to the right if needed */ - if (*f < -128) { - if (*f < -154) { - /* underflow */ - memset (a, 0, 3 * sizeof (REBCNT)); - *f = 0; - return; - } - /* shift and round */ - dsr (3, a, -128 - *f, &ta); - *f = -128; - if ((ta == 3) || ((ta == 2) && (a[0] % 2 == 1))) m_add_1 (a, 1); - return; - } - - /* decimally shift the significand to the left if needed */ - if (*f > 127) { - if ((*f >= 153) || (m_cmp (3, P[153 - *f], a) <= 0)) OVERFLOW_ERROR; - dsl (3, a, *f - 127); - *f = 127; - } +void m_ldexp (REBCNT a[4], REBINT *f, REBINT e, REBINT ta) { + /* take care of zero significand */ + if (m_is_zero (3, a)) { + *f = 0; + return; + } + + /* take care of exponent overflow */ + if (e >= 281) fail (Error_Overflow_Raw()); + if (e < -281) e = -282; + + *f += e; + + /* decimally shift the significand to the right if needed */ + if (*f < -128) { + if (*f < -154) { + /* underflow */ + memset (a, 0, 3 * sizeof (REBCNT)); + *f = 0; + return; + } + /* shift and round */ + dsr (3, a, -128 - *f, &ta); + *f = -128; + if ((ta == 3) || ((ta == 2) && (a[0] % 2 == 1))) m_add_1 (a, 1); + return; + } + + /* decimally shift the significand to the left if needed */ + if (*f > 127) { + if ((*f >= 153) || (m_cmp (3, P[153 - *f], a) <= 0)) + fail (Error_Overflow_Raw()); + dsl (3, a, *f - 127); + *f = 127; + } } /* Calculates a * (10 ** e); returns zero when underflow occurs */ deci deci_ldexp (deci a, REBINT e) { - REBCNT sa[] = {a.m0, a.m1, a.m2, 0}; - REBINT f = a.e; - - m_ldexp (sa, &f, e, 0); - a.m0 = sa[0]; - a.m1 = sa[1]; - a.m2 = sa[2]; - a.e = f; - return a; + REBINT f = a.e; + + // Must be compile-time const for '= {...}' style init (-Wc99-extensions) + REBCNT sa[4]; + + sa[0] = a.m0; + sa[1] = a.m1; + sa[2] = a.m2; + sa[3] = 0; + + m_ldexp (sa, &f, e, 0); + a.m0 = sa[0]; + a.m1 = sa[1]; + a.m2 = sa[2]; + a.e = f; + return a; } #define denormalize \ - if (a.e >= b.e) return a; \ - sa[0] = a.m0; \ - sa[1] = a.m1; \ - sa[2] = a.m2; \ - dsr (3, sa, b.e - a.e, &ta); \ - a.m0 = sa[0]; \ - a.m1 = sa[1]; \ - a.m2 = sa[2]; \ - a.e = b.e; \ - return a; + if (a.e >= b.e) return a; \ + sa[0] = a.m0; \ + sa[1] = a.m1; \ + sa[2] = a.m2; \ + dsr (3, sa, b.e - a.e, &ta); \ + a.m0 = sa[0]; \ + a.m1 = sa[1]; \ + a.m2 = sa[2]; \ + a.e = b.e; \ + return a; /* truncate a to obtain a multiple of b */ deci deci_truncate (deci a, deci b) { - deci c; - REBCNT sa[3]; - REBINT ta = 0; - - c = deci_mod (a, b); - /* negate c */ - c.s = !c.s; - a = deci_add (a, c); - /* a is now a multiple of b */ - - denormalize + deci c; + REBCNT sa[3]; + REBINT ta = 0; + + c = deci_mod (a, b); + /* negate c */ + c.s = !c.s; + a = deci_add (a, c); + /* a is now a multiple of b */ + + denormalize } /* round a away from zero to obtain a multiple of b */ deci deci_away (deci a, deci b) { - deci c; - REBCNT sa[3]; - REBINT ta = 0; - - c = deci_mod (a, b); - if (!deci_is_zero (c)) { - /* negate c and add b with the sign of c */ - b.s = c.s; - c.s = !c.s; - c = deci_add (c, b); - } - a = deci_add (a, c); - /* a is now a multiple of b */ - - denormalize + deci c; + REBCNT sa[3]; + REBINT ta = 0; + + c = deci_mod (a, b); + if (!deci_is_zero (c)) { + /* negate c and add b with the sign of c */ + b.s = c.s; + c.s = !c.s; + c = deci_add (c, b); + } + a = deci_add (a, c); + /* a is now a multiple of b */ + + denormalize } /* round a down to obtain a multiple of b */ deci deci_floor (deci a, deci b) { - deci c; - REBCNT sa[3]; - REBINT ta = 0; - - c = deci_mod (a, b); - /* negate c */ - c.s = !c.s; - if (!c.s && !deci_is_zero (c)) { - /* c is positive, add negative b to obtain a negative value */ - b.s = 1; - c = deci_add (b, c); - } - a = deci_add (a, c); - /* a is now a multiple of b */ - - denormalize + deci c; + REBCNT sa[3]; + REBINT ta = 0; + + c = deci_mod (a, b); + /* negate c */ + c.s = !c.s; + if (!c.s && !deci_is_zero (c)) { + /* c is positive, add negative b to obtain a negative value */ + b.s = 1; + c = deci_add (b, c); + } + a = deci_add (a, c); + /* a is now a multiple of b */ + + denormalize } /* round a up to obtain a multiple of b */ deci deci_ceil (deci a, deci b) { - deci c; - REBCNT sa[3]; - REBINT ta = 0; - - c = deci_mod (a, b); - /* negate c */ - c.s = !c.s; - if (c.s && !deci_is_zero (c)) { - /* c is negative, add positive b to obtain a positive value */ - b.s = 0; - c = deci_add (c, b); - } - a = deci_add (a, c); - /* a is now a multiple of b */ - - denormalize + deci c; + REBCNT sa[3]; + REBINT ta = 0; + + c = deci_mod (a, b); + /* negate c */ + c.s = !c.s; + if (c.s && !deci_is_zero (c)) { + /* c is negative, add positive b to obtain a positive value */ + b.s = 0; + c = deci_add (c, b); + } + a = deci_add (a, c); + /* a is now a multiple of b */ + + denormalize } /* round a half even to obtain a multiple of b */ deci deci_half_even (deci a, deci b) { - deci c, d, e, f; - REBCNT sa[3]; - REBINT ta = 0; - REBFLG g; - - c = deci_mod (a, b); - - /* compare c with b/2 not causing overflow */ - b.s = 0; - c.s = 1; - d = deci_add (b, c); - c.s = 0; - if (deci_is_equal (c, d)) { - /* rounding half */ - e = deci_add(b, b); /* this may cause overflow for large b */ - f = deci_mod(a, e); - f.s = 0; - g = deci_is_lesser_or_equal(f, b); - } else g = deci_is_lesser_or_equal(c, d); - if (g) { - /* rounding towards zero */ - c.s = !a.s; - } else { - /* rounding away from zero */ - c = d; - c.s = a.s; - } - a = deci_add (a, c); - /* a is now a multiple of b */ - - denormalize + deci c, d, e, f; + REBCNT sa[3]; + REBINT ta = 0; + REBOOL g; + + c = deci_mod (a, b); + + /* compare c with b/2 not causing overflow */ + b.s = 0; + c.s = 1; + d = deci_add (b, c); + c.s = 0; + if (deci_is_equal (c, d)) { + /* rounding half */ + e = deci_add(b, b); /* this may cause overflow for large b */ + f = deci_mod(a, e); + f.s = 0; + g = deci_is_lesser_or_equal(f, b); + } else g = deci_is_lesser_or_equal(c, d); + if (g) { + /* rounding towards zero */ + c.s = !a.s; + } else { + /* rounding away from zero */ + c = d; + c.s = a.s; + } + a = deci_add (a, c); + /* a is now a multiple of b */ + + denormalize } /* round a half away from zero to obtain a multiple of b */ deci deci_half_away (deci a, deci b) { - deci c, d; - REBCNT sa[3]; - REBINT ta = 0; - - c = deci_mod (a, b); - - /* compare c with b/2 not causing overflow */ - b.s = 0; - c.s = 1; - d = deci_add (b, c); - c.s = 0; - if (deci_is_lesser_or_equal (d, c)) { - /* rounding away */ - c = d; - c.s = a.s; - } else { - /* truncating */ - c.s = !a.s; - } - a = deci_add (a, c); - /* a is now a multiple of b */ - - denormalize + deci c, d; + REBCNT sa[3]; + REBINT ta = 0; + + c = deci_mod (a, b); + + /* compare c with b/2 not causing overflow */ + b.s = 0; + c.s = 1; + d = deci_add (b, c); + c.s = 0; + if (deci_is_lesser_or_equal (d, c)) { + /* rounding away */ + c = d; + c.s = a.s; + } else { + /* truncating */ + c.s = !a.s; + } + a = deci_add (a, c); + /* a is now a multiple of b */ + + denormalize } /* round a half truncate to obtain a multiple of b */ deci deci_half_truncate (deci a, deci b) { - deci c, d; - REBCNT sa[3]; - REBINT ta = 0; - - c = deci_mod (a, b); - - /* compare c with b/2 not causing overflow */ - b.s = 0; - c.s = 1; - d = deci_add (b, c); - c.s = 0; - if (deci_is_lesser_or_equal (c, d)) { - /* truncating */ - c.s = !a.s; - } else { - /* rounding away */ - c = d; - c.s = a.s; - } - a = deci_add (a, c); - /* a is now a multiple of b */ - - denormalize + deci c, d; + REBCNT sa[3]; + REBINT ta = 0; + + c = deci_mod (a, b); + + /* compare c with b/2 not causing overflow */ + b.s = 0; + c.s = 1; + d = deci_add (b, c); + c.s = 0; + if (deci_is_lesser_or_equal (c, d)) { + /* truncating */ + c.s = !a.s; + } else { + /* rounding away */ + c = d; + c.s = a.s; + } + a = deci_add (a, c); + /* a is now a multiple of b */ + + denormalize } /* round a half up to obtain a multiple of b */ deci deci_half_ceil (deci a, deci b) { - deci c, d; - REBCNT sa[3]; - REBINT ta = 0; - - c = deci_mod (a, b); - - /* compare c with b/2 not causing overflow */ - b.s = 0; - c.s = 1; - d = deci_add (b, c); - c.s = 0; - - if (a.s ? deci_is_lesser_or_equal(c, d) : !deci_is_lesser_or_equal(d, c)) { - /* truncating */ - c.s = !a.s; - } else { - /* rounding away */ - c = d; - c.s = a.s; - } + deci c, d; + REBCNT sa[3]; + REBINT ta = 0; + + c = deci_mod (a, b); + + /* compare c with b/2 not causing overflow */ + b.s = 0; + c.s = 1; + d = deci_add (b, c); + c.s = 0; + + if ( + a.s + ? deci_is_lesser_or_equal(c, d) + : NOT(deci_is_lesser_or_equal(d, c)) + ) { + /* truncating */ + c.s = !a.s; + } else { + /* rounding away */ + c = d; + c.s = a.s; + } #ifdef RM_FIX_B1471 - if (deci_is_lesser_or_equal (d, c)) { - /* rounding up */ - c.s = !a.s; - if (c.s && !deci_is_zero (c)) { - /* c is negative, use d */ - c = d; - c.s = a.s; - } - } else { - /* rounding down */ - c.s = !a.s; - if (!c.s && !deci_is_zero (c)) { - /* c is positive, use d */ - c = d; - c.s = a.s; - } - } + if (deci_is_lesser_or_equal (d, c)) { + /* rounding up */ + c.s = !a.s; + if (c.s && !deci_is_zero (c)) { + /* c is negative, use d */ + c = d; + c.s = a.s; + } + } else { + /* rounding down */ + c.s = !a.s; + if (!c.s && !deci_is_zero (c)) { + /* c is positive, use d */ + c = d; + c.s = a.s; + } + } #endif - a = deci_add(a, c); - /* a is now a multiple of b */ - - denormalize + a = deci_add(a, c); + /* a is now a multiple of b */ + + denormalize } /* round a half down to obtain a multiple of b */ deci deci_half_floor (deci a, deci b) { - deci c, d; - REBCNT sa[3]; - REBINT ta = 0; - - c = deci_mod (a, b); - - /* compare c with b/2 not causing overflow */ - b.s = 0; - c.s = 1; - d = deci_add (b, c); - c.s = 0; - - if (a.s ? !deci_is_lesser_or_equal(d, c) : deci_is_lesser_or_equal(c, d)) { - /* truncating */ - c.s = !a.s; - } else { - /* rounding away */ - c = d; - c.s = a.s; - } + deci c, d; + REBCNT sa[3]; + REBINT ta = 0; + + c = deci_mod (a, b); + + /* compare c with b/2 not causing overflow */ + b.s = 0; + c.s = 1; + d = deci_add (b, c); + c.s = 0; + + if ( + a.s + ? NOT(deci_is_lesser_or_equal(d, c)) + : deci_is_lesser_or_equal(c, d) + ) { + /* truncating */ + c.s = !a.s; + } else { + /* rounding away */ + c = d; + c.s = a.s; + } #ifdef RM_FIX_B1471 - if (deci_is_lesser_or_equal (c, d)) { - /* rounding down */ - c.s = !a.s; - if (!c.s && !deci_is_zero (c)) { - /* c is positive, use d */ - c = d; - c.s = a.s; - } - } else { - /* rounding up */ - c.s = !a.s; - if (c.s && !deci_is_zero (c)) { - /* c is negative, use d */ - c = d; - c.s = a.s; - } - } + if (deci_is_lesser_or_equal (c, d)) { + /* rounding down */ + c.s = !a.s; + if (!c.s && !deci_is_zero (c)) { + /* c is positive, use d */ + c = d; + c.s = a.s; + } + } else { + /* rounding up */ + c.s = !a.s; + if (c.s && !deci_is_zero (c)) { + /* c is negative, use d */ + c = d; + c.s = a.s; + } + } #endif - a = deci_add(a, c); - /* a is now a multiple of b */ - - denormalize + a = deci_add(a, c); + /* a is now a multiple of b */ + + denormalize } deci deci_multiply (const deci a, const deci b) { - deci c; - REBCNT sa[] = {a.m0, a.m1, a.m2}, sb[] = {b.m0, b.m1, b.m2}, sc[7]; - REBINT shift, tc = 0, e, f = 0; - - /* compute the sign */ - c.s = (!a.s && b.s) || (a.s && !b.s); - - /* multiply sa by sb yielding "double significand" sc */ - m_multiply (sc, 3, sa, 3, sb); - - /* normalize "double significand" sc and round if needed */ - shift = min_shift_right (sc); - e = a.e + b.e + shift; - if (shift > 0) { - dsr (6, sc, shift, &tc); - if (((tc == 3) || ((tc == 2) && (sc[0] % 2 == 1))) && (e >= -128)) m_add_1 (sc, 1); - } - - m_ldexp (sc, &f, e, tc); - c.m0 = sc[0]; - c.m1 = sc[1]; - c.m2 = sc[2]; - c.e = f; - return c; + deci c; + REBCNT sc[7]; + REBINT shift, tc = 0, e, f = 0; + + // Must be compile-time const for '= {...}' style init (-Wc99-extensions) + REBCNT sa[3]; + REBCNT sb[3]; + + sa[0] = a.m0; + sa[1] = a.m1; + sa[2] = a.m2; + + sb[0] = b.m0; + sb[1] = b.m1; + sb[2] = b.m2; + + /* compute the sign */ + c.s = (!a.s && b.s) || (a.s && !b.s); + + /* multiply sa by sb yielding "double significand" sc */ + m_multiply (sc, 3, sa, 3, sb); + + /* normalize "double significand" sc and round if needed */ + shift = min_shift_right (sc); + e = a.e + b.e + shift; + if (shift > 0) { + dsr (6, sc, shift, &tc); + if (((tc == 3) || ((tc == 2) && (sc[0] % 2 == 1))) && (e >= -128)) m_add_1 (sc, 1); + } + + m_ldexp (sc, &f, e, tc); + c.m0 = sc[0]; + c.m1 = sc[1]; + c.m2 = sc[2]; + c.e = f; + return c; } /* - b[m - 1] is supposed to be nonzero; - m <= n required; - a, b are copied on entry; - uses 64-bit arithmetic; + b[m - 1] is supposed to be nonzero; + m <= n required; + a, b are copied on entry; + uses 64-bit arithmetic; */ #define MAX_N 7 #define MAX_M 3 -INLINE void m_divide ( - REBCNT q[/* n - m + 1 */], - REBCNT r[/* m */], - const REBINT n, - const REBCNT a[/* n */], - const REBINT m, - const REBCNT b[/* m */] +void m_divide ( + REBCNT q[/* n - m + 1 */], + REBCNT r[/* m */], + const REBINT n, + const REBCNT a[/* n */], + const REBINT m, + const REBCNT b[/* m */] ) { - REBCNT c[MAX_N + 1], d[MAX_M + 1], e[MAX_M + 1]; - REBCNT bm = b[m - 1]; - REBU64 cm, dm; - REBINT i, j, k; - - if (m == 1) { - r[0] = m_divide_1 (n, q, a, bm); - return; - } + REBCNT c[MAX_N + 1], d[MAX_M + 1], e[MAX_M + 1]; + REBCNT bm = b[m - 1]; + REBU64 cm, dm; + REBINT i, j, k; + + if (m <= 1) { + // Note: the test here used to be `if (m == 1)` but gcc 4.9.2 would + // warn in -O2 mode that array subscripting with [m - 1] could be + // below array bounds, due to not knowing the caller wouldn't pass in + // zero. Changed test to `if (m <= 1)`, added assert m is not zero. + // + assert(m != 0); + r[0] = m_divide_1 (n, q, a, bm); + return; + } /* we shift both the divisor and the dividend to the left to obtain quotients that are off by one at most */ - /* the most significant bit of b[m - 1] */ - i = 0; - j = 31; - while (i < j) { - k = (i + j + 1) / 2; - if ((REBCNT)(1 << k) <= bm) i = k; else j = k - 1; - } - - /* shift the dividend to the left */ - for (j = 0; j < n; j++) c[j] = a[j] << (31 - i); - c[n] = 0; - for (j = 0; j < n; j++) c[j + 1] |= a[j] >> (i + 1); - - /* shift the divisor to the left */ - for (j = 0; j < m; j++) d[j] = b[j] << (31 - i); - d[m] = 0; - for (j = 0; j < m; j++) d[j + 1] |= b[j] >> (i + 1); - - dm = (REBU64) d[m - 1]; - - for (j = n - m; j >= 0; j--) { - cm = ((REBU64) c[j + m] << 32) + (REBU64) c[j + m - 1]; - cm /= dm; - if (cm > 0xffffffffu) cm = 0xffffffffu; - m_multiply_1 (m, e, d, (REBCNT) cm); - if (m_subtract (m + 1, c + j, c + j, e)) { - /* the quotient is off by one */ - cm--; - m_add (m, c + j, c + j, d); - } - q[j] = (REBCNT) cm; - } + /* the most significant bit of b[m - 1] */ + i = 0; + j = 31; + while (i < j) { + k = (i + j + 1) / 2; + if ((REBCNT)(1 << k) <= bm) i = k; else j = k - 1; + } + + /* shift the dividend to the left */ + for (j = 0; j < n; j++) c[j] = a[j] << (31 - i); + c[n] = 0; + for (j = 0; j < n; j++) c[j + 1] |= a[j] >> (i + 1); + + /* shift the divisor to the left */ + for (j = 0; j < m; j++) d[j] = b[j] << (31 - i); + d[m] = 0; + for (j = 0; j < m; j++) d[j + 1] |= b[j] >> (i + 1); + + dm = (REBU64) d[m - 1]; + + for (j = n - m; j >= 0; j--) { + cm = ((REBU64) c[j + m] << 32) + (REBU64) c[j + m - 1]; + cm /= dm; + if (cm > 0xffffffffu) cm = 0xffffffffu; + m_multiply_1 (m, e, d, (REBCNT) cm); + if (m_subtract (m + 1, c + j, c + j, e)) { + /* the quotient is off by one */ + cm--; + m_add (m, c + j, c + j, d); + } + q[j] = (REBCNT) cm; + } /* shift the remainder back to the right */ c[m] = 0; @@ -983,334 +1070,373 @@ INLINE void m_divide ( /* uses double arithmetic */ deci deci_divide (deci a, deci b) { - REBINT e = a.e - b.e, f = 0; - deci c; - REBCNT q[] = {0, 0, 0, 0, 0, 0}, r[4]; - REBCNT sa[] = {a.m0, a.m1, a.m2, 0, 0, 0}, sb[] = {b.m0, b.m1, b.m2, 0}; - double a_dbl, b_dbl, l10; - REBINT shift, na, nb, tc; - - if (deci_is_zero (b)) DIVIDE_BY_ZERO_ERROR; - - /* compute sign */ - c.s = (!a.s && b.s) || (a.s && !b.s); - - if (deci_is_zero (a)) { - c.m0 = 0; - c.m1 = 0; - c.m2 = 0; - c.e = 0; - return c; - } - - /* compute decimal shift needed to obtain the highest accuracy */ - a_dbl = (a.m2 * two_to_32 + a.m1) * two_to_32 + a.m0; - b_dbl = (b.m2 * two_to_32 + b.m1) * two_to_32 + b.m0; - l10 = log10 (a_dbl); - shift = (REBINT)ceil (25.5 + log10(b_dbl) - l10); - dsl (3, sa, shift); - e -= shift; - - /* count radix 2 ** 32 digits of the shifted significand sa */ - na = (REBINT)ceil ((l10 + shift) * 0.10381025296523 + 0.5); - if (sa[na - 1] == 0) na--; - - nb = b.m2 ? 3 : (b.m1 ? 2 : 1); - m_divide (q, r, na, sa, nb, sb); - - /* compute the truncate flag */ - m_multiply_1 (nb, r, r, 2); - tc = m_cmp (nb + 1, r, sb); - if (tc >= 0) tc = tc == 0 ? 2 : 3; - else tc = m_is_zero (nb + 1, r) ? 0 : 1; - - /* normalize the significand q */ - shift = min_shift_right (q); - if (shift > 0) { - dsr (3, q, shift, &tc); - e += shift; - } - - /* round q if needed */ - if (((tc == 3) || ((tc == 2) && (q[0] % 2 == 1))) && (e >= -128)) m_add_1 (q, 1); - - m_ldexp (q, &f, e, tc); - c.m0 = q[0]; - c.m1 = q[1]; - c.m2 = q[2]; - c.e = f; - return c; + REBINT e = a.e - b.e, f = 0; + deci c; + double a_dbl, b_dbl, l10; + REBINT shift, na, nb, tc; + REBCNT q[] = {0, 0, 0, 0, 0, 0}, r[4]; + + // Must be compile-time const for '= {...}' style init (-Wc99-extensions) + REBCNT sa[6]; + REBCNT sb[4]; + + sa[0] = a.m0; + sa[1] = a.m1; + sa[2] = a.m2; + sa[3] = 0; + sa[4] = 0; + sa[5] = 0; + + sb[0] = b.m0; + sb[1] = b.m1; + sb[2] = b.m2; + sb[3] = 0; + + if (deci_is_zero (b)) fail (Error_Zero_Divide_Raw()); + + /* compute sign */ + c.s = (!a.s && b.s) || (a.s && !b.s); + + if (deci_is_zero (a)) { + c.m0 = 0; + c.m1 = 0; + c.m2 = 0; + c.e = 0; + return c; + } + + /* compute decimal shift needed to obtain the highest accuracy */ + a_dbl = (a.m2 * two_to_32 + a.m1) * two_to_32 + a.m0; + b_dbl = (b.m2 * two_to_32 + b.m1) * two_to_32 + b.m0; + l10 = log10 (a_dbl); + shift = (REBINT)ceil (25.5 + log10(b_dbl) - l10); + dsl (3, sa, shift); + e -= shift; + + /* count radix 2 ** 32 digits of the shifted significand sa */ + na = (REBINT)ceil ((l10 + shift) * 0.10381025296523 + 0.5); + if (sa[na - 1] == 0) na--; + + nb = b.m2 ? 3 : (b.m1 ? 2 : 1); + m_divide (q, r, na, sa, nb, sb); + + /* compute the truncate flag */ + m_multiply_1 (nb, r, r, 2); + tc = m_cmp (nb + 1, r, sb); + if (tc >= 0) tc = tc == 0 ? 2 : 3; + else tc = m_is_zero (nb + 1, r) ? 0 : 1; + + /* normalize the significand q */ + shift = min_shift_right (q); + if (shift > 0) { + dsr (3, q, shift, &tc); + e += shift; + } + + /* round q if needed */ + if (((tc == 3) || ((tc == 2) && (q[0] % 2 == 1))) && (e >= -128)) m_add_1 (q, 1); + + m_ldexp (q, &f, e, tc); + c.m0 = q[0]; + c.m1 = q[1]; + c.m2 = q[2]; + c.e = f; + return c; } #define MAX_NB 7 -INLINE REBINT m_to_string (REBYTE *s, REBINT n, const REBCNT a[]) { +REBINT m_to_string (REBYTE *s, REBINT n, const REBCNT a[]) { REBCNT r, b[MAX_NB]; - REBYTE v[10 * MAX_NB + 1], *vmax, *k; - + REBYTE v[10 * MAX_NB + 1], *vmax, *k; + /* finds the first nonzero radix 2 ** 32 "digit" */ for (; (n > 0) && (a[n - 1] == 0); n--); - + if (n == 0) { - s[0] = '0'; - s[1] = '\0'; - return 1; - } - + s[0] = '0'; + s[1] = '\0'; + return 1; + } + /* copy a to preserve it */ - memcpy (b, a, n * sizeof (REBCNT)); - - k = vmax = v + 10 * MAX_NB; - *k = '\0'; + memcpy (b, a, n * sizeof (REBCNT)); + + k = vmax = v + 10 * MAX_NB; + *k = '\0'; while (n > 0) { - r = m_divide_1 (n, b, b, 10u); - if (b[n - 1] == 0) n--; - *--k = '0' + r; - } + r = m_divide_1 (n, b, b, 10u); + if (b[n - 1] == 0) n--; + *--k = '0' + r; + } - strcpy(s, k); + strcpy(s_cast(s), s_cast(k)); return vmax - k; } REBINT deci_to_string(REBYTE *string, const deci a, const REBYTE symbol, const REBYTE point) { - REBYTE *s = string; - REBCNT sa[] = {a.m0, a.m1, a.m2}; - REBINT j, e; - - /* sign */ - if (a.s) *s++ = '-'; - - if (symbol) *s++ = symbol; - - if (deci_is_zero (a)) { - *s++ = '0'; - *s = '\0'; - return s-string; - } - - j = m_to_string(s, 3, sa); - e = j + a.e; - - if (e < j) { - if (e <= 0) { - if (e < -6) { - s++; - if (j > 1) { - memmove(s + 1, s, j); - *s = point; - s += j; - } - *s++ = 'e'; - INT_TO_STR(e - 1, s); - s = strchr(s, '\0'); - } else { /* -6 <= e <= 0 */ - memmove(s + 2 - e, s, j + 1); - *s++ = '0'; - *s++ = point; - memset(s, '0', -e); - s += j - e; - } - } else { /* 0 < e < j */ - s += e; - memmove(s + 1, s, j - e + 1); - *s++ = point; - s += j - e; - } - } else if (e == j) { - s += j; - } else { /* j < e */ - s += j; - *s++ = 'e'; - INT_TO_STR(e - j, s); - s = strchr(s, '\0'); - } - - return s - string; + REBYTE *s = string; + REBINT j, e; + + // Must be compile-time const for '= {...}' style init (-Wc99-extensions) + REBCNT sa[3]; + + sa[0] = a.m0; + sa[1] = a.m1; + sa[2] = a.m2; + + /* sign */ + if (a.s) *s++ = '-'; + + if (symbol) *s++ = symbol; + + if (deci_is_zero (a)) { + *s++ = '0'; + *s = '\0'; + return s-string; + } + + j = m_to_string(s, 3, sa); + e = j + a.e; + + if (e < j) { + if (e <= 0) { + if (e < -6) { + s++; + if (j > 1) { + memmove(s + 1, s, j); + *s = point; + s += j; + } + *s++ = 'e'; + INT_TO_STR(e - 1, s); + s = b_cast(strchr(s_cast(s), '\0')); + } else { /* -6 <= e <= 0 */ + memmove(s + 2 - e, s, j + 1); + *s++ = '0'; + *s++ = point; + memset(s, '0', -e); + s += j - e; + } + } else { /* 0 < e < j */ + s += e; + memmove(s + 1, s, j - e + 1); + *s++ = point; + s += j - e; + } + } else if (e == j) { + s += j; + } else { /* j < e */ + s += j; + *s++ = 'e'; + INT_TO_STR(e - j, s); + s = b_cast(strchr(s_cast(s), '\0')); + } + + return s - string; } deci deci_mod (deci a, deci b) { - REBCNT sa[] = {a.m0, a.m1, a.m2}; - REBCNT sb[] = {b.m0, b.m1, b.m2,0}; /* the additional place is for dsl */ - REBCNT sc[] = {10u, 0, 0}; - REBCNT p[6]; /* for multiplication results */ - REBINT e, nb; - - if (deci_is_zero (b)) DIVIDE_BY_ZERO_ERROR; - if (deci_is_zero (a)) return deci_zero; - - e = a.e - b.e; - if (e < 0) { - if (max_shift_left (sb) < -e) return a; /* a < b */ - dsl (3, sb, -e); - b.e = a.e; - e = 0; - } - /* e >= 0 */ - - /* count radix 2 ** 32 digits of sb */ - nb = sb[2] ? 3 : (sb[1] ? 2 : 1); - - /* sa = remainder(sa, sb) */ - m_divide (p, sa, 3, sa, nb, sb); + REBCNT sc[] = {10u, 0, 0}; + REBCNT p[6]; /* for multiplication results */ + REBINT e, nb; + + // Must be compile-time const for '= {...}' style init (-Wc99-extensions) + REBCNT sa[3]; + REBCNT sb[4]; + + sa[0] = a.m0; + sa[1] = a.m1; + sa[2] = a.m2; + + sb[0] = b.m0; + sb[1] = b.m1; + sb[2] = b.m2; + sb[3] = 0; /* the additional place is for dsl */ + + if (deci_is_zero (b)) fail (Error_Zero_Divide_Raw()); + if (deci_is_zero (a)) return deci_zero; + + e = a.e - b.e; + if (e < 0) { + if (max_shift_left (sb) < -e) return a; /* a < b */ + dsl (3, sb, -e); + b.e = a.e; + e = 0; + } + /* e >= 0 */ + + /* count radix 2 ** 32 digits of sb */ + nb = sb[2] ? 3 : (sb[1] ? 2 : 1); + + /* sa = remainder(sa, sb) */ + m_divide (p, sa, 3, sa, nb, sb); while (e > 0) { /* invariants: computing remainder (sa * pow (sc, e), sb) sa has nb radix pow (2, 32) digits */ - if (e % 2) { - /* sa = remainder (sa * sc, sb) */ - m_multiply (p, nb, sa, nb, sc); - m_divide (p, sa, nb + nb, p, nb, sb); - e--; - } else { - /* sc = remainder (sc * sc, sb) */ - m_multiply (p, nb, sc, nb, sc); - m_divide (p, sc, nb + nb, p, nb, sb); - e /= 2; - } - } - /* e = 0 */ - - a.m0 = sa[0]; - a.m1 = nb >= 2 ? sa[1] : 0; - a.m2 = nb == 3 ? sa[2] : 0; - a.e = b.e; - return a; + if (e % 2) { + /* sa = remainder (sa * sc, sb) */ + m_multiply (p, nb, sa, nb, sc); + m_divide (p, sa, nb + nb, p, nb, sb); + e--; + } else { + /* sc = remainder (sc * sc, sb) */ + m_multiply (p, nb, sc, nb, sc); + m_divide (p, sc, nb + nb, p, nb, sb); + e /= 2; + } + } + /* e = 0 */ + + a.m0 = sa[0]; + a.m1 = nb >= 2 ? sa[1] : 0; + a.m2 = nb == 3 ? sa[2] : 0; + a.e = b.e; + return a; } /* in case of error the function returns deci_zero and *endptr = s */ -deci string_to_deci (REBYTE *s, REBYTE **endptr) { - REBYTE *a = s; - deci b = {0, 0, 0, 0, 0}; - REBCNT sb[] = {0, 0, 0, 0}; /* significand */ - REBINT f = 0, e = 0; /* exponents */ - REBINT fp = 0; /* full precision flag */ - REBINT dp = 0; /* decimal point encountered */ - REBINT tb = 0; /* truncate flag */ - REBINT d; /* digit */ - REBINT es = 1; /* exponent sign */ - - /* sign */ - if ('+' == *a) a++; else if ('-' == *a) { - b.s = 1; - a++; - } - - // optional $ - if ('$' == *a) a++; - - /* significand */ - for (; ; a++) - if (IS_DIGIT(*a)) { - d = *a - '0'; - if (m_cmp (3, sb, P[25]) < 0) { - m_multiply_1 (3, sb, sb, 10u); - m_add_1 (sb, d); - if (dp) f--; - } else { - if (fp) { - if ((tb == 0) && (d != 0)) tb = 1; - else if ((tb == 2) && (d != 0)) tb = 3; - } else { - fp = 1; - if (d > 0) tb = d < 5 ? 1 : (d == 5 ? 2 : 3); - } - if (!dp) f++; - } - } else if (('.' == *a) || (',' == *a)) { - /* decimal point */ - if (dp) { - *endptr = s; - return deci_zero; - } - else dp = 1; - } else if ('\'' != *a) break; - - /* exponent */ - if (('e' == *a) || ('E' == *a)) { - a++; - /* exponent sign */ - if ('+' == *a) a++; else if ('-' == *a) { - a++; - es = -1; - } - for (; ; a++) { - if (IS_DIGIT(*a)) { - d = *a - '0'; - e = e * 10 + d; - if (e > 200000000) { - if (es == 1) OVERFLOW_ERROR; - else e = 200000000; - } - } else break; - } - e *= es; - } - /* that is supposed to be all */ - *endptr = a; - e += f; - f = 0; - - /* round */ - if (((tb == 3) || ((tb == 2) && (sb[0] % 2 == 1))) && (e >= -128)) { - if (m_cmp (3, sb, P26_1) < 0) m_add_1 (sb, 1); - else { - dsr (3, sb, 1, &tb); - e++; - if ((tb == 3) || ((tb == 2) && (sb[0] % 2 == 1))) m_add_1 (sb, 1); - } - } - - m_ldexp (sb, &f, e, tb); - - b.m0 = sb[0]; - b.m1 = sb[1]; - b.m2 = sb[2]; - b.e = f; - return b; +deci string_to_deci (const REBYTE *s, const REBYTE **endptr) { + const REBYTE *a = s; + deci b = {0, 0, 0, 0, 0}; + REBCNT sb[] = {0, 0, 0, 0}; /* significand */ + REBINT f = 0, e = 0; /* exponents */ + REBINT fp = 0; /* full precision flag */ + REBINT dp = 0; /* decimal point encountered */ + REBINT tb = 0; /* truncate flag */ + REBINT d; /* digit */ + REBINT es = 1; /* exponent sign */ + + /* sign */ + if ('+' == *a) a++; else if ('-' == *a) { + b.s = 1; + a++; + } + + // optional $ + if ('$' == *a) a++; + + /* significand */ + for (; ; a++) + if (IS_DIGIT(*a)) { + d = *a - '0'; + if (m_cmp (3, sb, P[25]) < 0) { + m_multiply_1 (3, sb, sb, 10u); + m_add_1 (sb, d); + if (dp) f--; + } else { + if (fp) { + if ((tb == 0) && (d != 0)) tb = 1; + else if ((tb == 2) && (d != 0)) tb = 3; + } else { + fp = 1; + if (d > 0) tb = d < 5 ? 1 : (d == 5 ? 2 : 3); + } + if (!dp) f++; + } + } else if (('.' == *a) || (',' == *a)) { + /* decimal point */ + if (dp) { + *endptr = s; + return deci_zero; + } + else dp = 1; + } else if ('\'' != *a) break; + + /* exponent */ + if (('e' == *a) || ('E' == *a)) { + a++; + /* exponent sign */ + if ('+' == *a) a++; else if ('-' == *a) { + a++; + es = -1; + } + for (; ; a++) { + if (IS_DIGIT(*a)) { + d = *a - '0'; + e = e * 10 + d; + if (e > 200000000) { + if (es == 1) fail (Error_Overflow_Raw()); + else e = 200000000; + } + } else break; + } + e *= es; + } + /* that is supposed to be all */ + *endptr = a; + e += f; + f = 0; + + /* round */ + if (((tb == 3) || ((tb == 2) && (sb[0] % 2 == 1))) && (e >= -128)) { + if (m_cmp (3, sb, P26_1) < 0) m_add_1 (sb, 1); + else { + dsr (3, sb, 1, &tb); + e++; + if ((tb == 3) || ((tb == 2) && (sb[0] % 2 == 1))) m_add_1 (sb, 1); + } + } + + m_ldexp (sb, &f, e, tb); + + b.m0 = sb[0]; + b.m1 = sb[1]; + b.m2 = sb[2]; + b.e = f; + return b; } deci deci_sign (deci a) { - if (deci_is_zero (a)) return a; - if (a.s) return deci_minus_one; else return deci_one; + if (deci_is_zero (a)) return a; + if (a.s) return deci_minus_one; else return deci_one; } -REBFLG deci_is_same (deci a, deci b) { - if (deci_is_zero (a)) return deci_is_zero (b); - return (a.m0 == b.m0) && (a.m1 == b.m1) && (a.m2 == b.m2) && (a.s == b.s) && (a.e == b.e); +REBOOL deci_is_same (deci a, deci b) { + if (deci_is_zero (a)) return deci_is_zero (b); + return LOGICAL( + (a.m0 == b.m0) + && (a.m1 == b.m1) + && (a.m2 == b.m2) + && (a.s == b.s) + && (a.e == b.e) + ); } -deci binary_to_deci(REBYTE s[12]) { - deci d; - /* this looks like the only way, since the order of bits in bitsets is compiler-dependent */ - d.s = s[0] >> 7; - d.e = s[0] << 1 | s[1] >> 7; - d.m2 = (REBCNT)(s[1] << 1) << 15 | (REBCNT)s[2] << 8 | s[3]; - d.m1 = (REBCNT)s[4] << 24 | (REBCNT)s[5] << 16 | (REBCNT)s[6] << 8 | s[7]; - d.m0 = (REBCNT)s[8] << 24 | (REBCNT)s[9] << 16 | (REBCNT)s[10] << 8 | s[11]; - /* validity checks */ - if (d.m2 >= 5421010u) { - if (d.m1 >= 3704098002u) { - if (d.m0 > 3825205247u || d.m1 > 3704098002u) OVERFLOW_ERROR; - } else if (d.m2 > 5421010u) OVERFLOW_ERROR; - } - return d; +deci binary_to_deci(const REBYTE s[12]) { + deci d; + /* this looks like the only way, since the order of bits in bitsets is compiler-dependent */ + d.s = s[0] >> 7; + d.e = s[0] << 1 | s[1] >> 7; + d.m2 = (REBCNT)(s[1] << 1) << 15 | (REBCNT)s[2] << 8 | s[3]; + d.m1 = (REBCNT)s[4] << 24 | (REBCNT)s[5] << 16 | (REBCNT)s[6] << 8 | s[7]; + d.m0 = (REBCNT)s[8] << 24 | (REBCNT)s[9] << 16 | (REBCNT)s[10] << 8 | s[11]; + /* validity checks */ + if (d.m2 >= 5421010u) { + if (d.m1 >= 3704098002u) { + if (d.m0 > 3825205247u || d.m1 > 3704098002u) + fail (Error_Overflow_Raw()); + } else if (d.m2 > 5421010u) fail (Error_Overflow_Raw()); + } + return d; } REBYTE *deci_to_binary(REBYTE s[12], const deci d) { - /* this looks like the only way, since the order of bits in bitsets is compiler-dependent */ - s[0] = d.s << 7 | (REBYTE)d.e >> 1; - s[1] = (REBYTE)d.e << 7 | d.m2 >> 16; - s[2] = d.m2 >> 8; - s[3] = d.m2; - s[4] = d.m1 >> 24; - s[5] = d.m1 >> 16; - s[6] = d.m1 >> 8; - s[7] = d.m1; - s[8] = d.m0 >> 24; - s[9] = d.m0 >> 16; - s[10] = d.m0 >> 8; - s[11] = d.m0; - return s; + /* this looks like the only way, since the order of bits in bitsets is compiler-dependent */ + s[0] = d.s << 7 | (REBYTE)d.e >> 1; + s[1] = (REBYTE)d.e << 7 | d.m2 >> 16; + s[2] = d.m2 >> 8; + s[3] = d.m2; + s[4] = d.m1 >> 24; + s[5] = d.m1 >> 16; + s[6] = d.m1 >> 8; + s[7] = d.m1; + s[8] = d.m0 >> 24; + s[9] = d.m0 >> 16; + s[10] = d.m0 >> 8; + s[11] = d.m0; + return s; } diff --git a/src/core/f-dtoa.c b/src/core/f-dtoa.c index e96e78c221..9e0ac082e5 100644 --- a/src/core/f-dtoa.c +++ b/src/core/f-dtoa.c @@ -23,13 +23,13 @@ ***************************************************************/ /* Please send bug reports to David M. Gay (dmg at acm dot org, - * with " at " changed at "@" and " dot " changed to "."). */ + * with " at " changed at "@" and " dot " changed to "."). */ /* On a machine with IEEE extended-precision registers, it is * necessary to specify double-precision (53-bit) rounding precision * before invoking strtod or dtoa. If the machine uses (the equivalent * of) Intel 80x87 arithmetic, the call - * _control87(PC_53, MCW_PC); + * _control87(PC_53, MCW_PC); * does this with many compilers. Whether this or another call is * appropriate depends on the compiler; for this to work, it may be * necessary to #include "float.h" or another system-dependent header @@ -49,146 +49,146 @@ * * Modifications: * - * 1. We only require IEEE, IBM, or VAX double-precision - * arithmetic (not IEEE double-extended). - * 2. We get by with floating-point arithmetic in a case that - * Clinger missed -- when we're computing d * 10^n - * for a small integer d and the integer n is not too - * much larger than 22 (the maximum integer k for which - * we can represent 10^k exactly), we may be able to - * compute (d*10^k) * 10^(e-k) with just one roundoff. - * 3. Rather than a bit-at-a-time adjustment of the binary - * result in the hard case, we use floating-point - * arithmetic to determine the adjustment to within - * one bit; only in really hard cases do we need to - * compute a second residual. - * 4. Because of 3., we don't need a large table of powers of 10 - * for ten-to-e (just some small tables, e.g. of 10^k - * for 0 <= k <= 22). + * 1. We only require IEEE, IBM, or VAX double-precision + * arithmetic (not IEEE double-extended). + * 2. We get by with floating-point arithmetic in a case that + * Clinger missed -- when we're computing d * 10^n + * for a small integer d and the integer n is not too + * much larger than 22 (the maximum integer k for which + * we can represent 10^k exactly), we may be able to + * compute (d*10^k) * 10^(e-k) with just one roundoff. + * 3. Rather than a bit-at-a-time adjustment of the binary + * result in the hard case, we use floating-point + * arithmetic to determine the adjustment to within + * one bit; only in really hard cases do we need to + * compute a second residual. + * 4. Because of 3., we don't need a large table of powers of 10 + * for ten-to-e (just some small tables, e.g. of 10^k + * for 0 <= k <= 22). */ /* * #define IEEE_8087 for IEEE-arithmetic machines where the least - * significant byte has the lowest address. + * significant byte has the lowest address. * #define IEEE_MC68k for IEEE-arithmetic machines where the most - * significant byte has the lowest address. + * significant byte has the lowest address. * #define Long int on machines with 32-bit ints and 64-bit longs. * #define IBM for IBM mainframe-style floating-point arithmetic. * #define VAX for VAX-style floating-point arithmetic (D_floating). * #define No_leftright to omit left-right logic in fast floating-point - * computation of dtoa. This will cause dtoa modes 4 and 5 to be - * treated the same as modes 2 and 3 for some inputs. + * computation of dtoa. This will cause dtoa modes 4 and 5 to be + * treated the same as modes 2 and 3 for some inputs. * #define Honor_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 - * and strtod and dtoa should round accordingly. Unless Trust_FLT_ROUNDS - * is also #defined, fegetround() will be queried for the rounding mode. - * Note that both FLT_ROUNDS and fegetround() are specified by the C99 - * standard (and are specified to be consistent, with fesetround() - * affecting the value of FLT_ROUNDS), but that some (Linux) systems - * do not work correctly in this regard, so using fegetround() is more - * portable than using FLT_ROUNDS directly. + * and strtod and dtoa should round accordingly. Unless Trust_FLT_ROUNDS + * is also #defined, fegetround() will be queried for the rounding mode. + * Note that both FLT_ROUNDS and fegetround() are specified by the C99 + * standard (and are specified to be consistent, with fesetround() + * affecting the value of FLT_ROUNDS), but that some (Linux) systems + * do not work correctly in this regard, so using fegetround() is more + * portable than using FLT_ROUNDS directly. * #define Check_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 - * and Honor_FLT_ROUNDS is not #defined. + * and Honor_FLT_ROUNDS is not #defined. * #define RND_PRODQUOT to use rnd_prod and rnd_quot (assembly routines - * that use extended-precision instructions to compute rounded - * products and quotients) with IBM. + * that use extended-precision instructions to compute rounded + * products and quotients) with IBM. * #define ROUND_BIASED for IEEE-format with biased rounding and arithmetic - * that rounds toward +Infinity. + * that rounds toward +Infinity. * #define ROUND_BIASED_without_Round_Up for IEEE-format with biased - * rounding when the underlying floating-point arithmetic uses - * unbiased rounding. This prevent using ordinary floating-point - * arithmetic when the result could be computed with one rounding error. + * rounding when the underlying floating-point arithmetic uses + * unbiased rounding. This prevent using ordinary floating-point + * arithmetic when the result could be computed with one rounding error. * #define Inaccurate_Divide for IEEE-format with correctly rounded - * products but inaccurate quotients, e.g., for Intel i860. + * products but inaccurate quotients, e.g., for Intel i860. * #define NO_LONG_LONG on machines that do not have a "long long" - * integer type (of >= 64 bits). On such machines, you can - * #define Just_16 to store 16 bits per 32-bit Long when doing - * high-precision integer arithmetic. Whether this speeds things - * up or slows things down depends on the machine and the number - * being converted. If long long is available and the name is - * something other than "long long", #define Llong to be the name, - * and if "unsigned Llong" does not work as an unsigned version of - * Llong, #define #ULLong to be the corresponding unsigned type. + * integer type (of >= 64 bits). On such machines, you can + * #define Just_16 to store 16 bits per 32-bit Long when doing + * high-precision integer arithmetic. Whether this speeds things + * up or slows things down depends on the machine and the number + * being converted. If long long is available and the name is + * something other than "long long", #define Llong to be the name, + * and if "unsigned Llong" does not work as an unsigned version of + * Llong, #define #ULLong to be the corresponding unsigned type. * #define KR_headers for old-style C function headers. * #define Bad_float_h if your system lacks a float.h or if it does not - * define some or all of DBL_DIG, DBL_MAX_10_EXP, DBL_MAX_EXP, - * FLT_RADIX, FLT_ROUNDS, and DBL_MAX. + * define some or all of DBL_DIG, DBL_MAX_10_EXP, DBL_MAX_EXP, + * FLT_RADIX, FLT_ROUNDS, and DBL_MAX. * #define MALLOC your_malloc, where your_malloc(n) acts like malloc(n) - * if memory is available and otherwise does something you deem - * appropriate. If MALLOC is undefined, malloc will be invoked - * directly -- and assumed always to succeed. Similarly, if you - * want something other than the system's free() to be called to - * recycle memory acquired from MALLOC, #define FREE to be the - * name of the alternate routine. (FREE or free is only called in - * pathological cases, e.g., in a dtoa call after a dtoa return in - * mode 3 with thousands of digits requested.) + * if memory is available and otherwise does something you deem + * appropriate. If MALLOC is undefined, malloc will be invoked + * directly -- and assumed always to succeed. Similarly, if you + * want something other than the system's free() to be called to + * recycle memory acquired from MALLOC, #define FREE to be the + * name of the alternate routine. (FREE or free is only called in + * pathological cases, e.g., in a dtoa call after a dtoa return in + * mode 3 with thousands of digits requested.) * #define Omit_Private_Memory to omit logic (added Jan. 1998) for making - * memory allocations from a private pool of memory when possible. - * When used, the private pool is PRIVATE_MEM bytes long: 2304 bytes, - * unless #defined to be a different length. This default length - * suffices to get rid of MALLOC calls except for unusual cases, - * such as decimal-to-binary conversion of a very long string of - * digits. The longest string dtoa can return is about 751 bytes - * long. For conversions by strtod of strings of 800 digits and - * all dtoa conversions in single-threaded executions with 8-byte - * pointers, PRIVATE_MEM >= 7400 appears to suffice; with 4-byte - * pointers, PRIVATE_MEM >= 7112 appears adequate. + * memory allocations from a private pool of memory when possible. + * When used, the private pool is PRIVATE_MEM bytes long: 2304 bytes, + * unless #defined to be a different length. This default length + * suffices to get rid of MALLOC calls except for unusual cases, + * such as decimal-to-binary conversion of a very long string of + * digits. The longest string dtoa can return is about 751 bytes + * long. For conversions by strtod of strings of 800 digits and + * all dtoa conversions in single-threaded executions with 8-byte + * pointers, PRIVATE_MEM >= 7400 appears to suffice; with 4-byte + * pointers, PRIVATE_MEM >= 7112 appears adequate. * #define NO_INFNAN_CHECK if you do not wish to have INFNAN_CHECK - * #defined automatically on IEEE systems. On such systems, - * when INFNAN_CHECK is #defined, strtod checks - * for Infinity and NaN (case insensitively). On some systems - * (e.g., some HP systems), it may be necessary to #define NAN_WORD0 - * appropriately -- to the most significant word of a quiet NaN. - * (On HP Series 700/800 machines, -DNAN_WORD0=0x7ff40000 works.) - * When INFNAN_CHECK is #defined and No_Hex_NaN is not #defined, - * strtod also accepts (case insensitively) strings of the form - * NaN(x), where x is a string of hexadecimal digits and spaces; - * if there is only one string of hexadecimal digits, it is taken - * for the 52 fraction bits of the resulting NaN; if there are two - * or more strings of hex digits, the first is for the high 20 bits, - * the second and subsequent for the low 32 bits, with intervening - * white space ignored; but if this results in none of the 52 - * fraction bits being on (an IEEE Infinity symbol), then NAN_WORD0 - * and NAN_WORD1 are used instead. + * #defined automatically on IEEE systems. On such systems, + * when INFNAN_CHECK is #defined, strtod checks + * for Infinity and NaN (case insensitively). On some systems + * (e.g., some HP systems), it may be necessary to #define NAN_WORD0 + * appropriately -- to the most significant word of a quiet NaN. + * (On HP Series 700/800 machines, -DNAN_WORD0=0x7ff40000 works.) + * When INFNAN_CHECK is #defined and No_Hex_NaN is not #defined, + * strtod also accepts (case insensitively) strings of the form + * NaN(x), where x is a string of hexadecimal digits and spaces; + * if there is only one string of hexadecimal digits, it is taken + * for the 52 fraction bits of the resulting NaN; if there are two + * or more strings of hex digits, the first is for the high 20 bits, + * the second and subsequent for the low 32 bits, with intervening + * white space ignored; but if this results in none of the 52 + * fraction bits being on (an IEEE Infinity symbol), then NAN_WORD0 + * and NAN_WORD1 are used instead. * #define MULTIPLE_THREADS if the system offers preemptively scheduled - * multiple threads. In this case, you must provide (or suitably - * #define) two locks, acquired by ACQUIRE_DTOA_LOCK(n) and freed - * by FREE_DTOA_LOCK(n) for n = 0 or 1. (The second lock, accessed - * in pow5mult, ensures lazy evaluation of only one copy of high - * powers of 5; omitting this lock would introduce a small - * probability of wasting memory, but would otherwise be harmless.) - * You must also invoke freedtoa(s) to free the value s returned by - * dtoa. You may do so whether or not MULTIPLE_THREADS is #defined. + * multiple threads. In this case, you must provide (or suitably + * #define) two locks, acquired by ACQUIRE_DTOA_LOCK(n) and freed + * by FREE_DTOA_LOCK(n) for n = 0 or 1. (The second lock, accessed + * in pow5mult, ensures lazy evaluation of only one copy of high + * powers of 5; omitting this lock would introduce a small + * probability of wasting memory, but would otherwise be harmless.) + * You must also invoke freedtoa(s) to free the value s returned by + * dtoa. You may do so whether or not MULTIPLE_THREADS is #defined. * #define NO_IEEE_Scale to disable new (Feb. 1997) logic in strtod that - * avoids underflows on inputs whose result does not underflow. - * If you #define NO_IEEE_Scale on a machine that uses IEEE-format - * floating-point numbers and flushes underflows to zero rather - * than implementing gradual underflow, then you must also #define - * Sudden_Underflow. + * avoids underflows on inputs whose result does not underflow. + * If you #define NO_IEEE_Scale on a machine that uses IEEE-format + * floating-point numbers and flushes underflows to zero rather + * than implementing gradual underflow, then you must also #define + * Sudden_Underflow. * #define USE_LOCALE to use the current locale's decimal_point value. * #define SET_INEXACT if IEEE arithmetic is being used and extra - * computation should be done to set the inexact flag when the - * result is inexact and avoid setting inexact when the result - * is exact. In this case, dtoa.c must be compiled in - * an environment, perhaps provided by #include "dtoa.c" in a - * suitable wrapper, that defines two functions, - * int get_inexact(void); - * void clear_inexact(void); - * such that get_inexact() returns a nonzero value if the - * inexact bit is already set, and clear_inexact() sets the - * inexact bit to 0. When SET_INEXACT is #defined, strtod - * also does extra computations to set the underflow and overflow - * flags when appropriate (i.e., when the result is tiny and - * inexact or when it is a numeric value rounded to +-infinity). + * computation should be done to set the inexact flag when the + * result is inexact and avoid setting inexact when the result + * is exact. In this case, dtoa.c must be compiled in + * an environment, perhaps provided by #include "dtoa.c" in a + * suitable wrapper, that defines two functions, + * int get_inexact(void); + * void clear_inexact(void); + * such that get_inexact() returns a nonzero value if the + * inexact bit is already set, and clear_inexact() sets the + * inexact bit to 0. When SET_INEXACT is #defined, strtod + * also does extra computations to set the underflow and overflow + * flags when appropriate (i.e., when the result is tiny and + * inexact or when it is a numeric value rounded to +-infinity). * #define NO_ERRNO if strtod should not assign errno = ERANGE when - * the result overflows to +-Infinity or underflows to 0. + * the result overflows to +-Infinity or underflows to 0. * #define NO_HEX_FP to omit recognition of hexadecimal floating-point - * values by strtod. + * values by strtod. * #define NO_STRTOD_BIGCOMP (on IEEE-arithmetic systems only for now) - * to disable logic for "fast" testing of very long input strings - * to strtod. This testing proceeds by initially truncating the - * input string, then if necessary comparing the whole string with - * a decimal expansion to decide close cases. This logic is only - * used for input more than STRTOD_DIGLIM digits long (default 40). + * to disable logic for "fast" testing of very long input strings + * to strtod. This testing proceeds by initially truncating the + * input string, then if necessary comparing the whole string with + * a decimal expansion to decide close cases. This logic is only + * used for input more than STRTOD_DIGLIM digits long (default 40). */ #ifndef Long @@ -199,7 +199,7 @@ typedef unsigned Long ULong; #endif #ifdef DEBUG -#include "stdio.h" +// #include "stdio.h" // !!! No in Ren-C release builds #define Bug(x) {fprintf(stderr, "%s\n", x); exit(1);} #endif @@ -376,7 +376,7 @@ extern int strtod_diglim; #define Int_max 14 #ifndef NO_IEEE_Scale #define Avoid_Underflow -#ifdef Flush_Denorm /* debugging option */ +#ifdef Flush_Denorm /* debugging option */ #undef Sudden_Underflow #endif #endif @@ -417,7 +417,7 @@ extern int strtod_diglim; #define Emin (-260) #define Exp_1 0x41000000 #define Exp_11 0x41000000 -#define Ebits 8 /* exponent has 7 bits, but 8 is the right value in b2d */ +#define Ebits 8 /* exponent has 7 bits, but 8 is the right value in b2d */ #define Frac_mask 0xffffff #define Frac_mask1 0xffffff #define Bletch 4 @@ -512,7 +512,7 @@ BCinfo { int dp0, dp1, dplen, dsign, e0, inexact, nd, nd0, rounding, scale, uflc * slower. Hence the default is now to store 32 bits per Long. */ #endif -#else /* long long available */ +#else /* long long available */ #ifndef Llong #define Llong long long #endif @@ -522,24 +522,30 @@ BCinfo { int dp0, dp1, dplen, dsign, e0, inexact, nd, nd0, rounding, scale, uflc #endif /* NO_LONG_LONG */ #ifndef MULTIPLE_THREADS -#define ACQUIRE_DTOA_LOCK(n) /*nothing*/ -#define FREE_DTOA_LOCK(n) /*nothing*/ +#define ACQUIRE_DTOA_LOCK(n) /*nothing*/ +#define FREE_DTOA_LOCK(n) /*nothing*/ #endif #define Kmax 7 #ifdef __cplusplus -extern "C" double strtod(const char *s00, char **se); -extern "C" char *dtoa(double d, int mode, int ndigits, - int *decpt, int *sign, char **rve); +extern "C" { +#endif + +double strtod(const char *s00, const char **se); +char *dtoa(double d, int mode, int ndigits, + int *decpt, int *sign, char **rve); + +#ifdef __cplusplus +} #endif struct Bigint { - struct Bigint *next; - int k, maxwds, sign, wds; - ULong x[1]; - }; + struct Bigint *next; + int k, maxwds, sign, wds; + ULong x[1]; + }; typedef struct Bigint Bigint; @@ -548,67 +554,68 @@ Bigint { static Bigint * Balloc #ifdef KR_headers - (k) int k; + (k) int k; #else - (int k) + (int k) #endif { - int x; - Bigint *rv; + int x; + Bigint *rv; #ifndef Omit_Private_Memory - unsigned int len; + size_t len; // !!! Ren/C: (unsigned int => size_t) for -Wsign-compare + // !!! REVIEW: isn't size_t unsigned? Why did that fix it? #endif - ACQUIRE_DTOA_LOCK(0); - /* The k > Kmax case does not need ACQUIRE_DTOA_LOCK(0), */ - /* but this case seems very unlikely. */ - if (k <= Kmax && (rv = freelist[k])) - freelist[k] = rv->next; - else { - x = 1 << k; + ACQUIRE_DTOA_LOCK(0); + /* The k > Kmax case does not need ACQUIRE_DTOA_LOCK(0), */ + /* but this case seems very unlikely. */ + if (k <= Kmax && (rv = freelist[k])) + freelist[k] = rv->next; + else { + x = 1 << k; #ifdef Omit_Private_Memory - rv = (Bigint *)MALLOC(sizeof(Bigint) + (x-1)*sizeof(ULong)); + rv = (Bigint *)MALLOC(sizeof(Bigint) + (x-1)*sizeof(ULong)); #else - len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1) - /sizeof(double); - if (k <= Kmax && pmem_next - private_mem + len <= PRIVATE_mem) { - rv = (Bigint*)pmem_next; - pmem_next += len; - } - else - rv = (Bigint*)MALLOC(len*sizeof(double)); -#endif - rv->k = k; - rv->maxwds = x; - } - FREE_DTOA_LOCK(0); - rv->sign = rv->wds = 0; - return rv; - } + len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1) + /sizeof(double); + if (k <= Kmax && pmem_next - private_mem + len <= PRIVATE_mem) { + rv = (Bigint*)pmem_next; + pmem_next += len; + } + else + rv = (Bigint*)MALLOC(len*sizeof(double)); +#endif + rv->k = k; + rv->maxwds = x; + } + FREE_DTOA_LOCK(0); + rv->sign = rv->wds = 0; + return rv; + } static void Bfree #ifdef KR_headers - (v) Bigint *v; + (v) Bigint *v; #else - (Bigint *v) + (Bigint *v) #endif { - if (v) { - if (v->k > Kmax) + if (v) { + if (v->k > Kmax) #ifdef FREE - FREE((void*)v); + FREE((void*)v); #else - free((void*)v); + free((void*)v); #endif - else { - ACQUIRE_DTOA_LOCK(0); - v->next = freelist[v->k]; - freelist[v->k] = v; - FREE_DTOA_LOCK(0); - } - } - } + else { + ACQUIRE_DTOA_LOCK(0); + v->next = freelist[v->k]; + freelist[v->k] = v; + FREE_DTOA_LOCK(0); + } + } + } #define Bcopy(x,y) memcpy((char *)&x->sign, (char *)&y->sign, \ y->wds*sizeof(Long) + 2*sizeof(int)) @@ -616,861 +623,861 @@ y->wds*sizeof(Long) + 2*sizeof(int)) static Bigint * multadd #ifdef KR_headers - (b, m, a) Bigint *b; int m, a; + (b, m, a) Bigint *b; int m, a; #else - (Bigint *b, int m, int a) /* multiply by m and add a */ + (Bigint *b, int m, int a) /* multiply by m and add a */ #endif { - int i, wds; + int i, wds; #ifdef ULLong - ULong *x; - ULLong carry, y; + ULong *x; + ULLong carry, y; #else - ULong carry, *x, y; + ULong carry, *x, y; #ifdef Pack_32 - ULong xi, z; + ULong xi, z; #endif #endif - Bigint *b1; + Bigint *b1; - wds = b->wds; - x = b->x; - i = 0; - carry = a; - do { + wds = b->wds; + x = b->x; + i = 0; + carry = a; + do { #ifdef ULLong - y = *x * (ULLong)m + carry; - carry = y >> 32; - *x++ = y & FFFFFFFF; + y = *x * (ULLong)m + carry; + carry = y >> 32; + *x++ = y & FFFFFFFF; #else #ifdef Pack_32 - xi = *x; - y = (xi & 0xffff) * m + carry; - z = (xi >> 16) * m + (y >> 16); - carry = z >> 16; - *x++ = (z << 16) + (y & 0xffff); + xi = *x; + y = (xi & 0xffff) * m + carry; + z = (xi >> 16) * m + (y >> 16); + carry = z >> 16; + *x++ = (z << 16) + (y & 0xffff); #else - y = *x * m + carry; - carry = y >> 16; - *x++ = y & 0xffff; -#endif -#endif - } - while(++i < wds); - if (carry) { - if (wds >= b->maxwds) { - b1 = Balloc(b->k+1); - Bcopy(b1, b); - Bfree(b); - b = b1; - } - b->x[wds++] = carry; - b->wds = wds; - } - return b; - } + y = *x * m + carry; + carry = y >> 16; + *x++ = y & 0xffff; +#endif +#endif + } + while(++i < wds); + if (carry) { + if (wds >= b->maxwds) { + b1 = Balloc(b->k+1); + Bcopy(b1, b); + Bfree(b); + b = b1; + } + b->x[wds++] = carry; + b->wds = wds; + } + return b; + } static Bigint * s2b #ifdef KR_headers - (s, nd0, nd, y9, dplen) CONST char *s; int nd0, nd, dplen; ULong y9; + (s, nd0, nd, y9, dplen) CONST char *s; int nd0, nd, dplen; ULong y9; #else - (const char *s, int nd0, int nd, ULong y9, int dplen) + (const char *s, int nd0, int nd, ULong y9, int dplen) #endif { - Bigint *b; - int i, k; - Long x, y; + Bigint *b; + int i, k; + Long x, y; - x = (nd + 8) / 9; - for(k = 0, y = 1; x > y; y <<= 1, k++) ; + x = (nd + 8) / 9; + for(k = 0, y = 1; x > y; y <<= 1, k++) ; #ifdef Pack_32 - b = Balloc(k); - b->x[0] = y9; - b->wds = 1; + b = Balloc(k); + b->x[0] = y9; + b->wds = 1; #else - b = Balloc(k+1); - b->x[0] = y9 & 0xffff; - b->wds = (b->x[1] = y9 >> 16) ? 2 : 1; -#endif - - i = 9; - if (9 < nd0) { - s += 9; - do b = multadd(b, 10, *s++ - '0'); - while(++i < nd0); - s += dplen; - } - else - s += dplen + 9; - for(; i < nd; i++) - b = multadd(b, 10, *s++ - '0'); - return b; - } + b = Balloc(k+1); + b->x[0] = y9 & 0xffff; + b->wds = (b->x[1] = y9 >> 16) ? 2 : 1; +#endif + + i = 9; + if (9 < nd0) { + s += 9; + do b = multadd(b, 10, *s++ - '0'); + while(++i < nd0); + s += dplen; + } + else + s += dplen + 9; + for(; i < nd; i++) + b = multadd(b, 10, *s++ - '0'); + return b; + } static int hi0bits #ifdef KR_headers - (x) ULong x; + (x) ULong x; #else - (ULong x) + (ULong x) #endif { - int k = 0; - - if (!(x & 0xffff0000)) { - k = 16; - x <<= 16; - } - if (!(x & 0xff000000)) { - k += 8; - x <<= 8; - } - if (!(x & 0xf0000000)) { - k += 4; - x <<= 4; - } - if (!(x & 0xc0000000)) { - k += 2; - x <<= 2; - } - if (!(x & 0x80000000)) { - k++; - if (!(x & 0x40000000)) - return 32; - } - return k; - } + int k = 0; + + if (!(x & 0xffff0000)) { + k = 16; + x <<= 16; + } + if (!(x & 0xff000000)) { + k += 8; + x <<= 8; + } + if (!(x & 0xf0000000)) { + k += 4; + x <<= 4; + } + if (!(x & 0xc0000000)) { + k += 2; + x <<= 2; + } + if (!(x & 0x80000000)) { + k++; + if (!(x & 0x40000000)) + return 32; + } + return k; + } static int lo0bits #ifdef KR_headers - (y) ULong *y; + (y) ULong *y; #else - (ULong *y) + (ULong *y) #endif { - int k; - ULong x = *y; - - if (x & 7) { - if (x & 1) - return 0; - if (x & 2) { - *y = x >> 1; - return 1; - } - *y = x >> 2; - return 2; - } - k = 0; - if (!(x & 0xffff)) { - k = 16; - x >>= 16; - } - if (!(x & 0xff)) { - k += 8; - x >>= 8; - } - if (!(x & 0xf)) { - k += 4; - x >>= 4; - } - if (!(x & 0x3)) { - k += 2; - x >>= 2; - } - if (!(x & 1)) { - k++; - x >>= 1; - if (!x) - return 32; - } - *y = x; - return k; - } + int k; + ULong x = *y; + + if (x & 7) { + if (x & 1) + return 0; + if (x & 2) { + *y = x >> 1; + return 1; + } + *y = x >> 2; + return 2; + } + k = 0; + if (!(x & 0xffff)) { + k = 16; + x >>= 16; + } + if (!(x & 0xff)) { + k += 8; + x >>= 8; + } + if (!(x & 0xf)) { + k += 4; + x >>= 4; + } + if (!(x & 0x3)) { + k += 2; + x >>= 2; + } + if (!(x & 1)) { + k++; + x >>= 1; + if (!x) + return 32; + } + *y = x; + return k; + } static Bigint * i2b #ifdef KR_headers - (i) int i; + (i) int i; #else - (int i) + (int i) #endif { - Bigint *b; + Bigint *b; - b = Balloc(1); - b->x[0] = i; - b->wds = 1; - return b; - } + b = Balloc(1); + b->x[0] = i; + b->wds = 1; + return b; + } static Bigint * mult #ifdef KR_headers - (a, b) Bigint *a, *b; + (a, b) Bigint *a, *b; #else - (Bigint *a, Bigint *b) + (Bigint *a, Bigint *b) #endif { - Bigint *c; - int k, wa, wb, wc; - ULong *x, *xa, *xae, *xb, *xbe, *xc, *xc0; - ULong y; + Bigint *c; + int k, wa, wb, wc; + ULong *x, *xa, *xae, *xb, *xbe, *xc, *xc0; + ULong y; #ifdef ULLong - ULLong carry, z; + ULLong carry, z; #else - ULong carry, z; + ULong carry, z; #ifdef Pack_32 - ULong z2; -#endif -#endif - - if (a->wds < b->wds) { - c = a; - a = b; - b = c; - } - k = a->k; - wa = a->wds; - wb = b->wds; - wc = wa + wb; - if (wc > a->maxwds) - k++; - c = Balloc(k); - for(x = c->x, xa = x + wc; x < xa; x++) - *x = 0; - xa = a->x; - xae = xa + wa; - xb = b->x; - xbe = xb + wb; - xc0 = c->x; + ULong z2; +#endif +#endif + + if (a->wds < b->wds) { + c = a; + a = b; + b = c; + } + k = a->k; + wa = a->wds; + wb = b->wds; + wc = wa + wb; + if (wc > a->maxwds) + k++; + c = Balloc(k); + for(x = c->x, xa = x + wc; x < xa; x++) + *x = 0; + xa = a->x; + xae = xa + wa; + xb = b->x; + xbe = xb + wb; + xc0 = c->x; #ifdef ULLong - for(; xb < xbe; xc0++) { - if ((y = *xb++)) { - x = xa; - xc = xc0; - carry = 0; - do { - z = *x++ * (ULLong)y + *xc + carry; - carry = z >> 32; - *xc++ = z & FFFFFFFF; - } - while(x < xae); - *xc = carry; - } - } + for(; xb < xbe; xc0++) { + if ((y = *xb++)) { + x = xa; + xc = xc0; + carry = 0; + do { + z = *x++ * (ULLong)y + *xc + carry; + carry = z >> 32; + *xc++ = z & FFFFFFFF; + } + while(x < xae); + *xc = carry; + } + } #else #ifdef Pack_32 - for(; xb < xbe; xb++, xc0++) { - if (y = *xb & 0xffff) { - x = xa; - xc = xc0; - carry = 0; - do { - z = (*x & 0xffff) * y + (*xc & 0xffff) + carry; - carry = z >> 16; - z2 = (*x++ >> 16) * y + (*xc >> 16) + carry; - carry = z2 >> 16; - Storeinc(xc, z2, z); - } - while(x < xae); - *xc = carry; - } - if (y = *xb >> 16) { - x = xa; - xc = xc0; - carry = 0; - z2 = *xc; - do { - z = (*x & 0xffff) * y + (*xc >> 16) + carry; - carry = z >> 16; - Storeinc(xc, z, z2); - z2 = (*x++ >> 16) * y + (*xc & 0xffff) + carry; - carry = z2 >> 16; - } - while(x < xae); - *xc = z2; - } - } + for(; xb < xbe; xb++, xc0++) { + if (y = *xb & 0xffff) { + x = xa; + xc = xc0; + carry = 0; + do { + z = (*x & 0xffff) * y + (*xc & 0xffff) + carry; + carry = z >> 16; + z2 = (*x++ >> 16) * y + (*xc >> 16) + carry; + carry = z2 >> 16; + Storeinc(xc, z2, z); + } + while(x < xae); + *xc = carry; + } + if (y = *xb >> 16) { + x = xa; + xc = xc0; + carry = 0; + z2 = *xc; + do { + z = (*x & 0xffff) * y + (*xc >> 16) + carry; + carry = z >> 16; + Storeinc(xc, z, z2); + z2 = (*x++ >> 16) * y + (*xc & 0xffff) + carry; + carry = z2 >> 16; + } + while(x < xae); + *xc = z2; + } + } #else - for(; xb < xbe; xc0++) { - if (y = *xb++) { - x = xa; - xc = xc0; - carry = 0; - do { - z = *x++ * y + *xc + carry; - carry = z >> 16; - *xc++ = z & 0xffff; - } - while(x < xae); - *xc = carry; - } - } -#endif -#endif - for(xc0 = c->x, xc = xc0 + wc; wc > 0 && !*--xc; --wc) ; - c->wds = wc; - return c; - } + for(; xb < xbe; xc0++) { + if (y = *xb++) { + x = xa; + xc = xc0; + carry = 0; + do { + z = *x++ * y + *xc + carry; + carry = z >> 16; + *xc++ = z & 0xffff; + } + while(x < xae); + *xc = carry; + } + } +#endif +#endif + for(xc0 = c->x, xc = xc0 + wc; wc > 0 && !*--xc; --wc) ; + c->wds = wc; + return c; + } static Bigint *p5s; static Bigint * pow5mult #ifdef KR_headers - (b, k) Bigint *b; int k; + (b, k) Bigint *b; int k; #else - (Bigint *b, int k) + (Bigint *b, int k) #endif { - Bigint *b1, *p5, *p51; - int i; - static int p05[3] = { 5, 25, 125 }; + Bigint *b1, *p5, *p51; + int i; + static int p05[3] = { 5, 25, 125 }; - if ((i = k & 3)) - b = multadd(b, p05[i-1], 0); + if ((i = k & 3)) + b = multadd(b, p05[i-1], 0); - if (!(k >>= 2)) - return b; - if (!(p5 = p5s)) { - /* first time */ + if (!(k >>= 2)) + return b; + if (!(p5 = p5s)) { + /* first time */ #ifdef MULTIPLE_THREADS - ACQUIRE_DTOA_LOCK(1); - if (!(p5 = p5s)) { - p5 = p5s = i2b(625); - p5->next = 0; - } - FREE_DTOA_LOCK(1); + ACQUIRE_DTOA_LOCK(1); + if (!(p5 = p5s)) { + p5 = p5s = i2b(625); + p5->next = 0; + } + FREE_DTOA_LOCK(1); #else - p5 = p5s = i2b(625); - p5->next = 0; -#endif - } - for(;;) { - if (k & 1) { - b1 = mult(b, p5); - Bfree(b); - b = b1; - } - if (!(k >>= 1)) - break; - if (!(p51 = p5->next)) { + p5 = p5s = i2b(625); + p5->next = 0; +#endif + } + for(;;) { + if (k & 1) { + b1 = mult(b, p5); + Bfree(b); + b = b1; + } + if (!(k >>= 1)) + break; + if (!(p51 = p5->next)) { #ifdef MULTIPLE_THREADS - ACQUIRE_DTOA_LOCK(1); - if (!(p51 = p5->next)) { - p51 = p5->next = mult(p5,p5); - p51->next = 0; - } - FREE_DTOA_LOCK(1); + ACQUIRE_DTOA_LOCK(1); + if (!(p51 = p5->next)) { + p51 = p5->next = mult(p5,p5); + p51->next = 0; + } + FREE_DTOA_LOCK(1); #else - p51 = p5->next = mult(p5,p5); - p51->next = 0; + p51 = p5->next = mult(p5,p5); + p51->next = 0; #endif - } - p5 = p51; - } - return b; - } + } + p5 = p51; + } + return b; + } static Bigint * lshift #ifdef KR_headers - (b, k) Bigint *b; int k; + (b, k) Bigint *b; int k; #else - (Bigint *b, int k) + (Bigint *b, int k) #endif { - int i, k1, n, n1; - Bigint *b1; - ULong *x, *x1, *xe, z; + int i, k1, n, n1; + Bigint *b1; + ULong *x, *x1, *xe, z; #ifdef Pack_32 - n = k >> 5; + n = k >> 5; #else - n = k >> 4; -#endif - k1 = b->k; - n1 = n + b->wds + 1; - for(i = b->maxwds; n1 > i; i <<= 1) - k1++; - b1 = Balloc(k1); - x1 = b1->x; - for(i = 0; i < n; i++) - *x1++ = 0; - x = b->x; - xe = x + b->wds; + n = k >> 4; +#endif + k1 = b->k; + n1 = n + b->wds + 1; + for(i = b->maxwds; n1 > i; i <<= 1) + k1++; + b1 = Balloc(k1); + x1 = b1->x; + for(i = 0; i < n; i++) + *x1++ = 0; + x = b->x; + xe = x + b->wds; #ifdef Pack_32 - if (k &= 0x1f) { - k1 = 32 - k; - z = 0; - do { - *x1++ = *x << k | z; - z = *x++ >> k1; - } - while(x < xe); - if ((*x1 = z)) - ++n1; - } + if (k &= 0x1f) { + k1 = 32 - k; + z = 0; + do { + *x1++ = *x << k | z; + z = *x++ >> k1; + } + while(x < xe); + if ((*x1 = z)) + ++n1; + } #else - if (k &= 0xf) { - k1 = 16 - k; - z = 0; - do { - *x1++ = *x << k & 0xffff | z; - z = *x++ >> k1; - } - while(x < xe); - if (*x1 = z) - ++n1; - } -#endif - else do - *x1++ = *x++; - while(x < xe); - b1->wds = n1 - 1; - Bfree(b); - return b1; - } + if (k &= 0xf) { + k1 = 16 - k; + z = 0; + do { + *x1++ = *x << k & 0xffff | z; + z = *x++ >> k1; + } + while(x < xe); + if (*x1 = z) + ++n1; + } +#endif + else do + *x1++ = *x++; + while(x < xe); + b1->wds = n1 - 1; + Bfree(b); + return b1; + } static int cmp #ifdef KR_headers - (a, b) Bigint *a, *b; + (a, b) Bigint *a, *b; #else - (Bigint *a, Bigint *b) + (Bigint *a, Bigint *b) #endif { - ULong *xa, *xa0, *xb, *xb0; - int i, j; + ULong *xa, *xa0, *xb, *xb0; + int i, j; - i = a->wds; - j = b->wds; + i = a->wds; + j = b->wds; #ifdef DEBUG - if (i > 1 && !a->x[i-1]) - Bug("cmp called with a->x[a->wds-1] == 0"); - if (j > 1 && !b->x[j-1]) - Bug("cmp called with b->x[b->wds-1] == 0"); -#endif - if (i -= j) - return i; - xa0 = a->x; - xa = xa0 + j; - xb0 = b->x; - xb = xb0 + j; - for(;;) { - if (*--xa != *--xb) - return *xa < *xb ? -1 : 1; - if (xa <= xa0) - break; - } - return 0; - } + if (i > 1 && !a->x[i-1]) + Bug("cmp called with a->x[a->wds-1] == 0"); + if (j > 1 && !b->x[j-1]) + Bug("cmp called with b->x[b->wds-1] == 0"); +#endif + if (i -= j) + return i; + xa0 = a->x; + xa = xa0 + j; + xb0 = b->x; + xb = xb0 + j; + for(;;) { + if (*--xa != *--xb) + return *xa < *xb ? -1 : 1; + if (xa <= xa0) + break; + } + return 0; + } static Bigint * diff #ifdef KR_headers - (a, b) Bigint *a, *b; + (a, b) Bigint *a, *b; #else - (Bigint *a, Bigint *b) + (Bigint *a, Bigint *b) #endif { - Bigint *c; - int i, wa, wb; - ULong *xa, *xae, *xb, *xbe, *xc; + Bigint *c; + int i, wa, wb; + ULong *xa, *xae, *xb, *xbe, *xc; #ifdef ULLong - ULLong borrow, y; + ULLong borrow, y; #else - ULong borrow, y; + ULong borrow, y; #ifdef Pack_32 - ULong z; -#endif -#endif - - i = cmp(a,b); - if (!i) { - c = Balloc(0); - c->wds = 1; - c->x[0] = 0; - return c; - } - if (i < 0) { - c = a; - a = b; - b = c; - i = 1; - } - else - i = 0; - c = Balloc(a->k); - c->sign = i; - wa = a->wds; - xa = a->x; - xae = xa + wa; - wb = b->wds; - xb = b->x; - xbe = xb + wb; - xc = c->x; - borrow = 0; + ULong z; +#endif +#endif + + i = cmp(a,b); + if (!i) { + c = Balloc(0); + c->wds = 1; + c->x[0] = 0; + return c; + } + if (i < 0) { + c = a; + a = b; + b = c; + i = 1; + } + else + i = 0; + c = Balloc(a->k); + c->sign = i; + wa = a->wds; + xa = a->x; + xae = xa + wa; + wb = b->wds; + xb = b->x; + xbe = xb + wb; + xc = c->x; + borrow = 0; #ifdef ULLong - do { - y = (ULLong)*xa++ - *xb++ - borrow; - borrow = y >> 32 & (ULong)1; - *xc++ = y & FFFFFFFF; - } - while(xb < xbe); - while(xa < xae) { - y = *xa++ - borrow; - borrow = y >> 32 & (ULong)1; - *xc++ = y & FFFFFFFF; - } + do { + y = (ULLong)*xa++ - *xb++ - borrow; + borrow = y >> 32 & (ULong)1; + *xc++ = y & FFFFFFFF; + } + while(xb < xbe); + while(xa < xae) { + y = *xa++ - borrow; + borrow = y >> 32 & (ULong)1; + *xc++ = y & FFFFFFFF; + } #else #ifdef Pack_32 - do { - y = (*xa & 0xffff) - (*xb & 0xffff) - borrow; - borrow = (y & 0x10000) >> 16; - z = (*xa++ >> 16) - (*xb++ >> 16) - borrow; - borrow = (z & 0x10000) >> 16; - Storeinc(xc, z, y); - } - while(xb < xbe); - while(xa < xae) { - y = (*xa & 0xffff) - borrow; - borrow = (y & 0x10000) >> 16; - z = (*xa++ >> 16) - borrow; - borrow = (z & 0x10000) >> 16; - Storeinc(xc, z, y); - } + do { + y = (*xa & 0xffff) - (*xb & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*xa++ >> 16) - (*xb++ >> 16) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(xc, z, y); + } + while(xb < xbe); + while(xa < xae) { + y = (*xa & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*xa++ >> 16) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(xc, z, y); + } #else - do { - y = *xa++ - *xb++ - borrow; - borrow = (y & 0x10000) >> 16; - *xc++ = y & 0xffff; - } - while(xb < xbe); - while(xa < xae) { - y = *xa++ - borrow; - borrow = (y & 0x10000) >> 16; - *xc++ = y & 0xffff; - } -#endif -#endif - while(!*--xc) - wa--; - c->wds = wa; - return c; - } + do { + y = *xa++ - *xb++ - borrow; + borrow = (y & 0x10000) >> 16; + *xc++ = y & 0xffff; + } + while(xb < xbe); + while(xa < xae) { + y = *xa++ - borrow; + borrow = (y & 0x10000) >> 16; + *xc++ = y & 0xffff; + } +#endif +#endif + while(!*--xc) + wa--; + c->wds = wa; + return c; + } static double ulp #ifdef KR_headers - (x) U *x; + (x) U *x; #else - (U *x) + (U *x) #endif { - Long L; - U u; + Long L; + U u; - L = (word0(x) & Exp_mask) - (P-1)*Exp_msk1; + L = (word0(x) & Exp_mask) - (P-1)*Exp_msk1; #ifndef Avoid_Underflow #ifndef Sudden_Underflow - if (L > 0) { + if (L > 0) { #endif #endif #ifdef IBM - L |= Exp_msk1 >> 4; + L |= Exp_msk1 >> 4; #endif - word0(&u) = L; - word1(&u) = 0; + word0(&u) = L; + word1(&u) = 0; #ifndef Avoid_Underflow #ifndef Sudden_Underflow - } - else { - L = -L >> Exp_shift; - if (L < Exp_shift) { - word0(&u) = 0x80000 >> L; - word1(&u) = 0; - } - else { - word0(&u) = 0; - L -= Exp_shift; - word1(&u) = L >= 31 ? 1 : 1 << 31 - L; - } - } -#endif -#endif - return dval(&u); - } + } + else { + L = -L >> Exp_shift; + if (L < Exp_shift) { + word0(&u) = 0x80000 >> L; + word1(&u) = 0; + } + else { + word0(&u) = 0; + L -= Exp_shift; + word1(&u) = L >= 31 ? 1 : 1 << 31 - L; + } + } +#endif +#endif + return dval(&u); + } static double b2d #ifdef KR_headers - (a, e) Bigint *a; int *e; + (a, e) Bigint *a; int *e; #else - (Bigint *a, int *e) + (Bigint *a, int *e) #endif { - ULong *xa, *xa0, w, y, z; - int k; - U d; + ULong *xa, *xa0, w, y, z; + int k; + U d; #ifdef VAX - ULong d0, d1; + ULong d0, d1; #else #define d0 word0(&d) #define d1 word1(&d) #endif - xa0 = a->x; - xa = xa0 + a->wds; - y = *--xa; + xa0 = a->x; + xa = xa0 + a->wds; + y = *--xa; #ifdef DEBUG - if (!y) Bug("zero y in b2d"); + if (!y) Bug("zero y in b2d"); #endif - k = hi0bits(y); - *e = 32 - k; + k = hi0bits(y); + *e = 32 - k; #ifdef Pack_32 - if (k < Ebits) { - d0 = Exp_1 | y >> (Ebits - k); - w = xa > xa0 ? *--xa : 0; - d1 = y << ((32-Ebits) + k) | w >> (Ebits - k); - goto ret_d; - } - z = xa > xa0 ? *--xa : 0; - if (k -= Ebits) { - d0 = Exp_1 | y << k | z >> (32 - k); - y = xa > xa0 ? *--xa : 0; - d1 = z << k | y >> (32 - k); - } - else { - d0 = Exp_1 | y; - d1 = z; - } + if (k < Ebits) { + d0 = Exp_1 | y >> (Ebits - k); + w = xa > xa0 ? *--xa : 0; + d1 = y << ((32-Ebits) + k) | w >> (Ebits - k); + goto ret_d; + } + z = xa > xa0 ? *--xa : 0; + if (k -= Ebits) { + d0 = Exp_1 | y << k | z >> (32 - k); + y = xa > xa0 ? *--xa : 0; + d1 = z << k | y >> (32 - k); + } + else { + d0 = Exp_1 | y; + d1 = z; + } #else - if (k < Ebits + 16) { - z = xa > xa0 ? *--xa : 0; - d0 = Exp_1 | y << k - Ebits | z >> Ebits + 16 - k; - w = xa > xa0 ? *--xa : 0; - y = xa > xa0 ? *--xa : 0; - d1 = z << k + 16 - Ebits | w << k - Ebits | y >> 16 + Ebits - k; - goto ret_d; - } - z = xa > xa0 ? *--xa : 0; - w = xa > xa0 ? *--xa : 0; - k -= Ebits + 16; - d0 = Exp_1 | y << k + 16 | z << k | w >> 16 - k; - y = xa > xa0 ? *--xa : 0; - d1 = w << k + 16 | y << k; + if (k < Ebits + 16) { + z = xa > xa0 ? *--xa : 0; + d0 = Exp_1 | y << k - Ebits | z >> Ebits + 16 - k; + w = xa > xa0 ? *--xa : 0; + y = xa > xa0 ? *--xa : 0; + d1 = z << k + 16 - Ebits | w << k - Ebits | y >> 16 + Ebits - k; + goto ret_d; + } + z = xa > xa0 ? *--xa : 0; + w = xa > xa0 ? *--xa : 0; + k -= Ebits + 16; + d0 = Exp_1 | y << k + 16 | z << k | w >> 16 - k; + y = xa > xa0 ? *--xa : 0; + d1 = w << k + 16 | y << k; #endif ret_d: #ifdef VAX - word0(&d) = d0 >> 16 | d0 << 16; - word1(&d) = d1 >> 16 | d1 << 16; + word0(&d) = d0 >> 16 | d0 << 16; + word1(&d) = d1 >> 16 | d1 << 16; #else #undef d0 #undef d1 #endif - return dval(&d); - } + return dval(&d); + } static Bigint * d2b #ifdef KR_headers - (d, e, bits) U *d; int *e, *bits; + (d, e, bits) U *d; int *e, *bits; #else - (U *d, int *e, int *bits) + (U *d, int *e, int *bits) #endif { - Bigint *b; - int de, k; - ULong *x, y, z; + Bigint *b; + int de, k; + ULong *x, y, z; #ifndef Sudden_Underflow - int i; + int i; #endif #ifdef VAX - ULong d0, d1; - d0 = word0(d) >> 16 | word0(d) << 16; - d1 = word1(d) >> 16 | word1(d) << 16; + ULong d0, d1; + d0 = word0(d) >> 16 | word0(d) << 16; + d1 = word1(d) >> 16 | word1(d) << 16; #else #define d0 word0(d) #define d1 word1(d) #endif #ifdef Pack_32 - b = Balloc(1); + b = Balloc(1); #else - b = Balloc(2); + b = Balloc(2); #endif - x = b->x; + x = b->x; - z = d0 & Frac_mask; - d0 &= 0x7fffffff; /* clear sign bit, which we ignore */ + z = d0 & Frac_mask; + d0 &= 0x7fffffff; /* clear sign bit, which we ignore */ #ifdef Sudden_Underflow - de = (int)(d0 >> Exp_shift); + de = (int)(d0 >> Exp_shift); #ifndef IBM - z |= Exp_msk11; + z |= Exp_msk11; #endif #else - if ((de = (int)(d0 >> Exp_shift))) - z |= Exp_msk1; + if ((de = (int)(d0 >> Exp_shift))) + z |= Exp_msk1; #endif #ifdef Pack_32 - if ((y = d1)) { - if ((k = lo0bits(&y))) { - x[0] = y | z << (32 - k); - z >>= k; - } - else - x[0] = y; + if ((y = d1)) { + if ((k = lo0bits(&y))) { + x[0] = y | z << (32 - k); + z >>= k; + } + else + x[0] = y; #ifndef Sudden_Underflow - i = + i = #endif - b->wds = (x[1] = z) ? 2 : 1; - } - else { - k = lo0bits(&z); - x[0] = z; + b->wds = (x[1] = z) ? 2 : 1; + } + else { + k = lo0bits(&z); + x[0] = z; #ifndef Sudden_Underflow - i = + i = #endif - b->wds = 1; - k += 32; - } + b->wds = 1; + k += 32; + } #else - if (y = d1) { - if (k = lo0bits(&y)) - if (k >= 16) { - x[0] = y | z << 32 - k & 0xffff; - x[1] = z >> k - 16 & 0xffff; - x[2] = z >> k; - i = 2; - } - else { - x[0] = y & 0xffff; - x[1] = y >> 16 | z << 16 - k & 0xffff; - x[2] = z >> k & 0xffff; - x[3] = z >> k+16; - i = 3; - } - else { - x[0] = y & 0xffff; - x[1] = y >> 16; - x[2] = z & 0xffff; - x[3] = z >> 16; - i = 3; - } - } - else { + if (y = d1) { + if (k = lo0bits(&y)) + if (k >= 16) { + x[0] = y | z << 32 - k & 0xffff; + x[1] = z >> k - 16 & 0xffff; + x[2] = z >> k; + i = 2; + } + else { + x[0] = y & 0xffff; + x[1] = y >> 16 | z << 16 - k & 0xffff; + x[2] = z >> k & 0xffff; + x[3] = z >> k+16; + i = 3; + } + else { + x[0] = y & 0xffff; + x[1] = y >> 16; + x[2] = z & 0xffff; + x[3] = z >> 16; + i = 3; + } + } + else { #ifdef DEBUG - if (!z) - Bug("Zero passed to d2b"); -#endif - k = lo0bits(&z); - if (k >= 16) { - x[0] = z; - i = 0; - } - else { - x[0] = z & 0xffff; - x[1] = z >> 16; - i = 1; - } - k += 32; - } - while(!x[i]) - --i; - b->wds = i + 1; + if (!z) + Bug("Zero passed to d2b"); +#endif + k = lo0bits(&z); + if (k >= 16) { + x[0] = z; + i = 0; + } + else { + x[0] = z & 0xffff; + x[1] = z >> 16; + i = 1; + } + k += 32; + } + while(!x[i]) + --i; + b->wds = i + 1; #endif #ifndef Sudden_Underflow - if (de) { + if (de) { #endif #ifdef IBM - *e = (de - Bias - (P-1) << 2) + k; - *bits = 4*P + 8 - k - hi0bits(word0(d) & Frac_mask); + *e = (de - Bias - (P-1) << 2) + k; + *bits = 4*P + 8 - k - hi0bits(word0(d) & Frac_mask); #else - *e = de - Bias - (P-1) + k; - *bits = P - k; + *e = de - Bias - (P-1) + k; + *bits = P - k; #endif #ifndef Sudden_Underflow - } - else { - *e = de - Bias - (P-1) + 1 + k; + } + else { + *e = de - Bias - (P-1) + 1 + k; #ifdef Pack_32 - *bits = 32*i - hi0bits(x[i-1]); + *bits = 32*i - hi0bits(x[i-1]); #else - *bits = (i+2)*16 - hi0bits(x[i]); + *bits = (i+2)*16 - hi0bits(x[i]); #endif - } + } #endif - return b; - } + return b; + } #undef d0 #undef d1 static double ratio #ifdef KR_headers - (a, b) Bigint *a, *b; + (a, b) Bigint *a, *b; #else - (Bigint *a, Bigint *b) + (Bigint *a, Bigint *b) #endif { - U da, db; - int k, ka, kb; + U da, db; + int k, ka, kb; - dval(&da) = b2d(a, &ka); - dval(&db) = b2d(b, &kb); + dval(&da) = b2d(a, &ka); + dval(&db) = b2d(b, &kb); #ifdef Pack_32 - k = ka - kb + 32*(a->wds - b->wds); + k = ka - kb + 32*(a->wds - b->wds); #else - k = ka - kb + 16*(a->wds - b->wds); + k = ka - kb + 16*(a->wds - b->wds); #endif #ifdef IBM - if (k > 0) { - word0(&da) += (k >> 2)*Exp_msk1; - if (k &= 3) - dval(&da) *= 1 << k; - } - else { - k = -k; - word0(&db) += (k >> 2)*Exp_msk1; - if (k &= 3) - dval(&db) *= 1 << k; - } + if (k > 0) { + word0(&da) += (k >> 2)*Exp_msk1; + if (k &= 3) + dval(&da) *= 1 << k; + } + else { + k = -k; + word0(&db) += (k >> 2)*Exp_msk1; + if (k &= 3) + dval(&db) *= 1 << k; + } #else - if (k > 0) - word0(&da) += k*Exp_msk1; - else { - k = -k; - word0(&db) += k*Exp_msk1; - } + if (k > 0) + word0(&da) += k*Exp_msk1; + else { + k = -k; + word0(&db) += k*Exp_msk1; + } #endif - return dval(&da) / dval(&db); - } + return dval(&da) / dval(&db); + } static CONST double tens[] = { - 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, - 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, - 1e20, 1e21, 1e22 + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22 #ifdef VAX - , 1e23, 1e24 + , 1e23, 1e24 #endif - }; + }; static CONST double #ifdef IEEE_Arith bigtens[] = { 1e16, 1e32, 1e64, 1e128, 1e256 }; static CONST double tinytens[] = { 1e-16, 1e-32, 1e-64, 1e-128, #ifdef Avoid_Underflow - 9007199254740992.*9007199254740992.e-256 - /* = 2^106 * 1e-256 */ + 9007199254740992.*9007199254740992.e-256 + /* = 2^106 * 1e-256 */ #else - 1e-256 + 1e-256 #endif - }; + }; /* The factor of 2^53 in tinytens[4] helps us avoid setting the underflow */ /* flag unnecessarily. It leads to a song and dance at the end of strtod. */ #define Scale_Bit 0x10 @@ -1507,39 +1514,39 @@ static unsigned char hexdig[256]; static void htinit(unsigned char *h, unsigned char *s, int inc) { - int i, j; - for(i = 0; (j = s[i]) !=0; i++) - h[j] = i + inc; - } + int i, j; + for(i = 0; (j = s[i]) !=0; i++) + h[j] = i + inc; + } static void -hexdig_init(void) /* Use of hexdig_init omitted 20121220 to avoid a */ - /* race condition when multiple threads are used. */ +hexdig_init(void) /* Use of hexdig_init omitted 20121220 to avoid a */ + /* race condition when multiple threads are used. */ { #define USC (unsigned char *) - htinit(hexdig, USC "0123456789", 0x10); - htinit(hexdig, USC "abcdef", 0x10 + 10); - htinit(hexdig, USC "ABCDEF", 0x10 + 10); - } + htinit(hexdig, USC "0123456789", 0x10); + htinit(hexdig, USC "abcdef", 0x10 + 10); + htinit(hexdig, USC "ABCDEF", 0x10 + 10); + } #else static unsigned char hexdig[256] = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 16,17,18,19,20,21,22,23,24,25,0,0,0,0,0,0, - 0,26,27,28,29,30,31,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,26,27,28,29,30,31,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 - }; + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 16,17,18,19,20,21,22,23,24,25,0,0,0,0,0,0, + 0,26,27,28,29,30,31,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,26,27,28,29,30,31,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + }; #endif #endif /* } Need_Hexdig */ @@ -1556,90 +1563,90 @@ static unsigned char hexdig[256] = { static int match #ifdef KR_headers - (sp, t) char **sp, *t; + (sp, t) char **sp, *t; #else - (const char **sp, const char *t) + (const char **sp, const char *t) #endif { - int c, d; - CONST char *s = *sp; - - while((d = *t++)) { - if ((c = *++s) >= 'A' && c <= 'Z') - c += 'a' - 'A'; - if (c != d) - return 0; - } - *sp = s + 1; - return 1; - } + int c, d; + CONST char *s = *sp; + + while((d = *t++)) { + if ((c = *++s) >= 'A' && c <= 'Z') + c += 'a' - 'A'; + if (c != d) + return 0; + } + *sp = s + 1; + return 1; + } #ifndef No_Hex_NaN static void hexnan #ifdef KR_headers - (rvp, sp) U *rvp; CONST char **sp; + (rvp, sp) U *rvp; CONST char **sp; #else - (U *rvp, const char **sp) + (U *rvp, const char **sp) #endif { - ULong c, x[2]; - CONST char *s; - int c1, havedig, udx0, xshift; - - /**** if (!hexdig['0']) hexdig_init(); ****/ - x[0] = x[1] = 0; - havedig = xshift = 0; - udx0 = 1; - s = *sp; - /* allow optional initial 0x or 0X */ - while((c = *(CONST unsigned char*)(s+1)) && c <= ' ') - ++s; - if (s[1] == '0' && (s[2] == 'x' || s[2] == 'X')) - s += 2; - while((c = *(CONST unsigned char*)++s)) { - if ((c1 = hexdig[c])) - c = c1 & 0xf; - else if (c <= ' ') { - if (udx0 && havedig) { - udx0 = 0; - xshift = 1; - } - continue; - } + ULong c, x[2]; + CONST char *s; + int c1, havedig, udx0, xshift; + + /**** if (!hexdig['0']) hexdig_init(); ****/ + x[0] = x[1] = 0; + havedig = xshift = 0; + udx0 = 1; + s = *sp; + /* allow optional initial 0x or 0X */ + while((c = *(CONST unsigned char*)(s+1)) && c <= ' ') + ++s; + if (s[1] == '0' && (s[2] == 'x' || s[2] == 'X')) + s += 2; + while((c = *(CONST unsigned char*)++s)) { + if ((c1 = hexdig[c])) + c = c1 & 0xf; + else if (c <= ' ') { + if (udx0 && havedig) { + udx0 = 0; + xshift = 1; + } + continue; + } #ifdef GDTOA_NON_PEDANTIC_NANCHECK - else if (/*(*/ c == ')' && havedig) { - *sp = s + 1; - break; - } - else - return; /* invalid form: don't change *sp */ + else if (/*(*/ c == ')' && havedig) { + *sp = s + 1; + break; + } + else + return; /* invalid form: don't change *sp */ #else - else { - do { - if (/*(*/ c == ')') { - *sp = s + 1; - break; - } - } while((c = *++s)); - break; - } -#endif - havedig = 1; - if (xshift) { - xshift = 0; - x[0] = x[1]; - x[1] = 0; - } - if (udx0) - x[0] = (x[0] << 4) | (x[1] >> 28); - x[1] = (x[1] << 4) | c; - } - if ((x[0] &= 0xfffff) || x[1]) { - word0(rvp) = Exp_mask | x[0]; - word1(rvp) = x[1]; - } - } + else { + do { + if (/*(*/ c == ')') { + *sp = s + 1; + break; + } + } while((c = *++s)); + break; + } +#endif + havedig = 1; + if (xshift) { + xshift = 0; + x[0] = x[1]; + x[1] = 0; + } + if (udx0) + x[0] = (x[0] << 4) | (x[1] >> 28); + x[1] = (x[1] << 4) | c; + } + if ((x[0] &= 0xfffff) || x[1]) { + word0(rvp) = Exp_mask | x[0]; + word1(rvp) = x[1]; + } + } #endif /*No_Hex_NaN*/ #endif /* INFNAN_CHECK */ @@ -1661,29 +1668,29 @@ increment(b) Bigint *b; increment(Bigint *b) #endif { - ULong *x, *xe; - Bigint *b1; - - x = b->x; - xe = x + b->wds; - do { - if (*x < (ULong)0xffffffffL) { - ++*x; - return b; - } - *x++ = 0; - } while(x < xe); - { - if (b->wds >= b->maxwds) { - b1 = Balloc(b->k+1); - Bcopy(b1,b); - Bfree(b); - b = b1; - } - b->x[b->wds++] = 1; - } - return b; - } + ULong *x, *xe; + Bigint *b1; + + x = b->x; + xe = x + b->wds; + do { + if (*x < (ULong)0xffffffffL) { + ++*x; + return b; + } + *x++ = 0; + } while(x < xe); + { + if (b->wds >= b->maxwds) { + b1 = Balloc(b->k+1); + Bcopy(b1,b); + Bfree(b); + b = b1; + } + b->x[b->wds++] = 1; + } + return b; + } #endif /*}*/ @@ -1696,31 +1703,31 @@ rshift(b, k) Bigint *b; int k; rshift(Bigint *b, int k) #endif { - ULong *x, *x1, *xe, y; - int n; - - x = x1 = b->x; - n = k >> kshift; - if (n < b->wds) { - xe = x + b->wds; - x += n; - if (k &= kmask) { - n = 32 - k; - y = *x++ >> k; - while(x < xe) { - *x1++ = (y | (*x << n)) & 0xffffffff; - y = *x++ >> k; - } - if ((*x1 = y) !=0) - x1++; - } - else - while(x < xe) - *x1++ = *x++; - } - if ((b->wds = x1 - b->x) == 0) - b->x[0] = 0; - } + ULong *x, *x1, *xe, y; + int n; + + x = x1 = b->x; + n = k >> kshift; + if (n < b->wds) { + xe = x + b->wds; + x += n; + if (k &= kmask) { + n = 32 - k; + y = *x++ >> k; + while(x < xe) { + *x1++ = (y | (*x << n)) & 0xffffffff; + y = *x++ >> k; + } + if ((*x1 = y) !=0) + x1++; + } + else + while(x < xe) + *x1++ = *x++; + } + if ((b->wds = x1 - b->x) == 0) + b->x[0] = 0; + } static ULong #ifdef KR_headers @@ -1729,394 +1736,394 @@ any_on(b, k) Bigint *b; int k; any_on(Bigint *b, int k) #endif { - int n, nwds; - ULong *x, *x0, x1, x2; - - x = b->x; - nwds = b->wds; - n = k >> kshift; - if (n > nwds) - n = nwds; - else if (n < nwds && (k &= kmask)) { - x1 = x2 = x[n]; - x1 >>= k; - x1 <<= k; - if (x1 != x2) - return 1; - } - x0 = x; - x += n; - while(x > x0) - if (*--x) - return 1; - return 0; - } - -enum { /* rounding values: same as FLT_ROUNDS */ - Round_zero = 0, - Round_near = 1, - Round_up = 2, - Round_down = 3 - }; + int n, nwds; + ULong *x, *x0, x1, x2; + + x = b->x; + nwds = b->wds; + n = k >> kshift; + if (n > nwds) + n = nwds; + else if (n < nwds && (k &= kmask)) { + x1 = x2 = x[n]; + x1 >>= k; + x1 <<= k; + if (x1 != x2) + return 1; + } + x0 = x; + x += n; + while(x > x0) + if (*--x) + return 1; + return 0; + } + +enum { /* rounding values: same as FLT_ROUNDS */ + Round_zero = 0, + Round_near = 1, + Round_up = 2, + Round_down = 3 + }; void #ifdef KR_headers gethex(sp, rvp, rounding, sign) - CONST char **sp; U *rvp; int rounding, sign; + CONST char **sp; U *rvp; int rounding, sign; #else gethex( CONST char **sp, U *rvp, int rounding, int sign) #endif { - Bigint *b; - CONST unsigned char *decpt, *s0, *s, *s1; - Long e, e1; - ULong L, lostbits, *x; - int big, denorm, esign, havedig, k, n, nbits, up, zret; + Bigint *b; + CONST unsigned char *decpt, *s0, *s, *s1; + Long e, e1; + ULong L, lostbits, *x; + int big, denorm, esign, havedig, k, n, nbits, up, zret; #ifdef IBM - int j; + int j; #endif - enum { + enum { #ifdef IEEE_Arith /*{{*/ - emax = 0x7fe - Bias - P + 1, - emin = Emin - P + 1 + emax = 0x7fe - Bias - P + 1, + emin = Emin - P + 1 #else /*}{*/ - emin = Emin - P, + emin = Emin - P, #ifdef VAX - emax = 0x7ff - Bias - P + 1 + emax = 0x7ff - Bias - P + 1 #endif #ifdef IBM - emax = 0x7f - Bias - P + emax = 0x7f - Bias - P #endif #endif /*}}*/ - }; + }; #ifdef USE_LOCALE - int i; + int i; #ifdef NO_LOCALE_CACHE - const unsigned char *decimalpoint = (unsigned char*) - localeconv()->decimal_point; + const unsigned char *decimalpoint = (unsigned char*) + localeconv()->decimal_point; #else - const unsigned char *decimalpoint; - static unsigned char *decimalpoint_cache; - if (!(s0 = decimalpoint_cache)) { - s0 = (unsigned char*)localeconv()->decimal_point; - if ((decimalpoint_cache = (unsigned char*) - MALLOC(strlen((CONST char*)s0) + 1))) { - strcpy((char*)decimalpoint_cache, (CONST char*)s0); - s0 = decimalpoint_cache; - } - } - decimalpoint = s0; -#endif -#endif - - /**** if (!hexdig['0']) hexdig_init(); ****/ - havedig = 0; - s0 = *(CONST unsigned char **)sp + 2; - while(s0[havedig] == '0') - havedig++; - s0 += havedig; - s = s0; - decpt = 0; - zret = 0; - e = 0; - if (hexdig[*s]) - havedig++; - else { - zret = 1; + const unsigned char *decimalpoint; + static unsigned char *decimalpoint_cache; + if (!(s0 = decimalpoint_cache)) { + s0 = (unsigned char*)localeconv()->decimal_point; + if ((decimalpoint_cache = (unsigned char*) + MALLOC(strlen((CONST char*)s0) + 1))) { + strcpy((char*)decimalpoint_cache, (CONST char*)s0); + s0 = decimalpoint_cache; + } + } + decimalpoint = s0; +#endif +#endif + + /**** if (!hexdig['0']) hexdig_init(); ****/ + havedig = 0; + s0 = *(CONST unsigned char **)sp + 2; + while(s0[havedig] == '0') + havedig++; + s0 += havedig; + s = s0; + decpt = 0; + zret = 0; + e = 0; + if (hexdig[*s]) + havedig++; + else { + zret = 1; #ifdef USE_LOCALE - for(i = 0; decimalpoint[i]; ++i) { - if (s[i] != decimalpoint[i]) - goto pcheck; - } - decpt = s += i; + for(i = 0; decimalpoint[i]; ++i) { + if (s[i] != decimalpoint[i]) + goto pcheck; + } + decpt = s += i; #else - if (*s != '.') - goto pcheck; - decpt = ++s; -#endif - if (!hexdig[*s]) - goto pcheck; - while(*s == '0') - s++; - if (hexdig[*s]) - zret = 0; - havedig = 1; - s0 = s; - } - while(hexdig[*s]) - s++; + if (*s != '.') + goto pcheck; + decpt = ++s; +#endif + if (!hexdig[*s]) + goto pcheck; + while(*s == '0') + s++; + if (hexdig[*s]) + zret = 0; + havedig = 1; + s0 = s; + } + while(hexdig[*s]) + s++; #ifdef USE_LOCALE - if (*s == *decimalpoint && !decpt) { - for(i = 1; decimalpoint[i]; ++i) { - if (s[i] != decimalpoint[i]) - goto pcheck; - } - decpt = s += i; + if (*s == *decimalpoint && !decpt) { + for(i = 1; decimalpoint[i]; ++i) { + if (s[i] != decimalpoint[i]) + goto pcheck; + } + decpt = s += i; #else - if (*s == '.' && !decpt) { - decpt = ++s; -#endif - while(hexdig[*s]) - s++; - }/*}*/ - if (decpt) - e = -(((Long)(s-decpt)) << 2); + if (*s == '.' && !decpt) { + decpt = ++s; +#endif + while(hexdig[*s]) + s++; + }/*}*/ + if (decpt) + e = -(((Long)(s-decpt)) << 2); pcheck: - s1 = s; - big = esign = 0; - switch(*s) { - case 'p': - case 'P': - switch(*++s) { - case '-': - esign = 1; - /* no break */ - case '+': - s++; - } - if ((n = hexdig[*s]) == 0 || n > 0x19) { - s = s1; - break; - } - e1 = n - 0x10; - while((n = hexdig[*++s]) !=0 && n <= 0x19) { - if (e1 & 0xf8000000) - big = 1; - e1 = 10*e1 + n - 0x10; - } - if (esign) - e1 = -e1; - e += e1; - } - *sp = (char*)s; - if (!havedig) - *sp = (char*)s0 - 1; - if (zret) - goto retz1; - if (big) { - if (esign) { + s1 = s; + big = esign = 0; + switch(*s) { + case 'p': + case 'P': + switch(*++s) { + case '-': + esign = 1; + /* no break */ + case '+': + s++; + } + if ((n = hexdig[*s]) == 0 || n > 0x19) { + s = s1; + break; + } + e1 = n - 0x10; + while((n = hexdig[*++s]) !=0 && n <= 0x19) { + if (e1 & 0xf8000000) + big = 1; + e1 = 10*e1 + n - 0x10; + } + if (esign) + e1 = -e1; + e += e1; + } + *sp = (const char*)s; // Ren/C: fix cast away of const + if (!havedig) + *sp = (const char*)s0 - 1; // Ren/C: fix cast away of const + if (zret) + goto retz1; + if (big) { + if (esign) { #ifdef IEEE_Arith - switch(rounding) { - case Round_up: - if (sign) - break; - goto ret_tiny; - case Round_down: - if (!sign) - break; - goto ret_tiny; - } -#endif - goto retz; + switch(rounding) { + case Round_up: + if (sign) + break; + goto ret_tiny; + case Round_down: + if (!sign) + break; + goto ret_tiny; + } +#endif + goto retz; #ifdef IEEE_Arith ret_tiny: #ifndef NO_ERRNO - errno = ERANGE; + errno = ERANGE; #endif - word0(rvp) = 0; - word1(rvp) = 1; - return; + word0(rvp) = 0; + word1(rvp) = 1; + return; #endif /* IEEE_Arith */ - } - switch(rounding) { - case Round_near: - goto ovfl1; - case Round_up: - if (!sign) - goto ovfl1; - goto ret_big; - case Round_down: - if (sign) - goto ovfl1; - goto ret_big; - } + } + switch(rounding) { + case Round_near: + goto ovfl1; + case Round_up: + if (!sign) + goto ovfl1; + goto ret_big; + case Round_down: + if (sign) + goto ovfl1; + goto ret_big; + } ret_big: - word0(rvp) = Big0; - word1(rvp) = Big1; - return; - } - n = s1 - s0 - 1; - for(k = 0; n > (1 << (kshift-2)) - 1; n >>= 1) - k++; - b = Balloc(k); - x = b->x; - n = 0; - L = 0; + word0(rvp) = Big0; + word1(rvp) = Big1; + return; + } + n = s1 - s0 - 1; + for(k = 0; n > (1 << (kshift-2)) - 1; n >>= 1) + k++; + b = Balloc(k); + x = b->x; + n = 0; + L = 0; #ifdef USE_LOCALE - for(i = 0; decimalpoint[i+1]; ++i); + for(i = 0; decimalpoint[i+1]; ++i); #endif - while(s1 > s0) { + while(s1 > s0) { #ifdef USE_LOCALE - if (*--s1 == decimalpoint[i]) { - s1 -= i; - continue; - } + if (*--s1 == decimalpoint[i]) { + s1 -= i; + continue; + } #else - if (*--s1 == '.') - continue; -#endif - if (n == ULbits) { - *x++ = L; - L = 0; - n = 0; - } - L |= (hexdig[*s1] & 0x0f) << n; - n += 4; - } - *x++ = L; - b->wds = n = x - b->x; - n = ULbits*n - hi0bits(L); - nbits = Nbits; - lostbits = 0; - x = b->x; - if (n > nbits) { - n -= nbits; - if (any_on(b,n)) { - lostbits = 1; - k = n - 1; - if (x[k>>kshift] & 1 << (k & kmask)) { - lostbits = 2; - if (k > 0 && any_on(b,k)) - lostbits = 3; - } - } - rshift(b, n); - e += n; - } - else if (n < nbits) { - n = nbits - n; - b = lshift(b, n); - e -= n; - x = b->x; - } - if (e > Emax) { + if (*--s1 == '.') + continue; +#endif + if (n == ULbits) { + *x++ = L; + L = 0; + n = 0; + } + L |= (hexdig[*s1] & 0x0f) << n; + n += 4; + } + *x++ = L; + b->wds = n = x - b->x; + n = ULbits*n - hi0bits(L); + nbits = Nbits; + lostbits = 0; + x = b->x; + if (n > nbits) { + n -= nbits; + if (any_on(b,n)) { + lostbits = 1; + k = n - 1; + if (x[k>>kshift] & 1 << (k & kmask)) { + lostbits = 2; + if (k > 0 && any_on(b,k)) + lostbits = 3; + } + } + rshift(b, n); + e += n; + } + else if (n < nbits) { + n = nbits - n; + b = lshift(b, n); + e -= n; + x = b->x; + } + if (e > Emax) { ovfl: - Bfree(b); + Bfree(b); ovfl1: #ifndef NO_ERRNO - errno = ERANGE; -#endif - word0(rvp) = Exp_mask; - word1(rvp) = 0; - return; - } - denorm = 0; - if (e < emin) { - denorm = 1; - n = emin - e; - if (n >= nbits) { + errno = ERANGE; +#endif + word0(rvp) = Exp_mask; + word1(rvp) = 0; + return; + } + denorm = 0; + if (e < emin) { + denorm = 1; + n = emin - e; + if (n >= nbits) { #ifdef IEEE_Arith /*{*/ - switch (rounding) { - case Round_near: - if (n == nbits && (n < 2 || any_on(b,n-1))) - goto ret_tiny; - break; - case Round_up: - if (!sign) - goto ret_tiny; - break; - case Round_down: - if (sign) - goto ret_tiny; - } + switch (rounding) { + case Round_near: + if (n == nbits && (n < 2 || any_on(b,n-1))) + goto ret_tiny; + break; + case Round_up: + if (!sign) + goto ret_tiny; + break; + case Round_down: + if (sign) + goto ret_tiny; + } #endif /* } IEEE_Arith */ - Bfree(b); + Bfree(b); retz: #ifndef NO_ERRNO - errno = ERANGE; + errno = ERANGE; #endif retz1: - rvp->d = 0.; - return; - } - k = n - 1; - if (lostbits) - lostbits = 1; - else if (k > 0) - lostbits = any_on(b,k); - if (x[k>>kshift] & 1 << (k & kmask)) - lostbits |= 2; - nbits -= n; - rshift(b,n); - e = emin; - } - if (lostbits) { - up = 0; - switch(rounding) { - case Round_zero: - break; - case Round_near: - if (lostbits & 2 - && (lostbits & 1) | (x[0] & 1)) - up = 1; - break; - case Round_up: - up = 1 - sign; - break; - case Round_down: - up = sign; - } - if (up) { - k = b->wds; - b = increment(b); - x = b->x; - if (denorm) { + rvp->d = 0.; + return; + } + k = n - 1; + if (lostbits) + lostbits = 1; + else if (k > 0) + lostbits = any_on(b,k); + if (x[k>>kshift] & 1 << (k & kmask)) + lostbits |= 2; + nbits -= n; + rshift(b,n); + e = emin; + } + if (lostbits) { + up = 0; + switch(rounding) { + case Round_zero: + break; + case Round_near: + if (lostbits & 2 + && (lostbits & 1) | (x[0] & 1)) + up = 1; + break; + case Round_up: + up = 1 - sign; + break; + case Round_down: + up = sign; + } + if (up) { + k = b->wds; + b = increment(b); + x = b->x; + if (denorm) { #if 0 - if (nbits == Nbits - 1 - && x[nbits >> kshift] & 1 << (nbits & kmask)) - denorm = 0; /* not currently used */ -#endif - } - else if (b->wds > k - || ((n = nbits & kmask) !=0 - && hi0bits(x[k-1]) < 32-n)) { - rshift(b,1); - if (++e > Emax) - goto ovfl; - } - } - } + if (nbits == Nbits - 1 + && x[nbits >> kshift] & 1 << (nbits & kmask)) + denorm = 0; /* not currently used */ +#endif + } + else if (b->wds > k + || ((n = nbits & kmask) !=0 + && hi0bits(x[k-1]) < 32-n)) { + rshift(b,1); + if (++e > Emax) + goto ovfl; + } + } + } #ifdef IEEE_Arith - if (denorm) - word0(rvp) = b->wds > 1 ? b->x[1] & ~0x100000 : 0; - else - word0(rvp) = (b->x[1] & ~0x100000) | ((e + 0x3ff + 52) << 20); - word1(rvp) = b->x[0]; + if (denorm) + word0(rvp) = b->wds > 1 ? b->x[1] & ~0x100000 : 0; + else + word0(rvp) = (b->x[1] & ~0x100000) | ((e + 0x3ff + 52) << 20); + word1(rvp) = b->x[0]; #endif #ifdef IBM - if ((j = e & 3)) { - k = b->x[0] & ((1 << j) - 1); - rshift(b,j); - if (k) { - switch(rounding) { - case Round_up: - if (!sign) - increment(b); - break; - case Round_down: - if (sign) - increment(b); - break; - case Round_near: - j = 1 << (j-1); - if (k & j && ((k & (j-1)) | lostbits)) - increment(b); - } - } - } - e >>= 2; - word0(rvp) = b->x[1] | ((e + 65 + 13) << 24); - word1(rvp) = b->x[0]; + if ((j = e & 3)) { + k = b->x[0] & ((1 << j) - 1); + rshift(b,j); + if (k) { + switch(rounding) { + case Round_up: + if (!sign) + increment(b); + break; + case Round_down: + if (sign) + increment(b); + break; + case Round_near: + j = 1 << (j-1); + if (k & j && ((k & (j-1)) | lostbits)) + increment(b); + } + } + } + e >>= 2; + word0(rvp) = b->x[1] | ((e + 65 + 13) << 24); + word1(rvp) = b->x[0]; #endif #ifdef VAX - /* The next two lines ignore swap of low- and high-order 2 bytes. */ - /* word0(rvp) = (b->x[1] & ~0x800000) | ((e + 129 + 55) << 23); */ - /* word1(rvp) = b->x[0]; */ - word0(rvp) = ((b->x[1] & ~0x800000) >> 16) | ((e + 129 + 55) << 7) | (b->x[1] << 16); - word1(rvp) = (b->x[0] >> 16) | (b->x[0] << 16); -#endif - Bfree(b); - } + /* The next two lines ignore swap of low- and high-order 2 bytes. */ + /* word0(rvp) = (b->x[1] & ~0x800000) | ((e + 129 + 55) << 23); */ + /* word1(rvp) = b->x[0]; */ + word0(rvp) = ((b->x[1] & ~0x800000) >> 16) | ((e + 129 + 55) << 7) | (b->x[1] << 16); + word1(rvp) = (b->x[0] >> 16) | (b->x[0] << 16); +#endif + Bfree(b); + } #endif /*!NO_HEX_FP}*/ static int @@ -2126,1440 +2133,1440 @@ dshift(b, p2) Bigint *b; int p2; dshift(Bigint *b, int p2) #endif { - int rv = hi0bits(b->x[b->wds-1]) - 4; - if (p2 > 0) - rv -= p2; - return rv & kmask; - } + int rv = hi0bits(b->x[b->wds-1]) - 4; + if (p2 > 0) + rv -= p2; + return rv & kmask; + } static int quorem #ifdef KR_headers - (b, S) Bigint *b, *S; + (b, S) Bigint *b, *S; #else - (Bigint *b, Bigint *S) + (Bigint *b, Bigint *S) #endif { - int n; - ULong *bx, *bxe, q, *sx, *sxe; + int n; + ULong *bx, *bxe, q, *sx, *sxe; #ifdef ULLong - ULLong borrow, carry, y, ys; + ULLong borrow, carry, y, ys; #else - ULong borrow, carry, y, ys; + ULong borrow, carry, y, ys; #ifdef Pack_32 - ULong si, z, zs; + ULong si, z, zs; #endif #endif - n = S->wds; + n = S->wds; #ifdef DEBUG - /*debug*/ if (b->wds > n) - /*debug*/ Bug("oversize b in quorem"); -#endif - if (b->wds < n) - return 0; - sx = S->x; - sxe = sx + --n; - bx = b->x; - bxe = bx + n; - q = *bxe / (*sxe + 1); /* ensure q <= true quotient */ + /*debug*/ if (b->wds > n) + /*debug*/ Bug("oversize b in quorem"); +#endif + if (b->wds < n) + return 0; + sx = S->x; + sxe = sx + --n; + bx = b->x; + bxe = bx + n; + q = *bxe / (*sxe + 1); /* ensure q <= true quotient */ #ifdef DEBUG #ifdef NO_STRTOD_BIGCOMP - /*debug*/ if (q > 9) + /*debug*/ if (q > 9) #else - /* An oversized q is possible when quorem is called from bigcomp and */ - /* the input is near, e.g., twice the smallest denormalized number. */ - /*debug*/ if (q > 15) + /* An oversized q is possible when quorem is called from bigcomp and */ + /* the input is near, e.g., twice the smallest denormalized number. */ + /*debug*/ if (q > 15) #endif - /*debug*/ Bug("oversized quotient in quorem"); + /*debug*/ Bug("oversized quotient in quorem"); #endif - if (q) { - borrow = 0; - carry = 0; - do { + if (q) { + borrow = 0; + carry = 0; + do { #ifdef ULLong - ys = *sx++ * (ULLong)q + carry; - carry = ys >> 32; - y = *bx - (ys & FFFFFFFF) - borrow; - borrow = y >> 32 & (ULong)1; - *bx++ = y & FFFFFFFF; + ys = *sx++ * (ULLong)q + carry; + carry = ys >> 32; + y = *bx - (ys & FFFFFFFF) - borrow; + borrow = y >> 32 & (ULong)1; + *bx++ = y & FFFFFFFF; #else #ifdef Pack_32 - si = *sx++; - ys = (si & 0xffff) * q + carry; - zs = (si >> 16) * q + (ys >> 16); - carry = zs >> 16; - y = (*bx & 0xffff) - (ys & 0xffff) - borrow; - borrow = (y & 0x10000) >> 16; - z = (*bx >> 16) - (zs & 0xffff) - borrow; - borrow = (z & 0x10000) >> 16; - Storeinc(bx, z, y); + si = *sx++; + ys = (si & 0xffff) * q + carry; + zs = (si >> 16) * q + (ys >> 16); + carry = zs >> 16; + y = (*bx & 0xffff) - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*bx >> 16) - (zs & 0xffff) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(bx, z, y); #else - ys = *sx++ * q + carry; - carry = ys >> 16; - y = *bx - (ys & 0xffff) - borrow; - borrow = (y & 0x10000) >> 16; - *bx++ = y & 0xffff; -#endif -#endif - } - while(sx <= sxe); - if (!*bxe) { - bx = b->x; - while(--bxe > bx && !*bxe) - --n; - b->wds = n; - } - } - if (cmp(b, S) >= 0) { - q++; - borrow = 0; - carry = 0; - bx = b->x; - sx = S->x; - do { + ys = *sx++ * q + carry; + carry = ys >> 16; + y = *bx - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + *bx++ = y & 0xffff; +#endif +#endif + } + while(sx <= sxe); + if (!*bxe) { + bx = b->x; + while(--bxe > bx && !*bxe) + --n; + b->wds = n; + } + } + if (cmp(b, S) >= 0) { + q++; + borrow = 0; + carry = 0; + bx = b->x; + sx = S->x; + do { #ifdef ULLong - ys = *sx++ + carry; - carry = ys >> 32; - y = *bx - (ys & FFFFFFFF) - borrow; - borrow = y >> 32 & (ULong)1; - *bx++ = y & FFFFFFFF; + ys = *sx++ + carry; + carry = ys >> 32; + y = *bx - (ys & FFFFFFFF) - borrow; + borrow = y >> 32 & (ULong)1; + *bx++ = y & FFFFFFFF; #else #ifdef Pack_32 - si = *sx++; - ys = (si & 0xffff) + carry; - zs = (si >> 16) + (ys >> 16); - carry = zs >> 16; - y = (*bx & 0xffff) - (ys & 0xffff) - borrow; - borrow = (y & 0x10000) >> 16; - z = (*bx >> 16) - (zs & 0xffff) - borrow; - borrow = (z & 0x10000) >> 16; - Storeinc(bx, z, y); + si = *sx++; + ys = (si & 0xffff) + carry; + zs = (si >> 16) + (ys >> 16); + carry = zs >> 16; + y = (*bx & 0xffff) - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*bx >> 16) - (zs & 0xffff) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(bx, z, y); #else - ys = *sx++ + carry; - carry = ys >> 16; - y = *bx - (ys & 0xffff) - borrow; - borrow = (y & 0x10000) >> 16; - *bx++ = y & 0xffff; -#endif -#endif - } - while(sx <= sxe); - bx = b->x; - bxe = bx + n; - if (!*bxe) { - while(--bxe > bx && !*bxe) - --n; - b->wds = n; - } - } - return q; - } + ys = *sx++ + carry; + carry = ys >> 16; + y = *bx - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + *bx++ = y & 0xffff; +#endif +#endif + } + while(sx <= sxe); + bx = b->x; + bxe = bx + n; + if (!*bxe) { + while(--bxe > bx && !*bxe) + --n; + b->wds = n; + } + } + return q; + } #if defined(Avoid_Underflow) || !defined(NO_STRTOD_BIGCOMP) /*{*/ static double sulp #ifdef KR_headers - (x, bc) U *x; BCinfo *bc; + (x, bc) U *x; BCinfo *bc; #else - (U *x, BCinfo *bc) + (U *x, BCinfo *bc) #endif { - U u; - double rv; - int i; - - rv = ulp(x); - if (!bc->scale || (i = 2*P + 1 - ((word0(x) & Exp_mask) >> Exp_shift)) <= 0) - return rv; /* Is there an example where i <= 0 ? */ - word0(&u) = Exp_1 + (i << Exp_shift); - word1(&u) = 0; - return rv * u.d; - } + U u; + double rv; + int i; + + rv = ulp(x); + if (!bc->scale || (i = 2*P + 1 - ((word0(x) & Exp_mask) >> Exp_shift)) <= 0) + return rv; /* Is there an example where i <= 0 ? */ + word0(&u) = Exp_1 + (i << Exp_shift); + word1(&u) = 0; + return rv * u.d; + } #endif /*}*/ #ifndef NO_STRTOD_BIGCOMP static void bigcomp #ifdef KR_headers - (rv, s0, bc) - U *rv; CONST char *s0; BCinfo *bc; + (rv, s0, bc) + U *rv; CONST char *s0; BCinfo *bc; #else - (U *rv, const char *s0, BCinfo *bc) + (U *rv, const char *s0, BCinfo *bc) #endif { - Bigint *b, *d; - int b2, bbits, d2, dd, dig, dsign, i, j, nd, nd0, p2, p5, speccase; - - dsign = bc->dsign; - nd = bc->nd; - nd0 = bc->nd0; - p5 = nd + bc->e0 - 1; - speccase = 0; + Bigint *b, *d; + int b2, bbits, d2, dd, dig, dsign, i, j, nd, nd0, p2, p5, speccase; + + dsign = bc->dsign; + nd = bc->nd; + nd0 = bc->nd0; + p5 = nd + bc->e0 - 1; + speccase = 0; #ifndef Sudden_Underflow - if (rv->d == 0.) { /* special case: value near underflow-to-zero */ - /* threshold was rounded to zero */ - b = i2b(1); - p2 = Emin - P + 1; - bbits = 1; + if (rv->d == 0.) { /* special case: value near underflow-to-zero */ + /* threshold was rounded to zero */ + b = i2b(1); + p2 = Emin - P + 1; + bbits = 1; #ifdef Avoid_Underflow - word0(rv) = (P+2) << Exp_shift; + word0(rv) = (P+2) << Exp_shift; #else - word1(rv) = 1; + word1(rv) = 1; #endif - i = 0; + i = 0; #ifdef Honor_FLT_ROUNDS - if (bc->rounding == 1) -#endif - { - speccase = 1; - --p2; - dsign = 0; - goto have_i; - } - } - else -#endif - b = d2b(rv, &p2, &bbits); + if (bc->rounding == 1) +#endif + { + speccase = 1; + --p2; + dsign = 0; + goto have_i; + } + } + else +#endif + b = d2b(rv, &p2, &bbits); #ifdef Avoid_Underflow - p2 -= bc->scale; + p2 -= bc->scale; #endif - /* floor(log2(rv)) == bbits - 1 + p2 */ - /* Check for denormal case. */ - i = P - bbits; - if (i > (j = P - Emin - 1 + p2)) { + /* floor(log2(rv)) == bbits - 1 + p2 */ + /* Check for denormal case. */ + i = P - bbits; + if (i > (j = P - Emin - 1 + p2)) { #ifdef Sudden_Underflow - Bfree(b); - b = i2b(1); - p2 = Emin; - i = P - 1; + Bfree(b); + b = i2b(1); + p2 = Emin; + i = P - 1; #ifdef Avoid_Underflow - word0(rv) = (1 + bc->scale) << Exp_shift; + word0(rv) = (1 + bc->scale) << Exp_shift; #else - word0(rv) = Exp_msk1; + word0(rv) = Exp_msk1; #endif - word1(rv) = 0; + word1(rv) = 0; #else - i = j; + i = j; #endif - } + } #ifdef Honor_FLT_ROUNDS - if (bc->rounding != 1) { - if (i > 0) - b = lshift(b, i); - if (dsign) - b = increment(b); - } - else -#endif - { - b = lshift(b, ++i); - b->x[0] |= 1; - } + if (bc->rounding != 1) { + if (i > 0) + b = lshift(b, i); + if (dsign) + b = increment(b); + } + else +#endif + { + b = lshift(b, ++i); + b->x[0] |= 1; + } #ifndef Sudden_Underflow have_i: #endif - p2 -= p5 + i; - d = i2b(1); - /* Arrange for convenient computation of quotients: - * shift left if necessary so divisor has 4 leading 0 bits. - */ - if (p5 > 0) - d = pow5mult(d, p5); - else if (p5 < 0) - b = pow5mult(b, -p5); - if (p2 > 0) { - b2 = p2; - d2 = 0; - } - else { - b2 = 0; - d2 = -p2; - } - i = dshift(d, d2); - if ((b2 += i) > 0) - b = lshift(b, b2); - if ((d2 += i) > 0) - d = lshift(d, d2); - - /* Now b/d = exactly half-way between the two floating-point values */ - /* on either side of the input string. Compute first digit of b/d. */ - - if (!(dig = quorem(b,d))) { - b = multadd(b, 10, 0); /* very unlikely */ - dig = quorem(b,d); - } - - /* Compare b/d with s0 */ - - for(i = 0; i < nd0; ) { - if ((dd = s0[i++] - '0' - dig)) - goto ret; - if (!b->x[0] && b->wds == 1) { - if (i < nd) - dd = 1; - goto ret; - } - b = multadd(b, 10, 0); - dig = quorem(b,d); - } - for(j = bc->dp1; i++ < nd;) { - if ((dd = s0[j++] - '0' - dig)) - goto ret; - if (!b->x[0] && b->wds == 1) { - if (i < nd) - dd = 1; - goto ret; - } - b = multadd(b, 10, 0); - dig = quorem(b,d); - } - if (dig > 0 || b->x[0] || b->wds > 1) - dd = -1; + p2 -= p5 + i; + d = i2b(1); + /* Arrange for convenient computation of quotients: + * shift left if necessary so divisor has 4 leading 0 bits. + */ + if (p5 > 0) + d = pow5mult(d, p5); + else if (p5 < 0) + b = pow5mult(b, -p5); + if (p2 > 0) { + b2 = p2; + d2 = 0; + } + else { + b2 = 0; + d2 = -p2; + } + i = dshift(d, d2); + if ((b2 += i) > 0) + b = lshift(b, b2); + if ((d2 += i) > 0) + d = lshift(d, d2); + + /* Now b/d = exactly half-way between the two floating-point values */ + /* on either side of the input string. Compute first digit of b/d. */ + + if (!(dig = quorem(b,d))) { + b = multadd(b, 10, 0); /* very unlikely */ + dig = quorem(b,d); + } + + /* Compare b/d with s0 */ + + for(i = 0; i < nd0; ) { + if ((dd = s0[i++] - '0' - dig)) + goto ret; + if (!b->x[0] && b->wds == 1) { + if (i < nd) + dd = 1; + goto ret; + } + b = multadd(b, 10, 0); + dig = quorem(b,d); + } + for(j = bc->dp1; i++ < nd;) { + if ((dd = s0[j++] - '0' - dig)) + goto ret; + if (!b->x[0] && b->wds == 1) { + if (i < nd) + dd = 1; + goto ret; + } + b = multadd(b, 10, 0); + dig = quorem(b,d); + } + if (dig > 0 || b->x[0] || b->wds > 1) + dd = -1; ret: - Bfree(b); - Bfree(d); + Bfree(b); + Bfree(d); #ifdef Honor_FLT_ROUNDS - if (bc->rounding != 1) { - if (dd < 0) { - if (bc->rounding == 0) { - if (!dsign) - goto retlow1; - } - else if (dsign) - goto rethi1; - } - else if (dd > 0) { - if (bc->rounding == 0) { - if (dsign) - goto rethi1; - goto ret1; - } - if (!dsign) - goto rethi1; - dval(rv) += 2.*sulp(rv,bc); - } - else { - bc->inexact = 0; - if (dsign) - goto rethi1; - } - } - else -#endif - if (speccase) { - if (dd <= 0) - rv->d = 0.; - } - else if (dd < 0) { - if (!dsign) /* does not happen for round-near */ + if (bc->rounding != 1) { + if (dd < 0) { + if (bc->rounding == 0) { + if (!dsign) + goto retlow1; + } + else if (dsign) + goto rethi1; + } + else if (dd > 0) { + if (bc->rounding == 0) { + if (dsign) + goto rethi1; + goto ret1; + } + if (!dsign) + goto rethi1; + dval(rv) += 2.*sulp(rv,bc); + } + else { + bc->inexact = 0; + if (dsign) + goto rethi1; + } + } + else +#endif + if (speccase) { + if (dd <= 0) + rv->d = 0.; + } + else if (dd < 0) { + if (!dsign) /* does not happen for round-near */ retlow1: - dval(rv) -= sulp(rv,bc); - } - else if (dd > 0) { - if (dsign) { + dval(rv) -= sulp(rv,bc); + } + else if (dd > 0) { + if (dsign) { rethi1: - dval(rv) += sulp(rv,bc); - } - } - else { - /* Exact half-way case: apply round-even rule. */ - if ((j = ((word0(rv) & Exp_mask) >> Exp_shift) - bc->scale) <= 0) { - i = 1 - j; - if (i <= 31) { - if (word1(rv) & (0x1 << i)) - goto odd; - } - else if (word0(rv) & (0x1 << (i-32))) - goto odd; - } - else if (word1(rv) & 1) { + dval(rv) += sulp(rv,bc); + } + } + else { + /* Exact half-way case: apply round-even rule. */ + if ((j = ((word0(rv) & Exp_mask) >> Exp_shift) - bc->scale) <= 0) { + i = 1 - j; + if (i <= 31) { + if (word1(rv) & (0x1 << i)) + goto odd; + } + else if (word0(rv) & (0x1 << (i-32))) + goto odd; + } + else if (word1(rv) & 1) { odd: - if (dsign) - goto rethi1; - goto retlow1; - } - } + if (dsign) + goto rethi1; + goto retlow1; + } + } #ifdef Honor_FLT_ROUNDS ret1: #endif - return; - } + return; + } #endif /* NO_STRTOD_BIGCOMP */ double strtod #ifdef KR_headers - (s00, se) CONST char *s00; char **se; + (s00, se) CONST char *s00; const char **se; // Ren/C: fix cast away of const #else - (const char *s00, char **se) + (const char *s00, const char **se) // Ren/C: fix cast away of const #endif { - int bb2, bb5, bbe, bd2, bd5, bbbits, bs2, c, e, e1; - int esign, i, j, k, nd, nd0, nf, nz, nz0, nz1, sign; - CONST char *s, *s0, *s1; - double aadj, aadj1; - Long L; - U aadj2, adj, rv, rv0; - ULong y, z; - BCinfo bc; - Bigint *bb, *bb1, *bd, *bd0, *bs, *delta; + int bb2, bb5, bbe, bd2, bd5, bbbits, bs2, c, e, e1; + int esign, i, j, k, nd, nd0, nf, nz, nz0, nz1, sign; + CONST char *s, *s0, *s1; + double aadj, aadj1; + Long L; + U aadj2, adj, rv, rv0; + ULong y, z; + BCinfo bc; + Bigint *bb, *bb1, *bd, *bd0, *bs, *delta; #ifdef Avoid_Underflow - ULong Lsb, Lsb1; + ULong Lsb, Lsb1; #endif #ifdef SET_INEXACT - int oldinexact; + int oldinexact; #endif #ifndef NO_STRTOD_BIGCOMP - int req_bigcomp = 0; + int req_bigcomp = 0; #endif #ifdef Honor_FLT_ROUNDS /*{*/ #ifdef Trust_FLT_ROUNDS /*{{ only define this if FLT_ROUNDS really works! */ - bc.rounding = Flt_Rounds; + bc.rounding = Flt_Rounds; #else /*}{*/ - bc.rounding = 1; - switch(fegetround()) { - case FE_TOWARDZERO: bc.rounding = 0; break; - case FE_UPWARD: bc.rounding = 2; break; - case FE_DOWNWARD: bc.rounding = 3; - } + bc.rounding = 1; + switch(fegetround()) { + case FE_TOWARDZERO: bc.rounding = 0; break; + case FE_UPWARD: bc.rounding = 2; break; + case FE_DOWNWARD: bc.rounding = 3; + } #endif /*}}*/ #endif /*}*/ #ifdef USE_LOCALE - CONST char *s2; -#endif - - sign = nz0 = nz1 = nz = bc.dplen = bc.uflchk = 0; - dval(&rv) = 0.; - for(s = s00;;s++) switch(*s) { - case '-': - sign = 1; - /* no break */ - case '+': - if (*++s) - goto break2; - /* no break */ - case 0: - goto ret0; - case '\t': - case '\n': - case '\v': - case '\f': - case '\r': - case ' ': - continue; - default: - goto break2; - } + CONST char *s2; +#endif + + sign = nz0 = nz1 = nz = bc.dplen = bc.uflchk = 0; + dval(&rv) = 0.; + for(s = s00;;s++) switch(*s) { + case '-': + sign = 1; + /* no break */ + case '+': + if (*++s) + goto break2; + /* no break */ + case 0: + goto ret0; + case '\t': + case '\n': + case '\v': + case '\f': + case '\r': + case ' ': + continue; + default: + goto break2; + } break2: - if (*s == '0') { + if (*s == '0') { #ifndef NO_HEX_FP /*{*/ - switch(s[1]) { - case 'x': - case 'X': + switch(s[1]) { + case 'x': + case 'X': #ifdef Honor_FLT_ROUNDS - gethex(&s, &rv, bc.rounding, sign); + gethex(&s, &rv, bc.rounding, sign); #else - gethex(&s, &rv, 1, sign); + gethex(&s, &rv, 1, sign); #endif - goto ret; - } + goto ret; + } #endif /*}*/ - nz0 = 1; - while(*++s == '0') ; - if (!*s) - goto ret; - } - s0 = s; - y = z = 0; - for(nd = nf = 0; (c = *s) >= '0' && c <= '9'; nd++, s++) - if (nd < 9) - y = 10*y + c - '0'; - else if (nd < 16) - z = 10*z + c - '0'; - nd0 = nd; - bc.dp0 = bc.dp1 = s - s0; - for(s1 = s; s1 > s0 && *--s1 == '0'; ) - ++nz1; + nz0 = 1; + while(*++s == '0') ; + if (!*s) + goto ret; + } + s0 = s; + y = z = 0; + for(nd = nf = 0; (c = *s) >= '0' && c <= '9'; nd++, s++) + if (nd < 9) + y = 10*y + c - '0'; + else if (nd < 16) + z = 10*z + c - '0'; + nd0 = nd; + bc.dp0 = bc.dp1 = s - s0; + for(s1 = s; s1 > s0 && *--s1 == '0'; ) + ++nz1; #ifdef USE_LOCALE - s1 = localeconv()->decimal_point; - if (c == *s1) { - c = '.'; - if (*++s1) { - s2 = s; - for(;;) { - if (*++s2 != *s1) { - c = 0; - break; - } - if (!*++s1) { - s = s2; - break; - } - } - } - } -#endif - if (c == '.') { - c = *++s; - bc.dp1 = s - s0; - bc.dplen = bc.dp1 - bc.dp0; - if (!nd) { - for(; c == '0'; c = *++s) - nz++; - if (c > '0' && c <= '9') { - bc.dp0 = s0 - s; - bc.dp1 = bc.dp0 + bc.dplen; - s0 = s; - nf += nz; - nz = 0; - goto have_dig; - } - goto dig_done; - } - for(; c >= '0' && c <= '9'; c = *++s) { + s1 = localeconv()->decimal_point; + if (c == *s1) { + c = '.'; + if (*++s1) { + s2 = s; + for(;;) { + if (*++s2 != *s1) { + c = 0; + break; + } + if (!*++s1) { + s = s2; + break; + } + } + } + } +#endif + if (c == '.') { + c = *++s; + bc.dp1 = s - s0; + bc.dplen = bc.dp1 - bc.dp0; + if (!nd) { + for(; c == '0'; c = *++s) + nz++; + if (c > '0' && c <= '9') { + bc.dp0 = s0 - s; + bc.dp1 = bc.dp0 + bc.dplen; + s0 = s; + nf += nz; + nz = 0; + goto have_dig; + } + goto dig_done; + } + for(; c >= '0' && c <= '9'; c = *++s) { have_dig: - nz++; - if (c -= '0') { - nf += nz; - for(i = 1; i < nz; i++) - if (nd++ < 9) - y *= 10; - else if (nd <= DBL_DIG + 1) - z *= 10; - if (nd++ < 9) - y = 10*y + c; - else if (nd <= DBL_DIG + 1) - z = 10*z + c; - nz = nz1 = 0; - } - } - } + nz++; + if (c -= '0') { + nf += nz; + for(i = 1; i < nz; i++) + if (nd++ < 9) + y *= 10; + else if (nd <= DBL_DIG + 1) + z *= 10; + if (nd++ < 9) + y = 10*y + c; + else if (nd <= DBL_DIG + 1) + z = 10*z + c; + nz = nz1 = 0; + } + } + } dig_done: - e = 0; - if (c == 'e' || c == 'E') { - if (!nd && !nz && !nz0) { - goto ret0; - } - s00 = s; - esign = 0; - switch(c = *++s) { - case '-': - esign = 1; - case '+': - c = *++s; - } - if (c >= '0' && c <= '9') { - while(c == '0') - c = *++s; - if (c > '0' && c <= '9') { - L = c - '0'; - s1 = s; - while((c = *++s) >= '0' && c <= '9') - L = 10*L + c - '0'; - if (s - s1 > 8 || L > 19999) - /* Avoid confusion from exponents - * so large that e might overflow. - */ - e = 19999; /* safe for 16 bit ints */ - else - e = (int)L; - if (esign) - e = -e; - } - else - e = 0; - } - else - s = s00; - } - if (!nd) { - if (!nz && !nz0) { + e = 0; + if (c == 'e' || c == 'E') { + if (!nd && !nz && !nz0) { + goto ret0; + } + s00 = s; + esign = 0; + switch(c = *++s) { + case '-': + esign = 1; + case '+': + c = *++s; + } + if (c >= '0' && c <= '9') { + while(c == '0') + c = *++s; + if (c > '0' && c <= '9') { + L = c - '0'; + s1 = s; + while((c = *++s) >= '0' && c <= '9') + L = 10*L + c - '0'; + if (s - s1 > 8 || L > 19999) + /* Avoid confusion from exponents + * so large that e might overflow. + */ + e = 19999; /* safe for 16 bit ints */ + else + e = (int)L; + if (esign) + e = -e; + } + else + e = 0; + } + else + s = s00; + } + if (!nd) { + if (!nz && !nz0) { #ifdef INFNAN_CHECK - /* Check for Nan and Infinity */ - if (!bc.dplen) - switch(c) { - case 'i': - case 'I': - if (match(&s,"nf")) { - --s; - if (!match(&s,"inity")) - ++s; - word0(&rv) = 0x7ff00000; - word1(&rv) = 0; - goto ret; - } - break; - case 'n': - case 'N': - if (match(&s, "an")) { - word0(&rv) = NAN_WORD0; - word1(&rv) = NAN_WORD1; + /* Check for Nan and Infinity */ + if (!bc.dplen) + switch(c) { + case 'i': + case 'I': + if (match(&s,"nf")) { + --s; + if (!match(&s,"inity")) + ++s; + word0(&rv) = 0x7ff00000; + word1(&rv) = 0; + goto ret; + } + break; + case 'n': + case 'N': + if (match(&s, "an")) { + word0(&rv) = NAN_WORD0; + word1(&rv) = NAN_WORD1; #ifndef No_Hex_NaN - if (*s == '(') /*)*/ - hexnan(&rv, &s); + if (*s == '(') /*)*/ + hexnan(&rv, &s); #endif - goto ret; - } - } + goto ret; + } + } #endif /* INFNAN_CHECK */ ret0: - s = s00; - sign = 0; - } - goto ret; - } - bc.e0 = e1 = e -= nf; - - /* Now we have nd0 digits, starting at s0, followed by a - * decimal point, followed by nd-nd0 digits. The number we're - * after is the integer represented by those digits times - * 10**e */ - - if (!nd0) - nd0 = nd; - k = nd < DBL_DIG + 1 ? nd : DBL_DIG + 1; - dval(&rv) = y; - if (k > 9) { + s = s00; + sign = 0; + } + goto ret; + } + bc.e0 = e1 = e -= nf; + + /* Now we have nd0 digits, starting at s0, followed by a + * decimal point, followed by nd-nd0 digits. The number we're + * after is the integer represented by those digits times + * 10**e */ + + if (!nd0) + nd0 = nd; + k = nd < DBL_DIG + 1 ? nd : DBL_DIG + 1; + dval(&rv) = y; + if (k > 9) { #ifdef SET_INEXACT - if (k > DBL_DIG) - oldinexact = get_inexact(); + if (k > DBL_DIG) + oldinexact = get_inexact(); #endif - dval(&rv) = tens[k - 9] * dval(&rv) + z; - } - bd0 = 0; - if (nd <= DBL_DIG + dval(&rv) = tens[k - 9] * dval(&rv) + z; + } + bd0 = 0; + if (nd <= DBL_DIG #ifndef RND_PRODQUOT #ifndef Honor_FLT_ROUNDS - && Flt_Rounds == 1 + && Flt_Rounds == 1 #endif #endif - ) { - if (!e) - goto ret; + ) { + if (!e) + goto ret; #ifndef ROUND_BIASED_without_Round_Up - if (e > 0) { - if (e <= Ten_pmax) { + if (e > 0) { + if (e <= Ten_pmax) { #ifdef VAX - goto vax_ovfl_check; + goto vax_ovfl_check; #else #ifdef Honor_FLT_ROUNDS - /* round correctly FLT_ROUNDS = 2 or 3 */ - if (sign) { - rv.d = -rv.d; - sign = 0; - } -#endif - /* rv = */ rounded_product(dval(&rv), tens[e]); - goto ret; -#endif - } - i = DBL_DIG - nd; - if (e <= Ten_pmax + i) { - /* A fancier test would sometimes let us do - * this for larger i values. - */ + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + rv.d = -rv.d; + sign = 0; + } +#endif + /* rv = */ rounded_product(dval(&rv), tens[e]); + goto ret; +#endif + } + i = DBL_DIG - nd; + if (e <= Ten_pmax + i) { + /* A fancier test would sometimes let us do + * this for larger i values. + */ #ifdef Honor_FLT_ROUNDS - /* round correctly FLT_ROUNDS = 2 or 3 */ - if (sign) { - rv.d = -rv.d; - sign = 0; - } -#endif - e -= i; - dval(&rv) *= tens[i]; + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + rv.d = -rv.d; + sign = 0; + } +#endif + e -= i; + dval(&rv) *= tens[i]; #ifdef VAX - /* VAX exponent range is so narrow we must - * worry about overflow here... - */ + /* VAX exponent range is so narrow we must + * worry about overflow here... + */ vax_ovfl_check: - word0(&rv) -= P*Exp_msk1; - /* rv = */ rounded_product(dval(&rv), tens[e]); - if ((word0(&rv) & Exp_mask) - > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) - goto ovfl; - word0(&rv) += P*Exp_msk1; + word0(&rv) -= P*Exp_msk1; + /* rv = */ rounded_product(dval(&rv), tens[e]); + if ((word0(&rv) & Exp_mask) + > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) + goto ovfl; + word0(&rv) += P*Exp_msk1; #else - /* rv = */ rounded_product(dval(&rv), tens[e]); + /* rv = */ rounded_product(dval(&rv), tens[e]); #endif - goto ret; - } - } + goto ret; + } + } #ifndef Inaccurate_Divide - else if (e >= -Ten_pmax) { + else if (e >= -Ten_pmax) { #ifdef Honor_FLT_ROUNDS - /* round correctly FLT_ROUNDS = 2 or 3 */ - if (sign) { - rv.d = -rv.d; - sign = 0; - } + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + rv.d = -rv.d; + sign = 0; + } #endif - /* rv = */ rounded_quotient(dval(&rv), tens[-e]); - goto ret; - } + /* rv = */ rounded_quotient(dval(&rv), tens[-e]); + goto ret; + } #endif #endif /* ROUND_BIASED_without_Round_Up */ - } - e1 += nd - k; + } + e1 += nd - k; #ifdef IEEE_Arith #ifdef SET_INEXACT - bc.inexact = 1; - if (k <= DBL_DIG) - oldinexact = get_inexact(); + bc.inexact = 1; + if (k <= DBL_DIG) + oldinexact = get_inexact(); #endif #ifdef Avoid_Underflow - bc.scale = 0; + bc.scale = 0; #endif #ifdef Honor_FLT_ROUNDS - if (bc.rounding >= 2) { - if (sign) - bc.rounding = bc.rounding == 2 ? 0 : 2; - else - if (bc.rounding != 2) - bc.rounding = 0; - } + if (bc.rounding >= 2) { + if (sign) + bc.rounding = bc.rounding == 2 ? 0 : 2; + else + if (bc.rounding != 2) + bc.rounding = 0; + } #endif #endif /*IEEE_Arith*/ - /* Get starting approximation = rv * 10**e1 */ + /* Get starting approximation = rv * 10**e1 */ - if (e1 > 0) { - if ((i = e1 & 15)) - dval(&rv) *= tens[i]; - if (e1 &= ~15) { - if (e1 > DBL_MAX_10_EXP) { + if (e1 > 0) { + if ((i = e1 & 15)) + dval(&rv) *= tens[i]; + if (e1 &= ~15) { + if (e1 > DBL_MAX_10_EXP) { ovfl: - /* Can't trust HUGE_VAL */ + /* Can't trust HUGE_VAL */ #ifdef IEEE_Arith #ifdef Honor_FLT_ROUNDS - switch(bc.rounding) { - case 0: /* toward 0 */ - case 3: /* toward -infinity */ - word0(&rv) = Big0; - word1(&rv) = Big1; - break; - default: - word0(&rv) = Exp_mask; - word1(&rv) = 0; - } + switch(bc.rounding) { + case 0: /* toward 0 */ + case 3: /* toward -infinity */ + word0(&rv) = Big0; + word1(&rv) = Big1; + break; + default: + word0(&rv) = Exp_mask; + word1(&rv) = 0; + } #else /*Honor_FLT_ROUNDS*/ - word0(&rv) = Exp_mask; - word1(&rv) = 0; + word0(&rv) = Exp_mask; + word1(&rv) = 0; #endif /*Honor_FLT_ROUNDS*/ #ifdef SET_INEXACT - /* set overflow bit */ - dval(&rv0) = 1e300; - dval(&rv0) *= dval(&rv0); + /* set overflow bit */ + dval(&rv0) = 1e300; + dval(&rv0) *= dval(&rv0); #endif #else /*IEEE_Arith*/ - word0(&rv) = Big0; - word1(&rv) = Big1; + word0(&rv) = Big0; + word1(&rv) = Big1; #endif /*IEEE_Arith*/ range_err: - if (bd0) { - Bfree(bb); - Bfree(bd); - Bfree(bs); - Bfree(bd0); - Bfree(delta); - } + if (bd0) { + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(bd0); + Bfree(delta); + } #ifndef NO_ERRNO - errno = ERANGE; -#endif - goto ret; - } - e1 >>= 4; - for(j = 0; e1 > 1; j++, e1 >>= 1) - if (e1 & 1) - dval(&rv) *= bigtens[j]; - /* The last multiplication could overflow. */ - word0(&rv) -= P*Exp_msk1; - dval(&rv) *= bigtens[j]; - if ((z = word0(&rv) & Exp_mask) - > Exp_msk1*(DBL_MAX_EXP+Bias-P)) - goto ovfl; - if (z > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) { - /* set to largest number */ - /* (Can't trust DBL_MAX) */ - word0(&rv) = Big0; - word1(&rv) = Big1; - } - else - word0(&rv) += P*Exp_msk1; - } - } - else if (e1 < 0) { - e1 = -e1; - if ((i = e1 & 15)) - dval(&rv) /= tens[i]; - if (e1 >>= 4) { - if (e1 >= 1 << n_bigtens) - goto undfl; + errno = ERANGE; +#endif + goto ret; + } + e1 >>= 4; + for(j = 0; e1 > 1; j++, e1 >>= 1) + if (e1 & 1) + dval(&rv) *= bigtens[j]; + /* The last multiplication could overflow. */ + word0(&rv) -= P*Exp_msk1; + dval(&rv) *= bigtens[j]; + if ((z = word0(&rv) & Exp_mask) + > Exp_msk1*(DBL_MAX_EXP+Bias-P)) + goto ovfl; + if (z > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) { + /* set to largest number */ + /* (Can't trust DBL_MAX) */ + word0(&rv) = Big0; + word1(&rv) = Big1; + } + else + word0(&rv) += P*Exp_msk1; + } + } + else if (e1 < 0) { + e1 = -e1; + if ((i = e1 & 15)) + dval(&rv) /= tens[i]; + if (e1 >>= 4) { + if (e1 >= 1 << n_bigtens) + goto undfl; #ifdef Avoid_Underflow - if (e1 & Scale_Bit) - bc.scale = 2*P; - for(j = 0; e1 > 0; j++, e1 >>= 1) - if (e1 & 1) - dval(&rv) *= tinytens[j]; - if (bc.scale && (j = 2*P + 1 - ((word0(&rv) & Exp_mask) - >> Exp_shift)) > 0) { - /* scaled rv is denormal; clear j low bits */ - if (j >= 32) { - if (j > 54) - goto undfl; - word1(&rv) = 0; - if (j >= 53) - word0(&rv) = (P+2)*Exp_msk1; - else - word0(&rv) &= 0xffffffff << (j-32); - } - else - word1(&rv) &= 0xffffffff << j; - } + if (e1 & Scale_Bit) + bc.scale = 2*P; + for(j = 0; e1 > 0; j++, e1 >>= 1) + if (e1 & 1) + dval(&rv) *= tinytens[j]; + if (bc.scale && (j = 2*P + 1 - ((word0(&rv) & Exp_mask) + >> Exp_shift)) > 0) { + /* scaled rv is denormal; clear j low bits */ + if (j >= 32) { + if (j > 54) + goto undfl; + word1(&rv) = 0; + if (j >= 53) + word0(&rv) = (P+2)*Exp_msk1; + else + word0(&rv) &= 0xffffffff << (j-32); + } + else + word1(&rv) &= 0xffffffff << j; + } #else - for(j = 0; e1 > 1; j++, e1 >>= 1) - if (e1 & 1) - dval(&rv) *= tinytens[j]; - /* The last multiplication could underflow. */ - dval(&rv0) = dval(&rv); - dval(&rv) *= tinytens[j]; - if (!dval(&rv)) { - dval(&rv) = 2.*dval(&rv0); - dval(&rv) *= tinytens[j]; -#endif - if (!dval(&rv)) { + for(j = 0; e1 > 1; j++, e1 >>= 1) + if (e1 & 1) + dval(&rv) *= tinytens[j]; + /* The last multiplication could underflow. */ + dval(&rv0) = dval(&rv); + dval(&rv) *= tinytens[j]; + if (!dval(&rv)) { + dval(&rv) = 2.*dval(&rv0); + dval(&rv) *= tinytens[j]; +#endif + if (!dval(&rv)) { undfl: - dval(&rv) = 0.; - goto range_err; - } + dval(&rv) = 0.; + goto range_err; + } #ifndef Avoid_Underflow - word0(&rv) = Tiny0; - word1(&rv) = Tiny1; - /* The refinement below will clean - * this approximation up. - */ - } + word0(&rv) = Tiny0; + word1(&rv) = Tiny1; + /* The refinement below will clean + * this approximation up. + */ + } #endif - } - } + } + } - /* Now the hard part -- adjusting rv to the correct value.*/ + /* Now the hard part -- adjusting rv to the correct value.*/ - /* Put digits into bd: true value = bd * 10^e */ + /* Put digits into bd: true value = bd * 10^e */ - bc.nd = nd - nz1; + bc.nd = nd - nz1; #ifndef NO_STRTOD_BIGCOMP - bc.nd0 = nd0; /* Only needed if nd > strtod_diglim, but done here */ - /* to silence an erroneous warning about bc.nd0 */ - /* possibly not being initialized. */ - if (nd > strtod_diglim) { - /* ASSERT(strtod_diglim >= 18); 18 == one more than the */ - /* minimum number of decimal digits to distinguish double values */ - /* in IEEE arithmetic. */ - i = j = 18; - if (i > nd0) - j += bc.dplen; - for(;;) { - if (--j < bc.dp1 && j >= bc.dp0) - j = bc.dp0 - 1; - if (s0[j] != '0') - break; - --i; - } - e += nd - i; - nd = i; - if (nd0 > nd) - nd0 = nd; - if (nd < 9) { /* must recompute y */ - y = 0; - for(i = 0; i < nd0; ++i) - y = 10*y + s0[i] - '0'; - for(j = bc.dp1; i < nd; ++i) - y = 10*y + s0[j++] - '0'; - } - } -#endif - bd0 = s2b(s0, nd0, nd, y, bc.dplen); - - for(;;) { - bd = Balloc(bd0->k); - Bcopy(bd, bd0); - bb = d2b(&rv, &bbe, &bbbits); /* rv = bb * 2^bbe */ - bs = i2b(1); - - if (e >= 0) { - bb2 = bb5 = 0; - bd2 = bd5 = e; - } - else { - bb2 = bb5 = -e; - bd2 = bd5 = 0; - } - if (bbe >= 0) - bb2 += bbe; - else - bd2 -= bbe; - bs2 = bb2; + bc.nd0 = nd0; /* Only needed if nd > strtod_diglim, but done here */ + /* to silence an erroneous warning about bc.nd0 */ + /* possibly not being initialized. */ + if (nd > strtod_diglim) { + /* assert(strtod_diglim >= 18); 18 == one more than the */ + /* minimum number of decimal digits to distinguish double values */ + /* in IEEE arithmetic. */ + i = j = 18; + if (i > nd0) + j += bc.dplen; + for(;;) { + if (--j < bc.dp1 && j >= bc.dp0) + j = bc.dp0 - 1; + if (s0[j] != '0') + break; + --i; + } + e += nd - i; + nd = i; + if (nd0 > nd) + nd0 = nd; + if (nd < 9) { /* must recompute y */ + y = 0; + for(i = 0; i < nd0; ++i) + y = 10*y + s0[i] - '0'; + for(j = bc.dp1; i < nd; ++i) + y = 10*y + s0[j++] - '0'; + } + } +#endif + bd0 = s2b(s0, nd0, nd, y, bc.dplen); + + for(;;) { + bd = Balloc(bd0->k); + Bcopy(bd, bd0); + bb = d2b(&rv, &bbe, &bbbits); /* rv = bb * 2^bbe */ + bs = i2b(1); + + if (e >= 0) { + bb2 = bb5 = 0; + bd2 = bd5 = e; + } + else { + bb2 = bb5 = -e; + bd2 = bd5 = 0; + } + if (bbe >= 0) + bb2 += bbe; + else + bd2 -= bbe; + bs2 = bb2; #ifdef Honor_FLT_ROUNDS - if (bc.rounding != 1) - bs2++; + if (bc.rounding != 1) + bs2++; #endif #ifdef Avoid_Underflow - Lsb = LSB; - Lsb1 = 0; - j = bbe - bc.scale; - i = j + bbbits - 1; /* logb(rv) */ - j = P + 1 - bbbits; - if (i < Emin) { /* denormal */ - i = Emin - i; - j -= i; - if (i < 32) - Lsb <<= i; - else if (i < 52) - Lsb1 = Lsb << (i-32); - else - Lsb1 = Exp_mask; - } + Lsb = LSB; + Lsb1 = 0; + j = bbe - bc.scale; + i = j + bbbits - 1; /* logb(rv) */ + j = P + 1 - bbbits; + if (i < Emin) { /* denormal */ + i = Emin - i; + j -= i; + if (i < 32) + Lsb <<= i; + else if (i < 52) + Lsb1 = Lsb << (i-32); + else + Lsb1 = Exp_mask; + } #else /*Avoid_Underflow*/ #ifdef Sudden_Underflow #ifdef IBM - j = 1 + 4*P - 3 - bbbits + ((bbe + bbbits - 1) & 3); + j = 1 + 4*P - 3 - bbbits + ((bbe + bbbits - 1) & 3); #else - j = P + 1 - bbbits; + j = P + 1 - bbbits; #endif #else /*Sudden_Underflow*/ - j = bbe; - i = j + bbbits - 1; /* logb(rv) */ - if (i < Emin) /* denormal */ - j += P - Emin; - else - j = P + 1 - bbbits; + j = bbe; + i = j + bbbits - 1; /* logb(rv) */ + if (i < Emin) /* denormal */ + j += P - Emin; + else + j = P + 1 - bbbits; #endif /*Sudden_Underflow*/ #endif /*Avoid_Underflow*/ - bb2 += j; - bd2 += j; + bb2 += j; + bd2 += j; #ifdef Avoid_Underflow - bd2 += bc.scale; -#endif - i = bb2 < bd2 ? bb2 : bd2; - if (i > bs2) - i = bs2; - if (i > 0) { - bb2 -= i; - bd2 -= i; - bs2 -= i; - } - if (bb5 > 0) { - bs = pow5mult(bs, bb5); - bb1 = mult(bs, bb); - Bfree(bb); - bb = bb1; - } - if (bb2 > 0) - bb = lshift(bb, bb2); - if (bd5 > 0) - bd = pow5mult(bd, bd5); - if (bd2 > 0) - bd = lshift(bd, bd2); - if (bs2 > 0) - bs = lshift(bs, bs2); - delta = diff(bb, bd); - bc.dsign = delta->sign; - delta->sign = 0; - i = cmp(delta, bs); + bd2 += bc.scale; +#endif + i = bb2 < bd2 ? bb2 : bd2; + if (i > bs2) + i = bs2; + if (i > 0) { + bb2 -= i; + bd2 -= i; + bs2 -= i; + } + if (bb5 > 0) { + bs = pow5mult(bs, bb5); + bb1 = mult(bs, bb); + Bfree(bb); + bb = bb1; + } + if (bb2 > 0) + bb = lshift(bb, bb2); + if (bd5 > 0) + bd = pow5mult(bd, bd5); + if (bd2 > 0) + bd = lshift(bd, bd2); + if (bs2 > 0) + bs = lshift(bs, bs2); + delta = diff(bb, bd); + bc.dsign = delta->sign; + delta->sign = 0; + i = cmp(delta, bs); #ifndef NO_STRTOD_BIGCOMP /*{*/ - if (bc.nd > nd && i <= 0) { - if (bc.dsign) { - /* Must use bigcomp(). */ - req_bigcomp = 1; - break; - } + if (bc.nd > nd && i <= 0) { + if (bc.dsign) { + /* Must use bigcomp(). */ + req_bigcomp = 1; + break; + } #ifdef Honor_FLT_ROUNDS - if (bc.rounding != 1) { - if (i < 0) { - req_bigcomp = 1; - break; - } - } - else -#endif - i = -1; /* Discarded digits make delta smaller. */ - } + if (bc.rounding != 1) { + if (i < 0) { + req_bigcomp = 1; + break; + } + } + else +#endif + i = -1; /* Discarded digits make delta smaller. */ + } #endif /*}*/ #ifdef Honor_FLT_ROUNDS /*{*/ - if (bc.rounding != 1) { - if (i < 0) { - /* Error is less than an ulp */ - if (!delta->x[0] && delta->wds <= 1) { - /* exact */ + if (bc.rounding != 1) { + if (i < 0) { + /* Error is less than an ulp */ + if (!delta->x[0] && delta->wds <= 1) { + /* exact */ #ifdef SET_INEXACT - bc.inexact = 0; -#endif - break; - } - if (bc.rounding) { - if (bc.dsign) { - adj.d = 1.; - goto apply_adj; - } - } - else if (!bc.dsign) { - adj.d = -1.; - if (!word1(&rv) - && !(word0(&rv) & Frac_mask)) { - y = word0(&rv) & Exp_mask; + bc.inexact = 0; +#endif + break; + } + if (bc.rounding) { + if (bc.dsign) { + adj.d = 1.; + goto apply_adj; + } + } + else if (!bc.dsign) { + adj.d = -1.; + if (!word1(&rv) + && !(word0(&rv) & Frac_mask)) { + y = word0(&rv) & Exp_mask; #ifdef Avoid_Underflow - if (!bc.scale || y > 2*P*Exp_msk1) + if (!bc.scale || y > 2*P*Exp_msk1) #else - if (y) -#endif - { - delta = lshift(delta,Log2P); - if (cmp(delta, bs) <= 0) - adj.d = -0.5; - } - } + if (y) +#endif + { + delta = lshift(delta,Log2P); + if (cmp(delta, bs) <= 0) + adj.d = -0.5; + } + } apply_adj: #ifdef Avoid_Underflow /*{*/ - if (bc.scale && (y = word0(&rv) & Exp_mask) - <= 2*P*Exp_msk1) - word0(&adj) += (2*P+1)*Exp_msk1 - y; + if (bc.scale && (y = word0(&rv) & Exp_mask) + <= 2*P*Exp_msk1) + word0(&adj) += (2*P+1)*Exp_msk1 - y; #else #ifdef Sudden_Underflow - if ((word0(&rv) & Exp_mask) <= - P*Exp_msk1) { - word0(&rv) += P*Exp_msk1; - dval(&rv) += adj.d*ulp(dval(&rv)); - word0(&rv) -= P*Exp_msk1; - } - else + if ((word0(&rv) & Exp_mask) <= + P*Exp_msk1) { + word0(&rv) += P*Exp_msk1; + dval(&rv) += adj.d*ulp(dval(&rv)); + word0(&rv) -= P*Exp_msk1; + } + else #endif /*Sudden_Underflow*/ #endif /*Avoid_Underflow}*/ - dval(&rv) += adj.d*ulp(&rv); - } - break; - } - adj.d = ratio(delta, bs); - if (adj.d < 1.) - adj.d = 1.; - if (adj.d <= 0x7ffffffe) { - /* adj = rounding ? ceil(adj) : floor(adj); */ - y = adj.d; - if (y != adj.d) { - if (!((bc.rounding>>1) ^ bc.dsign)) - y++; - adj.d = y; - } - } + dval(&rv) += adj.d*ulp(&rv); + } + break; + } + adj.d = ratio(delta, bs); + if (adj.d < 1.) + adj.d = 1.; + if (adj.d <= 0x7ffffffe) { + /* adj = rounding ? ceil(adj) : floor(adj); */ + y = adj.d; + if (y != adj.d) { + if (!((bc.rounding>>1) ^ bc.dsign)) + y++; + adj.d = y; + } + } #ifdef Avoid_Underflow /*{*/ - if (bc.scale && (y = word0(&rv) & Exp_mask) <= 2*P*Exp_msk1) - word0(&adj) += (2*P+1)*Exp_msk1 - y; + if (bc.scale && (y = word0(&rv) & Exp_mask) <= 2*P*Exp_msk1) + word0(&adj) += (2*P+1)*Exp_msk1 - y; #else #ifdef Sudden_Underflow - if ((word0(&rv) & Exp_mask) <= P*Exp_msk1) { - word0(&rv) += P*Exp_msk1; - adj.d *= ulp(dval(&rv)); - if (bc.dsign) - dval(&rv) += adj.d; - else - dval(&rv) -= adj.d; - word0(&rv) -= P*Exp_msk1; - goto cont; - } + if ((word0(&rv) & Exp_mask) <= P*Exp_msk1) { + word0(&rv) += P*Exp_msk1; + adj.d *= ulp(dval(&rv)); + if (bc.dsign) + dval(&rv) += adj.d; + else + dval(&rv) -= adj.d; + word0(&rv) -= P*Exp_msk1; + goto cont; + } #endif /*Sudden_Underflow*/ #endif /*Avoid_Underflow}*/ - adj.d *= ulp(&rv); - if (bc.dsign) { - if (word0(&rv) == Big0 && word1(&rv) == Big1) - goto ovfl; - dval(&rv) += adj.d; - } - else - dval(&rv) -= adj.d; - goto cont; - } + adj.d *= ulp(&rv); + if (bc.dsign) { + if (word0(&rv) == Big0 && word1(&rv) == Big1) + goto ovfl; + dval(&rv) += adj.d; + } + else + dval(&rv) -= adj.d; + goto cont; + } #endif /*}Honor_FLT_ROUNDS*/ - if (i < 0) { - /* Error is less than half an ulp -- check for - * special case of mantissa a power of two. - */ - if (bc.dsign || word1(&rv) || word0(&rv) & Bndry_mask + if (i < 0) { + /* Error is less than half an ulp -- check for + * special case of mantissa a power of two. + */ + if (bc.dsign || word1(&rv) || word0(&rv) & Bndry_mask #ifdef IEEE_Arith /*{*/ #ifdef Avoid_Underflow - || (word0(&rv) & Exp_mask) <= (2*P+1)*Exp_msk1 + || (word0(&rv) & Exp_mask) <= (2*P+1)*Exp_msk1 #else - || (word0(&rv) & Exp_mask) <= Exp_msk1 + || (word0(&rv) & Exp_mask) <= Exp_msk1 #endif #endif /*}*/ - ) { + ) { #ifdef SET_INEXACT - if (!delta->x[0] && delta->wds <= 1) - bc.inexact = 0; + if (!delta->x[0] && delta->wds <= 1) + bc.inexact = 0; #endif - break; - } - if (!delta->x[0] && delta->wds <= 1) { - /* exact result */ + break; + } + if (!delta->x[0] && delta->wds <= 1) { + /* exact result */ #ifdef SET_INEXACT - bc.inexact = 0; -#endif - break; - } - delta = lshift(delta,Log2P); - if (cmp(delta, bs) > 0) - goto drop_down; - break; - } - if (i == 0) { - /* exactly half-way between */ - if (bc.dsign) { - if ((word0(&rv) & Bndry_mask1) == Bndry_mask1 - && word1(&rv) == ( + bc.inexact = 0; +#endif + break; + } + delta = lshift(delta,Log2P); + if (cmp(delta, bs) > 0) + goto drop_down; + break; + } + if (i == 0) { + /* exactly half-way between */ + if (bc.dsign) { + if ((word0(&rv) & Bndry_mask1) == Bndry_mask1 + && word1(&rv) == ( #ifdef Avoid_Underflow - (bc.scale && (y = word0(&rv) & Exp_mask) <= 2*P*Exp_msk1) - ? (0xffffffff & (0xffffffff << (2*P+1-(y>>Exp_shift)))) : -#endif - 0xffffffff)) { - /*boundary case -- increment exponent*/ - if (word0(&rv) == Big0 && word1(&rv) == Big1) - goto ovfl; - word0(&rv) = (word0(&rv) & Exp_mask) - + Exp_msk1 + (bc.scale && (y = word0(&rv) & Exp_mask) <= 2*P*Exp_msk1) + ? (0xffffffff & (0xffffffff << (2*P+1-(y>>Exp_shift)))) : +#endif + 0xffffffff)) { + /*boundary case -- increment exponent*/ + if (word0(&rv) == Big0 && word1(&rv) == Big1) + goto ovfl; + word0(&rv) = (word0(&rv) & Exp_mask) + + Exp_msk1 #ifdef IBM - | Exp_msk1 >> 4 + | Exp_msk1 >> 4 #endif - ; - word1(&rv) = 0; + ; + word1(&rv) = 0; #ifdef Avoid_Underflow - bc.dsign = 0; + bc.dsign = 0; #endif - break; - } - } - else if (!(word0(&rv) & Bndry_mask) && !word1(&rv)) { + break; + } + } + else if (!(word0(&rv) & Bndry_mask) && !word1(&rv)) { drop_down: - /* boundary case -- decrement exponent */ + /* boundary case -- decrement exponent */ #ifdef Sudden_Underflow /*{{*/ - L = word0(&rv) & Exp_mask; + L = word0(&rv) & Exp_mask; #ifdef IBM - if (L < Exp_msk1) + if (L < Exp_msk1) #else #ifdef Avoid_Underflow - if (L <= (bc.scale ? (2*P+1)*Exp_msk1 : Exp_msk1)) + if (L <= (bc.scale ? (2*P+1)*Exp_msk1 : Exp_msk1)) #else - if (L <= Exp_msk1) + if (L <= Exp_msk1) #endif /*Avoid_Underflow*/ #endif /*IBM*/ - { - if (bc.nd >nd) { - bc.uflchk = 1; - break; - } - goto undfl; - } - L -= Exp_msk1; + { + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } + L -= Exp_msk1; #else /*Sudden_Underflow}{*/ #ifdef Avoid_Underflow - if (bc.scale) { - L = word0(&rv) & Exp_mask; - if (L <= (2*P+1)*Exp_msk1) { - if (L > (P+2)*Exp_msk1) - /* round even ==> */ - /* accept rv */ - break; - /* rv = smallest denormal */ - if (bc.nd >nd) { - bc.uflchk = 1; - break; - } - goto undfl; - } - } + if (bc.scale) { + L = word0(&rv) & Exp_mask; + if (L <= (2*P+1)*Exp_msk1) { + if (L > (P+2)*Exp_msk1) + /* round even ==> */ + /* accept rv */ + break; + /* rv = smallest denormal */ + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } + } #endif /*Avoid_Underflow*/ - L = (word0(&rv) & Exp_mask) - Exp_msk1; + L = (word0(&rv) & Exp_mask) - Exp_msk1; #endif /*Sudden_Underflow}}*/ - word0(&rv) = L | Bndry_mask1; - word1(&rv) = 0xffffffff; + word0(&rv) = L | Bndry_mask1; + word1(&rv) = 0xffffffff; #ifdef IBM - goto cont; + goto cont; #else #ifndef NO_STRTOD_BIGCOMP - if (bc.nd > nd) - goto cont; + if (bc.nd > nd) + goto cont; #endif - break; + break; #endif - } + } #ifndef ROUND_BIASED #ifdef Avoid_Underflow - if (Lsb1) { - if (!(word0(&rv) & Lsb1)) - break; - } - else if (!(word1(&rv) & Lsb)) - break; + if (Lsb1) { + if (!(word0(&rv) & Lsb1)) + break; + } + else if (!(word1(&rv) & Lsb)) + break; #else - if (!(word1(&rv) & LSB)) - break; + if (!(word1(&rv) & LSB)) + break; #endif #endif - if (bc.dsign) + if (bc.dsign) #ifdef Avoid_Underflow - dval(&rv) += sulp(&rv, &bc); + dval(&rv) += sulp(&rv, &bc); #else - dval(&rv) += ulp(&rv); + dval(&rv) += ulp(&rv); #endif #ifndef ROUND_BIASED - else { + else { #ifdef Avoid_Underflow - dval(&rv) -= sulp(&rv, &bc); + dval(&rv) -= sulp(&rv, &bc); #else - dval(&rv) -= ulp(&rv); + dval(&rv) -= ulp(&rv); #endif #ifndef Sudden_Underflow - if (!dval(&rv)) { - if (bc.nd >nd) { - bc.uflchk = 1; - break; - } - goto undfl; - } -#endif - } + if (!dval(&rv)) { + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } +#endif + } #ifdef Avoid_Underflow - bc.dsign = 1 - bc.dsign; + bc.dsign = 1 - bc.dsign; #endif #endif - break; - } - if ((aadj = ratio(delta, bs)) <= 2.) { - if (bc.dsign) - aadj = aadj1 = 1.; - else if (word1(&rv) || word0(&rv) & Bndry_mask) { + break; + } + if ((aadj = ratio(delta, bs)) <= 2.) { + if (bc.dsign) + aadj = aadj1 = 1.; + else if (word1(&rv) || word0(&rv) & Bndry_mask) { #ifndef Sudden_Underflow - if (word1(&rv) == Tiny1 && !word0(&rv)) { - if (bc.nd >nd) { - bc.uflchk = 1; - break; - } - goto undfl; - } -#endif - aadj = 1.; - aadj1 = -1.; - } - else { - /* special case -- power of FLT_RADIX to be */ - /* rounded down... */ - - if (aadj < 2./FLT_RADIX) - aadj = 1./FLT_RADIX; - else - aadj *= 0.5; - aadj1 = -aadj; - } - } - else { - aadj *= 0.5; - aadj1 = bc.dsign ? aadj : -aadj; + if (word1(&rv) == Tiny1 && !word0(&rv)) { + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } +#endif + aadj = 1.; + aadj1 = -1.; + } + else { + /* special case -- power of FLT_RADIX to be */ + /* rounded down... */ + + if (aadj < 2./FLT_RADIX) + aadj = 1./FLT_RADIX; + else + aadj *= 0.5; + aadj1 = -aadj; + } + } + else { + aadj *= 0.5; + aadj1 = bc.dsign ? aadj : -aadj; #ifdef Check_FLT_ROUNDS - switch(bc.rounding) { - case 2: /* towards +infinity */ - aadj1 -= 0.5; - break; - case 0: /* towards 0 */ - case 3: /* towards -infinity */ - aadj1 += 0.5; - } + switch(bc.rounding) { + case 2: /* towards +infinity */ + aadj1 -= 0.5; + break; + case 0: /* towards 0 */ + case 3: /* towards -infinity */ + aadj1 += 0.5; + } #else - if (Flt_Rounds == 0) - aadj1 += 0.5; + if (Flt_Rounds == 0) + aadj1 += 0.5; #endif /*Check_FLT_ROUNDS*/ - } - y = word0(&rv) & Exp_mask; - - /* Check for overflow */ - - if (y == Exp_msk1*(DBL_MAX_EXP+Bias-1)) { - dval(&rv0) = dval(&rv); - word0(&rv) -= P*Exp_msk1; - adj.d = aadj1 * ulp(&rv); - dval(&rv) += adj.d; - if ((word0(&rv) & Exp_mask) >= - Exp_msk1*(DBL_MAX_EXP+Bias-P)) { - if (word0(&rv0) == Big0 && word1(&rv0) == Big1) - goto ovfl; - word0(&rv) = Big0; - word1(&rv) = Big1; - goto cont; - } - else - word0(&rv) += P*Exp_msk1; - } - else { + } + y = word0(&rv) & Exp_mask; + + /* Check for overflow */ + + if (y == Exp_msk1*(DBL_MAX_EXP+Bias-1)) { + dval(&rv0) = dval(&rv); + word0(&rv) -= P*Exp_msk1; + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; + if ((word0(&rv) & Exp_mask) >= + Exp_msk1*(DBL_MAX_EXP+Bias-P)) { + if (word0(&rv0) == Big0 && word1(&rv0) == Big1) + goto ovfl; + word0(&rv) = Big0; + word1(&rv) = Big1; + goto cont; + } + else + word0(&rv) += P*Exp_msk1; + } + else { #ifdef Avoid_Underflow - if (bc.scale && y <= 2*P*Exp_msk1) { - if (aadj <= 0x7fffffff) { - if ((z = aadj) <= 0) - z = 1; - aadj = z; - aadj1 = bc.dsign ? aadj : -aadj; - } - dval(&aadj2) = aadj1; - word0(&aadj2) += (2*P+1)*Exp_msk1 - y; - aadj1 = dval(&aadj2); - adj.d = aadj1 * ulp(&rv); - dval(&rv) += adj.d; - if (rv.d == 0.) + if (bc.scale && y <= 2*P*Exp_msk1) { + if (aadj <= 0x7fffffff) { + if ((z = aadj) <= 0) + z = 1; + aadj = z; + aadj1 = bc.dsign ? aadj : -aadj; + } + dval(&aadj2) = aadj1; + word0(&aadj2) += (2*P+1)*Exp_msk1 - y; + aadj1 = dval(&aadj2); + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; + if (rv.d == 0.) #ifdef NO_STRTOD_BIGCOMP - goto undfl; + goto undfl; #else - { - if (bc.nd > nd) - bc.dsign = 1; - break; - } -#endif - } - else { - adj.d = aadj1 * ulp(&rv); - dval(&rv) += adj.d; - } + { + if (bc.nd > nd) + bc.dsign = 1; + break; + } +#endif + } + else { + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; + } #else #ifdef Sudden_Underflow - if ((word0(&rv) & Exp_mask) <= P*Exp_msk1) { - dval(&rv0) = dval(&rv); - word0(&rv) += P*Exp_msk1; - adj.d = aadj1 * ulp(&rv); - dval(&rv) += adj.d; + if ((word0(&rv) & Exp_mask) <= P*Exp_msk1) { + dval(&rv0) = dval(&rv); + word0(&rv) += P*Exp_msk1; + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; #ifdef IBM - if ((word0(&rv) & Exp_mask) < P*Exp_msk1) + if ((word0(&rv) & Exp_mask) < P*Exp_msk1) #else - if ((word0(&rv) & Exp_mask) <= P*Exp_msk1) -#endif - { - if (word0(&rv0) == Tiny0 - && word1(&rv0) == Tiny1) { - if (bc.nd >nd) { - bc.uflchk = 1; - break; - } - goto undfl; - } - word0(&rv) = Tiny0; - word1(&rv) = Tiny1; - goto cont; - } - else - word0(&rv) -= P*Exp_msk1; - } - else { - adj.d = aadj1 * ulp(&rv); - dval(&rv) += adj.d; - } + if ((word0(&rv) & Exp_mask) <= P*Exp_msk1) +#endif + { + if (word0(&rv0) == Tiny0 + && word1(&rv0) == Tiny1) { + if (bc.nd >nd) { + bc.uflchk = 1; + break; + } + goto undfl; + } + word0(&rv) = Tiny0; + word1(&rv) = Tiny1; + goto cont; + } + else + word0(&rv) -= P*Exp_msk1; + } + else { + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; + } #else /*Sudden_Underflow*/ - /* Compute adj so that the IEEE rounding rules will - * correctly round rv + adj in some half-way cases. - * If rv * ulp(rv) is denormalized (i.e., - * y <= (P-1)*Exp_msk1), we must adjust aadj to avoid - * trouble from bits lost to denormalization; - * example: 1.2e-307 . - */ - if (y <= (P-1)*Exp_msk1 && aadj > 1.) { - aadj1 = (double)(int)(aadj + 0.5); - if (!bc.dsign) - aadj1 = -aadj1; - } - adj.d = aadj1 * ulp(&rv); - dval(&rv) += adj.d; + /* Compute adj so that the IEEE rounding rules will + * correctly round rv + adj in some half-way cases. + * If rv * ulp(rv) is denormalized (i.e., + * y <= (P-1)*Exp_msk1), we must adjust aadj to avoid + * trouble from bits lost to denormalization; + * example: 1.2e-307 . + */ + if (y <= (P-1)*Exp_msk1 && aadj > 1.) { + aadj1 = (double)(int)(aadj + 0.5); + if (!bc.dsign) + aadj1 = -aadj1; + } + adj.d = aadj1 * ulp(&rv); + dval(&rv) += adj.d; #endif /*Sudden_Underflow*/ #endif /*Avoid_Underflow*/ - } - z = word0(&rv) & Exp_mask; + } + z = word0(&rv) & Exp_mask; #ifndef SET_INEXACT - if (bc.nd == nd) { + if (bc.nd == nd) { #ifdef Avoid_Underflow - if (!bc.scale) -#endif - if (y == z) { - /* Can we stop now? */ - L = (Long)aadj; - aadj -= L; - /* The tolerances below are conservative. */ - if (bc.dsign || word1(&rv) || word0(&rv) & Bndry_mask) { - if (aadj < .4999999 || aadj > .5000001) - break; - } - else if (aadj < .4999999/FLT_RADIX) - break; - } - } + if (!bc.scale) +#endif + if (y == z) { + /* Can we stop now? */ + L = (Long)aadj; + aadj -= L; + /* The tolerances below are conservative. */ + if (bc.dsign || word1(&rv) || word0(&rv) & Bndry_mask) { + if (aadj < .4999999 || aadj > .5000001) + break; + } + else if (aadj < .4999999/FLT_RADIX) + break; + } + } #endif cont: - Bfree(bb); - Bfree(bd); - Bfree(bs); - Bfree(delta); - } - Bfree(bb); - Bfree(bd); - Bfree(bs); - Bfree(bd0); - Bfree(delta); + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(delta); + } + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(bd0); + Bfree(delta); #ifndef NO_STRTOD_BIGCOMP - if (req_bigcomp) { - bd0 = 0; - bc.e0 += nz1; - bigcomp(&rv, s0, &bc); - y = word0(&rv) & Exp_mask; - if (y == Exp_mask) - goto ovfl; - if (y == 0 && rv.d == 0.) - goto undfl; - } + if (req_bigcomp) { + bd0 = 0; + bc.e0 += nz1; + bigcomp(&rv, s0, &bc); + y = word0(&rv) & Exp_mask; + if (y == Exp_mask) + goto ovfl; + if (y == 0 && rv.d == 0.) + goto undfl; + } #endif #ifdef SET_INEXACT - if (bc.inexact) { - if (!oldinexact) { - word0(&rv0) = Exp_1 + (70 << Exp_shift); - word1(&rv0) = 0; - dval(&rv0) += 1.; - } - } - else if (!oldinexact) - clear_inexact(); + if (bc.inexact) { + if (!oldinexact) { + word0(&rv0) = Exp_1 + (70 << Exp_shift); + word1(&rv0) = 0; + dval(&rv0) += 1.; + } + } + else if (!oldinexact) + clear_inexact(); #endif #ifdef Avoid_Underflow - if (bc.scale) { - word0(&rv0) = Exp_1 - 2*P*Exp_msk1; - word1(&rv0) = 0; - dval(&rv) *= dval(&rv0); + if (bc.scale) { + word0(&rv0) = Exp_1 - 2*P*Exp_msk1; + word1(&rv0) = 0; + dval(&rv) *= dval(&rv0); #ifndef NO_ERRNO - /* try to avoid the bug of testing an 8087 register value */ + /* try to avoid the bug of testing an 8087 register value */ #ifdef IEEE_Arith - if (!(word0(&rv) & Exp_mask)) + if (!(word0(&rv) & Exp_mask)) #else - if (word0(&rv) == 0 && word1(&rv) == 0) + if (word0(&rv) == 0 && word1(&rv) == 0) #endif - errno = ERANGE; + errno = ERANGE; #endif - } + } #endif /* Avoid_Underflow */ #ifdef SET_INEXACT - if (bc.inexact && !(word0(&rv) & Exp_mask)) { - /* set underflow bit */ - dval(&rv0) = 1e-300; - dval(&rv0) *= dval(&rv0); - } + if (bc.inexact && !(word0(&rv) & Exp_mask)) { + /* set underflow bit */ + dval(&rv0) = 1e-300; + dval(&rv0) *= dval(&rv0); + } #endif ret: - if (se) - *se = (char *)s; - return sign ? -dval(&rv) : dval(&rv); - } + if (se) + *se = (const char *)s; // Ren/C: fix cast away of const + return sign ? -dval(&rv) : dval(&rv); + } #ifndef MULTIPLE_THREADS static char *dtoa_result; @@ -3567,26 +3574,26 @@ strtod static char * #ifdef KR_headers -rv_alloc(i) int i; +rv_alloc(i) size_t i; // !!! Ren/C: (int => size_t) for -Wsign-compare #else -rv_alloc(int i) +rv_alloc(size_t i) // !!! Ren/C: (int => size_t) for -Wsign-compare #endif { - int j, k, *r; - - j = sizeof(ULong); - for(k = 0; - sizeof(Bigint) - sizeof(ULong) - sizeof(int) + j <= i; - j <<= 1) - k++; - r = (int*)Balloc(k); - *r = k; - return + int j, k, *r; + + j = sizeof(ULong); + for(k = 0; + sizeof(Bigint) - sizeof(ULong) - sizeof(int) + j <= i; + j <<= 1) + k++; + r = (int*)Balloc(k); + *r = k; + return #ifndef MULTIPLE_THREADS - dtoa_result = + dtoa_result = #endif - (char *)(r+1); - } + (char *)(r+1); + } static char * #ifdef KR_headers @@ -3595,14 +3602,14 @@ nrv_alloc(s, rve, n) char *s, **rve; int n; nrv_alloc(const char *s, char **rve, int n) #endif { - char *rv, *t; + char *rv, *t; - t = rv = rv_alloc(n); - while((*t = *s++)) t++; - if (rve) - *rve = t; - return rv; - } + t = rv = rv_alloc(n); + while((*t = *s++)) t++; + if (rve) + *rve = t; + return rv; + } /* freedtoa(s) must be used to free values s returned by dtoa * when MULTIPLE_THREADS is #defined. It should be used in all cases, @@ -3617,14 +3624,14 @@ freedtoa(s) char *s; freedtoa(char *s) #endif { - Bigint *b = (Bigint *)((int *)s - 1); - b->maxwds = 1 << (b->k = *(int*)b); - Bfree(b); + Bigint *b = (Bigint *)((int *)s - 1); + b->maxwds = 1 << (b->k = *(int*)b); + Bfree(b); #ifndef MULTIPLE_THREADS - if (s == dtoa_result) - dtoa_result = 0; + if (s == dtoa_result) + dtoa_result = 0; #endif - } + } /* dtoa for IEEE arithmetic (dmg): convert double to ASCII string. * @@ -3632,743 +3639,743 @@ freedtoa(char *s) * Guy L. Steele, Jr. and Jon L. White [Proc. ACM SIGPLAN '90, pp. 112-126]. * * Modifications: - * 1. Rather than iterating, we use a simple numeric overestimate - * to determine k = floor(log10(d)). We scale relevant - * quantities using O(log2(k)) rather than O(k) multiplications. - * 2. For some modes > 2 (corresponding to ecvt and fcvt), we don't - * try to generate digits strictly left to right. Instead, we - * compute with fewer bits and propagate the carry if necessary - * when rounding the final digit up. This is often faster. - * 3. Under the assumption that input will be rounded nearest, - * mode 0 renders 1e23 as 1e23 rather than 9.999999999999999e22. - * That is, we allow equality in stopping tests when the - * round-nearest rule will give the same floating-point value - * as would satisfaction of the stopping test with strict - * inequality. - * 4. We remove common factors of powers of 2 from relevant - * quantities. - * 5. When converting floating-point integers less than 1e16, - * we use floating-point arithmetic rather than resorting - * to multiple-precision integers. - * 6. When asked to produce fewer than 15 digits, we first try - * to get by with floating-point arithmetic; we resort to - * multiple-precision integer arithmetic only if we cannot - * guarantee that the floating-point calculation has given - * the correctly rounded result. For k requested digits and - * "uniformly" distributed input, the probability is - * something like 10^(k-15) that we must resort to the Long - * calculation. + * 1. Rather than iterating, we use a simple numeric overestimate + * to determine k = floor(log10(d)). We scale relevant + * quantities using O(log2(k)) rather than O(k) multiplications. + * 2. For some modes > 2 (corresponding to ecvt and fcvt), we don't + * try to generate digits strictly left to right. Instead, we + * compute with fewer bits and propagate the carry if necessary + * when rounding the final digit up. This is often faster. + * 3. Under the assumption that input will be rounded nearest, + * mode 0 renders 1e23 as 1e23 rather than 9.999999999999999e22. + * That is, we allow equality in stopping tests when the + * round-nearest rule will give the same floating-point value + * as would satisfaction of the stopping test with strict + * inequality. + * 4. We remove common factors of powers of 2 from relevant + * quantities. + * 5. When converting floating-point integers less than 1e16, + * we use floating-point arithmetic rather than resorting + * to multiple-precision integers. + * 6. When asked to produce fewer than 15 digits, we first try + * to get by with floating-point arithmetic; we resort to + * multiple-precision integer arithmetic only if we cannot + * guarantee that the floating-point calculation has given + * the correctly rounded result. For k requested digits and + * "uniformly" distributed input, the probability is + * something like 10^(k-15) that we must resort to the Long + * calculation. */ char * dtoa #ifdef KR_headers - (dd, mode, ndigits, decpt, sign, rve) - double dd; int mode, ndigits, *decpt, *sign; char **rve; + (dd, mode, ndigits, decpt, sign, rve) + double dd; int mode, ndigits, *decpt, *sign; char **rve; #else - (double dd, int mode, int ndigits, int *decpt, int *sign, char **rve) + (double dd, int mode, int ndigits, int *decpt, int *sign, char **rve) #endif { - /* Arguments ndigits, decpt, sign are similar to those - of ecvt and fcvt; trailing zeros are suppressed from - the returned string. If not null, *rve is set to point - to the end of the return value. If d is +-Infinity or NaN, - then *decpt is set to 9999. - - mode: - 0 ==> shortest string that yields d when read in - and rounded to nearest. - 1 ==> like 0, but with Steele & White stopping rule; - e.g. with IEEE P754 arithmetic , mode 0 gives - 1e23 whereas mode 1 gives 9.999999999999999e22. - 2 ==> max(1,ndigits) significant digits. This gives a - return value similar to that of ecvt, except - that trailing zeros are suppressed. - 3 ==> through ndigits past the decimal point. This - gives a return value similar to that from fcvt, - except that trailing zeros are suppressed, and - ndigits can be negative. - 4,5 ==> similar to 2 and 3, respectively, but (in - round-nearest mode) with the tests of mode 0 to - possibly return a shorter string that rounds to d. - With IEEE arithmetic and compilation with - -DHonor_FLT_ROUNDS, modes 4 and 5 behave the same - as modes 2 and 3 when FLT_ROUNDS != 1. - 6-9 ==> Debugging modes similar to mode - 4: don't try - fast floating-point estimate (if applicable). - - Values of mode other than 0-9 are treated as mode 0. - - Sufficient space is allocated to the return value - to hold the suppressed trailing zeros. - */ - - int bbits, b2, b5, be, dig, i, ieps, ilim, ilim0, ilim1, - j, j1, k, k0, k_check, leftright, m2, m5, s2, s5, - spec_case, try_quick; - Long L; + /* Arguments ndigits, decpt, sign are similar to those + of ecvt and fcvt; trailing zeros are suppressed from + the returned string. If not null, *rve is set to point + to the end of the return value. If d is +-Infinity or NaN, + then *decpt is set to 9999. + + mode: + 0 ==> shortest string that yields d when read in + and rounded to nearest. + 1 ==> like 0, but with Steele & White stopping rule; + e.g. with IEEE P754 arithmetic , mode 0 gives + 1e23 whereas mode 1 gives 9.999999999999999e22. + 2 ==> max(1,ndigits) significant digits. This gives a + return value similar to that of ecvt, except + that trailing zeros are suppressed. + 3 ==> through ndigits past the decimal point. This + gives a return value similar to that from fcvt, + except that trailing zeros are suppressed, and + ndigits can be negative. + 4,5 ==> similar to 2 and 3, respectively, but (in + round-nearest mode) with the tests of mode 0 to + possibly return a shorter string that rounds to d. + With IEEE arithmetic and compilation with + -DHonor_FLT_ROUNDS, modes 4 and 5 behave the same + as modes 2 and 3 when FLT_ROUNDS != 1. + 6-9 ==> Debugging modes similar to mode - 4: don't try + fast floating-point estimate (if applicable). + + Values of mode other than 0-9 are treated as mode 0. + + Sufficient space is allocated to the return value + to hold the suppressed trailing zeros. + */ + + int bbits, b2, b5, be, dig, i, ieps, ilim, ilim0, ilim1, + j, j1, k, k0, k_check, leftright, m2, m5, s2, s5, + spec_case, try_quick; + Long L; #ifndef Sudden_Underflow - int denorm; - ULong x; + int denorm; + ULong x; #endif - Bigint *b, *b1, *delta, *mlo, *mhi, *S; - U d2, eps, u; - double ds; - char *s, *s0; + Bigint *b, *b1, *delta, *mlo, *mhi, *S; + U d2, eps, u; + double ds; + char *s, *s0; #ifndef No_leftright #ifdef IEEE_Arith - U eps1; + U eps1; #endif #endif #ifdef SET_INEXACT - int inexact, oldinexact; + int inexact, oldinexact; #endif #ifdef Honor_FLT_ROUNDS /*{*/ - int Rounding; + int Rounding; #ifdef Trust_FLT_ROUNDS /*{{ only define this if FLT_ROUNDS really works! */ - Rounding = Flt_Rounds; + Rounding = Flt_Rounds; #else /*}{*/ - Rounding = 1; - switch(fegetround()) { - case FE_TOWARDZERO: Rounding = 0; break; - case FE_UPWARD: Rounding = 2; break; - case FE_DOWNWARD: Rounding = 3; - } + Rounding = 1; + switch(fegetround()) { + case FE_TOWARDZERO: Rounding = 0; break; + case FE_UPWARD: Rounding = 2; break; + case FE_DOWNWARD: Rounding = 3; + } #endif /*}}*/ #endif /*}*/ #ifndef MULTIPLE_THREADS - if (dtoa_result) { - freedtoa(dtoa_result); - dtoa_result = 0; - } + if (dtoa_result) { + freedtoa(dtoa_result); + dtoa_result = 0; + } #endif - u.d = dd; - if (word0(&u) & Sign_bit) { - /* set sign for everything, including 0's and NaNs */ - *sign = 1; - word0(&u) &= ~Sign_bit; /* clear sign bit */ - } - else - *sign = 0; + u.d = dd; + if (word0(&u) & Sign_bit) { + /* set sign for everything, including 0's and NaNs */ + *sign = 1; + word0(&u) &= ~Sign_bit; /* clear sign bit */ + } + else + *sign = 0; #if defined(IEEE_Arith) + defined(VAX) #ifdef IEEE_Arith - if ((word0(&u) & Exp_mask) == Exp_mask) + if ((word0(&u) & Exp_mask) == Exp_mask) #else - if (word0(&u) == 0x8000) + if (word0(&u) == 0x8000) #endif - { - /* Infinity or NaN */ - *decpt = 9999; + { + /* Infinity or NaN */ + *decpt = 9999; #ifdef IEEE_Arith - if (!word1(&u) && !(word0(&u) & 0xfffff)) - return nrv_alloc("Infinity", rve, 8); + if (!word1(&u) && !(word0(&u) & 0xfffff)) + return nrv_alloc("Infinity", rve, 8); #endif - return nrv_alloc("NaN", rve, 3); - } + return nrv_alloc("NaN", rve, 3); + } #endif #ifdef IBM - dval(&u) += 0; /* normalize */ + dval(&u) += 0; /* normalize */ #endif - if (!dval(&u)) { - *decpt = 1; - return nrv_alloc("0", rve, 1); - } + if (!dval(&u)) { + *decpt = 1; + return nrv_alloc("0", rve, 1); + } #ifdef SET_INEXACT - try_quick = oldinexact = get_inexact(); - inexact = 1; + try_quick = oldinexact = get_inexact(); + inexact = 1; #endif #ifdef Honor_FLT_ROUNDS - if (Rounding >= 2) { - if (*sign) - Rounding = Rounding == 2 ? 0 : 2; - else - if (Rounding != 2) - Rounding = 0; - } + if (Rounding >= 2) { + if (*sign) + Rounding = Rounding == 2 ? 0 : 2; + else + if (Rounding != 2) + Rounding = 0; + } #endif - b = d2b(&u, &be, &bbits); + b = d2b(&u, &be, &bbits); #ifdef Sudden_Underflow - i = (int)(word0(&u) >> Exp_shift1 & (Exp_mask>>Exp_shift1)); + i = (int)(word0(&u) >> Exp_shift1 & (Exp_mask>>Exp_shift1)); #else - if ((i = (int)(word0(&u) >> Exp_shift1 & (Exp_mask>>Exp_shift1)))) { + if ((i = (int)(word0(&u) >> Exp_shift1 & (Exp_mask>>Exp_shift1)))) { #endif - dval(&d2) = dval(&u); - word0(&d2) &= Frac_mask1; - word0(&d2) |= Exp_11; + dval(&d2) = dval(&u); + word0(&d2) &= Frac_mask1; + word0(&d2) |= Exp_11; #ifdef IBM - if (j = 11 - hi0bits(word0(&d2) & Frac_mask)) - dval(&d2) /= 1 << j; -#endif - - /* log(x) ~=~ log(1.5) + (x-1.5)/1.5 - * log10(x) = log(x) / log(10) - * ~=~ log(1.5)/log(10) + (x-1.5)/(1.5*log(10)) - * log10(d) = (i-Bias)*log(2)/log(10) + log10(d2) - * - * This suggests computing an approximation k to log10(d) by - * - * k = (i - Bias)*0.301029995663981 - * + ( (d2-1.5)*0.289529654602168 + 0.176091259055681 ); - * - * We want k to be too large rather than too small. - * The error in the first-order Taylor series approximation - * is in our favor, so we just round up the constant enough - * to compensate for any error in the multiplication of - * (i - Bias) by 0.301029995663981; since |i - Bias| <= 1077, - * and 1077 * 0.30103 * 2^-52 ~=~ 7.2e-14, - * adding 1e-13 to the constant term more than suffices. - * Hence we adjust the constant term to 0.1760912590558. - * (We could get a more accurate k by invoking log10, - * but this is probably not worthwhile.) - */ - - i -= Bias; + if (j = 11 - hi0bits(word0(&d2) & Frac_mask)) + dval(&d2) /= 1 << j; +#endif + + /* log(x) ~=~ log(1.5) + (x-1.5)/1.5 + * log10(x) = log(x) / log(10) + * ~=~ log(1.5)/log(10) + (x-1.5)/(1.5*log(10)) + * log10(d) = (i-Bias)*log(2)/log(10) + log10(d2) + * + * This suggests computing an approximation k to log10(d) by + * + * k = (i - Bias)*0.301029995663981 + * + ( (d2-1.5)*0.289529654602168 + 0.176091259055681 ); + * + * We want k to be too large rather than too small. + * The error in the first-order Taylor series approximation + * is in our favor, so we just round up the constant enough + * to compensate for any error in the multiplication of + * (i - Bias) by 0.301029995663981; since |i - Bias| <= 1077, + * and 1077 * 0.30103 * 2^-52 ~=~ 7.2e-14, + * adding 1e-13 to the constant term more than suffices. + * Hence we adjust the constant term to 0.1760912590558. + * (We could get a more accurate k by invoking log10, + * but this is probably not worthwhile.) + */ + + i -= Bias; #ifdef IBM - i <<= 2; - i += j; + i <<= 2; + i += j; #endif #ifndef Sudden_Underflow - denorm = 0; - } - else { - /* d is denormalized */ - - i = bbits + be + (Bias + (P-1) - 1); - x = i > 32 ? word0(&u) << (64 - i) | word1(&u) >> (i - 32) - : word1(&u) << (32 - i); - dval(&d2) = x; - word0(&d2) -= 31*Exp_msk1; /* adjust exponent */ - i -= (Bias + (P-1) - 1) + 1; - denorm = 1; - } -#endif - ds = (dval(&d2)-1.5)*0.289529654602168 + 0.1760912590558 + i*0.301029995663981; - k = (int)ds; - if (ds < 0. && ds != k) - k--; /* want k = floor(ds) */ - k_check = 1; - if (k >= 0 && k <= Ten_pmax) { - if (dval(&u) < tens[k]) - k--; - k_check = 0; - } - j = bbits - i - 1; - if (j >= 0) { - b2 = 0; - s2 = j; - } - else { - b2 = -j; - s2 = 0; - } - if (k >= 0) { - b5 = 0; - s5 = k; - s2 += k; - } - else { - b2 -= k; - b5 = -k; - s5 = 0; - } - if (mode < 0 || mode > 9) - mode = 0; + denorm = 0; + } + else { + /* d is denormalized */ + + i = bbits + be + (Bias + (P-1) - 1); + x = i > 32 ? word0(&u) << (64 - i) | word1(&u) >> (i - 32) + : word1(&u) << (32 - i); + dval(&d2) = x; + word0(&d2) -= 31*Exp_msk1; /* adjust exponent */ + i -= (Bias + (P-1) - 1) + 1; + denorm = 1; + } +#endif + ds = (dval(&d2)-1.5)*0.289529654602168 + 0.1760912590558 + i*0.301029995663981; + k = (int)ds; + if (ds < 0. && ds != k) + k--; /* want k = floor(ds) */ + k_check = 1; + if (k >= 0 && k <= Ten_pmax) { + if (dval(&u) < tens[k]) + k--; + k_check = 0; + } + j = bbits - i - 1; + if (j >= 0) { + b2 = 0; + s2 = j; + } + else { + b2 = -j; + s2 = 0; + } + if (k >= 0) { + b5 = 0; + s5 = k; + s2 += k; + } + else { + b2 -= k; + b5 = -k; + s5 = 0; + } + if (mode < 0 || mode > 9) + mode = 0; #ifndef SET_INEXACT #ifdef Check_FLT_ROUNDS - try_quick = Rounding == 1; + try_quick = Rounding == 1; #else - try_quick = 1; + try_quick = 1; #endif #endif /*SET_INEXACT*/ - if (mode > 5) { - mode -= 4; - try_quick = 0; - } - leftright = 1; - ilim = ilim1 = -1; /* Values for cases 0 and 1; done here to */ - /* silence erroneous "gcc -Wall" warning. */ - switch(mode) { - case 0: - case 1: - i = 18; - ndigits = 0; - break; - case 2: - leftright = 0; - /* no break */ - case 4: - if (ndigits <= 0) - ndigits = 1; - ilim = ilim1 = i = ndigits; - break; - case 3: - leftright = 0; - /* no break */ - case 5: - i = ndigits + k + 1; - ilim = i; - ilim1 = i - 1; - if (i <= 0) - i = 1; - } - s = s0 = rv_alloc(i); + if (mode > 5) { + mode -= 4; + try_quick = 0; + } + leftright = 1; + ilim = ilim1 = -1; /* Values for cases 0 and 1; done here to */ + /* silence erroneous "gcc -Wall" warning. */ + switch(mode) { + case 0: + case 1: + i = 18; + ndigits = 0; + break; + case 2: + leftright = 0; + /* no break */ + case 4: + if (ndigits <= 0) + ndigits = 1; + ilim = ilim1 = i = ndigits; + break; + case 3: + leftright = 0; + /* no break */ + case 5: + i = ndigits + k + 1; + ilim = i; + ilim1 = i - 1; + if (i <= 0) + i = 1; + } + s = s0 = rv_alloc(i); #ifdef Honor_FLT_ROUNDS - if (mode > 1 && Rounding != 1) - leftright = 0; -#endif - - if (ilim >= 0 && ilim <= Quick_max && try_quick) { - - /* Try to get by with floating-point arithmetic. */ - - i = 0; - dval(&d2) = dval(&u); - k0 = k; - ilim0 = ilim; - ieps = 2; /* conservative */ - if (k > 0) { - ds = tens[k&0xf]; - j = k >> 4; - if (j & Bletch) { - /* prevent overflows */ - j &= Bletch - 1; - dval(&u) /= bigtens[n_bigtens-1]; - ieps++; - } - for(; j; j >>= 1, i++) - if (j & 1) { - ieps++; - ds *= bigtens[i]; - } - dval(&u) /= ds; - } - else if ((j1 = -k)) { - dval(&u) *= tens[j1 & 0xf]; - for(j = j1 >> 4; j; j >>= 1, i++) - if (j & 1) { - ieps++; - dval(&u) *= bigtens[i]; - } - } - if (k_check && dval(&u) < 1. && ilim > 0) { - if (ilim1 <= 0) - goto fast_failed; - ilim = ilim1; - k--; - dval(&u) *= 10.; - ieps++; - } - dval(&eps) = ieps*dval(&u) + 7.; - word0(&eps) -= (P-1)*Exp_msk1; - if (ilim == 0) { - S = mhi = 0; - dval(&u) -= 5.; - if (dval(&u) > dval(&eps)) - goto one_digit; - if (dval(&u) < -dval(&eps)) - goto no_digits; - goto fast_failed; - } + if (mode > 1 && Rounding != 1) + leftright = 0; +#endif + + if (ilim >= 0 && ilim <= Quick_max && try_quick) { + + /* Try to get by with floating-point arithmetic. */ + + i = 0; + dval(&d2) = dval(&u); + k0 = k; + ilim0 = ilim; + ieps = 2; /* conservative */ + if (k > 0) { + ds = tens[k&0xf]; + j = k >> 4; + if (j & Bletch) { + /* prevent overflows */ + j &= Bletch - 1; + dval(&u) /= bigtens[n_bigtens-1]; + ieps++; + } + for(; j; j >>= 1, i++) + if (j & 1) { + ieps++; + ds *= bigtens[i]; + } + dval(&u) /= ds; + } + else if ((j1 = -k)) { + dval(&u) *= tens[j1 & 0xf]; + for(j = j1 >> 4; j; j >>= 1, i++) + if (j & 1) { + ieps++; + dval(&u) *= bigtens[i]; + } + } + if (k_check && dval(&u) < 1. && ilim > 0) { + if (ilim1 <= 0) + goto fast_failed; + ilim = ilim1; + k--; + dval(&u) *= 10.; + ieps++; + } + dval(&eps) = ieps*dval(&u) + 7.; + word0(&eps) -= (P-1)*Exp_msk1; + if (ilim == 0) { + S = mhi = 0; + dval(&u) -= 5.; + if (dval(&u) > dval(&eps)) + goto one_digit; + if (dval(&u) < -dval(&eps)) + goto no_digits; + goto fast_failed; + } #ifndef No_leftright - if (leftright) { - /* Use Steele & White method of only - * generating digits needed. - */ - dval(&eps) = 0.5/tens[ilim-1] - dval(&eps); + if (leftright) { + /* Use Steele & White method of only + * generating digits needed. + */ + dval(&eps) = 0.5/tens[ilim-1] - dval(&eps); #ifdef IEEE_Arith - if (k0 < 0 && j1 >= 307) { - eps1.d = 1.01e256; /* 1.01 allows roundoff in the next few lines */ - word0(&eps1) -= Exp_msk1 * (Bias+P-1); - dval(&eps1) *= tens[j1 & 0xf]; - for(i = 0, j = (j1-256) >> 4; j; j >>= 1, i++) - if (j & 1) - dval(&eps1) *= bigtens[i]; - if (eps.d < eps1.d) - eps.d = eps1.d; - } -#endif - for(i = 0;;) { - L = dval(&u); - dval(&u) -= L; - *s++ = '0' + (int)L; - if (1. - dval(&u) < dval(&eps)) - goto bump_up; - if (dval(&u) < dval(&eps)) - goto ret1; - if (++i >= ilim) - break; - dval(&eps) *= 10.; - dval(&u) *= 10.; - } - } - else { -#endif - /* Generate ilim digits, then fix them up. */ - dval(&eps) *= tens[ilim-1]; - for(i = 1;; i++, dval(&u) *= 10.) { - L = (Long)(dval(&u)); - if (!(dval(&u) -= L)) - ilim = i; - *s++ = '0' + (int)L; - if (i == ilim) { - if (dval(&u) > 0.5 + dval(&eps)) - goto bump_up; - else if (dval(&u) < 0.5 - dval(&eps)) { - while(*--s == '0'); - s++; - goto ret1; - } - break; - } - } + if (k0 < 0 && j1 >= 307) { + eps1.d = 1.01e256; /* 1.01 allows roundoff in the next few lines */ + word0(&eps1) -= Exp_msk1 * (Bias+P-1); + dval(&eps1) *= tens[j1 & 0xf]; + for(i = 0, j = (j1-256) >> 4; j; j >>= 1, i++) + if (j & 1) + dval(&eps1) *= bigtens[i]; + if (eps.d < eps1.d) + eps.d = eps1.d; + } +#endif + for(i = 0;;) { + L = dval(&u); + dval(&u) -= L; + *s++ = '0' + (int)L; + if (1. - dval(&u) < dval(&eps)) + goto bump_up; + if (dval(&u) < dval(&eps)) + goto ret1; + if (++i >= ilim) + break; + dval(&eps) *= 10.; + dval(&u) *= 10.; + } + } + else { +#endif + /* Generate ilim digits, then fix them up. */ + dval(&eps) *= tens[ilim-1]; + for(i = 1;; i++, dval(&u) *= 10.) { + L = (Long)(dval(&u)); + if (!(dval(&u) -= L)) + ilim = i; + *s++ = '0' + (int)L; + if (i == ilim) { + if (dval(&u) > 0.5 + dval(&eps)) + goto bump_up; + else if (dval(&u) < 0.5 - dval(&eps)) { + while(*--s == '0'); + s++; + goto ret1; + } + break; + } + } #ifndef No_leftright - } + } #endif fast_failed: - s = s0; - dval(&u) = dval(&d2); - k = k0; - ilim = ilim0; - } - - /* Do we have a "small" integer? */ - - if (be >= 0 && k <= Int_max) { - /* Yes. */ - ds = tens[k]; - if (ndigits < 0 && ilim <= 0) { - S = mhi = 0; - if (ilim < 0 || dval(&u) <= 5*ds) - goto no_digits; - goto one_digit; - } - for(i = 1;; i++, dval(&u) *= 10.) { - L = (Long)(dval(&u) / ds); - dval(&u) -= L*ds; + s = s0; + dval(&u) = dval(&d2); + k = k0; + ilim = ilim0; + } + + /* Do we have a "small" integer? */ + + if (be >= 0 && k <= Int_max) { + /* Yes. */ + ds = tens[k]; + if (ndigits < 0 && ilim <= 0) { + S = mhi = 0; + if (ilim < 0 || dval(&u) <= 5*ds) + goto no_digits; + goto one_digit; + } + for(i = 1;; i++, dval(&u) *= 10.) { + L = (Long)(dval(&u) / ds); + dval(&u) -= L*ds; #ifdef Check_FLT_ROUNDS - /* If FLT_ROUNDS == 2, L will usually be high by 1 */ - if (dval(&u) < 0) { - L--; - dval(&u) += ds; - } -#endif - *s++ = '0' + (int)L; - if (!dval(&u)) { + /* If FLT_ROUNDS == 2, L will usually be high by 1 */ + if (dval(&u) < 0) { + L--; + dval(&u) += ds; + } +#endif + *s++ = '0' + (int)L; + if (!dval(&u)) { #ifdef SET_INEXACT - inexact = 0; + inexact = 0; #endif - break; - } - if (i == ilim) { + break; + } + if (i == ilim) { #ifdef Honor_FLT_ROUNDS - if (mode > 1) - switch(Rounding) { - case 0: goto ret1; - case 2: goto bump_up; - } + if (mode > 1) + switch(Rounding) { + case 0: goto ret1; + case 2: goto bump_up; + } #endif - dval(&u) += dval(&u); + dval(&u) += dval(&u); #ifdef ROUND_BIASED - if (dval(&u) >= ds) + if (dval(&u) >= ds) #else - if (dval(&u) > ds || (dval(&u) == ds && L & 1)) + if (dval(&u) > ds || (dval(&u) == ds && L & 1)) #endif - { + { bump_up: - while(*--s == '9') - if (s == s0) { - k++; - *s = '0'; - break; - } - ++*s++; - } - break; - } - } - goto ret1; - } - - m2 = b2; - m5 = b5; - mhi = mlo = 0; - if (leftright) { - i = + while(*--s == '9') + if (s == s0) { + k++; + *s = '0'; + break; + } + ++*s++; + } + break; + } + } + goto ret1; + } + + m2 = b2; + m5 = b5; + mhi = mlo = 0; + if (leftright) { + i = #ifndef Sudden_Underflow - denorm ? be + (Bias + (P-1) - 1 + 1) : + denorm ? be + (Bias + (P-1) - 1 + 1) : #endif #ifdef IBM - 1 + 4*P - 3 - bbits + ((bbits + be - 1) & 3); + 1 + 4*P - 3 - bbits + ((bbits + be - 1) & 3); #else - 1 + P - bbits; -#endif - b2 += i; - s2 += i; - mhi = i2b(1); - } - if (m2 > 0 && s2 > 0) { - i = m2 < s2 ? m2 : s2; - b2 -= i; - m2 -= i; - s2 -= i; - } - if (b5 > 0) { - if (leftright) { - if (m5 > 0) { - mhi = pow5mult(mhi, m5); - b1 = mult(mhi, b); - Bfree(b); - b = b1; - } - if ((j = b5 - m5)) - b = pow5mult(b, j); - } - else - b = pow5mult(b, b5); - } - S = i2b(1); - if (s5 > 0) - S = pow5mult(S, s5); - - /* Check for special case that d is a normalized power of 2. */ - - spec_case = 0; - if ((mode < 2 || leftright) + 1 + P - bbits; +#endif + b2 += i; + s2 += i; + mhi = i2b(1); + } + if (m2 > 0 && s2 > 0) { + i = m2 < s2 ? m2 : s2; + b2 -= i; + m2 -= i; + s2 -= i; + } + if (b5 > 0) { + if (leftright) { + if (m5 > 0) { + mhi = pow5mult(mhi, m5); + b1 = mult(mhi, b); + Bfree(b); + b = b1; + } + if ((j = b5 - m5)) + b = pow5mult(b, j); + } + else + b = pow5mult(b, b5); + } + S = i2b(1); + if (s5 > 0) + S = pow5mult(S, s5); + + /* Check for special case that d is a normalized power of 2. */ + + spec_case = 0; + if ((mode < 2 || leftright) #ifdef Honor_FLT_ROUNDS - && Rounding == 1 + && Rounding == 1 #endif - ) { - if (!word1(&u) && !(word0(&u) & Bndry_mask) + ) { + if (!word1(&u) && !(word0(&u) & Bndry_mask) #ifndef Sudden_Underflow - && word0(&u) & (Exp_mask & ~Exp_msk1) -#endif - ) { - /* The special case */ - b2 += Log2P; - s2 += Log2P; - spec_case = 1; - } - } - - /* Arrange for convenient computation of quotients: - * shift left if necessary so divisor has 4 leading 0 bits. - * - * Perhaps we should just compute leading 28 bits of S once - * and for all and pass them and a shift to quorem, so it - * can do shifts and ors to compute the numerator for q. - */ - i = dshift(S, s2); - b2 += i; - m2 += i; - s2 += i; - if (b2 > 0) - b = lshift(b, b2); - if (s2 > 0) - S = lshift(S, s2); - if (k_check) { - if (cmp(b,S) < 0) { - k--; - b = multadd(b, 10, 0); /* we botched the k estimate */ - if (leftright) - mhi = multadd(mhi, 10, 0); - ilim = ilim1; - } - } - if (ilim <= 0 && (mode == 3 || mode == 5)) { - if (ilim < 0 || cmp(b,S = multadd(S,5,0)) <= 0) { - /* no digits, fcvt style */ + && word0(&u) & (Exp_mask & ~Exp_msk1) +#endif + ) { + /* The special case */ + b2 += Log2P; + s2 += Log2P; + spec_case = 1; + } + } + + /* Arrange for convenient computation of quotients: + * shift left if necessary so divisor has 4 leading 0 bits. + * + * Perhaps we should just compute leading 28 bits of S once + * and for all and pass them and a shift to quorem, so it + * can do shifts and ors to compute the numerator for q. + */ + i = dshift(S, s2); + b2 += i; + m2 += i; + s2 += i; + if (b2 > 0) + b = lshift(b, b2); + if (s2 > 0) + S = lshift(S, s2); + if (k_check) { + if (cmp(b,S) < 0) { + k--; + b = multadd(b, 10, 0); /* we botched the k estimate */ + if (leftright) + mhi = multadd(mhi, 10, 0); + ilim = ilim1; + } + } + if (ilim <= 0 && (mode == 3 || mode == 5)) { + if (ilim < 0 || cmp(b,S = multadd(S,5,0)) <= 0) { + /* no digits, fcvt style */ no_digits: - k = -1 - ndigits; - goto ret; - } + k = -1 - ndigits; + goto ret; + } one_digit: - *s++ = '1'; - k++; - goto ret; - } - if (leftright) { - if (m2 > 0) - mhi = lshift(mhi, m2); - - /* Compute mlo -- check for special case - * that d is a normalized power of 2. - */ - - mlo = mhi; - if (spec_case) { - mhi = Balloc(mhi->k); - Bcopy(mhi, mlo); - mhi = lshift(mhi, Log2P); - } - - for(i = 1;;i++) { - dig = quorem(b,S) + '0'; - /* Do we yet have the shortest decimal string - * that will round to d? - */ - j = cmp(b, mlo); - delta = diff(S, mhi); - j1 = delta->sign ? 1 : cmp(b, delta); - Bfree(delta); + *s++ = '1'; + k++; + goto ret; + } + if (leftright) { + if (m2 > 0) + mhi = lshift(mhi, m2); + + /* Compute mlo -- check for special case + * that d is a normalized power of 2. + */ + + mlo = mhi; + if (spec_case) { + mhi = Balloc(mhi->k); + Bcopy(mhi, mlo); + mhi = lshift(mhi, Log2P); + } + + for(i = 1;;i++) { + dig = quorem(b,S) + '0'; + /* Do we yet have the shortest decimal string + * that will round to d? + */ + j = cmp(b, mlo); + delta = diff(S, mhi); + j1 = delta->sign ? 1 : cmp(b, delta); + Bfree(delta); #ifndef ROUND_BIASED - if (j1 == 0 && mode != 1 && !(word1(&u) & 1) + if (j1 == 0 && mode != 1 && !(word1(&u) & 1) #ifdef Honor_FLT_ROUNDS - && Rounding >= 1 + && Rounding >= 1 #endif - ) { - if (dig == '9') - goto round_9_up; - if (j > 0) - dig++; + ) { + if (dig == '9') + goto round_9_up; + if (j > 0) + dig++; #ifdef SET_INEXACT - else if (!b->x[0] && b->wds <= 1) - inexact = 0; + else if (!b->x[0] && b->wds <= 1) + inexact = 0; #endif - *s++ = dig; - goto ret; - } + *s++ = dig; + goto ret; + } #endif - if (j < 0 || (j == 0 && mode != 1 + if (j < 0 || (j == 0 && mode != 1 #ifndef ROUND_BIASED - && !(word1(&u) & 1) + && !(word1(&u) & 1) #endif - )) { - if (!b->x[0] && b->wds <= 1) { + )) { + if (!b->x[0] && b->wds <= 1) { #ifdef SET_INEXACT - inexact = 0; + inexact = 0; #endif - goto accept_dig; - } + goto accept_dig; + } #ifdef Honor_FLT_ROUNDS - if (mode > 1) - switch(Rounding) { - case 0: goto accept_dig; - case 2: goto keep_dig; - } + if (mode > 1) + switch(Rounding) { + case 0: goto accept_dig; + case 2: goto keep_dig; + } #endif /*Honor_FLT_ROUNDS*/ - if (j1 > 0) { - b = lshift(b, 1); - j1 = cmp(b, S); + if (j1 > 0) { + b = lshift(b, 1); + j1 = cmp(b, S); #ifdef ROUND_BIASED - if (j1 >= 0 /*)*/ + if (j1 >= 0 /*)*/ #else - if ((j1 > 0 || (j1 == 0 && dig & 1)) + if ((j1 > 0 || (j1 == 0 && dig & 1)) #endif - && dig++ == '9') - goto round_9_up; - } + && dig++ == '9') + goto round_9_up; + } accept_dig: - *s++ = dig; - goto ret; - } - if (j1 > 0) { + *s++ = dig; + goto ret; + } + if (j1 > 0) { #ifdef Honor_FLT_ROUNDS - if (!Rounding) - goto accept_dig; + if (!Rounding) + goto accept_dig; #endif - if (dig == '9') { /* possible if i == 1 */ + if (dig == '9') { /* possible if i == 1 */ round_9_up: - *s++ = '9'; - goto roundoff; - } - *s++ = dig + 1; - goto ret; - } + *s++ = '9'; + goto roundoff; + } + *s++ = dig + 1; + goto ret; + } #ifdef Honor_FLT_ROUNDS keep_dig: #endif - *s++ = dig; - if (i == ilim) - break; - b = multadd(b, 10, 0); - if (mlo == mhi) - mlo = mhi = multadd(mhi, 10, 0); - else { - mlo = multadd(mlo, 10, 0); - mhi = multadd(mhi, 10, 0); - } - } - } - else - for(i = 1;; i++) { - *s++ = dig = quorem(b,S) + '0'; - if (!b->x[0] && b->wds <= 1) { + *s++ = dig; + if (i == ilim) + break; + b = multadd(b, 10, 0); + if (mlo == mhi) + mlo = mhi = multadd(mhi, 10, 0); + else { + mlo = multadd(mlo, 10, 0); + mhi = multadd(mhi, 10, 0); + } + } + } + else + for(i = 1;; i++) { + *s++ = dig = quorem(b,S) + '0'; + if (!b->x[0] && b->wds <= 1) { #ifdef SET_INEXACT - inexact = 0; + inexact = 0; #endif - goto ret; - } - if (i >= ilim) - break; - b = multadd(b, 10, 0); - } + goto ret; + } + if (i >= ilim) + break; + b = multadd(b, 10, 0); + } - /* Round off last digit */ + /* Round off last digit */ #ifdef Honor_FLT_ROUNDS - switch(Rounding) { - case 0: goto trimzeros; - case 2: goto roundoff; - } + switch(Rounding) { + case 0: goto trimzeros; + case 2: goto roundoff; + } #endif - b = lshift(b, 1); - j = cmp(b, S); + b = lshift(b, 1); + j = cmp(b, S); #ifdef ROUND_BIASED - if (j >= 0) + if (j >= 0) #else - if (j > 0 || (j == 0 && dig & 1)) + if (j > 0 || (j == 0 && dig & 1)) #endif - { + { roundoff: - while(*--s == '9') - if (s == s0) { - k++; - *s++ = '1'; - goto ret; - } - ++*s++; - } - else { + while(*--s == '9') + if (s == s0) { + k++; + *s++ = '1'; + goto ret; + } + ++*s++; + } + else { #ifdef Honor_FLT_ROUNDS trimzeros: #endif - while(*--s == '0'); - s++; - } + while(*--s == '0'); + s++; + } ret: - Bfree(S); - if (mhi) { - if (mlo && mlo != mhi) - Bfree(mlo); - Bfree(mhi); - } + Bfree(S); + if (mhi) { + if (mlo && mlo != mhi) + Bfree(mlo); + Bfree(mhi); + } ret1: #ifdef SET_INEXACT - if (inexact) { - if (!oldinexact) { - word0(&u) = Exp_1 + (70 << Exp_shift); - word1(&u) = 0; - dval(&u) += 1.; - } - } - else if (!oldinexact) - clear_inexact(); -#endif - Bfree(b); - *s = 0; - *decpt = k + 1; - if (rve) - *rve = s; - return s0; - } + if (inexact) { + if (!oldinexact) { + word0(&u) = Exp_1 + (70 << Exp_shift); + word1(&u) = 0; + dval(&u) += 1.; + } + } + else if (!oldinexact) + clear_inexact(); +#endif + Bfree(b); + *s = 0; + *decpt = k + 1; + if (rve) + *rve = s; + return s0; + } #ifdef __cplusplus } #endif diff --git a/src/core/f-enbase.c b/src/core/f-enbase.c index 10181c64a0..717c311a7b 100644 --- a/src/core/f-enbase.c +++ b/src/core/f-enbase.c @@ -1,503 +1,531 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: f-enbase.c -** Summary: base representation conversions -** Section: functional -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %f-enbase.c +// Summary: "base representation conversions" +// Section: functional +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -#include "sys-scan.h" -/*********************************************************************** -** -*/ static const REBYTE Debase64[128] = -/* -** Base-64 binary decoder table. -** -***********************************************************************/ +// +// Base-64 binary decoder table. +// +static const REBYTE Debase64[128] = { - #define BIN_ERROR (REBYTE)0x80 - #define BIN_SPACE (REBYTE)0x40 - #define BIN_VALUE (REBYTE)0x3f - #define IS_BIN_SPACE(c) (Debase64[c] & BIN_SPACE) - - /* Control Chars */ - BIN_ERROR,BIN_ERROR,BIN_ERROR,BIN_ERROR, /* 80 */ - BIN_ERROR,BIN_ERROR,BIN_ERROR,BIN_ERROR, - BIN_SPACE,BIN_SPACE,BIN_SPACE,BIN_ERROR, - BIN_SPACE,BIN_SPACE,BIN_ERROR,BIN_ERROR, - BIN_ERROR,BIN_ERROR,BIN_ERROR,BIN_ERROR, - BIN_ERROR,BIN_ERROR,BIN_ERROR,BIN_ERROR, - BIN_ERROR,BIN_ERROR,BIN_ERROR,BIN_ERROR, - BIN_ERROR,BIN_ERROR,BIN_ERROR,BIN_ERROR, - - /* 20 */ BIN_SPACE, - /* 21 ! */ BIN_ERROR, - /* 22 " */ BIN_ERROR, - /* 23 # */ BIN_ERROR, - /* 24 $ */ BIN_ERROR, - /* 25 % */ BIN_ERROR, - /* 26 & */ BIN_ERROR, - /* 27 ' */ BIN_SPACE, - /* 28 ( */ BIN_ERROR, - /* 29 ) */ BIN_ERROR, - /* 2A * */ BIN_ERROR, - /* 2B + */ 62, - /* 2C , */ BIN_ERROR, - /* 2D - */ BIN_ERROR, - /* 2E . */ BIN_ERROR, - /* 2F / */ 63, - - /* 30 0 */ 52, - /* 31 1 */ 53, - /* 32 2 */ 54, - /* 33 3 */ 55, - /* 34 4 */ 56, - /* 35 5 */ 57, - /* 36 6 */ 58, - /* 37 7 */ 59, - /* 38 8 */ 60, - /* 39 9 */ 61, - /* 3A : */ BIN_ERROR, - /* 3B ; */ BIN_ERROR, - /* 3C < */ BIN_ERROR, - /* 3D = */ 0, // pad char - /* 3E > */ BIN_ERROR, - /* 3F ? */ BIN_ERROR, - - /* 40 @ */ BIN_ERROR, - /* 41 A */ 0, - /* 42 B */ 1, - /* 43 C */ 2, - /* 44 D */ 3, - /* 45 E */ 4, - /* 46 F */ 5, - /* 47 G */ 6, - /* 48 H */ 7, - /* 49 I */ 8, - /* 4A J */ 9, - /* 4B K */ 10, - /* 4C L */ 11, - /* 4D M */ 12, - /* 4E N */ 13, - /* 4F O */ 14, - - /* 50 P */ 15, - /* 51 Q */ 16, - /* 52 R */ 17, - /* 53 S */ 18, - /* 54 T */ 19, - /* 55 U */ 20, - /* 56 V */ 21, - /* 57 W */ 22, - /* 58 X */ 23, - /* 59 Y */ 24, - /* 5A Z */ 25, - /* 5B [ */ BIN_ERROR, - /* 5C \ */ BIN_ERROR, - /* 5D ] */ BIN_ERROR, - /* 5E ^ */ BIN_ERROR, - /* 5F _ */ BIN_ERROR, - - /* 60 ` */ BIN_ERROR, - /* 61 a */ 26, - /* 62 b */ 27, - /* 63 c */ 28, - /* 64 d */ 29, - /* 65 e */ 30, - /* 66 f */ 31, - /* 67 g */ 32, - /* 68 h */ 33, - /* 69 i */ 34, - /* 6A j */ 35, - /* 6B k */ 36, - /* 6C l */ 37, - /* 6D m */ 38, - /* 6E n */ 39, - /* 6F o */ 40, - - /* 70 p */ 41, - /* 71 q */ 42, - /* 72 r */ 43, - /* 73 s */ 44, - /* 74 t */ 45, - /* 75 u */ 46, - /* 76 v */ 47, - /* 77 w */ 48, - /* 78 x */ 49, - /* 79 y */ 50, - /* 7A z */ 51, - /* 7B { */ BIN_ERROR, - /* 7C | */ BIN_ERROR, - /* 7D } */ BIN_ERROR, - /* 7E ~ */ BIN_ERROR, - /* 7F DEL */ BIN_ERROR, + #define BIN_ERROR (REBYTE)0x80 + #define BIN_SPACE (REBYTE)0x40 + #define BIN_VALUE (REBYTE)0x3f + #define IS_BIN_SPACE(c) LOGICAL(Debase64[c] & BIN_SPACE) + + /* Control Chars */ + BIN_ERROR,BIN_ERROR,BIN_ERROR,BIN_ERROR, /* 80 */ + BIN_ERROR,BIN_ERROR,BIN_ERROR,BIN_ERROR, + BIN_SPACE,BIN_SPACE,BIN_SPACE,BIN_ERROR, + BIN_SPACE,BIN_SPACE,BIN_ERROR,BIN_ERROR, + BIN_ERROR,BIN_ERROR,BIN_ERROR,BIN_ERROR, + BIN_ERROR,BIN_ERROR,BIN_ERROR,BIN_ERROR, + BIN_ERROR,BIN_ERROR,BIN_ERROR,BIN_ERROR, + BIN_ERROR,BIN_ERROR,BIN_ERROR,BIN_ERROR, + + /* 20 */ BIN_SPACE, + /* 21 ! */ BIN_ERROR, + /* 22 " */ BIN_ERROR, + /* 23 # */ BIN_ERROR, + /* 24 $ */ BIN_ERROR, + /* 25 % */ BIN_ERROR, + /* 26 & */ BIN_ERROR, + /* 27 ' */ BIN_SPACE, + /* 28 ( */ BIN_ERROR, + /* 29 ) */ BIN_ERROR, + /* 2A * */ BIN_ERROR, + /* 2B + */ 62, + /* 2C , */ BIN_ERROR, + /* 2D - */ BIN_ERROR, + /* 2E . */ BIN_ERROR, + /* 2F / */ 63, + + /* 30 0 */ 52, + /* 31 1 */ 53, + /* 32 2 */ 54, + /* 33 3 */ 55, + /* 34 4 */ 56, + /* 35 5 */ 57, + /* 36 6 */ 58, + /* 37 7 */ 59, + /* 38 8 */ 60, + /* 39 9 */ 61, + /* 3A : */ BIN_ERROR, + /* 3B ; */ BIN_ERROR, + /* 3C < */ BIN_ERROR, + /* 3D = */ 0, // pad char + /* 3E > */ BIN_ERROR, + /* 3F ? */ BIN_ERROR, + + /* 40 @ */ BIN_ERROR, + /* 41 A */ 0, + /* 42 B */ 1, + /* 43 C */ 2, + /* 44 D */ 3, + /* 45 E */ 4, + /* 46 F */ 5, + /* 47 G */ 6, + /* 48 H */ 7, + /* 49 I */ 8, + /* 4A J */ 9, + /* 4B K */ 10, + /* 4C L */ 11, + /* 4D M */ 12, + /* 4E N */ 13, + /* 4F O */ 14, + + /* 50 P */ 15, + /* 51 Q */ 16, + /* 52 R */ 17, + /* 53 S */ 18, + /* 54 T */ 19, + /* 55 U */ 20, + /* 56 V */ 21, + /* 57 W */ 22, + /* 58 X */ 23, + /* 59 Y */ 24, + /* 5A Z */ 25, + /* 5B [ */ BIN_ERROR, + /* 5C \ */ BIN_ERROR, + /* 5D ] */ BIN_ERROR, + /* 5E ^ */ BIN_ERROR, + /* 5F _ */ BIN_ERROR, + + /* 60 ` */ BIN_ERROR, + /* 61 a */ 26, + /* 62 b */ 27, + /* 63 c */ 28, + /* 64 d */ 29, + /* 65 e */ 30, + /* 66 f */ 31, + /* 67 g */ 32, + /* 68 h */ 33, + /* 69 i */ 34, + /* 6A j */ 35, + /* 6B k */ 36, + /* 6C l */ 37, + /* 6D m */ 38, + /* 6E n */ 39, + /* 6F o */ 40, + + /* 70 p */ 41, + /* 71 q */ 42, + /* 72 r */ 43, + /* 73 s */ 44, + /* 74 t */ 45, + /* 75 u */ 46, + /* 76 v */ 47, + /* 77 w */ 48, + /* 78 x */ 49, + /* 79 y */ 50, + /* 7A z */ 51, + /* 7B { */ BIN_ERROR, + /* 7C | */ BIN_ERROR, + /* 7D } */ BIN_ERROR, + /* 7E ~ */ BIN_ERROR, + /* 7F DEL */ BIN_ERROR, }; -/*********************************************************************** -** -*/ static const REBYTE Enbase64[64] = -/* -** Base-64 binary encoder table. -** -***********************************************************************/ +// Base-64 binary encoder table. +// +// NOTE: Entered one-character-at-a-time in array initialization +// format to avoid the length of 65 which would be needed if +// a string literal were used. This helps memory tools trap +// errant accesses to Enbase64[64] if there's an algorithm bug. +// +static const REBYTE Enbase64[64] = { - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/" + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; -/*********************************************************************** -** -*/ static REBSER *Decode_Base2(REBYTE **src, REBCNT len, REBYTE delim) -/* -***********************************************************************/ +// +// Decode_Base2: C +// +static REBSER *Decode_Base2(const REBYTE **src, REBCNT len, REBYTE delim) { - REBYTE *bp; - REBYTE *cp; - REBCNT count = 0; - REBINT accum = 0; - REBYTE lex; - REBSER *ser; + REBYTE *bp; + const REBYTE *cp; + REBCNT count = 0; + REBCNT accum = 0; + REBYTE lex; + REBSER *ser; - ser = Make_Binary(len >> 3); - bp = BIN_HEAD(ser); - cp = *src; + ser = Make_Binary(len >> 3); + bp = BIN_HEAD(ser); + cp = *src; - for (; len > 0; cp++, len--) { + for (; len > 0; cp++, len--) { - if (delim && *cp == delim) break; + if (delim && *cp == delim) break; - lex = Lex_Map[*cp]; + lex = Lex_Map[*cp]; - if (lex >= LEX_NUMBER) { + if (lex >= LEX_NUMBER) { - if (*cp == '0') accum *= 2; - else if (*cp == '1') accum = (accum * 2) + 1; - else goto err; + if (*cp == '0') accum *= 2; + else if (*cp == '1') accum = (accum * 2) + 1; + else goto err; - if (count++ >= 7) { - *bp++ = (REBYTE)accum; - count = 0; - accum = 0; - } - } - else if (!*cp || lex > LEX_DELIMIT_RETURN) goto err; - } - if (count) goto err; // improper modulus + if (count++ >= 7) { + *bp++ = cast(REBYTE, accum); + count = 0; + accum = 0; + } + } + else if (!*cp || lex > LEX_DELIMIT_RETURN) goto err; + } + if (count) goto err; // improper modulus - *bp = 0; - ser->tail = bp - STR_HEAD(ser); - return ser; + *bp = 0; + SET_SERIES_LEN(ser, bp - BIN_HEAD(ser)); + ASSERT_SERIES_TERM(ser); + return ser; err: - Free_Series(ser); - *src = cp; - return 0; + Free_Series(ser); + *src = cp; + return 0; } -/*********************************************************************** -** -*/ static REBSER *Decode_Base16(REBYTE **src, REBCNT len, REBYTE delim) -/* -***********************************************************************/ +// +// Decode_Base16: C +// +static REBSER *Decode_Base16(const REBYTE **src, REBCNT len, REBYTE delim) { - REBYTE *bp; - REBYTE *cp; - REBCNT count = 0; - REBINT accum = 0; - REBYTE lex; - REBINT val; - REBSER *ser; - - ser = Make_Binary(len / 2); - bp = STR_HEAD(ser); - cp = *src; - - for (; len > 0; cp++, len--) { - - if (delim && *cp == delim) break; - - lex = Lex_Map[*cp]; - - if (lex > LEX_WORD) { - val = lex & LEX_VALUE; // char num encoded into lex - if (!val && lex < LEX_NUMBER) goto err; // invalid char (word but no val) - accum = (accum << 4) + val; - if (count++ & 1) *bp++ = (REBYTE)accum; - } - else if (!*cp || lex > LEX_DELIMIT_RETURN) goto err; - } - if (count & 1) goto err; // improper modulus - - *bp = 0; - ser->tail = bp - STR_HEAD(ser); - return ser; + REBYTE *bp; + const REBYTE *cp; + REBCNT count = 0; + REBCNT accum = 0; + REBYTE lex; + REBINT val; + REBSER *ser; + + ser = Make_Binary(len / 2); + bp = BIN_HEAD(ser); + cp = *src; + + for (; len > 0; cp++, len--) { + + if (delim && *cp == delim) break; + + lex = Lex_Map[*cp]; + + if (lex > LEX_WORD) { + val = lex & LEX_VALUE; // char num encoded into lex + if (!val && lex < LEX_NUMBER) goto err; // invalid char (word but no val) + accum = (accum << 4) + val; + if (count++ & 1) *bp++ = cast(REBYTE, accum); + } + else if (!*cp || lex > LEX_DELIMIT_RETURN) goto err; + } + if (count & 1) goto err; // improper modulus + + *bp = 0; + SET_SERIES_LEN(ser, bp - BIN_HEAD(ser)); + ASSERT_SERIES_TERM(ser); + return ser; err: - Free_Series(ser); - *src = cp; - return 0; + Free_Series(ser); + *src = cp; + return 0; } - -/*********************************************************************** -** -*/ static REBSER *Decode_Base64(REBYTE **src, REBCNT len, REBYTE delim) -/* -***********************************************************************/ + +// +// Decode_Base64: C +// +static REBSER *Decode_Base64(const REBYTE **src, REBCNT len, REBYTE delim) { - REBYTE *bp; - REBYTE *cp; - REBCNT flip = 0; - REBINT accum = 0; - REBYTE lex; - REBSER *ser; - - // Allocate buffer large enough to hold result: - // Accounts for e bytes decoding into 3 bytes. - ser = Make_Binary(((len + 3) * 3) / 4); - bp = STR_HEAD(ser); - cp = *src; - - for (; len > 0; cp++, len--) { - - // Check for terminating delimiter (optional): - if (delim && *cp == delim) break; - - // Check for char out of range: - if (*cp > 127) { - if (*cp == 0xA0) continue; // hard space - goto err; - } - - lex = Debase64[*cp]; - - if (lex < BIN_SPACE) { - - if (*cp != '=') { - accum = (accum << 6) + lex; - if (flip++ == 3) { - *bp++ = (REBYTE)(accum >> 16); - *bp++ = (REBYTE)(accum >> 8); - *bp++ = (REBYTE)(accum); - accum = 0; - flip = 0; - } - } else { - // Special padding: "=" - cp++; - len--; - if (flip == 3) { - *bp++ = (REBYTE)(accum >> 10); - *bp++ = (REBYTE)(accum >> 2); - flip = 0; - } - else if (flip == 2) { - if (!Skip_To_Char(cp, cp + len, '=')) goto err; - cp++; - *bp++ = (REBYTE)(accum >> 4); - flip = 0; - } - else goto err; - break; - } - } - else if (lex == BIN_ERROR) goto err; - } - - if (flip) goto err; - - *bp = 0; - ser->tail = bp - STR_HEAD(ser); - return ser; + REBYTE *bp; + const REBYTE *cp; + REBCNT flip = 0; + REBCNT accum = 0; + REBYTE lex; + REBSER *ser; + + // Allocate buffer large enough to hold result: + // Accounts for e bytes decoding into 3 bytes. + ser = Make_Binary(((len + 3) * 3) / 4); + bp = BIN_HEAD(ser); + cp = *src; + + for (; len > 0; cp++, len--) { + + // Check for terminating delimiter (optional): + if (delim && *cp == delim) break; + + // Check for char out of range: + if (*cp > 127) { + if (*cp == 0xA0) continue; // hard space + goto err; + } + + lex = Debase64[*cp]; + + if (lex < BIN_SPACE) { + + if (*cp != '=') { + accum = (accum << 6) + lex; + if (flip++ == 3) { + *bp++ = cast(REBYTE, accum >> 16); + *bp++ = cast(REBYTE, accum >> 8); + *bp++ = cast(REBYTE, accum); + accum = 0; + flip = 0; + } + } else { + // Special padding: "=" + cp++; + len--; + if (flip == 3) { + *bp++ = cast(REBYTE, accum >> 10); + *bp++ = cast(REBYTE, accum >> 2); + flip = 0; + } + else if (flip == 2) { + if (!Skip_To_Byte(cp, cp + len, '=')) goto err; + cp++; + *bp++ = cast(REBYTE, accum >> 4); + flip = 0; + } + else goto err; + break; + } + } + else if (lex == BIN_ERROR) goto err; + } + + if (flip) goto err; + + *bp = 0; + SET_SERIES_LEN(ser, bp - BIN_HEAD(ser)); + ASSERT_SERIES_TERM(ser); + return ser; err: - Free_Series(ser); - *src = cp; - return 0; + Free_Series(ser); + *src = cp; + return 0; } -/*********************************************************************** -** -*/ REBYTE *Decode_Binary(REBVAL *value, REBYTE *src, REBCNT len, REBINT base, REBYTE delim) -/* -** Scan and convert a binary string. -** -***********************************************************************/ -{ - REBSER *ser = 0; - - switch (base) { - case 64: - ser = Decode_Base64(&src, len, delim); - break; - case 16: - ser = Decode_Base16(&src, len, delim); - break; - case 2: - ser = Decode_Base2 (&src, len, delim); - break; - } +// +// Decode_Binary: C +// +// Scan and convert a binary string. +// +const REBYTE *Decode_Binary( + REBVAL *value, + const REBYTE *src, + REBCNT len, + REBINT base, + REBYTE delim +) { + REBSER *ser = 0; + + switch (base) { + case 64: + ser = Decode_Base64(&src, len, delim); + break; + case 16: + ser = Decode_Base16(&src, len, delim); + break; + case 2: + ser = Decode_Base2 (&src, len, delim); + break; + } + + if (!ser) return 0; + + Init_Binary(value, ser); + + return src; +} - if (!ser) return 0; - Set_Binary(value, ser); +// +// Encode_Base2: C +// +// Base2 encode a given series. Must be BYTES, not UNICODE. +// +REBSER *Encode_Base2(const REBVAL *value, REBSER *series, REBOOL brk) +{ + REBYTE *p; // ?? should it be REBYTE? Same with below functions? + REBYTE *src; + REBINT len; + REBINT i; + REBINT n; + REBYTE b; - return src; -} + len = VAL_LEN_AT(value); + src = VAL_BIN_AT(value); + // Add slop-factor + series = Prep_String(series, &p, 8 * len + 2 * (len / 8) + 4); -/*********************************************************************** -** -*/ REBSER *Encode_Base2(REBVAL *value, REBSER *series, REBFLG brk) -/* -** Base2 encode a given series. Must be BYTES, not UNICODE. -** -***********************************************************************/ -{ - REBYTE *p; // ?? should it be REBYTE? Same with below functions? - REBYTE *src; - REBINT len; - REBINT i; - REBINT n; - REBYTE b; + // If the input series was zero length, return empty series + if (len == 0) { + TERM_SEQUENCE_LEN(series, 0); + return series; + } - len = VAL_LEN(value); - src = VAL_BIN_DATA(value); + if (len > 8 && brk) *p++ = LF; - // Add slop-factor - series = Prep_String (series, &p, 8 * len + 2 * (len / 8) + 4); - if (len > 8 && brk) *p++ = LF; + for (i = 0; i < len; i++) { - for (i = 0; i < len; i++) { + b = src[i]; - b = src[i]; + for (n = 0x80; n > 0; n = n>>1) { + *p++ = (b & n) ? '1' : '0'; + } - for (n = 0x80; n > 0; n = n>>1) { - *p++ = (b & n) ? '1' : '0'; - } - - if ((i+1) % 8 == 0 && brk) - *p++ = LF; - } - *p = 0; + if ((i+1) % 8 == 0 && brk) + *p++ = LF; + } + *p = 0; - if (*(p-1) != LF && len > 9 && brk) *p++ = LF; + if (*(p-1) != LF && len > 9 && brk) *p++ = LF; - SERIES_TAIL(series) = DIFF_PTRS(p, series->data); - return series; + SET_SERIES_LEN(series, cast(REBCNT, p - BIN_HEAD(series))); + ASSERT_SERIES_TERM(series); + return series; } -/*********************************************************************** -** -*/ REBSER *Encode_Base16(REBVAL *value, REBSER *series, REBFLG brk) -/* -** Base16 encode a given series. Must be BYTES, not UNICODE. -** -***********************************************************************/ +// +// Encode_Base16: C +// +// Base16 encode a given series. Must be BYTES, not UNICODE. +// +REBSER *Encode_Base16(const REBVAL *value, REBSER *series, REBOOL brk) { - REBCNT count; - REBCNT len; - REBYTE *bp; - REBYTE *src; - - len = VAL_LEN(value); - src = VAL_BIN_DATA(value); - - // Account for hex, lines, and extra syntax: - series = Prep_String(series, &bp, len*2 + len/32 + 32); - // (Note: tail not properly set yet) - - if (len >= 32 && brk) *bp++ = LF; - for (count = 1; count <= len; count++) { - bp = Form_Hex2(bp, *src++); - if (brk && ((count % 32) == 0)) *bp++ = LF; - } - - if (*(bp-1) != LF && (len >= 32) && brk) *bp++ = LF; - *bp = 0; - - SERIES_TAIL(series) = DIFF_PTRS(bp, series->data); - - return series; + REBCNT count; + REBCNT len; + REBYTE *bp; + REBYTE *src; + + len = VAL_LEN_AT(value); + src = VAL_BIN_AT(value); + + // Account for hex, lines, and extra syntax: + series = Prep_String(series, &bp, len*2 + len/32 + 32); + // (Note: tail not properly set yet) + + // If the input series was zero length, return empty series + if (len == 0) { + TERM_SEQUENCE_LEN(series, 0); + return series; + } + + if (len >= 32 && brk) *bp++ = LF; + for (count = 1; count <= len; count++) { + bp = Form_Hex2(bp, *src++); + if (brk && ((count % 32) == 0)) *bp++ = LF; + } + + if (*(bp-1) != LF && (len >= 32) && brk) *bp++ = LF; + *bp = 0; + + SET_SERIES_LEN(series, cast(REBCNT, bp - BIN_HEAD(series))); + ASSERT_SERIES_TERM(series); + return series; } -/*********************************************************************** -** -*/ REBSER *Encode_Base64(REBVAL *value, REBSER *series, REBFLG brk) -/* -** Base64 encode a given series. Must be BYTES, not UNICODE. -** -***********************************************************************/ +// +// Encode_Base64: C +// +// Base64 encode a given series. Must be BYTES, not UNICODE. +// +REBSER *Encode_Base64(const REBVAL *value, REBSER *series, REBOOL brk) { - REBYTE *p; - REBYTE *src; - REBCNT len; - REBINT x, loop; - - len = VAL_LEN(value); - src = VAL_BIN(value); - - // slop-factor - series = Prep_String (series, &p, 4 * len / 3 + 2 * (len / 32) + 5); - loop = (int) (len / 3) - 1; - if (4 * loop > 64 && brk) *p++ = LF; - - for (x = 0; x <= 3 * loop; x += 3) { - *p++ = Enbase64[src[x] >> 2]; - *p++ = Enbase64[((src[x] & 0x3) << 4) + (src[x + 1] >> 4)]; - *p++ = Enbase64[((src[x + 1] & 0xF) << 2) + (src[x + 2] >> 6)]; - *p++ = Enbase64[(src[x + 2] % 0x40)]; - if ((x+3) % 48 == 0 && brk) - *p++ = LF; - } - - if ((len % 3) != 0) { - p[2] = p[3] = '='; - *p++ = Enbase64[src[x] >> 2]; - if ((len - x) >= 1) - *p++ = Enbase64[((src[x] & 0x3) << 4) + ((len - x) == 1 ? 0 : src[x + 1] >> 4)]; - else p++; - if ((len - x) == 2) - *p++ = Enbase64[(src[x + 1] & 0xF) << 2]; - else p++; - p++; - } - - if (*(p-1) != LF && x > 49 && brk) *p++ = LF; - *p = 0; - - SERIES_TAIL(series) = DIFF_PTRS(p, series->data); /* 4 * (int) (len % 3 ? (len / 3) + 1 : len / 3); */ - - return series; + REBYTE *p; + REBYTE *src; + REBCNT len; + REBINT x, loop; + + len = VAL_LEN_AT(value); + src = VAL_BIN(value); + + // slop-factor + series = Prep_String (series, &p, 4 * len / 3 + 2 * (len / 32) + 5); + + // If the input series was zero length, return empty series + if (len == 0) { + TERM_SEQUENCE_LEN(series, 0); + return series; + } + + loop = (int) (len / 3) - 1; + if (4 * loop > 64 && brk) *p++ = LF; + + for (x = 0; x <= 3 * loop; x += 3) { + *p++ = Enbase64[src[x] >> 2]; + *p++ = Enbase64[((src[x] & 0x3) << 4) + (src[x + 1] >> 4)]; + *p++ = Enbase64[((src[x + 1] & 0xF) << 2) + (src[x + 2] >> 6)]; + *p++ = Enbase64[(src[x + 2] % 0x40)]; + if ((x+3) % 48 == 0 && brk) + *p++ = LF; + } + + if ((len % 3) != 0) { + p[2] = p[3] = '='; + *p++ = Enbase64[src[x] >> 2]; + if ((len - x) >= 1) + *p++ = Enbase64[ + ((src[x] & 0x3) << 4) + + ((len - x) == 1 ? 0 : src[x + 1] >> 4) + ]; + else p++; + if ((len - x) == 2) + *p++ = Enbase64[(src[x + 1] & 0xF) << 2]; + else p++; + p++; + } + + if (*(p-1) != LF && x > 49 && brk) *p++ = LF; + *p = 0; + + // + // !!! "4 * (int) (len % 3 ? (len / 3) + 1 : len / 3);" ...? + // + SET_SERIES_LEN(series, cast(REBCNT, p - BIN_HEAD(series))); + ASSERT_SERIES_TERM(series); + return series; } diff --git a/src/core/f-extension.c b/src/core/f-extension.c index b504f5634e..d06d670ef4 100644 --- a/src/core/f-extension.c +++ b/src/core/f-extension.c @@ -1,64 +1,63 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: f-extension.c -** Summary: support for extensions -** Section: functional -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %f-extension.c +// Summary: "support for extensions" +// Section: functional +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// NOTE: The R3-Alpha extension mechanism and API are deprecated in Ren-C. +// +// See %reb-ext.h for a general overview of R3-Alpha extensions. Also: +// +// http://www.rebol.com/r3/docs/concepts/extensions-embedded.html +// #include "sys-core.h" -#include "reb-ext.h" // includes copy of ext-types.h +#include "reb-ext.h" #include "reb-evtypes.h" #include "reb-lib.h" - -// Extension evaluation categories: -enum { - RXE_NULL, // unset - RXE_PTR, // any pointer - RXE_32, // logic - RXE_64, // integer, decimal, etc. - RXE_SYM, // word - RXE_SER, // string - RXE_IMAGE, // image - RXE_DATE, // from upper section - RXE_MAX -}; +#include "sys-ext.h" //(*call)(int cmd, RXIFRM *args); -typedef struct reb_ext { - RXICAL call; // Call(function) entry point - void *dll; // DLL library "handle" - int index; // Index in extension table - int object; // extension object reference -} REBEXT; +typedef struct rxi_cmd_context { + void *envr; // for holding a reference to your environment + REBARR *block; // block being evaluated + REBCNT index; // 0-based index of current command in block +} REBCEC; -#include "tmp-exttypes.h" +typedef int (*RXICAL)(int cmd, const REBVAL *frame, REBCEC *ctx); -extern const REBDOF Func_Dispatch[]; +typedef struct reb_ext { + RXICAL call; // Call(function) entry point + void *dll; // DLL library "handle" + int index; // Index in extension table + int object; // extension object reference +} REBEXT; // !!!! The list below should not be hardcoded, but until someone // needs a lot of extensions, it will do fine. @@ -66,583 +65,390 @@ REBEXT Ext_List[64]; REBCNT Ext_Next = 0; -/*********************************************************************** -** -** Local functions -** -***********************************************************************/ +typedef REBYTE *(INFO_FUNC)(REBINT opts, void *lib); -/*********************************************************************** -** -x*/ RXIARG Value_To_RXI(REBVAL *val) -/* -***********************************************************************/ +// +// Just an ID for the handler +// +static void cleanup_extension_init_handler(const REBVAL *val) { - RXIARG arg; - - switch (RXT_Eval_Class[Reb_To_RXT[VAL_TYPE(val)]]) { - case RXE_64: - arg.int64 = VAL_INT64(val); - break; - case RXE_SER: - arg.series = VAL_SERIES(val); - arg.index = VAL_INDEX(val); - break; - case RXE_PTR: - arg.addr = VAL_HANDLE(val); - break; - case RXE_32: - arg.int32a = VAL_I32(val); - arg.int32b = 0; - break; - case RXE_DATE: - arg.int32a = VAL_ALL_BITS(val)[2]; - arg.int32b = 0; - break; - case RXE_SYM: - arg.int32a = VAL_WORD_CANON(val); - arg.int32b = 0; - break; - case RXE_IMAGE: - arg.series = VAL_SERIES(val); - arg.width = VAL_IMAGE_WIDE(val); - arg.height = VAL_IMAGE_HIGH(val); - break; - case RXE_NULL: - default: - arg.int64 = 0; - break; - } - return arg; + UNUSED(val); } -/*********************************************************************** -** -x*/ void RXI_To_Value(REBVAL *val, RXIARG arg, REBCNT type) -/* -***********************************************************************/ +static void cleanup_extension_quit_handler(const REBVAL *val) { - VAL_SET(val, RXT_To_Reb[type]); - switch (RXT_Eval_Class[type]) { - case RXE_64: - VAL_INT64(val) = arg.int64; - break; - case RXE_SER: - VAL_SERIES(val) = arg.series; - VAL_INDEX(val) = arg.index; - break; - case RXE_PTR: - VAL_HANDLE(val) = arg.addr; - break; - case RXE_32: - VAL_I32(val) = arg.int32a; - break; - case RXE_DATE: - VAL_TIME(val) = NO_TIME; - VAL_ALL_BITS(val)[2] = arg.int32a; - break; - case RXE_SYM: - VAL_WORD_SYM(val) = arg.int32a; - VAL_WORD_FRAME(val) = 0; - VAL_WORD_INDEX(val) = 0; - break; - case RXE_IMAGE: - VAL_SERIES(val) = arg.series; - VAL_IMAGE_WIDE(val) = arg.width; - VAL_IMAGE_HIGH(val) = arg.height; - break; - case RXE_NULL: - VAL_INT64(val) = 0; - break; - default: - SET_NONE(val); - } + UNUSED(val); } -/*********************************************************************** -** -x*/ void RXI_To_Block(RXIFRM *frm, REBVAL *out) { -/* -***********************************************************************/ - REBCNT n; - REBSER *blk; - REBVAL *val; - REBCNT len; - - blk = Make_Block(len = RXA_COUNT(frm)); - for (n = 1; n <= len; n++) { - val = Append_Value(blk); - RXI_To_Value(val, frm->args[n], RXA_TYPE(frm, n)); - } - Set_Block(out, blk); +// +// load-extension-helper: native [ +// +// "Low level extension module loader (for DLLs)." +// +// path-or-handle [file! handle!] "Path to the extension file or handle to a builtin extension" +// ] +// +REBNATIVE(load_extension_helper) +// +// Low level extension loader: +// +// 1. Opens the DLL for the extension +// 2. Calls RX_Init() to initialize and get its definition header (REBOL) +// 3. Creates a extension object and returns it +// 4. REBOL code then uses that object to define the extension module +// including natives, data, exports, etc. +// +// Each extension is defined as DLL with: +// +// RX_Init() - init anything needed +// optinoal RX_Quit() - cleanup anything needed +{ + INCLUDE_PARAMS_OF_LOAD_EXTENSION_HELPER; + + REBCTX *std_ext_ctx = VAL_CONTEXT(Get_System(SYS_STANDARD, STD_EXTENSION)); + REBCTX *context; + + if (IS_FILE(ARG(path_or_handle))) { + REBVAL *path = ARG(path_or_handle); + + //Check_Security(SYM_EXTENSION, POL_EXEC, val); + + DECLARE_LOCAL (lib); + MAKE_Library(lib, REB_LIBRARY, path); + + // check if it's reloading an existing extension + REBVAL *loaded_exts = CTX_VAR(VAL_CONTEXT(ROOT_SYSTEM), SYS_EXTENSIONS); + if (IS_BLOCK(loaded_exts)) { + RELVAL *item = VAL_ARRAY_HEAD(loaded_exts); + for (; NOT_END(item); ++item) { + // do some sanity checking, just to avoid crashing if system/extensions was messed up + if (!IS_OBJECT(item)) + fail(Error_Bad_Extension_Raw(item)); + + REBCTX *item_ctx = VAL_CONTEXT(item); + if ((CTX_LEN(item_ctx) <= STD_EXTENSION_LIB_BASE) + || CTX_KEY_SPELLING(item_ctx, STD_EXTENSION_LIB_BASE) + != CTX_KEY_SPELLING(std_ext_ctx, STD_EXTENSION_LIB_BASE) + ) { + fail(Error_Bad_Extension_Raw(item)); + } + else { + if (IS_BLANK(CTX_VAR(item_ctx, STD_EXTENSION_LIB_BASE))) {//builtin extension + continue; + } + } + + assert(IS_LIBRARY(CTX_VAR(item_ctx, STD_EXTENSION_LIB_BASE))); + + if (VAL_LIBRARY_FD(lib) + == VAL_LIBRARY_FD(CTX_VAR(item_ctx, STD_EXTENSION_LIB_BASE))) { + // found the existing extension + OS_CLOSE_LIBRARY(VAL_LIBRARY_FD(lib)); //decrease the reference added by MAKE_library + Move_Value(D_OUT, KNOWN(item)); + return R_OUT; + } + } + } + context = Copy_Context_Shallow(std_ext_ctx); + Move_Value(CTX_VAR(context, STD_EXTENSION_LIB_BASE), lib); + Move_Value(CTX_VAR(context, STD_EXTENSION_LIB_FILE), path); + + CFUNC *RX_Init = OS_FIND_FUNCTION(VAL_LIBRARY_FD(lib), "RX_Init"); + if (RX_Init == NULL) { + OS_CLOSE_LIBRARY(VAL_LIBRARY_FD(lib)); + fail(Error_Bad_Extension_Raw(path)); + } + + // Call its RX_Init function for header and code body: + if (cast(INIT_FUNC, RX_Init)(CTX_VAR(context, STD_EXTENSION_SCRIPT), + CTX_VAR(context, STD_EXTENSION_MODULES)) < 0) { + OS_CLOSE_LIBRARY(VAL_LIBRARY_FD(lib)); + fail(Error_Extension_Init_Raw(path)); + } + } + else { + assert(IS_HANDLE(ARG(path_or_handle))); + REBVAL *handle = ARG(path_or_handle); + if (VAL_HANDLE_CLEANER(handle) != cleanup_extension_init_handler) + fail(Error_Bad_Extension_Raw(handle)); + + INIT_FUNC RX_Init = cast(INIT_FUNC, VAL_HANDLE_CFUNC(handle)); + context = Copy_Context_Shallow(std_ext_ctx); + if ( + RX_Init( + CTX_VAR(context, STD_EXTENSION_SCRIPT), + CTX_VAR(context, STD_EXTENSION_MODULES) + ) < 0 + ){ + fail(Error_Extension_Init_Raw(handle)); + } + } + + Init_Object(D_OUT, context); + return R_OUT; } -/*********************************************************************** -** -x*/ int Do_Callback(REBSER *obj, u32 name, RXIARG *args, RXIARG *result) -/* -** Given an object and a word id, call a REBOL function. -** The arguments are converted from extension format directly -** to the data stack. The result is passed back in ext format, -** with the datatype returned or zero if there was a problem. -** -***********************************************************************/ +// +// unload-extension-helper: native [ +// +// "Unload an extension" +// +// return: [] +// ext [object!] +// "The extension to be unloaded" +// /cleanup +// cleaner [handle!] +// "The RX_Quit pointer for the builtin extension" +// ] +// +REBNATIVE(unload_extension_helper) { - REBVAL *val; - REBCNT dsf; - REBCNT len; - REBCNT n; - REBCNT dsp = DSP; // to restore stack on errors - - // Find word in object, verify it is a function. - if (!(val = Find_Word_Value(obj, name))) { - SET_EXT_ERROR(result, RXE_NO_WORD); - return 0; - } - if (!ANY_FUNC(val)) { - SET_EXT_ERROR(result, RXE_NOT_FUNC); - return 0; - } - - // Get block and index from prior function stack frame: - dsf = PRIOR_DSF(DSF); - - // Create stack frame (use prior stack frame for location info): - dsf = Push_Func(0, VAL_SERIES(DSF_BACK(dsf)), VAL_INDEX(DSF_BACK(dsf)), name, val); - val = DSF_FUNC(dsf); // for safety from GC - obj = VAL_FUNC_WORDS(val); // func words - len = SERIES_TAIL(obj)-1; // number of args (may include locals) - - // Push args. Too short or too long arg frames are handled W/O error. - // Note that refinements args can be set to anything. - for (n = 1; n <= len && n <= RXI_COUNT(args); n++) { - DS_SKIP; - RXI_To_Value(DS_TOP, args[n], RXI_TYPE(args, n)); - // Check type for word at the given offset: - if (!TYPE_CHECK(BLK_SKIP(obj, n), VAL_TYPE(DS_TOP))) { - result->int32b = n; - SET_EXT_ERROR(result, RXE_BAD_ARGS); - DSP = dsp; - return 0; - } - } - // Fill with NONE if necessary: - for (; n <= len; n++) { - DS_SKIP; - SET_NONE(DS_TOP); - if (!TYPE_CHECK(BLK_SKIP(obj, n), VAL_TYPE(DS_TOP))) { - result->int32b = n; - SET_EXT_ERROR(result, RXE_BAD_ARGS); - DSP = dsp; - return 0; - } - } - - // Evaluate the function: - DSF = dsf; - Func_Dispatch[VAL_TYPE(val) - REB_NATIVE](val); - DSF = PRIOR_DSF(dsf); - DSP = dsf-1; - - // Return resulting value from TOS1 (volatile location): - *result = Value_To_RXI(DS_VALUE(dsf)); - return Reb_To_RXT[VAL_TYPE(DS_VALUE(dsf))]; + INCLUDE_PARAMS_OF_UNLOAD_EXTENSION_HELPER; + + REBCTX *std = VAL_CONTEXT(Get_System(SYS_STANDARD, STD_EXTENSION)); + REBCTX *context = VAL_CONTEXT(ARG(ext)); + + if ( + (CTX_LEN(context) <= STD_EXTENSION_LIB_BASE) + || ( + CTX_KEY_CANON(context, STD_EXTENSION_LIB_BASE) + != CTX_KEY_CANON(std, STD_EXTENSION_LIB_BASE) + ) + ){ + fail (ARG(ext)); + } + + int ret; + if (!REF(cleanup)) { + REBVAL *lib = CTX_VAR(context, STD_EXTENSION_LIB_BASE); + if (!IS_LIBRARY(lib)) + fail (ARG(ext)); + + if (IS_LIB_CLOSED(VAL_LIBRARY(lib))) + fail (Error_Bad_Library_Raw()); + + QUIT_FUNC quitter = cast( + QUIT_FUNC, OS_FIND_FUNCTION(VAL_LIBRARY_FD(lib), "RX_Quit") + ); + + if (quitter == NULL) + ret = 0; + else + ret = quitter(); + + OS_CLOSE_LIBRARY(VAL_LIBRARY_FD(lib)); + } + else { + if (VAL_HANDLE_CLEANER(ARG(cleaner)) != cleanup_extension_quit_handler) + fail (ARG(cleaner)); + + QUIT_FUNC quitter = cast(QUIT_FUNC, VAL_HANDLE_CFUNC(ARG(cleaner))); + assert(quitter != NULL); + + ret = quitter(); + } + + if (ret < 0) { + DECLARE_LOCAL (i); + Init_Integer(i, ret); + fail (Error_Fail_To_Quit_Extension_Raw(i)); + } + + return R_VOID; } -/*********************************************************************** -** -*/ REBNATIVE(do_callback) -/* -** object word arg1 arg2 -** -***********************************************************************/ +// +// Just an ID for the handler +// +static void cleanup_module_handler(const REBVAL *val) { - RXICBI *cbi; - REBVAL *event = D_ARG(1); - REBCNT n; - - // Sanity check: - if (VAL_EVENT_TYPE(event) != EVT_CALLBACK || !(cbi = VAL_EVENT_SER(event))) - return R_NONE; - - n = Do_Callback(cbi->obj, cbi->word, cbi->args, &(cbi->result)); - - SET_FLAG(cbi->flags, RXC_DONE); + UNUSED(val); +} - if (!n) Trap_Num(RE_INVALID_ARG, GET_EXT_ERROR(&cbi->result)); - RXI_To_Value(ds, cbi->result, n); - return R_RET; +// +// Make_Extension_Module_Array: C +// +// Make an extension module array for being loaded later +// +REBARR *Make_Extension_Module_Array( + const REBYTE spec[], + REBCNT len, + REBNAT impl[], + REBCNT n, + REBCNT error_base +) { + // the array will be like [spec C_func error_base/none] + REBARR *arr = Make_Array(3); + + Init_Binary(ARR_AT(arr, 0), Copy_Bytes(spec, len)); + + Init_Handle_Managed( + ARR_AT(arr, 1), + impl, // It's a *pointer to function pointer*, not a function pointer + n, + &cleanup_module_handler + ); + + if (error_base == 0) + Init_Blank(ARR_AT(arr, 2)); + else + Init_Integer(ARR_AT(arr, 2), error_base); + + TERM_ARRAY_LEN(arr, 3); + return arr; } -/*********************************************************************** -** -*/ REBNATIVE(load_extension) -/* -** arg 1: filename | body binary string (UTF-8) -** arg 2: dispatch -** arg 3: function handle -** -** Low level extension loader: -** -** 1. Opens the DLL for the extension -** 2. Calls its Info() command to get its definition header (REBOL) -** 3. Inits an extension structure (dll, Call() function) -** 4. Creates a extension object and returns it -** 5. REBOL code then uses that object to define the extension module -** including commands, functions, data, exports, etc. -** -** Each extension is defined as DLL with: -** -** init() - init anything needed -** quit() - cleanup anything needed -** call() - dispatch a native -** -***********************************************************************/ +// +// Prepare_Boot_Extensions: C +// +// Convert an extension [Init Quit] array to [handle! handle!] array +// +void Prepare_Boot_Extensions(REBVAL *exts, CFUNC **funcs, REBCNT n) { - REBCHR *name; - void *dll; - REBCNT error; - REBYTE *code; - REBYTE *(*info)(REBINT opts, void *lib); - REBSER *obj; - REBVAL *val = D_ARG(1); - REBEXT *ext; - RXICAL call; - REBSER *src; - int Remove_after_first_run; - //Check_Security(SYM_EXTENSION, POL_EXEC, val); - - if (!D_REF(2)) { // No /dispatch, use the DLL file: - - if (!IS_FILE(val)) Trap_Arg(val); - - name = Val_Str_To_OS(val); - - // Try to load the DLL file: - if (!(dll = OS_OPEN_LIBRARY(name, &error))) { - Trap1(RE_NO_EXTENSION, val); - } - - // Call its info() function for header and code body: - if (!(info = OS_FIND_FUNCTION(dll, BOOT_STR(RS_EXTENSION, 0)))){ - OS_CLOSE_LIBRARY(dll); - Trap1(RE_BAD_EXTENSION, val); - } - - // Obtain info string as UTF8: - if (!(code = info(0, Extension_Lib()))) { - OS_CLOSE_LIBRARY(dll); - Trap1(RE_EXTENSION_INIT, val); - } - - // Import the string into REBOL-land: - src = Copy_Bytes(code, -1); // Nursery protected - call = OS_FIND_FUNCTION(dll, BOOT_STR(RS_EXTENSION, 2)); // zero is allowed - } - else { - // Hosted extension: - src = VAL_SERIES(val); - call = (RXICAL)VAL_HANDLE(D_ARG(3)); - dll = 0; - } - - ext = &Ext_List[Ext_Next]; - CLEARS(ext); - ext->call = call; - ext->dll = dll; - ext->index = Ext_Next++; - - // Extension return: dll, info, filename - obj = VAL_OBJ_FRAME(Get_System(SYS_STANDARD, STD_EXTENSION)); - obj = CLONE_OBJECT(obj); - Set_Object(D_RET, obj); - - // Set extension fields needed: - val = FRM_VALUE(obj, STD_EXTENSION_LIB_BASE); - VAL_SET(val, REB_HANDLE); - VAL_I32(val) = ext->index; - if (!D_REF(2)) *FRM_VALUE(obj, STD_EXTENSION_LIB_FILE) = *D_ARG(1); - Set_Binary(FRM_VALUE(obj, STD_EXTENSION_LIB_BOOT), src); - - return R_RET; + REBARR *arr = Make_Array(n); + REBCNT i; + for (i = 0; i < n; i += 2) { + Init_Handle_Managed_Cfunc( + Alloc_Tail_Array(arr), + funcs[i], + 0, // length, currently unused + &cleanup_extension_init_handler + ); + + Init_Handle_Managed_Cfunc( + Alloc_Tail_Array(arr), + funcs[i + 1], + 0, // length, currently unused + &cleanup_extension_quit_handler + ); + } + Init_Block(exts, arr); } - -/*********************************************************************** -** -*/ void Make_Command(REBVAL *value, REBVAL *def) -/* -** Assumes prior function has already stored the spec and args -** series. This function validates the body. -** -***********************************************************************/ +// +// Shutdown_Boot_Extensions: C +// +// Call QUIT functions of boot extensions in the reversed order +// +// Note that this function does not call unload-extension, that is why it is +// called SHUTDOWN instead of UNLOAD, because it's only supposed to be called +// when the interpreter is shutting down, at which point, unloading an extension +// is not necessary. Plus, there is not an elegant way to call unload-extension +// on each of boot extensions: boot extensions are passed to host-start as a +// block, and there is no host-shutdown function which would be an ideal place +// to such things. +// +void Shutdown_Boot_Extensions(CFUNC **funcs, REBCNT n) { - REBVAL *args = BLK_HEAD(VAL_FUNC_ARGS(value)); - REBCNT n; - REBVAL *val = VAL_BLK_SKIP(def, 1); - REBEXT *ext; - - if ( - VAL_LEN(def) != 3 - || !(IS_MODULE(val) || IS_OBJECT(val)) - || !IS_HANDLE(VAL_OBJ_VALUE(val, 1)) - || !IS_INTEGER(val+1) - || VAL_INT64(val+1) > 0xffff - ) Trap1(RE_BAD_FUNC_DEF, def); - - val = VAL_OBJ_VALUE(val, 1); - if ( - !(ext = &Ext_List[VAL_I32(val)]) - || !(ext->call) - ) Trap1(RE_BAD_EXTENSION, def); - - // make command! [[arg-spec] handle cmd-index] - VAL_FUNC_BODY(value) = Copy_Block_Len(VAL_SERIES(def), 1, 2); - - // Check for valid command arg datatypes: - args++; // skip self - n = 1; - for (; NOT_END(args); args++, n++) { - // If the typeset contains args that are not valid: - // (3 is the default when no args given, for not END and UNSET) - if (3 != ~VAL_TYPESET(args) && (VAL_TYPESET(args) & ~RXT_ALLOWED_TYPES)) - Trap1(RE_BAD_FUNC_ARG, args); - } - - VAL_SET(value, REB_COMMAND); + for (; n > 1; n -= 2) { + cast(QUIT_FUNC, funcs[n - 1])(); + } } -/*********************************************************************** -** -*/ void Do_Command(REBVAL *value) -/* -** Evaluates the arguments for a command function and creates -** a resulting stack frame (struct or object) for command processing. -** -** A command value consists of: -** args - same as other funcs -** spec - same as other funcs -** body - [ext-obj func-index] -** -***********************************************************************/ +// +// load-native: native [ +// +// "Load a native from a built-in extension" +// +// return: [function!] +// "function value, will be created from the native implementation" +// spec [block!] +// "spec of the native" +// impl [handle!] +// "a handle returned from RX_Init_ of the extension" +// index [integer!] +// "Index of the native" +// /body +// code [block!] +// "User-equivalent body" +// /unloadable +// "The native can be unloaded later (when extension is unloaded)" +// ] +// +REBNATIVE(load_native) { - REBVAL *val = BLK_HEAD(VAL_FUNC_BODY(value)); - REBEXT *ext; - REBCNT cmd; - REBCNT argc; - REBCNT n; - RXIFRM frm; // args stored here - - // All of these were checked above on definition: - val = BLK_HEAD(VAL_FUNC_BODY(value)); - cmd = (int)VAL_INT64(val+1); - ext = &Ext_List[VAL_I32(VAL_OBJ_VALUE(val, 1))]; // Handler - - // Copy args to command frame (array of args): - RXA_COUNT(&frm) = argc = SERIES_TAIL(VAL_FUNC_ARGS(value))-1; // not self - if (argc > 7) Trap0(RE_BAD_COMMAND); - val = DS_ARG(1); - for (n = 1; n <= argc; n++, val++) { - RXA_TYPE(&frm, n) = Reb_To_RXT[VAL_TYPE(val)]; - frm.args[n] = Value_To_RXI(val); - } - - // Call the command: - n = ext->call(cmd, &frm, 0); - val = DS_RETURN; - switch (n) { - case RXR_VALUE: - RXI_To_Value(val, frm.args[1], RXA_TYPE(&frm, 1)); - break; - case RXR_BLOCK: - RXI_To_Block(&frm, val); - break; - case RXR_UNSET: - SET_UNSET(val); - break; - case RXR_NONE: - SET_NONE(val); - break; - case RXR_TRUE: - SET_TRUE(val); - break; - case RXR_FALSE: - SET_FALSE(val); - break; - case RXR_ERROR: - default: - SET_UNSET(val); - } + INCLUDE_PARAMS_OF_LOAD_NATIVE; + + if (VAL_HANDLE_CLEANER(ARG(impl)) != cleanup_module_handler) + fail ("HANDLE! passed to LOAD-NATIVE did not come from RX_Init"); + + REBI64 index = VAL_INT64(ARG(index)); + if (index < 0 || cast(REBUPT, index) >= VAL_HANDLE_LEN(ARG(impl))) + fail ("Index of native is outside range specified by RX_Init"); + + REBNAT dispatcher = VAL_HANDLE_POINTER(REBNAT, ARG(impl))[index]; + REBFUN *fun = Make_Function( + Make_Paramlist_Managed_May_Fail( + ARG(spec), + MKF_KEYWORDS | MKF_FAKE_RETURN + ), + dispatcher, // unique + NULL, // no underlying function, this is fundamental + NULL // not providing a specialization + ); + + if (REF(unloadable)) + SET_VAL_FLAG(FUNC_VALUE(fun), FUNC_FLAG_UNLOADABLE_NATIVE); + + if (REF(body)) { + *FUNC_BODY(fun) = *ARG(code); + } + Move_Value(D_OUT, FUNC_VALUE(fun)); + return R_OUT; } -/*********************************************************************** -** -*/ void Do_Commands(REBSER *cmds, void *context) -/* -** Evaluate a block of commands as efficiently as possible. -** The arguments to each command must already be reduced or -** use only variable lookup. -** -** Returns the last evaluated value, if provided. -** -***********************************************************************/ +// +// Unloaded_Dispatcher: C +// +// This will be the dispatcher for the natives in an extension after the +// extension is unloaded. +// +static REB_R Unloaded_Dispatcher(REBFRM *f) { - REBVAL *blk; - REBCNT index = 0; - REBVAL *set_word = 0; - REBVAL *cmd_word; - REBSER *words; - REBVAL *args; - REBVAL *val; - REBVAL *func; - RXIFRM frm; // args stored here - REBCNT n; - REBEXT *ext; - REBCEC *ctx; - - if ((ctx = context)) ctx->block = cmds; - blk = BLK_HEAD(cmds); - - while (NOT_END(blk)) { - - // var: command result - if IS_SET_WORD(blk) { - set_word = blk++; - index++; - }; - - // get command function - if (IS_WORD(cmd_word = blk)) { - // Optimized var fetch: - n = VAL_WORD_INDEX(blk); - if (n > 0) func = FRM_VALUES(VAL_WORD_FRAME(blk)) + n; - else func = Get_Var(blk); // fallback - } else func = blk; - - if (!IS_COMMAND(func)) Trap2(RE_EXPECT_VAL, Get_Type_Word(REB_COMMAND), blk); - - // Advance to next value - blk++; - if (ctx) ctx->index = index; // position of function - index++; - - // get command arguments and body - words = VAL_FUNC_WORDS(func); - RXA_COUNT(&frm) = SERIES_TAIL(VAL_FUNC_ARGS(func))-1; // not self - - // collect each argument (arg list already validated on MAKE) - n = 0; - for (args = BLK_SKIP(words, 1); NOT_END(args); args++) { - - //Debug_Type(args); - val = blk++; - index++; - if (IS_END(val)) Trap2(RE_NO_ARG, cmd_word, args); - //Debug_Type(val); - - // actual arg is a word, lookup? - if (VAL_TYPE(val) >= REB_WORD) { - if (IS_WORD(val)) { - if (IS_WORD(args)) val = Get_Var(val); - } - else if (IS_PATH(val)) { - if (IS_WORD(args)) val = Get_Any_Var(val); // volatile value! - } - else if (IS_PAREN(val)) { - val = Do_Blk(VAL_SERIES(val), 0); // volatile value! - } - // all others fall through - } - - // check datatype - if (!TYPE_CHECK(args, VAL_TYPE(val))) - Trap3(RE_EXPECT_ARG, cmd_word, args, Of_Type(val)); - - // put arg into command frame - n++; - RXA_TYPE(&frm, n) = Reb_To_RXT[VAL_TYPE(val)]; - frm.args[n] = Value_To_RXI(val); - } - - // Call the command (also supports different extension modules): - func = BLK_HEAD(VAL_FUNC_BODY(func)); - n = (REBCNT)VAL_INT64(func + 1); - ext = &Ext_List[VAL_I32(VAL_OBJ_VALUE(func, 1))]; // Handler - n = ext->call(n, &frm, context); - val = DS_RETURN; - switch (n) { - case RXR_VALUE: - RXI_To_Value(val, frm.args[1], RXA_TYPE(&frm, 1)); - break; - case RXR_BLOCK: - RXI_To_Block(&frm, val); - break; - case RXR_UNSET: - SET_UNSET(val); - break; - case RXR_NONE: - SET_NONE(val); - break; - case RXR_TRUE: - SET_TRUE(val); - break; - case RXR_FALSE: - SET_FALSE(val); - break; - case RXR_ERROR: - default: - SET_UNSET(val); - } - - if (set_word) { - Set_Var(set_word, val); - set_word = 0; - } - } + UNUSED(f); + + fail (Error_Native_Unloaded_Raw(FUNC_VALUE(f->phase))); } -/*********************************************************************** -** -*/ REBNATIVE(do_commands) -/* -***********************************************************************/ +// +// unload-native: native [ +// +// "Unload a native when the containing extension is unloaded" +// +// return: [] +// nat [function!] "The native function to be unloaded" +// ] +// +REBNATIVE(unload_native) { - REBCEC ctx; + INCLUDE_PARAMS_OF_UNLOAD_NATIVE; - ctx.envr = 0; - ctx.block = VAL_SERIES(D_ARG(1)); - ctx.index = 0; - Do_Commands(ctx.block, &ctx); + REBFUN *fun = VAL_FUNC(ARG(nat)); + if (NOT_VAL_FLAG(FUNC_VALUE(fun), FUNC_FLAG_UNLOADABLE_NATIVE)) + fail (Error_Non_Unloadable_Native_Raw(ARG(nat))); - return R_RET; -} + FUNC_DISPATCHER(VAL_FUNC(ARG(nat))) = Unloaded_Dispatcher; + return R_VOID; +} -#ifdef notused -/*********************************************************************** -** -xx*/ REBVAL *Prior_Func_Frame(void) -/* -***********************************************************************/ +// +// Init_Extension_Words: C +// +// Intern strings and save their canonical forms +// +void Init_Extension_Words(const REBYTE* strings[], REBSTR *canons[], REBCNT n) { - REBCNT dsf = DSF; - REBVAL *val; - - for (dsf = DSF; dsf > 0; dsf = PRIOR_DSF(dsf)) { - val = DSF_BACK(dsf); - if (IS_BLOCK(val) && VAL_SERIES(val)) - return val; - } - - return 0; + REBCNT i; + for (i = 0; i < n; ++i) { + canons[i] = STR_CANON(Intern_UTF8_Managed(strings[i], LEN_BYTES(strings[i]))); + } } -#endif - diff --git a/src/core/f-int.c b/src/core/f-int.c new file mode 100644 index 0000000000..62d803dc64 --- /dev/null +++ b/src/core/f-int.c @@ -0,0 +1,181 @@ +// +// File: %f-int.c +// Summary: "integer arithmetic functions" +// Section: functional +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2014 Atronix Engineering, Inc +// Copyright 2014-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Based on original code in t-integer.c +// + +#include "reb-c.h" +#include "sys-int-funcs.h" + +REBOOL reb_i32_add_overflow(i32 x, i32 y, i32 *sum) +{ + i64 sum64 = (i64)x + (i64)y; + if (sum64 > MAX_I32 || sum64 < MIN_I32) return TRUE; + *sum = (i32)sum64; + return FALSE; +} + +REBOOL reb_u32_add_overflow(u32 x, u32 y, u32 *sum) +{ + u64 s = (u64)x + (u64)y; + if (s > MAX_I32) return TRUE; + *sum = (u32)s; + return FALSE; +} + +REBOOL reb_i64_add_overflow(i64 x, i64 y, i64 *sum) +{ + *sum = (REBU64)x + (REBU64)y; /* never overflow with unsigned integers*/ + if (((x < 0) == (y < 0)) + && ((x < 0) != (*sum < 0))) return TRUE; + return FALSE; +} + +REBOOL reb_u64_add_overflow(u64 x, u64 y, u64 *sum) +{ + *sum = x + y; + if (*sum < x || *sum < y) return TRUE; + return FALSE; +} + +REBOOL reb_i32_sub_overflow(i32 x, i32 y, i32 *diff) +{ + *diff = (i64)x - (i64)y; + if (((x < 0) != (y < 0)) && ((x < 0) != (*diff < 0))) return TRUE; + + return FALSE; +} + +REBOOL reb_i64_sub_overflow(i64 x, i64 y, i64 *diff) +{ + *diff = (REBU64)x - (REBU64)y; + if (((x < 0) != (y < 0)) && ((x < 0) != (*diff < 0))) return TRUE; + + return FALSE; +} + +REBOOL reb_i32_mul_overflow(i32 x, i32 y, i32 *prod) +{ + i64 p = (i64)x * (i64)y; + if (p > MAX_I32 || p < MIN_I32) return TRUE; + *prod = (i32)p; + return FALSE; +} + +REBOOL reb_u32_mul_overflow(u32 x, u32 y, u32 *prod) +{ + u64 p = (u64)x * (u64)y; + if (p > MAX_U32) return TRUE; + *prod = (u32)p; + return FALSE; +} + +REBOOL reb_i64_mul_overflow(i64 x, i64 y, i64 *prod) +{ + REBOOL sgn; + u64 p = 0; + + if (!x || !y) { + *prod = 0; + return FALSE; + } + + sgn = LOGICAL(x < 0); + if (sgn) { + if (x == MIN_I64) { + switch (y) { + case 0: + *prod = 0; + return FALSE; + case 1: + *prod = x; + return FALSE; + default: + return TRUE; + } + } + x = -x; /* undefined when x == MIN_I64 */ + } + if (y < 0) { + sgn = NOT(sgn); + if (y == MIN_I64) { + switch (x) { + case 0: + *prod = 0; + return FALSE; + case 1: + if (!sgn) { + return TRUE; + } else { + *prod = y; + return FALSE; + } + default: + return TRUE; + } + } + y = -y; /* undefined when y == MIN_I64 */ + } + + if (REB_U64_MUL_OF(x, y, (u64 *)&p) + || (!sgn && p > MAX_I64) + || (sgn && p - 1 > MAX_I64)) return TRUE; /* assumes 2's complements */ + + if (sgn && p == (u64)MIN_I64) { + *prod = MIN_I64; + return FALSE; + } + + if (sgn) + *prod = -cast(i64, p); + else + *prod = p; + + return FALSE; +} + +REBOOL reb_u64_mul_overflow(u64 x, u64 y, u64 *prod) +{ + u64 x0, y0, x1, y1; + u64 b = U64_C(1) << 32; + u64 tmp = 0; + x1 = x >> 32; + x0 = (u32)x; + y1 = y >> 32; + y0 = (u32)y; + + /* p = (x1 * y1) * b^2 + (x0 * y1 + x1 * y0) * b + x0 * y0 */ + + if (x1 && y1) return TRUE; /* (x1 * y1) * b^2 overflows */ + + tmp = (x0 * y1 + x1 * y0); /* never overflow, because x1 * y1 == 0 */ + if (tmp >= b) return TRUE; /*(x0 * y1 + x1 * y0) * b overflows */ + + return LOGICAL(REB_U64_ADD_OF(tmp << 32, x0 * y0, prod)); +} diff --git a/src/core/f-math.c b/src/core/f-math.c index 5cbdbf2d1d..2abb9df8d4 100644 --- a/src/core/f-math.c +++ b/src/core/f-math.c @@ -1,473 +1,339 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: f-math.c -** Summary: basic math conversions -** Section: functional -** Author: Carl Sassenrath -** Notes: -** Do not underestimate what it takes to make some parts of this -** portable over all systems. Modifications to this code should be -** tested on multiple operating system runtime libraries, including -** older/obsolete systems. -** -***********************************************************************/ +// +// File: %f-math.c +// Summary: "basic math conversions" +// Section: functional +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Do not underestimate what it takes to make some parts of this +// portable over all systems. Modifications to this code should be +// tested on multiple operating system runtime libraries, including +// older/obsolete systems. +// #include "sys-core.h" #include "sys-dec-to-char.h" -/*********************************************************************** -** -*/ REBYTE *Grab_Int(REBYTE *cp, REBINT *val) -/* -** Grab an integer value from the string. -** -** Return the character position just after the integer and return -** the integer via a pointer to it. -** -** Notes: -** 1. Stops at the first non-digit. -** 2. If no integer found, pointer doesn't change position. -** 3. Integers may contain REBOL tick (') marks. -** -***********************************************************************/ +// +// Grab_Int: C +// +// Grab an integer value from the string. +// +// Return the character position just after the integer and return +// the integer via a pointer to it. +// +// Notes: +// 1. Stops at the first non-digit. +// 2. If no integer found, pointer doesn't change position. +// 3. Integers may contain REBOL tick (') marks. +// +const REBYTE *Grab_Int(const REBYTE *cp, REBINT *val) { - REBINT value = 0; - REBINT neg = FALSE; + REBINT value = 0; + REBOOL neg = FALSE; - if (*cp == '-') cp++, neg = TRUE; - else if (*cp == '+') cp++; + if (*cp == '-') cp++, neg = TRUE; + else if (*cp == '+') cp++; - while (*cp >= '0' && *cp <= '9') { - value = (value * 10) + (*cp - '0'); - cp++; - } + while (*cp >= '0' && *cp <= '9') { + value = (value * 10) + (*cp - '0'); + cp++; + } - *val = neg ? -value : value; + *val = neg ? -value : value; - return cp; + return cp; } -/*********************************************************************** -** -*/ REBYTE *Grab_Int_Scale(REBYTE *cp, REBINT *val, REBCNT scale) -/* -** Return integer scaled to the number of digits specified. -** Used for the decimal part of numbers (e.g. times). -** -***********************************************************************/ +// +// Grab_Int_Scale: C +// +// Return integer scaled to the number of digits specified. +// Used for the decimal part of numbers (e.g. times). +// +const REBYTE *Grab_Int_Scale(const REBYTE *cp, REBINT *val, REBCNT scale) { - REBI64 value = 0; + REBI64 value = 0; - for (;scale > 0 && *cp >= '0' && *cp <= '9'; scale--) { - value = (value * 10) + (*cp - '0'); - cp++; - } + for (;scale > 0 && *cp >= '0' && *cp <= '9'; scale--) { + value = (value * 10) + (*cp - '0'); + cp++; + } - // Round up if necessary: - if (*cp >= '5' && *cp <= '9') value++; + // Round up if necessary: + if (*cp >= '5' && *cp <= '9') value++; - // Ignore excess digits: - while (*cp >= '0' && *cp <= '9') cp++; + // Ignore excess digits: + while (*cp >= '0' && *cp <= '9') cp++; - // Make sure its full scale: - for (;scale > 0; scale--) value *= 10; + // Make sure its full scale: + for (;scale > 0; scale--) value *= 10; - *val = (REBINT)value; - return cp; + *val = (REBINT)value; + return cp; } -/*********************************************************************** -** -*/ REBINT Form_Int_Len(REBYTE *buf, REBI64 val, REBINT maxl) -/* -** Form an integer string into the given buffer. Result will -** not exceed maxl length, including terminator. -** -** Returns the length of the string. -** -** Notes: -** 1. If result is longer than maxl, returns 0 length. -** 2. Make sure you have room in your buffer! -** -***********************************************************************/ +// +// Form_Int_Len: C +// +// Form an integer string into the given buffer. Result will +// not exceed maxl length, including terminator. +// +// Returns the length of the string. +// +// Notes: +// 1. If result is longer than maxl, returns 0 length. +// 2. Make sure you have room in your buffer! +// +REBINT Form_Int_Len(REBYTE *buf, REBI64 val, REBINT maxl) { - REBYTE tmp[MAX_NUM_LEN]; - REBYTE *tp = tmp; - REBI64 n; - REBI64 r; - REBINT len = 0; - - // defaults for problem cases - buf[0] = '?'; - buf[1] = 0; - - if (maxl == 0) return 0; - - if (val == 0) { - *buf++ = '0'; - *buf = 0; - return 1; - } - - if (val < 0) { - val = -val; - *buf++ = '-'; - maxl--; - len = 1; - } - - // Generate string in reverse: - *tp++ = 0; - while (val != 0) { - n = val / 10; // not using ldiv for easier compatibility - r = val % 10; - if (r < 0) { // check for overflow case when val = 0x80000000... - r = -r; - n = -n; - } - *tp++ = (REBYTE)('0' + (REBYTE)(r)); - val = n; - } - tp--; - - if (tp - tmp > maxl) return 0; - - while (NZ(*buf++ = *tp--)) len++; - return len; + REBYTE tmp[MAX_NUM_LEN]; + REBYTE *tp = tmp; + REBI64 n; + REBI64 r; + REBINT len = 0; + + // defaults for problem cases + buf[0] = '?'; + buf[1] = 0; + + if (maxl == 0) return 0; + + if (val == 0) { + *buf++ = '0'; + *buf = 0; + return 1; + } + +#define MIN_I64_STR "-9223372036854775808" + if (val == MIN_I64) { + len = strlen(MIN_I64_STR); + if (maxl < len + 1) return 0; + memcpy(buf, MIN_I64_STR, len + 1); + return len; + } + + if (val < 0) { + val = -val; + *buf++ = '-'; + maxl--; + len = 1; + } + + // Generate string in reverse: + *tp++ = 0; + while (val != 0 && maxl > 0 && tp < tmp + MAX_NUM_LEN) { + n = val / 10; // not using ldiv for easier compatibility + r = val % 10; + *tp++ = (REBYTE)('0' + (REBYTE)(r)); + val = n; + maxl --; + } + tp--; + + if (maxl == 0) { + return 0; + } + + while ((*buf++ = *tp--)) len++; + return len; } -/*********************************************************************** -** -*/ REBYTE *Form_Int_Pad(REBYTE *buf, REBI64 val, REBINT max, REBINT len, REBYTE pad) -/* -** Form an integer string in the given buffer with a min -** width padded out with the given character. Len > 0 left -** aligned. Len < 0 is right aligned. -** -** If len = 0 and val = 0, a null string is formed. -** Make sure you have room in your buffer before calling this! -** -***********************************************************************/ +// +// Form_Int_Pad: C +// +// Form an integer string in the given buffer with a min +// width padded out with the given character. Len > 0 left +// aligned. Len < 0 is right aligned. +// +// If len = 0 and val = 0, a null string is formed. +// Make sure you have room in your buffer before calling this! +// +REBYTE *Form_Int_Pad(REBYTE *buf, REBI64 val, REBINT max, REBINT len, REBYTE pad) { - REBYTE tmp[MAX_NUM_LEN]; - REBINT n; - - n = Form_Int_Len(tmp, val, max); - if (n == 0) { - strcpy(buf, "??"); - return buf; // too long - } - - if (len >= 0) { - strcpy(buf, tmp); - buf += n; - for (; n < len; n++) *buf++ = pad; - } - else { // len < 0 - for (; n < -len; len++) *buf++ = pad; - strcpy(buf, tmp); - buf += n; - } - - *buf = 0; - return buf; + REBYTE tmp[MAX_NUM_LEN]; + REBINT n; + + n = Form_Int_Len(tmp, val, max + 1); + if (n == 0) { + strcpy(s_cast(buf), "??"); + return buf; // too long + } + + if (len >= 0) { + strcpy(s_cast(buf), s_cast(tmp)); + buf += n; + for (; n < len; n++) *buf++ = pad; + } + else { // len < 0 + for (; n < -len; len++) *buf++ = pad; + strcpy(s_cast(buf), s_cast(tmp)); + buf += n; + } + + *buf = 0; + return buf; } -/*********************************************************************** -** -*/ REBYTE *Form_Int(REBYTE *buf, REBINT val) -/* -** Form 32 bit integer string in the given buffer. -** Make sure you have room in your buffer before calling this! -** -***********************************************************************/ +// +// Form_Int: C +// +// Form 32 bit integer string in the given buffer. +// Make sure you have room in your buffer before calling this! +// +REBYTE *Form_Int(REBYTE *buf, REBINT val) { - REBINT len = Form_Int_Len(buf, val, MAX_NUM_LEN); - return buf + len; + REBINT len = Form_Int_Len(buf, val, MAX_NUM_LEN); + return buf + len; } -/*********************************************************************** -** -*/ REBYTE *Form_Integer(REBYTE *buf, REBI64 val) -/* -** Form standard REBOL integer value (32 or 64). -** Make sure you have room in your buffer before calling this! -** -***********************************************************************/ +// +// Form_Integer: C +// +// Form standard REBOL integer value (32 or 64). +// Make sure you have room in your buffer before calling this! +// +REBYTE *Form_Integer(REBYTE *buf, REBI64 val) { - INT_TO_STR(val, buf); - return buf+LEN_BYTES(buf); + INT_TO_STR(val, buf); + return buf+LEN_BYTES(buf); } -/*********************************************************************** -** -*/ REBINT Emit_Integer(REBYTE *buf, REBI64 val) -/* -***********************************************************************/ +// +// Emit_Integer: C +// +REBINT Emit_Integer(REBYTE *buf, REBI64 val) { - INT_TO_STR(val, buf); - return LEN_BYTES(buf); + INT_TO_STR(val, buf); + return LEN_BYTES(buf); } -#ifdef OLDER -/*********************************************************************** -** -xx*/ REBCNT Set_Random(REBCNT seed) -/* -***********************************************************************/ -{ - REBCNT save = next; - next = seed; - return save; -} - - -/*********************************************************************** -** -xx*/ REBCNT Random_Int(REBFLG secure) -/* -** Return random integer. Secure uses SHA1 for better quality. -** Be careful of endian-ness. -** -***********************************************************************/ -{ - REBCNT tmp; - - next = next * 1103515245L + 12345L; - tmp = next & 0xffff0000; - next = next * 1103515245L + 12345L; - tmp |= (next >> 16); - - if (secure) { - REBYTE srcbuf[20], dstbuf[20]; - REBCNT i; - - Long_To_Bytes(srcbuf, tmp); - for(i = sizeof(tmp); i < 20; i += sizeof(tmp)) - memcpy(srcbuf + i, srcbuf, sizeof(tmp)); - SHA1(srcbuf, i, dstbuf); - tmp = Bytes_To_Long(dstbuf); - } - - return tmp; -} -#endif - -#ifdef OLD_DEC_TO_STR -static int Convert_Decimal(REBDEC d, REBI64 *sig, REBINT *point) -{ - REBDEC e; - REBDEC n; - - // Check if num needs exp format: - e = floor(log10(d)); - if (e > 15 || e < -6) return 0; // use gcvt - - modf(d * pow(10, (15-e)), &n); - *sig = (REBI64)n; - *point = 1 + (REBINT)e; - return 1; -} - -/*********************************************************************** -** -*/ REBINT Emit_Decimal(REBYTE *cp, REBDEC d, REBFLG percent, REBYTE point, REBINT digits) -/* -***********************************************************************/ -{ - REBYTE out[MAX_NUMCHR]; - REBINT len; - REBINT n; - REBINT i; - REBI64 sig; - REBINT pt; - REBFLG neg; - REBYTE *start = cp; - - *cp = out[0] = 0; - - // Deal with 0 as special case: - if (d == 0.0 || d == -0.0) { - *cp++ = '0'; - if (!percent) { - *cp++ = '.'; - *cp++ = '0'; - } - } - else { - - if (percent) d *= 100.0; - - if (NZ(neg = (d < 0))) d = -d; - - if (Convert_Decimal(d, &sig, &pt)) { - // Not exp format. - len = Form_Integer(out, sig) - out; - if (neg) *cp++ = '-'; - - // Trim un-needed trailing zeros: - for (len--; len > 0 && len >= pt; len--) { - if (out[len] == '0') out[len] = 0; - else break; - } - - // Leading zero, as in 0.1 - if (pt <= 0) *cp++ = '0'; - - // Other leading digits: - for (n = 0; out[n] && n < pt; n++) *cp++ = out[n]; - - if (!percent || n <= len) { - // Decimal point: - *cp++ = point; - - // Zeros before first significant digit: - for (i = 0; i > pt; i--) *cp++ = '0'; - - // All remaining digits: - for (; n <= len; n++) *cp++ = out[n]; - - // Force extra zero in 1.0 cases: - if (cp[-1] == point) *cp++ = '0'; - } - } - else { - REBYTE *pp; - - // Requires exp format: - if (percent) Trap0(RE_OVERFLOW); - len = Get_System_Int(SYS_OPTIONS, OPTIONS_DECIMAL_DIGITS, MAX_DIGITS); - if (len > MAX_DIGITS) len = MAX_DIGITS; - gcvt(d, len, cp); // returns 1.2e123 (also 1e123) - pp = strchr(cp, '.'); - if (pp && (pp[1] == 'e' || pp[1] == 'E')) { - memcpy(pp, pp+1, strlen(pp)); - } - if (point != '.' && pp) { - cp = strchr(cp, '.'); - if (cp) *cp = point; - } - cp = start + LEN_BYTES(start); - } - } - - if (percent) *cp++ = '%'; - *cp = 0; - - return cp - start; -} - -#else // NEW_DEC_TO_STR #define MIN_DIGITS 1 /* this is appropriate for 64-bit IEEE754 binary floating point format */ #define MAX_DIGITS 17 -REBINT Emit_Decimal(REBYTE *cp, REBDEC d, REBFLG trim, REBYTE point, REBINT decimal_digits) { - REBYTE *start = cp, *sig, *rve; - int e, sgn; - REBINT digits_obtained; - - /* sanity checks */ - if (decimal_digits < MIN_DIGITS) decimal_digits = MIN_DIGITS; - else if (decimal_digits > MAX_DIGITS) decimal_digits = MAX_DIGITS; - - sig = (REBYTE *) dtoa (d, 0, decimal_digits, &e, &sgn, (char **) &rve); - - digits_obtained = rve - sig; - - /* handle sign */ - if (sgn) *cp++ = '-'; - - if (trim == DEC_MOLD_PERCENT) e += 2; - - if ((e > decimal_digits) || (e <= -6)) { - /* e-format */ - *cp++ = *sig++; - - /* insert the radix point */ - *cp++ = point; - - /* insert the rest */ - memcpy(cp, sig, digits_obtained - 1); - cp += digits_obtained - 1; - } else if (e > 0) { - if (e <= digits_obtained) { - /* insert digits preceding point */ - memcpy (cp, sig, e); - cp += e; - sig += e; - - *cp++ = point; - - /* insert digits following point */ - memcpy(cp, sig, digits_obtained - e); - cp += digits_obtained - e; - } else { - /* insert all digits obtained */ - memcpy (cp, sig, digits_obtained); - cp += digits_obtained; - - /* insert zeros preceding point */ - memset (cp, '0', e - digits_obtained); - cp += e - digits_obtained; - - *cp++ = point; - } - e = 0; - } else { - *cp++ = '0'; - - *cp++ = point; - - memset(cp, '0', -e); - cp -= e; - - memcpy(cp, sig, digits_obtained); - cp += digits_obtained; - - e = 0; - } - - // Add at least one zero after point (unless percent or pair): - if (*(cp - 1) == point) {if (trim) cp--; else *cp++ = '0';} - - // Add E part if needed: - if (e) { - *cp++ = 'e'; - INT_TO_STR(e - 1, cp); - cp = strchr(cp, 0); - } - - if (trim == DEC_MOLD_PERCENT) *cp++ = '%'; - *cp = 0; - return cp - start; +// +// Emit_Decimal: C +// +REBINT Emit_Decimal( + REBYTE *cp, + REBDEC d, + REBFLGS flags, // DEC_MOLD_PERCENT, DEC_MOLD_MINIMAL + REBYTE point, + REBINT decimal_digits +) { + REBYTE *start = cp, *sig, *rve; + int e, sgn; + REBINT digits_obtained; + + /* sanity checks */ + if (decimal_digits < MIN_DIGITS) decimal_digits = MIN_DIGITS; + else if (decimal_digits > MAX_DIGITS) decimal_digits = MAX_DIGITS; + + sig = (REBYTE *) dtoa (d, 0, decimal_digits, &e, &sgn, (char **) &rve); + + digits_obtained = rve - sig; + + /* handle sign */ + if (sgn) *cp++ = '-'; + + if (flags & DEC_MOLD_PERCENT) e += 2; + + if ((e > decimal_digits) || (e <= -6)) { + /* e-format */ + *cp++ = *sig++; + + /* insert the radix point */ + *cp++ = point; + + /* insert the rest */ + memcpy(cp, sig, digits_obtained - 1); + cp += digits_obtained - 1; + } else if (e > 0) { + if (e <= digits_obtained) { + /* insert digits preceding point */ + memcpy (cp, sig, e); + cp += e; + sig += e; + + *cp++ = point; + + /* insert digits following point */ + memcpy(cp, sig, digits_obtained - e); + cp += digits_obtained - e; + } else { + /* insert all digits obtained */ + memcpy (cp, sig, digits_obtained); + cp += digits_obtained; + + /* insert zeros preceding point */ + memset (cp, '0', e - digits_obtained); + cp += e - digits_obtained; + + *cp++ = point; + } + e = 0; + } else { + *cp++ = '0'; + + *cp++ = point; + + memset(cp, '0', -e); + cp -= e; + + memcpy(cp, sig, digits_obtained); + cp += digits_obtained; + + e = 0; + } + + // Add at least one zero after point (unless percent or pair): + if (*(cp - 1) == point) { + if ((flags & DEC_MOLD_PERCENT) || (flags & DEC_MOLD_MINIMAL)) + cp--; + else + *cp++ = '0'; + } + + // Add E part if needed: + if (e) { + *cp++ = 'e'; + INT_TO_STR(e - 1, cp); + cp = b_cast(strchr(s_cast(cp), 0)); + } + + if (flags & DEC_MOLD_PERCENT) *cp++ = '%'; + *cp = 0; + return cp - start; } -#endif // NEW_DEC_TO_STR diff --git a/src/core/f-modify.c b/src/core/f-modify.c index 2e6f3c5943..293d8295c0 100644 --- a/src/core/f-modify.c +++ b/src/core/f-modify.c @@ -1,198 +1,275 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: f-modify.c -** Summary: block series modification (insert, append, change) -** Section: functional -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %f-modify.c +// Summary: "block series modification (insert, append, change)" +// Section: functional +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/*********************************************************************** -** -*/ REBCNT Modify_Block(REBCNT action, REBSER *dst_ser, REBCNT dst_idx, REBVAL *src_val, REBCNT flags, REBINT dst_len, REBINT dups) -/* -** action: INSERT, APPEND, CHANGE -** -** dst_ser: target -** dst_idx: position -** src_val: source -** flags: AN_ONLY, AN_PART -** dst_len: length to remove -** dups: dup count -** -** return: new dst_idx -** -***********************************************************************/ -{ - REBCNT tail = SERIES_TAIL(dst_ser); - REBINT ilen = 1; // length to be inserted - REBINT size; // total to insert - REBFLG is_blk = FALSE; // src_val is a block not a value - - if (dups < 0) return (action == A_APPEND) ? 0 : dst_idx; - if (action == A_APPEND || dst_idx > tail) dst_idx = tail; - - // Check /PART, compute LEN: - if (!GET_FLAG(flags, AN_ONLY) && ANY_BLOCK(src_val)) { - is_blk = TRUE; // src_val is a block - // Are we modifying ourselves? If so, copy src_val block first: - if (dst_ser == VAL_SERIES(src_val)) { - VAL_SERIES(src_val) = Copy_Block(VAL_SERIES(src_val), VAL_INDEX(src_val)); - VAL_INDEX(src_val) = 0; - } - // Length of insertion: - ilen = (action != A_CHANGE && GET_FLAG(flags, AN_PART)) ? dst_len : VAL_LEN(src_val); - } - - // Total to insert: - size = dups * ilen; - - if (action != A_CHANGE) { - // Always expand dst_ser for INSERT and APPEND actions: - Expand_Series(dst_ser, dst_idx, size); - } else { - if (size > dst_len) - Expand_Series(dst_ser, dst_idx, size-dst_len); - else if (size < dst_len && GET_FLAG(flags, AN_PART)) - Remove_Series(dst_ser, dst_idx, dst_len-size); - else if (size + dst_idx > tail) { - EXPAND_SERIES_TAIL(dst_ser, size - (tail - dst_idx)); - } - } - - tail = (action == A_APPEND) ? 0 : size + dst_idx; - - if (is_blk) src_val = VAL_BLK_DATA(src_val); - - dst_idx *= SERIES_WIDE(dst_ser); // loop invariant - ilen *= SERIES_WIDE(dst_ser); // loop invariant - for (; dups > 0; dups--) { - memcpy(dst_ser->data + dst_idx, (REBYTE *)src_val, ilen); - dst_idx += ilen; - } - BLK_TERM(dst_ser); - - return tail; +// +// Modify_Array: C +// +// Returns new dst_idx +// +REBCNT Modify_Array( + REBCNT action, // INSERT, APPEND, CHANGE + REBARR *dst_arr, // target + REBCNT dst_idx, // position + const REBVAL *src_val, // source + REBCNT flags, // AM_ONLY, AM_PART + REBINT dst_len, // length to remove + REBINT dups // dup count +) { + REBCNT tail = ARR_LEN(dst_arr); + + REBINT ilen = 1; // length to be inserted + + const RELVAL *src_rel; + REBSPC *specifier; + + if (IS_VOID(src_val) || dups < 0) { + // If they are effectively asking for "no action" then all we have + // to do is return the natural index result for the operation. + // (APPEND will return 0, insert the tail of the insertion...so index) + + return (action == SYM_APPEND) ? 0 : dst_idx; + } + + if (action == SYM_APPEND || dst_idx > tail) dst_idx = tail; + + // Check /PART, compute LEN: + if (NOT(flags & AM_ONLY) && ANY_ARRAY(src_val)) { + // Adjust length of insertion if changing /PART: + if (action != SYM_CHANGE && (flags & AM_PART)) + ilen = dst_len; + else + ilen = VAL_LEN_AT(src_val); + + // Are we modifying ourselves? If so, copy src_val block first: + if (dst_arr == VAL_ARRAY(src_val)) { + REBARR *copy = Copy_Array_At_Shallow( + VAL_ARRAY(src_val), VAL_INDEX(src_val), VAL_SPECIFIER(src_val) + ); + MANAGE_ARRAY(copy); // !!! Review: worth it to not manage and free? + src_rel = ARR_HEAD(copy); + specifier = SPECIFIED; // copy already specified it + } + else { + src_rel = VAL_ARRAY_AT(src_val); // skips by VAL_INDEX values + specifier = VAL_SPECIFIER(src_val); + } + } + else { + // use passed in RELVAL and specifier + src_rel = src_val; + specifier = SPECIFIED; // it's a REBVAL, not a RELVAL, so specified + } + + REBINT size = dups * ilen; // total to insert + + if (action != SYM_CHANGE) { + // Always expand dst_arr for INSERT and APPEND actions: + Expand_Series(SER(dst_arr), dst_idx, size); + } + else { + if (size > dst_len) + Expand_Series(SER(dst_arr), dst_idx, size-dst_len); + else if (size < dst_len && (flags & AM_PART)) + Remove_Series(SER(dst_arr), dst_idx, dst_len-size); + else if (size + dst_idx > tail) { + EXPAND_SERIES_TAIL(SER(dst_arr), size - (tail - dst_idx)); + } + } + + tail = (action == SYM_APPEND) ? 0 : size + dst_idx; + +#if !defined(NDEBUG) + if (IS_ARRAY_MANAGED(dst_arr)) { + REBINT i; + for (i = 0; i < ilen; ++i) + ASSERT_VALUE_MANAGED(&src_rel[i]); + } +#endif + + for (; dups > 0; dups--) { + REBINT index = 0; + for (; index < ilen; ++index, ++dst_idx) { + Derelativize( + ARR_HEAD(dst_arr) + dst_idx, + src_rel + index, + specifier + ); + } + } + TERM_ARRAY_LEN(dst_arr, ARR_LEN(dst_arr)); + + ASSERT_ARRAY(dst_arr); + + return tail; } -/*********************************************************************** -** -*/ REBCNT Modify_String(REBCNT action, REBSER *dst_ser, REBCNT dst_idx, REBVAL *src_val, REBCNT flags, REBINT dst_len, REBINT dups) -/* -** action: INSERT, APPEND, CHANGE -** -** dst_ser: target -** dst_idx: position -** src_val: source -** flags: AN_PART -** dst_len: length to remove -** dups: dup count -** -** return: new dst_idx -** -***********************************************************************/ -{ - REBSER *src_ser = 0; - REBCNT src_idx = 0; - REBCNT src_len; - REBCNT tail = SERIES_TAIL(dst_ser); - REBINT size; // total to insert - - if (dups < 0) return (action == A_APPEND) ? 0 : dst_idx; - if (action == A_APPEND || dst_idx > tail) dst_idx = tail; - - // If the src_val is not a string, then we need to create a string: - if (GET_FLAG(flags, AN_SERIES)) { // used to indicate a BINARY series - if (IS_INTEGER(src_val)) { - src_ser = Append_Byte(0, Int8u(src_val)); // creates a binary - } - else if (IS_BLOCK(src_val)) { - src_ser = Join_Binary(src_val); // NOTE: it's the shared FORM buffer! - } - else if (IS_CHAR(src_val)) { - src_ser = Make_Binary(6); // (I hate unicode) - src_ser->tail = Encode_UTF8_Char(BIN_HEAD(src_ser), VAL_CHAR(src_val)); - } - else if (!ANY_BINSTR(src_val)) Trap_Arg(src_val); - } - else if (IS_CHAR(src_val)) { - src_ser = Append_Byte(0, VAL_CHAR(src_val)); // unicode ok too - } - else if (IS_BLOCK(src_val)) { - src_ser = Form_Tight_Block(src_val); - } - else if (!ANY_STR(src_val) || IS_TAG(src_val)) { - src_ser = Copy_Form_Value(src_val, 0); - } - - // Use either new src or the one that was passed: - if (src_ser) { - src_len = SERIES_TAIL(src_ser); - } - else { - src_ser = VAL_SERIES(src_val); - src_idx = VAL_INDEX(src_val); - src_len = VAL_LEN(src_val); - } - - // For INSERT or APPEND with /PART use the dst_len not src_len: - if (action != A_CHANGE && GET_FLAG(flags, AN_PART)) src_len = dst_len; - - // If Source == Destination we need to prevent possible conflicts. - // Clone the argument just to be safe. - // (Note: It may be possible to optimize special cases like append !!) - if (dst_ser == src_ser) { - src_ser = Copy_Series_Part(src_ser, src_idx, src_len); - src_idx = 0; - } - - // Total to insert: - size = dups * src_len; - - if (action != A_CHANGE) { - // Always expand dst_ser for INSERT and APPEND actions: - Expand_Series(dst_ser, dst_idx, size); - } else { - if (size > dst_len) - Expand_Series(dst_ser, dst_idx, size - dst_len); - else if (size < dst_len && GET_FLAG(flags, AN_PART)) - Remove_Series(dst_ser, dst_idx, dst_len - size); - else if (size + dst_idx > tail) { - EXPAND_SERIES_TAIL(dst_ser, size - (tail - dst_idx)); - } - } - - // For dup count: - for (; dups > 0; dups--) { - Insert_String(dst_ser, dst_idx, src_ser, src_idx, src_len, TRUE); - dst_idx += src_len; - } - - TERM_SERIES(dst_ser); - - return (action == A_APPEND) ? 0 : dst_idx; +// +// Modify_String: C +// +// Returns new dst_idx. +// +REBCNT Modify_String( + REBCNT action, // INSERT, APPEND, CHANGE + REBSER *dst_ser, // target + REBCNT dst_idx, // position + const REBVAL *src_val, // source + REBFLGS flags, // AM_PART, AM_BINARY_SERIES + REBINT dst_len, // length to remove + REBINT dups // dup count +) { + REBSER *src_ser = 0; + REBCNT src_idx = 0; + REBCNT src_len; + REBCNT tail = SER_LEN(dst_ser); + REBINT size; // total to insert + REBOOL needs_free; + REBINT limit; + + // For INSERT/PART and APPEND/PART + if (action != SYM_CHANGE && (flags & AM_PART)) + limit = dst_len; // should be non-negative + else + limit = -1; + + if (IS_VOID(src_val) || limit == 0 || dups < 0) + return (action == SYM_APPEND) ? 0 : dst_idx; + + if (action == SYM_APPEND || dst_idx > tail) dst_idx = tail; + + // If the src_val is not a string, then we need to create a string: + if (flags & AM_BINARY_SERIES) { + if (IS_INTEGER(src_val)) { + src_ser = Make_Series_Codepoint(Int8u(src_val)); + needs_free = TRUE; + limit = -1; + } + else if (IS_BLOCK(src_val)) { + src_ser = Join_Binary(src_val, limit); // NOTE: it's the shared FORM buffer! + needs_free = FALSE; + limit = -1; + } + else if (IS_CHAR(src_val)) { + // + // "UTF-8 was originally specified to allow codepoints with up to + // 31 bits (or 6 bytes). But with RFC3629, this was reduced to 4 + // bytes max. to be more compatible to UTF-16." So depending on + // which RFC you consider "the UTF-8", max size is either 4 or 6. + // + src_ser = Make_Binary(6); + SET_SERIES_LEN( + src_ser, + Encode_UTF8_Char(BIN_HEAD(src_ser), VAL_CHAR(src_val)) + ); + needs_free = TRUE; + limit = -1; + } + else if (ANY_STRING(src_val)) { + src_len = VAL_LEN_AT(src_val); + if (limit >= 0 && src_len > cast(REBCNT, limit)) + src_len = limit; + src_ser = Make_UTF8_From_Any_String(src_val, src_len, 0); + needs_free = TRUE; + limit = -1; + } + else if (!IS_BINARY(src_val)) + fail (src_val); + } + else if (IS_CHAR(src_val)) { + src_ser = Make_Series_Codepoint(VAL_CHAR(src_val)); + needs_free = TRUE; + } + else if (IS_BLOCK(src_val)) { + src_ser = Form_Tight_Block(src_val); + needs_free = TRUE; + } + else if (!ANY_STRING(src_val) || IS_TAG(src_val)) { + src_ser = Copy_Form_Value(src_val, 0); + needs_free = TRUE; + } + + // Use either new src or the one that was passed: + if (src_ser) { + src_len = SER_LEN(src_ser); + } + else { + src_ser = VAL_SERIES(src_val); + src_idx = VAL_INDEX(src_val); + src_len = VAL_LEN_AT(src_val); + needs_free = FALSE; + } + + if (limit >= 0) src_len = limit; + + // If Source == Destination we need to prevent possible conflicts. + // Clone the argument just to be safe. + // (Note: It may be possible to optimize special cases like append !!) + if (dst_ser == src_ser) { + assert(!needs_free); + src_ser = Copy_Sequence_At_Len(src_ser, src_idx, src_len); + needs_free = TRUE; + src_idx = 0; + } + + // Total to insert: + size = dups * src_len; + + if (action != SYM_CHANGE) { + // Always expand dst_ser for INSERT and APPEND actions: + Expand_Series(dst_ser, dst_idx, size); + } else { + if (size > dst_len) + Expand_Series(dst_ser, dst_idx, size - dst_len); + else if (size < dst_len && (flags & AM_PART)) + Remove_Series(dst_ser, dst_idx, dst_len - size); + else if (size + dst_idx > tail) { + EXPAND_SERIES_TAIL(dst_ser, size - (tail - dst_idx)); + } + } + + // For dup count: + for (; dups > 0; dups--) { + Insert_String(dst_ser, dst_idx, src_ser, src_idx, src_len, TRUE); + dst_idx += src_len; + } + + TERM_SEQUENCE(dst_ser); + + if (needs_free) { + // If we did not use the series that was passed in, but rather + // created an internal temporary one, we need to free it. + Free_Series(src_ser); + } + + return (action == SYM_APPEND) ? 0 : dst_idx; } diff --git a/src/core/f-qsort.c b/src/core/f-qsort.c index 0040472a75..dd42fdaf29 100644 --- a/src/core/f-qsort.c +++ b/src/core/f-qsort.c @@ -2,9 +2,24 @@ * https://raw.github.com/android/platform_bionic/master/libc/upstream-freebsd/lib/libc/stdlib/qsort.c */ + +// "The qsort_r() function is identical to qsort() except that the comparison +// function takes a third argument. A pointer is passed to the comparison +// function via [thunk]. In this way, the comparison function does not +// need to use global variables to pass through arbitrary arguments, and +// is therefore reentrant and safe to use in threads." +// +// This file can declare either qsort or qsort_r, and we'd like the latter. +// Note that `qsort_r` is part of no portability standard, and this version +// (used by Android) puts the "thunk" as the next to last parameter instead +// of the last one. :-/ +// +#define I_AM_QSORT_R + + /*- * Copyright (c) 1992, 1993 - * The Regents of the University of California. All rights reserved. + * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -32,7 +47,7 @@ */ #if defined(LIBC_SCCS) && !defined(lint) -static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; +static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; #endif /* LIBC_SCCS and not lint */ /* commented out by L.M. @@ -42,73 +57,85 @@ __FBSDID("$FreeBSD$"); #include +// When qsort_r is defined, it will actually wind up being named reb_qsort_r. +// Define this after including to avoid the prototype being +// declared as extern "C" +// +#define qsort_r reb_qsort_r + #ifdef I_AM_QSORT_R -typedef int cmp_t(void *, const void *, const void *); +typedef int cmp_t(void *, const void *, const void *); #else -typedef int cmp_t(const void *, const void *); +typedef int cmp_t(const void *, const void *); #endif -static inline char *med3(char *, char *, char *, cmp_t *, void *); -static inline void swapfunc(char *, char *, int, int); +#ifdef _MSC_VER +#define __inline__ +#else +#define __inline__ inline +#endif + +static __inline__ char *med3(char *, char *, char *, cmp_t *, void *); +static __inline__ void swapfunc(char *, char *, int, int); -#define min(a, b) (a) < (b) ? a : b +#if !defined(min) + #define min(a, b) (a) < (b) ? a : b +#endif /* * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". */ -#define swapcode(TYPE, parmi, parmj, n) { \ - long i = (n) / sizeof (TYPE); \ - TYPE *pi = (TYPE *) (parmi); \ - TYPE *pj = (TYPE *) (parmj); \ - do { \ - TYPE t = *pi; \ - *pi++ = *pj; \ - *pj++ = t; \ - } while (--i > 0); \ +#define swapcode(TYPE, parmi, parmj, n) { \ + long i = (n) / sizeof (TYPE); \ + TYPE *pi = (TYPE *) (parmi); \ + TYPE *pj = (TYPE *) (parmj); \ + do { \ + TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ } #define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ - es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; + es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; -static inline void -swapfunc(a, b, n, swaptype) - char *a, *b; - int n, swaptype; +static __inline__ void +swapfunc(char *a, char *b, int n, int swaptype) { - if(swaptype <= 1) - swapcode(long, a, b, n) - else - swapcode(char, a, b, n) + if(swaptype <= 1) + swapcode(long, a, b, n) + else + swapcode(char, a, b, n) } -#define swap(a, b) \ - if (swaptype == 0) { \ - long t = *(long *)(a); \ - *(long *)(a) = *(long *)(b); \ - *(long *)(b) = t; \ - } else \ - swapfunc(a, b, es, swaptype) +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long *)(a); \ + *(long *)(a) = *(long *)(b); \ + *(long *)(b) = t; \ + } else \ + swapfunc((char*)a, (char*)b, es, swaptype) -#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) +#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) #ifdef I_AM_QSORT_R -#define CMP(t, x, y) (cmp((t), (x), (y))) +#define CMP(t, x, y) (cmp((t), (x), (y))) #else -#define CMP(t, x, y) (cmp((x), (y))) +#define CMP(t, x, y) (cmp((x), (y))) #endif -static inline char * +static __inline__ char * med3(char *a, char *b, char *c, cmp_t *cmp, void *thunk #ifndef I_AM_QSORT_R -/* commented out by L.M. +/* commented out by L.M. __unused */ #endif ) { - return CMP(thunk, a, b) < 0 ? - (CMP(thunk, b, c) < 0 ? b : (CMP(thunk, a, c) < 0 ? c : a )) + return CMP(thunk, a, b) < 0 ? + (CMP(thunk, b, c) < 0 ? b : (CMP(thunk, a, c) < 0 ? c : a )) :(CMP(thunk, b, c) > 0 ? b : (CMP(thunk, a, c) < 0 ? a : c )); } @@ -121,86 +148,87 @@ void qsort(void *a, size_t n, size_t es, cmp_t *cmp) #endif { - char *pa, *pb, *pc, *pd, *pl, *pm, *pn; - size_t d, r; - int cmp_result; - int swaptype, swap_cnt; - -loop: SWAPINIT(a, es); - swap_cnt = 0; - if (n < 7) { - for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) - for (pl = pm; - pl > (char *)a && CMP(thunk, pl - es, pl) > 0; - pl -= es) - swap(pl, pl - es); - return; - } - pm = (char *)a + (n / 2) * es; - if (n > 7) { - pl = a; - pn = (char *)a + (n - 1) * es; - if (n > 40) { - d = (n / 8) * es; - pl = med3(pl, pl + d, pl + 2 * d, cmp, thunk); - pm = med3(pm - d, pm, pm + d, cmp, thunk); - pn = med3(pn - 2 * d, pn - d, pn, cmp, thunk); - } - pm = med3(pl, pm, pn, cmp, thunk); - } - swap(a, pm); - pa = pb = (char *)a + es; - - pc = pd = (char *)a + (n - 1) * es; - for (;;) { - while (pb <= pc && (cmp_result = CMP(thunk, pb, a)) <= 0) { - if (cmp_result == 0) { - swap_cnt = 1; - swap(pa, pb); - pa += es; - } - pb += es; - } - while (pb <= pc && (cmp_result = CMP(thunk, pc, a)) >= 0) { - if (cmp_result == 0) { - swap_cnt = 1; - swap(pc, pd); - pd -= es; - } - pc -= es; - } - if (pb > pc) - break; - swap(pb, pc); - swap_cnt = 1; - pb += es; - pc -= es; - } - if (swap_cnt == 0) { /* Switch to insertion sort */ - for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) - for (pl = pm; - pl > (char *)a && CMP(thunk, pl - es, pl) > 0; - pl -= es) - swap(pl, pl - es); - return; - } - - pn = (char *)a + n * es; - r = min(pa - (char *)a, pb - pa); - vecswap(a, pb - r, r); - r = min(pd - pc, pn - pd - es); - vecswap(pb, pn - r, r); - if ((r = pb - pa) > es) + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + size_t d, r; + int cmp_result; + int swaptype, swap_cnt; + +loop: SWAPINIT(a, es); + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; + pl > (char *)a && CMP(thunk, pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = (char *)a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp, thunk); + pm = med3(pm - d, pm, pm + d, cmp, thunk); + pn = med3(pn - 2 * d, pn - d, pn, cmp, thunk); + } + pm = med3(pl, pm, pn, cmp, thunk); + } + swap(a, pm); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (cmp_result = CMP(thunk, pb, a)) <= 0) { + if (cmp_result == 0) { + swap_cnt = 1; + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (cmp_result = CMP(thunk, pc, a)) >= 0) { + if (cmp_result == 0) { + swap_cnt = 1; + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swap(pb, pc); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; + pl > (char *)a && CMP(thunk, pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + + pn = (char *)a + n * es; + r = min(pa - (char *)a, pb - pa); + vecswap((char*)a, (char *)(pb - r), r); + // !!! Ren/C: pn - pd - es => (long)(pn - pd - ps) for -Wsign-compare + r = min(pd - pc, (long)(pn - pd - es)); + vecswap(pb, pn - r, r); + if ((r = pb - pa) > es) #ifdef I_AM_QSORT_R - qsort_r(a, r / es, es, thunk, cmp); + qsort_r(a, r / es, es, thunk, cmp); #else - qsort(a, r / es, es, cmp); + qsort(a, r / es, es, cmp); #endif - if ((r = pd - pc) > es) { - /* Iterate rather than recurse to save stack space */ - a = pn - r; - n = r / es; - goto loop; - } -/* qsort(pn - r, r / es, es, cmp);*/ + if ((r = pd - pc) > es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } +/* qsort(pn - r, r / es, es, cmp);*/ } diff --git a/src/core/f-random.c b/src/core/f-random.c index 99cacc3f8b..c51582a264 100644 --- a/src/core/f-random.c +++ b/src/core/f-random.c @@ -1,74 +1,76 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: f-random.c -** Summary: random number generation -** Section: functional -** Notes: -** -***********************************************************************/ +// +// File: %f-random.c +// Summary: "random number generation" +// Section: functional +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/* This program by D E Knuth is in the public domain and freely copyable. - * It is explained in Seminumerical Algorithms, 3rd edition, Section 3.6 - * (or in the errata to the 2nd edition --- see - * http://www-cs-faculty.stanford.edu/~knuth/taocp.html - * in the changes to Volume 2 on pages 171 and following). */ +/* This program by D E Knuth is in the public domain and freely copyable. + * It is explained in Seminumerical Algorithms, 3rd edition, Section 3.6 + * (or in the errata to the 2nd edition --- see + * http://www-cs-faculty.stanford.edu/~knuth/taocp.html + * in the changes to Volume 2 on pages 171 and following). */ -/* N.B. The MODIFICATIONS introduced in the 9th printing (2002) are - included here; there's no backwards compatibility with the original. */ +/* N.B. The MODIFICATIONS introduced in the 9th printing (2002) are + included here; there's no backwards compatibility with the original. */ -/* This version also adopts Brendan McKay's suggestion to - accommodate naive users who forget to call Set_Random (seed). */ +/* This version also adopts Brendan McKay's suggestion to + accommodate naive users who forget to call Set_Random (seed). */ -/* If you find any bugs, please report them immediately to - * taocp@cs.stanford.edu - * (and you will be rewarded if the bug is genuine). Thanks! */ +/* If you find any bugs, please report them immediately to + * taocp@cs.stanford.edu + * (and you will be rewarded if the bug is genuine). Thanks! */ /************ see the book for explanations and caveats! *******************/ /************ in particular, you need two's complement arithmetic **********/ /* Modified by Ladislav Mecir for REBOL to generate 62-bit numbers */ -#define KK 100 /* the long lag */ -#define LL 37 /* the short lag */ -#define MM ((REBI64)1<<62) /* the modulus, 2^62 */ -#define mod_diff(x,y) (((x)-(y))&(MM-1)) /* subtraction mod MM */ +#define KK 100 /* the long lag */ +#define LL 37 /* the short lag */ +#define MM ((REBI64)1<<62) /* the modulus, 2^62 */ +#define mod_diff(x,y) (((x)-(y))&(MM-1)) /* subtraction mod MM */ -static REBI64 ran_x[KK]; /* the generator state */ +static REBI64 ran_x[KK]; /* the generator state */ -#ifdef __STDC__ +#if defined __STDC__ || defined __cplusplus void ran_array(REBI64 aa[], int n) #else -void ran_array(aa,n) /* put n new random numbers in aa */ - REBI64 *aa; /* destination */ - int n; /* array length (must be at least KK) */ +void ran_array(aa,n) /* put n new random numbers in aa */ + REBI64 *aa; /* destination */ + int n; /* array length (must be at least KK) */ #endif { - register int i,j; - for (j=0;j=MM) ss-=MM-2; /* cyclic shift 61 bits */ - } - x[1]++; /* make x[1] (and only x[1]) odd */ - for (ss=seed&(MM-1),t=TT-1; t;) { - for (j=KK-1;j>0;j--) x[j+j]=x[j], x[j+j-1]=0; /* "square" */ - for (j=KK+KK-2;j>=KK;j--) - x[j-(KK-LL)]=mod_diff(x[j-(KK-LL)],x[j]), - x[j-KK]=mod_diff(x[j-KK],x[j]); - if (is_odd(ss)) { /* "multiply by z" */ - for (j=KK;j>0;j--) x[j]=x[j-1]; - x[0]=x[KK]; /* shift the buffer cyclically */ - x[LL]=mod_diff(x[LL],x[KK]); - } - if (ss) ss>>=1; else t--; - } - for (j=0;j=MM) ss-=MM-2; /* cyclic shift 61 bits */ + } + x[1]++; /* make x[1] (and only x[1]) odd */ + for (ss=seed&(MM-1),t=TT-1; t;) { + for (j=KK-1;j>0;j--) x[j+j]=x[j], x[j+j-1]=0; /* "square" */ + for (j=KK+KK-2;j>=KK;j--) + x[j-(KK-LL)]=mod_diff(x[j-(KK-LL)],x[j]), + x[j-KK]=mod_diff(x[j-KK],x[j]); + if (is_odd(ss)) { /* "multiply by z" */ + for (j=KK;j>0;j--) x[j]=x[j-1]; + x[0]=x[KK]; /* shift the buffer cyclically */ + x[LL]=mod_diff(x[LL],x[KK]); + } + if (ss) ss>>=1; else t--; + } + for (j=0;j=0? *ran_arr_ptr++: ran_arr_cycle()) static REBI64 ran_arr_cycle() { - if (ran_arr_ptr==&ran_arr_dummy) - Set_Random(314159L); /* the user forgot to initialize */ - ran_array(ran_arr_buf,QUALITY); - ran_arr_buf[KK]=-1; - ran_arr_ptr=ran_arr_buf+1; - return ran_arr_buf[0]; + if (ran_arr_ptr==&ran_arr_dummy) + Set_Random(314159L); /* the user forgot to initialize */ + ran_array(ran_arr_buf,QUALITY); + ran_arr_buf[KK]=-1; + ran_arr_ptr=ran_arr_buf+1; + return ran_arr_buf[0]; } -/*********************************************************************** -** -*/ REBI64 Random_Int(REBFLG secure) -/* -** Return random integer. Secure uses SHA1 for better safety. -** -***********************************************************************/ +// +// Random_Int: C +// +// Return random integer. Secure uses SHA1 for better safety. +// +REBI64 Random_Int(REBOOL secure) { - REBI64 tmp; - tmp = ran_arr_next(); + REBI64 tmp; + tmp = ran_arr_next(); - if (secure) { - REBYTE srcbuf[20], dstbuf[20]; + if (secure) { + REBYTE srcbuf[20], dstbuf[20]; - memcpy(srcbuf, (REBYTE*)&tmp, sizeof(tmp)); - memset(srcbuf + sizeof(tmp), *(REBYTE*)&tmp, 20 - sizeof(tmp)); + memcpy(srcbuf, &tmp, sizeof(tmp)); + memset(srcbuf + sizeof(tmp), *(REBYTE*)&tmp, 20 - sizeof(tmp)); - SHA1(srcbuf, 20, dstbuf); - memcpy((REBYTE*)&tmp, dstbuf, sizeof(tmp)); - } + SHA1(srcbuf, 20, dstbuf); + memcpy(&tmp, dstbuf, sizeof(tmp)); + } - return tmp; + return tmp; } -#define MAX_U64 ((REBU64)(REBI64)-1) -/*********************************************************************** -** -*/ REBI64 Random_Range(REBI64 r, REBFLG secure) -/* -***********************************************************************/ +// +// Random_Range: C +// +REBI64 Random_Range(REBI64 r, REBOOL secure) { - REBU64 s, m, u; - if (r == 0) return 0; - s = (r < 0) ? -r : r; - if (!secure && s > MM) Trap0(RE_OVERFLOW); - m = secure ? MAX_U64 - (MAX_U64 - s + 1) % s : MM - MM % s - 1; /* rejection limit */ - do u = Random_Int(secure); while (u > m); /* get a random below the limit */ - u = u % s + 1; - return (r > 0) ? u : - (REBI64)u; + REBU64 s, m, u; + if (r == 0) return 0; + s = (r < 0) ? -r : r; + if (!secure && s > MM) fail (Error_Overflow_Raw()); + m = secure ? MAX_U64 - (MAX_U64 - s + 1) % s : MM - MM % s - 1; /* rejection limit */ + do u = Random_Int(secure); while (u > m); /* get a random below the limit */ + u = u % s + 1; + return (r > 0) ? cast(REBI64, u) : -cast(REBI64, u); } -/*********************************************************************** -** -*/ REBDEC Random_Dec(REBDEC r, REBFLG secure) -/* -***********************************************************************/ +// +// Random_Dec: C +// +REBDEC Random_Dec(REBDEC r, REBOOL secure) { - REBDEC t, s; - t = secure ? 5.4210108624275222e-20 /* 2^-64 */ : 2.1684043449710089e-19 /* 2^-62 */; - /* care is taken to never overflow and yield a correct sign */ - s = (REBDEC)Random_Int(secure); - if (s < 0.0) s += 1.8446744073709552e19; - return (s * t) * r; + REBDEC t, s; + t = secure ? 5.4210108624275222e-20 /* 2^-64 */ : 2.1684043449710089e-19 /* 2^-62 */; + /* care is taken to never overflow and yield a correct sign */ + s = (REBDEC)Random_Int(secure); + if (s < 0.0) s += 1.8446744073709552e19; + return (s * t) * r; } diff --git a/src/core/f-round.c b/src/core/f-round.c index d62b7cccbc..c88379423b 100644 --- a/src/core/f-round.c +++ b/src/core/f-round.c @@ -1,227 +1,204 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: f-round.c -** Summary: special rounding math functions -** Section: functional -** Notes: -** -***********************************************************************/ +// +// File: %f-round.c +// Summary: "special rounding math functions" +// Section: functional +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" #include "sys-deci-funcs.h" -// Rounding flags (passed as refinements to ROUND function): -enum { - RF_TO, - RF_EVEN, - RF_DOWN, - RF_HALF_DOWN, - RF_FLOOR, - RF_CEILING, - RF_HALF_CEILING -}; - -#define RB_DFC (1 << RF_DOWN | 1 << RF_FLOOR | 1 << RF_CEILING) - -/*********************************************************************** -** -*/ REBCNT Get_Round_Flags(REBVAL *ds) -/* -** 1 n [number! money! time!] "The value to round" -** 2 /to "Return the nearest multiple of the scale parameter" -** 3 scale [number! money! time!] "Must be a non-zero value" -** 4 /even "Halves round toward even results" -** 5 /down "Round toward zero, ignoring discarded digits. (truncate)" -** 6 /half-down "Halves round toward zero" -** 7 /floor "Round in negative direction" -** 8 /ceiling "Round in positive direction" -** 9 /half-ceiling "Halves round in positive direction" -** -***********************************************************************/ +#define Dec_Trunc(x) (((x) < 0.0) ? -1.0 : 1.0) * floor(fabs(x)) +#define Dec_Away(x) (((x) < 0.0) ? -1.0 : 1.0) * ceil(fabs(x)) + +// +// Round_Dec: C +// +// Identical to ROUND mezzanine function. +// Note: scale arg only valid if RF_TO is set +// +REBDEC Round_Dec(REBDEC dec, REBCNT flags, REBDEC scale) { - REBCNT flags = 0; + REBDEC r; + int e; + REBOOL v; + union {REBDEC d; REBI64 i;} m; + REBI64 j; + + if (flags & RF_TO) { + if (scale == 0.0) fail (Error_Zero_Divide_Raw()); + scale = fabs(scale); + } else scale = 1.0; + + /* is scale negligible? */ + if (scale < ldexp(fabs(dec), -53)) return dec; + + if ((v = LOGICAL(scale >= 1.0))) dec = dec / scale; + else { + r = frexp(scale, &e); + if (e <= -1022) { + scale = r; + dec = ldexp(dec, e); + } else e = 0; + scale = 1.0 / scale; + dec = dec * scale; + } + if (flags & (RF_DOWN | RF_FLOOR | RF_CEILING)) { + if (flags & RF_FLOOR) dec = floor(dec); + else if (flags & RF_DOWN) dec = Dec_Trunc(dec); + else dec = ceil(dec); + } else { + /* integer-compare fabs(dec) and floor(fabs(dec)) + 0.5, + which is equivalent to "tolerant comparison" of the + fractional part with 0.5 */ + m.d = fabs(dec); + j = m.i; + m.d = floor(m.d) + 0.5; + if (j - m.i < -10) dec = Dec_Trunc(dec); + else if (j - m.i > 10) dec = Dec_Away(dec); + else if (flags & RF_EVEN) { + if (fmod(fabs(dec), 2.0) < 1.0) dec = Dec_Trunc(dec); + else dec = Dec_Away(dec); + } + else if (flags & RF_HALF_DOWN) dec = Dec_Trunc(dec); + else if (flags & RF_HALF_CEILING) dec = ceil(dec); + else dec = Dec_Away(dec); + } + + if (v) { + if (fabs(dec = dec * scale) != HUGE_VAL) + return dec; + else + fail (Error_Overflow_Raw()); + } + return ldexp(dec / scale, e); +} - if (D_REF(2)) SET_FLAG(flags, RF_TO); - if (D_REF(4)) SET_FLAG(flags, RF_EVEN); - if (D_REF(5)) SET_FLAG(flags, RF_DOWN); - if (D_REF(6)) SET_FLAG(flags, RF_HALF_DOWN); - if (D_REF(7)) SET_FLAG(flags, RF_FLOOR); - if (D_REF(8)) SET_FLAG(flags, RF_CEILING); - if (D_REF(9)) SET_FLAG(flags, RF_HALF_CEILING); +#define Int_Abs(x) ((x) < 0) ? -(x) : (x) - return flags; +#define Int_Trunc { \ + num = (num > 0) ? cast(REBI64, n - r) : -cast(REBI64, n - r); \ } +#define Int_Floor { \ + if (num > 0) \ + num = n - r; \ + else if ((m = n + s) <= cast(REBU64, 1) << 63) \ + num = -cast(REBI64, m); \ + else \ + fail (Error_Overflow_Raw()); \ +} -#define Dec_Trunc(x) (((x) < 0.0) ? -1.0 : 1.0) * floor(fabs(x)) -#define Dec_Away(x) (((x) < 0.0) ? -1.0 : 1.0) * ceil(fabs(x)) +#define Int_Ceil { \ + if (num < 0) \ + num = -cast(REBI64, n - r); \ + else if ((m = n + s) < cast(REBU64, 1) << 63) \ + num = m; \ + else \ + fail (Error_Overflow_Raw()); \ +} -/*********************************************************************** -** -*/ REBDEC Round_Dec(REBDEC dec, REBCNT flags, REBDEC scale) -/* -** Identical to ROUND mezzanine function. -** Note: scale arg only valid if RF_TO is set -** -***********************************************************************/ -{ - REBDEC r; - int e; - REBFLG v; - union {REBDEC d; REBI64 i;} m; - REBI64 j; - - if (GET_FLAG(flags, RF_TO)) { - if (scale == 0.0) Trap0(RE_ZERO_DIVIDE); - scale = fabs(scale); - } else scale = 1.0; - - /* is scale negligible? */ - if (scale < ldexp(fabs(dec), -53)) return dec; - - if (v = scale >= 1.0) dec = dec / scale; - else { - r = frexp(scale, &e); - if (e <= -1022) { - scale = r; - dec = ldexp(dec, e); - } else e = 0; - scale = 1.0 / scale; - dec = dec * scale; - } - if (flags & RB_DFC) { - if (GET_FLAG(flags, RF_FLOOR)) dec = floor(dec); - else if (GET_FLAG(flags, RF_DOWN)) dec = Dec_Trunc(dec); - else dec = ceil(dec); - } else { - /* integer-compare fabs(dec) and floor(fabs(dec)) + 0.5, - which is equivalent to "tolerant comparison" of the - fractional part with 0.5 */ - m.d = fabs(dec); - j = m.i; - m.d = floor(m.d) + 0.5; - if (j - m.i < -10) dec = Dec_Trunc(dec); - else if (j - m.i > 10) dec = Dec_Away(dec); - else if (GET_FLAG(flags, RF_EVEN)) { - if (fmod(fabs(dec), 2.0) < 1.0) dec = Dec_Trunc(dec); - else dec = Dec_Away(dec); - } - else if (GET_FLAG(flags, RF_HALF_DOWN)) dec = Dec_Trunc(dec); - else if (GET_FLAG(flags, RF_HALF_CEILING)) dec = ceil(dec); - else dec = Dec_Away(dec); - } - - if (v) { - if (fabs(dec = dec * scale) != HUGE_VAL) return dec; - else Trap0(RE_OVERFLOW); - } - return ldexp(dec / scale, e); +#define Int_Away { \ + if ((m = n + s) >= cast(REBU64, 1) << 63) \ + if (num < 0 && m == cast(REBU64, 1) << 63) \ + num = m; \ + else \ + fail (Error_Overflow_Raw()); \ + else \ + num = (num > 0) ? cast(REBI64, m) : -cast(REBI64, m); \ } -#define Int_Abs(x) ((x) < 0) ? -(x) : (x) -#define Int_Trunc num = (num > 0) ? n - r : -(REBI64)(n - r) -#define Int_Floor {\ - if (num > 0) num = n - r;\ - else if ((m = n + s) <= (REBU64)1 << 63) num = -(REBI64)m;\ - else Trap0(RE_OVERFLOW);\ - } -#define Int_Ceil {\ - if (num < 0) num = -(REBI64)(n - r);\ - else if ((m = n + s) < (REBU64)1 << 63) num = m;\ - else Trap0(RE_OVERFLOW);\ - } -#define Int_Away if ((m = n + s) >= (REBU64)1 << 63)\ - if (num < 0 && m == (REBU64) 1 << 63) num = m;\ - else Trap0(RE_OVERFLOW);\ - else num = (num > 0) ? m : -(REBI64)m - -/*********************************************************************** -** -*/ REBI64 Round_Int(REBI64 num, REBCNT flags, REBI64 scale) -/* -** Identical to ROUND mezzanine function. -** Note: scale arg only valid if RF_TO is set -** -***********************************************************************/ + +// +// Round_Int: C +// +// Identical to ROUND mezzanine function. +// Note: scale arg only valid if RF_TO is set +// +REBI64 Round_Int(REBI64 num, REBCNT flags, REBI64 scale) { - /* using safe unsigned arithmetic */ - REBU64 sc, n, r, m, s; - - if (GET_FLAG(flags, RF_TO)) { - if (scale == 0) Trap0(RE_ZERO_DIVIDE); - sc = Int_Abs(scale); - } - else sc = 1; - - n = Int_Abs(num); - r = n % sc; - s = sc - r; - if (r == 0) return num; - - if (flags & RB_DFC) { - if (GET_FLAG(flags, RF_DOWN)) {Int_Trunc; return num;} - if (GET_FLAG(flags, RF_FLOOR)) {Int_Floor; return num;} - Int_Ceil; return num; - } - - /* "genuine" rounding */ - if (r < s) {Int_Trunc; return num;} - else if (r > s) {Int_Away; return num;} - - /* half */ - if (GET_FLAG(flags, RF_EVEN)) { - if ((n / sc) & 1) {Int_Away; return num;} - else {Int_Trunc; return num;} - } - if (GET_FLAG(flags, RF_HALF_DOWN)) {Int_Trunc; return num;} - if (GET_FLAG(flags, RF_HALF_CEILING)) {Int_Ceil; return num;} - - Int_Away; return num; /* this is round_half_away */ + /* using safe unsigned arithmetic */ + REBU64 sc, n, r, m, s; + + if (flags & RF_TO) { + if (scale == 0) fail (Error_Zero_Divide_Raw()); + sc = Int_Abs(scale); + } + else sc = 1; + + n = Int_Abs(num); + r = n % sc; + s = sc - r; + if (r == 0) return num; + + if (flags & (RF_DOWN | RF_FLOOR | RF_CEILING)) { + if (flags & RF_DOWN) {Int_Trunc; return num;} + if (flags & RF_FLOOR) {Int_Floor; return num;} + Int_Ceil; + return num; + } + + /* "genuine" rounding */ + if (r < s) {Int_Trunc; return num;} + else if (r > s) {Int_Away; return num;} + + /* half */ + if (flags & RF_EVEN) { + if ((n / sc) & 1) {Int_Away; return num;} + else {Int_Trunc; return num;} + } + if (flags & RF_HALF_DOWN) {Int_Trunc; return num;} + if (flags & RF_HALF_CEILING) {Int_Ceil; return num;} + + Int_Away; return num; /* this is round_half_away */ } -/*********************************************************************** -** -*/ REBDCI Round_Deci(REBDCI num, REBCNT flags, REBDCI scale) -/* -** Identical to ROUND mezzanine function. -** Note: scale arg only valid if RF_TO is set -** -***********************************************************************/ +// +// Round_Deci: C +// +// Identical to ROUND mezzanine function. +// Note: scale arg only valid if RF_TO is set +// +deci Round_Deci(deci num, REBCNT flags, deci scale) { - REBDCI deci_one = {1u, 0u, 0u, 0u, 0}; - - if (GET_FLAG(flags, RF_TO)) { - if (deci_is_zero(scale)) Trap0(RE_ZERO_DIVIDE); - scale = deci_abs(scale); - } - else scale = deci_one; - - if (GET_FLAG(flags, RF_EVEN)) return deci_half_even(num, scale); - if (GET_FLAG(flags, RF_DOWN)) return deci_truncate(num, scale); - if (GET_FLAG(flags, RF_HALF_DOWN)) return deci_half_truncate(num, scale); - if (GET_FLAG(flags, RF_FLOOR)) return deci_floor(num, scale); - if (GET_FLAG(flags, RF_CEILING)) return deci_ceil(num, scale); - if (GET_FLAG(flags, RF_HALF_CEILING)) return deci_half_ceil(num, scale); - - return deci_half_away(num, scale); + deci deci_one = {1u, 0u, 0u, 0u, 0}; + + if (flags & RF_TO) { + if (deci_is_zero(scale)) fail (Error_Zero_Divide_Raw()); + scale = deci_abs(scale); + } + else scale = deci_one; + + if (flags & RF_EVEN) return deci_half_even(num, scale); + if (flags & RF_DOWN) return deci_truncate(num, scale); + if (flags & RF_HALF_DOWN) return deci_half_truncate(num, scale); + if (flags & RF_FLOOR) return deci_floor(num, scale); + if (flags & RF_CEILING) return deci_ceil(num, scale); + if (flags & RF_HALF_CEILING) return deci_half_ceil(num, scale); + + return deci_half_away(num, scale); } diff --git a/src/core/f-series.c b/src/core/f-series.c index 172f4a40b8..45bd42b22e 100644 --- a/src/core/f-series.c +++ b/src/core/f-series.c @@ -1,321 +1,321 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: f-series.c -** Summary: common series handling functions -** Section: functional -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %f-series.c +// Summary: "common series handling functions" +// Section: functional +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" +#include "sys-deci-funcs.h" #define THE_SIGN(v) ((v < 0) ? -1 : (v > 0) ? 1 : 0) -/*********************************************************************** -** -*/ REBINT Do_Series_Action(REBCNT action, REBVAL *value, REBVAL *arg) -/* -** Common series functions. -** -***********************************************************************/ -{ - REBINT index; - REBINT tail; - REBINT len = 0; - - // Common setup code for all actions: - if (action != A_MAKE && action != A_TO) { - index = (REBINT)VAL_INDEX(value); - tail = (REBINT)VAL_TAIL(value); - } else return -1; - - switch (action) { - - //-- Navigation: - - case A_HEAD: - VAL_INDEX(value) = 0; - break; - - case A_TAIL: - VAL_INDEX(value) = (REBCNT)tail; - break; - - case A_HEADQ: - DECIDE(index == 0); - - case A_TAILQ: - DECIDE(index >= tail); - - case A_PASTQ: - DECIDE(index > tail); - - case A_NEXT: - if (index < tail) VAL_INDEX(value)++; - break; - - case A_BACK: - if (index > 0) VAL_INDEX(value)--; - break; - - case A_SKIP: - case A_AT: - len = Get_Num_Arg(arg); - { - REBI64 i = (REBI64)index + (REBI64)len; - if (action == A_SKIP) { - if (IS_LOGIC(arg)) i--; - } else { // A_AT - if (len > 0) i--; - } - if (i > (REBI64)tail) i = (REBI64)tail; - else if (i < 0) i = 0; - VAL_INDEX(value) = (REBCNT)i; - } - break; -/* - case A_ATZ: - len = Get_Num_Arg(arg); - { - REBI64 idx = Add_Max(0, index, len, MAX_I32); - if (idx < 0) idx = 0; - VAL_INDEX(value) = (REBCNT)idx; - } - break; -*/ - case A_INDEXQ: - SET_INTEGER(DS_RETURN, ((REBI64)index) + 1); - return R_RET; - - case A_LENGTHQ: - SET_INTEGER(DS_RETURN, tail > index ? tail - index : 0); - return R_RET; - - case A_REMOVE: - // /PART length - TRAP_PROTECT(VAL_SERIES(value)); - len = DS_REF(2) ? Partial(value, 0, DS_ARG(3), 0) : 1; - index = (REBINT)VAL_INDEX(value); - if (index < tail && len != 0) - Remove_Series(VAL_SERIES(value), VAL_INDEX(value), len); - break; - - case A_ADD: // Join_Strings(value, arg); - case A_SUBTRACT: // "test this" - 10 - case A_MULTIPLY: // "t" * 4 = "tttt" - case A_DIVIDE: - case A_REMAINDER: - case A_POWER: - case A_ODDQ: - case A_EVENQ: - case A_ABSOLUTE: - Trap_Action(VAL_TYPE(value), action); - - default: - return -1; - } - - DS_RET_VALUE(value); - return R_RET; - -is_false: - return R_FALSE; - -is_true: - return R_TRUE; +// +// Series_Common_Action_Maybe_Unhandled: C +// +// This routine is called to handle actions on ANY-SERIES! that can be taken +// care of without knowing what specific kind of series it is. So generally +// index manipulation, and things like LENGTH/etc. +// +REB_R Series_Common_Action_Maybe_Unhandled( + REBFRM *frame_, + REBSYM action +) { + REBVAL *value = D_ARG(1); + REBVAL *arg = D_ARGC > 1 ? D_ARG(2) : NULL; + + REBINT index = cast(REBINT, VAL_INDEX(value)); + REBINT tail = cast(REBINT, VAL_LEN_HEAD(value)); + REBINT len = 0; + + switch (action) { + + //-- Navigation: + + case SYM_HEAD_OF: + VAL_INDEX(value) = 0; + break; + + case SYM_TAIL_OF: + VAL_INDEX(value) = cast(REBCNT, tail); + break; + + case SYM_HEAD_Q: + return R_FROM_BOOL(LOGICAL(index == 0)); + + case SYM_TAIL_Q: + return R_FROM_BOOL(LOGICAL(index >= tail)); + + case SYM_PAST_Q: + return R_FROM_BOOL(LOGICAL(index > tail)); + + case SYM_SKIP: + case SYM_AT: + len = Get_Num_From_Arg(arg); + { + REBI64 i = (REBI64)index + (REBI64)len; + if (action == SYM_SKIP) { + if (IS_LOGIC(arg)) i--; + } else { // A_AT + if (len > 0) i--; + } + if (i > (REBI64)tail) i = (REBI64)tail; + else if (i < 0) i = 0; + VAL_INDEX(value) = (REBCNT)i; + } + break; + + case SYM_INDEX_OF: + Init_Integer(D_OUT, cast(REBI64, index) + 1); + return R_OUT; // handled + + case SYM_LENGTH_OF: + Init_Integer(D_OUT, tail > index ? tail - index : 0); + return R_OUT; // handled + + case SYM_REMOVE: { + INCLUDE_PARAMS_OF_REMOVE; + + UNUSED(PAR(series)); // already accounted for + + if (REF(map)) { + UNUSED(ARG(key)); + fail (Error_Bad_Refines_Raw()); + } + + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(value)); + len = REF(part) ? Partial(value, 0, ARG(limit)) : 1; + index = cast(REBINT, VAL_INDEX(value)); + if (index < tail && len != 0) + Remove_Series(VAL_SERIES(value), VAL_INDEX(value), len); + break; } + + default: + return R_UNHANDLED; // not a common operation, not handled + } + + Move_Value(D_OUT, value); + return R_OUT; } -/*********************************************************************** -** -*/ REBINT Cmp_Block(REBVAL *sval, REBVAL *tval, REBFLG is_case) -/* -** Compare two blocks and return the difference of the first -** non-matching value. -** -***********************************************************************/ +// +// Cmp_Array: C +// +// Compare two arrays and return the difference of the first +// non-matching value. +// +REBINT Cmp_Array(const RELVAL *sval, const RELVAL *tval, REBOOL is_case) { - REBVAL *s = VAL_BLK_DATA(sval); - REBVAL *t = VAL_BLK_DATA(tval); - REBINT diff; - - CHECK_STACK(&s); - - if ((VAL_SERIES(sval)==VAL_SERIES(tval))&& - (VAL_INDEX(sval)==VAL_INDEX(tval))) - return 0; - - while (!IS_END(s) && (VAL_TYPE(s) == VAL_TYPE(t) || - (IS_NUMBER(s) && IS_NUMBER(t)))) { - if ((diff = Cmp_Value(s, t, is_case)) != 0) - return diff; - s++, t++; - } - return VAL_TYPE(s) - VAL_TYPE(t); + RELVAL *s = VAL_ARRAY_AT(sval); + RELVAL *t = VAL_ARRAY_AT(tval); + REBINT diff; + + if (C_STACK_OVERFLOWING(&s)) Trap_Stack_Overflow(); + + if ((VAL_SERIES(sval)==VAL_SERIES(tval))&& + (VAL_INDEX(sval)==VAL_INDEX(tval))) + return 0; + + if (IS_END(s) || IS_END(t)) goto diff_of_ends; + + while ( + (VAL_TYPE(s) == VAL_TYPE(t) || + (ANY_NUMBER(s) && ANY_NUMBER(t))) + ) { + if ((diff = Cmp_Value(s, t, is_case)) != 0) + return diff; + + s++; + t++; + + if (IS_END(s) || IS_END(t)) goto diff_of_ends; + } + + return VAL_TYPE(s) - VAL_TYPE(t); + +diff_of_ends: + // Treat end as if it were a REB_xxx type of 0, so all other types would + // compare larger than it. + // + if (IS_END(s)) { + if (IS_END(t)) return 0; + return -1; + } + return 1; } -/*********************************************************************** -** -*/ REBINT Cmp_Value(REBVAL *s, REBVAL *t, REBFLG is_case) -/* -** Compare two values and return the difference. -** -** is_case TRUE for case sensitive compare -** -***********************************************************************/ +// +// Cmp_Value: C +// +// Compare two values and return the difference. +// +// is_case TRUE for case sensitive compare +// +REBINT Cmp_Value(const RELVAL *s, const RELVAL *t, REBOOL is_case) { - REBDEC d1, d2; - - if (VAL_TYPE(t) != VAL_TYPE(s) && !(IS_NUMBER(s) && IS_NUMBER(t))) - return VAL_TYPE(s) - VAL_TYPE(t); - - switch(VAL_TYPE(s)) { - - case REB_INTEGER: - if (IS_DECIMAL(t)) { - d1 = (REBDEC)VAL_INT64(s); - d2 = VAL_DECIMAL(t); - goto chkDecimal; - } - return THE_SIGN(VAL_INT64(s) - VAL_INT64(t)); - - case REB_LOGIC: - return VAL_LOGIC(s) - VAL_LOGIC(t); - - case REB_CHAR: - if (is_case) return THE_SIGN(VAL_CHAR(s) - VAL_CHAR(t)); - return THE_SIGN((REBINT)(UP_CASE(VAL_CHAR(s)) - UP_CASE(VAL_CHAR(t)))); - - case REB_DECIMAL: - case REB_MONEY: - d1 = VAL_DECIMAL(s); - if (IS_INTEGER(t)) - d2 = (REBDEC)VAL_INT64(t); - else - d2 = VAL_DECIMAL(t); + REBDEC d1, d2; + + if (VAL_TYPE(t) != VAL_TYPE(s) && !(ANY_NUMBER(s) && ANY_NUMBER(t))) + return VAL_TYPE(s) - VAL_TYPE(t); + + assert(NOT_END(s) && NOT_END(t)); + + switch(VAL_TYPE(s)) { + + case REB_INTEGER: + if (IS_DECIMAL(t)) { + d1 = (REBDEC)VAL_INT64(s); + d2 = VAL_DECIMAL(t); + goto chkDecimal; + } + return THE_SIGN(VAL_INT64(s) - VAL_INT64(t)); + + case REB_LOGIC: + return VAL_LOGIC(s) - VAL_LOGIC(t); + + case REB_CHAR: + if (is_case) return THE_SIGN(VAL_CHAR(s) - VAL_CHAR(t)); + return THE_SIGN((REBINT)(UP_CASE(VAL_CHAR(s)) - UP_CASE(VAL_CHAR(t)))); + + case REB_PERCENT: + case REB_DECIMAL: + case REB_MONEY: + if (IS_MONEY(s)) + d1 = deci_to_decimal(VAL_MONEY_AMOUNT(s)); + else + d1 = VAL_DECIMAL(s); + if (IS_INTEGER(t)) + d2 = cast(REBDEC, VAL_INT64(t)); + else if (IS_MONEY(t)) + d2 = deci_to_decimal(VAL_MONEY_AMOUNT(t)); + else + d2 = VAL_DECIMAL(t); chkDecimal: - if (Eq_Decimal(d1, d2)) - return 0; - if (d1 < d2) - return -1; - return 1; - - case REB_PAIR: - return Cmp_Pair(s, t); - - case REB_EVENT: - return Cmp_Event(s, t); - - case REB_GOB: - return Cmp_Gob(s, t); - - case REB_TUPLE: - return Cmp_Tuple(s, t); - - case REB_TIME: - return Cmp_Time(s, t); - - case REB_DATE: - return Cmp_Date(s, t); - - case REB_BLOCK: - case REB_PAREN: - case REB_MAP: - case REB_PATH: - case REB_SET_PATH: - case REB_GET_PATH: - case REB_LIT_PATH: - return Cmp_Block(s, t, is_case); - - case REB_STRING: - case REB_FILE: - case REB_EMAIL: - case REB_URL: - case REB_TAG: - return Compare_String_Vals(s, t, (REBOOL)!is_case); - - case REB_BITSET: - case REB_BINARY: - case REB_IMAGE: - return Compare_Binary_Vals(s, t); - - case REB_VECTOR: - return Compare_Vector(s, t); - - case REB_DATATYPE: - return VAL_DATATYPE(s) - VAL_DATATYPE(t); - - case REB_WORD: - case REB_SET_WORD: - case REB_GET_WORD: - case REB_LIT_WORD: - case REB_REFINEMENT: - case REB_ISSUE: - return Compare_Word(s,t,is_case); - - case REB_ERROR: - return VAL_ERR_NUM(s) - VAL_ERR_NUM(s); - - case REB_OBJECT: - case REB_MODULE: - case REB_PORT: - return VAL_OBJ_FRAME(s) - VAL_OBJ_FRAME(t); - - case REB_NATIVE: - return &VAL_FUNC_CODE(s) - &VAL_FUNC_CODE(t); - - case REB_ACTION: - case REB_COMMAND: - case REB_OP: - case REB_FUNCTION: - return VAL_FUNC_BODY(s) - VAL_FUNC_BODY(t); - - case REB_NONE: - case REB_UNSET: - case REB_END: - default: - break; - - } - return 0; + if (Eq_Decimal(d1, d2)) + return 0; + if (d1 < d2) + return -1; + return 1; + + case REB_PAIR: + return Cmp_Pair(s, t); + + case REB_EVENT: + return Cmp_Event(s, t); + + case REB_GOB: + return Cmp_Gob(s, t); + + case REB_TUPLE: + return Cmp_Tuple(s, t); + + case REB_TIME: + return Cmp_Time(s, t); + + case REB_DATE: + return Cmp_Date(s, t); + + case REB_BLOCK: + case REB_GROUP: + case REB_MAP: + case REB_PATH: + case REB_SET_PATH: + case REB_GET_PATH: + case REB_LIT_PATH: + return Cmp_Array(s, t, is_case); + + case REB_STRING: + case REB_FILE: + case REB_EMAIL: + case REB_URL: + case REB_TAG: + return Compare_String_Vals(s, t, NOT(is_case)); + + case REB_BITSET: + case REB_BINARY: + case REB_IMAGE: + return Compare_Binary_Vals(s, t); + + case REB_VECTOR: + return Compare_Vector(s, t); + + case REB_DATATYPE: + return VAL_TYPE_KIND(s) - VAL_TYPE_KIND(t); + + case REB_WORD: + case REB_SET_WORD: + case REB_GET_WORD: + case REB_LIT_WORD: + case REB_REFINEMENT: + case REB_ISSUE: + return Compare_Word(s,t,is_case); + + case REB_ERROR: + return VAL_ERR_NUM(s) - VAL_ERR_NUM(t); + + case REB_OBJECT: + case REB_MODULE: + case REB_PORT: + return VAL_CONTEXT(s) - VAL_CONTEXT(t); + + case REB_FUNCTION: + return VAL_FUNC_PARAMLIST(s) - VAL_FUNC_PARAMLIST(t); + + case REB_LIBRARY: + return VAL_LIBRARY(s) - VAL_LIBRARY(t); + + case REB_STRUCT: + return Cmp_Struct(s, t); + + case REB_BLANK: + case REB_MAX_VOID: + default: + break; + + } + return 0; } -/*********************************************************************** -** -*/ REBCNT Find_Block_Simple(REBSER *series, REBCNT index, REBVAL *target) -/* -** Simple search for a value in a block. Return the index of -** the value or the TAIL index if not found. -** -***********************************************************************/ +// +// Find_In_Array_Simple: C +// +// Simple search for a value in an array. Return the index of +// the value or the TAIL index if not found. +// +REBCNT Find_In_Array_Simple(REBARR *array, REBCNT index, const RELVAL *target) { - REBVAL *value = BLK_HEAD(series); + RELVAL *value = ARR_HEAD(array); - for (; index < SERIES_TAIL(series); index++) { - if (0 == Cmp_Value(value+index, target, FALSE)) return index; - } + for (; index < ARR_LEN(array); index++) { + if (0 == Cmp_Value(value + index, target, FALSE)) + return index; + } - return SERIES_TAIL(series); + return ARR_LEN(array); } diff --git a/src/core/f-stubs.c b/src/core/f-stubs.c index 86903768e7..d66e8e8d23 100644 --- a/src/core/f-stubs.c +++ b/src/core/f-stubs.c @@ -1,948 +1,644 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: f-stubs.c -** Summary: miscellaneous little functions -** Section: functional -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %f-stubs.c +// Summary: "miscellaneous little functions" +// Section: functional +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" #include "sys-deci-funcs.h" -/*********************************************************************** -** -*/ void Long_To_Bytes(REBYTE *out, REBCNT in) -/* -***********************************************************************/ -{ - out[0] = (REBYTE) in; - out[1] = (REBYTE)(in >> 8); - out[2] = (REBYTE)(in >> 16); - out[3] = (REBYTE)(in >> 24); -} - -/*********************************************************************** -** -*/ REBCNT Bytes_To_Long(REBYTE const *in) -/* -***********************************************************************/ +// +// Get_Num_From_Arg: C +// +// Get the amount to skip or pick. +// Allow multiple types. Throw error if not valid. +// Note that the result is one-based. +// +REBINT Get_Num_From_Arg(const REBVAL *val) { - return (REBCNT) in[0] // & 0xFF - | (REBCNT) (in[1] << 8) // & 0xFF00; - | (REBCNT) (in[2] << 16) // & 0xFF0000; - | (REBCNT) (in[3] << 24); // & 0xFF000000; -} + REBINT n; + if (IS_INTEGER(val)) { + if (VAL_INT64(val) > (i64)MAX_I32 || VAL_INT64(val) < (i64)MIN_I32) + fail (Error_Out_Of_Range(val)); + n = VAL_INT32(val); + } + else if (IS_DECIMAL(val) || IS_PERCENT(val)) { + if (VAL_DECIMAL(val) > MAX_I32 || VAL_DECIMAL(val) < MIN_I32) + fail (Error_Out_Of_Range(val)); + n = (REBINT)VAL_DECIMAL(val); + } + else if (IS_LOGIC(val)) + n = (VAL_LOGIC(val) ? 1 : 2); + else + fail (val); -/*********************************************************************** -** -*/ REBCNT Find_Int(REBINT *array, REBINT num) -/* -***********************************************************************/ -{ - REBCNT n; - - for (n = 0; array[n] && array[n] != num; n++); - if (array[n]) return n; - return NOT_FOUND; + return n; } -/*********************************************************************** -** -*/ REBINT Get_Num_Arg(REBVAL *val) -/* -** Get the amount to skip or pick. -** Allow multiple types. Throw error if not valid. -** Note that the result is one-based. -** -***********************************************************************/ +// +// Float_Int16: C +// +REBINT Float_Int16(REBD32 f) { - REBINT n; - - if (IS_INTEGER(val)) { - if (VAL_INT64(val) > (i64)MAX_I32 || VAL_INT64(val) < (i64)MIN_I32) - Trap_Range(val); - n = VAL_INT32(val); - } - else if (IS_DECIMAL(val) || IS_PERCENT(val)) { - if (VAL_DECIMAL(val) > MAX_I32 || VAL_DECIMAL(val) < MIN_I32) - Trap_Range(val); - n = (REBINT)VAL_DECIMAL(val); - } - else if (IS_LOGIC(val)) n = (VAL_LOGIC(val) ? 1 : 2); - else Trap_Arg(val); - - return n; -} - + if (fabs(f) > cast(REBD32, 0x7FFF)) { + DECLARE_LOCAL (temp); + Init_Decimal(temp, f); -/*********************************************************************** -** -*/ REBINT Float_Int16(REBD32 f) -/* -***********************************************************************/ -{ - if (fabs(f) > (REBD32)(0x7FFF)) { - DS_PUSH_DECIMAL(f); - Trap_Range(DS_TOP); - } - return (REBINT)f; + fail (Error_Out_Of_Range(temp)); + } + return cast(REBINT, f); } -/*********************************************************************** -** -*/ REBINT Int32(REBVAL *val) -/* -***********************************************************************/ +// +// Int32: C +// +REBINT Int32(const RELVAL *val) { - REBINT n = 0; - - if (IS_DECIMAL(val)) { - if (VAL_DECIMAL(val) > MAX_I32 || VAL_DECIMAL(val) < MIN_I32) - Trap_Range(val); - n = (REBINT)VAL_DECIMAL(val); - } else { - if (VAL_INT64(val) > (i64)MAX_I32 || VAL_INT64(val) < (i64)MIN_I32) - Trap_Range(val); - n = VAL_INT32(val); - } - - return n; -} + if (IS_DECIMAL(val)) { + if (VAL_DECIMAL(val) > MAX_I32 || VAL_DECIMAL(val) < MIN_I32) + goto out_of_range; + return cast(REBINT, VAL_DECIMAL(val)); + } -/*********************************************************************** -** -*/ REBINT Int32s(REBVAL *val, REBINT sign) -/* -** Get integer as positive, negative 32 bit value. -** Sign field can be -** 0: >= 0 -** 1: > 0 -** -1: < 0 -** -***********************************************************************/ -{ - REBINT n = 0; - - if (IS_DECIMAL(val)) { - if (VAL_DECIMAL(val) > MAX_I32 || VAL_DECIMAL(val) < MIN_I32) - Trap_Range(val); - - n = (REBINT)VAL_DECIMAL(val); - } else { - if (VAL_INT64(val) > (i64)MAX_I32 || VAL_INT64(val) < (i64)MIN_I32) - Trap_Range(val); - - n = VAL_INT32(val); - } - - // More efficient to use positive sense: - if ( - (sign == 0 && n >= 0) || - (sign > 0 && n > 0) || - (sign < 0 && n < 0) - ) - return n; - - Trap_Range(val); - return 0; -} - + assert(IS_INTEGER(val)); -/*********************************************************************** -** -*/ REBI64 Int64(REBVAL *val) -/* -***********************************************************************/ -{ - if (IS_INTEGER(val)) return VAL_INT64(val); - if (IS_DECIMAL(val) || IS_PERCENT(val)) return (REBI64)VAL_DECIMAL(val); - if (IS_MONEY(val)) return deci_to_int(VAL_DECI(val)); - Trap_Arg(val); - return 0; -} + if ( + VAL_INT64(val) > cast(i64, MAX_I32) + || VAL_INT64(val) < cast(i64, MIN_I32) + ) { + goto out_of_range; + } + return VAL_INT32(val); -/*********************************************************************** -** -*/ REBDEC Dec64(REBVAL *val) -/* -***********************************************************************/ -{ - if (IS_DECIMAL(val) || IS_PERCENT(val)) return VAL_DECIMAL(val); - if (IS_INTEGER(val)) return (REBDEC)VAL_INT64(val); - if (IS_MONEY(val)) return deci_to_decimal(VAL_DECI(val)); - Trap_Arg(val); - return 0; +out_of_range: + fail (Error_Out_Of_Range(const_KNOWN(val))); } -/*********************************************************************** -** -*/ REBI64 Int64s(REBVAL *val, REBINT sign) -/* -** Get integer as positive, negative 64 bit value. -** Sign field can be -** 0: >= 0 -** 1: > 0 -** -1: < 0 -** -***********************************************************************/ +// +// Int32s: C +// +// Get integer as positive, negative 32 bit value. +// Sign field can be +// 0: >= 0 +// 1: > 0 +// -1: < 0 +// +REBINT Int32s(const RELVAL *val, REBINT sign) { - REBI64 n; - - if (IS_DECIMAL(val)) { - if (VAL_DECIMAL(val) > MAX_I64 || VAL_DECIMAL(val) < MIN_I64) - Trap_Range(val); - n = (REBI64)VAL_DECIMAL(val); - } else { - n = VAL_INT64(val); - } - - // More efficient to use positive sense: - if ( - (sign == 0 && n >= 0) || - (sign > 0 && n > 0) || - (sign < 0 && n < 0) - ) - return n; - - Trap_Range(val); - DEAD_END; -} + REBINT n; + if (IS_DECIMAL(val)) { + if (VAL_DECIMAL(val) > MAX_I32 || VAL_DECIMAL(val) < MIN_I32) + goto out_of_range; -/*********************************************************************** -** -*/ REBINT Int8u(REBVAL *val) -/* -***********************************************************************/ -{ - if (VAL_INT64(val) > (i64)255 || VAL_INT64(val) < (i64)0) Trap_Range(val); - return VAL_INT32(val); -} + n = cast(REBINT, VAL_DECIMAL(val)); + } else { + assert(IS_INTEGER(val)); + if (VAL_INT64(val) > cast(i64, MAX_I32)) + goto out_of_range; -/*********************************************************************** -** -*/ REBCNT Find_Refines(REBVAL *ds, REBCNT mask) -/* -** Scans the stack for function refinements that have been -** specified in the mask (each as a bit) and are being used. -** -***********************************************************************/ -{ - REBINT n; - REBCNT result = 0; - REBINT len = DS_ARGC; - - for (n = 0; n < len; n++) { - if ((mask & (1 << n) && D_REF(n+1))) - result |= 1 << n; - } - return result; -} + n = VAL_INT32(val); + } + // More efficient to use positive sense: + if ( + (sign == 0 && n >= 0) || + (sign > 0 && n > 0) || + (sign < 0 && n < 0) + ) + return n; -/*********************************************************************** -** -*/ void Set_Datatype(REBVAL *value, REBINT n) -/* -***********************************************************************/ -{ - *value = *BLK_SKIP(Lib_Context, n+1); +out_of_range: + fail (Error_Out_Of_Range(const_KNOWN(val))); } -/*********************************************************************** -** -*/ REBVAL *Get_Type(REBCNT index) -/* -** Returns the specified datatype value from the system context. -** The datatypes are all at the head of the context. -** -***********************************************************************/ +// +// Int64: C +// +REBI64 Int64(const REBVAL *val) { - ASSERT(index < SERIES_TAIL(Lib_Context), RP_BAD_OBJ_INDEX); - return FRM_VALUES(Lib_Context) + index + 1; -} - + if (IS_INTEGER(val)) + return VAL_INT64(val); + if (IS_DECIMAL(val) || IS_PERCENT(val)) + return cast(REBI64, VAL_DECIMAL(val)); + if (IS_MONEY(val)) + return deci_to_int(VAL_MONEY_AMOUNT(val)); -/*********************************************************************** -** -*/ REBVAL *Of_Type(REBVAL *value) -/* -** Returns the datatype value for the given value. -** The datatypes are all at the head of the context. -** -***********************************************************************/ -{ - return FRM_VALUES(Lib_Context) + VAL_TYPE(value) + 1; + fail (val); } -/*********************************************************************** -** -*/ REBINT Get_Type_Sym(REBCNT type) -/* -** Returns the datatype word for the given type number. -** -***********************************************************************/ +// +// Dec64: C +// +REBDEC Dec64(const REBVAL *val) { - return FRM_WORD_SYM(Lib_Context, type + 1); -} + if (IS_DECIMAL(val) || IS_PERCENT(val)) + return VAL_DECIMAL(val); + if (IS_INTEGER(val)) + return cast(REBDEC, VAL_INT64(val)); + if (IS_MONEY(val)) + return deci_to_decimal(VAL_MONEY_AMOUNT(val)); - -/*********************************************************************** -** -*/ REBVAL *Get_Type_Word(REBCNT type) -/* -** Returns the datatype word for the given type number. -** -***********************************************************************/ -{ - return FRM_WORD(Lib_Context, type + 1); + fail (val); } -/*********************************************************************** -** -*/ REBYTE *Get_Field_Name(REBSER *obj, REBCNT index) -/* -** Get the name of a field of an object. -** -***********************************************************************/ +// +// Int64s: C +// +// Get integer as positive, negative 64 bit value. +// Sign field can be +// 0: >= 0 +// 1: > 0 +// -1: < 0 +// +REBI64 Int64s(const REBVAL *val, REBINT sign) { - ASSERT1(index < SERIES_TAIL(obj), RP_BAD_OBJ_INDEX); - return Get_Sym_Name(FRM_WORD_SYM(obj, index)); -} - + REBI64 n; -/*********************************************************************** -** -*/ REBVAL *Get_Field(REBSER *obj, REBCNT index) -/* -** Get an instance variable from an object series. -** -***********************************************************************/ -{ - ASSERT1(index < SERIES_TAIL(obj), RP_BAD_OBJ_INDEX); - return FRM_VALUES(obj) + index; -} + if (IS_DECIMAL(val)) { + if (VAL_DECIMAL(val) > MAX_I64 || VAL_DECIMAL(val) < MIN_I64) + fail (Error_Out_Of_Range(val)); + n = (REBI64)VAL_DECIMAL(val); + } else { + n = VAL_INT64(val); + } + // More efficient to use positive sense: + if ( + (sign == 0 && n >= 0) || + (sign > 0 && n > 0) || + (sign < 0 && n < 0) + ) + return n; -/*********************************************************************** -** -*/ REBVAL *Get_Object(REBVAL *objval, REBCNT index) -/* -** Get an instance variable from an object value. -** -***********************************************************************/ -{ - REBSER *obj = VAL_OBJ_FRAME(objval); - ASSERT1(IS_FRAME(BLK_HEAD(obj)), RP_BAD_OBJ_FRAME); - ASSERT1(index < SERIES_TAIL(obj), RP_BAD_OBJ_INDEX); - return FRM_VALUES(obj) + index; -} - - -/*********************************************************************** -** -*/ REBVAL *In_Object(REBSER *base, ...) -/* -** Get value from nested list of objects. List is null terminated. -** Returns object value, else returns 0 if not found. -** -***********************************************************************/ -{ - REBVAL *obj = 0; - REBCNT n; - va_list args; - - va_start(args, base); - while (NZ(n = va_arg(args, REBCNT))) { - if (n >= SERIES_TAIL(base)) return 0; - obj = OFV(base, n); - if (!IS_OBJECT(obj)) return 0; - base = VAL_OBJ_FRAME(obj); - } - va_end(args); - - return obj; -} - - -/*********************************************************************** -** -*/ REBVAL *Get_System(REBCNT i1, REBCNT i2) -/* -** Return a second level object field of the system object. -** -***********************************************************************/ -{ - REBVAL *obj; - - obj = VAL_OBJ_VALUES(ROOT_SYSTEM) + i1; - if (!i2) return obj; - ASSERT1(IS_OBJECT(obj), RP_BAD_OBJ_INDEX); - return Get_Field(VAL_OBJ_FRAME(obj), i2); + fail (Error_Out_Of_Range(val)); } -/*********************************************************************** -** -*/ REBINT Get_System_Int(REBCNT i1, REBCNT i2, REBINT default_int) -/* -** Get an integer from system object. -** -***********************************************************************/ +// +// Int8u: C +// +REBINT Int8u(const REBVAL *val) { - REBVAL *val = Get_System(i1, i2); - if (IS_INTEGER(val)) return VAL_INT32(val); - return default_int; -} - + if (VAL_INT64(val) > cast(i64, 255) || VAL_INT64(val) < cast(i64, 0)) + fail (Error_Out_Of_Range(val)); -/*********************************************************************** -** -*/ REBSER *Make_Std_Object(REBCNT index) -/* -***********************************************************************/ -{ - return CLONE_OBJECT(VAL_OBJ_FRAME(Get_System(SYS_STANDARD, index))); + return VAL_INT32(val); } -/*********************************************************************** -** -*/ void Set_Object_Values(REBSER *obj, REBVAL *vals) -/* -***********************************************************************/ +// +// Val_Init_Datatype: C +// +void Val_Init_Datatype(REBVAL *out, enum Reb_Kind kind) { - REBVAL *value; - - for (value = FRM_VALUES(obj) + 1; NOT_END(value); value++) { // skip self - if (IS_END(vals)) SET_NONE(value); - else *value = *vals++; - } + assert(kind > REB_0 && kind < REB_MAX); + Move_Value(out, CTX_VAR(Lib_Context, SYM_FROM_KIND(kind))); } -/*********************************************************************** -** -*/ void Set_Series(REBINT type, REBVAL *value, REBSER *series) -/* -** Common function. -** -***********************************************************************/ +// +// Get_Type: C +// +// Returns the specified datatype value from the system context. +// The datatypes are all at the head of the context. +// +REBVAL *Get_Type(enum Reb_Kind kind) { - VAL_SET(value, type); - VAL_SERIES(value) = series; - VAL_INDEX(value) = 0; - VAL_SERIES_SIDE(value) = 0; + assert(kind > REB_0 && kind < REB_MAX); + return CTX_VAR(Lib_Context, SYM_FROM_KIND(kind)); } -/*********************************************************************** -** -*/ void Set_Block(REBVAL *value, REBSER *series) -/* -** Common function. -** -***********************************************************************/ +// +// Type_Of: C +// +// Returns the datatype value for the given value. +// The datatypes are all at the head of the context. +// +REBVAL *Type_Of(const RELVAL *value) { - VAL_SET(value, REB_BLOCK); - VAL_SERIES(value) = series; - VAL_INDEX(value) = 0; - VAL_SERIES_SIDE(value) = 0; -} - - -/*********************************************************************** -** -*/ void Set_Block_Index(REBVAL *value, REBSER *series, REBCNT index) -/* -** Common function. -** -***********************************************************************/ -{ - VAL_SET(value, REB_BLOCK); - VAL_SERIES(value) = series; - VAL_INDEX(value) = index; - VAL_SERIES_SIDE(value) = 0; -} - - -/*********************************************************************** -** -*/ void Set_String(REBVAL *value, REBSER *series) -/* -** Common function. -** -***********************************************************************/ -{ - VAL_SET(value, REB_STRING); - VAL_SERIES(value) = series; - VAL_INDEX(value) = 0; - VAL_SERIES_SIDE(value) = 0; -} - - -/*********************************************************************** -** -*/ void Set_Binary(REBVAL *value, REBSER *series) -/* -** Common function. -** -***********************************************************************/ -{ - VAL_SET(value, REB_BINARY); - VAL_SERIES(value) = series; - VAL_INDEX(value) = 0; - VAL_SERIES_SIDE(value) = 0; -} - - -/*********************************************************************** -** -*/ void Set_Tuple(REBVAL *value, REBYTE *bytes, REBCNT len) -/* -***********************************************************************/ -{ - REBYTE *bp; - - VAL_SET(value, REB_TUPLE); - VAL_TUPLE_LEN(value) = (REBYTE)len; - for (bp = VAL_TUPLE(value); len > 0; len--) - *bp++ = *bytes++; + return CTX_VAR(Lib_Context, SYM_FROM_KIND(VAL_TYPE(value))); } -/*********************************************************************** -** -*/ void Set_Object(REBVAL *value, REBSER *series) -/* -***********************************************************************/ -{ - VAL_SET(value, REB_OBJECT); - VAL_OBJ_FRAME(value) = series; -} - - -/*********************************************************************** -** -*/ REBCNT Val_Series_Len(REBVAL *value) -/* -** Get length of series, but avoid negative values. -** -***********************************************************************/ -{ - if (VAL_INDEX(value) >= VAL_TAIL(value)) return 0; - return VAL_TAIL(value) - VAL_INDEX(value); -} - - -/*********************************************************************** -** -*/ REBCNT Val_Byte_Len(REBVAL *value) -/* -** Get length of series in bytes. -** -***********************************************************************/ -{ - if (VAL_INDEX(value) >= VAL_TAIL(value)) return 0; - return (VAL_TAIL(value) - VAL_INDEX(value)) * SERIES_WIDE(VAL_SERIES(value)); -} - - -/*********************************************************************** -** -*/ REBFLG Get_Logic_Arg(REBVAL *arg) -/* -***********************************************************************/ -{ - if (IS_NONE(arg)) return 0; - if (IS_INTEGER(arg)) return (VAL_INT64(arg) != 0); - if (IS_LOGIC(arg)) return (VAL_LOGIC(arg) != 0); - if (IS_DECIMAL(arg) || IS_PERCENT(arg)) return (VAL_DECIMAL(arg) != 0.0); - Trap_Arg(arg); - DEAD_END; -} - - - -#ifdef ndef -/*********************************************************************** -** -*/ REBCNT Get_Part_Length(REBVAL *bval, REBVAL *eval) -/* -** Determine the length of a /PART value. -** If /PART value is an integer just use it. -** If it is a series and it is the same series as the first, -** use the difference between the two indices. -** -** If the length ends up negative, back up the index as much -** as possible. If backed up over the head, adjust the length. -** -** Note: This one does not handle list datatypes. -** -***********************************************************************/ -{ - REBINT len; - REBCNT tail; - - if (IS_INTEGER(eval) || IS_DECIMAL(eval)) { - len = Int32(eval); - if (IS_SCALAR(bval) && VAL_TYPE(bval) != REB_PORT) - Trap1(RE_INVALID_PART, bval); - } - else if ( - ( - // IF normal series and self referencing: - VAL_TYPE(eval) >= REB_STRING && - VAL_TYPE(eval) <= REB_BLOCK && - VAL_TYPE(bval) == VAL_TYPE(eval) && - VAL_SERIES(bval) == VAL_SERIES(eval) - ) || ( - // OR IF it is a port: - IS_PORT(bval) && IS_PORT(eval) && - VAL_OBJ_FRAME(bval) == VAL_OBJ_FRAME(eval) - ) - ) - len = (REBINT)VAL_INDEX(eval) - (REBINT)VAL_INDEX(bval); - else - Trap1(RE_INVALID_PART, eval); -/* !!!! - if (IS_PORT(bval)) { - PORT_STATE_OBJ *port; - - port = VAL_PORT(&VAL_PSP(bval)->state); - if (PORT_FLAG(port) & PF_DIRECT) - tail = 0x7fffffff; - else - tail = PORT_TAIL(VAL_PORT(&VAL_PSP(bval)->state)); - } - else -*/ tail = VAL_TAIL(bval); - - if (len < 0) { - len = -len; - if (len > (REBINT)VAL_INDEX(bval)) - len = (REBINT)VAL_INDEX(bval); - VAL_INDEX(bval) -= (REBCNT)len; - } - else if (!IS_INTEGER(eval) && (len + VAL_INDEX(bval)) > tail) - len = (REBINT)(tail - VAL_INDEX(bval)); - - return (REBCNT)len; -} +// +// In_Object: C +// +// Get value from nested list of objects. List is null terminated. +// Returns object value, else returns 0 if not found. +// +REBVAL *In_Object(REBCTX *base, ...) +{ + REBVAL *context = NULL; + REBCNT n; + va_list va; + + va_start(va, base); + while ((n = va_arg(va, REBCNT))) { + if (n > CTX_LEN(base)) { + va_end(va); + return NULL; + } + context = CTX_VAR(base, n); + if (!ANY_CONTEXT(context)) { + va_end(va); + return NULL; + } + base = VAL_CONTEXT(context); + } + va_end(va); + + return context; +} + + +// +// Get_System: C +// +// Return a second level object field of the system object. +// +REBVAL *Get_System(REBCNT i1, REBCNT i2) +{ + REBVAL *obj; + + obj = CTX_VAR(VAL_CONTEXT(ROOT_SYSTEM), i1); + if (i2 == 0) return obj; + assert(IS_OBJECT(obj)); + return CTX_VAR(VAL_CONTEXT(obj), i2); +} + + +// +// Get_System_Int: C +// +// Get an integer from system object. +// +REBINT Get_System_Int(REBCNT i1, REBCNT i2, REBINT default_int) +{ + REBVAL *val = Get_System(i1, i2); + if (IS_INTEGER(val)) return VAL_INT32(val); + return default_int; +} + + +// +// Init_Any_Series_At_Core: C +// +// Common function. +// +void Init_Any_Series_At_Core( + RELVAL *out, // allows RELVAL slot as input, but will be filled w/REBVAL + enum Reb_Kind type, + REBSER *series, + REBCNT index, + REBSPC *specifier +) { + ENSURE_SERIES_MANAGED(series); + + if (type != REB_IMAGE && type != REB_VECTOR) { + // Code in various places seemed to have different opinions of + // whether a BINARY needed to be zero terminated. It doesn't + // make a lot of sense to zero terminate a binary unless it + // simplifies the code assumptions somehow--it's in the class + // "ANY_BINSTR()" so that suggests perhaps it has a bit more + // obligation to conform. Also, the original Make_Binary comment + // from the open source release read: + // + // Make a binary string series. For byte, C, and UTF8 strings. + // Add 1 extra for terminator. + // + // Until that is consciously overturned, check the REB_BINARY too + + ASSERT_SERIES_TERM(series); // doesn't apply to image/vector + } + + VAL_RESET_HEADER(out, type); + out->payload.any_series.series = series; + VAL_INDEX(out) = index; + if (specifier == SPECIFIED) + INIT_SPECIFIC(out, SPECIFIED); + else + INIT_SPECIFIC(out, CTX(specifier)); + +#if !defined(NDEBUG) + if (GET_SER_FLAG(series, SERIES_FLAG_ARRAY) && specifier == SPECIFIED) { + // + // If a SPECIFIED is used for an array, then that top level of the + // array cannot have any relative values in it. Catch it here vs. + // waiting until a later assertion. + // + ASSERT_NO_RELATIVE(ARR(series), FALSE); + } + else if (ANY_STRING(out)) + assert(SER_WIDE(series) == 1 || SER_WIDE(series) == 2); #endif - - -/*********************************************************************** -** -*/ REBINT Partial1(REBVAL *sval, REBVAL *lval) -/* -** Process the /part (or /skip) and other length modifying -** arguments. -** -***********************************************************************/ -{ - REBI64 len; - REBINT maxlen; - REBINT is_ser = ANY_SERIES(sval); - - // If lval = NONE, use the current len of the target value: - if (IS_NONE(lval)) { - if (!is_ser) return 1; - if (VAL_INDEX(sval) >= VAL_TAIL(sval)) return 0; - return (VAL_TAIL(sval) - VAL_INDEX(sval)); - } - if (IS_INTEGER(lval) || IS_DECIMAL(lval)) len = Int32(lval); - else { - if (is_ser && VAL_TYPE(sval) == VAL_TYPE(lval) && VAL_SERIES(sval) == VAL_SERIES(lval)) - len = (REBINT)VAL_INDEX(lval) - (REBINT)VAL_INDEX(sval); - else - Trap1(RE_INVALID_PART, lval); - - } - - if (is_ser) { - // Restrict length to the size available: - if (len >= 0) { - maxlen = (REBINT)VAL_LEN(sval); - if (len > maxlen) len = maxlen; - } else { - len = -len; - if (len > (REBINT)VAL_INDEX(sval)) len = (REBINT)VAL_INDEX(sval); - VAL_INDEX(sval) -= (REBCNT)len; - } - } - - return (REBINT)len; } -/*********************************************************************** -** -*/ REBINT Partial(REBVAL *aval, REBVAL *bval, REBVAL *lval, REBFLG flag) -/* -** Args: -** aval: target value -** bval: argument to modify target (optional) -** lval: length value (or none) -** -** Determine the length of a /PART value. It can be: -** 1. integer or decimal -** 2. relative to A value (bval is null) -** 3. relative to B value -** -** Flag: indicates special treatment for CHANGE. As in: -** CHANGE/part "abcde" "xy" 3 => "xyde" -** -** NOTE: Can modify the value's index! -** The result can be negative. ??? -** -***********************************************************************/ -{ - REBVAL *val; - REBINT len; - REBINT maxlen; - - // If lval = NONE, use the current len of the target value: - if (IS_NONE(lval)) { - val = (bval && ANY_SERIES(bval)) ? bval : aval; - if (VAL_INDEX(val) >= VAL_TAIL(val)) return 0; - return (VAL_TAIL(val) - VAL_INDEX(val)); - } - - if (IS_INTEGER(lval)) { - len = Int32(lval); - val = flag ? aval : bval; - } - - else if (IS_DECIMAL(lval)) { - len = Int32(lval); - val = bval; - } - - else { - // So, lval must be relative to aval or bval series: - if (VAL_TYPE(aval) == VAL_TYPE(lval) && VAL_SERIES(aval) == VAL_SERIES(lval)) - val = aval; - else if (bval && VAL_TYPE(bval) == VAL_TYPE(lval) && VAL_SERIES(bval) == VAL_SERIES(lval)) - val = bval; - else - Trap1(RE_INVALID_PART, lval); - - len = (REBINT)VAL_INDEX(lval) - (REBINT)VAL_INDEX(val); - } - - if (!val) val = aval; - - // Restrict length to the size available: - if (len >= 0) { - maxlen = (REBINT)VAL_LEN(val); - if (len > maxlen) len = maxlen; - } else { - len = -len; - if (len > (REBINT)VAL_INDEX(val)) len = (REBINT)VAL_INDEX(val); - VAL_INDEX(val) -= (REBCNT)len; -// if ((-len) > (REBINT)VAL_INDEX(val)) len = -(REBINT)VAL_INDEX(val); - } - - return len; -} - - -#ifdef ndef -/*********************************************************************** -** -*/ void Define_File_Global(REBYTE *name, REBYTE *content) -/* -** Util function used in startup. -** -***********************************************************************/ -{ - REBCNT sym = Make_Word(name, 0); - REBSER *str = Make_CStr(content); - REBVAL *value; - - value = Append_Frame(Main_Context, 0, sym); - SET_STR_TYPE(REB_FILE, value, str); -} - +// +// Set_Tuple: C +// +void Set_Tuple(REBVAL *value, REBYTE *bytes, REBCNT len) +{ + REBYTE *bp; + + VAL_RESET_HEADER(value, REB_TUPLE); + VAL_TUPLE_LEN(value) = (REBYTE)len; + for (bp = VAL_TUPLE(value); len > 0; len--) + *bp++ = *bytes++; +} + + +// +// Init_Any_Context_Core: C +// +// Common routine for initializing OBJECT, MODULE!, PORT!, and ERROR! +// +// A fully constructed context can reconstitute the ANY-CONTEXT! REBVAL that +// is its canon form from a single pointer...the REBVAL sitting in the 0 slot +// of the context's varlist. +// +void Init_Any_Context_Core( + RELVAL *out, // allows RELVAL slot as input, but will be filled w/REBVAL + enum Reb_Kind kind, + REBCTX *c +) { +#if defined(NDEBUG) + UNUSED(kind); +#else + // + // In a debug build we check to make sure the type of the embedded value + // matches the type of what is intended (so someone who thinks they are + // initializing a REB_OBJECT from a CONTEXT does not accidentally get a + // REB_ERROR, for instance.) It's a point for several other integrity + // checks as well. + // + REBVAL *archetype = CTX_VALUE(c); + assert(VAL_CONTEXT(archetype) == c); + + assert(CTX_TYPE(c) == kind); + if (CTX_KEYLIST(c) == NULL) + panic (c); + + assert(GET_SER_FLAG(CTX_VARLIST(c), ARRAY_FLAG_VARLIST)); + + assert(NOT_SER_FLAG(CTX_VARLIST(c), SERIES_FLAG_FILE_LINE)); + assert(NOT_SER_FLAG(CTX_KEYLIST(c), SERIES_FLAG_FILE_LINE)); + + if (IS_FRAME(CTX_VALUE(c))) + assert(IS_FUNCTION(CTX_FRAME_FUNC_VALUE(c))); + + // !!! Currently only a context can serve as the "meta" information, + // though the interface may expand. + // + assert(CTX_META(c) == NULL || ANY_CONTEXT(CTX_VALUE(CTX_META(c)))); #endif -/*********************************************************************** -** -*/ int Clip_Int(int val, int mini, int maxi) -/* -***********************************************************************/ -{ - if (val < mini) val = mini; - else if (val > maxi) val = maxi; - return val; -} - -/*********************************************************************** -** -*/ void memswapl(void *m1, void *m2, size_t len) -/* -** For long integer memory units, not chars. It is assumed that -** the len is an exact modulo of long. -** -***********************************************************************/ -{ - long t, *a, *b; - - a = m1; - b = m2; - len /= sizeof(long); - while (len--) { - t = *b; - *b++ = *a; - *a++ = t; - } -} - - -/*********************************************************************** -** -*/ i64 Add_Max(int type, i64 n, i64 m, i64 maxi) -/* -***********************************************************************/ -{ - i64 r = n + m; - if (r < -maxi || r > maxi) { - if (type) Trap1(RE_TYPE_LIMIT, Get_Type(type)); - r = r > 0 ? maxi : -maxi; - } - return r; -} - - -/*********************************************************************** -** -*/ int Mul_Max(int type, i64 n, i64 m, i64 maxi) -/* -***********************************************************************/ -{ - i64 r = n * m; - if (r < -maxi || r > maxi) Trap1(RE_TYPE_LIMIT, Get_Type(type)); - return (int)r; -} - - -/*********************************************************************** -** -*/ REBVAL *Make_OS_Error() -/* -***********************************************************************/ -{ - REBCHR str[100]; - - OS_FORM_ERROR(0, str, 100); - Set_String(DS_RETURN, Copy_OS_Str(str, LEN_STR(str))); - return DS_RETURN; + // Some contexts (stack frames in particular) start out unmanaged, and + // then check to see if an operation like Init_Any_Context set them to + // managed. If not, they will free the context. This avoids the need + // for the garbage collector to have to deal with the series if there's + // no reason too. + // + // Here is a case of where we mark the context as having an extant usage, + // so that at minimum this value must become unreachable from the root GC + // set before they are GC'd. For another case, see INIT_WORD_CONTEXT(), + // where an ANY-WORD! can mark a context as in use. + // + ENSURE_ARRAY_MANAGED(CTX_VARLIST(c)); + + // Keylists are different, because they may-or-may-not-be-reused by some + // operations. There needs to be a uniform policy on their management, + // or certain routines would return "sometimes managed, sometimes not" + // keylist series...a bad invariant. + // + ASSERT_ARRAY_MANAGED(CTX_KEYLIST(c)); + + Move_Value(out, CTX_VALUE(c)); + + // Currently only FRAME! uses the ->binding field. Following the pattern + // of function, we assume the archetype form of a frame has no binding, + // and it's only REBVAL instances besides the canon that become bound. + // + assert(VAL_BINDING(out) == NULL); + + // Only FRAME!s are allowed to have phases. + // + assert( + out->payload.any_context.phase == NULL + || ( + CTX_TYPE(c) == REB_FRAME + && NOT(IS_POINTER_TRASH_DEBUG(out->payload.any_context.phase)) + ) + ); +} + + +// +// Partial1: C +// +// Process the /part (or /skip) and other length modifying arguments. +// +// Adjusts the value's index if necessary, and returns the length indicated. +// Hence if a negative limit is passed in, it will adjust value to the +// position that negative limit would seek to...and save the length of +// the span to get to the original index. +// +void Partial1(REBVAL *value, const REBVAL *limit, REBCNT *span) +{ + REBOOL is_series = ANY_SERIES(value); + + if (IS_VOID(limit)) { // use current length of the target value + if (!is_series) { + *span = 1; + } + else if (VAL_INDEX(value) >= VAL_LEN_HEAD(value)) { + *span = 0; + } + else { + *span = (VAL_LEN_HEAD(value) - VAL_INDEX(value)); + } + return; + } + + REBI64 len; + if (IS_INTEGER(limit) || IS_DECIMAL(limit)) + len = Int32(limit); + else { + if ( + !is_series + || VAL_TYPE(value) != VAL_TYPE(limit) + || VAL_SERIES(value) != VAL_SERIES(limit) + ){ + fail (Error_Invalid_Part_Raw(limit)); + } + + len = cast(REBINT, VAL_INDEX(limit)) - cast(REBINT, VAL_INDEX(value)); + + } + + if (is_series) { + // Restrict length to the size available: + if (len >= 0) { + REBCNT maxlen = VAL_LEN_AT(value); + if (len > cast(REBINT, maxlen)) + len = maxlen; + } + else { + len = -len; + if (len > cast(REBINT, VAL_INDEX(value))) + len = VAL_INDEX(value); + assert(len >= 0); + VAL_INDEX(value) -= cast(REBCNT, len); + } + } + + assert(len >= 0); + *span = cast(REBCNT, len); +} + + +// +// Partial: C +// +// Args: +// aval: target value +// bval: argument to modify target (optional) +// lval: length value (or blank) +// +// Determine the length of a /PART value. It can be: +// 1. integer or decimal +// 2. relative to A value (bval is null) +// 3. relative to B value +// +// NOTE: Can modify the value's index! +// The result can be negative. ??? +// +REBINT Partial(REBVAL *aval, REBVAL *bval, REBVAL *lval) +{ + REBVAL *val; + REBINT len; + REBINT maxlen; + + // If lval is unset, use the current len of the target value: + if (IS_VOID(lval)) { + val = (bval && ANY_SERIES(bval)) ? bval : aval; + if (VAL_INDEX(val) >= VAL_LEN_HEAD(val)) return 0; + return (VAL_LEN_HEAD(val) - VAL_INDEX(val)); + } + + if (IS_INTEGER(lval) || IS_DECIMAL(lval)) { + len = Int32(lval); + val = bval; + } + else { + // So, lval must be relative to aval or bval series: + if ( + VAL_TYPE(aval) == VAL_TYPE(lval) + && VAL_SERIES(aval) == VAL_SERIES(lval) + ) { + val = aval; + } + else if ( + bval + && VAL_TYPE(bval) == VAL_TYPE(lval) + && VAL_SERIES(bval) == VAL_SERIES(lval) + ) { + val = bval; + } + else + fail (Error_Invalid_Part_Raw(lval)); + + len = cast(REBINT, VAL_INDEX(lval)) - cast(REBINT, VAL_INDEX(val)); + } + + if (!val) val = aval; + + // Restrict length to the size available + // + if (len >= 0) { + maxlen = (REBINT)VAL_LEN_AT(val); + if (len > maxlen) len = maxlen; + } + else { + len = -len; + if (len > cast(REBINT, VAL_INDEX(val))) + len = cast(REBINT, VAL_INDEX(val)); + VAL_INDEX(val) -= (REBCNT)len; + } + + return len; +} + + +// +// Clip_Int: C +// +int Clip_Int(int val, int mini, int maxi) +{ + if (val < mini) val = mini; + else if (val > maxi) val = maxi; + return val; +} + + +// +// Add_Max: C +// +i64 Add_Max(enum Reb_Kind type, i64 n, i64 m, i64 maxi) +{ + i64 r = n + m; + if (r < -maxi || r > maxi) { + if (type != REB_0) fail (Error_Type_Limit_Raw(Get_Type(type))); + r = r > 0 ? maxi : -maxi; + } + return r; +} + + +// +// Mul_Max: C +// +int Mul_Max(enum Reb_Kind type, i64 n, i64 m, i64 maxi) +{ + i64 r = n * m; + if (r < -maxi || r > maxi) fail (Error_Type_Limit_Raw(Get_Type(type))); + return (int)r; } - -/*********************************************************************** -** -*/ REBSER *At_Head(REBVAL *value) -/* -** Return the series for a value, but if it has an index -** offset, return a copy of the series from that position. -** Useful for functions that do not accept index offsets. -** -***********************************************************************/ -{ - REBCNT len; - REBSER *ser; - REBSER *src = VAL_SERIES(value); - REBCNT wide; - - if (VAL_INDEX(value) == 0) return src; - - len = VAL_LEN(value); - wide = SERIES_WIDE(src); - ser = Make_Series(len, wide, FALSE); - - memcpy(ser->data, src->data + (VAL_INDEX(value) * wide), len * wide); - ser->tail = len; - - return ser; -} - - -/*********************************************************************** -** -*/ REBSER *Collect_Set_Words(REBVAL *val) -/* -** Scan a block, collecting all of its SET words as a block. -** -***********************************************************************/ -{ - REBCNT cnt = 0; - REBVAL *val2 = val; - REBSER *ser; - - for (; NOT_END(val); val++) if (IS_SET_WORD(val)) cnt++; - val = val2; - - ser = Make_Block(cnt); - val2 = BLK_HEAD(ser); - for (; NOT_END(val); val++) { - if (IS_SET_WORD(val)) Init_Word(val2++, VAL_WORD_SYM(val)); - } - SET_END(val2); - SERIES_TAIL(ser) = cnt; - - return ser; -} - - -/*********************************************************************** -** -*/ REBINT What_Reflector(REBVAL *word) -/* -***********************************************************************/ -{ - if (IS_WORD(word)) { - switch (VAL_WORD_SYM(word)) { - case SYM_SPEC: return OF_SPEC; - case SYM_BODY: return OF_BODY; - case SYM_WORDS: return OF_WORDS; - case SYM_VALUES: return OF_VALUES; - case SYM_TYPES: return OF_TYPES; - case SYM_TITLE: return OF_TITLE; - } - } - return 0; -} diff --git a/src/core/l-scan.c b/src/core/l-scan.c old mode 100644 new mode 100755 index 46351735a6..6a61bb3ece --- a/src/core/l-scan.c +++ b/src/core/l-scan.c @@ -1,1702 +1,2449 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: l-scan.c -** Summary: lexical analyzer for source to binary translation -** Section: lexical -** Author: Carl Sassenrath -** Notes: -** WARNING WARNING WARNING -** This is highly tuned code that should only be modified by experts -** who fully understand its design. It is very easy to create odd -** side effects so please be careful and extensively test all changes! -** -***********************************************************************/ +// +// File: %l-scan.c +// Summary: "lexical analyzer for source to binary translation" +// Section: lexical +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Rebol's lexical scanner was implemented as hand-coded C, as opposed to +// using a more formal grammar and generator. This makes the behavior hard +// to formalize, though some attempts have been made to do so: +// +// http://rgchris.github.io/Rebol-Notation/ +// +// Because Red is implemented using Rebol, it has a more abstract definition +// in the sense that it uses PARSE rules: +// +// https://github.com/red/red/blob/master/lexer.r +// +// It would likely be desirable to bring more formalism and generativeness +// to Rebol's scanner; though the current method of implementation was +// ostensibly chosen for performance. +// #include "sys-core.h" -#include "sys-scan.h" -// In UTF8 C0, C1, F5, and FF are invalid. -#ifdef USE_UNICODE -#define LEX_UTFE LEX_DEFAULT -#else -#define LEX_UTFE LEX_WORD -#endif -/*********************************************************************** -** -*/ const REBYTE Lex_Map[256] = -/* -** Maps each character to its lexical attributes, using -** a frequency optimized encoding. -** -** UTF8: The values C0, C1, F5 to FF never appear. -** -***********************************************************************/ +// +// Maps each character to its lexical attributes, using +// a frequency optimized encoding. +// +// UTF8: The values C0, C1, F5 to FF never appear. +// +const REBYTE Lex_Map[256] = { - /* 00 EOF */ LEX_DELIMIT|LEX_DELIMIT_END_FILE, - /* 01 */ LEX_DEFAULT, - /* 02 */ LEX_DEFAULT, - /* 03 */ LEX_DEFAULT, - /* 04 */ LEX_DEFAULT, - /* 05 */ LEX_DEFAULT, - /* 06 */ LEX_DEFAULT, - /* 07 */ LEX_DEFAULT, - /* 08 BS */ LEX_DEFAULT, - /* 09 TAB */ LEX_DEFAULT, - /* 0A LF */ LEX_DELIMIT|LEX_DELIMIT_LINEFEED, - /* 0B */ LEX_DEFAULT, - /* 0C PG */ LEX_DEFAULT, - /* 0D CR */ LEX_DELIMIT|LEX_DELIMIT_RETURN, - /* 0E */ LEX_DEFAULT, - /* 0F */ LEX_DEFAULT, - - /* 10 */ LEX_DEFAULT, - /* 11 */ LEX_DEFAULT, - /* 12 */ LEX_DEFAULT, - /* 13 */ LEX_DEFAULT, - /* 14 */ LEX_DEFAULT, - /* 15 */ LEX_DEFAULT, - /* 16 */ LEX_DEFAULT, - /* 17 */ LEX_DEFAULT, - /* 18 */ LEX_DEFAULT, - /* 19 */ LEX_DEFAULT, - /* 1A */ LEX_DEFAULT, - /* 1B */ LEX_DEFAULT, - /* 1C */ LEX_DEFAULT, - /* 1D */ LEX_DEFAULT, - /* 1E */ LEX_DEFAULT, - /* 1F */ LEX_DEFAULT, - - /* 20 */ LEX_DELIMIT|LEX_DELIMIT_SPACE, - /* 21 ! */ LEX_WORD, - /* 22 " */ LEX_DELIMIT|LEX_DELIMIT_QUOTE, - /* 23 # */ LEX_SPECIAL|LEX_SPECIAL_POUND, - /* 24 $ */ LEX_SPECIAL|LEX_SPECIAL_DOLLAR, - /* 25 % */ LEX_SPECIAL|LEX_SPECIAL_PERCENT, - /* 26 & */ LEX_WORD, - /* 27 ' */ LEX_SPECIAL|LEX_SPECIAL_TICK, - /* 28 ( */ LEX_DELIMIT|LEX_DELIMIT_LEFT_PAREN, - /* 29 ) */ LEX_DELIMIT|LEX_DELIMIT_RIGHT_PAREN, - /* 2A * */ LEX_WORD, - /* 2B + */ LEX_SPECIAL|LEX_SPECIAL_PLUS, - /* 2C , */ LEX_SPECIAL|LEX_SPECIAL_COMMA, - /* 2D - */ LEX_SPECIAL|LEX_SPECIAL_MINUS, - /* 2E . */ LEX_SPECIAL|LEX_SPECIAL_PERIOD, - /* 2F / */ LEX_DELIMIT|LEX_DELIMIT_SLASH, - - /* 30 0 */ LEX_NUMBER|0, - /* 31 1 */ LEX_NUMBER|1, - /* 32 2 */ LEX_NUMBER|2, - /* 33 3 */ LEX_NUMBER|3, - /* 34 4 */ LEX_NUMBER|4, - /* 35 5 */ LEX_NUMBER|5, - /* 36 6 */ LEX_NUMBER|6, - /* 37 7 */ LEX_NUMBER|7, - /* 38 8 */ LEX_NUMBER|8, - /* 39 9 */ LEX_NUMBER|9, - /* 3A : */ LEX_SPECIAL|LEX_SPECIAL_COLON, - /* 3B ; */ LEX_DELIMIT|LEX_DELIMIT_SEMICOLON, - /* 3C < */ LEX_SPECIAL|LEX_SPECIAL_LESSER, - /* 3D = */ LEX_WORD, - /* 3E > */ LEX_SPECIAL|LEX_SPECIAL_GREATER, - /* 3F ? */ LEX_WORD, - - /* 40 @ */ LEX_SPECIAL|LEX_SPECIAL_AT, - /* 41 A */ LEX_WORD|10, - /* 42 B */ LEX_WORD|11, - /* 43 C */ LEX_WORD|12, - /* 44 D */ LEX_WORD|13, - /* 45 E */ LEX_WORD|14, - /* 46 F */ LEX_WORD|15, - /* 47 G */ LEX_WORD, - /* 48 H */ LEX_WORD, - /* 49 I */ LEX_WORD, - /* 4A J */ LEX_WORD, - /* 4B K */ LEX_WORD, - /* 4C L */ LEX_WORD, - /* 4D M */ LEX_WORD, - /* 4E N */ LEX_WORD, - /* 4F O */ LEX_WORD, - - /* 50 P */ LEX_WORD, - /* 51 Q */ LEX_WORD, - /* 52 R */ LEX_WORD, - /* 53 S */ LEX_WORD, - /* 54 T */ LEX_WORD, - /* 55 U */ LEX_WORD, - /* 56 V */ LEX_WORD, - /* 57 W */ LEX_WORD, - /* 58 X */ LEX_WORD, - /* 59 Y */ LEX_WORD, - /* 5A Z */ LEX_WORD, - /* 5B [ */ LEX_DELIMIT|LEX_DELIMIT_LEFT_BRACKET, - /* 5C \ */ LEX_SPECIAL|LEX_SPECIAL_BACKSLASH, - /* 5D ] */ LEX_DELIMIT|LEX_DELIMIT_RIGHT_BRACKET, - /* 5E ^ */ LEX_WORD, - /* 5F _ */ LEX_WORD, - - /* 60 ` */ LEX_WORD, - /* 61 a */ LEX_WORD|10, - /* 62 b */ LEX_WORD|11, - /* 63 c */ LEX_WORD|12, - /* 64 d */ LEX_WORD|13, - /* 65 e */ LEX_WORD|14, - /* 66 f */ LEX_WORD|15, - /* 67 g */ LEX_WORD, - /* 68 h */ LEX_WORD, - /* 69 i */ LEX_WORD, - /* 6A j */ LEX_WORD, - /* 6B k */ LEX_WORD, - /* 6C l */ LEX_WORD, - /* 6D m */ LEX_WORD, - /* 6E n */ LEX_WORD, - /* 6F o */ LEX_WORD, - - /* 70 p */ LEX_WORD, - /* 71 q */ LEX_WORD, - /* 72 r */ LEX_WORD, - /* 73 s */ LEX_WORD, - /* 74 t */ LEX_WORD, - /* 75 u */ LEX_WORD, - /* 76 v */ LEX_WORD, - /* 77 w */ LEX_WORD, - /* 78 x */ LEX_WORD, - /* 79 y */ LEX_WORD, - /* 7A z */ LEX_WORD, - /* 7B { */ LEX_DELIMIT|LEX_DELIMIT_LEFT_BRACE, - /* 7C | */ LEX_WORD, - /* 7D } */ LEX_DELIMIT|LEX_DELIMIT_RIGHT_BRACE, - /* 7E ~ */ LEX_WORD, //LEX_SPECIAL|LEX_SPECIAL_TILDE, - /* 7F DEL */ LEX_DEFAULT, - - /* Odd Control Chars */ - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, /* 80 */ - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - - /* Alternate Chars */ -#ifdef USE_UNICODE - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, -#else - LEX_DEFAULT,LEX_WORD,LEX_WORD,LEX_WORD, /* A0 (a space) */ -#endif - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - - // C0, C1 - LEX_UTFE,LEX_UTFE,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_UTFE,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, - LEX_WORD,LEX_WORD,LEX_WORD,LEX_UTFE + /* 00 EOF */ LEX_DELIMIT|LEX_DELIMIT_END, + /* 01 */ LEX_DEFAULT, + /* 02 */ LEX_DEFAULT, + /* 03 */ LEX_DEFAULT, + /* 04 */ LEX_DEFAULT, + /* 05 */ LEX_DEFAULT, + /* 06 */ LEX_DEFAULT, + /* 07 */ LEX_DEFAULT, + /* 08 BS */ LEX_DEFAULT, + /* 09 TAB */ LEX_DEFAULT, + /* 0A LF */ LEX_DELIMIT|LEX_DELIMIT_LINEFEED, + /* 0B */ LEX_DEFAULT, + /* 0C PG */ LEX_DEFAULT, + /* 0D CR */ LEX_DELIMIT|LEX_DELIMIT_RETURN, + /* 0E */ LEX_DEFAULT, + /* 0F */ LEX_DEFAULT, + + /* 10 */ LEX_DEFAULT, + /* 11 */ LEX_DEFAULT, + /* 12 */ LEX_DEFAULT, + /* 13 */ LEX_DEFAULT, + /* 14 */ LEX_DEFAULT, + /* 15 */ LEX_DEFAULT, + /* 16 */ LEX_DEFAULT, + /* 17 */ LEX_DEFAULT, + /* 18 */ LEX_DEFAULT, + /* 19 */ LEX_DEFAULT, + /* 1A */ LEX_DEFAULT, + /* 1B */ LEX_DEFAULT, + /* 1C */ LEX_DEFAULT, + /* 1D */ LEX_DEFAULT, + /* 1E */ LEX_DEFAULT, + /* 1F */ LEX_DEFAULT, + + /* 20 */ LEX_DELIMIT|LEX_DELIMIT_SPACE, + /* 21 ! */ LEX_WORD, + /* 22 " */ LEX_DELIMIT|LEX_DELIMIT_DOUBLE_QUOTE, + /* 23 # */ LEX_SPECIAL|LEX_SPECIAL_POUND, + /* 24 $ */ LEX_SPECIAL|LEX_SPECIAL_DOLLAR, + /* 25 % */ LEX_SPECIAL|LEX_SPECIAL_PERCENT, + /* 26 & */ LEX_WORD, + /* 27 ' */ LEX_SPECIAL|LEX_SPECIAL_APOSTROPHE, + /* 28 ( */ LEX_DELIMIT|LEX_DELIMIT_LEFT_PAREN, + /* 29 ) */ LEX_DELIMIT|LEX_DELIMIT_RIGHT_PAREN, + /* 2A * */ LEX_WORD, + /* 2B + */ LEX_SPECIAL|LEX_SPECIAL_PLUS, + /* 2C , */ LEX_SPECIAL|LEX_SPECIAL_COMMA, + /* 2D - */ LEX_SPECIAL|LEX_SPECIAL_MINUS, + /* 2E . */ LEX_SPECIAL|LEX_SPECIAL_PERIOD, + /* 2F / */ LEX_DELIMIT|LEX_DELIMIT_SLASH, + + /* 30 0 */ LEX_NUMBER|0, + /* 31 1 */ LEX_NUMBER|1, + /* 32 2 */ LEX_NUMBER|2, + /* 33 3 */ LEX_NUMBER|3, + /* 34 4 */ LEX_NUMBER|4, + /* 35 5 */ LEX_NUMBER|5, + /* 36 6 */ LEX_NUMBER|6, + /* 37 7 */ LEX_NUMBER|7, + /* 38 8 */ LEX_NUMBER|8, + /* 39 9 */ LEX_NUMBER|9, + /* 3A : */ LEX_SPECIAL|LEX_SPECIAL_COLON, + /* 3B ; */ LEX_DELIMIT|LEX_DELIMIT_SEMICOLON, + /* 3C < */ LEX_SPECIAL|LEX_SPECIAL_LESSER, + /* 3D = */ LEX_WORD, + /* 3E > */ LEX_SPECIAL|LEX_SPECIAL_GREATER, + /* 3F ? */ LEX_WORD, + + /* 40 @ */ LEX_SPECIAL|LEX_SPECIAL_AT, + /* 41 A */ LEX_WORD|10, + /* 42 B */ LEX_WORD|11, + /* 43 C */ LEX_WORD|12, + /* 44 D */ LEX_WORD|13, + /* 45 E */ LEX_WORD|14, + /* 46 F */ LEX_WORD|15, + /* 47 G */ LEX_WORD, + /* 48 H */ LEX_WORD, + /* 49 I */ LEX_WORD, + /* 4A J */ LEX_WORD, + /* 4B K */ LEX_WORD, + /* 4C L */ LEX_WORD, + /* 4D M */ LEX_WORD, + /* 4E N */ LEX_WORD, + /* 4F O */ LEX_WORD, + + /* 50 P */ LEX_WORD, + /* 51 Q */ LEX_WORD, + /* 52 R */ LEX_WORD, + /* 53 S */ LEX_WORD, + /* 54 T */ LEX_WORD, + /* 55 U */ LEX_WORD, + /* 56 V */ LEX_WORD, + /* 57 W */ LEX_WORD, + /* 58 X */ LEX_WORD, + /* 59 Y */ LEX_WORD, + /* 5A Z */ LEX_WORD, + /* 5B [ */ LEX_DELIMIT|LEX_DELIMIT_LEFT_BRACKET, + /* 5C \ */ LEX_SPECIAL|LEX_SPECIAL_BACKSLASH, + /* 5D ] */ LEX_DELIMIT|LEX_DELIMIT_RIGHT_BRACKET, + /* 5E ^ */ LEX_WORD, + /* 5F _ */ LEX_SPECIAL|LEX_SPECIAL_BLANK, + + /* 60 ` */ LEX_WORD, + /* 61 a */ LEX_WORD|10, + /* 62 b */ LEX_WORD|11, + /* 63 c */ LEX_WORD|12, + /* 64 d */ LEX_WORD|13, + /* 65 e */ LEX_WORD|14, + /* 66 f */ LEX_WORD|15, + /* 67 g */ LEX_WORD, + /* 68 h */ LEX_WORD, + /* 69 i */ LEX_WORD, + /* 6A j */ LEX_WORD, + /* 6B k */ LEX_WORD, + /* 6C l */ LEX_WORD, + /* 6D m */ LEX_WORD, + /* 6E n */ LEX_WORD, + /* 6F o */ LEX_WORD, + + /* 70 p */ LEX_WORD, + /* 71 q */ LEX_WORD, + /* 72 r */ LEX_WORD, + /* 73 s */ LEX_WORD, + /* 74 t */ LEX_WORD, + /* 75 u */ LEX_WORD, + /* 76 v */ LEX_WORD, + /* 77 w */ LEX_WORD, + /* 78 x */ LEX_WORD, + /* 79 y */ LEX_WORD, + /* 7A z */ LEX_WORD, + /* 7B { */ LEX_DELIMIT|LEX_DELIMIT_LEFT_BRACE, + /* 7C | */ LEX_SPECIAL|LEX_SPECIAL_BAR, + /* 7D } */ LEX_DELIMIT|LEX_DELIMIT_RIGHT_BRACE, + /* 7E ~ */ LEX_WORD, //LEX_SPECIAL|LEX_SPECIAL_TILDE, + /* 7F DEL */ LEX_DEFAULT, + + /* Odd Control Chars */ + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, /* 80 */ + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + + /* Alternate Chars */ + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + + // C0, C1 + LEX_UTFE,LEX_UTFE,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_UTFE,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_WORD, + LEX_WORD,LEX_WORD,LEX_WORD,LEX_UTFE }; #ifdef LOWER_CASE_BYTE -/*********************************************************************** -** -*/ const REBYTE Upper_Case[256] = -/* -** Maps each character to its upper case value. Done this -** way for speed. Note the odd cases in last block. -** -***********************************************************************/ +// +// Maps each character to its upper case value. Done this +// way for speed. Note the odd cases in last block. +// +const REBYTE Upper_Case[256] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - - 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, - 96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, - 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,123,124,125,126,127, - - 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, - 144,145,146,147,148,149,150,151,152,153,138,155,156,141,142,159, /* some up/low cases mod 16 (not mod 32) */ - 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, - 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, - - 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, - 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, - 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, - 208,209,210,211,212,213,214,247,216,217,218,219,220,221,222,159 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,123,124,125,126,127, + + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + // some up/low cases mod 16 (not mod 32) + 144,145,146,147,148,149,150,151,152,153,138,155,156,141,142,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,247,216,217,218,219,220,221,222,159 }; -/*********************************************************************** -** -*/ const REBYTE Lower_Case[256] = -/* -** Maps each character to its lower case value. Done this -** way for speed. Note the odd cases in last block. -** -***********************************************************************/ +// +// Maps each character to its lower case value. Done this +// way for speed. Note the odd cases in last block. +// +const REBYTE Lower_Case[256] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - - 64, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, - 112,113,114,115,116,117,118,119,120,121,122, 91, 92, 93, 94, 95, - 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, - 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, - - 128,129,130,131,132,133,134,135,136,137,154,139,140,157,158,143, - 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,255, /* some up/low cases mod 16 (not mod 32) */ - 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, - 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, - - 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, - 240,241,242,243,244,245,246,215,248,249,250,251,252,253,254,223, - 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, - 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + + 64, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122, 91, 92, 93, 94, 95, + 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, + + 128,129,130,131,132,133,134,135,136,137,154,139,140,157,158,143, + // some up/low cases mod 16 (not mod 32) + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,255, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,215,248,249,250,251,252,253,254,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 }; #endif -/*********************************************************************** -** -*/ static REBINT Scan_Char(REBYTE **bp) -/* -** Scan a char, handling ^A, ^/, ^(null), ^(1234) -** -** Returns the numeric value for char, or -1 for errors. -** -** Advances the cp to just past the last position. -** -** test: to-integer load to-binary mold to-char 1234 -** -***********************************************************************/ +// +// Scan_UTF8_Char_Escapable: C +// +// Scan a char, handling ^A, ^/, ^(null), ^(1234) +// +// Returns the numeric value for char, or NULL for errors. +// 0 is a legal codepoint value which may be returned. +// +// Advances the cp to just past the last position. +// +// test: to-integer load to-binary mold to-char 1234 +// +static const REBYTE *Scan_UTF8_Char_Escapable(REBUNI *out, const REBYTE *bp) { - REBINT n; - REBYTE *cp; - REBYTE c; - REBYTE lex; + const REBYTE *cp; + REBYTE c; + REBYTE lex; - c = **bp; + c = *bp; - // Handle unicoded char: - if (c >= 0x80) { - n = Decode_UTF8_Char(bp, 0); // zero on error - (*bp)++; // skip char - return n; - } + // Handle unicoded char: + if (c >= 0x80) { + if (!(bp = Back_Scan_UTF8_Char(out, bp, NULL))) return NULL; + return bp + 1; // Back_Scan advances one less than the full encoding + } - (*bp)++; + bp++; - if (c != '^') return c; + if (c != '^') { + *out = c; + return bp; + } - // Must be ^ escaped char: - c = **bp; - (*bp)++; + // Must be ^ escaped char: + c = *bp; + bp++; switch (c) { - case 0: - n = 0; - break; + case 0: + *out = 0; + break; - case '/': - n = LF; - break; + case '/': + *out = LF; + break; - case '^': - n = c; - break; + case '^': + *out = c; + break; case '-': - n = TAB; - break; - - case '!': - n = '\036'; // record separator - break; - - case '(': // ^(tab) ^(1234) - // Check for hex integers ^(1234): - cp = *bp; // restart location - n = 0; - while ((lex = Lex_Map[*cp]) > LEX_WORD) { - c = lex & LEX_VALUE; - if (!c && lex < LEX_NUMBER) break; - n = (n << 4) + c; - cp++; - } - if ((cp - *bp) > 4) return -1; - if (*cp == ')') { - cp++; - *bp = cp; - return n; - } - - // Check for identifiers: - for (n = 0; n < ESC_MAX; n++) { - if (NZ(cp = Match_Bytes(*bp, (REBYTE*)(Esc_Names[n])))) { - if (cp && *cp == ')') { - *bp = cp + 1; - return Esc_Codes[n]; - } - } - } - return -1; + *out = '\t'; // tab character + break; + + case '!': + *out = '\036'; // record separator + break; + + case '(': // ^(tab) ^(1234) + // Check for hex integers ^(1234): + cp = bp; // restart location + *out = 0; + while ((lex = Lex_Map[*cp]) > LEX_WORD) { + c = lex & LEX_VALUE; + if (!c && lex < LEX_NUMBER) break; + *out = (*out << 4) + c; + cp++; + } + if ((cp - bp) > 4) return NULL; + if (*cp == ')') { + cp++; + return cp; + } + + // Check for identifiers: + for (c = 0; c < ESC_MAX; c++) { + if ((cp = Match_Bytes(bp, cb_cast(Esc_Names[c])))) { + if (cp && *cp == ')') { + bp = cp + 1; + *out = Esc_Codes[c]; + return bp; + } + } + } + return NULL; default: - n = UP_CASE(c); - if (n >= '@' && n <= '_') n -= '@'; - else if (n == '~') n = 0x7f; // special for DEL - else n = c; // includes: ^{ ^} ^" + *out = c; + + c = UP_CASE(c); + if (c >= '@' && c <= '_') *out = c - '@'; + else if (c == '~') *out = 0x7f; // special for DEL + else { + // keep original `c` value before UP_CASE (includes: ^{ ^} ^") + } } - return n; + return bp; } -/*********************************************************************** -** -*/ REBYTE *Scan_Quote(REBYTE *src, SCAN_STATE *scan_state) -/* -** Scan a quoted string, handling all the escape characters. -** -** The result will be put into the temporary MOLD_BUF unistring. -** -***********************************************************************/ -{ - REBINT nest = 0; - REBUNI term; - REBINT chr; - REBCNT lines = 0; - REBSER *buf = BUF_MOLD; +// +// Scan_Quote_Push_Mold: C +// +// Scan a quoted string, handling all the escape characters. +// +// The result will be put into the temporary unistring mold buffer. +// +static const REBYTE *Scan_Quote_Push_Mold( + REB_MOLD *mo, + const REBYTE *src, + SCAN_STATE *ss +) { + assert(ss != NULL); - RESET_TAIL(buf); + Push_Mold(mo); - term = (*src++ == '{') ? '}' : '"'; // pick termination + REBUNI term = (*src == '{') ? '}' : '"'; // pick termination + ++src; - while (*src != term || nest > 0) { - - chr = *src; + REBINT nest = 0; + REBCNT lines = 0; + while (*src != term || nest > 0) { + REBUNI chr = *src; switch (chr) { - case 0: - return 0; // Scan_state shows error location. - - case '^': - chr = Scan_Char(&src); - if (chr == -1) return 0; - src--; + case 0: + return NULL; // Scan_state shows error location. + + case '^': + if ((src = Scan_UTF8_Char_Escapable(&chr, src)) == NULL) + return NULL; + --src; break; - case '{': - if (term != '"') nest++; - break; + case '{': + if (term != '"') + ++nest; + break; - case '}': - if (term != '"' && nest > 0) nest--; - break; + case '}': + if (term != '"' && nest > 0) + --nest; + break; - case CR: - if (src[1] == LF) src++; - // fall thru + case CR: + if (src[1] == LF) src++; + // fall thru case LF: - if (term == '"') return 0; - lines++; - chr = LF; - break; + if (term == '"') + return NULL; + lines++; + chr = LF; + break; + + default: + if (chr >= 0x80) { + if ((src = Back_Scan_UTF8_Char(&chr, src, NULL)) == NULL) + return NULL; + } + } - default: - if (chr >= 0x80) { - chr = Decode_UTF8_Char(&src, 0); // zero on error - if (chr == 0) return 0; - } - } + src++; - src++; + if (SER_LEN(mo->series) + 1 >= SER_REST(mo->series)) // incl term + Extend_Series(mo->series, 1); - *UNI_SKIP(buf, buf->tail) = chr; + *UNI_TAIL(mo->series) = chr; - if (++(buf->tail) >= SERIES_REST(buf)) Extend_Series(buf, 1); + SET_SERIES_LEN(mo->series, SER_LEN(mo->series) + 1); } - src++; // Skip ending quote or brace. + src++; // Skip ending quote or brace. - if (scan_state) scan_state->line_count += lines; + ss->line += lines; - UNI_TERM(buf); + TERM_UNI(mo->series); - return src; + return src; } -/*********************************************************************** -** -*/ REBYTE *Scan_Item(REBYTE *src, REBYTE *end, REBUNI term, REBYTE *invalid) -/* -** Scan as UTF8 an item like a file or URL. -** -** Returns continuation point or zero for error. -** -** Put result into the MOLD_BUF as uni-chars. -** -***********************************************************************/ -{ - REBUNI c; - REBSER *buf; +// +// Scan_Item_Push_Mold: C +// +// Scan as UTF8 an item like a file or URL. +// +// Returns continuation point or zero for error. +// +// Put result into the temporary mold buffer as uni-chars. +// +const REBYTE *Scan_Item_Push_Mold( + REB_MOLD *mo, + const REBYTE *src, + const REBYTE *end, + REBUNI term, + const REBYTE *invalid +) { + REBUNI c; - buf = BUF_MOLD; - RESET_TAIL(buf); + Push_Mold(mo); - while (src < end && *src != term) { + while (src < end && *src != term) { - c = *src; + c = *src; - // End of stream? - if (c == 0) break; + // End of stream? + if (c == 0) break; - // If no term, then any white will terminate: - if (!term && IS_WHITE(c)) break; + // If no term, then any white will terminate: + if (!term && IS_WHITE(c)) break; - // Ctrl chars are invalid: - if (c < ' ') return 0; // invalid char + // Ctrl chars are invalid: + if (c < ' ') return 0; // invalid char - if (c == '\\') c = '/'; + if (c == '\\') c = '/'; - // Accept %xx encoded char: - else if (c == '%') { - if (!Scan_Hex2(src+1, &c, FALSE)) return 0; - src += 2; - } + // Accept %xx encoded char: + else if (c == '%') { + if (!Scan_Hex2(src+1, &c, FALSE)) return 0; + src += 2; + } - // Accept ^X encoded char: - else if (c == '^') { - if (src+1 == end) return 0; // nothing follows ^ - c = Scan_Char(&src); - if (!term && IS_WHITE(c)) break; - src--; - } + // Accept ^X encoded char: + else if (c == '^') { + if (src+1 == end) return 0; // nothing follows ^ + if (!(src = Scan_UTF8_Char_Escapable(&c, src))) return NULL; + if (!term && IS_WHITE(c)) break; + src--; + } + + // Accept UTF8 encoded char: + else if (c >= 0x80) { + if (!(src = Back_Scan_UTF8_Char(&c, src, 0))) return NULL; + } - // Accept UTF8 encoded char: - else if (c >= 0x80) { - c = Decode_UTF8_Char(&src, 0); // zero on error - if (c == 0) return 0; - } + // Is char as literal valid? (e.g. () [] etc.) + else if (invalid && strchr(cs_cast(invalid), c)) return 0; - // Is char as literal valid? (e.g. () [] etc.) - else if (invalid && strchr(invalid, c)) return 0; + src++; - src++; + *UNI_TAIL(mo->series) = c; // not affected by Extend_Series - *UNI_SKIP(buf, buf->tail) = c; // not affected by Extend_Series + SET_SERIES_LEN(mo->series, SER_LEN(mo->series) + 1); - if (++(buf->tail) >= SERIES_REST(buf)) Extend_Series(buf, 1); + if (SER_LEN(mo->series) >= SER_REST(mo->series)) + Extend_Series(mo->series, 1); } - if (*src && *src == term) src++; + if (*src && *src == term) src++; - UNI_TERM(buf); + TERM_UNI(mo->series); - return src; + return src; } -/*********************************************************************** -** -*/ static REBYTE *Skip_Tag(REBYTE *cp) -/* -** Skip the entire contents of a tag, including quoted strings. -** The argument points to the opening '<'. Zero is returned on -** errors. -** -***********************************************************************/ +// +// Skip_Tag: C +// +// Skip the entire contents of a tag, including quoted strings. +// The argument points to the opening '<'. Zero is returned on +// errors. +// +static const REBYTE *Skip_Tag(const REBYTE *cp) { - if (*cp == '<') cp++; - while (*cp && *cp != '>') { - if (*cp == '"') { - cp++; - while (*cp && *cp != '"') cp++; - if (!*cp) return 0; - } - cp++; - } - if (*cp) return cp+1; + if (*cp == '<') cp++; + while (*cp && *cp != '>') { + if (*cp == '"') { + cp++; + while (*cp && *cp != '"') cp++; + if (!*cp) return 0; + } + cp++; + } + if (*cp) return cp+1; return 0; } -/*********************************************************************** -** -*/ static void Scan_Error(REBCNT errnum, SCAN_STATE *ss, REBCNT tkn, REBYTE *arg, REBCNT size, REBVAL *relax) -/* -** Scanner error handler -** -***********************************************************************/ -{ - ERROR_OBJ *error; - REBSER *errs; - REBYTE *name; - REBYTE *cp; - REBYTE *bp; - REBSER *ser; - REBCNT len = 0; - - ss->errors++; - - if (PG_Boot_Strs) - name = BOOT_STR(RS_SCAN,tkn); - else - name = (REBYTE*)"boot"; - - cp = ss->head_line; - while (IS_LEX_SPACE(*cp)) cp++; // skip indentation - bp = cp; - while (NOT_NEWLINE(*cp)) cp++, len++; - - //DISABLE_GC; - errs = Make_Error(errnum, 0, 0, 0); - error = (ERROR_OBJ *)FRM_VALUES(errs); - ser = Make_Binary(len + 16); - Append_Bytes(ser, "(line "); - Append_Int(ser, ss->line_count); - Append_Bytes(ser, ") "); - Append_Series(ser, (REBYTE*)bp, len); - Set_String(&error->nearest, ser); - Set_String(&error->arg1, Copy_Bytes(name, -1)); - Set_String(&error->arg2, Copy_Bytes(arg, size)); - - if (relax) { - SET_ERROR(relax, errnum, errs); - //ENABLE_GC; - return; - } - - Throw_Error(errs); // ENABLE_GC implied +// +// Update_Error_Near_For_Line: C +// +// The NEAR information in an error is typically expressed in terms of loaded +// Rebol code. Scanner errors have historically used the NEAR not to tell you +// where the LOAD that is failing is in Rebol, but to form a string of the +// "best place" to report the textual error. +// +// While this is probably a bad overloading of NEAR, it is being made more +// clear that this is what's happening for the moment. +// +static void Update_Error_Near_For_Line( + REBCTX *error, + REBCNT line, + const REBYTE *line_head +){ + // Skip indentation (don't include in the NEAR) + // + const REBYTE *cp = line_head; + while (IS_LEX_SPACE(*cp)) + ++cp; + + // Find end of line to capture in error message + // + REBCNT len = 0; + const REBYTE *bp = cp; + while (!ANY_CR_LF_END(*cp)) { + cp++; + len++; + } + + // Put the line count and the line's text into a string. + // + // !!! This should likely be separated into an integer and a string, so + // that those processing the error don't have to parse it back out. + // + REBSER *ser = Make_Binary(len + 16); + Append_Unencoded(ser, "(line "); + Append_Int(ser, line); + Append_Unencoded(ser, ") "); + Append_Series(ser, bp, len); + + ERROR_VARS *vars = ERR_VARS(error); + Init_String(&vars->nearest, ser); +} + + +// +// Error_Syntax: C +// +// Catch-all scanner error handler. Reports the name of the token that gives +// the complaint, and gives the substring of the token's text. Populates +// the NEAR field of the error with the "current" line number and line text, +// e.g. where the end point of the token is seen. +// +static REBCTX *Error_Syntax(SCAN_STATE *ss) { + DECLARE_LOCAL (token_name); + Init_String(token_name, Copy_Bytes(cb_cast(Token_Names[ss->token]), -1)); + + // !!! Note: This uses Copy_Bytes, which assumes Latin1 safe characters. + // But this could be UTF8. + // + DECLARE_LOCAL (token_text); + Init_String( + token_text, + Copy_Bytes(ss->begin, cast(REBCNT, ss->end - ss->begin)) + ); + + REBCTX *error = Error(RE_SCAN_INVALID, token_name, token_text, END); + Update_Error_Near_For_Line(error, ss->line, ss->line_head); + return error; +} + + +// +// Error_Missing: C +// +// For instance, `load "( abc"`. +// +// Note: This error is useful for things like multi-line input, because it +// indicates a state which could be reconciled by adding more text. A +// better form of this error would walk the scan state stack and be able to +// report all the unclosed terms. +// +static REBCTX *Error_Missing(SCAN_STATE *ss, char wanted) { + REBYTE tmp_buf[2]; + tmp_buf[0] = wanted; + tmp_buf[1] = 0; + + DECLARE_LOCAL (expected); + Init_String(expected, Copy_Bytes(tmp_buf, 1)); + + REBCTX *error = Error(RE_SCAN_MISSING, expected, END); + Update_Error_Near_For_Line(error, ss->start_line, ss->start_line_head); + return error; +} + + +// +// Error_Extra: C +// +// For instance, `load "abc ]"` +// +static REBCTX *Error_Extra(SCAN_STATE *ss, char seen) { + REBYTE tmp_buf[2]; // Temporary error string + tmp_buf[0] = seen; + tmp_buf[1] = 0; + + DECLARE_LOCAL (unexpected); + Init_String(unexpected, Copy_Bytes(tmp_buf, 1)); + + REBCTX *error = Error(RE_SCAN_EXTRA, unexpected, END); + Update_Error_Near_For_Line(error, ss->line, ss->line_head); + return error; +} + + +// +// Error_Mismatch: C +// +// For instance, `load "( abc ]"` +// +// Note: This answer would be more useful for syntax highlighting or other +// applications if it would point out the locations of both points. R3-Alpha +// only pointed out the location of the start token. +// +static REBCTX *Error_Mismatch(SCAN_STATE *ss, char wanted, char seen) { + REBYTE tmp_buf[2]; // Temporary error string + tmp_buf[0] = wanted; + tmp_buf[1] = 0; + + DECLARE_LOCAL (expected); + Init_String(expected, Copy_Bytes(tmp_buf, 1)); + + tmp_buf[0] = seen; + + DECLARE_LOCAL (unexpected); + Init_String(unexpected, Copy_Bytes(tmp_buf, 1)); + + REBCTX *error = Error(RE_SCAN_MISMATCH, expected, unexpected, END); + Update_Error_Near_For_Line(error, ss->start_line, ss->start_line_head); + return error; } -/*********************************************************************** -** -*/ static REBCNT Prescan(SCAN_STATE *scan_state) -/* -** The general idea of this function is to break up a string -** into tokens, with sensitivity to common token frequencies. -** That is, find DELIMITERS, simple WORDS, and simple NUMBERS -** rapidly. For everything else, find the substring and note -** the special characters that it contains. All scans start -** by skipping whitespace and are concluded by a delimiter. -** A delimiter is returned only when nothing was found before -** it (i.e. not part of other lexical tokens). -** -** Returns a word with bit flags indicating special chars -** that were found during the scan (other than the first -** char, which is not part of the flags). -** Both the beginning and ending positions are updated. -** -***********************************************************************/ +// +// Prescan_Token: C +// +// This function updates `ss->begin` to skip past leading +// whitespace. If the first character it finds after that is a +// LEX_DELIMITER (`"`, `[`, `)`, `{`, etc. or a space/newline) +// then it will advance the end position to just past that one +// character. For all other leading characters, it will advance +// the end pointer up to the first delimiter class byte (but not +// include it.) +// +// If the first character is not a delimiter, then this routine +// also gathers a quick "fingerprint" of the special characters +// that appeared after it, but before a delimiter was found. +// This comes from unioning LEX_SPECIAL_XXX flags of the bytes +// that are seen (plus LEX_SPECIAL_WORD if any legal word bytes +// were found in that range.) +// +// So if the input were "$#foobar[@" this would come back with +// the flags LEX_SPECIAL_POUND and LEX_SPECIAL_WORD set. Since +// it is the first character, the `$` would not be counted to +// add LEX_SPECIAL_DOLLAR. And LEX_SPECIAL_AT would not be set +// even though there is an `@` character, because it occurs +// after the `[` which is LEX_DELIMITER class. +// +// Note: The reason the first character's lexical class is not +// considered is because it's important to know it exactly, so +// the caller will use GET_LEX_CLASS(ss->begin[0]). +// Fingerprinting just helps accelerate further categorization. +// +static REBCNT Prescan_Token(SCAN_STATE *ss) { - REBYTE *cp = scan_state->begin; /* char scan pointer */ - REBCNT flags = 0; /* lexical flags */ + const REBYTE *cp = ss->begin; + REBCNT flags = 0; - while (IS_LEX_SPACE(*cp)) cp++; /* skip white space */ - scan_state->begin = cp; /* start of lexical symbol */ + // Skip whitespace (if any) and update the ss + while (IS_LEX_SPACE(*cp)) cp++; + ss->begin = cp; - while (1) { + while (TRUE) { switch (GET_LEX_CLASS(*cp)) { case LEX_CLASS_DELIMIT: - if (cp == scan_state->begin) cp++; /* returning delimiter */ - scan_state->end = cp; + if (cp == ss->begin) { + // Include the delimiter if it is the only character we + // are returning in the range (leave it out otherwise) + ss->end = cp + 1; + + // Note: We'd liked to have excluded LEX_DELIMIT_END, but + // would require a GET_LEX_VALUE() call to know to do so. + // Locate_Token_May_Push_Mold() does a `switch` on that, + // so it can subtract this addition back out itself. + } + else + ss->end = cp; return flags; - case LEX_CLASS_SPECIAL: /* Flag all but first special char: */ - if (cp != scan_state->begin) SET_LEX_FLAG(flags, GET_LEX_VALUE(*cp)); + case LEX_CLASS_SPECIAL: + if (cp != ss->begin) { + // As long as it isn't the first character, we union a flag + // in the result mask to signal this special char's presence + SET_LEX_FLAG(flags, GET_LEX_VALUE(*cp)); + } cp++; break; case LEX_CLASS_WORD: - SET_LEX_FLAG(flags, LEX_SPECIAL_WORD); /* flags word char (for nums) */ - while (IS_LEX_AT_LEAST_WORD(*cp)) cp++; /* word or number */ + // !!! Comment said "flags word char (for nums)"...meaning? + SET_LEX_FLAG(flags, LEX_SPECIAL_WORD); + while (IS_LEX_WORD_OR_NUMBER(*cp)) cp++; break; case LEX_CLASS_NUMBER: - while (IS_LEX_AT_LEAST_NUMBER(*cp)) cp++; + while (IS_LEX_NUMBER(*cp)) cp++; break; } } } -/*********************************************************************** -** -*/ static REBINT Scan_Token(SCAN_STATE *scan_state) -/* -** Scan the next lexical object and determine its datatype. -** Skip all leading whitespace and conclude on a delimiter. -** -** Returns the value type (VT) identifying the token. -** Negative value types indicate an error in that type. -** Both the beginning and ending positions are updated. -** -** Note: this function does not need to find errors in types -** that are to be scanned and converted. It only needs to -** recognize that the value should be of that type. For words -** however, since no further scanning is done, they must be -** checked for errors here. Same is true for delimiters. -** -***********************************************************************/ -{ - REBCNT flags; - REBYTE *cp; - REBINT type; +// +// Locate_Token_May_Push_Mold: C +// +// Find the beginning and end character pointers for the next +// TOKEN_ in the scanner state. The TOKEN_ type returned will +// correspond directly to a Rebol datatype if it isn't an +// ANY-ARRAY! (e.g. TOKEN_INTEGER for INTEGER! or TOKEN_STRING +// for STRING!). When a block or group delimiter was found it +// will indicate that (e.g. TOKEN_BLOCK_BEGIN or TOKEN_GROUP_END). +// Hence the routine will have to be called multiple times during +// the array's content scan. +// +// !!! This should be modified to explain how paths work, once +// I can understand how paths work. :-/ --HF +// +// The scan state will be updated so that `ss->begin` has been moved past any +// leading whitespace that was pending in the buffer. `ss->end` will hold the +// conclusion at a delimiter. TOKEN_END is returned if end of input is +// reached (signaled by a null byte). +// +// Newlines that should be internal to a non-ANY-ARRAY! type are +// included in the scanned range between the `begin` and `end`. +// But newlines that are found outside of a string are returned +// as TOKEN_NEWLINE. (These are used to set the OPTS_VALUE_LINE +// formatting bit on the values.) +// +// Determining the end point of token types that need escaping +// requires processing (for instance `{a^}b}` can't see the first +// close brace as ending the string). To avoid double processing, +// the routine decodes the string's content into UNI_BUF for any +// quoted form to be used by the caller. This is overwritten in +// successive calls, and is only done for quoted forms (e.g. %"foo" +// will have data in UNI_BUF but %foo will not.) +// +// !!! This is a somewhat weird separation of responsibilities, +// that seems to arise from a desire to make "Scan_XXX" functions +// independent of the "Locate_Token_May_Push_Mold" function. +// But if the work of locating the value means you have to basically +// do what you'd do to read it into a REBVAL anyway, why split it? +// +// Error handling is limited for most types, as an additional +// phase is needed to load their data into a REBOL value. Yet if +// a "cheap" error is incidentally found during this routine +// without extra cost to compute, it can fail here. +// +// Examples with ss's (B)egin (E)nd and return value: +// +// foo: baz bar => TOKEN_SET +// B E +// +// [quick brown fox] => TOKEN_BLOCK_BEGIN +// B +// E +// +// "brown fox]" => TOKEN_WORD +// B E +// +// $10AE.20 sent => fail() +// B E +// +// {line1\nline2} => TOKEN_STRING (content in UNI_BUF) +// B E +// +// \n{line2} => TOKEN_NEWLINE (newline is external) +// BB +// E +// +// %"a ^"b^" c" d => TOKEN_FILE (content in UNI_BUF) +// B E +// +// %a-b.c d => TOKEN_FILE (content *not* in UNI_BUF) +// B E +// +// \0 => TOKEN_END +// BB +// EE +// +// Note: The reason that the code is able to use byte scanning +// over UTF-8 encoded source is because all the characters +// that dictate the tokenization are ASCII (< 128). +// +static void Locate_Token_May_Push_Mold( + REB_MOLD *mo, + SCAN_STATE *ss +) { +#if !defined(NDEBUG) + ss->token = TOKEN_MAX; +#endif + + TRASH_POINTER_IF_DEBUG(ss->end); // prescan only uses ->begin + + REBCNT flags = Prescan_Token(ss); // sets ->begin, ->end - flags = Prescan(scan_state); - cp = scan_state->begin; + const REBYTE *cp = ss->begin; switch (GET_LEX_CLASS(*cp)) { case LEX_CLASS_DELIMIT: switch (GET_LEX_VALUE(*cp)) { - case LEX_DELIMIT_SPACE: /* white space (pre-processed above) */ + case LEX_DELIMIT_SPACE: + panic ("Prescan_Token did not skip whitespace"); + case LEX_DELIMIT_SEMICOLON: /* ; begin comment */ - while (NOT_NEWLINE(*cp)) cp++; - if (!*cp) cp--; /* avoid passing EOF */ - if (*cp == LF) goto line_feed; + while (NOT(ANY_CR_LF_END(*cp))) + ++cp; + if (*cp == '\0') + --cp; /* avoid passing EOF */ + if (*cp == LF) goto line_feed; /* fall thru */ - case LEX_DELIMIT_RETURN: /* CR */ - if (cp[1] == LF) cp++; + case LEX_DELIMIT_RETURN: + if (cp[1] == LF) + ++cp; /* fall thru */ - case LEX_DELIMIT_LINEFEED: /* LF */ - line_feed: - scan_state->line_count++; - scan_state->end = cp+1; - return TOKEN_LINE; + case LEX_DELIMIT_LINEFEED: + line_feed: + ss->line++; + ss->end = cp + 1; + ss->token = TOKEN_NEWLINE; + return; + + + // [BRACKETS] + + case LEX_DELIMIT_LEFT_BRACKET: + ss->token = TOKEN_BLOCK_BEGIN; + return; + + case LEX_DELIMIT_RIGHT_BRACKET: + ss->token = TOKEN_BLOCK_END; + return; - case LEX_DELIMIT_LEFT_BRACKET: /* [ begin block */ - return TOKEN_BLOCK; + // (PARENS) - case LEX_DELIMIT_RIGHT_BRACKET: /* ] end block */ - return TOKEN_BLOCK_END; + case LEX_DELIMIT_LEFT_PAREN: + ss->token = TOKEN_GROUP_BEGIN; + return; - case LEX_DELIMIT_LEFT_PAREN: /* ( begin paren */ - return TOKEN_PAREN; + case LEX_DELIMIT_RIGHT_PAREN: + ss->token = TOKEN_GROUP_END; + return; - case LEX_DELIMIT_RIGHT_PAREN: /* ) end paren */ - return TOKEN_PAREN_END; - case LEX_DELIMIT_QUOTE: /* " quote */ - cp = Scan_Quote(cp, scan_state); // stores result string in BUF_MOLD + // "QUOTES" and {BRACES} + + case LEX_DELIMIT_DOUBLE_QUOTE: + cp = Scan_Quote_Push_Mold(mo, cp, ss); goto check_str; - case LEX_DELIMIT_LEFT_BRACE: /* { begin quote */ - cp = Scan_Quote(cp, scan_state); // stores result string in BUF_MOLD + case LEX_DELIMIT_LEFT_BRACE: + cp = Scan_Quote_Push_Mold(mo, cp, ss); check_str: if (cp) { - scan_state->end = cp; - return TOKEN_STRING; - } else { /* try to recover at next new line... */ - for (cp = (scan_state->begin)+1; NOT_NEWLINE(*cp); cp++); - scan_state->end = cp; - return -TOKEN_STRING; - } - - case LEX_DELIMIT_RIGHT_BRACE: /* } end quote !!! handle better (missing) */ - return -TOKEN_STRING; - - case LEX_DELIMIT_SLASH: /* probably / or / * */ - while (*cp && *cp == '/') cp++; - if (IS_LEX_AT_LEAST_WORD(*cp) || *cp=='+' || *cp=='-' || *cp=='.') { - // ///refine not allowed - if (scan_state->begin + 1 != cp) { - scan_state->end = cp; - return -TOKEN_REFINE; - } - scan_state->begin = cp; - flags = Prescan(scan_state); - scan_state->begin--; - type = TOKEN_REFINE; - // Fast easy case: - if (ONLY_LEX_FLAG(flags, LEX_SPECIAL_WORD)) return type; - goto scanword; - } - if (cp[0] == '<' || cp[0] == '>') { - scan_state->end = cp+1; - return -TOKEN_REFINE; - } - scan_state->end = cp; - return TOKEN_WORD; - - case LEX_DELIMIT_END_FILE: /* end of file */ - scan_state->end--; - return TOKEN_EOF; + ss->end = cp; + ss->token = TOKEN_STRING; + return; + } + // try to recover at next new line... + cp = ss->begin + 1; + while (NOT(ANY_CR_LF_END(*cp))) + ++cp; + ss->end = cp; + ss->token = TOKEN_STRING; + if (ss->begin[0] == '"') + fail (Error_Missing(ss, '"')); + if (ss->begin[0] == '{') + fail (Error_Missing(ss, '}')); + panic ("Invalid string start delimiter"); + + case LEX_DELIMIT_RIGHT_BRACE: + ss->token = TOKEN_STRING; + fail (Error_Extra(ss, '}')); + + + // /SLASH + + case LEX_DELIMIT_SLASH: + while (*cp && *cp == '/') + ++cp; + if ( + IS_LEX_WORD_OR_NUMBER(*cp) + || *cp == '+' + || *cp == '-' + || *cp == '.' + || *cp == '|' + || *cp == '_' + ){ + // ///refine not allowed + if (ss->begin + 1 != cp) { + ss->end = cp; + ss->token = TOKEN_REFINE; + fail (Error_Syntax(ss)); + } + ss->begin = cp; + flags = Prescan_Token(ss); + ss->begin--; + ss->token = TOKEN_REFINE; + // Fast easy case: + if (ONLY_LEX_FLAG(flags, LEX_SPECIAL_WORD)) + return; + goto scanword; + } + if (cp[0] == '<' || cp[0] == '>') { + ss->end = cp + 1; + ss->token = TOKEN_REFINE; + fail (Error_Syntax(ss)); + } + ss->end = cp; + ss->token = TOKEN_WORD; + return; + + case LEX_DELIMIT_END: + // Prescan_Token() spans the terminator as if it were a byte + // to process, so we collapse end to begin to signal no data + ss->end--; + assert(ss->end == ss->begin); + ss->token = TOKEN_END; + return; case LEX_DELIMIT_UTF8_ERROR: + ss->token = TOKEN_WORD; + fail (Error_Syntax(ss)); + default: - return -TOKEN_WORD; /* just in case */ + panic ("Invalid LEX_DELIMIT class"); } case LEX_CLASS_SPECIAL: - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_AT) && *cp != '<') return TOKEN_EMAIL; + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_AT) && *cp != '<') { + ss->token = TOKEN_EMAIL; + return; + } next_ls: switch (GET_LEX_VALUE(*cp)) { case LEX_SPECIAL_AT: - return -TOKEN_EMAIL; + ss->token = TOKEN_EMAIL; + fail (Error_Syntax(ss)); case LEX_SPECIAL_PERCENT: /* %filename */ - cp = scan_state->end; + cp = ss->end; if (*cp == '"') { - cp = Scan_Quote(cp, scan_state); // stores result string in BUF_MOLD - if (!cp) return -TOKEN_FILE; - scan_state->end = cp; - return TOKEN_FILE; + cp = Scan_Quote_Push_Mold(mo, cp, ss); + ss->token = TOKEN_FILE; + if (cp == NULL) + fail (Error_Syntax(ss)); + ss->end = cp; + ss->token = TOKEN_FILE; + return; } while (*cp == '/') { /* deal with path delimiter */ cp++; - while (IS_LEX_AT_LEAST_SPECIAL(*cp)) cp++; + while (IS_LEX_NOT_DELIMIT(*cp)) + ++cp; } - scan_state->end = cp; - return TOKEN_FILE; + ss->end = cp; + ss->token = TOKEN_FILE; + return; case LEX_SPECIAL_COLON: /* :word :12 (time) */ - if (IS_LEX_NUMBER(cp[1])) return TOKEN_TIME; - if (ONLY_LEX_FLAG(flags, LEX_SPECIAL_WORD)) return TOKEN_GET; /* common case */ - if (cp[1] == '\'') return -TOKEN_WORD; - // Various special cases of < << <> >> > >= <= - if (cp[1] == '<' || cp[1] == '>') { - cp++; - if (cp[1] == '<' || cp[1] == '>' || cp[1] == '=') cp++; - if (!IS_LEX_DELIMIT(cp[1])) return -TOKEN_GET; - scan_state->end = cp+1; - return TOKEN_GET; - } - type = TOKEN_GET; - cp++; /* skip ':' */ + if (IS_LEX_NUMBER(cp[1])) { + ss->token = TOKEN_TIME; + return; + } + if (ONLY_LEX_FLAG(flags, LEX_SPECIAL_WORD)) { + ss->token = TOKEN_GET; + return; // common case + } + if (cp[1] == '\'') { + ss->token = TOKEN_WORD; + fail (Error_Syntax(ss)); + } + // Various special cases of < << <> >> > >= <= + if (cp[1] == '<' || cp[1] == '>') { + cp++; + if (cp[1] == '<' || cp[1] == '>' || cp[1] == '=') + ++cp; + ss->token = TOKEN_GET; + if (NOT(IS_LEX_DELIMIT(cp[1]))) + fail (Error_Syntax(ss)); + ss->end = cp + 1; + return; + } + ss->token = TOKEN_GET; + ++cp; // skip ':' goto scanword; - case LEX_SPECIAL_TICK: - if (IS_LEX_NUMBER(cp[1])) return -TOKEN_LIT; // no '2nd - if (cp[1] == ':') return -TOKEN_LIT; // no ':X - if (ONLY_LEX_FLAG(flags, LEX_SPECIAL_WORD)) return TOKEN_LIT; /* common case */ - if (!IS_LEX_WORD(cp[1])) { - // Various special cases of < << <> >> > >= <= - if ((cp[1] == '-' || cp[1] == '+') && IS_LEX_NUMBER(cp[2])) return -TOKEN_WORD; - if (cp[1] == '<' || cp[1] == '>') { - cp++; - if (cp[1] == '<' || cp[1] == '>' || cp[1] == '=') cp++; - if (!IS_LEX_DELIMIT(cp[1])) return -TOKEN_LIT; - scan_state->end = cp+1; - return TOKEN_LIT; - } - } - if (cp[1] == '\'') return -TOKEN_WORD; - type = TOKEN_LIT; + case LEX_SPECIAL_APOSTROPHE: + if (IS_LEX_NUMBER(cp[1])) { // no '2nd + ss->token = TOKEN_LIT; + fail (Error_Syntax(ss)); + } + if (cp[1] == ':') { // no ':X + ss->token = TOKEN_LIT; + fail (Error_Syntax(ss)); + } + if ( + cp[1] == '|' + && (IS_LEX_DELIMIT(cp[2]) || IS_LEX_ANY_SPACE(cp[2])) + ){ + ss->token = TOKEN_LIT_BAR; + return; // '| is a LIT-BAR!, '|foo is LIT-WORD! + } + if (ONLY_LEX_FLAG(flags, LEX_SPECIAL_WORD)) { + ss->token = TOKEN_LIT; + return; // common case + } + if (NOT(IS_LEX_WORD(cp[1]))) { + // Various special cases of < << <> >> > >= <= + if ((cp[1] == '-' || cp[1] == '+') && IS_LEX_NUMBER(cp[2])) { + ss->token = TOKEN_WORD; + fail (Error_Syntax(ss)); + } + if (cp[1] == '<' || cp[1] == '>') { + cp++; + if (cp[1] == '<' || cp[1] == '>' || cp[1] == '=') + ++cp; + ss->token = TOKEN_LIT; + if (NOT(IS_LEX_DELIMIT(cp[1]))) + fail (Error_Syntax(ss)); + ss->end = cp + 1; + return; + } + } + if (cp[1] == '\'') { + ss->token = TOKEN_WORD; + fail (Error_Syntax(ss)); + } + ss->token = TOKEN_LIT; goto scanword; case LEX_SPECIAL_COMMA: /* ,123 */ case LEX_SPECIAL_PERIOD: /* .123 .123.456.789 */ SET_LEX_FLAG(flags, (GET_LEX_VALUE(*cp))); - if (IS_LEX_NUMBER(cp[1])) goto num; - if (GET_LEX_VALUE(*cp) != LEX_SPECIAL_PERIOD) return -TOKEN_WORD; - type = TOKEN_WORD; - goto scanword; - - case LEX_SPECIAL_GREATER: - if (IS_LEX_DELIMIT(cp[1])) return TOKEN_WORD; // RAMBO 3903 - if (cp[1] == '>') { - if (IS_LEX_DELIMIT(cp[2])) return TOKEN_WORD; - return -TOKEN_WORD; - } - case LEX_SPECIAL_LESSER: - if (IS_LEX_ANY_SPACE(cp[1]) || cp[1] == ']' || cp[1] == 0) return TOKEN_WORD; // CES.9121 Was LEX_DELIMIT - changed for - if ((cp[0] == '<' && cp[1] == '<') || cp[1] == '=' || cp[1] == '>') { - if (IS_LEX_DELIMIT(cp[2])) return TOKEN_WORD; - return -TOKEN_WORD; - } - if (GET_LEX_VALUE(*cp) == LEX_SPECIAL_GREATER) return -TOKEN_WORD; - cp = Skip_Tag(cp); - if (!cp) return -TOKEN_TAG; - scan_state->end = cp; - return TOKEN_TAG; + if (IS_LEX_NUMBER(cp[1])) + goto num; + ss->token = TOKEN_WORD; + if (GET_LEX_VALUE(*cp) != LEX_SPECIAL_PERIOD) + fail (Error_Syntax(ss)); + ss->token = TOKEN_WORD; + goto scanword; + + case LEX_SPECIAL_GREATER: + if (IS_LEX_DELIMIT(cp[1])) { + ss->token = TOKEN_WORD; + return; + } + if (cp[1] == '>') { + ss->token = TOKEN_WORD; + if (IS_LEX_DELIMIT(cp[2])) + return; + fail (Error_Syntax(ss)); + } + // falls through + case LEX_SPECIAL_LESSER: + if (IS_LEX_ANY_SPACE(cp[1]) || cp[1] == ']' || cp[1] == 0) { + ss->token = TOKEN_WORD; // changed for + return; + } + if ( + (cp[0] == '<' && cp[1] == '<') || cp[1] == '=' || cp[1] == '>' + ){ + ss->token = TOKEN_WORD; + if (IS_LEX_DELIMIT(cp[2])) + return; + fail (Error_Syntax(ss)); + } + if ( + cp[0] == '<' && (cp[1] == '-' || cp[1] == '|') + && (IS_LEX_DELIMIT(cp[2]) || IS_LEX_ANY_SPACE(cp[2])) + ){ + ss->token = TOKEN_WORD; + return; // "<|" and "<-" + } + if (GET_LEX_VALUE(*cp) == LEX_SPECIAL_GREATER) { + ss->token = TOKEN_WORD; + fail (Error_Syntax(ss)); + } + cp = Skip_Tag(cp); + ss->token = TOKEN_TAG; + if (cp == NULL) + fail (Error_Syntax(ss)); + ss->end = cp; + return; case LEX_SPECIAL_PLUS: /* +123 +123.45 +$123 */ case LEX_SPECIAL_MINUS: /* -123 -123.45 -$123 */ - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_AT)) return TOKEN_EMAIL; - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_DOLLAR)) return TOKEN_MONEY; + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_AT)) { + ss->token = TOKEN_EMAIL; + return; + } + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_DOLLAR)) { + ss->token = TOKEN_MONEY; + return; + } if (HAS_LEX_FLAG(flags, LEX_SPECIAL_COLON)) { - cp = Skip_To_Char(cp, scan_state->end, ':'); - if (cp && (cp+1) != scan_state->end) return TOKEN_TIME; /* 12:34 */ - cp = scan_state->begin; - if (cp[1] == ':') { // +: -: - type = TOKEN_WORD; + cp = Skip_To_Byte(cp, ss->end, ':'); + if (cp != NULL && (cp + 1) != ss->end) { // 12:34 + ss->token = TOKEN_TIME; + return; + } + cp = ss->begin; + if (cp[1] == ':') { // +: -: + ss->token = TOKEN_WORD; goto scanword; - } - } + } + } cp++; - if (IS_LEX_AT_LEAST_NUMBER(*cp)) goto num; + if (IS_LEX_NUMBER(*cp)) + goto num; if (IS_LEX_SPECIAL(*cp)) { - if ((GET_LEX_VALUE(*cp)) >= LEX_SPECIAL_PERIOD) goto next_ls; -/* if (*cp == '#') goto hex; */ + if ((GET_LEX_VALUE(*cp)) >= LEX_SPECIAL_PERIOD) + goto next_ls; if (*cp == '+' || *cp == '-') { - type = TOKEN_WORD; + ss->token = TOKEN_WORD; goto scanword; } - return -TOKEN_WORD; + if ( + *cp == '>' + && (IS_LEX_DELIMIT(cp[1]) || IS_LEX_ANY_SPACE(cp[1])) + ) { + // Special exemption for -> + ss->token = TOKEN_WORD; + return; + } + ss->token = TOKEN_WORD; + fail (Error_Syntax(ss)); } - type = TOKEN_WORD; + ss->token = TOKEN_WORD; + goto scanword; + + case LEX_SPECIAL_BAR: + // + // `|` standalone should become a BAR!, so if followed by a + // delimiter or space. However `|a|` and `a|b` are left as + // legal words (at least for the time being). + // + if (IS_LEX_DELIMIT(cp[1]) || IS_LEX_ANY_SPACE(cp[1])) { + ss->token = TOKEN_BAR; + return; + } + if ( + cp[1] == '>' + && (IS_LEX_DELIMIT(cp[2]) || IS_LEX_ANY_SPACE(cp[2])) + ) { + ss->token = TOKEN_WORD; + return; // for `|>` + } + ss->token = TOKEN_WORD; + goto scanword; + + case LEX_SPECIAL_BLANK: + // + // `_` standalone should become a BLANK!, so if followed by a + // delimiter or space. However `_a_` and `a_b` are left as + // legal words (at least for the time being). + // + if (IS_LEX_DELIMIT(cp[1]) || IS_LEX_ANY_SPACE(cp[1])) { + ss->token = TOKEN_BLANK; + return; + } + ss->token = TOKEN_WORD; goto scanword; case LEX_SPECIAL_POUND: pound: cp++; -/* hex: - if (HAS_LEX_FLAGS(flags, ~(LEX_FLAG(LEX_SPECIAL_POUND) | LEX_FLAG(LEX_SPECIAL_PERIOD) - | LEX_FLAG(LEX_SPECIAL_TICK) | LEX_FLAG(LEX_SPECIAL_WORD)))) return -TOKEN_INTEGER; -*/ -/* if (HAS_LEX_FLAG(flags, LEX_SPECIAL_PERIOD)) return TOKEN_BYTES; */ - if (*cp == '[') { - scan_state->end = ++cp; - return TOKEN_CONSTRUCT; - } - if (*cp == '"') { /* CHAR #"C" */ - cp++; - type = Scan_Char(&cp); - if (type >= 0 && *cp == '"') { - scan_state->end = cp+1; - return TOKEN_CHAR; - } else { /* try to recover at next new line... */ - for (cp = (scan_state->begin)+1; NOT_NEWLINE(*cp); cp++); - scan_state->end = cp; - return -TOKEN_CHAR; - } - } - if (*cp == '{') { /* BINARY #{12343132023902902302938290382} */ - scan_state->end = scan_state->begin; /* save start */ - scan_state->begin = cp; - cp = Scan_Quote(cp, scan_state); // stores result string in BUF_MOLD !!?? - scan_state->begin = scan_state->end; /* restore start */ - if (cp) { - scan_state->end = cp; - return TOKEN_BINARY; - } else { /* try to recover at next new line... */ - for (cp = (scan_state->begin)+1; NOT_NEWLINE(*cp); cp++); - scan_state->end = cp; - return -TOKEN_BINARY; - } - } - if (cp-1 == scan_state->begin) return TOKEN_ISSUE; - else return -TOKEN_INTEGER; + if (*cp == '[') { + ss->end = ++cp; + ss->token = TOKEN_CONSTRUCT; + return; + } + if (*cp == '"') { /* CHAR #"C" */ + REBUNI dummy; + cp++; + cp = Scan_UTF8_Char_Escapable(&dummy, cp); + if (cp && *cp == '"') { + ss->end = cp + 1; + ss->token = TOKEN_CHAR; + return; + } + // try to recover at next new line... + cp = ss->begin + 1; + while (NOT(ANY_CR_LF_END(*cp))) + ++cp; + ss->end = cp; + ss->token = TOKEN_CHAR; + fail (Error_Syntax(ss)); + } + if (*cp == '{') { /* BINARY #{12343132023902902302938290382} */ + ss->end = ss->begin; /* save start */ + ss->begin = cp; + cp = Scan_Quote_Push_Mold(mo, cp, ss); + ss->begin = ss->end; /* restore start */ + if (cp) { + ss->end = cp; + ss->token = TOKEN_BINARY; + return; + } + // try to recover at next new line... + cp = ss->begin + 1; + while (NOT(ANY_CR_LF_END(*cp))) + ++cp; + ss->end = cp; + ss->token = TOKEN_BINARY; + fail (Error_Syntax(ss)); + } + if (cp - 1 == ss->begin) { + ss->token = TOKEN_ISSUE; + return; + } + + ss->token = TOKEN_INTEGER; + fail (Error_Syntax(ss)); case LEX_SPECIAL_DOLLAR: - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_AT)) return TOKEN_EMAIL; - return TOKEN_MONEY; + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_AT)) { + ss->token = TOKEN_EMAIL; + return; + } + ss->token = TOKEN_MONEY; + return; default: - return -TOKEN_WORD; + ss->token = TOKEN_WORD; + fail (Error_Syntax(ss)); } case LEX_CLASS_WORD: - if (ONLY_LEX_FLAG(flags, LEX_SPECIAL_WORD)) return TOKEN_WORD; - type = TOKEN_WORD; + ss->token = TOKEN_WORD; + if (ONLY_LEX_FLAG(flags, LEX_SPECIAL_WORD)) + return; goto scanword; case LEX_CLASS_NUMBER: /* order of tests is important */ num: - if (!flags) return TOKEN_INTEGER; /* simple integer */ - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_AT)) return TOKEN_EMAIL; - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_POUND)) { - if (cp == scan_state->begin) { // no +2 +16 +64 allowed - if ( - (cp[0] == '6' && cp[1] == '4' && cp[2] == '#' && cp[3] == '{') - || (cp[0] == '1' && cp[1] == '6' && cp[2] == '#' && cp[3] == '{') // rare - ) {cp += 2; goto pound;} - if (cp[0] == '2' && cp[1] == '#' && cp[2] == '{') - {cp++; goto pound;} // very rare - } - return -TOKEN_INTEGER; - } - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_COLON)) return TOKEN_TIME; /* 12:34 */ - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_PERIOD)) { /* 1.2 1.2.3 1,200.3 1.200,3 1.E-2 */ - if (Skip_To_Char(cp, scan_state->end, 'x')) return TOKEN_PAIR; - cp = Skip_To_Char(cp, scan_state->end, '.'); - if (!(HAS_LEX_FLAG(flags, LEX_SPECIAL_COMMA)) && /* no comma in bytes */ - Skip_To_Char(cp+1, scan_state->end, '.')) return TOKEN_TUPLE; - return TOKEN_DECIMAL; - } - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_COMMA)) { - if (Skip_To_Char(cp, scan_state->end, 'x')) return TOKEN_PAIR; - return TOKEN_DECIMAL; /* 1,23 */ - } - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_POUND)) { /* -#123 2#1010 */ - if (HAS_LEX_FLAGS(flags, ~(LEX_FLAG(LEX_SPECIAL_POUND) | LEX_FLAG(LEX_SPECIAL_PERIOD) | LEX_FLAG(LEX_SPECIAL_TICK)))) return -TOKEN_INTEGER; - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_PERIOD)) return TOKEN_TUPLE; - return TOKEN_INTEGER; - } - /* Note: cannot detect dates of the form 1/2/1998 because they - ** may appear within a path, where they are not actually dates! - ** Special parsing is required at the next level up. */ - for (;cp != scan_state->end; cp++) { /* what do we hit first? 1-AUG-97 or 123E-4 */ - if (*cp == '-') return TOKEN_DATE; /* 1-2-97 1-jan-97 */ - if (*cp == 'x' || *cp == 'X') return TOKEN_PAIR; // 320x200 - if (*cp == 'E' || *cp == 'e') { - if (Skip_To_Char(cp, scan_state->end, 'x')) return TOKEN_PAIR; - return TOKEN_DECIMAL; /* 123E4 */ - } - if (*cp == '%') return TOKEN_PERCENT; - } - /*cp = scan_state->begin;*/ - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_TICK)) return TOKEN_INTEGER; /* 1'200 */ - return -TOKEN_INTEGER; + if (flags == 0) { // simple integer + ss->token = TOKEN_INTEGER; + return; + } + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_AT)) { + ss->token = TOKEN_EMAIL; + return; + } + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_POUND)) { + if (cp == ss->begin) { // no +2 +16 +64 allowed + if ( + ( + cp[0] == '6' + && cp[1] == '4' + && cp[2] == '#' + && cp[3] == '{' + ) || ( + cp[0] == '1' + && cp[1] == '6' + && cp[2] == '#' + && cp[3] == '{' + ) // rare + ) { + cp += 2; + goto pound; + } + if (cp[0] == '2' && cp[1] == '#' && cp[2] == '{') { + // very rare + cp++; + goto pound; + } + } + ss->token = TOKEN_INTEGER; + fail (Error_Syntax(ss)); + } + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_COLON)) { // 12:34 + ss->token = TOKEN_TIME; + return; + } + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_PERIOD)) { + // 1.2 1.2.3 1,200.3 1.200,3 1.E-2 + if (Skip_To_Byte(cp, ss->end, 'x')) { + ss->token = TOKEN_TIME; + return; + } + cp = Skip_To_Byte(cp, ss->end, '.'); + // Note: no comma in bytes + if ( + NOT(HAS_LEX_FLAG(flags, LEX_SPECIAL_COMMA)) + && Skip_To_Byte(cp + 1, ss->end, '.') + ){ + ss->token = TOKEN_TUPLE; + return; + } + ss->token = TOKEN_DECIMAL; + return; + } + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_COMMA)) { + if (Skip_To_Byte(cp, ss->end, 'x')) { + ss->token = TOKEN_PAIR; + return; + } + ss->token = TOKEN_DECIMAL; // 1,23 + return; + } + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_POUND)) { // -#123 2#1010 + if ( + HAS_LEX_FLAGS( + flags, + ~( + LEX_FLAG(LEX_SPECIAL_POUND) + | LEX_FLAG(LEX_SPECIAL_PERIOD) + | LEX_FLAG(LEX_SPECIAL_APOSTROPHE) + ) + ) + ){ + ss->token = TOKEN_INTEGER; + fail (Error_Syntax(ss)); + } + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_PERIOD)) { + ss->token = TOKEN_TUPLE; + return; + } + ss->token = TOKEN_INTEGER; + return; + } + /* Note: cannot detect dates of the form 1/2/1998 because they + ** may appear within a path, where they are not actually dates! + ** Special parsing is required at the next level up. */ + for (;cp != ss->end; cp++) { + // what do we hit first? 1-AUG-97 or 123E-4 + if (*cp == '-') { + ss->token = TOKEN_DATE; + return; // 1-2-97 1-jan-97 + } + if (*cp == 'x' || *cp == 'X') { + ss->token = TOKEN_PAIR; + return; // 320x200 + } + if (*cp == 'E' || *cp == 'e') { + if (Skip_To_Byte(cp, ss->end, 'x')) { + ss->token = TOKEN_PAIR; + return; + } + ss->token = TOKEN_DECIMAL; // 123E4 + return; + } + if (*cp == '%') { + ss->token = TOKEN_PERCENT; + return; + } + } + ss->token = TOKEN_INTEGER; + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_APOSTROPHE)) // 1'200 + return; + fail (Error_Syntax(ss)); default: - return -TOKEN_WORD; + panic ("Invalid LEX class"); } -#if ndef // unreachable code - /* avoid '123 :123 from scanning as a word.... */ - if (IS_LEX_WORD(cp[1]) && !HAS_LEX_FLAGS(flags, LEX_WORD_FLAGS)) - return TOKEN_LIT; - return -TOKEN_WORD; -#endif + DEAD_END; scanword: - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_COLON)) { /* word: url:words */ - if (type != TOKEN_WORD) return type; //-TOKEN_WORD; /* only valid with WORD (not set or lit) */ - cp = Skip_To_Char(cp, scan_state->end, ':'); /* always returns a pointer (always a ':') */ - if (cp[1] != '/' && Lex_Map[(REBYTE)cp[1]] < LEX_SPECIAL) { /* a valid delimited word SET? */ - if (HAS_LEX_FLAGS(flags, ~LEX_FLAG(LEX_SPECIAL_COLON) & LEX_WORD_FLAGS)) return -TOKEN_WORD; - return TOKEN_SET; +#if !defined(NDEBUG) + assert(ss->token != TOKEN_MAX); +#endif + + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_COLON)) { // word: url:words + if (ss->token != TOKEN_WORD) { + // only valid with WORD (not set or lit) + return; + } + // This Skip_To_Byte always returns a pointer (always a ':') + cp = Skip_To_Byte(cp, ss->end, ':'); + if (cp[1] != '/' && Lex_Map[cp[1]] < LEX_SPECIAL) { + // a valid delimited word SET? + if ( + HAS_LEX_FLAGS( + flags, ~LEX_FLAG(LEX_SPECIAL_COLON) & LEX_WORD_FLAGS + ) + ){ + ss->token = TOKEN_WORD; + fail (Error_Syntax(ss)); + } + ss->token = TOKEN_SET; + return; } - cp = scan_state->end; /* then, must be a URL */ + cp = ss->end; /* then, must be a URL */ while (*cp == '/') { /* deal with path delimiter */ cp++; - while (IS_LEX_AT_LEAST_SPECIAL(*cp) || *cp == '/') cp++; + while (IS_LEX_NOT_DELIMIT(*cp) || *cp == '/') + ++cp; } - scan_state->end = cp; - return TOKEN_URL; + ss->end = cp; + ss->token = TOKEN_URL; + return; + } + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_AT)) { + ss->token = TOKEN_EMAIL; + return; + } + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_DOLLAR)) { + ss->token = TOKEN_MONEY; + return; } - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_AT)) return TOKEN_EMAIL; - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_DOLLAR)) return TOKEN_MONEY; - if (HAS_LEX_FLAGS(flags, LEX_WORD_FLAGS)) return -type; /* has chars not allowed in word (eg % \ ) */ - if (HAS_LEX_FLAG(flags, LEX_SPECIAL_LESSER)) { - // Allow word and word but not word< word<= word<> etc. - cp = Skip_To_Char(cp, scan_state->end, '<'); - if (cp[1] == '<' || cp[1] == '>' || cp[1] == '=' || - IS_LEX_SPACE(cp[1]) || (cp[1] != '/' && IS_LEX_DELIMIT(cp[1]))) - return -type; - /*bogus: if (HAS_LEX_FLAG(flags, LEX_SPECIAL_GREATER) && - Skip_To_Char(scan_state->begin, cp, '>')) return -TOKEN_WORD; */ - scan_state->end = cp; - } else if (HAS_LEX_FLAG(flags, LEX_SPECIAL_GREATER)) return -type; - return type; + if (HAS_LEX_FLAGS(flags, LEX_WORD_FLAGS)) { + // has chars not allowed in word (eg % \ ) + fail (Error_Syntax(ss)); + } + if (HAS_LEX_FLAG(flags, LEX_SPECIAL_LESSER)) { + // Allow word and word but not word< word<= word<> etc. + cp = Skip_To_Byte(cp, ss->end, '<'); + if ( + cp[1] == '<' || cp[1] == '>' || cp[1] == '=' + || IS_LEX_SPACE(cp[1]) + || (cp[1] != '/' && IS_LEX_DELIMIT(cp[1])) + ){ + fail (Error_Syntax(ss)); + } + ss->end = cp; + } + else if (HAS_LEX_FLAG(flags, LEX_SPECIAL_GREATER)) + fail (Error_Syntax(ss)); + + return; } -/*********************************************************************** -** -*/ static void Init_Scan_State(SCAN_STATE *scan_state, REBYTE *cp, REBCNT limit) -/* -** Initialize a scanner state structure. Set the standard -** scan pointers and the limit pointer. -** -***********************************************************************/ -{ - scan_state->head_line = scan_state->begin = scan_state->end = cp; - scan_state->limit = cp + limit; - scan_state->line_count = 1; - scan_state->opts = 0; - scan_state->errors = 0; -// scan_state->error_id = (REBYTE *)""; +// +// Init_Scan_State: C +// +// Initialize a scanner state structure. Set the standard +// scan pointers and the limit pointer. +// +static void Init_Scan_State( + SCAN_STATE *ss, + const REBYTE *utf8, + REBCNT limit, + REBSTR *filename, + REBUPT line +) { + ss->start_line_head = ss->line_head = ss->begin = utf8; + TRASH_POINTER_IF_DEBUG(ss->end); + ss->limit = utf8 + limit; + ss->start_line = ss->line = line; + ss->filename = filename; + ss->opts = 0; + +#if !defined(NDEBUG) + ss->token = TOKEN_MAX; +#endif } -/*********************************************************************** -** -*/ static REBINT Scan_Head(SCAN_STATE *scan_state) -/* -** Search text for a REBOL header. It is distinguished as -** the word REBOL followed by a '[' (they can be separated -** only by lines and comments). There can be nothing on the -** line before the header. Also, if a '[' preceedes the -** header, then note its position (for embedded code). -** The scan_state begin pointer is updated to point to the header block. -** Keep track of line-count. -** -** Returns: -** 0 if no header, -** 1 if header, -** -1 if embedded header (inside []). -** -** The scan_state structure is updated to point to the -** beginning of the source text. -** -***********************************************************************/ +// +// Scan_Head: C +// +// Search text for a REBOL header. It is distinguished as +// the word REBOL followed by a '[' (they can be separated +// only by lines and comments). There can be nothing on the +// line before the header. Also, if a '[' preceedes the +// header, then note its position (for embedded code). +// The ss begin pointer is updated to point to the header block. +// Keep track of line-count. +// +// Returns: +// 0 if no header, +// 1 if header, +// -1 if embedded header (inside []). +// +// The ss structure is updated to point to the +// beginning of the source text. +// +static REBINT Scan_Head(SCAN_STATE *ss) { - REBYTE *rp = 0; /* pts to the REBOL word */ - REBYTE *bp = 0; /* pts to optional [ just before REBOL */ - REBYTE *cp = scan_state->begin; - REBCNT count = scan_state->line_count; + const REBYTE *rp = 0; /* pts to the REBOL word */ + const REBYTE *bp = 0; /* pts to optional [ just before REBOL */ + const REBYTE *cp = ss->begin; + REBCNT count = ss->line; - while (TRUE) { + while (TRUE) { while (IS_LEX_SPACE(*cp)) cp++; /* skip white space */ - switch (*cp) { - case '[': + switch (*cp) { + case '[': if (rp) { - scan_state->begin = ++cp; //(bp ? bp : cp); - scan_state->line_count = count; + ss->begin = ++cp; //(bp ? bp : cp); + ss->line = count; return (bp ? -1 : 1); } - bp = cp++; - break; + bp = cp++; + break; case 'R': - case 'r': - if (Match_Bytes(cp, (REBYTE *)&Str_REBOL[0])) { - rp = cp; + case 'r': + if (Match_Bytes(cp, cb_cast(Str_REBOL))) { + rp = cp; cp += 5; - break; + break; } cp++; - bp = 0; /* prior '[' was a red herring */ - /* fall thru... */ + bp = 0; /* prior '[' was a red herring */ + /* fall thru... */ case ';': goto skipline; case 0: - return 0; - default: /* everything else... */ - if NOT_NEWLINE(*cp) rp = bp = 0; - skipline: - while NOT_NEWLINE(*cp) cp++; - if (*cp == CR && cp[1] == LF) cp++; - if (*cp) cp++; - count++; + return 0; + default: /* everything else... */ + if (!ANY_CR_LF_END(*cp)) rp = bp = 0; + skipline: + while (!ANY_CR_LF_END(*cp)) cp++; + if (*cp == CR && cp[1] == LF) cp++; + if (*cp) cp++; + count++; break; - } - } + } + } } -#ifdef not_used -//!!! -/*********************************************************************** -** - REBOOL Construct_Simple(REBVAL *value, REBSER *spec) -/* -** Handle special #[type] constructs. These are used to -** boot REBOL, so must not require binding. -** -***********************************************************************/ -{ - REBVAL *blk = BLK_HEAD(spec); - if (!IS_WORD(blk)) return FALSE; - switch (VAL_WORD_SYM(blk)-1) { - case SYM_NONE: - SET_NONE(value); - break; - case SYM_FALSE: - SET_LOGIC(value, FALSE); - break; - case SYM_TRUE: - SET_LOGIC(value, TRUE); - break; - default: - return FALSE; - } - return TRUE; -} -#endif -extern REBSER *Scan_Full_Block(SCAN_STATE *scan_state, REBYTE mode_char); +static REBARR *Scan_Full_Array(SCAN_STATE *ss, REBYTE mode_char); +static REBARR *Scan_Child_Array(SCAN_STATE *ss, REBYTE mode_char); + +// +// Scan_Array: C +// +// Scans an array of values, based on a mode_char. This character can be +// '[', '(', or '/' to indicate the processing type. Always returns array. +// +// If the source bytes are "1" then it will be the array [1] +// If the source bytes are "[1]" then it will be the array [[1]] +// +// Variations like GET-PATH!, SET-PATH! or LIT-PATH! are not discerned in +// the result here. Instead, ordinary path scanning is done, followed by a +// transformation (e.g. if the first element was a GET-WORD!, change it to +// an ordinary WORD! and make it a GET-PATH!) The caller does this. +// +static REBARR *Scan_Array( + SCAN_STATE *ss, + REBYTE mode_char +) { + const REBDSP dsp_orig = DSP; + + // just_once for load/next see Load_Script for more info. + const REBOOL just_once = GET_FLAG(ss->opts, SCAN_NEXT); + + struct Reb_State state; + REBCTX *error; + + if (C_STACK_OVERFLOWING(&state)) + Trap_Stack_Overflow(); + + if (GET_FLAG(ss->opts, SCAN_RELAX)) { + PUSH_TRAP(&error, &state); + if (error != NULL) { + ss->begin = ss->end; // skip malformed token + + DS_PUSH_TRASH; + Init_Error(DS_TOP, error); + + goto array_done_relax; + } + } -/*********************************************************************** -** -*/ static REBSER *Scan_Block(SCAN_STATE *scan_state, REBYTE mode_char) -/* -** Scan a block (or paren) and return it. -** Sub scanners may return bad by setting value type to zero. -** -***********************************************************************/ -{ - REBINT token; - REBCNT len; - REBYTE *bp; - REBYTE *ep; - REBVAL *value = 0; - REBSER *emitbuf = BUF_EMIT; - REBSER *block; - REBCNT begin = emitbuf->tail; // starting point in block buffer - REBOOL line = FALSE; -#ifdef COMP_LINES - REBINT linenum; -#endif - REBCNT start = scan_state->line_count; - REBYTE *start_line = scan_state->head_line; - // just_once for load/next see Load_Script for more info. - REBOOL just_once = GET_FLAG(scan_state->opts, SCAN_NEXT); + REBOOL line; // goto would cross init, moving up gets clobber warning + line = FALSE; - CHECK_STACK(&token); - - if (just_once) - CLR_FLAG(scan_state->opts, SCAN_NEXT); // no deeper + REB_MOLD mo; + CLEARS(&mo); - //scan_state->error_id = (REBYTE *) ""; + if (just_once) + CLR_FLAG(ss->opts, SCAN_NEXT); // no deeper while ( -#ifdef COMP_LINES - linenum=scan_state->line_count, -#endif - ((token = Scan_Token(scan_state)) != TOKEN_EOF) - ) { - - bp = scan_state->begin; - ep = scan_state->end; - len = (REBCNT)(ep - bp); - - if (token < 0) { // Check for error tokens - token = -token; - ACCEPT_TOKEN(scan_state); - goto syntax_error; - } - - // Is output block buffer large enough? - if (token >= TOKEN_WORD && SERIES_FULL(emitbuf)) - Extend_Series(emitbuf, 1024); - - value = BLK_TAIL(emitbuf); - SET_END(value); - // Line opt was set here. Moved to end in 3.0. + Drop_Mold_If_Pushed(&mo), + Locate_Token_May_Push_Mold(&mo, ss), + (ss->token != TOKEN_END) + ){ + const REBYTE *bp = ss->begin; + const REBYTE *ep = ss->end; + REBCNT len = cast(REBCNT, ep - bp); // If in a path, handle start of path /word or word//word cases: if (mode_char == '/' && *bp == '/') { - SET_NONE(value); - emitbuf->tail++; - scan_state->begin = bp + 1; - continue; + DS_PUSH_TRASH; + Init_Blank(DS_TOP); + ss->begin = bp + 1; + continue; } // Check for new path: /word or word/word: - if ((token == TOKEN_PATH || ((token == TOKEN_WORD || token == TOKEN_LIT || - token == TOKEN_GET) && *ep == '/')) - && mode_char != '/') { - //line = VAL_GET_LINE(value); - block = Scan_Block(scan_state, '/'); // (could realloc emitbuf) - value = BLK_TAIL(emitbuf); - VAL_SERIES(value) = block; - if (token == TOKEN_LIT) { - token = REB_LIT_PATH; - VAL_SET(BLK_HEAD(block), REB_WORD); // NO_FRAME - } - else if (IS_GET_WORD(BLK_HEAD(block))) { - if (*scan_state->end == ':') goto syntax_error; - token = REB_GET_PATH; - VAL_SET(BLK_HEAD(block), REB_WORD); // NO_FRAME - } - else { - if (*scan_state->end == ':') { - token = REB_SET_PATH; - scan_state->begin = ++(scan_state->end); - } else token = REB_PATH; - } -// if (IS_SET_WORD(BLK_SKIP(block, block->tail - 1) - VAL_SET(value, token); - VAL_INDEX(value) = 0; - //if (line) line = FALSE, VAL_SET_LINE(value); - token = TOKEN_PATH; - } else { - ACCEPT_TOKEN(scan_state); + if ( + ( + ss->token == TOKEN_PATH + || ( + ( + ss->token == TOKEN_WORD + || ss->token == TOKEN_LIT + || ss->token == TOKEN_GET + ) + && *ep == '/' + ) + ) + && mode_char != '/' + ) { + REBARR *array = Scan_Child_Array(ss, '/'); + + DS_PUSH_TRASH; + + if (ss->token == TOKEN_LIT) { + VAL_RESET_HEADER(DS_TOP, REB_LIT_PATH); + VAL_RESET_HEADER(ARR_HEAD(array), REB_WORD); + assert(IS_WORD_UNBOUND(ARR_HEAD(array))); + } + else if (IS_GET_WORD(ARR_HEAD(array))) { + if (*ss->end == ':') + fail (Error_Syntax(ss)); + VAL_RESET_HEADER(DS_TOP, REB_GET_PATH); + VAL_RESET_HEADER(ARR_HEAD(array), REB_WORD); + assert(IS_WORD_UNBOUND(ARR_HEAD(array))); + } + else { + if (*ss->end == ':') { + VAL_RESET_HEADER(DS_TOP, REB_SET_PATH); + ss->begin = ++ss->end; + } + else + VAL_RESET_HEADER(DS_TOP, REB_PATH); + } + INIT_VAL_ARRAY(DS_TOP, array); // copies args + VAL_INDEX(DS_TOP) = 0; + ss->token = TOKEN_PATH; } + else + ss->begin = ss->end; // accept token + + // Process each lexical token appropriately: + switch (ss->token) { + + case TOKEN_NEWLINE: + line = TRUE; + ss->line_head = ep; + continue; + + case TOKEN_BAR: + DS_PUSH_TRASH; + Init_Bar(DS_TOP); + ++bp; + break; + + case TOKEN_LIT_BAR: + DS_PUSH_TRASH; + Init_Lit_Bar(DS_TOP); + ++bp; + break; + + case TOKEN_BLANK: + DS_PUSH_TRASH; + Init_Blank(DS_TOP); + ++bp; + break; + + case TOKEN_LIT: + case TOKEN_GET: + if (ep[-1] == ':') { + if (len == 1 || mode_char != '/') + fail (Error_Syntax(ss)); + --len; + --ss->end; + } + bp++; + // falls through + case TOKEN_SET: + len--; + if (mode_char == '/' && ss->token == TOKEN_SET) { + ss->token = TOKEN_WORD; // will be a PATH_SET + ss->end--; // put ':' back on end but not beginning + } + // falls through + case TOKEN_WORD: { + if (len == 0) { + --bp; + fail (Error_Syntax(ss)); + } + + REBSTR *spelling = Intern_UTF8_Managed(bp, len); + DS_PUSH_TRASH; + Init_Any_Word( + DS_TOP, KIND_OF_WORD_FROM_TOKEN(ss->token), spelling + ); + break; } + + case TOKEN_REFINE: { + REBSTR *spelling = Intern_UTF8_Managed(bp + 1, len - 1); + DS_PUSH_TRASH; + Init_Refinement(DS_TOP, spelling); + break; } + + case TOKEN_ISSUE: + if (len == 1) { + if (bp[1] == '(') { + ss->token = TOKEN_CONSTRUCT; + fail (Error_Syntax(ss)); + } + DS_PUSH_TRASH; + Init_Blank(DS_TOP); // A single # means NONE + } + else { + DS_PUSH_TRASH; + if (ep != Scan_Issue(DS_TOP, bp + 1, len - 1)) + fail (Error_Syntax(ss)); + } + break; + + case TOKEN_BLOCK_BEGIN: + case TOKEN_GROUP_BEGIN: { + REBARR *array = Scan_Child_Array( + ss, (ss->token == TOKEN_BLOCK_BEGIN) ? ']' : ')' + ); + + ep = ss->end; + + DS_PUSH_TRASH; + Init_Any_Array( + DS_TOP, + (ss->token == TOKEN_BLOCK_BEGIN) ? REB_BLOCK : REB_GROUP, + array + ); + break; } + + case TOKEN_PATH: + break; + + case TOKEN_BLOCK_END: { + if (mode_char == ']') + goto array_done; + + if (mode_char != 0) // expected a `)` or otherwise before the `]` + fail (Error_Mismatch(ss, mode_char, ']')); - // Process each lexical token appropriately: - switch (token) { // (idea is that compiler selects computed branch) - - case TOKEN_LINE: - #ifdef TEST_SCAN - Wait_User("next..."); - #endif - line = TRUE; - scan_state->head_line = ep; - continue; - - case TOKEN_LIT: - case TOKEN_GET: - if (ep[-1] == ':') { - if (len == 1 || mode_char != '/') goto syntax_error; - len--, scan_state->end--; - } - bp++; - case TOKEN_SET: - len--; - if (mode_char == '/' && token == TOKEN_SET) { - token = TOKEN_WORD; // will be a PATH_SET - scan_state->end--; // put ':' back on end but not beginning - } - case TOKEN_WORD: - if (len == 0) {bp--; goto syntax_error;} - VAL_SET(value, (REBYTE)(REB_WORD + (token - TOKEN_WORD))); // NO_FRAME - if (!(VAL_WORD_SYM(value) = Make_Word(bp, len))) goto syntax_error; - VAL_WORD_FRAME(value) = 0; - break; - - case TOKEN_REFINE: - VAL_SET(value, REB_REFINEMENT); // NO_FRAME - if (!(VAL_WORD_SYM(value) = Make_Word(bp+1, len-1))) goto syntax_error; - break; - - case TOKEN_ISSUE: - if (len == 1) { - if (bp[1] == '(') {token = TOKEN_CONSTRUCT; goto syntax_error;} - SET_NONE(value); // A single # means NONE - } - else { - VAL_SET(value, REB_ISSUE); // NO_FRAME - if (!(VAL_WORD_SYM(value) = Scan_Issue(bp+1, len-1))) goto syntax_error; - } - break; - - case TOKEN_BLOCK: - case TOKEN_PAREN: - //line = VAL_GET_LINE(value); - block = Scan_Block(scan_state, (REBYTE)((token == TOKEN_BLOCK) ? ']' : ')')); - // (above line could have realloced emitbuf) - ep = scan_state->end; - value = BLK_TAIL(emitbuf); - if (scan_state->errors) { - *value = *BLK_LAST(block); // Copy the error - emitbuf->tail++; - goto exit_block; - } - VAL_SERIES(value) = block; - VAL_SET(value, (REBYTE)((token == TOKEN_BLOCK) ? REB_BLOCK : REB_PAREN)); - VAL_INDEX(value) = 0; - //if (line) line = FALSE, VAL_SET_LINE(value); - break; - - case TOKEN_PATH: - break; - - case TOKEN_BLOCK_END: - if (!mode_char) { mode_char = '['; goto extra_error; } - else if (mode_char != ']') goto missing_error; - else goto exit_block; - - case TOKEN_PAREN_END: - if (!mode_char) { mode_char = '('; goto extra_error; } - else if (mode_char != ')') goto missing_error; - else goto exit_block; - - case TOKEN_INTEGER: // or start of DATE + // just a stray unexpected ']' + // + fail (Error_Extra(ss, ']')); } + + case TOKEN_GROUP_END: { + if (mode_char == ')') + goto array_done; + + if (mode_char != 0) // expected a ']' or otherwise before the ')' + fail (Error_Mismatch(ss, mode_char, ')')); + + // just a stray unexpected ')' + // + fail (Error_Extra(ss, ')')); } + + case TOKEN_INTEGER: // or start of DATE if (*ep != '/' || mode_char == '/') { - if (0 == Scan_Integer(bp, len, value)) - goto syntax_error; - } - else { // A / and not in block - token = TOKEN_DATE; - while (*ep == '/' || IS_LEX_AT_LEAST_SPECIAL(*ep)) ep++; - scan_state->begin = ep; - len = (REBCNT)(ep - bp); - if (ep != Scan_Date(bp, len, value)) goto syntax_error; - } - break; - - case TOKEN_DECIMAL: - case TOKEN_PERCENT: - // Do not allow 1.2/abc: - if (*ep == '/' || !Scan_Decimal(bp, len, value, 0)) goto syntax_error; - if (bp[len-1] == '%') { - VAL_SET(value, REB_PERCENT); - VAL_DECIMAL(value) /= 100.0; - } - break; - - case TOKEN_MONEY: - // Do not allow $1/$2: - if (*ep == '/') {ep++; goto syntax_error;} - if (!Scan_Money(bp, len, value)) goto syntax_error; - break; - - case TOKEN_TIME: - if (bp[len-1] == ':' && mode_char == '/') { // could be path/10: set - if (!Scan_Integer(bp, len-1, value)) goto syntax_error; - scan_state->end--; // put ':' back on end but not beginning - break; - } - if (ep != Scan_Time(bp, len, value)) goto syntax_error; - break; - - case TOKEN_DATE: - while (*ep == '/' && mode_char != '/') { // Is it a date/time? - ep++; - while (IS_LEX_AT_LEAST_SPECIAL(*ep)) ep++; - len = (REBCNT)(ep - bp); - if (len > 50) break; // prevent inf-loop - should never be longer than this - scan_state->begin = ep; // End point extended to cover time - } - if (ep != Scan_Date(bp, len, value)) goto syntax_error; - break; - - case TOKEN_CHAR: - bp += 2; // skip #" - VAL_CHAR(value) = Scan_Char(&bp); - bp++; // skip end " - VAL_SET(value, REB_CHAR); - break; - - case TOKEN_STRING: - // During scan above, string was stored in BUF_MOLD (with Uni width) - Set_String(value, Copy_String(BUF_MOLD, 0, -1)); - LABEL_SERIES(VAL_SERIES(value), "scan string"); - break; - - case TOKEN_BINARY: - Scan_Binary(bp, len, value); - LABEL_SERIES(VAL_SERIES(value), "scan binary"); - break; - - case TOKEN_PAIR: - Scan_Pair(bp, len, value); - break; - - case TOKEN_TUPLE: - if (!Scan_Tuple(bp, len, value)) goto syntax_error; - break; - - case TOKEN_FILE: - Scan_File(bp, len, value); - LABEL_SERIES(VAL_SERIES(value), "scan file"); - break; - - case TOKEN_EMAIL: - Scan_Email(bp, len, value); - LABEL_SERIES(VAL_SERIES(value), "scan email"); - break; - - case TOKEN_URL: - Scan_URL(bp, len, value); - LABEL_SERIES(VAL_SERIES(value), "scan url"); - break; - - case TOKEN_TAG: - Scan_Any(bp+1, len-2, value, REB_TAG); - LABEL_SERIES(VAL_SERIES(value), "scan tag"); - break; - - case TOKEN_CONSTRUCT: - block = Scan_Full_Block(scan_state, ']'); - value = BLK_TAIL(emitbuf); - emitbuf->tail++; // Protect the block from GC -// if (!Construct_Simple(value, block)) { - Bind_Block(Lib_Context, BLK_HEAD(block), BIND_ALL|BIND_DEEP); - //Bind_Global_Block(BLK_HEAD(block)); - if (!Construct_Value(value, block)) { - if (IS_END(value)) Set_Block(value, block); - Trap1(RE_MALCONSTRUCT, value); - } - emitbuf->tail--; // Unprotect - break; - - case TOKEN_EOF: continue; - - default: ; - SET_NONE(value); - } - - if (line) { - line = FALSE; - VAL_SET_LINE(value); - } - - #ifdef TEST_SCAN - Print((REBYTE*)"%s - %s", Token_Names[token], Use_Buf(bp,ep)); - if (VAL_TYPE(value) >= REB_STRING && VAL_TYPE(value) <= REB_URL) - Print_Str(VAL_BIN(value)); - //Wait_User(0); - #endif - -#ifdef COMP_LINES - VAL_LINE(value)=linenum; - VAL_FLAGS(value)|=FLAGS_LINE; + DS_PUSH_TRASH; + if (ep != Scan_Integer(DS_TOP, bp, len)) + fail (Error_Syntax(ss)); + } + else { // A / and not in block + ss->token = TOKEN_DATE; + while (*ep == '/' || IS_LEX_NOT_DELIMIT(*ep)) + ++ep; + ss->begin = ep; + len = cast(REBCNT, ep - bp); + DS_PUSH_TRASH; + if (ep != Scan_Date(DS_TOP, bp, len)) + fail (Error_Syntax(ss)); + } + break; + + case TOKEN_DECIMAL: + case TOKEN_PERCENT: + // Do not allow 1.2/abc: + if (*ep == '/') + fail (Error_Syntax(ss)); + + DS_PUSH_TRASH; + if (ep != Scan_Decimal(DS_TOP, bp, len, FALSE)) + fail (Error_Syntax(ss)); + + if (bp[len - 1] == '%') { + VAL_RESET_HEADER(DS_TOP, REB_PERCENT); + VAL_DECIMAL(DS_TOP) /= 100.0; + } + break; + + case TOKEN_MONEY: + // Do not allow $1/$2: + if (*ep == '/') { + ++ep; + fail (Error_Syntax(ss)); + } + + DS_PUSH_TRASH; + if (ep != Scan_Money(DS_TOP, bp, len)) + fail (Error_Syntax(ss)); + break; + + case TOKEN_TIME: + if (bp[len-1] == ':' && mode_char == '/') { // could be path/10: set + DS_PUSH_TRASH; + if (ep - 1 != Scan_Integer(DS_TOP, bp, len - 1)) + fail (Error_Syntax(ss)); + ss->end--; // put ':' back on end but not beginning + break; + } + DS_PUSH_TRASH; + if (ep != Scan_Time(DS_TOP, bp, len)) + fail (Error_Syntax(ss)); + break; + + case TOKEN_DATE: + while (*ep == '/' && mode_char != '/') { // Is it a date/time? + ep++; + while (IS_LEX_NOT_DELIMIT(*ep)) ep++; + len = (REBCNT)(ep - bp); + if (len > 50) { + // prevent infinite loop, should never be longer than this + break; + } + ss->begin = ep; // End point extended to cover time + } + DS_PUSH_TRASH; + if (ep != Scan_Date(DS_TOP, bp, len)) + fail (Error_Syntax(ss)); + break; + + case TOKEN_CHAR: + DS_PUSH_TRASH; + bp += 2; // skip #", and subtract 1 from ep for " + if (ep - 1 != Scan_UTF8_Char_Escapable(&VAL_CHAR(DS_TOP), bp)) + fail (Error_Syntax(ss)); + VAL_RESET_HEADER(DS_TOP, REB_CHAR); + break; + + case TOKEN_STRING: { + // During scan above, string was stored in UNI_BUF (with Uni width) + // + REBSER *s = Pop_Molded_String(&mo); + DS_PUSH_TRASH; + Init_String(DS_TOP, s); + break; } + + case TOKEN_BINARY: + DS_PUSH_TRASH; + if (ep != Scan_Binary(DS_TOP, bp, len)) + fail (Error_Syntax(ss)); + break; + + case TOKEN_PAIR: + DS_PUSH_TRASH; + if (ep != Scan_Pair(DS_TOP, bp, len)) + fail (Error_Syntax(ss)); + break; + + case TOKEN_TUPLE: + DS_PUSH_TRASH; + if (ep != Scan_Tuple(DS_TOP, bp, len)) + fail (Error_Syntax(ss)); + break; + + case TOKEN_FILE: + DS_PUSH_TRASH; + if (ep != Scan_File(DS_TOP, bp, len)) + fail (Error_Syntax(ss)); + break; + + case TOKEN_EMAIL: + DS_PUSH_TRASH; + if (ep != Scan_Email(DS_TOP, bp, len)) + fail (Error_Syntax(ss)); + break; + + case TOKEN_URL: + DS_PUSH_TRASH; + if (ep != Scan_URL(DS_TOP, bp, len)) + fail (Error_Syntax(ss)); + break; + + case TOKEN_TAG: + DS_PUSH_TRASH; + + // The Scan_Any routine (only used here for tag) doesn't + // know where the tag ends, so it scans the len. + // + if (ep - 1 != Scan_Any(DS_TOP, bp + 1, len - 2, REB_TAG)) + fail (Error_Syntax(ss)); + break; + + case TOKEN_CONSTRUCT: { + REBARR *array = Scan_Full_Array(ss, ']'); + + // !!! Should the scanner be doing binding at all, and if so why + // just Lib_Context? Not binding would break functions entirely, + // but they can't round-trip anyway. See #2262. + // + Bind_Values_All_Deep(ARR_HEAD(array), Lib_Context); + + if (ARR_LEN(array) == 0 || !IS_WORD(ARR_HEAD(array))) { + DECLARE_LOCAL (temp); + Init_Block(temp, array); + fail (Error_Malconstruct_Raw(temp)); + } + + REBSYM sym = VAL_WORD_SYM(ARR_HEAD(array)); + if (IS_KIND_SYM(sym)) { + enum Reb_Kind kind = KIND_FROM_SYM(sym); + + MAKE_FUNC dispatcher = Make_Dispatch[kind]; + + if (dispatcher == NULL || ARR_LEN(array) != 2) { + DECLARE_LOCAL (temp); + Init_Block(temp, array); + fail (Error_Malconstruct_Raw(temp)); + } + + // !!! As written today, MAKE may call into the evaluator, and + // hence a GC may be triggered. Performing evaluations during + // the scanner is a questionable idea, but at the very least + // `array` must be guarded, and a data stack cell can't be + // used as the destination...because a raw pointer into the + // data stack could go bad on any DS_PUSH or DS_DROP. + // + DECLARE_LOCAL (cell); + PUSH_GUARD_ARRAY(array); + SET_UNREADABLE_BLANK(cell); + PUSH_GUARD_VALUE(cell); + + dispatcher(cell, kind, KNOWN(ARR_AT(array, 1))); // may fail() + + DS_PUSH_TRASH; + Move_Value(DS_TOP, cell); + DROP_GUARD_VALUE(cell); + DROP_GUARD_ARRAY(array); + } + else { + if (ARR_LEN(array) != 1) { + DECLARE_LOCAL (temp); + Init_Block(temp, array); + fail (Error_Malconstruct_Raw(temp)); + } + + // !!! Construction syntax allows the "type" slot to be one of + // the literals #[false], #[true]... along with legacy #[none] + // while the legacy #[unset] is no longer possible (but + // could load some kind of erroring function value) + // + switch (sym) { + #if !defined(NDEBUG) + case SYM_NONE: + // Should be under a LEGACY flag... + DS_PUSH_TRASH; + Init_Blank(DS_TOP); + break; + #endif + + case SYM_FALSE: + DS_PUSH_TRASH; + Init_Logic(DS_TOP, FALSE); + break; + + case SYM_TRUE: + DS_PUSH_TRASH; + Init_Logic(DS_TOP, TRUE); + break; + + default: { + DECLARE_LOCAL (temp); + Init_Block(temp, array); + fail (Error_Malconstruct_Raw(temp)); } + } + } + break; } // case TOKEN_CONSTRUCT + + case TOKEN_END: + continue; + + default: + panic ("Invalid TOKEN in Scanner."); + } + + if (ANY_ARRAY(DS_TOP)) { + // + // Current thinking is that only arrays will preserve file and + // line numbers, because if ANY-STRING! merges with WORD! then + // they might wind up using the ->misc and ->link fields for + // canonizing and interning like REBSTR* does. + // + REBSER *s = VAL_SERIES(DS_TOP); + s->misc.line = ss->line; + s->link.filename = ss->filename; + SET_SER_FLAG(s, SERIES_FLAG_FILE_LINE); + } + + if (line) { + line = FALSE; + SET_VAL_FLAG(DS_TOP, VALUE_FLAG_LINE); + } + + // Check for end of path: + if (mode_char == '/') { + if (*ep == '/') { + ep++; + ss->begin = ep; // skip next / + if (*ep != '(' && IS_LEX_DELIMIT(*ep)) { + ss->token = TOKEN_PATH; + fail (Error_Syntax(ss)); + } + } + else goto array_done; + } + + // Added for load/next + if (GET_FLAG(ss->opts, SCAN_ONLY) || just_once) + goto array_done; + } + + // At some point, a token for an end of block or group needed to jump to + // the array_done. If it didn't, we never got a proper closing. + // + if (mode_char == ']' || mode_char == ')') + fail (Error_Missing(ss, mode_char)); + +array_done: + if (GET_FLAG(ss->opts, SCAN_RELAX)) + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + +array_done_relax: + Drop_Mold_If_Pushed(&mo); + + REBARR *result = Pop_Stack_Values(dsp_orig); + + // All scanned code is expected to be managed by the GC (because walking + // the tree after constructing it to add the "manage GC" bit would be + // expensive, and we don't load source and free it manually anyway) + // + MANAGE_ARRAY(result); + + // In Legacy mode, it can be helpful to know if a block of code is + // loaded after legacy mode is turned on. This way, for instance a + // SWITCH can run differently based on noticing it was dispatched from + // a reference living in that legacy code. + // + // !!! Currently cued by the REFINEMENTS_BLANK option which also applies + // to functions, but should be its own independent switch. + // +#if !defined(NDEBUG) + if (LEGACY(OPTIONS_REFINEMENTS_BLANK)) + SET_SER_INFO(result, SERIES_INFO_LEGACY_DEBUG); #endif - if (VAL_TYPE(value)) emitbuf->tail++; - else { - syntax_error: - value = BLK_TAIL(emitbuf); - Scan_Error(RE_INVALID, scan_state, (REBCNT)token, bp, (REBCNT)(ep-bp), GET_FLAG(scan_state->opts, SCAN_RELAX) ? value : 0); - emitbuf->tail++; - goto exit_block; - missing_error: - scan_state->line_count = start; // where block started - scan_state->head_line = start_line; - extra_error: { - REBYTE tmp_buf[4]; // Temporary error string - tmp_buf[0] = mode_char; - tmp_buf[1] = 0; - value = BLK_TAIL(emitbuf); - Scan_Error(RE_MISSING, scan_state, (REBCNT)token, tmp_buf, 1, GET_FLAG(scan_state->opts, SCAN_RELAX) ? value : 0); - emitbuf->tail++; - goto exit_block; - } - } - - // Check for end of path: - if (mode_char == '/') { - if (*ep == '/') { - ep++; - scan_state->begin = ep; // skip next / - if (*ep != '(' && IS_LEX_DELIMIT(*ep)) { - token = TOKEN_PATH; - goto syntax_error; - } - } - else goto exit_block; - } - - // Added for load/next - if (GET_FLAG(scan_state->opts, SCAN_ONLY) || just_once) goto exit_block; - } - - if (mode_char == ']' || mode_char == ')') goto missing_error; - -exit_block: - if (line && value) VAL_SET_LINE(value); - #ifdef TEST_SCAN - Print((REBYTE*)"block of %d values ", emitbuf->tail - begin); //Wait_User("..."); - #endif - - len = emitbuf->tail; - block = Copy_Values(BLK_SKIP(emitbuf, begin), len - begin); - LABEL_SERIES(block, "scan block"); - SERIES_SET_FLAG(block, SER_MON); - emitbuf->tail = begin; -//!!!! if (value) VAL_OPTS(BLK_TAIL(block)) = VAL_OPTS(value); // save NEWLINE marker - - return block; + + return result; } -/*********************************************************************** -** -*/ REBSER *Scan_Full_Block(SCAN_STATE *scan_state, REBYTE mode_char) -/* -** Simple variation of scan_block to avoid problem with -** construct of aggregate values. -** -***********************************************************************/ +// +// Scan_Child_Array: C +// +// This routine would create a new structure on the scanning stack. Putting +// what would be local variables for each level into a structure helps with +// reflection, allowing for better introspection and error messages. (This +// is similar to the benefits of Reb_Frame.) +// +static REBARR *Scan_Child_Array(SCAN_STATE *ss, REBYTE mode_char) { - REBFLG only = GET_FLAG(scan_state->opts, SCAN_ONLY); - REBSER *ser; - CLR_FLAG(scan_state->opts, SCAN_ONLY); - ser = Scan_Block(scan_state, mode_char); - if (only) SET_FLAG(scan_state->opts, SCAN_ONLY); - return ser; -} + SCAN_STATE child = *ss; + // Capture current line and head of line into the starting points, because + // some errors wish to report the start of the array's location. + // + child.start_line = ss->line; + child.start_line_head = ss->line_head; -/*********************************************************************** -** -*/ REBSER *Scan_Code(SCAN_STATE *scan_state, REBYTE mode_char) -/* -** Scan source code, given a scan state. Allows scan of source -** code a section at a time (used for LOAD/next). -** -** Note: Renamed this from Scan_Trap (a bad name, no trap used) -** -***********************************************************************/ -{ -// REBSER *ser; + REBARR *result = Scan_Array(&child, mode_char); - BLK_RESET(BUF_EMIT); // Prevents growth (when errors are thrown) - return Scan_Block(scan_state, mode_char); -// Set_Block(Temp_Scan_Value, ser); -// return Temp_Scan_Value; -} + // The only variables that should actually be written back into the + // parent ss are those reflecting an update in the "feed" of + // data. Here's a quick hack while the shape of that is discovered. + REBCNT line_count = ss->line; + const REBYTE *line_head = ss->line_head; + enum Reb_Token token = ss->token; -/*********************************************************************** -** -*/ REBSER *Scan_Source(REBYTE *src, REBCNT len) -/* -** Scan source code. Scan state initialized. No header required. -** If len = 0, then use the C string terminated length. -** -***********************************************************************/ -{ - SCAN_STATE scan_state; + *ss = child; + + ss->line = line_count; + ss->line_head = line_head; + ss->token = token; - Check_Stack(); - if (!len) len = LEN_BYTES(src); - Init_Scan_State(&scan_state, src, len); - return Scan_Code(&scan_state, 0); + return result; } -/*********************************************************************** -** -*/ REBINT Scan_Header(REBYTE *src, REBCNT len) -/* -** Scan for header, return its offset if found or -1 if not. -** -***********************************************************************/ +// +// Scan_Full_Array: C +// +// Simple variation of scan_block to avoid problem with +// construct of aggregate values. +// +static REBARR *Scan_Full_Array(SCAN_STATE *ss, REBYTE mode_char) { - SCAN_STATE scan_state; - REBYTE *cp; - REBINT result; - - // Must be UTF8 byte-stream: - Init_Scan_State(&scan_state, src, len); - result = Scan_Head(&scan_state); - if (!result) return -1; - - cp = scan_state.begin-2; - // Backup to start of it: - if (result > 0) { // normal header found - while (cp != src && *cp != 'r' && *cp != 'R') cp--; - } else { - while (cp != src && *cp != '[') cp--; - } - return (REBINT)(cp - src); + REBOOL saved_only = GET_FLAG(ss->opts, SCAN_ONLY); + CLR_FLAG(ss->opts, SCAN_ONLY); + + REBARR *array = Scan_Child_Array(ss, mode_char); + + if (saved_only) SET_FLAG(ss->opts, SCAN_ONLY); + return array; } -/*********************************************************************** -** -*/ void Init_Scanner(void) -/* -***********************************************************************/ +// +// Scan_UTF8_Managed: C +// +// Scan source code. Scan state initialized. No header required. +// +REBARR *Scan_UTF8_Managed(const REBYTE *utf8, REBCNT len, REBSTR *filename) { - Set_Root_Series(TASK_BUF_EMIT, Make_Block(511), "emit block"); - Set_Root_Series(TASK_BUF_UTF8, Make_Unicode(1020), "utf8 buffer"); + SCAN_STATE ss; + const REBUPT start_line = 1; + Init_Scan_State(&ss, utf8, len, filename, start_line); + return Scan_Array(&ss, 0); } -/*********************************************************************** -** -*/ REBNATIVE(transcode) -/* -** Allows BINARY! input only! -** -***********************************************************************/ +// +// Scan_Header: C +// +// Scan for header, return its offset if found or -1 if not. +// +REBINT Scan_Header(const REBYTE *utf8, REBCNT len) { - REBSER *blk; - SCAN_STATE scan_state; + SCAN_STATE ss; + REBSTR * const filename = Canon(SYM___ANONYMOUS__); + const REBUPT start_line = 1; + Init_Scan_State(&ss, utf8, len, filename, start_line); + + REBINT result = Scan_Head(&ss); + if (result == 0) + return -1; + + const REBYTE *cp = ss.begin - 2; + + // Backup to start of it: + if (result > 0) { // normal header found + while (cp != utf8 && *cp != 'r' && *cp != 'R') + --cp; + } else { + while (cp != utf8 && *cp != '[') + --cp; + } + return cast(REBINT, cp - utf8); +} - Init_Scan_State(&scan_state, VAL_BIN_DATA(D_ARG(1)), VAL_LEN(D_ARG(1))); - if (D_REF(2)) SET_FLAG(scan_state.opts, SCAN_NEXT); - if (D_REF(3)) SET_FLAG(scan_state.opts, SCAN_ONLY); - if (D_REF(4)) SET_FLAG(scan_state.opts, SCAN_RELAX); +// +// Startup_Scanner: C +// +void Startup_Scanner(void) +{ + REBCNT n = 0; + while (Token_Names[n] != NULL) + ++n; + assert(cast(enum Reb_Token, n) == TOKEN_MAX); - blk = Scan_Code(&scan_state, 0); - DS_RELOAD(ds); // in case stack moved - Set_Block(D_RET, blk); + Init_String(TASK_BUF_UTF8, Make_Unicode(1020)); +} - VAL_INDEX(D_ARG(1)) = scan_state.end - VAL_BIN(D_ARG(1)); - Append_Val(blk, D_ARG(1)); - return R_RET; +// +// Shutdown_Scanner: C +// +void Shutdown_Scanner(void) +{ + // Note: Emit and UTF8 buffers freed by task root set } -/*********************************************************************** -** -*/ REBCNT Scan_Word(REBYTE *cp, REBCNT len) -/* -** Scan word chars and make word symbol for it. -** This method gets exactly the same results as scanner. -** Returns symbol number, or zero for errors. -** -***********************************************************************/ +// +// transcode: native [ +// +// {Translates UTF-8 binary source to values. Returns [value binary].} +// +// source [binary!] +// "Must be Unicode UTF-8 encoded" +// /next +// {Translate next complete value (blocks as single value)} +// /only +// "Translate only a single value (blocks dissected)" +// /relax +// {Do not cause errors - return error object as value in place} +// /file +// file-name [file! url!] +// /line +// line-number [integer!] +// ] +// +REBNATIVE(transcode) { - SCAN_STATE scan_state; - - Init_Scan_State(&scan_state, cp, len); + INCLUDE_PARAMS_OF_TRANSCODE; + + REBSTR *filename; + if (REF(file)) { + // + // The file string may be mutable, so we wouldn't want to store it + // persistently as-is. Consider: + // + // file: copy %test + // x: transcode/file data1 file + // append file "-2" + // y: transcode/file data2 file + // + // You would not want the change of `file` to affect the filename + // references in x's loaded source. So the series shouldn't be used + // directly, and as long as another reference is needed, use an + // interned one (the same mechanic words use). Since the source + // filename may be a wide string it is converted to UTF-8 first. + // + // !!! Should the base name and extension be stored, or whole path? + // + REBCNT index = VAL_INDEX(ARG(file_name)); + REBCNT len = VAL_LEN_AT(ARG(file_name)); + REBSER *temp = Temp_Bin_Str_Managed(ARG(file_name), &index, &len); + filename = Intern_UTF8_Managed(BIN_AT(temp, index), len); + } + else + filename = Canon(SYM___ANONYMOUS__); + + REBUPT start_line = 1; + if (REF(line)) { + start_line = VAL_INT32(ARG(line_number)); + if (start_line <= 0) + fail (ARG(line_number)); + } + else + start_line = 1; + + SCAN_STATE ss; + Init_Scan_State( + &ss, + VAL_BIN_AT(ARG(source)), + VAL_LEN_AT(ARG(source)), + filename, + start_line + ); + + if (REF(next)) + SET_FLAG(ss.opts, SCAN_NEXT); + if (REF(only)) + SET_FLAG(ss.opts, SCAN_ONLY); + if (REF(relax)) + SET_FLAG(ss.opts, SCAN_RELAX); + + // The scanner always returns an "array" series. So set the result + // to a BLOCK! of the results. + // + // If the source data bytes are "1" then it will be the block [1] + // if the source data is "[1]" then it will be the block [[1]] + + Init_Block(D_OUT, Scan_Array(&ss, 0)); + + // Add a value to the tail of the result, representing the input + // with position advanced past the content consumed by the scan. + // (Returning a length 2 block is how TRANSCODE does a "multiple + // return value, but #1916 discusses a possible "revamp" of this.) + + VAL_INDEX(ARG(source)) = ss.end - VAL_BIN(ARG(source)); + Append_Value(VAL_ARRAY(D_OUT), ARG(source)); + + return R_OUT; +} - if (TOKEN_WORD == Scan_Token(&scan_state)) return Make_Word(cp, len); - return 0; +// +// Scan_Any_Word: C +// +// Scan word chars and make word symbol for it. +// This method gets exactly the same results as scanner. +// Returns symbol number, or zero for errors. +// +const REBYTE *Scan_Any_Word( + REBVAL *out, + enum Reb_Kind kind, + const REBYTE *utf8, + REBCNT len +) { + SCAN_STATE ss; + REBSTR * const filename = Canon(SYM___ANONYMOUS__); + const REBUPT start_line = 1; + Init_Scan_State(&ss, utf8, len, filename, start_line); + + REB_MOLD mo; + CLEARS(&mo); + + Locate_Token_May_Push_Mold(&mo, &ss); + if (ss.token != TOKEN_WORD) + return NULL; + + Init_Any_Word(out, kind, Intern_UTF8_Managed(utf8, len)); + Drop_Mold_If_Pushed(&mo); + return ss.begin; // !!! is this right? } -/*********************************************************************** -** -*/ REBCNT Scan_Issue(REBYTE *cp, REBCNT len) -/* -** Scan an issue word, allowing special characters. -** -***********************************************************************/ +// +// Scan_Issue: C +// +// Scan an issue word, allowing special characters. +// +const REBYTE *Scan_Issue(REBVAL *out, const REBYTE *cp, REBCNT len) { - REBYTE *bp; - REBCNT l = len; - REBCNT c; + if (len == 0) return NULL; // will trigger error - if (len == 0) return 0; while (IS_LEX_SPACE(*cp)) cp++; /* skip white space */ - bp = cp; + const REBYTE *bp = cp; + REBCNT l = len; while (l > 0) { switch (GET_LEX_CLASS(*bp)) { case LEX_CLASS_DELIMIT: - return 0; - - case LEX_CLASS_SPECIAL: /* Flag all but first special char: */ - c = GET_LEX_VALUE(*bp); - if (!(LEX_SPECIAL_TICK == c - || LEX_SPECIAL_COMMA == c - || LEX_SPECIAL_PERIOD == c - || LEX_SPECIAL_PLUS == c - || LEX_SPECIAL_MINUS == c - || LEX_SPECIAL_TILDE == c - )) - return 0; - + return NULL; // will trigger error + + case LEX_CLASS_SPECIAL: { // Flag all but first special char + REBCNT c = GET_LEX_VALUE(*bp); + if (!(LEX_SPECIAL_APOSTROPHE == c + || LEX_SPECIAL_COMMA == c + || LEX_SPECIAL_PERIOD == c + || LEX_SPECIAL_PLUS == c + || LEX_SPECIAL_MINUS == c + || LEX_SPECIAL_TILDE == c + || LEX_SPECIAL_BAR == c + || LEX_SPECIAL_BLANK == c + )) { + return NULL; // will trigger error + }} + // fallthrough case LEX_CLASS_WORD: case LEX_CLASS_NUMBER: bp++; - l--; + l--; break; } } - return Make_Word(cp, len); + REBSTR *str = Intern_UTF8_Managed(cp, len); + Init_Issue(out, str); + return bp; } diff --git a/src/core/l-types.c b/src/core/l-types.c index 4436f13e18..2771e2d2c7 100644 --- a/src/core/l-types.c +++ b/src/core/l-types.c @@ -1,1098 +1,1351 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: l-types.c -** Summary: special lexical type converters -** Section: lexical -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %l-types.c +// Summary: "special lexical type converters" +// Section: lexical +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -#include "sys-scan.h" #include "sys-deci-funcs.h" #include "sys-dec-to-char.h" #include -typedef REBFLG (*MAKE_FUNC)(REBVAL *, REBVAL *, REBCNT); -#include "tmp-maketypes.h" - - -/*********************************************************************** -** -*/ REBYTE *Scan_Hex(REBYTE *cp, REBI64 *num, REBCNT minlen, REBCNT maxlen) -/* -** Scans hex while it is valid and does not exceed the maxlen. -** If the hex string is longer than maxlen - it's an error. -** If a bad char is found less than the minlen - it's an error. -** String must not include # - ~ or other invalid chars. -** If minlen is zero, and no string, that's a valid zero value. -** -** Note, this function relies on LEX_WORD lex values having a LEX_VALUE -** field of zero, except for hex values. -** -***********************************************************************/ -{ - REBYTE lex; - REBYTE v; - REBI64 n = 0; - REBCNT cnt = 0; - - if (maxlen > MAX_HEX_LEN) return 0; - while ((lex = Lex_Map[*cp]) > LEX_WORD) { - if (++cnt > maxlen) return 0; - v = (REBYTE)(lex & LEX_VALUE); /* char num encoded into lex */ - if (!v && lex < LEX_NUMBER) return 0; /* invalid char (word but no val) */ - n = (n << 4) + v; - cp++; - } - - if (cnt < minlen) return 0; - *num = n; - return cp; -} +// +// The scanning code in R3-Alpha used NULL to return failure during the scan +// of a value, possibly leaving the value itself in an incomplete or invalid +// state. Rather than write stray incomplete values into these spots, Ren-C +// puts "unreadable blank" +// -/*********************************************************************** -** -*/ REBOOL Scan_Hex2(REBYTE *bp, REBUNI *n, REBFLG uni) -/* -** Decode a %xx hex encoded byte into a char. -** -** The % should already be removed before calling this. -** -** We don't allow a %00 in files, urls, email, etc... so -** a return of 0 is used to indicate an error. -** -***********************************************************************/ -{ - REBUNI c1, c2; - REBYTE d1, d2; - REBYTE lex; - - if (uni) { - REBUNI *up = (REBUNI*)bp; - c1 = up[0]; - c2 = up[1]; - } else { - c1 = bp[0]; - c2 = bp[1]; - } - - lex = Lex_Map[c1]; - d1 = lex & LEX_VALUE; - if (lex < LEX_WORD || (!d1 && lex < LEX_NUMBER)) return FALSE; - - lex = Lex_Map[c2]; - d2 = lex & LEX_VALUE; - if (lex < LEX_WORD || (!d2 && lex < LEX_NUMBER)) return FALSE; +#define return_NULL \ + do { SET_UNREADABLE_BLANK(out); return NULL; } while (TRUE) - *n = (REBUNI)((d1 << 4) + d2); - return TRUE; +// +// MAKE_Fail: C +// +void MAKE_Fail(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) +{ + UNUSED(out); + UNUSED(kind); + UNUSED(arg); + + fail ("Datatype does not have a MAKE handler registered"); } -/*********************************************************************** -** -*/ REBINT Scan_Hex_Bytes(REBVAL *val, REBCNT maxlen, REBYTE *out) -/* -** Low level conversion of hex chars into binary bytes. -** Returns the number of bytes in binary. -** -***********************************************************************/ +// +// make: native [ +// +// {Constructs or allocates the specified datatype.} +// +// return: [any-value!] +// {Constructed value.} +// type [any-value!] +// {The datatype -or- an examplar value of the type to construct} +// def [any-value!] +// {Definition or size of the new value (binding may be modified)} +// ] +// +REBNATIVE(make) { - REBYTE b, n = 0; - REBCNT cnt; - REBYTE lex; - REBCNT len; - REBUNI c; - REBYTE *start = out; - - len = VAL_LEN(val); - if (len > maxlen) return 0; - - for (cnt = 0; cnt < len; cnt++) { - c = GET_ANY_CHAR(VAL_SERIES(val), VAL_INDEX(val)+cnt); - if (c > 127) return 0; - lex = Lex_Map[c]; - b = (REBYTE)(lex & LEX_VALUE); /* char num encoded into lex */ - if (!b && lex < LEX_NUMBER) return 0; /* invalid char (word but no val) */ - if ((cnt + len) & 1) *out++ = (n << 4) + b; // cnt + len deals with odd # of chars - else n = b & 15; - } - - return (out - start); + INCLUDE_PARAMS_OF_MAKE; + + REBVAL *type = ARG(type); + REBVAL *arg = ARG(def); + +#if !defined(NDEBUG) + if (IS_GOB(type)) { + // + // !!! It appears that GOBs had some kind of inheritance mechanism, by + // which you would write: + // + // gob1: make gob! [...] + // gob2: make gob1 [...] + // + // The new plan is that MAKE operates on a definition spec, and that + // this type slot is always a value or exemplar. So if the feature + // is needed, it should be something like: + // + // gob1: make gob! [...] + // gob2: make gob! [gob1 ...] + // + // Or perhaps not use make at all, but some other operation. + // + assert(FALSE); + } + else if (IS_EVENT(type)) { + assert(FALSE); // ^-- same for events (?) + } +#endif + + enum Reb_Kind kind; + if (IS_DATATYPE(type)) + kind = VAL_TYPE_KIND(type); + else + kind = VAL_TYPE(type); + + MAKE_FUNC dispatcher = Make_Dispatch[kind]; + if (dispatcher == NULL) + fail (Error_Bad_Make(kind, arg)); + + if (IS_VARARGS(arg)) { + // + // Converting a VARARGS! to an ANY-ARRAY! involves spooling those + // varargs to the end and making an array out of that. It's not known + // how many elements that will be, so they're gathered to the data + // stack to find the size, then an array made. Note that | will stop + // varargs gathering. + // + // !!! MAKE should likely not be allowed to THROW in the general + // case--especially if it is the implementation of construction + // syntax (arbitrary code should not run during LOAD). Since + // vararg spooling may involve evaluation (e.g. to create an array) + // it may be a poor fit for the MAKE umbrella. + // + // Temporarily putting the code here so that the make dispatchers + // do not have to bubble up throws, but it is likely that this + // should not have been a MAKE operation in the first place. + // + // !!! This MAKE will be destructive to its input (the varargs will + // be fetched and exhausted). That's not necessarily obvious, but + // with a TO conversion it would be even less obvious... + // + if (dispatcher != &MAKE_Array) + fail (Error_Bad_Make(kind, arg)); + + // If there's any chance that the argument could produce voids, we + // can't guarantee an array can be made out of it. + // + if (arg->extra.binding == NULL) { + // + // A vararg created from a block AND never passed as an argument + // so no typeset or quoting settings available. Can't produce + // any voids, because the data source is a block. + // + assert( + NOT_SER_FLAG( + arg->payload.varargs.feed, ARRAY_FLAG_VARLIST + ) + ); + } + else { + REBCTX *context = CTX(arg->extra.binding); + REBFRM *param_frame = CTX_FRAME_IF_ON_STACK(context); + + // If the VARARGS! has a call frame, then ensure that the call + // frame where the VARARGS! originated is still on the stack. + // + if (param_frame == NULL) + fail (Error_Varargs_No_Stack_Raw()); + + REBVAL *param = FUNC_FACADE_HEAD(param_frame->phase) + + arg->payload.varargs.param_offset; + + if (TYPE_CHECK(param, REB_MAX_VOID)) + fail (Error_Void_Vararg_Array_Raw()); + } + + REBDSP dsp_orig = DSP; + + do { + REB_R r = Do_Vararg_Op_May_Throw(D_OUT, arg, VARARG_OP_TAKE); + + if (r == R_OUT_IS_THROWN) { + DS_DROP_TO(dsp_orig); + return R_OUT_IS_THROWN; + } + if (r == R_VOID) + break; + assert(r == R_OUT); + + DS_PUSH(D_OUT); + SET_END(D_OUT); // expected by Do_Vararg_Op + } while (TRUE); + + Init_Any_Array(D_OUT, kind, Pop_Stack_Values(dsp_orig)); + return R_OUT; + } + + dispatcher(D_OUT, kind, arg); // may fail() + return R_OUT; } -/*********************************************************************** -** -*/ REBCNT Scan_Hex_Value(void *src, REBCNT len, REBOOL uni) -/* -** Given a string, scan it as hex. Chars can be 8 or 16 bit. -** Result is 32 bits max. -** Throw errors. -** -***********************************************************************/ +// +// TO_Fail: C +// +void TO_Fail(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBUNI c; - REBCNT n; - REBYTE lex; - REBCNT num = 0; - - if (len > 8) goto bad_hex; - - for (n = 0; n < len; n++) { - - c = (REBUNI)(uni ? ((REBUNI*)src)[n] : ((REBYTE*)src)[n]); - - if (c > 255) goto bad_hex; - - lex = Lex_Map[c]; - if (lex > LEX_WORD) { - c = lex & LEX_VALUE; - if (!c && lex < LEX_NUMBER) goto bad_hex; - num = (num << 4) + c; - } - else { -bad_hex: Trap0(RE_INVALID_CHARS); - } - } - return num; + UNUSED(out); + UNUSED(kind); + UNUSED(arg); + + fail ("Datatype does not have a TO handler registered"); } -/*********************************************************************** -** -*/ REBYTE *Scan_Dec_Buf(REBYTE *cp, REBCNT len, REBYTE *buf) -/* -** Validate a decimal number. Return on first invalid char -** (or end). Return zero if not valid. -** -** len: max size of buffer (must be MAX_NUM_LEN or larger). -** -** Scan is valid for 1 1.2 1,2 1'234.5 1x 1.2x 1% 1.2% etc. -** -***********************************************************************/ +// +// to: native [ +// +// {Converts to a specified datatype.} +// +// type [any-value!] +// {The datatype -or- an exemplar value of the target type} +// value [any-value!] +// {The source value to convert} +// ] +// +REBNATIVE(to) { - REBYTE *bp = buf; - REBYTE *be = bp + len - 1; - REBOOL dig = FALSE; /* flag that a digit was present */ - - if (*cp == '+' || *cp == '-') *bp++ = *cp++; - while (IS_LEX_NUMBER(*cp) || *cp == '\'') - if (*cp != '\'') { - *bp++ = *cp++; - if (bp >= be) return 0; - dig=1; - } - else cp++; - if (*cp == ',' || *cp == '.') cp++; - *bp++ = '.'; - if (bp >= be) return 0; - while (IS_LEX_NUMBER(*cp) || *cp == '\'') - if (*cp != '\'') { - *bp++ = *cp++; - if (bp >= be) return 0; - dig=1; - } - else cp++; - if (!dig) return 0; - if (*cp == 'E' || *cp == 'e') { - *bp++ = *cp++; - if (bp >= be) return 0; - dig = 0; - if (*cp == '-' || *cp == '+') { - *bp++ = *cp++; - if (bp >= be) return 0; - } - while (IS_LEX_NUMBER(*cp)) { - *bp++ = *cp++; - if (bp >= be) return 0; - dig=1; - } - if (!dig) return 0; - } - *bp = 0; - return cp; + INCLUDE_PARAMS_OF_TO; + + REBVAL *type = ARG(type); + REBVAL *arg = ARG(value); + + enum Reb_Kind kind; + if (IS_DATATYPE(type)) + kind = VAL_TYPE_KIND(type); + else + kind = VAL_TYPE(type); + + // !!! The only thing you can TO convert a blank into is a BLANK!. This + // allows one to sort of opt-out: + // + // kind: get-kind-maybe-blank x y z + // if blank? converted: to kind value [...] + // + // Is this a good rule, or should types be able to have a custom behavior + // for the TO of a blank conversion into them? + // + if (IS_BLANK(arg)) { + if (kind == REB_BLANK) + return R_BLANK; + fail (arg); + } + + TO_FUNC dispatcher = To_Dispatch[kind]; + if (dispatcher == NULL) + fail (arg); + + dispatcher(D_OUT, kind, arg); // may fail(); + return R_OUT; } -/*********************************************************************** -** -*/ REBYTE *Scan_Decimal(REBYTE *cp, REBCNT len, REBVAL *value, REBFLG dec_only) -/* -** Scan and convert a decimal value. Return zero if error. -** -***********************************************************************/ -{ - REBYTE *bp = cp; - REBYTE buf[MAX_NUM_LEN+4]; - REBYTE *ep = buf; - REBOOL dig = FALSE; /* flag that a digit was present */ - char *se; - - if (len > MAX_NUM_LEN) return 0; - - if (*cp == '+' || *cp == '-') *ep++ = *cp++; - while (IS_LEX_NUMBER(*cp) || *cp == '\'') - if (*cp != '\'') *ep++ = *cp++, dig=1; - else cp++; - if (*cp == ',' || *cp == '.') cp++; - *ep++ = '.'; - while (IS_LEX_NUMBER(*cp) || *cp == '\'') - if (*cp != '\'') *ep++ = *cp++, dig=1; - else cp++; - if (!dig) return 0; - if (*cp == 'E' || *cp == 'e') { - *ep++ = *cp++; - dig = 0; - if (*cp == '-' || *cp == '+') *ep++ = *cp++; - while (IS_LEX_NUMBER(*cp)) *ep++ = *cp++, dig=1; - if (!dig) return 0; - } - if (*cp == '%') { - if (dec_only) return 0; - cp++; // ignore it - } - *ep = 0; - - if ((REBCNT)(cp-bp) != len) return 0; - - VAL_SET(value, REB_DECIMAL); - VAL_DECIMAL(value) = STRTOD((char *)buf, &se); // need check for NaN, and INF !!! - if (fabs(VAL_DECIMAL(value)) == HUGE_VAL) Trap0(RE_OVERFLOW); - return cp; +// +// Scan_Hex: C +// +// Scans hex while it is valid and does not exceed the maxlen. +// If the hex string is longer than maxlen - it's an error. +// If a bad char is found less than the minlen - it's an error. +// String must not include # - ~ or other invalid chars. +// If minlen is zero, and no string, that's a valid zero value. +// +// Note, this function relies on LEX_WORD lex values having a LEX_VALUE +// field of zero, except for hex values. +// +const REBYTE *Scan_Hex( + REBVAL *out, + const REBYTE *cp, + REBCNT minlen, + REBCNT maxlen +) { + TRASH_CELL_IF_DEBUG(out); + + if (maxlen > MAX_HEX_LEN) + return_NULL; + + REBI64 i = 0; + REBCNT cnt = 0; + REBYTE lex; + while ((lex = Lex_Map[*cp]) > LEX_WORD) { + REBYTE v; + if (++cnt > maxlen) + return_NULL; + v = cast(REBYTE, lex & LEX_VALUE); // char num encoded into lex + if (!v && lex < LEX_NUMBER) + return_NULL; // invalid char (word but no val) + i = (i << 4) + v; + cp++; + } + + if (cnt < minlen) + return_NULL; + + Init_Integer(out, i); + return cp; } -/*********************************************************************** -** -*/ REBYTE *Scan_Integer(REBYTE *cp, REBCNT len, REBVAL *value) -/* -** Scan and convert an integer value. Return zero if error. -** Allow preceding + - and any combination of ' marks. -** -***********************************************************************/ +// +// Scan_Hex2: C +// +// Decode a %xx hex encoded byte into a char. +// +// The % should already be removed before calling this. +// +// We don't allow a %00 in files, urls, email, etc... so +// a return of 0 is used to indicate an error. +// +REBOOL Scan_Hex2(const REBYTE *bp, REBUNI *n, REBOOL unicode) { - REBINT num = (REBINT)len; - REBYTE buf[MAX_NUM_LEN+4]; - REBYTE *bp; - REBI64 n; - REBOOL neg = FALSE; - - // Super-fast conversion of zero and one (most common cases): - if (num == 1) { - if (*cp == '0') {SET_INTEGER(value, 0); return cp+1;} - if (*cp == '1') {SET_INTEGER(value, 1); return cp+1;} - } - - if (len > MAX_NUM_LEN) return 0; // prevent buffer overflow - len = 0; - bp = buf; - - // Strip leading signs: - if (*cp == '-') *bp++ = *cp++, num--, neg = TRUE; - else if (*cp == '+') cp++, num--; - - // Remove leading zeros: - for (; num > 0; num--) { - if (*cp == '0' || *cp == '\'') cp++; - else break; - } - - // Copy all digits, except ' : - for (; num > 0; num--) { - if (*cp >= '0' && *cp <= '9') *bp++ = *cp++; - else if (*cp == '\'') cp++; - else return 0; - } - *bp = 0; - - // Too many digits? - len = bp - &buf[0]; - if (neg) len--; - if (len > 19) return 0; - - // Convert, check, and return: - n = CHR_TO_INT(buf); - if ((n > 0 && neg) || (n < 0 && !neg)) return 0; - SET_INTEGER(value, n); - return cp; -} + REBUNI c1, c2; + REBYTE d1, d2; + REBYTE lex; + + if (unicode) { + const REBUNI *up = cast(const REBUNI*, bp); + c1 = up[0]; + c2 = up[1]; + } else { + c1 = bp[0]; + c2 = bp[1]; + } + + lex = Lex_Map[c1]; + d1 = lex & LEX_VALUE; + if (lex < LEX_WORD || (!d1 && lex < LEX_NUMBER)) return FALSE; + + lex = Lex_Map[c2]; + d2 = lex & LEX_VALUE; + if (lex < LEX_WORD || (!d2 && lex < LEX_NUMBER)) return FALSE; + *n = (REBUNI)((d1 << 4) + d2); -/*********************************************************************** -** -*/ REBYTE *Scan_Money(REBYTE *cp, REBCNT len, REBVAL *value) -/* -** Scan and convert money. Return zero if error. -** -***********************************************************************/ -{ - REBYTE *end; - - if (*cp == '$') cp++, len--; - if (len == 0) return 0; - VAL_DECI(value) = string_to_deci(cp, &end); - if (end != cp + len) return 0; - VAL_SET(value, REB_MONEY); - - return end; - -#ifdef ndef - REBYTE *bp = cp; - REBYTE buf[MAX_NUM_LEN+8]; - REBYTE *ep = buf; - REBCNT n = 0; - REBOOL dig = FALSE; - - if (*cp == '+') cp++; - else if (*cp == '-') *ep++ = *cp++; - - if (*cp != '$') { - for (; Upper_Case[*cp] >= 'A' && Upper_Case[*cp] <= 'Z' && n < 3; cp++, n++) { - VAL_MONEY_DENOM(value)[n] = Upper_Case[*cp]; - } - if (*cp != '$' || n > 3) return 0; - VAL_MONEY_DENOM(value)[n] = 0; - } else VAL_MONEY_DENOM(value)[0] = 0; - cp++; - - while (ep < buf+MAX_NUM_LEN && (IS_LEX_NUMBER(*cp) || *cp == '\'')) - if (*cp != '\'') *ep++ = *cp++, dig=1; - else cp++; - if (*cp == ',' || *cp == '.') cp++; - *ep++ = '.'; - while (ep < buf+MAX_NUM_LEN && (IS_LEX_NUMBER(*cp) || *cp == '\'')) - if (*cp != '\'') *ep++ = *cp++, dig=1; - else cp++; - if (!dig) return 0; - if (ep >= buf+MAX_NUM_LEN) return 0; - *ep = 0; - - if ((REBCNT)(cp-bp) != len) return 0; - VAL_SET(value, REB_MONEY); - VAL_MONEY_AMOUNT(value) = atof((char*)(&buf[0])); - if (fabs(VAL_MONEY_AMOUNT(value)) == HUGE_VAL) Trap0(RE_OVERFLOW); - return cp; -#endif + return TRUE; } -/*********************************************************************** -** -*/ REBYTE *Scan_Date(REBYTE *cp, REBCNT len, REBVAL *value) -/* -** Scan and convert a date. Also can include a time and zone. -** -***********************************************************************/ +// +// Scan_Hex_Value: C +// +// Given a string, scan it as hex. Chars can be 8 or 16 bit. +// Result is 32 bits max. +// Throw errors. +// +REBCNT Scan_Hex_Value(const void *p, REBCNT len, REBOOL unicode) { - REBYTE *ep; - REBYTE *end = cp + len; - REBINT num; - REBINT day = 0; - REBINT month; - REBINT year; - REBINT tz = 0; - REBYTE sep; - REBCNT size; - - // Skip spaces: - for (; *cp == ' ' && cp != end; cp++); - - // Skip day name, comma, and spaces: - for (ep = cp; *ep != ',' && ep != end; ep++); - if (ep != end) { - cp = ep + 1; - while (*cp == ' ' && cp != end) cp++; - } - if (cp == end) return 0; - - // Day or 4-digit year: - ep = Grab_Int(cp, &num); - if (num < 0) return 0; - size = (REBCNT)(ep - cp); - if (size >= 4) year = num; - else if (size) day = num; - else return 0; - cp = ep; - - // Determine field separator: - if (*cp != '/' && *cp != '-' && *cp != '.' && *cp != ' ') return 0; - sep = *cp++; - - // Month as number or name: - ep = Grab_Int(cp, &num); - if (num < 0) return 0; - size = (REBCNT)(ep - cp); - if (size > 0) month = num; // got a number - else { // must be a word - for (ep = cp; IS_LEX_WORD(*ep); ep++); // scan word - size = (REBCNT)(ep - cp); - if (size < 3) return 0; - for (num = 0; num < 12; num++) { - if (!Compare_Bytes((REBYTE *)(Month_Names[num]), cp, size, TRUE)) break; - } - month = num + 1; - } - if (month < 1 || month > 12) return 0; - cp = ep; - if (*cp++ != sep) return 0; - - // Year or day (if year was first): - ep = Grab_Int(cp, &num); - if (*cp == '-' || num < 0) return 0; - size = (REBCNT)(ep - cp); - if (!size) return 0; - if (!day) day = num; - else { // it is a year - // Allow shorthand form (e.g. /96) ranging +49,-51 years - // (so in year 2050 a 0 -> 2000 not 2100) - if (size >= 3) year = num; - else { - year = (Current_Year / 100) * 100 + num; - if (year - Current_Year > 50) year -=100; - else if (year - Current_Year < -50) year += 100; - } - } - if (year > MAX_YEAR || day < 1 || day > (REBINT)(Month_Lengths[month-1])) return 0; - // Check February for leap year or century: - if (month == 2 && day == 29) { - if (((year % 4) != 0) || // not leap year - ((year % 100) == 0 && // century? - (year % 400) != 0)) return 0; // not leap century - } - - cp = ep; - VAL_TIME(value) = NO_TIME; - if (cp >= end) goto end_date; - - if (*cp == '/' || *cp == ' ') { - sep = *cp++; - if (cp >= end) goto end_date; - cp = Scan_Time(cp, 0, value); - if (!IS_TIME(value) || (VAL_TIME(value) < 0) || (VAL_TIME(value) >= TIME_SEC(24 * 60 * 60))) - return 0; - } - - if (*cp == sep) cp++; - - // Time zone can be 12:30 or 1230 (optional hour indicator) - if (*cp == '-' || *cp == '+') { - if (cp >= end) goto end_date; - ep = Grab_Int(cp+1, &num); - if (ep-cp == 0) return 0; - if (*ep != ':') { - int h, m; - if (num < -1500 || num > 1500) return 0; - h = (num / 100); - m = (num - (h * 100)); - tz = (h * 60 + m) / ZONE_MINS; - } else { - if (num < -15 || num > 15) return 0; - tz = num * (60/ZONE_MINS); - if (*ep == ':') { - ep = Grab_Int(ep+1, &num); - if (num % ZONE_MINS != 0) return 0; - tz += num / ZONE_MINS; - } - } - if (ep != end) return 0; - if (*cp == '-') tz = -tz; - cp = ep; - } -end_date: - Set_Date_UTC(value, year, month, day, VAL_TIME(value), tz); - return cp; + REBUNI c; + REBCNT n; + REBYTE lex; + REBCNT num = 0; + const REBYTE *bp = unicode ? NULL : cast(const REBYTE *, p); + const REBUNI *up = unicode ? cast(const REBUNI *, p) : NULL; + + if (len > 8) goto bad_hex; + + for (n = 0; n < len; n++) { + c = unicode ? up[n] : cast(REBUNI, bp[n]); + + if (c > 255) goto bad_hex; + + lex = Lex_Map[c]; + if (lex <= LEX_WORD) goto bad_hex; + + c = lex & LEX_VALUE; + if (!c && lex < LEX_NUMBER) goto bad_hex; + num = (num << 4) + c; + } + return num; + +bad_hex: + fail (Error_Invalid_Chars_Raw()); } -#ifdef moved -/*********************************************************************** -** -**/ REBCNT Scan_Word(REBYTE *cp, REBCNT len) -/* -** Scan word chars and make word symbol for it. -** Returns symbol number, or zero for errors. -** -***********************************************************************/ -{ - REBCNT n; - - if ( - IS_LEX_WORD(*cp) - || strchr("/+-<>.", *cp) - ) { - // Special / and // cases: - if (*cp == '/') { - if (len == 1 || (len == 2 && cp[1] == '/')) - return Make_Word(cp, len); - else - return 0; - } - - // Check other cases: - for (n = 1; n < len; n++) { - if ( - !IS_LEX_AT_LEAST_SPECIAL(cp[n]) - || strchr(":/", cp[n]) - ) { - return 0; - } - } - } - else - return 0; - - return Make_Word(cp, len); +// +// Scan_Dec_Buf: C +// +// Validate a decimal number. Return on first invalid char (or end). +// Returns NULL if not valid. +// +// Scan is valid for 1 1.2 1,2 1'234.5 1x 1.2x 1% 1.2% etc. +// +// !!! Is this redundant with Scan_Decimal? Appears to be similar code. +// +const REBYTE *Scan_Dec_Buf( + REBYTE *out, // may live in data stack (do not call DS_PUSH, GC, eval) + const REBYTE *cp, + REBCNT len // max size of buffer +) { + assert(len >= MAX_NUM_LEN); + + REBYTE *bp = out; + REBYTE *be = bp + len - 1; + + if (*cp == '+' || *cp == '-') + *bp++ = *cp++; + + REBOOL digit_present = FALSE; + while (IS_LEX_NUMBER(*cp) || *cp == '\'') { + if (*cp != '\'') { + *bp++ = *cp++; + if (bp >= be) + return NULL; + digit_present = TRUE; + } + else + ++cp; + } + + if (*cp == ',' || *cp == '.') + cp++; + + *bp++ = '.'; + if (bp >= be) + return NULL; + + while (IS_LEX_NUMBER(*cp) || *cp == '\'') { + if (*cp != '\'') { + *bp++ = *cp++; + if (bp >= be) + return NULL; + digit_present = TRUE; + } + else + ++cp; + } + + if (NOT(digit_present)) + return NULL; + + if (*cp == 'E' || *cp == 'e') { + *bp++ = *cp++; + if (bp >= be) + return NULL; + + digit_present = FALSE; + + if (*cp == '-' || *cp == '+') { + *bp++ = *cp++; + if (bp >= be) + return NULL; + } + + while (IS_LEX_NUMBER(*cp)) { + *bp++ = *cp++; + if (bp >= be) + return NULL; + digit_present = TRUE; + } + + if (NOT(digit_present)) + return NULL; + } + + *bp = '\0'; + return cp; } -#endif -#ifdef not_used -/*********************************************************************** -** -*/ REBYTE *Scan_String(REBYTE *cp, REBCNT len, REBVAL *value) -/* -** Scan and convert a string. Return zero if error. -** -***********************************************************************/ -{ - REBYTE *ep; - Reset_Buffer(BUF_FORM, len); +// +// Scan_Decimal: C +// +// Scan and convert a decimal value. Return zero if error. +// +const REBYTE *Scan_Decimal( + REBVAL *out, // may live in data stack (do not call DS_PUSH, GC, eval) + const REBYTE *cp, + REBCNT len, + REBOOL dec_only +) { + TRASH_CELL_IF_DEBUG(out); - if (!(ep = Scan_Quote(cp, BIN_HEAD(BUF_FORM), 0))) { - VAL_CLEAR(value); - return 0; - } + REBYTE buf[MAX_NUM_LEN + 4]; + REBYTE *ep = buf; + if (len > MAX_NUM_LEN) + return_NULL; - Set_String(value, Decode_UTF8_Value(BIN_HEAD(BUF_FORM), (REBCNT)(ep - BIN_HEAD(BUF_FORM)))); + const REBYTE *bp = cp; - return ep; -} -#endif + if (*cp == '+' || *cp == '-') + *ep++ = *cp++; -/*********************************************************************** -** -*/ REBYTE *Scan_File(REBYTE *cp, REBCNT len, REBVAL *value) -/* -** Scan and convert a file name. -** -***********************************************************************/ -{ - REBUNI term = 0; - REBYTE *invalid = ":;()[]\""; - - if (*cp == '%') cp++, len--; - if (*cp == '"') { - cp++; - len--; - term = '"'; - invalid = ":;\""; - } - cp = Scan_Item(cp, cp+len, term, invalid); - if (cp) - Set_Series(REB_FILE, value, Copy_String(BUF_MOLD, 0, -1)); - return cp; - -#ifdef ndef - extern REBYTE *Scan_Quote(REBYTE *src, SCAN_STATE *scan_state); - - if (*cp == '%') cp++, len--; - if (len == 0) return 0; - if (*cp == '"') { - cp = Scan_Quote(cp, 0); - if (cp) { - int need_changes; - Set_String(value, Copy_String(BUF_MOLD, 0, -1)); - VAL_SET(value, REB_FILE); - } - return cp; - } - - VAL_SERIES(value) = Make_Binary(len); - VAL_INDEX(value) = 0; - - str = VAL_BIN(value); - for (; len > 0; len--) { - if (*cp == '%' && len > 2 && Scan_Hex2(cp+1, &n, FALSE)) { - *str++ = n; - cp += 3; - len -= 2; - } - else if (*cp == '\\') cp++, *str++ = '/'; - else if (strchr(":;()[]\"", *cp)) return 0; // chars not allowed in files !!! - else *str++ = *cp++; - } - *str = 0; - VAL_TAIL(value) = (REBCNT)(str - VAL_BIN(value)); - VAL_SET(value, REB_FILE); - return cp; -#endif -} + REBOOL digit_present = FALSE; + while (IS_LEX_NUMBER(*cp) || *cp == '\'') { + if (*cp != '\'') { + *ep++ = *cp++; + digit_present = TRUE; + } + else + ++cp; + } -/*********************************************************************** -** -*/ REBYTE *Scan_Email(REBYTE *cp, REBCNT len, REBVAL *value) -/* -** Scan and convert email. -** -***********************************************************************/ -{ - REBYTE *str; - REBOOL at = FALSE; - REBUNI n; - - VAL_SERIES(value) = Make_Binary(len); - VAL_INDEX(value) = 0; - - str = VAL_BIN(value); - for (; len > 0; len--) { - if (*cp == '@') { - if (at) return 0; - at = TRUE; - } - if (*cp == '%') { - if (len <= 2 || !Scan_Hex2(cp+1, &n, FALSE)) return 0; - *str++ = (REBYTE)n; - cp += 3; - len -= 2; - } - else *str++ = *cp++; - } - *str = 0; - if (!at) return 0; - VAL_TAIL(value) = (REBCNT)(str - VAL_BIN(value)); - VAL_SET(value, REB_EMAIL); - return cp; -} + if (*cp == ',' || *cp == '.') + ++cp; + *ep++ = '.'; -/*********************************************************************** -** -*/ REBYTE *Scan_URL(REBYTE *cp, REBCNT len, REBVAL *value) -/* -** Scan and convert a URL. -** -***********************************************************************/ -{ - REBYTE *str; - REBUNI n; + while (IS_LEX_NUMBER(*cp) || *cp == '\'') { + if (*cp != '\'') { + *ep++ = *cp++; + digit_present = TRUE; + } + else + ++cp; + } -// !!! Need to check for any possible scheme followed by ':' + if (NOT(digit_present)) + return_NULL; -// for (n = 0; n < URL_MAX; n++) { -// if (str = Match_Bytes(cp, (REBYTE *)(URL_Schemes[n]))) break; -// } -// if (n >= URL_MAX) return 0; -// if (*str != ':') return 0; - - VAL_SERIES(value) = Make_Binary(len); - VAL_INDEX(value) = 0; - - str = VAL_BIN(value); - for (; len > 0; len--) { - //if (*cp == '%' && len > 2 && Scan_Hex2(cp+1, &n, FALSE)) { - if (*cp == '%') { - if (len <= 2 || !Scan_Hex2(cp+1, &n, FALSE)) return 0; - *str++ = (REBYTE)n; - cp += 3; - len -= 2; - } - else *str++ = *cp++; - } - *str = 0; - VAL_TAIL(value) = (REBCNT)(str - VAL_BIN(value)); - VAL_SET(value, REB_URL); - return cp; -} + if (*cp == 'E' || *cp == 'e') { + *ep++ = *cp++; + digit_present = FALSE; + if (*cp == '-' || *cp == '+') + *ep++ = *cp++; -/*********************************************************************** -** -*/ REBYTE *Scan_Pair(REBYTE *cp, REBCNT len, REBVAL *value) -/* -** Scan and convert a pair -** -***********************************************************************/ -{ - REBYTE *ep, *xp; - REBYTE buf[MAX_NUM_LEN+4]; - - ep = cp; - //ep = Grab_Int(ep, &n); - ep = Scan_Dec_Buf(cp, MAX_NUM_LEN, &buf[0]); - if (!ep) return 0; - VAL_PAIR_X(value) = (float)atof((char*)(&buf[0])); //n; - if (*ep != 'x' && *ep != 'X') return 0; - ep++; - - xp = Scan_Dec_Buf(ep, MAX_NUM_LEN, &buf[0]); - if (!xp) return 0; - VAL_PAIR_Y(value) = (float)atof((char*)(&buf[0])); //n; - - if (len > (REBCNT)(xp - cp)) return 0; - VAL_SET(value, REB_PAIR); - return xp; + while (IS_LEX_NUMBER(*cp)) { + *ep++ = *cp++; + digit_present = TRUE; + } + + if (NOT(digit_present)) + return_NULL; + } + + if (*cp == '%') { + if (dec_only) + return_NULL; + + ++cp; // ignore it + } + + *ep = '\0'; + + if (cast(REBCNT, cp - bp) != len) + return_NULL; + + VAL_RESET_HEADER(out, REB_DECIMAL); + + const char *se; + VAL_DECIMAL(out) = STRTOD(s_cast(buf), &se); + + // !!! TBD: need check for NaN, and INF + + if (fabs(VAL_DECIMAL(out)) == HUGE_VAL) + fail (Error_Overflow_Raw()); + + return cp; } -/*********************************************************************** -** -*/ REBYTE *Scan_Tuple(REBYTE *cp, REBCNT len, REBVAL *value) -/* -** Scan and convert a tuple. -** -***********************************************************************/ -{ - REBYTE *ep; - REBYTE *tp; - REBCNT size = 1; - REBINT n; - - if (len == 0) return 0; - for (n = (REBINT)len, ep = cp; n > 0; n--, ep++) // count '.' - if (*ep == '.') size++; - if (size > MAX_TUPLE) return 0; - if (size < 3) size = 3; - VAL_TUPLE_LEN(value) = (REBYTE)size; - tp = VAL_TUPLE(value); - memset(tp, 0, sizeof(REBTUP)-2); - for (ep = cp; len > (REBCNT)(ep - cp); ep++) { - ep = Grab_Int(ep, &n); - if (n < 0 || n > 255) return 0; - *tp++ = (REBYTE)n; - if (*ep != '.') break; - } - if (len > (REBCNT)(ep - cp)) return 0; - VAL_SET(value, REB_TUPLE); - return ep; +// +// Scan_Integer: C +// +// Scan and convert an integer value. Return zero if error. +// Allow preceding + - and any combination of ' marks. +// +const REBYTE *Scan_Integer( + REBVAL *out, // may live in data stack (do not call DS_PUSH, GC, eval) + const REBYTE *cp, + REBCNT len +) { + TRASH_CELL_IF_DEBUG(out); + + // Super-fast conversion of zero and one (most common cases): + if (len == 1) { + if (*cp == '0') { + Init_Integer(out, 0); + return cp + 1; + } + if (*cp == '1') { + Init_Integer(out, 1); + return cp + 1; + } + } + + REBYTE buf[MAX_NUM_LEN + 4]; + if (len > MAX_NUM_LEN) + return_NULL; // prevent buffer overflow + + REBYTE *bp = buf; + + REBOOL neg = FALSE; + + REBINT num = cast(REBINT, len); + + // Strip leading signs: + if (*cp == '-') { + *bp++ = *cp++; + --num; + neg = TRUE; + } + else if (*cp == '+') { + ++cp; + --num; + } + + // Remove leading zeros: + for (; num > 0; num--) { + if (*cp == '0' || *cp == '\'') + ++cp; + else + break; + } + + if (num == 0) { // all zeros or ' + // return early to avoid platform dependant error handling in CHR_TO_INT + Init_Integer(out, 0); + return cp; + } + + // Copy all digits, except ' : + for (; num > 0; num--) { + if (*cp >= '0' && *cp <= '9') + *bp++ = *cp++; + else if (*cp == '\'') + ++cp; + else + return_NULL; + } + *bp = '\0'; + + // Too many digits? + len = bp - &buf[0]; + if (neg) + --len; + if (len > 19) { + // !!! magic number :-( How does it relate to MAX_INT_LEN (also magic) + return_NULL; + } + + // Convert, check, and return: + errno = 0; + + VAL_RESET_HEADER(out, REB_INTEGER); + + VAL_INT64(out) = CHR_TO_INT(buf); + if (errno != 0) + return_NULL; // overflow + + if ((VAL_INT64(out) > 0 && neg) || (VAL_INT64(out) < 0 && !neg)) + return_NULL; + + return cp; } -/*********************************************************************** -** -*/ REBYTE *Scan_Binary(REBYTE *cp, REBCNT len, REBVAL *value) -/* -** Scan and convert binary strings. -** -***********************************************************************/ -{ - REBYTE *ep; - REBINT base = 16; - - if (*cp != '#') { - ep = Grab_Int(cp, &base); - if (cp == ep || *ep != '#') return 0; - len -= (REBCNT)(ep - cp); - cp = ep; - } - cp++; // skip # - if (*cp++ != '{') return 0; - len -= 2; - - cp = Decode_Binary(value, cp, len, base, '}'); - if (!cp) return 0; - - cp = Skip_To_Char(cp, cp + len, '}'); - if (!cp) return 0; // series will be gc'd - - return cp; +// +// Scan_Money: C +// +// Scan and convert money. Return zero if error. +// +const REBYTE *Scan_Money( + REBVAL *out, // may live in data stack (do not call DS_PUSH, GC, eval) + const REBYTE *cp, + REBCNT len +) { + TRASH_CELL_IF_DEBUG(out); + + const REBYTE *end; + + if (*cp == '$') { + ++cp; + --len; + } + if (len == 0) + return_NULL; + + Init_Money(out, string_to_deci(cp, &end)); + if (end != cp + len) + return_NULL; + + return end; } -/*********************************************************************** -** -*/ REBYTE *Scan_Any(REBYTE *cp, REBCNT len, REBVAL *value, REBYTE type) -/* -** Scan any string that does not require special decoding. -** -***********************************************************************/ -{ - REBCNT n; +// +// Scan_Date: C +// +// Scan and convert a date. Also can include a time and zone. +// +const REBYTE *Scan_Date( + REBVAL *out, // may live in data stack (do not call DS_PUSH, GC, eval) + const REBYTE *cp, + REBCNT len +) { + TRASH_CELL_IF_DEBUG(out); + + const REBYTE *end = cp + len; + + // Skip spaces: + for (; *cp == ' ' && cp != end; cp++); + + // Skip day name, comma, and spaces: + const REBYTE *ep; + for (ep = cp; *ep != ',' && ep != end; ep++); + if (ep != end) { + cp = ep + 1; + while (*cp == ' ' && cp != end) cp++; + } + if (cp == end) + return_NULL; + + REBINT num; + + // Day or 4-digit year: + ep = Grab_Int(cp, &num); + if (num < 0) + return_NULL; + + REBINT day; + REBINT month; + REBINT year; + REBINT tz = 0; + + REBCNT size = cast(REBCNT, ep - cp); + if (size >= 4) { + // year is set in this branch (we know because day is 0) + // Ex: 2009/04/20/19:00:00+0:00 + year = num; + day = 0; + } + else if (size) { + // year is not set in this branch (we know because day ISN'T 0) + // Ex: 12-Dec-2012 + day = num; + if (day == 0) + return_NULL; + + // !!! Clang static analyzer doesn't know from test of `day` below + // how it connects with year being set or not. Suppress warning. + year = MIN_I32; // !!! Garbage, should not be read. + } + else + return_NULL; + + cp = ep; + + // Determine field separator: + if (*cp != '/' && *cp != '-' && *cp != '.' && *cp != ' ') + return_NULL; + + REBYTE sep = *cp++; + + // Month as number or name: + ep = Grab_Int(cp, &num); + if (num < 0) + return_NULL; + + size = cast(REBCNT, ep - cp); + + if (size > 0) + month = num; // got a number + else { // must be a word + for (ep = cp; IS_LEX_WORD(*ep); ep++) + NOOP; // scan word + + size = cast(REBCNT, ep - cp); + if (size < 3) + return_NULL; + + for (num = 0; num < 12; num++) { + if (!Compare_Bytes(cb_cast(Month_Names[num]), cp, size, TRUE)) + break; + } + month = num + 1; + } + + if (month < 1 || month > 12) + return_NULL; + + cp = ep; + if (*cp++ != sep) + return_NULL; + + // Year or day (if year was first): + ep = Grab_Int(cp, &num); + if (*cp == '-' || num < 0) + return_NULL; + + size = cast(REBCNT, ep - cp); + if (size == 0) + return_NULL; + + if (day == 0) { + // year already set, but day hasn't been + day = num; + } + else { + // day has been set, but year hasn't been. + if (size >= 3) + year = num; + else { + // !!! Originally this allowed shorthands, so that 96 = 1996, etc. + // + // if (num >= 70) + // year = 1900 + num; + // else + // year = 2000 + num; + // + // It was trickier than that, because it actually used the current + // year (from the clock) to guess what the short year meant. That + // made it so the scanner would scan the same source code + // differently based on the clock, which is bad. By allowing + // short dates to be turned into their short year equivalents, the + // user code can parse such dates and fix them up after the fact + // according to their requirements, `if date/year < 100 [...]` + // + year = num; + } + } + + if (year > MAX_YEAR || day < 1 || day > Month_Max_Days[month-1]) + return_NULL; + + // Check February for leap year or century: + if (month == 2 && day == 29) { + if ( + ((year % 4) != 0) || // not leap year + ((year % 100) == 0 && // century? + (year % 400) != 0) + ){ + return_NULL; // not leap century + } + } + + cp = ep; + + VAL_RESET_HEADER(out, REB_DATE); + VAL_NANO(out) = NO_TIME; + + if (cp >= end) + goto end_date; + + if (*cp == '/' || *cp == ' ') { + sep = *cp++; + + if (cp >= end) + goto end_date; + + cp = Scan_Time(out, cp, 0); + if ( + cp == NULL + || !IS_TIME(out) + || (VAL_NANO(out) < 0) + || (VAL_NANO(out) >= SECS_TO_NANO(24 * 60 * 60)) + ){ + return_NULL; + } + } + + if (*cp == sep) cp++; + + // Time zone can be 12:30 or 1230 (optional hour indicator) + if (*cp == '-' || *cp == '+') { + if (cp >= end) + goto end_date; + + ep = Grab_Int(cp + 1, &num); + if (ep - cp == 0) + return_NULL; + + if (*ep != ':') { + if (num < -1500 || num > 1500) + return_NULL; + + int h = (num / 100); + int m = (num - (h * 100)); + + tz = (h * 60 + m) / ZONE_MINS; + } + else { + if (num < -15 || num > 15) + return_NULL; + + tz = num * (60 / ZONE_MINS); + + if (*ep == ':') { + ep = Grab_Int(ep + 1, &num); + if (num % ZONE_MINS != 0) + return_NULL; + + tz += num / ZONE_MINS; + } + } + + if (ep != end) + return_NULL; + + if (*cp == '-') + tz = -tz; + + cp = ep; + } - VAL_SET(value, type); - VAL_SERIES(value) = Append_UTF8(0, cp, len); - VAL_INDEX(value) = 0; - VAL_TAIL(value) = len; +end_date: + Set_Date_UTC(out, year, month, day, VAL_NANO(out), tz); + return cp; +} - if (VAL_BYTE_SIZE(value)) { - n = Deline_Bytes(VAL_BIN(value), len); - } else { - n = Deline_Uni(VAL_UNI(value), len); - } - VAL_TAIL(value) = n; - return cp + len; +// +// Scan_File: C +// +// Scan and convert a file name. +// +const REBYTE *Scan_File( + REBVAL *out, // may live in data stack (do not call DS_PUSH, GC, eval) + const REBYTE *cp, + REBCNT len +) { + TRASH_CELL_IF_DEBUG(out); + + if (*cp == '%') { + cp++; + len--; + } + + REBUNI term = 0; + const REBYTE *invalid; + if (*cp == '"') { + cp++; + len--; + term = '"'; + invalid = cb_cast(":;\""); + } + else { + term = 0; + invalid = cb_cast(":;()[]\""); + } + + REB_MOLD mo; + CLEARS(&mo); + + cp = Scan_Item_Push_Mold(&mo, cp, cp + len, term, invalid); + if (cp == NULL) { + Drop_Mold(&mo); + return_NULL; + } + + Init_File(out, Pop_Molded_String(&mo)); + return cp; } -/*********************************************************************** -** -*/ static void Append_Markup(REBSER *series, REBCNT type, REBYTE *bp, REBINT len) -/* -** Add a new string or tag to a markup block, advancing the tail. -** -***********************************************************************/ -{ - REBVAL *val; - if (SERIES_FULL(series)) Extend_Series(series, 8); - val = BLK_TAIL(series); - SET_END(val); - series->tail++; - SET_END(val+1); - SET_STR_TYPE(type, val, Append_UTF8(0, bp, len)); +// +// Scan_Email: C +// +// Scan and convert email. +// +const REBYTE *Scan_Email( + REBVAL *out, // may live in data stack (do not call DS_PUSH, GC, eval) + const REBYTE *cp, + REBCNT len +) { + TRASH_CELL_IF_DEBUG(out); + + REBSER *series = Make_Binary(len); + + REBOOL at = FALSE; + REBYTE *str = BIN_HEAD(series); + for (; len > 0; len--) { + if (*cp == '@') { + if (at) return_NULL; + at = TRUE; + } + + if (*cp == '%') { + REBUNI n; + if (len <= 2 || !Scan_Hex2(cp + 1, &n, FALSE)) + return_NULL; + *str++ = cast(REBYTE, n); + cp += 3; + len -= 2; + } + else + *str++ = *cp++; + } + *str = 0; + if (NOT(at)) + return_NULL; + + SET_SERIES_LEN(series, cast(REBCNT, str - BIN_HEAD(series))); + + Init_Email(out, series); + return cp; } -/*********************************************************************** -** -*/ REBSER *Load_Markup(REBYTE *cp, REBINT len) -/* -** Scan a string as HTML or XML and convert it to a block -** of strings and tags. Return the block as a series. -** -***********************************************************************/ -{ - REBYTE *bp = cp; - REBSER *series; - REBYTE quote; - - series = Make_Block(16); - //DISABLE_GC; - - while (len > 0) { - // Look for tag, gathering text as we go: - for (; len > 0 && *cp != '<'; len--, cp++); - if (len <= 0) break; - if (!IS_LEX_WORD(cp[1]) && cp[1] != '/' && cp[1] != '?' && cp[1] != '!') { - cp++; len--; continue; - } - if (cp != bp) Append_Markup(series, REB_STRING, bp, cp - bp); - bp = ++cp; // skip < - - // Check for comment tag: - if (*cp == '!' && len > 7 && cp[1] == '-' && cp[2] == '-') { - for (len -= 3, cp += 3; len > 2 && - !(*cp == '-' && cp[1] == '-' && cp[2] == '>'); cp++, len--); - if (len > 2) cp += 2, len -= 2; - // fall into tag code below... - } - // Look for end of tag, watch for quotes: - for (len--; len > 0; len--, cp++) { - if (*cp == '>') { - Append_Markup(series, REB_TAG, bp, cp - bp); - bp = ++cp; len--; - break; - } - if (*cp == '"' || *cp == '\'') { // quote in tag - quote = *cp++; - for (len--; len > 0 && *cp != quote; len--, cp++); // find end quote - if (len <= 0) break; - } - } - // Note: if final tag does not end, then it is treated as text. - } - if (cp != bp) Append_Markup(series, REB_STRING, bp, cp - bp); - //ENABLE_GC; - - return series; +// +// Scan_URL: C +// +// Scan and convert a URL. +// +const REBYTE *Scan_URL( + REBVAL *out, // may live in data stack (do not call DS_PUSH, GC, eval) + const REBYTE *cp, + REBCNT len +) { + TRASH_CELL_IF_DEBUG(out); + +// !!! Need to check for any possible scheme followed by ':' + +// for (n = 0; n < URL_MAX; n++) { +// if (str = Match_Bytes(cp, (REBYTE *)(URL_Schemes[n]))) break; +// } +// if (n >= URL_MAX) return_NULL; +// if (*str != ':') return_NULL; + + REBSER *series = Make_Binary(len); + + REBYTE *str = BIN_HEAD(series); + for (; len > 0; len--) { + //if (*cp == '%' && len > 2 && Scan_Hex2(cp+1, &n, FALSE)) { + if (*cp == '%') { + REBUNI n; + if (len <= 2 || !Scan_Hex2(cp + 1, &n, FALSE)) + return_NULL; + + *str++ = cast(REBYTE, n); + cp += 3; + len -= 2; + } + else + *str++ = *cp++; + } + *str = 0; + SET_SERIES_LEN(series, cast(REBCNT, str - BIN_HEAD(series))); + + Init_Url(out, series); + return cp; } -/*********************************************************************** -** -*/ REBFLG Construct_Value(REBVAL *value, REBSER *spec) -/* -** Lexical datatype constructor. Return TRUE on success. -** -** This function makes datatypes that are not normally expressible -** in unevaluated source code format. The format of the datatype -** constructor is: -** -** #[datatype! | keyword spec] -** -** The first item is a datatype word or NONE, FALSE or TRUE. The -** second part is a specification for the datatype, as a basic -** type (such as a string) or a block. -** -** Keep in mind that this function is being called as part of the -** scanner, so optimal performance is critical. -** -***********************************************************************/ -{ - REBVAL *val; - REBCNT type; - MAKE_FUNC func; +// +// Scan_Pair: C +// +// Scan and convert a pair +// +const REBYTE *Scan_Pair( + REBVAL *out, // may live in data stack (do not call DS_PUSH, GC, eval) + const REBYTE *cp, + REBCNT len +) { + TRASH_CELL_IF_DEBUG(out); + + REBYTE buf[MAX_NUM_LEN + 4]; + + const REBYTE *ep = Scan_Dec_Buf(&buf[0], cp, MAX_NUM_LEN); + if (ep == NULL) + return_NULL; + if (*ep != 'x' && *ep != 'X') + return_NULL; + + VAL_RESET_HEADER(out, REB_PAIR); + out->payload.pair = Alloc_Pairing(NULL); + VAL_RESET_HEADER(out->payload.pair, REB_DECIMAL); + VAL_RESET_HEADER(PAIRING_KEY(out->payload.pair), REB_DECIMAL); + + VAL_PAIR_X(out) = cast(float, atof(cast(char*, &buf[0]))); //n; + ep++; + + const REBYTE *xp = Scan_Dec_Buf(&buf[0], ep, MAX_NUM_LEN); + if (!xp) { + Free_Pairing(out->payload.pair); + return_NULL; + } + + VAL_PAIR_Y(out) = cast(float, atof(cast(char*, &buf[0]))); //n; + + if (len > cast(REBCNT, xp - cp)) { + Free_Pairing(out->payload.pair); + return_NULL; + } + + Manage_Pairing(out->payload.pair); + return xp; +} - val = BLK_HEAD(spec); - if (!IS_WORD(val)) return FALSE; +// +// Scan_Tuple: C +// +// Scan and convert a tuple. +// +const REBYTE *Scan_Tuple( + REBVAL *out, // may live in data stack (do not call DS_PUSH, GC, eval) + const REBYTE *cp, + REBCNT len +) { + TRASH_CELL_IF_DEBUG(out); - Set_Block(value, spec); //GC + if (len == 0) + return_NULL; - // Handle the datatype or keyword: - type = VAL_WORD_CANON(val); - if (type > REB_MAX) { // >, not >=, because they are one-based + const REBYTE *ep; + REBCNT size = 1; + REBINT n; + for (n = cast(REBINT, len), ep = cp; n > 0; n--, ep++) { // count '.' + if (*ep == '.') + ++size; + } - switch (type) { + if (size > MAX_TUPLE) + return_NULL; - case SYM_NONE: - SET_NONE(value); - return TRUE; + if (size < 3) + size = 3; - case SYM_FALSE: - SET_FALSE(value); - return TRUE; + VAL_RESET_HEADER(out, REB_TUPLE); + VAL_TUPLE_LEN(out) = cast(REBYTE, size); - case SYM_TRUE: - SET_TRUE(value); - return TRUE; + REBYTE *tp = VAL_TUPLE(out); + memset(tp, 0, sizeof(REBTUP) - 2); - default: - return FALSE; - } - } - type--; // The global word for datatype x is at word x+1. + for (ep = cp; len > cast(REBCNT, ep - cp); ++ep) { + ep = Grab_Int(ep, &n); + if (n < 0 || n > 255) + return_NULL; + + *tp++ = cast(REBYTE, n); + if (*ep != '.') + break; + } + + if (len > cast(REBCNT, ep - cp)) + return_NULL; + + return ep; +} - // Check for trivial types: - if (type == REB_UNSET) { - SET_UNSET(value); - return TRUE; - } - if (type == REB_NONE) { - SET_NONE(value); - return TRUE; - } - val++; - if (IS_END(val)) return FALSE; +// +// Scan_Binary: C +// +// Scan and convert binary strings. +// +const REBYTE *Scan_Binary( + REBVAL *out, // may live in data stack (do not call DS_PUSH, GC, eval) + const REBYTE *cp, + REBCNT len +) { + TRASH_CELL_IF_DEBUG(out); + + REBINT base = 16; + + if (*cp != '#') { + const REBYTE *ep = Grab_Int(cp, &base); + if (cp == ep || *ep != '#') + return_NULL; + len -= cast(REBCNT, ep - cp); + cp = ep; + } + + cp++; // skip # + if (*cp++ != '{') + return_NULL; + + len -= 2; + + cp = Decode_Binary(out, cp, len, base, '}'); + if (cp == NULL) + return_NULL; + + cp = Skip_To_Byte(cp, cp + len, '}'); + if (cp == NULL) + return_NULL; // series will be gc'd + + return cp + 1; // include the "}" in the scan total +} - // Dispatch maker: - if (NZ(func = Make_Dispatch[type])) { - if (func(value, val, type)) return TRUE; - } - return FALSE; +// +// Scan_Any: C +// +// Scan any string that does not require special decoding. +// +const REBYTE *Scan_Any( + REBVAL *out, // may live in data stack (do not call DS_PUSH, GC, eval) + const REBYTE *cp, + REBCNT num_bytes, + enum Reb_Kind type +) { + TRASH_CELL_IF_DEBUG(out); + + REBSER *s = Append_UTF8_May_Fail(NULL, cp, num_bytes); // NULL means alloc + + REBCNT delined_len; + if (BYTE_SIZE(s)) { + delined_len = Deline_Bytes(BIN_HEAD(s), SER_LEN(s)); + } else { + delined_len = Deline_Uni(UNI_HEAD(s), SER_LEN(s)); + } + + // We hand it over to management by the GC, but don't run the GC before + // the source has been scanned and put somewhere safe! + // + SET_SERIES_LEN(s, delined_len); + Init_Any_Series(out, type, s); + + return cp + num_bytes; } -/*********************************************************************** -** -*/ REBSER *Scan_Net_Header(REBSER *blk, REBYTE *str) -/* -** Scan an Internet-style header (HTTP, SMTP). -** Fields with duplicate words will be merged into a block. -** -***********************************************************************/ +// +// scan-net-header: native [ +// {Scan an Internet-style header (HTTP, SMTP).} +// +// header [string! binary!] +// {Fields with duplicate words will be merged into a block.} +// ] +// +REBNATIVE(scan_net_header) +// +// !!! This routine used to be a feature of CONSTRUCT in R3-Alpha, and was +// used by %prot-http.r. The idea was that instead of providing a parent +// object, a STRING! or BINARY! could be provided which would be turned +// into a block by this routine. +// +// The only reason it seemed to support BINARY! was to optimize the case +// where the binary only contained ASCII codepoints to dodge a string +// conversion. +// +// It doesn't make much sense to have this coded in C rather than using PARSE +// It's only being converted into a native to avoid introducing bugs by +// rewriting it as Rebol in the middle of other changes. { - REBYTE *cp = str; - REBYTE *start; - REBVAL *val; - REBINT len; - REBSER *ser; - - while (IS_LEX_ANY_SPACE(*cp)) cp++; // skip white space - - while (1) { - // Scan valid word: - if (GET_LEX_CLASS(*cp) == LEX_CLASS_WORD) { - start = cp; - while ( - IS_LEX_AT_LEAST_WORD(*cp) || *cp == '.' || *cp == '-' || *cp == '_' - ) cp++; // word char or number - } - else break; - - if (*cp == ':') { - REBCNT sym = Make_Word(start, cp-start); - cp++; - // Search if word already present: - for (val = BLK_HEAD(blk); NOT_END(val); val += 2) { - if (VAL_WORD_SYM(val) == sym) { - // Does it already use a block? - if (IS_BLOCK(val+1)) { - // Block of values already exists: - val = Append_Value(VAL_SERIES(val+1)); - } - else { - // Create new block for values: - REBVAL *val2; - ser = Make_Block(2); - val2 = Append_Value(ser); // prior value - *val2 = val[1]; - Set_Block(val+1, ser); - val = Append_Value(ser); // for new value - } - break; - } - } - if (IS_END(val)) { - val = Append_Value(blk); // add new word - Init_Word(val, sym); - VAL_SET(val, REB_SET_WORD); - val = Append_Value(blk); // for new value - } - } - else break; - - // Get value: - while (IS_LEX_SPACE(*cp)) cp++; - start = cp; - len = 0; - while (NOT_NEWLINE(*cp)) len++, cp++; - // Is it continued on next line? - while (*cp) { - if (*cp == CR) cp++; - if (*cp == LF) cp++; - if (IS_LEX_SPACE(*cp)) { - while (IS_LEX_SPACE(*cp)) cp++; - while (NOT_NEWLINE(*cp)) len++, cp++; - } - else break; - } - - // Create string value (ignoring lines and indents): - ser = Make_Binary(len); - ser->tail = len; - str = STR_HEAD(ser); - cp = start; - // Code below *MUST* mirror that above: - while (NOT_NEWLINE(*cp)) *str++ = *cp++; - while (*cp) { - if (*cp == CR) cp++; - if (*cp == LF) cp++; - if (IS_LEX_SPACE(*cp)) { - while (IS_LEX_SPACE(*cp)) cp++; - while (NOT_NEWLINE(*cp)) *str++ = *cp++; - } - else break; - } - *str = 0; - Set_String(val, ser); - } - - return blk; + INCLUDE_PARAMS_OF_SCAN_NET_HEADER; + + REBARR *result = Make_Array(10); // Just a guess at size (use STD_BUF?) + + // Convert string if necessary. Store back for GC safety. + // + REBVAL *header = ARG(header); + REBCNT index; + REBSER *utf8 = Temp_Bin_Str_Managed(header, &index, NULL); + INIT_VAL_SERIES(header, utf8); // GC protect, unnecessary? + + REBYTE *cp = BIN_HEAD(utf8) + index; + + while (IS_LEX_ANY_SPACE(*cp)) cp++; // skip white space + + REBYTE *start; + REBINT len; + + while (TRUE) { + // Scan valid word: + if (IS_LEX_WORD(*cp)) { + start = cp; + while ( + IS_LEX_WORD_OR_NUMBER(*cp) + || *cp == '.' + || *cp == '-' + || *cp == '_' + ) { + cp++; + } + } + else break; + + if (*cp != ':') + break; + + REBVAL *val = NULL; // rigorous checks worry it could be uninitialized + + REBSTR *name = Intern_UTF8_Managed(start, cp - start); + RELVAL *item; + + cp++; + // Search if word already present: + for (item = ARR_HEAD(result); NOT_END(item); item += 2) { + assert(IS_STRING(item + 1) || IS_BLOCK(item + 1)); + if (SAME_STR(VAL_WORD_SPELLING(item), name)) { + // Does it already use a block? + if (IS_BLOCK(item + 1)) { + // Block of values already exists: + val = Alloc_Tail_Array(VAL_ARRAY(item + 1)); + } + else { + // Create new block for values: + REBARR *array = Make_Array(2); + Derelativize( + Alloc_Tail_Array(array), + item + 1, // prior value + SPECIFIED // no relative values added + ); + val = Alloc_Tail_Array(array); + SET_UNREADABLE_BLANK(val); // for Init_Block + Init_Block(item + 1, array); + } + break; + } + } + + if (IS_END(item)) { // didn't break, add space for new word/value + Init_Set_Word(Alloc_Tail_Array(result), name); + val = Alloc_Tail_Array(result); + } + + while (IS_LEX_SPACE(*cp)) cp++; + start = cp; + len = 0; + while (!ANY_CR_LF_END(*cp)) { + len++; + cp++; + } + // Is it continued on next line? + while (*cp) { + if (*cp == CR) cp++; + if (*cp == LF) cp++; + if (IS_LEX_SPACE(*cp)) { + while (IS_LEX_SPACE(*cp)) cp++; + while (!ANY_CR_LF_END(*cp)) { + len++; + cp++; + } + } + else break; + } + + // Create string value (ignoring lines and indents): + REBSER *string = Make_Binary(len); + SET_SERIES_LEN(string, len); + REBYTE *str = BIN_HEAD(string); + cp = start; + // Code below *MUST* mirror that above: + while (!ANY_CR_LF_END(*cp)) *str++ = *cp++; + while (*cp) { + if (*cp == CR) cp++; + if (*cp == LF) cp++; + if (IS_LEX_SPACE(*cp)) { + while (IS_LEX_SPACE(*cp)) cp++; + while (!ANY_CR_LF_END(*cp)) + *str++ = *cp++; + } + else break; + } + *str = '\0'; + Init_String(val, string); + } + + Init_Block(D_OUT, result); + return R_OUT; } diff --git a/src/core/m-gc.c b/src/core/m-gc.c old mode 100644 new mode 100755 index 8c86ed2e3f..fdd2cdef7b --- a/src/core/m-gc.c +++ b/src/core/m-gc.c @@ -1,679 +1,1860 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: m-gc.c -** Summary: main memory garbage collection -** Section: memory -** Author: Carl Sassenrath, Ladislav Mecir, HostileFork -** Notes: -** WARNING WARNING WARNING -** This is highly tuned code that should only be modified by experts -** who fully understand its design. It is very easy to create odd -** side effects so please be careful and extensively test all changes! -** -** The process consists of two stages: -** -** MARK - Mark all series and gobs ("collectible values") -* that can be found in: -** -** Root Block: special structures and buffers -** Task Block: special structures and buffers per task -** Data Stack: current state of evaluation -** Safe Series: saves the last N allocations -** -** Mark is recursive until we reach the terminals, or -** until we hit values already marked. -** -** SWEEP - Free all collectible values that were not marked. -** -** GC protection methods: -** -** KEEP flag - protects an individual series from GC, but -** does not protect its contents (if it holds values). -** Reserved for non-block system series. -** -** Root_Context - protects all series listed. This list is -** used by Sweep as the root of the in-use memory tree. -** Reserved for important system series only. -** -** Task_Context - protects all series listed. This list is -** the same as Root, but per the current task context. -** -** Save_Series - protects temporary series. Used with the -** SAVE_SERIES and UNSAVE_SERIES macros. Throws and errors -** must roll back this series to avoid "stuck" memory. -** -** Safe_Series - protects last MAX_SAFE_SERIES series from GC. -** Can only be used if no deeply allocating functions are -** called within the scope of its protection. Not affected -** by throws and errors. -** -** Data_Stack - all values in the data stack that are below -** the TOP (DSP) are automatically protected. This is a -** common protection method used by native functions. -** -** DISABLE_GC - macro that turns off GC. A quick way to avoid -** GC, but must only be used for well-behaved sections -** or could cause substantial memory growth. -** -** DONE flag - do not scan the series; it has no links. -** -***********************************************************************/ +// +// File: %m-gc.c +// Summary: "main memory garbage collection" +// Section: memory +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Today's garbage collector is based on a conventional "mark and sweep", +// of REBSER "nodes", which is how it was done in R3-Alpha: +// +// https://en.wikipedia.org/wiki/Tracing_garbage_collection +// +// A REBVAL's "payload" and "extra" field may or may not contain pointers to +// REBSERs that the GC needs to be aware of. Some small values like LOGIC! +// or INTEGER! don't, because they can fit the entirety of their data into the +// REBVAL's 4*sizeof(void) cell...though this would change if INTEGER! added +// support for arbitrary-sized-numbers. +// +// Some REBVALs embed REBSER pointers even when the payload would technically +// fit inside their cell. They do this in order to create a level of +// indirection so that their data can be shared among copies of that REBVAL. +// For instance, HANDLE! does this. +// +// "Deep" marking in R3-Alpha was originally done with recursion, and the +// recursion would stop whenever a mark was hit. But this meant deeply nested +// structures could quickly wind up overflowing the C stack. Consider: +// +// a: copy [] +// loop 200'000 [a: append/only copy [] a] +// recycle +// +// The simple solution is that when an unmarked array is hit that it is +// marked and put into a queue for processing (instead of recursed on the +// spot). This queue is then handled as soon as the marking call is exited, +// and the process repeated until no more items are queued. +// +// !!! There is actually not a specific list of roots of the garbage collect, +// so a first pass of all the REBSER nodes must be done to find them. This is +// because with the redesigned "RL_API" in Ren-C, ordinary REBSER nodes do +// double duty as lifetime-managed containers for REBVALs handed out by the +// API--without requiring a separate series data allocation. These could be +// in their own "pool", but that would prevent mingling and reuse among REBSER +// nodes used for other purposes. Review in light of any new garbage collect +// approaches used. +// #include "sys-core.h" + +#include "mem-pools.h" // low-level memory pool access +#include "mem-series.h" // low-level series memory access + +#include "sys-int-funcs.h" + + +// +// !!! In R3-Alpha, the core included specialized structures which required +// their own GC participation. This is because rather than store their +// properties in conventional Rebol types (like an OBJECT!) they wanted to +// compress their data into a tighter bit pattern than that would allow. +// +// Ren-C has attempted to be increasingly miserly about bytes, and also +// added the ability for C extensions to hook the GC for a cleanup callback +// relating to HANDLE! for any non-Rebol types. Hopefully this will reduce +// the desire to hook the core garbage collector more deeply. If a tighter +// structure is desired, that can be done with a HANDLE! or BINARY!, so long +// as any Rebol series/arrays/contexts/functions are done with full values. +// +// Events, Devices, and Gobs are slated to be migrated to structures that +// lean less heavily on C structs and raw C pointers, and leverage higher +// level Rebol services. So ultimately their implementations would not +// require including specialized code in the garbage collector. For the +// moment, they still need the hook. +// + #include "reb-evtypes.h" +static void Queue_Mark_Event_Deep(const RELVAL *value); + +#define IS_GOB_MARK(g) \ + GET_GOB_FLAG((g), GOBF_MARK) +#define MARK_GOB(g) \ + SET_GOB_FLAG((g), GOBF_MARK) +#define UNMARK_GOB(g) \ + CLR_GOB_FLAG((g), GOBF_MARK) +static void Queue_Mark_Gob_Deep(REBGOB *gob); +static REBCNT Sweep_Gobs(void); + +static void Mark_Devices_Deep(void); -#ifdef REB_API -extern REBOL_HOST_LIB *Host_Lib; + +#ifndef NDEBUG + static REBOOL in_mark = FALSE; // needs to be per-GC thread #endif -//-- For Serious Debugging: -#ifdef WATCH_GC_VALUE -REBSER *Watcher = 0; -REBVAL *WatchVar = 0; -REBVAL *GC_Break_Point(REBVAL *val) {return val;} -REBVAL *N_watch(REBFRM *frame, REBVAL **inter_block) +#define ASSERT_NO_GC_MARKS_PENDING() \ + assert(SER_LEN(GC_Mark_Stack) == 0) + + +// Private routines for dealing with the GC mark bit. Note that not all +// REBSERs are actually series at the present time, because some are +// "pairings". Plus the name Mark_Rebser_Only helps drive home that it's +// not actually marking an "any_series" type (like array) deeply. +// +static inline void Mark_Rebser_Only(REBSER *s) { - WatchVar = Get_Word(FRM_ARG1(frame)); - Watcher = VAL_SERIES(WatchVar); - SET_INTEGER(FRM_ARG1(frame), 0); - return Nothing; +#if !defined(NDEBUG) + if (NOT(IS_SERIES_MANAGED(s))) { + printf("Link to non-MANAGED item reached by GC\n"); + panic (s); + } +#endif + assert(NOT_SER_FLAG(s, SERIES_FLAG_ARRAY)); + + if (s->header.bits & SERIES_FLAG_FILE_LINE) + s->link.filename->header.bits |= NODE_FLAG_MARKED; + s->header.bits |= NODE_FLAG_MARKED; +} + +static inline REBOOL Is_Rebser_Marked_Or_Pending(REBSER *rebser) { + return LOGICAL(rebser->header.bits & NODE_FLAG_MARKED); +} + +static inline REBOOL Is_Rebser_Marked(REBSER *rebser) { + // ASSERT_NO_GC_MARKS_PENDING(); // overkill check, but must be true + return LOGICAL(rebser->header.bits & NODE_FLAG_MARKED); +} + +static inline void Unmark_Rebser(REBSER *rebser) { + rebser->header.bits &= ~cast(REBUPT, NODE_FLAG_MARKED); +} + + +// +// Queue_Mark_Array_Subclass_Deep: C +// +// Submits the array into the deferred stack to be processed later with +// Propagate_All_GC_Marks(). If it were not queued and just used recursion +// (as R3-Alpha did) then deeply nested arrays could overflow the C stack. +// +// Although there are subclasses of REBARR which have ->link and ->misc +// and other properties that must be marked, the subclass processing is done +// during the propagation. This is to prevent recursion from within the +// subclass queueing routine itself. Hence this routine is the workhorse for +// the subclasses, but there are type-checked specializations for clarity +// if you have a REBFUN*, REBCTX*, etc. +// +// (Note: The data structure used for this processing is a "stack" and not +// a "queue". But when you use 'queue' as a verb, it has more leeway than as +// the CS noun, and can just mean "put into a list for later processing".) +// +static void Queue_Mark_Array_Subclass_Deep(REBARR *a) +{ +#if !defined(NDEBUG) + if (IS_FREE_NODE(a)) + panic (a); + + if (NOT_SER_FLAG(a, SERIES_FLAG_ARRAY)) + panic (a); + + if (!IS_ARRAY_MANAGED(a)) + panic (a); +#endif + + // A marked array doesn't necessarily mean all references reached from it + // have been marked yet--it could still be waiting in the queue. But we + // don't want to wastefully submit it to the queue multiple times. + // + if (Is_Rebser_Marked_Or_Pending(SER(a))) + return; + + SER(a)->header.bits |= NODE_FLAG_MARKED; // the up-front marking + + // Add series to the end of the mark stack series. The length must be + // maintained accurately to know when the stack needs to grow. + // + // !!! Should this use a "bumping a NULL at the end" technique to grow, + // like the data stack? + // + if (SER_FULL(GC_Mark_Stack)) + Extend_Series(GC_Mark_Stack, 8); + *SER_AT(REBARR*, GC_Mark_Stack, SER_LEN(GC_Mark_Stack)) = a; + SET_SERIES_LEN(GC_Mark_Stack, SER_LEN(GC_Mark_Stack) + 1); // unterminated +} + +inline static void Queue_Mark_Array_Deep(REBARR *a) { + assert(NOT_SER_FLAG(a, ARRAY_FLAG_VARLIST)); + assert(NOT_SER_FLAG(a, ARRAY_FLAG_PARAMLIST)); + assert(NOT_SER_FLAG(a, ARRAY_FLAG_PAIRLIST)); + + if (GET_SER_FLAG(a, SERIES_FLAG_FILE_LINE)) + SER(a)->link.filename->header.bits |= NODE_FLAG_MARKED; + + Queue_Mark_Array_Subclass_Deep(a); +} + +inline static void Queue_Mark_Context_Deep(REBCTX *c) { + REBARR *a = CTX_VARLIST(c); + assert(GET_SER_FLAG(a, ARRAY_FLAG_VARLIST)); + assert(NOT_SER_FLAG(a, ARRAY_FLAG_PARAMLIST)); + assert(NOT_SER_FLAG(a, ARRAY_FLAG_PAIRLIST)); + assert(NOT_SER_FLAG(a, SERIES_FLAG_FILE_LINE)); + + Queue_Mark_Array_Subclass_Deep(a); + + // Further handling is in Propagate_All_GC_Marks() for ARRAY_FLAG_VARLIST + // where it can safely call Queue_Mark_Context_Deep() again without it + // being a recursion. (e.g. marking the context for this context's meta) } + +inline static void Queue_Mark_Function_Deep(REBFUN *f) { + REBARR *a = FUNC_PARAMLIST(f); + assert(GET_SER_FLAG(a, ARRAY_FLAG_PARAMLIST)); + assert(NOT_SER_FLAG(a, ARRAY_FLAG_VARLIST)); + assert(NOT_SER_FLAG(a, ARRAY_FLAG_PAIRLIST)); + assert(NOT_SER_FLAG(a, SERIES_FLAG_FILE_LINE)); + + Queue_Mark_Array_Subclass_Deep(a); + + // Further handling is in Propagate_All_GC_Marks() for ARRAY_FLAG_PARAMLIST + // where it can safely call Queue_Mark_Function_Deep() again without it + // being a recursion. (e.g. marking underlying function for this function) +} + +inline static void Queue_Mark_Map_Deep(REBMAP *m) { + REBARR *a = MAP_PAIRLIST(m); + assert(GET_SER_FLAG(a, ARRAY_FLAG_PAIRLIST)); + assert(NOT_SER_FLAG(a, ARRAY_FLAG_PARAMLIST)); + assert(NOT_SER_FLAG(a, ARRAY_FLAG_VARLIST)); + assert(NOT_SER_FLAG(a, SERIES_FLAG_FILE_LINE)); + + + Queue_Mark_Array_Subclass_Deep(a); + + // Further handling is in Propagate_All_GC_Marks() for ARRAY_FLAG_PAIRLIST + // where it can safely call Queue_Mark_Map_Deep() again without it + // being a recursion. (e.g. marking underlying function for this function) +} + + +static void Queue_Mark_Opt_Value_Deep(const RELVAL *v); + +// A singular array, if you know it to be singular, can be marked a little +// faster by avoiding a queue step for the array node or walk. +// +inline static void Queue_Mark_Singular_Array(REBARR *a) { + assert(NOT_SER_FLAG(a, ARRAY_FLAG_PAIRLIST)); + assert(NOT_SER_FLAG(a, ARRAY_FLAG_PARAMLIST)); + assert(NOT_SER_FLAG(a, ARRAY_FLAG_VARLIST)); + assert(NOT_SER_FLAG(a, SERIES_FLAG_FILE_LINE)); + + assert(NOT_SER_INFO(a, SERIES_INFO_HAS_DYNAMIC)); + + SER(a)->header.bits |= NODE_FLAG_MARKED; + Queue_Mark_Opt_Value_Deep(ARR_HEAD(a)); +} + + +// +// Queue_Mark_Opt_Value_Deep: C +// +// This queues *optional* values, which may include void cells. If a slot is +// not supposed to allow a void, use Queue_Mark_Value_Deep() +// +static void Queue_Mark_Opt_Value_Deep(const RELVAL *v) +{ + assert(NOT(in_mark)); + + // If this happens, it means somehow Recycle() got called between + // when an `if (Do_XXX_Throws())` branch was taken and when the throw + // should have been caught up the stack (before any more calls made). + // + assert(NOT(v->header.bits & VALUE_FLAG_THROWN)); + +#if !defined(NDEBUG) + if (IS_UNREADABLE_IF_DEBUG(v)) + return; #endif -// This can be put below -#ifdef WATCH_GC_VALUE - if (Watcher && ser == Watcher) - GC_Break_Point(val); +#if !defined(NDEBUG) + in_mark = TRUE; +#endif - // for (n = 0; n < depth * 2; n++) Prin_Str(" "); - // Mark_Count++; - // Print("Mark: %s %x", TYPE_NAME(val), val); + // This switch is done via contiguous REB_XXX values, in order to + // facilitate use of a "jump table optimization": + // + // http://stackoverflow.com/questions/17061967/c-switch-and-jump-tables + // + enum Reb_Kind kind = VAL_TYPE(v); + switch (kind) { + case REB_0: + // + // Should not be possible, REB_0 instances should not exist or + // be filtered out by caller. + // + panic (v); + + case REB_FUNCTION: { + REBFUN *func = VAL_FUNC(v); + Queue_Mark_Function_Deep(func); + + if (VAL_BINDING(v) != NULL) + Queue_Mark_Array_Subclass_Deep(VAL_BINDING(v)); + + #if !defined(NDEBUG) + // + // Make sure the [0] slot of the paramlist holds an archetype that is + // consistent with the paramlist itself. + // + REBVAL *archetype = FUNC_VALUE(func); + assert(FUNC_PARAMLIST(func) == VAL_FUNC_PARAMLIST(archetype)); + assert(FUNC_BODY(func) == VAL_FUNC_BODY(archetype)); + + // It would be prohibitive to do validity checks on the facade of + // a function on each call to FUNC_FACADE, so it is checked here. + // + // Though a facade *may* be a paramlist, it could just be an array + // that *looks* like a paramlist, holding the underlying function the + // facade is "fronting for" in the head slot. The facade must always + // hold the same number of parameters as the underlying function. + // + REBARR *facade = SER(FUNC_PARAMLIST(func))->misc.facade; + assert(IS_FUNCTION(ARR_HEAD(facade))); + REBARR *underlying = ARR_HEAD(facade)->payload.function.paramlist; + if (underlying != facade) { + assert(NOT_SER_FLAG(facade, ARRAY_FLAG_PARAMLIST)); + assert(GET_SER_FLAG(underlying, ARRAY_FLAG_PARAMLIST)); + assert(ARR_LEN(facade) == ARR_LEN(underlying)); + } + #endif + break; } + + case REB_BAR: + case REB_LIT_BAR: + break; + + case REB_WORD: + case REB_SET_WORD: + case REB_GET_WORD: + case REB_LIT_WORD: + case REB_REFINEMENT: + case REB_ISSUE: { + REBSTR *spelling = v->payload.any_word.spelling; + + // A word marks the specific spelling it uses, but not the canon + // value. That's because if the canon value gets GC'd, then + // another value might become the new canon during that sweep. + // + Mark_Rebser_Only(spelling); + + // A GC cannot run during a binding process--which is the only + // time a canon word's "index" field is allowed to be nonzero. + // + assert( + NOT_SER_INFO(spelling, STRING_INFO_CANON) + || ( + spelling->misc.bind_index.high == 0 + && spelling->misc.bind_index.low == 0 + ) + ); + + if (GET_VAL_FLAG(v, WORD_FLAG_BOUND)) { + assert(v->payload.any_word.index != 0); + + if (GET_VAL_FLAG(v, VALUE_FLAG_RELATIVE)) { + // Bound relative to a function, keep that function alive. + // + // (To turn a relative binding into a specific one, a + // frame is needed from the BLOCK!/GROUP!/PATH! etc. that + // holds a word instance. So those frames are kept alive + // by the REB_BLOCK/REB_GROUP/REB_PATH lines in this switch + // where they mark their "binding" field.) + // + REBFUN* func = VAL_WORD_FUNC(v); + Queue_Mark_Function_Deep(func); + } + else { + // Bound to a specific context, keep that context alive. + // + REBCTX* context = VAL_WORD_CONTEXT(const_KNOWN(v)); + Queue_Mark_Context_Deep(context); + } + } + else { + // The word is unbound...make sure index is 0 in debug build. + // (it can be left uninitialized in release builds, for now) + // + assert(!GET_VAL_FLAG(v, VALUE_FLAG_RELATIVE)); + #if !defined(NDEBUG) + assert(v->payload.any_word.index == 0); + #endif + } + break; } + + case REB_PATH: + case REB_SET_PATH: + case REB_GET_PATH: + case REB_LIT_PATH: + case REB_BLOCK: + case REB_GROUP: { + if (IS_SPECIFIC(v)) { + REBSPC *specifier = VAL_SPECIFIER(const_KNOWN(v)); + if (specifier != SPECIFIED) + Queue_Mark_Context_Deep(CTX(specifier)); + } + else { + // We trust that if a relative array's context needs to make + // it into the transitive closure, that will be taken care + // of by a higher-up array reference that holds it. + // + REBFUN* func = VAL_RELATIVE(v); + Queue_Mark_Function_Deep(func); + } + + Queue_Mark_Array_Deep(VAL_ARRAY(v)); + break; } + + case REB_BINARY: + case REB_STRING: + case REB_FILE: + case REB_EMAIL: + case REB_URL: + case REB_TAG: + case REB_BITSET: { + REBSER *series = VAL_SERIES(v); + assert(SER_WIDE(series) <= sizeof(REBUNI)); + Mark_Rebser_Only(series); + break; } + + case REB_HANDLE: { // See %sys-handle.h + REBARR *singular = v->extra.singular; + if (singular == NULL) { + // + // This HANDLE! was created with Init_Handle_Simple. There is + // no GC interaction. + } + else { + // Handle was created with Init_Handle_Managed. It holds a + // REBSER node that contains exactly one handle, and the actual + // data for the handle lives in that shared location. There is + // nothing the GC needs to see inside a handle. + // + SER(singular)->header.bits |= NODE_FLAG_MARKED; + + #if !defined(NDEBUG) + assert(ARR_LEN(singular) == 1); + RELVAL *single = ARR_HEAD(singular); + assert(IS_HANDLE(single)); + assert(single->extra.singular == v->extra.singular); + if (v != single) { + // + // In order to make it clearer that individual handles do not + // hold the shared data (there'd be no way to update all the + // references at once), the data pointers in all but the + // shared singular value are NULL. + // + if (GET_VAL_FLAG(v, HANDLE_FLAG_CFUNC)) + assert( + IS_CFUNC_TRASH_DEBUG(v->payload.handle.data.cfunc) + ); + else + assert( + IS_POINTER_TRASH_DEBUG(v->payload.handle.data.pointer) + ); + } + #endif + } + break; } + + case REB_IMAGE: + Mark_Rebser_Only(VAL_SERIES(v)); + break; + + case REB_VECTOR: + Mark_Rebser_Only(VAL_SERIES(v)); + break; + + case REB_BLANK: + case REB_LOGIC: + case REB_INTEGER: + case REB_DECIMAL: + case REB_PERCENT: + case REB_MONEY: + case REB_CHAR: + break; + + case REB_PAIR: { + // + // Ren-C's PAIR! uses a special kind of REBSER that does no additional + // memory allocation, but embeds two REBVALs in the REBSER itself. + // A REBVAL has a REBUPT-sized header at the beginning of its struct, + // just like a REBSER, and the NODE_FLAG_MARKED bit is a 0 + // if unmarked...so it can stealthily participate in the marking + // process, as long as the bit is cleared at the end. + // + REBSER *pairing = cast(REBSER*, PAIRING_KEY(v->payload.pair)); + pairing->header.bits |= NODE_FLAG_MARKED; // read via REBSER + break; } + + case REB_TUPLE: + case REB_TIME: + case REB_DATE: + break; + + case REB_MAP: { + REBMAP* map = VAL_MAP(v); + Queue_Mark_Map_Deep(map); + break; + } + + case REB_DATATYPE: + // Type spec is allowed to be NULL. See %typespec.r file + if (VAL_TYPE_SPEC(v)) + Queue_Mark_Array_Deep(VAL_TYPE_SPEC(v)); + break; + + case REB_TYPESET: + // + // Not all typesets have symbols--only those that serve as the + // keys of objects (or parameters of functions) + // + if (v->extra.key_spelling != NULL) + Mark_Rebser_Only(v->extra.key_spelling); + break; + + case REB_VARARGS: { + // + // Binding may be NULL if the varargs was a MAKE VARARGS! and hasn't + // been passed through any parameter. Otherwise it is the frame + // context where the param and arg live (possibly expired). + // + REBARR *binding = v->extra.binding; + if (binding != NULL) { + if (IS_ARRAY_MANAGED(binding)) + Queue_Mark_Context_Deep(CTX(v->extra.binding)); + else { + // !!! Should assert that the binding is to a frame that is + // in mid-fulfillment on the stack + } + } + + // The data feed is either a frame context or a singular block which + // holds the shared index among all same varargs into that array. + // + REBARR *feed = v->payload.varargs.feed; + assert(GET_SER_FLAG(feed, ARRAY_FLAG_VARLIST) || ARR_LEN(feed) == 1); + if (IS_ARRAY_MANAGED(feed)) + Queue_Mark_Array_Subclass_Deep(feed); + else { + // !!! Should also assert that this is a frame in mid-fulfillment + // on the stack. + // + assert(GET_SER_FLAG(feed, ARRAY_FLAG_VARLIST)); + } + break; } + + case REB_OBJECT: + case REB_FRAME: + case REB_MODULE: + case REB_ERROR: + case REB_PORT: { + REBCTX *context = VAL_CONTEXT(v); + Queue_Mark_Context_Deep(context); + + // Currently the "binding" in a context is only used by FRAME! to + // preserve the binding of the FUNCTION! value that spawned that + // frame. Currently that binding is typically NULL inside of a + // function's REBVAL unless it is a definitional RETURN or LEAVE. + // + // !!! Expanded usages may be found in other situations that mix an + // archetype with an instance (e.g. an archetypal function body that + // could apply to any OBJECT!, but the binding cheaply makes it + // a method for that object.) + // + REBARR *binding = VAL_BINDING(v); + if (binding != NULL) { + assert(CTX_TYPE(context) == REB_FRAME); + + #if !defined(NDEBUG) + if (CTX_VARS_UNAVAILABLE(context)) { + // + // !!! It seems a bit wasteful to keep alive the binding of a + // stack frame you can no longer get values out of. But + // However, FUNCTION-OF still works on a FRAME! value after + // the function is finished, if the FRAME! value was kept. + // And that needs to give back a correct binding. + // + } + else { + struct Reb_Frame *f = CTX_FRAME_IF_ON_STACK(context); + if (f != NULL) // comes from execution, not MAKE FRAME! + assert(binding == f->binding); + } + #endif + + Queue_Mark_Array_Subclass_Deep(binding); + } + + REBFUN *phase = v->payload.any_context.phase; + if (phase != NULL) { + if (CTX_TYPE(context) != REB_FRAME) + panic (context); + Queue_Mark_Function_Deep(phase); + } + + #if !defined(NDEBUG) + REBVAL *archetype = CTX_VALUE(context); + assert(CTX_TYPE(context) == VAL_TYPE(v)); + assert(VAL_CONTEXT(archetype) == context); + assert(VAL_CONTEXT_META(archetype) == CTX_META(context)); + #endif + + // Note: for VAL_CONTEXT_FRAME, the FRM_CALL is either on the stack + // (in which case it's already taken care of for marking) or it + // has gone bad, in which case it should be ignored. + + break; } + + case REB_GOB: + Queue_Mark_Gob_Deep(VAL_GOB(v)); + break; + + case REB_EVENT: + Queue_Mark_Event_Deep(v); + break; + + case REB_STRUCT: { + // + // The struct gets its GC'able identity and is passable by one + // pointer from the fact that it is a single-element array that + // contains the REBVAL of the struct itself. (Because it is + // "singular" it is only a REBSER node--no data allocation.) + // + Queue_Mark_Array_Deep(VAL_STRUCT(v)); + + // The schema is the hierarchical description of the struct. + // + REBFLD *schema = SER(VAL_STRUCT(v))->link.schema; + assert(FLD_IS_STRUCT(schema)); + Queue_Mark_Array_Deep(schema); + + // The symbol needs to be GC protected, but only fields have them + // + assert(FLD_NAME(schema) == NULL); + + // The data series needs to be marked. It needs to be marked + // even for structs that aren't at the 0 offset--because their + // lifetime can be longer than the struct which they represent + // a "slice" out of. + // + // Note this may be a singular array handle, or it could be a BINARY! + // + Mark_Rebser_Only(v->payload.structure.data); + break; } + + case REB_LIBRARY: { + Queue_Mark_Array_Deep(VAL_LIBRARY(v)); + REBCTX *meta = VAL_LIBRARY_META(v); + if (meta != NULL) + Queue_Mark_Context_Deep(meta); + break; } + + case REB_MAX_VOID: + // + // Not an actual ANY-VALUE! "value", just a void cell. Instead of + // this "Opt"ional routine, use Queue_Mark_Value_Deep() on slots + // that should not be void. + // + break; + + default: + panic (v); + } + +#if !defined(NDEBUG) + in_mark = FALSE; #endif +} + +inline static void Queue_Mark_Value_Deep(const RELVAL *v) +{ +#if !defined(NDEBUG) + if (IS_VOID(v)) + panic (v); +#endif + Queue_Mark_Opt_Value_Deep(v); +} + + +// +// Propagate_All_GC_Marks: C +// +// The Mark Stack is a series containing series pointers. They have already +// had their SERIES_FLAG_MARK set to prevent being added to the stack multiple +// times, but the items they can reach are not necessarily marked yet. +// +// Processing continues until all reachable items from the mark stack are +// known to be marked. +// +static void Propagate_All_GC_Marks(void) +{ + assert(!in_mark); + + while (SER_LEN(GC_Mark_Stack) != 0) { + SET_SERIES_LEN(GC_Mark_Stack, SER_LEN(GC_Mark_Stack) - 1); // still ok + + // Data pointer may change in response to an expansion during + // Mark_Array_Deep_Core(), so must be refreshed on each loop. + // + REBARR *a = *SER_AT(REBARR*, GC_Mark_Stack, SER_LEN(GC_Mark_Stack)); + + // Termination is not required in the release build (the length is + // enough to know where it ends). But overwrite with trash in debug. + // + TRASH_POINTER_IF_DEBUG( + *SER_AT(REBARR*, GC_Mark_Stack, SER_LEN(GC_Mark_Stack)) + ); + + // We should have marked this series at queueing time to keep it from + // being doubly added before the queue had a chance to be processed + // + assert(Is_Rebser_Marked(SER(a))); + + #ifdef HEAVY_CHECKS + // + // The GC is a good general hook point that all series which have been + // managed will go through, so it's a good time to assert properties + // about the array. + // + ASSERT_ARRAY(a); + #else + // + // For a lighter check, make sure it's marked as a value-bearing array + // and that it hasn't been freed. + // + assert(GET_SER_FLAG(a, SERIES_FLAG_ARRAY)); + assert(!IS_FREE_NODE(SER(a))); + #endif + + RELVAL *v = ARR_HEAD(a); + + if (GET_SER_FLAG(a, ARRAY_FLAG_PARAMLIST)) { + // + // These queueings cannot be done in Queue_Mark_Function_Deep + // because of the potential for overflowing the C stack with calls + // to Queue_Mark_Function_Deep. + + REBARR *body_holder = v->payload.function.body_holder; + Queue_Mark_Singular_Array(body_holder); + + REBCTX *exemplar = SER(body_holder)->link.exemplar; + if (exemplar != NULL) + Queue_Mark_Context_Deep(exemplar); + + REBCTX *meta = SER(a)->link.meta; + if (meta != NULL) + Queue_Mark_Context_Deep(meta); + + REBARR *facade = SER(a)->misc.facade; + Queue_Mark_Array_Subclass_Deep(facade); + + assert(IS_FUNCTION(v)); + assert(v->extra.binding == NULL); // archetypes have no binding + ++v; // function archetype completely marked by this process + } + else if (GET_SER_FLAG(a, ARRAY_FLAG_VARLIST)) { + // + // These queueings cannot be done in Queue_Mark_Context_Deep + // because of the potential for overflowing the C stack with calls + // to Queue_Mark_Context_Deep. + + REBARR *keylist = SER(a)->link.keylist; + assert(keylist == CTX_KEYLIST(CTX(a))); + Queue_Mark_Array_Subclass_Deep(keylist); // might be paramlist + + REBCTX *meta = SER(keylist)->link.meta; + if (meta != NULL) + Queue_Mark_Context_Deep(meta); + + assert(ANY_CONTEXT(v)); + assert(v->extra.binding == NULL); // archetypes have no binding + ++v; // context archtype completely marked by this process + } + else if (GET_SER_FLAG(a, ARRAY_FLAG_PAIRLIST)) { + // + // There was once a "small map" optimization that wouldn't + // produce a hashlist for small maps and just did linear search. + // @giuliolunati deleted that for the time being because it + // seemed to be a source of bugs, but it may be added again...in + // which case the hashlist may be NULL. + // + REBSER *hashlist = SER(a)->link.hashlist; + assert(hashlist != NULL); + + Mark_Rebser_Only(hashlist); + } + + if (GET_SER_INFO(a, SERIES_INFO_INACCESSIBLE)) { + // + // At present the only inaccessible arrays are expired frames of + // functions with stack-bound arg and local lifetimes. They are + // just singular REBARRs with the FRAME! archetype value. + // + assert(GET_SER_FLAG(a, ARRAY_FLAG_VARLIST)); + assert(IS_FRAME(ARR_HEAD(a))); + assert(GET_SER_INFO(a, CONTEXT_INFO_STACK)); + continue; + } + + for (; NOT_END(v); ++v) { + Queue_Mark_Opt_Value_Deep(v); + // + #if !defined(NDEBUG) + // + // Voids are illegal in most arrays, but the varlist of a context + // uses void values to denote that the variable is not set. Also + // reified C va_lists as Do_Core() sources can have them. + // + if (NOT(IS_BLANK_RAW(v)) && IS_VOID(v)) { + if( + !GET_SER_FLAG(a, ARRAY_FLAG_VARLIST) + && !GET_SER_FLAG(a, ARRAY_FLAG_VOIDS_LEGAL) + ) + panic(a); + } + #endif + } + } +} + -static void Mark_Series(REBSER *series, REBCNT depth); +// +// Reify_Any_C_Valist_Frames: C +// +// Some of the call stack frames may have been invoked with a C function call +// that took a comma-separated list of REBVAL (the way printf works, a +// variadic "va_list"). +// +// http://en.cppreference.com/w/c/variadic +// +// Although it's a list of REBVAL*, these call frames have no REBARR series +// behind. Yet they still need to be enumerated to protect the values coming +// up in the later DO/NEXTs. But enumerating a C va_list can't be undone. +// The REBVAL* is lost if it isn't saved, and these frames may be in +// mid-evaluation. +// +// Hence, the garbage collector has to "reify" the remaining portion of the +// va_list into a REBARR before starting the GC. Then the rest of the +// evaluation happens on that array. +// +static void Reify_Any_C_Valist_Frames(void) +{ + // IMPORTANT: This must be done *before* any of the mark/sweep logic + // begins, because it creates new arrays. In the future it may be + // possible to introduce new series in mid-garbage collection (which would + // be necessary for an incremental garbage collector), but for now the + // feature is not supported. + // + ASSERT_NO_GC_MARKS_PENDING(); + + REBFRM *f = FS_TOP; + for (; f != NULL; f = f->prior) { + if (f->flags.bits & DO_FLAG_VA_LIST) { + const REBOOL truncated = TRUE; + Reify_Va_To_Array_In_Frame(f, truncated); + } + } +} + + +// +// Mark_Root_Series: C +// +// In Ren-C, there is a concept of there being an open number of GC roots. +// Through the API, each cell held by a "paired" which is under GC management +// is considered to be a root. +// +// There is also a special ability of a paired, such that if the "key" is +// a frame with a certain bit set, then it will tie its lifetime to the +// lifetime of that frame on the stack. (Not to the lifetime of the FRAME! +// value itself, which could be indefinite.) +// +static void Mark_Root_Series(void) +{ + REBSEG *seg; + for (seg = Mem_Pools[SER_POOL].segs; seg; seg = seg->next) { + REBSER *s = cast(REBSER *, seg + 1); + REBCNT n; + for (n = Mem_Pools[SER_POOL].units; n > 0; --n, ++s) { + if (IS_FREE_NODE(s)) + continue; + + assert(NOT(Is_Rebser_Marked(s))); // can't be marked yet + + if (NOT(s->header.bits & NODE_FLAG_ROOT)) + continue; + + // If something is marked as a root, then it has its contents + // GC managed...even if it is not itself a candidate for GC. + + if (s->header.bits & NODE_FLAG_CELL) { + // + // There is a special feature of root paired series, which + // is that if the "key" is a frame marked in a certain way, + // it will tie its lifetime to that of the execution of that + // frame. When the frame is done executing, it will no + // longer preserve the paired. + // + // (Note: This does not have anything to do with the lifetime + // of the FRAME! value itself, which could be indefinite.) + // + // !!! Does it need to check for pending? Could it be set + // up such that you can't make an owning frame that's in + // a pending state? + // + REBVAL *key = cast(REBVAL*, s); + REBVAL *paired = key + 1; + if ( + IS_FRAME(key) + && GET_VAL_FLAG(key, ANY_CONTEXT_FLAG_OWNS_PAIRED) + && !Is_Context_Running_Or_Pending(VAL_CONTEXT(key)) + ){ + Free_Pairing(paired); // don't consider a root + continue; + } + + // It's alive and a root. Pick up its dependencies deeply. + // Note that ENDs are allowed because for instance, a DO + // might be executed with the pairing as the OUT slot (since + // it is memory guaranteed not to relocate) + // + Mark_Rebser_Only(s); + Queue_Mark_Value_Deep(key); + if (NOT_END(paired)) + Queue_Mark_Value_Deep(paired); + } + else { + // We have to do the queueing based on whatever type of series + // this is. So if it's a context, we have to get the + // keylist...etc. + // + if (GET_SER_FLAG(s, SERIES_FLAG_ARRAY)) + Queue_Mark_Array_Subclass_Deep(ARR(s)); + else + Mark_Rebser_Only(s); + } + } + } + + Propagate_All_GC_Marks(); +} -/*********************************************************************** -** -*/ static void Mark_Gob(REBGOB *gob, REBCNT depth) -/* -***********************************************************************/ +// +// Mark_Data_Stack: C +// +// The data stack logic is that it is contiguous values with no END markers +// except at the array end. Bumping up against that END signal is how the +// stack knows when it needs to grow. +// +// But every drop of the stack doesn't overwrite the dropped value. Since the +// values are not END markers, they are considered fine as far as a NOT_END() +// test is concerned to indicate unused capacity. So the values are good +// for the testing purpose, yet the GC doesn't want to consider those to be +// "live" references. So rather than to a full Queue_Mark_Array_Deep() on +// the capacity of the data stack's underlying array, it begins at DS_TOP. +// +static void Mark_Data_Stack(void) { - REBGOB **pane; - REBCNT i; + assert(IS_UNREADABLE_IF_DEBUG(&DS_Movable_Base[0])); + + REBVAL *stackval = DS_TOP; + for (; stackval != &DS_Movable_Base[0]; --stackval) { + // + // During path evaluation, function refinements are pushed to the + // data stack as WORD!. If the order of definition of refinements + // in the function spec doesn't match the order of usage, then the + // refinement will need to be revisited. The WORD! is converted + // into a "pickup" which stores the parameter and argument position. + // These are only legal on the data stack, and are skipped by the GC. + // + if (VAL_TYPE(stackval) == REB_0_PICKUP) + continue; + + Queue_Mark_Value_Deep(stackval); + } + + Propagate_All_GC_Marks(); +} - if (IS_GOB_MARK(gob)) return; - MARK_GOB(gob); +// +// Mark_Symbol_Series: C +// +// Mark symbol series. These canon words for SYM_XXX are the only ones that +// are never candidates for GC (until shutdown). All other symbol series may +// go away if no words, parameters, object keys, etc. refer to them. +// +static void Mark_Symbol_Series(void) +{ + REBSTR **canon = SER_HEAD(REBSTR*, PG_Symbol_Canons); + assert(IS_POINTER_TRASH_DEBUG(*canon)); // SYM_0 is for all non-builtin words + ++canon; + for (; *canon != NULL; ++canon) + Mark_Rebser_Only(*canon); - if (GOB_PANE(gob)) { - MARK_SERIES(GOB_PANE(gob)); - pane = GOB_HEAD(gob); - for (i = 0; i < GOB_TAIL(gob); i++, pane++) { - Mark_Gob(*pane, depth); - } - } + ASSERT_NO_GC_MARKS_PENDING(); // doesn't ues any queueing +} - if (GOB_PARENT(gob)) Mark_Gob(GOB_PARENT(gob), depth); - if (GOB_CONTENT(gob)) { - if (GOB_TYPE(gob) >= GOBT_IMAGE && GOB_TYPE(gob) <= GOBT_STRING) { - MARK_SERIES(GOB_CONTENT(gob)); - } else if (GOB_TYPE(gob) >= GOBT_DRAW && GOB_TYPE(gob) <= GOBT_EFFECT) { - CHECK_MARK(GOB_CONTENT(gob), depth); - } - } +// +// Mark_Natives: C +// +// For each native C implemenation, a REBVAL is created during init to +// represent it as a FUNCTION!. These are kept in a global array and are +// protected from GC. It might not technically be necessary to do so for +// all natives, but at least some have their paramlists referenced by the +// core code (such as RETURN). +// +static void Mark_Natives(void) +{ + REBCNT n; + for (n = 0; n < NUM_NATIVES; ++n) + Queue_Mark_Value_Deep(&Natives[n]); - if (GOB_DATA(gob) && GOB_DTYPE(gob) && GOB_DTYPE(gob) != GOBD_INTEGER) { - CHECK_MARK(GOB_DATA(gob), depth); - } + Propagate_All_GC_Marks(); } -/*********************************************************************** -** -*/ static void Mark_Event(REBVAL *value, REBCNT depth) -/* -***********************************************************************/ +// +// Mark_Guarded_Nodes: C +// +// Mark series and values that have been temporarily protected from garbage +// collection with PUSH_GUARD_SERIES and PUSH_GUARD_VALUE. +// +// Note: If the REBSER is actually a REBCTX, REBFUN, or REBARR then the +// reachable values for the series will be guarded appropriate to its type. +// (e.g. guarding a REBSER of an array will mark the values in that array, +// not just shallow mark the REBSER node) +// +static void Mark_Guarded_Nodes(void) { - REBREQ *req; - - if ( - IS_EVENT_MODEL(value, EVM_PORT) - || IS_EVENT_MODEL(value, EVM_OBJECT) - || (VAL_EVENT_TYPE(value) == EVT_DROP_FILE && GET_FLAG(VAL_EVENT_FLAGS(value), EVF_COPIED)) - ) { - // The ->ser field of the REBEVT is void*, so we must cast - // Comment says it is a "port or object" - CHECK_MARK((REBSER*)VAL_EVENT_SER(value), depth); - } - - if (IS_EVENT_MODEL(value, EVM_DEVICE)) { - // In the case of being an EVM_DEVICE event type, the port! will - // not be in VAL_EVENT_SER of the REBEVT structure. It is held - // indirectly by the REBREQ ->req field of the event, which - // in turn possibly holds a singly linked list of other requests. - req = VAL_EVENT_REQ(value); - - while(req) { - // The ->port field of the REBREQ is void*, so we must cast - // Comment says it is "link back to REBOL port object" - if (req->port) CHECK_MARK((REBSER*)req->port, depth); - req = req->next; - } - } -} - -/*********************************************************************** -** -*/ static void Mark_Devices(REBCNT depth) -/* -** Mark all devices. Search for pending requests. -** -***********************************************************************/ + REBNOD **np = SER_HEAD(REBNOD*, GC_Guarded); + REBCNT n = SER_LEN(GC_Guarded); + for (; n > 0; --n, ++np) { + REBNOD *node = *np; + if (node->header.bits & NODE_FLAG_CELL) { // a value cell + if (NOT(node->header.bits & NODE_FLAG_END)) + Queue_Mark_Opt_Value_Deep(cast(REBVAL*, node)); + } + else { // a series + REBSER *s = cast(REBSER*, node); + if (GET_SER_FLAG(s, SERIES_FLAG_ARRAY)) + Queue_Mark_Array_Subclass_Deep(ARR(s)); + else + Mark_Rebser_Only(s); + } + Propagate_All_GC_Marks(); + } +} + + +// +// Mark_Frame_Stack_Deep: C +// +// Mark values being kept live by all call frames. If a function is running, +// then this will keep the function itself live, as well as the arguments. +// There is also an "out" slot--which may point to an arbitrary REBVAL cell +// on the C stack. The out slot is initialized to an END marker at the +// start of every function call, so that it won't be uninitialized bits +// which would crash the GC...but it must be turned into a value (or a void) +// by the time the function is finished running. +// +// Since function argument slots are not pre-initialized, how far the function +// has gotten in its fulfillment must be taken into account. Only those +// argument slots through points of fulfillment may be GC protected. +// +// This should be called at the top level, and not from inside a +// Propagate_All_GC_Marks(). All marks will be propagated. +// +static void Mark_Frame_Stack_Deep(void) { - int d; - REBDEV *dev; - REBREQ *req; - REBDEV **devices = Host_Lib->devices; - - for (d = 0; d < RDI_MAX; d++) { - dev = devices[d]; - if (dev) - for (req = dev->pending; req; req = req->next) - if (req->port) CHECK_MARK((REBSER*)req->port, depth); - } -} - -/*********************************************************************** -** -*/ static void Mark_Series(REBSER *series, REBCNT depth) -/* -** Mark all series reachable from the block. -** -***********************************************************************/ + REBFRM *f = TG_Frame_Stack; + + for (; f != NULL; f = f->prior) { + assert(f->eval_type <= REB_MAX_VOID); + + // Should have taken care of reifying all the VALIST on the stack + // earlier in the recycle process (don't want to create new arrays + // once the recycling has started...) + // + assert(f->pending != VA_LIST_PENDING); + + ASSERT_ARRAY_MANAGED(f->source.array); + Queue_Mark_Array_Deep(f->source.array); + + // END is possible, because the frame could be sitting at the end of + // a block when a function runs, e.g. `do [zero-arity]`. That frame + // will stay on the stack while the zero-arity function is running. + // The array still might be used in an error, so can't GC it. + // + if (f->value && NOT_END(f->value) && Is_Value_Managed(f->value)) + Queue_Mark_Value_Deep(f->value); + + if (f->specifier != SPECIFIED) + Queue_Mark_Context_Deep(CTX(f->specifier)); + + if (NOT_END(f->out)) // never NULL, always initialized bit pattern + Queue_Mark_Opt_Value_Deep(f->out); + + if (NOT(Is_Any_Function_Frame(f))) { + // + // Consider something like `eval copy quote (recycle)`, because + // while evaluating the group it has no anchor anywhere in the + // root set and could be GC'd. The Reb_Frame's array ref is it. + // + continue; + } + + if (NOT_END(&f->cell)) + Queue_Mark_Opt_Value_Deep(&f->cell); + + Queue_Mark_Function_Deep(f->phase); // never NULL + Mark_Rebser_Only(f->label); // also never NULL + + if (!Is_Function_Frame_Fulfilling(f)) { + assert(IS_END(f->param)); // indicates function is running + + // refine and special can be used to GC protect an arbitrary + // value while a function is running, currently. (A more + // important purpose may come up...) + + if (NOT_END(f->refine) && Is_Value_Managed(f->refine)) + Queue_Mark_Opt_Value_Deep(f->refine); + + if (NOT_END(f->special) && Is_Value_Managed(f->special)) + Queue_Mark_Opt_Value_Deep(f->special); + } + + // Need to keep the label symbol alive for error messages/stacktraces + // + Mark_Rebser_Only(f->label); + + // We need to GC protect the values in the args no matter what, + // but it might not be managed yet (e.g. could still contain garbage + // during argument fulfillment). But if it is managed, then it needs + // to be handed to normal GC. + // + if (f->varlist != NULL && IS_ARRAY_MANAGED(f->varlist)) + Queue_Mark_Context_Deep(CTX(f->varlist)); + + // (Although the above will mark the varlist, it may not mark the + // values...because it may be a single element array that merely + // points at the stackvars. Queue_Mark_Context expects stackvars + // to be marked separately.) + + // The slots may be stack based or dynamic. Mark in use but only + // as far as parameter filling has gotten (may be garbage bits + // past that). Could also be an END value of an in-progress arg + // fulfillment, but in that case it is protected by the evaluating + // frame's f->out. + // + // Refinements need special treatment, and also consideration + // of if this is the "doing pickups" or not. If doing pickups + // then skip the cells for pending refinement arguments. + // + REBVAL *param = FUNC_FACADE_HEAD(f->phase); + REBVAL *arg = f->args_head; // may be stack or dynamic + for (; NOT_END(param); ++param, ++arg) { + if (param == f->param && !f->doing_pickups) + break; // protect arg for current param, but no further + + assert(!IS_UNREADABLE_IF_DEBUG(arg) || f->doing_pickups); + + Queue_Mark_Opt_Value_Deep(arg); + } + assert(IS_END(param) ? IS_END(arg) : TRUE); // may not enforce + + Propagate_All_GC_Marks(); + } +} + + +// +// Sweep_Series: C +// +// Scans all series nodes (REBSER structs) in all segments that are part of +// the SER_POOL. If a series had its lifetime management delegated to the +// garbage collector with MANAGE_SERIES(), then if it didn't get "marked" as +// live during the marking phase then free it. +// +static REBCNT Sweep_Series(void) { - REBCNT len; - REBSER *ser; - REBVAL *val; - - ASSERT(series != 0, RP_NULL_MARK_SERIES); - - if (SERIES_FREED(series)) return; // series data freed already - - MARK_SERIES(series); - - // If not a block, go no further - if (SERIES_WIDE(series) != sizeof(REBVAL)) return; - - ASSERT2(RP_SERIES_OVERFLOW, SERIES_TAIL(series) < SERIES_REST(series)); - - //Moved to end: ASSERT1(IS_END(BLK_TAIL(series)), RP_MISSING_END); - - //if (depth == 1 && series->label) Print("Marking %s", series->label); - - depth++; - - for (len = 0; len < series->tail; len++) { - val = BLK_SKIP(series, len); - - switch (VAL_TYPE(val)) { - - case REB_END: - // We should never reach the end before len above. - // Exception is the stack itself. - if (series != DS_Series) Crash(RP_UNEXPECTED_END); - break; - - case REB_UNSET: - case REB_TYPESET: - case REB_HANDLE: - break; - - case REB_DATATYPE: - if (VAL_TYPE_SPEC(val)) { // allow it to be zero - CHECK_MARK(VAL_TYPE_SPEC(val), depth); // check typespec.r file - } - break; - - case REB_ERROR: - // If it has an actual error object, then mark it. Otherwise, - // it is a THROW, and GC of a THROW value is invalid because - // it contains temporary values on the stack that could be - // above the current DSP (where the THROW was done). - if (VAL_ERR_NUM(val) > RE_THROW_MAX) { - if (VAL_ERR_OBJECT(val)) CHECK_MARK(VAL_ERR_OBJECT(val), depth); - } - // else Crash(RP_THROW_IN_GC); // !!!! in question - is it true? - break; - - case REB_TASK: // not yet implemented - break; - - case REB_FRAME: - // Mark special word list. Contains no pointers because - // these are special word bindings (to typesets if used). - if (VAL_FRM_WORDS(val)) MARK_SERIES(VAL_FRM_WORDS(val)); - if (VAL_FRM_SPEC(val)) {CHECK_MARK(VAL_FRM_SPEC(val), depth);} - break; - - case REB_PORT: - // Debug_Fmt("\n\nmark port: %x %d", val, VAL_TAIL(val)); - // Debug_Values(VAL_OBJ_VALUE(val,1), VAL_TAIL(val)-1, 100); - goto mark_obj; - - case REB_MODULE: - if (VAL_MOD_BODY(val)) CHECK_MARK(VAL_MOD_BODY(val), depth); - case REB_OBJECT: - // Object is just a block with special first value (context): -mark_obj: - if (!IS_MARK_SERIES(VAL_OBJ_FRAME(val))) { - Mark_Series(VAL_OBJ_FRAME(val), depth); - if (SERIES_TAIL(VAL_OBJ_FRAME(val)) >= 1) - ; //Dump_Frame(VAL_OBJ_FRAME(val), 4); - } - break; - - case REB_FUNCTION: - case REB_COMMAND: - case REB_CLOSURE: - case REB_REBCODE: - CHECK_MARK(VAL_FUNC_BODY(val), depth); - case REB_NATIVE: - case REB_ACTION: - case REB_OP: - CHECK_MARK(VAL_FUNC_SPEC(val), depth); - MARK_SERIES(VAL_FUNC_ARGS(val)); - // There is a problem for user define function operators !!! - // Their bodies are not GC'd! - break; - - case REB_WORD: // (and also used for function STACK backtrace frame) - case REB_SET_WORD: - case REB_GET_WORD: - case REB_LIT_WORD: - case REB_REFINEMENT: - case REB_ISSUE: - // Special word used in word frame, stack, or errors: - if (VAL_GET_OPT(val, OPTS_UNWORD)) break; - // Mark its context, if it has one: - if (VAL_WORD_INDEX(val) > 0 && NZ(ser = VAL_WORD_FRAME(val))) { - //if (SERIES_TAIL(ser) > 100) Dump_Word_Value(val); - CHECK_MARK(ser, depth); - } - // Possible bug above!!! We cannot mark relative words (negative - // index) because the frame pointer does not point to a context, - // it may point to a function body, native code, or action number. - // But, what if a function is GC'd during it's own evaluation, what - // keeps the function's code block from being GC'd? - break; - - case REB_NONE: - case REB_LOGIC: - case REB_INTEGER: - case REB_DECIMAL: - case REB_PERCENT: - case REB_MONEY: - case REB_TIME: - case REB_DATE: - case REB_CHAR: - case REB_PAIR: - case REB_TUPLE: - break; - - case REB_STRING: - case REB_BINARY: - case REB_FILE: - case REB_EMAIL: - case REB_URL: - case REB_TAG: - case REB_BITSET: - ser = VAL_SERIES(val); - if (SERIES_WIDE(ser) > sizeof(REBUNI)) - Crash(RP_BAD_WIDTH, sizeof(REBUNI), SERIES_WIDE(ser), VAL_TYPE(val)); - MARK_SERIES(ser); - break; - - case REB_IMAGE: - //MARK_SERIES(VAL_SERIES_SIDE(val)); //???? - MARK_SERIES(VAL_SERIES(val)); - break; - - case REB_VECTOR: - MARK_SERIES(VAL_SERIES(val)); - break; - - case REB_BLOCK: - case REB_PAREN: - case REB_PATH: - case REB_SET_PATH: - case REB_GET_PATH: - case REB_LIT_PATH: - ser = VAL_SERIES(val); - ASSERT(ser != 0, RP_NULL_SERIES); - if (IS_BARE_SERIES(ser)) { - MARK_SERIES(ser); - break; - } -#if (ALEVEL>0) - if (!IS_END(BLK_SKIP(ser, SERIES_TAIL(ser))) && ser != DS_Series) - Crash(RP_MISSING_END); + REBCNT count = 0; + + // Optimization here depends on SWITCH of a bank of 4 bits. + // + static_assert_c( + NODE_FLAG_MARKED == FLAGIT_LEFT(3) // 0x1 after right shift + && (NODE_FLAG_MANAGED == FLAGIT_LEFT(2)) // 0x2 after right shift + && (NODE_FLAG_FREE == FLAGIT_LEFT(1)) // 0x4 after right shift + && (NODE_FLAG_NODE == FLAGIT_LEFT(0)) // 0x8 after right shift + ); + + REBSEG *seg; + for (seg = Mem_Pools[SER_POOL].segs; seg != NULL; seg = seg->next) { + REBSER *s = cast(REBSER*, seg + 1); + REBCNT n; + for (n = Mem_Pools[SER_POOL].units; n > 0; --n, ++s) { + switch (LEFT_N_BITS(s->header.bits, 4)) { + case 0: + case 1: // 0x1 + case 2: // 0x2 + case 3: // 0x2 + 0x1 + case 4: // 0x4 + case 5: // 0x4 + 0x1 + case 6: // 0x4 + 0x2 + case 7: // 0x4 + 0x2 + 0x1 + // + // NODE_FLAG_NODE (0x8) is clear. This signature is + // reserved for UTF-8 strings (corresponding to valid ASCII + // values in the first byte). + // + panic (s); + + // v-- Everything below here has NODE_FLAG_NODE set (0x8) + + case 8: + // 0x8: unmanaged and unmarked, e.g. a series that was made + // with Make_Series() and hasn't been managed. It doesn't + // participate in the GC. Leave it as is. + // + break; + + case 9: + // 0x8 + 0x1: marked but not managed, this can't happen, + // because the marking itself asserts nodes are managed. + // + panic (s); + + case 10: + // 0x8 + 0x2: managed but didn't get marked, should be GC'd + // + // !!! It would be nice if we could have NODE_FLAG_CELL here + // as part of the switch, but see its definition for why it + // is at position 8 from left and not an earlier bit. + // + if (s->header.bits & NODE_FLAG_CELL) + Free_Node(SER_POOL, s); // Free_Pairing is for manuals + else + GC_Kill_Series(s); + ++count; + break; + + case 11: + // 0x8 + 0x2 + 0x1: managed and marked, so it's still live. + // Don't GC it, just clear the mark. + // + s->header.bits &= ~NODE_FLAG_MARKED; + break; + + // v-- Everything below this line has the two leftmost bits set + // in the header. In the *general* case this could be a valid + // first byte of a multi-byte sequence in UTF-8...so only the + // special bit pattern of the free case uses this. + + case 12: + // 0x8 + 0x4: free node, uses special illegal UTF-8 byte + // + assert(LEFT_8_BITS(s->header.bits) == FREED_SERIES_BYTE); + break; + + case 13: + // 0x8 + 0x4 + 0x1: "free unmanaged marked node" (?!) + // + panic (s); + + case 14: + // 0x8 + 0x4 + 0x2: "free managed unmarked node" (?!) + // + panic (s); + + case 15: + // 0x8 + 0x4 + 0x2 + 0x1: "free managed marked node" (?!) + // + panic (s); + } + } + } + + return count; +} + + +#if !defined(NDEBUG) + +// +// Fill_Sweeplist: C +// +REBCNT Fill_Sweeplist(REBSER *sweeplist) +{ + assert(SER_WIDE(sweeplist) == sizeof(REBNOD*)); + assert(SER_LEN(sweeplist) == 0); + + REBCNT count = 0; + + REBSEG *seg; + for (seg = Mem_Pools[SER_POOL].segs; seg != NULL; seg = seg->next) { + REBSER *s = cast(REBSER*, seg + 1); + REBCNT n; + for (n = Mem_Pools[SER_POOL].units; n > 0; --n, ++s) { + switch (LEFT_N_BITS(s->header.bits, 4)) { + case 9: // 0x8 + 0x1 + assert(IS_SERIES_MANAGED(s)); + if (Is_Rebser_Marked(s)) + Unmark_Rebser(s); + else { + EXPAND_SERIES_TAIL(sweeplist, 1); + *SER_AT(REBNOD*, sweeplist, count) = NOD(s); + ++count; + } + break; + + case 11: // 0x8 + 0x2 + 0x1 + // + // It's a cell which is managed where the key is not an END. + // This is a managed pairing, so mark bit should be heeded. + // + // !!! It is a REBNOD, but *not* a "series". + // + assert(IS_SERIES_MANAGED(s)); + if (Is_Rebser_Marked(s)) + Unmark_Rebser(s); + else { + EXPAND_SERIES_TAIL(sweeplist, 1); + *SER_AT(REBNOD*, sweeplist, count) = NOD(s); + ++count; + } + break; + } + } + } + + return count; +} + +#endif + + +// +// Recycle_Core: C +// +// Recycle memory no longer needed. If sweeplist is not NULL, then it needs +// to be a series whose width is sizeof(REBSER*), and it will be filled with +// the list of series that *would* be recycled. +// +REBCNT Recycle_Core(REBOOL shutdown, REBSER *sweeplist) +{ + // Ordinarily, it should not be possible to spawn a recycle during a + // recycle. But when debug code is added into the recycling code, it + // could cause a recursion. Be tolerant of such recursions to make that + // debugging easier...but make a note that it's not ordinarily legal. + // +#if !defined(NDEBUG) + if (GC_Recycling) { + printf("Recycle re-entry; should only happen in debug scenarios.\n"); + SET_SIGNAL(SIG_RECYCLE); + return 0; + } +#endif + + // If disabled by RECYCLE/OFF, exit now but set the pending flag. (If + // shutdown, ignore so recycling runs and can be checked for balance.) + // + if (!shutdown && GC_Disabled) { + SET_SIGNAL(SIG_RECYCLE); + return 0; + } + +#if !defined(NDEBUG) + GC_Recycling = TRUE; +#endif + + ASSERT_NO_GC_MARKS_PENDING(); + + Reify_Any_C_Valist_Frames(); + + +#if !defined(NDEBUG) + PG_Reb_Stats->Recycle_Counter++; + PG_Reb_Stats->Recycle_Series = Mem_Pools[SER_POOL].free; + + PG_Reb_Stats->Mark_Count = 0; #endif - if (SERIES_WIDE(ser) != sizeof(REBVAL) && SERIES_WIDE(ser) != 4 && SERIES_WIDE(ser) != 0) - Crash(RP_BAD_WIDTH, 16, SERIES_WIDE(ser), VAL_TYPE(val)); - CHECK_MARK(ser, depth); - break; - - case REB_MAP: - ser = VAL_SERIES(val); - CHECK_MARK(ser, depth); - if (ser->series) { - MARK_SERIES(ser->series); - } - break; - -#ifdef ndef - case REB_ROUTINE: - // Deal with the co-joined struct value... - CHECK_MARK(VAL_STRUCT_SPEC(VAL_ROUTINE_SPEC(val)), depth); - CHECK_MARK(VAL_STRUCT_VALS(VAL_ROUTINE_SPEC(val)), depth); - MARK_SERIES(VAL_STRUCT_DATA(VAL_ROUTINE_SPEC(val))); - MARK_SERIES(VAL_ROUTINE_SPEC_SER(val)); -//!!! if (Current_Closing_Library && VAL_ROUTINE_ID(val) == Current_Closing_Library) - VAL_ROUTINE_ID(val) = 0; // Invalidate the routine - break; + + // WARNING: This terminates an existing open block. This could be a + // problem if code is building a new value at the tail, but has not yet + // updated the TAIL marker. + // + TERM_ARRAY_LEN(BUF_COLLECT, ARR_LEN(BUF_COLLECT)); + + // MARKING PHASE: the "root set" from which we determine the liveness + // (or deadness) of a series. If we are shutting down, we do not mark + // several categories of series...but we do need to run the root marking. + // (In particular because that is when pairing series whose lifetimes + // are bound to frames will be freed, if the frame is expired.) + // + Mark_Root_Series(); + + if (!shutdown) { + Mark_Natives(); + Mark_Symbol_Series(); + + Mark_Data_Stack(); + + Mark_Guarded_Nodes(); + + Mark_Frame_Stack_Deep(); + + // Mark potential error object from callback! + if (!IS_BLANK_RAW(&Callback_Error)) { + assert(NOT_VAL_FLAG(&Callback_Error, VALUE_FLAG_RELATIVE)); + Queue_Mark_Value_Deep(&Callback_Error); + } + Propagate_All_GC_Marks(); + + Mark_Devices_Deep(); + + } + + // SWEEPING PHASE + + ASSERT_NO_GC_MARKS_PENDING(); + + REBCNT count = 0; + + if (sweeplist != NULL) { + #if defined(NDEBUG) + panic (sweeplist); + #else + count += Fill_Sweeplist(sweeplist); + #endif + } + else + count += Sweep_Series(); + + // !!! The intent is for GOB! to be unified in the REBNOD pattern, the + // way that the FFI structures were. So they are not included in the + // count, in order to help make the numbers returned consistent between + // when the sweeplist is used and not. + // + Sweep_Gobs(); + +#if !defined(NDEBUG) + // Compute new stats: + PG_Reb_Stats->Recycle_Series + = Mem_Pools[SER_POOL].free - PG_Reb_Stats->Recycle_Series; + PG_Reb_Stats->Recycle_Series_Total += PG_Reb_Stats->Recycle_Series; + PG_Reb_Stats->Recycle_Prior_Eval = Eval_Cycles; #endif - case REB_LIBRARY: - MARK_SERIES(VAL_LIBRARY_NAME(val)); -//!!! if (Current_Closing_Library && VAL_LIBRARY_ID(val) == Current_Closing_Library) - VAL_LIBRARY_ID(val) = 0; // Invalidate the library - break; - - case REB_STRUCT: - CHECK_MARK(VAL_STRUCT_SPEC(val), depth); // is a block - CHECK_MARK(VAL_STRUCT_VALS(val), depth); // " " - MARK_SERIES(VAL_STRUCT_DATA(val)); - break; - - case REB_GOB: - Mark_Gob(VAL_GOB(val), depth); - break; - - case REB_EVENT: - Mark_Event(val, depth); - break; - - default: - Crash(RP_DATATYPE+1, VAL_TYPE(val)); - } - } - -#if (ALEVEL>0) - if (!IS_END(BLK_SKIP(series, len)) && series != DS_Series) - Crash(RP_MISSING_END); + // Do not adjust task variables or boot strings in shutdown when they + // are being freed. + // + if (!shutdown) { + // + // !!! This code was added by Atronix to deal with frequent garbage + // collection, but the logic is not correct. The issue has been + // raised and is commented out pending a correct solution. + // + // https://github.com/zsx/r3/issues/32 + // + /*if (GC_Ballast <= VAL_INT32(TASK_BALLAST) / 2 + && VAL_INT64(TASK_BALLAST) < MAX_I32) { + //increasing ballast by half + VAL_INT64(TASK_BALLAST) /= 2; + VAL_INT64(TASK_BALLAST) *= 3; + } else if (GC_Ballast >= VAL_INT64(TASK_BALLAST) * 2) { + //reduce ballast by half + VAL_INT64(TASK_BALLAST) /= 2; + } + + // avoid overflow + if ( + VAL_INT64(TASK_BALLAST) < 0 + || VAL_INT64(TASK_BALLAST) >= MAX_I32 + ) { + VAL_INT64(TASK_BALLAST) = MAX_I32; + }*/ + + GC_Ballast = VAL_INT32(TASK_BALLAST); + + if (Reb_Opts->watch_recycle) + Debug_Fmt(RM_WATCH_RECYCLE, count); + } + + ASSERT_NO_GC_MARKS_PENDING(); + +#if !defined(NDEBUG) + GC_Recycling = FALSE; #endif + + return count; } -/*********************************************************************** -** -*/ static REBCNT Sweep_Series(void) -/* -** Free all unmarked series. -** -** Scans all series in all segments that are part of the -** SERIES_POOL. Free series that have not been marked. -** -***********************************************************************/ -{ - REBSEG *seg; - REBSER *series; - REBCNT n; - REBCNT count = 0; - - for (seg = Mem_Pools[SERIES_POOL].segs; seg; seg = seg->next) { - series = (REBSER *) (seg + 1); - for (n = Mem_Pools[SERIES_POOL].units; n > 0; n--) { - SKIP_WALL(series); - MUNG_CHECK(SERIES_POOL, series, sizeof(*series)); - if (!SERIES_FREED(series)) { - if (IS_FREEABLE(series)) { - Free_Series(series); - count++; - } else - UNMARK_SERIES(series); - } - series++; - SKIP_WALL(series); - } - } - - return count; -} - - -/*********************************************************************** -** -*/ static REBCNT Sweep_Gobs(void) -/* -** Free all unmarked gobs. -** -** Scans all gobs in all segments that are part of the -** GOB_POOL. Free gobs that have not been marked. -** -***********************************************************************/ +// +// Recycle: C +// +// Recycle memory no longer needed. +// +REBCNT Recycle(void) { - REBSEG *seg; - REBGOB *gob; - REBCNT n; - REBCNT count = 0; - - for (seg = Mem_Pools[GOB_POOL].segs; seg; seg = seg->next) { - gob = (REBGOB *) (seg + 1); - for (n = Mem_Pools[GOB_POOL].units; n > 0; n--) { -#ifdef MUNGWALL - gob = (gob *) (((REBYTE *)s)+MUNG_SIZE); - MUNG_CHECK(GOB_POOL, gob, sizeof(*gob)); + // Default to not passing the `shutdown` flag. + // + REBCNT n = Recycle_Core(FALSE, NULL); + +#ifdef DOUBLE_RECYCLE_TEST + // + // If there are two recycles in a row, then the second should not free + // any additional series that were not freed by the first. (It also + // shouldn't crash.) This is an expensive check, but helpful to try if + // it seems a GC left things in a bad state that crashed a later GC. + // + REBCNT n2 = Recycle_Core(FALSE, NULL); + assert(n2 == 0); #endif - if (IS_GOB_USED(gob)) { - if (IS_GOB_MARK(gob)) - UNMARK_GOB(gob); - else { - Free_Gob(gob); - count++; - } - } - gob++; -#ifdef MUNGWALL - gob = (gob *) (((REBYTE *)s)+MUNG_SIZE); + + return n; +} + + +// +// Guard_Node_Core: C +// +void Guard_Node_Core(const REBNOD *node) +{ +#if !defined(NDEBUG) + if (node->header.bits & NODE_FLAG_CELL) { + // + // It is a value. Cheap check: require that it already contain valid + // data when the guard call is made (even if GC isn't necessarily + // going to happen immediately, and value could theoretically become + // valid before then.) + // + const REBVAL* value = cast(const REBVAL*, node); + assert( + IS_END(value) + || IS_BLANK_RAW(value) + || VAL_TYPE(value) <= REB_MAX_VOID + ); + + #ifdef STRESS_CHECK_GUARD_VALUE_POINTER + // + // Technically we should never call this routine to guard a value + // that lives inside of a series. Not only would we have to guard the + // containing series, we would also have to lock the series from + // being able to resize and reallocate the data pointer. But this is + // a somewhat expensive check, so only feasible to run occasionally. + // + REBSER *containing = Try_Find_Containing_Series_Debug(value); + if (containing != NULL) + panic (containing); + #endif + } + else { + // It's a series. Does not ensure the series being guarded is + // managed, since it can be interesting to guard the managed + // *contents* of an unmanaged array. The calling wrappers ensure + // managedness or not. + } #endif - } - } - return count; + if (SER_FULL(GC_Guarded)) + Extend_Series(GC_Guarded, 8); + + *SER_AT( + const REBNOD*, + GC_Guarded, + SER_LEN(GC_Guarded) + ) = node; + + SET_SERIES_LEN(GC_Guarded, SER_LEN(GC_Guarded) + 1); } -/*********************************************************************** -** -*/ REBCNT Recycle(void) -/* -** Recycle memory no longer needed. -** -***********************************************************************/ +// +// Snapshot_All_Functions: C +// +// This routine can be used to get a list of all the functions in the system +// at a given moment in time. Be sure to protect this array from GC when +// enumerating if there is any chance the GC might run (e.g. if user code +// is called to process the function list) +// +REBARR *Snapshot_All_Functions(void) { - REBINT n; - REBSER **sp; - REBCNT count; - - //Debug_Num("GC", GC_Disabled); - - // If disabled, exit now but set the pending flag. - if (GC_Disabled || !GC_Active) { - SET_SIGNAL(SIG_RECYCLE); - //Print("pending"); - return 0; - } - - if (Reb_Opts->watch_recycle) Debug_Str(BOOT_STR(RS_WATCH, 0)); - - GC_Disabled = 1; - - PG_Reb_Stats->Recycle_Counter++; - PG_Reb_Stats->Recycle_Series = Mem_Pools[SERIES_POOL].free; - - PG_Reb_Stats->Mark_Count = 0; - - // WARNING: These terminate existing open blocks. This could - // be a problem if code is building a new value at the tail, - // but has not yet updated the TAIL marker. - DS_TERMINATE; // Update data stack tail -// SET_END(DS_NEXT); - VAL_BLK_TERM(TASK_BUF_EMIT); - VAL_BLK_TERM(TASK_BUF_WORDS); -//!!! SET_END(BLK_TAIL(Save_Value_List)); - - // Mark series stack (temp-saved series): - sp = (REBSER **)GC_Protect->data; - for (n = SERIES_TAIL(GC_Protect); n > 0; n--) { - Mark_Series(*sp++, 0); - } - - // Mark all special series: - sp = (REBSER **)GC_Series->data; - for (n = SERIES_TAIL(GC_Series); n > 0; n--) { - Mark_Series(*sp++, 0); - } - - // Mark the last MAX_SAFE "infant" series that were created. - // We must assume that infant blocks are valid - that they contain - // no partially valid datatypes (that are under construction). - for (n = 0; n < MAX_SAFE_SERIES; n++) { - REBSER *ser; - if (NZ(ser = GC_Infants[n])) { - //Dump_Series(ser, "Safe Series"); - Mark_Series(ser, 0); - } else break; - } - - // Mark all root series: - Mark_Series(VAL_SERIES(ROOT_ROOT), 0); - Mark_Series(Task_Series, 0); - - // Mark all devices: - Mark_Devices(0); - - count = Sweep_Series(); - count += Sweep_Gobs(); - - CHECK_MEMORY(4); - - // Compute new stats: - PG_Reb_Stats->Recycle_Series = Mem_Pools[SERIES_POOL].free - PG_Reb_Stats->Recycle_Series; - PG_Reb_Stats->Recycle_Series_Total += PG_Reb_Stats->Recycle_Series; - PG_Reb_Stats->Recycle_Prior_Eval = Eval_Cycles; - - // Reset stack to prevent invalid MOLD access: - RESET_TAIL(DS_Series); - - GC_Ballast = VAL_INT32(TASK_BALLAST); - GC_Disabled = 0; - - if (Reb_Opts->watch_recycle) Debug_Fmt(BOOT_STR(RS_WATCH, 1), count); - return count; -} - - -/*********************************************************************** -** -*/ void Save_Series(REBSER *series) -/* -***********************************************************************/ + REBDSP dsp_orig = DSP; + + REBSEG *seg; + for (seg = Mem_Pools[SER_POOL].segs; seg != NULL; seg = seg->next) { + REBSER *s = cast(REBSER*, seg + 1); + REBCNT n; + for (n = Mem_Pools[SER_POOL].units; n > 0; --n, ++s) { + switch (s->header.bits & 0x7) { + case 5: + // A managed REBSER which has no cell mask and is marked as + // *not* an END. This is the typical signature of what one + // would call an "ordinary managed REBSER". (For the meanings + // of other bits, see Sweep_Series.) + // + assert(IS_SERIES_MANAGED(s)); + if (GET_SER_FLAG(s, ARRAY_FLAG_PARAMLIST)) { + REBVAL *v = KNOWN(ARR_HEAD(ARR(s))); + assert(IS_FUNCTION(v)); + DS_PUSH(v); + } + break; + } + } + } + + return Pop_Stack_Values(dsp_orig); +} + + +// +// Startup_GC: C +// +// Initialize garbage collector. +// +void Startup_GC(void) { - if (SERIES_FULL(GC_Protect)) Extend_Series(GC_Protect, 8); - ((REBSER **)GC_Protect->data)[GC_Protect->tail++] = series; + assert(NOT(GC_Disabled)); + assert(NOT(GC_Recycling)); + + GC_Ballast = MEM_BALLAST; + + // Temporary series and values protected from GC. Holds node pointers. + // + GC_Guarded = Make_Series(15, sizeof(REBNOD*)); + + // The marking queue used in lieu of recursion to ensure that deeply + // nested structures don't cause the C stack to overflow. + // + GC_Mark_Stack = Make_Series(100, sizeof(REBARR*)); + TERM_SEQUENCE(GC_Mark_Stack); } -/*********************************************************************** -** -*/ void Guard_Series(REBSER *series) -/* -** A list of protected series, managed by specific removal. -** -***********************************************************************/ +// +// Shutdown_GC: C +// +void Shutdown_GC(void) { - LABEL_SERIES(series, "guarded"); - if (SERIES_FULL(GC_Series)) Extend_Series(GC_Series, 8); - ((REBSER **)GC_Series->data)[GC_Series->tail++] = series; + Free_Series(GC_Guarded); + Free_Series(GC_Mark_Stack); } -/*********************************************************************** -** -*/ void Loose_Series(REBSER *series) -/* -** Remove a series from the protected list. -** -***********************************************************************/ +//=////////////////////////////////////////////////////////////////////////=// +// +// DEPRECATED HOOKS INTO THE CORE GARBAGE COLLECTOR +// +//=////////////////////////////////////////////////////////////////////////=// + +// +// Queue_Mark_Gob_Deep: C +// +// 'Queue' refers to the fact that after calling this routine, +// one will have to call Propagate_All_GC_Marks() to have the +// deep transitive closure be guaranteed fully marked. +// +// Note: only referenced blocks are queued, the GOB structure +// itself is processed via recursion. Deeply nested GOBs could +// in theory overflow the C stack. +// +static void Queue_Mark_Gob_Deep(REBGOB *gob) { - REBSER **sp; - REBCNT n; - - LABEL_SERIES(series, "unguarded"); - sp = (REBSER **)GC_Series->data; - for (n = 0; n < SERIES_TAIL(GC_Series); n++) { - if (sp[n] == series) { - Remove_Series(GC_Series, n, sizeof(REBSER *)); - break; - } - } -} - - -/*********************************************************************** -** -*/ void Init_Memory(REBINT scale) -/* -** Initialize memory system. -** -***********************************************************************/ + REBGOB **pane; + REBCNT i; + + if (IS_GOB_MARK(gob)) return; + + MARK_GOB(gob); + + if (GOB_PANE(gob)) { + Mark_Rebser_Only(GOB_PANE(gob)); + pane = GOB_HEAD(gob); + for (i = 0; i < GOB_LEN(gob); i++, pane++) + Queue_Mark_Gob_Deep(*pane); + } + + if (GOB_PARENT(gob)) Queue_Mark_Gob_Deep(GOB_PARENT(gob)); + + if (GOB_CONTENT(gob)) { + if (GOB_TYPE(gob) >= GOBT_IMAGE && GOB_TYPE(gob) <= GOBT_STRING) + Mark_Rebser_Only(GOB_CONTENT(gob)); + else if (GOB_TYPE(gob) >= GOBT_DRAW && GOB_TYPE(gob) <= GOBT_EFFECT) + Queue_Mark_Array_Deep(ARR(GOB_CONTENT(gob))); + } + + if (GOB_DATA(gob)) { + switch (GOB_DTYPE(gob)) { + case GOBD_INTEGER: + case GOBD_NONE: + default: + break; + case GOBD_OBJECT: + Queue_Mark_Context_Deep(CTX(GOB_DATA(gob))); + break; + case GOBD_STRING: + case GOBD_BINARY: + Mark_Rebser_Only(GOB_DATA(gob)); + break; + case GOBD_BLOCK: + Queue_Mark_Array_Deep(ARR(GOB_DATA(gob))); + } + } +} + + +// +// Sweep_Gobs: C +// +// Free all unmarked gobs. +// +// Scans all gobs in all segments that are part of the +// GOB_POOL. Free gobs that have not been marked. +// +static REBCNT Sweep_Gobs(void) { - GC_Active = 0; // TRUE when recycle is enabled (set by RECYCLE func) - GC_Disabled = 0; // GC disabled counter for critical sections. - GC_Ballast = MEM_BALLAST; - GC_Last_Infant = 0; // Keep the last N series safe from GC. - GC_Infants = Make_Mem((MAX_SAFE_SERIES + 2) * sizeof(REBSER*)); // extra + REBCNT count = 0; + + REBSEG *seg; + for (seg = Mem_Pools[GOB_POOL].segs; seg; seg = seg->next) { + REBGOB *gob = cast(REBGOB*, seg + 1); + + REBCNT n; + for (n = Mem_Pools[GOB_POOL].units; n > 0; --n, ++gob) { + if (IS_FREE_NODE(gob)) // unused REBNOD + continue; + + if (IS_GOB_MARK(gob)) + UNMARK_GOB(gob); + else { + Free_Node(GOB_POOL, gob); + + // GC_Ballast is of type REBINT, which might be long + // and REB_I32_ADD_OF takes (int*) + // it's illegal to convert form (long*) to (int*) in C++ + i32 tmp; + GC_Ballast = REB_I32_ADD_OF( + GC_Ballast, Mem_Pools[GOB_POOL].wide, &tmp + ) ? MAX_I32 : tmp; + + if (GC_Ballast > 0) + CLR_SIGNAL(SIG_RECYCLE); + + count++; + } + } + } + + return count; +} + - Init_Pools(scale); +// +// Queue_Mark_Event_Deep: C +// +// 'Queue' refers to the fact that after calling this routine, +// one will have to call Propagate_All_GC_Marks() to have the +// deep transitive closure completely marked. +// +static void Queue_Mark_Event_Deep(const RELVAL *value) +{ + REBREQ *req; + + if ( + IS_EVENT_MODEL(value, EVM_PORT) + || IS_EVENT_MODEL(value, EVM_OBJECT) + ) { + Queue_Mark_Context_Deep(CTX(VAL_EVENT_SER(m_cast(RELVAL*, value)))); + } + else if (IS_EVENT_MODEL(value, EVM_GUI)) { + Queue_Mark_Gob_Deep(cast(REBGOB*, VAL_EVENT_SER(m_cast(RELVAL*, value)))); + } + + // FIXME: This test is not in parallel to others. + if (VAL_EVENT_TYPE(value) == EVT_DROP_FILE + && GET_FLAG(VAL_EVENT_FLAGS(value), EVF_COPIED) + ) + { + assert(FALSE); + Queue_Mark_Array_Deep(ARR(VAL_EVENT_SER(m_cast(RELVAL*, value)))); + } + + if (IS_EVENT_MODEL(value, EVM_DEVICE)) { + // In the case of being an EVM_DEVICE event type, the port! will + // not be in VAL_EVENT_SER of the REBEVT structure. It is held + // indirectly by the REBREQ ->req field of the event, which + // in turn possibly holds a singly linked list of other requests. + req = VAL_EVENT_REQ(value); + + while (req) { + // Comment says void* ->port is "link back to REBOL port object" + if (req->port) + Queue_Mark_Context_Deep(CTX(req->port)); + req = req->next; + } + } +} + + +// +// Mark_Devices_Deep: C +// +// Mark all devices. Search for pending requests. +// +// This should be called at the top level, and as it is not +// 'Queued' it guarantees that the marks have been propagated. +// +static void Mark_Devices_Deep(void) +{ + REBDEV **devices = Host_Lib->devices; - Prior_Expand = Make_Mem(MAX_EXPAND_LIST * sizeof(REBSER*)); - Prior_Expand[0] = (REBSER*)1; + int d; + for (d = 0; d < RDI_MAX; d++) { + REBREQ *req; + REBDEV *dev = devices[d]; + if (!dev) + continue; - // Temporary series protected from GC. Holds series pointers. - GC_Protect = Make_Series(15, sizeof(REBSER *), FALSE); - KEEP_SERIES(GC_Protect, "gc protected"); + for (req = dev->pending; req; req = req->next) + if (req->port) + Queue_Mark_Context_Deep(CTX(req->port)); + } - GC_Series = Make_Series(60, sizeof(REBSER *), FALSE); - KEEP_SERIES(GC_Series, "gc guarded"); + Propagate_All_GC_Marks(); } diff --git a/src/core/m-pools.c b/src/core/m-pools.c index 828e3c6e8a..00a9a935e8 100644 --- a/src/core/m-pools.c +++ b/src/core/m-pools.c @@ -1,851 +1,2216 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: m-pools.c -** Summary: memory allocation pool management -** Section: memory -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ -/* - Ideas... +// +// File: %m-pools.c +// Summary: "memory allocation pool management" +// Section: memory +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// A point of Rebol's design was to remain small and solve its domain without +// relying on a lot of abstraction. Its memory-management was thus focused on +// staying low-level...and being able to do efficient and lightweight +// allocations of series. +// +// Unless they've been explicitly marked as fixed-size, series have a dynamic +// component. But they also have a fixed-size component that is allocated +// from a memory pool of other fixed-size things. This is called the "Node" +// in both Rebol and Red terminology. It is an item whose pointer is valid +// for the lifetime of the object, regardless of resizing. This is where +// header information is stored, and pointers to these objects may be saved +// in REBVAL values; such that they are kept alive by the garbage collector. +// +// The more complicated thing to do memory pooling of is the variable-sized +// portion of a series (currently called the "series data")...as series sizes +// can vary widely. But a trick Rebol has is that a series might be able to +// take advantage of being given back an allocation larger than requested. +// They can use it as reserved space for growth. +// +// (Typical models for implementation of things like C++'s std::vector do not +// reach below new[] or delete[]...which are generally implemented with malloc +// and free under the hood. Their buffered additional capacity is done +// assuming the allocation they get is as big as they asked for...no more and +// no less.) +// +// !!! While the space usage is very optimized in this model, there was no +// consideration for intelligent thread safety for allocations and frees. +// So although code like `tcmalloc` might be slower and have more overhead, +// it does offer that advantage. +// +// R3-Alpha included some code to assist in debugging client code using series +// such as by initializing the memory to garbage values. Given the existence +// of modern tools like Valgrind and Address Sanitizer, Ren-C instead has a +// mode in which pools are not used for data allocations, but going through +// malloc and free. You can enable this by setting the environment variable +// R3_ALWAYS_MALLOC to 1. +// - Each task needs its own series-save list that is simply a pointer - array of un-rooted (NEW) series that should not be GCed. When - a TRAP or THROW occurs, the list is trimmed back to its prior - marker, allowing series that were orphaned by the TRAP to be GCed. +#include "sys-core.h" - When GC occurs, each series on the save list is mark-scanned to - keep it alive. The save list can be expanded, but care should be - used to avoid creating a huge list when recursion happens. +#include "mem-pools.h" // low-level memory pool access +#include "mem-series.h" // low-level series memory access + +#include "sys-int-funcs.h" + + +// +// Alloc_Mem: C +// +//=////////////////////////////////////////////////////////////////////////=// +// +// NOTE: Instead of Alloc_Mem, use the ALLOC and ALLOC_N wrapper macros to +// ensure the memory block being freed matches the size for the type. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Alloc_Mem is an interface for a basic memory allocator. It is coupled with +// a Free_Mem function that clients must call with the correct size of the +// memory block to be freed. It is thus lower-level than malloc()... whose +// where clients do not need to remember the size of the allocation to pass +// into free(). +// +// One motivation behind using such an allocator in Rebol is to allow it to +// keep knowledge of how much memory the system is using. This means it can +// decide when to trigger a garbage collection, or raise an out-of-memory error +// before the operating system would, e.g. via 'ulimit': +// +// http://stackoverflow.com/questions/1229241/ +// +// Finer-grained allocations are done with memory pooling. But the blocks of +// memory used by the pools are still acquired using ALLOC_N and FREE_N, which +// are interfaces to this routine. +// +void *Alloc_Mem(size_t size) +{ + // Trap memory usage limit *before* the allocation is performed - What if interpreter kept track of save list marker when calling - each native, and reset it on return? -*/ + PG_Mem_Usage += size; + if ((PG_Mem_Limit != 0) && (PG_Mem_Usage > PG_Mem_Limit)) + Check_Security(Canon(SYM_MEMORY), POL_EXEC, 0); -//-- Special Debugging Options: -//#define CHAFF // Fill series data to crash old references -//#define HIT_END // Crash if block tail is past block terminator. -//#define WATCH_FREED // Show # series freed each GC -//#define MEM_STRESS // Special torture mode enabled -//#define INSPECT_SERIES + // While conceptually a simpler interface than malloc(), the + // current implementations on all C platforms just pass through to + // malloc and free. -#include "sys-core.h" +#ifdef NDEBUG + return malloc(size); +#else + // In debug builds we cache the size at the head of the allocation + // so we can check it. This also allows us to catch cases when + // free() is paired with Alloc_Mem() instead of using Free_Mem() + // + // Note that we use a 64-bit quantity, as we want the allocations + // to remain suitable in alignment for 64-bit values! + + void *ptr = malloc(size + sizeof(REBI64)); + if (ptr == NULL) + return NULL; + *cast(REBI64 *, ptr) = size; + return cast(char *, ptr) + sizeof(REBI64); +#endif +} -#define POOL_MAP -#define BAD_MEM_PTR ((REBYTE *)0xBAD1BAD1) +// +// Free_Mem: C +// +//=////////////////////////////////////////////////////////////////////////=// +// +// NOTE: Instead of Free_Mem, use the FREE and FREE_N wrapper macros to ensure +// the memory block being freed matches the appropriate size for the type. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Free_Mem is a wrapper over free(), that subtracts from a total count that +// Rebol can see how much memory was released. This information assists in +// deciding when it is necessary to run a garbage collection, or when to +// impose a quota. +// +// Release builds have no way to check that the correct size is passed in +// for the allocated unit. But in debug builds the size is stored with the +// allocation and checked here. Also, the pointer is skewed such that if +// clients try to use a normal free() and bypass Free_Mem it will trigger +// debug alerts from the C runtime of trying to free a non-head-of-malloc. +// +// We also know the host allocator (OS_Alloc_Mem) uses a similar trick. But +// since it doesn't require callers to remember the size, it puts a known +// garbage value for this routine to check for--to give a useful message. +// +void Free_Mem(void *mem, size_t size) +{ +#ifdef NDEBUG + free(mem); +#else + assert(mem != NULL); + char *ptr = cast(char *, mem) - sizeof(REBI64); + if (*cast(REBI64 *, ptr) == cast(REBI64, -1020)) + panic ("** FREE() used on OS_Alloc_Mem() memory instead of FREE()"); + + assert(*cast(REBI64*, ptr) == cast(REBI64, size)); + free(ptr); +#endif + PG_Mem_Usage -= size; +} + -//#define GC_TRIGGER (GC_Active && (GC_Ballast <= 0 || (GC_Pending && !GC_Disabled))) +#define POOL_MAP #ifdef POOL_MAP -#define FIND_POOL(n) ((n <= 4 * MEM_BIG_SIZE) ? (REBCNT)(PG_Pool_Map[n]) : SYSTEM_POOL) + #ifdef NDEBUG + #define FIND_POOL(n) \ + ((n <= 4 * MEM_BIG_SIZE) \ + ? cast(REBCNT, PG_Pool_Map[n]) \ + : cast(REBCNT, SYSTEM_POOL)) + #else + #define FIND_POOL(n) \ + ((!PG_Always_Malloc && (n <= 4 * MEM_BIG_SIZE)) \ + ? cast(REBCNT, PG_Pool_Map[n]) \ + : cast(REBCNT, SYSTEM_POOL)) + #endif #else -#define FIND_POOL(n) Find_Pool(n); + #ifdef NDEBUG + #define FIND_POOL(n) Find_Pool(n) + #else + #define FIND_POOL(n) (PG_Always_Malloc ? SYSTEM_POOL : Find_Pool(n)) + #endif #endif - /*********************************************************************** ** -** MEMORY POOLS +** MEMORY POOLS ** -** Memory management operates off an array of pools, the first -** group of which are fixed size (so require no compaction). +** Memory management operates off an array of pools, the first +** group of which are fixed size (so require no compaction). ** ***********************************************************************/ const REBPOOLSPEC Mem_Pool_Spec[MAX_POOLS] = { - {8, 256}, // 0-8 Small string pool - - MOD_POOL( 1, 256), // 9-16 (when REBVAL is 16) - MOD_POOL( 2, 512), // 17-32 - Small series (x 16) - MOD_POOL( 3, 1024), // 33-64 - MOD_POOL( 4, 512), - MOD_POOL( 5, 256), - MOD_POOL( 6, 128), - MOD_POOL( 7, 128), - MOD_POOL( 8, 64), - MOD_POOL( 9, 64), - MOD_POOL(10, 64), - MOD_POOL(11, 32), - MOD_POOL(12, 32), - MOD_POOL(13, 32), - MOD_POOL(14, 32), - MOD_POOL(15, 32), - MOD_POOL(16, 64), // 257 - MOD_POOL(20, 32), // 321 - Mid-size series (x 64) - MOD_POOL(24, 16), // 385 - MOD_POOL(28, 16), // 449 - MOD_POOL(32, 8), // 513 - - DEF_POOL(MEM_BIG_SIZE, 16), // 1K - Large series (x 1024) - DEF_POOL(MEM_BIG_SIZE*2, 8), // 2K - DEF_POOL(MEM_BIG_SIZE*3, 4), // 3K - DEF_POOL(MEM_BIG_SIZE*4, 4), // 4K - - DEF_POOL(sizeof(REBSER), 4096), // Series headers - DEF_POOL(sizeof(REBGOB), 128), // Gobs - DEF_POOL(1, 1), // Just used for tracking main memory + // R3-Alpha had a "0-8 small string pool". e.g. a pool of allocations for + // payloads 0 to 8 bytes in length. These are not technically possible in + // Ren-C's pool, because it requires 2*sizeof(void*) for each node at the + // minimum...because instead of just the freelist pointer, it has a + // standardized header (0 when free). + // + // This is not a problem, since all such small strings would also need + // REBSERs...and Ren-C has a better answer to embed the payload directly + // into the REBSER. This wouldn't apply if you were trying to do very + // small allocations of strings that did not have associated REBSERs.. + // but those don't exist in the code. + + MOD_POOL( 1, 256), // 9-16 (when REBVAL is 16) + MOD_POOL( 2, 512), // 17-32 - Small series (x 16) + MOD_POOL( 3, 1024), // 33-64 + MOD_POOL( 4, 512), + MOD_POOL( 5, 256), + MOD_POOL( 6, 128), + MOD_POOL( 7, 128), + MOD_POOL( 8, 64), + MOD_POOL( 9, 64), + MOD_POOL(10, 64), + MOD_POOL(11, 32), + MOD_POOL(12, 32), + MOD_POOL(13, 32), + MOD_POOL(14, 32), + MOD_POOL(15, 32), + MOD_POOL(16, 64), // 257 + MOD_POOL(20, 32), // 321 - Mid-size series (x 64) + MOD_POOL(24, 16), // 385 + MOD_POOL(28, 16), // 449 + MOD_POOL(32, 8), // 513 + + DEF_POOL(MEM_BIG_SIZE, 16), // 1K - Large series (x 1024) + DEF_POOL(MEM_BIG_SIZE*2, 8), // 2K + DEF_POOL(MEM_BIG_SIZE*3, 4), // 3K + DEF_POOL(MEM_BIG_SIZE*4, 4), // 4K + + DEF_POOL(sizeof(REBSER), 4096), // Series headers + DEF_POOL(sizeof(REBGOB), 128), // Gobs + DEF_POOL(sizeof(REBI64), 1), // Just used for tracking main memory }; -/*********************************************************************** -** -*/ void *Make_Mem(size_t size) -/* -** Main memory allocation wrapper function. -** -***********************************************************************/ +// +// Startup_Pools: C +// +// Initialize memory pool array. +// +void Startup_Pools(REBINT scale) { - void *ptr; +#ifndef NDEBUG + const char *env_always_malloc = NULL; + env_always_malloc = getenv("R3_ALWAYS_MALLOC"); + if (env_always_malloc != NULL && atoi(env_always_malloc) != 0) { + Debug_Str( + "**\n" + "** R3_ALWAYS_MALLOC is TRUE in environment variable!\n" + "** Memory allocations aren't pooled, expect slowness...\n" + "**\n" + ); + PG_Always_Malloc = TRUE; + } +#endif - if (!(ptr = malloc(size))) return 0; - PG_Mem_Usage += size; - if (PG_Mem_Limit != 0 && (PG_Mem_Usage > PG_Mem_Limit)) { - Check_Security(SYM_MEMORY, POL_EXEC, 0); - } - CLEAR(ptr, size); + REBINT unscale = 1; + if (scale == 0) + scale = 1; + else if (scale < 0) { + unscale = -scale; + scale = 1; + } + + Mem_Pools = ALLOC_N(REBPOL, MAX_POOLS); + + // Copy pool sizes to new pool structure: + // + REBCNT n; + for (n = 0; n < MAX_POOLS; n++) { + Mem_Pools[n].segs = NULL; + Mem_Pools[n].first = NULL; + Mem_Pools[n].last = NULL; + + // The current invariant is that allocations returned from Make_Node() + // should always come back as being at a legal 64-bit alignment point. + // Although it would be possible to round the allocations, turning it + // into an alert helps make sure available space isn't idly wasted. + // + // A panic is used instead of an assert, since the debug sizes and + // release sizes may be different...and both must be checked. + // + if (Mem_Pool_Spec[n].wide % sizeof(REBI64) != 0) + panic ("memory pool width is not 64-bit aligned"); + + Mem_Pools[n].wide = Mem_Pool_Spec[n].wide; + + Mem_Pools[n].units = (Mem_Pool_Spec[n].units * scale) / unscale; + if (Mem_Pools[n].units < 2) Mem_Pools[n].units = 2; + Mem_Pools[n].free = 0; + Mem_Pools[n].has = 0; + } + + // For pool lookup. Maps size to pool index. (See Find_Pool below) + PG_Pool_Map = ALLOC_N(REBYTE, (4 * MEM_BIG_SIZE) + 1); + + // sizes 0 - 8 are pool 0 + for (n = 0; n <= 8; n++) PG_Pool_Map[n] = 0; + for (; n <= 16 * MEM_MIN_SIZE; n++) + PG_Pool_Map[n] = MEM_TINY_POOL + ((n-1) / MEM_MIN_SIZE); + for (; n <= 32 * MEM_MIN_SIZE; n++) + PG_Pool_Map[n] = MEM_SMALL_POOLS-4 + ((n-1) / (MEM_MIN_SIZE * 4)); + for (; n <= 4 * MEM_BIG_SIZE; n++) + PG_Pool_Map[n] = MEM_MID_POOLS + ((n-1) / MEM_BIG_SIZE); + + // !!! Revisit where series init/shutdown goes when the code is more + // organized to have some of the logic not in the pools file + +#if !defined(NDEBUG) + PG_Reb_Stats = ALLOC(REB_STATS); +#endif - return ptr; + // Manually allocated series that GC is not responsible for (unless a + // trap occurs). Holds series pointers. + // + // As a trick to keep this series from trying to track itself, say it's + // managed, then sneak the flag off. + // + GC_Manuals = Make_Series_Core(15, sizeof(REBSER *), NODE_FLAG_MANAGED); + CLEAR_SER_FLAG(GC_Manuals, NODE_FLAG_MANAGED); + + Prior_Expand = ALLOC_N(REBSER*, MAX_EXPAND_LIST); + CLEAR(Prior_Expand, sizeof(REBSER*) * MAX_EXPAND_LIST); + Prior_Expand[0] = (REBSER*)1; } -/*********************************************************************** -** -*/ void Free_Mem(void *mem, size_t size) -/* -***********************************************************************/ +// +// Shutdown_Pools: C +// +// Release all segments in all pools, and the pools themselves. +// +void Shutdown_Pools(void) { - PG_Mem_Usage -= size; - free(mem); -} + // Can't use Free_Series() because GC_Manuals couldn't be put in + // the manuals list... + // + GC_Kill_Series(GC_Manuals); + +#if !defined(NDEBUG) + REBSEG *seg = Mem_Pools[SER_POOL].segs; + for(; seg != NULL; seg = seg->next) { + REBSER *series = cast(REBSER*, seg + 1); + REBCNT n; + for (n = Mem_Pools[SER_POOL].units; n > 0; n--, series++) { + if (IS_FREE_NODE(series)) + continue; + + printf("At least one leaked series at shutdown...\n"); + panic (series); + } + } +#endif + REBCNT pool_num; + for (pool_num = 0; pool_num < MAX_POOLS; pool_num++) { + REBPOL *pool = &Mem_Pools[pool_num]; + REBCNT mem_size = pool->wide * pool->units + sizeof(REBSEG); -/*********************************************************************** -** -*/ void Init_Pools(REBINT scale) -/* -** Initialize memory pool array. -** -***********************************************************************/ -{ - REBINT n; - REBINT unscale = 1; - - if (scale == 0) scale = 1; - else if (scale < 0) unscale = -scale, scale = 1; - - // Copy pool sizes to new pool structure: - Mem_Pools = Make_Mem(sizeof(REBPOL) * MAX_POOLS); - for (n = 0; n < MAX_POOLS; n++) { - Mem_Pools[n].wide = Mem_Pool_Spec[n].wide; - Mem_Pools[n].units = (Mem_Pool_Spec[n].units * scale) / unscale; - if (Mem_Pools[n].units < 2) Mem_Pools[n].units = 2; - } - - // For pool lookup. Maps size to pool index. (See Find_Pool below) - PG_Pool_Map = Make_Mem((4 * MEM_BIG_SIZE) + 4); // extra - n = 9; // sizes 0 - 8 are pool 0 - for (; n <= 16 * MEM_MIN_SIZE; n++) PG_Pool_Map[n] = MEM_TINY_POOL + ((n-1) / MEM_MIN_SIZE); - for (; n <= 32 * MEM_MIN_SIZE; n++) PG_Pool_Map[n] = MEM_SMALL_POOLS-4 + ((n-1) / (MEM_MIN_SIZE * 4)); - for (; n <= 4 * MEM_BIG_SIZE; n++) PG_Pool_Map[n] = MEM_MID_POOLS + ((n-1) / MEM_BIG_SIZE); + REBSEG *seg = pool->segs; + while (seg) { + REBSEG *next; + next = seg->next; + FREE_N(char, mem_size, cast(char*, seg)); + seg = next; + } + } + + FREE_N(REBPOL, MAX_POOLS, Mem_Pools); + + FREE_N(REBYTE, (4 * MEM_BIG_SIZE) + 1, PG_Pool_Map); + + // !!! Revisit location (just has to be after all series are freed) + FREE_N(REBSER*, MAX_EXPAND_LIST, Prior_Expand); + +#if !defined(NDEBUG) + FREE(REB_STATS, PG_Reb_Stats); +#endif + +#if !defined(NDEBUG) + if (PG_Mem_Usage != 0) { + // + // If using valgrind or address sanitizer, they can present more + // information about leaks than just how much was leaked. So don't + // assert...exit normally so they go through their process of + // presenting the leaks at program termination. + // + printf( + "*** PG_Mem_Usage = %lu ***\n", + cast(unsigned long, PG_Mem_Usage) + ); + + printf( + "Memory accounting imbalance: Rebol internally tracks how much\n" + "memory it uses to know when to garbage collect, etc. For\n" + "some reason this accounting did not balance to zero on exit.\n" + "Run under Valgrind with --leak-check=full --track-origins=yes\n" + "to find out why this is happening.\n" + ); + } +#endif } -#ifndef POOL_MAP -/*********************************************************************** -** -*/ static INLINE REBCNT Find_Pool(REBCNT size) -/* -** Given a size, tell us what pool it belongs to. -** -***********************************************************************/ +// +// Fill_Pool: C +// +// Allocate memory for a pool. The amount allocated will be determined from +// the size and units specified when the pool header was created. The nodes +// of the pool are linked to the free list. +// +static void Fill_Pool(REBPOL *pool) { - if (size <= 8) return 0; // Note: 0 - 8 (and size change for proper modulus) - size--; - if (size < 16 * MEM_MIN_SIZE) return MEM_TINY_POOL + (size / MEM_MIN_SIZE); - if (size < 32 * MEM_MIN_SIZE) return MEM_SMALL_POOLS-4 + (size / (MEM_MIN_SIZE * 4)); - if (size < 4 * MEM_BIG_SIZE) return MEM_MID_POOLS + (size / MEM_BIG_SIZE); - return SYSTEM_POOL; + REBCNT units = pool->units; + REBCNT mem_size = pool->wide * units + sizeof(REBSEG); + + REBSEG *seg = cast(REBSEG *, ALLOC_N(char, mem_size)); + if (seg == NULL) { + panic ("Out of memory error during Fill_Pool()"); + + // Rebol's safe handling of running out of memory was never really + // articulated. Yet it should be possible to run a fail()...at least + // of a certain type...without allocating more memory. (This probably + // suggests a need for pre-creation of the out of memory objects, + // as is done with the stack overflow error) + // + // fail (Error_No_Memory(mem_size)); + } + + seg->size = mem_size; + seg->next = pool->segs; + pool->segs = seg; + pool->has += units; + pool->free += units; + + // Add new nodes to the end of free list: + + // Can't use NOD() here because it tests for NOT(NODE_FLAG_FREE) + // + REBNOD *node = cast(REBNOD*, seg + 1); + + if (pool->first == NULL) { + assert(pool->last == NULL); + pool->first = node; + } + else { + assert(pool->last != NULL); + pool->last->next_if_free = node; + } + + while (TRUE) { + // + // See Init_Endlike_Header() for why we do this + // + struct Reb_Header *alias = &node->header; + alias->bits = FLAGBYTE_FIRST(FREED_SERIES_BYTE); + + if (--units == 0) { + node->next_if_free = NULL; + break; + } + + // Can't use NOD() here because it tests for NODE_FLAG_FREE + // + node->next_if_free = cast(REBNOD*, cast(REBYTE*, node) + pool->wide); + node = node->next_if_free; + } + + pool->last = node; } -/*********************************************************************** -** -** void Check_Pool_Map(void) -/* -************************************************************************ +// +// Make_Node: C +// +// Allocate a node from a pool. If the pool has run out of nodes, it will +// be refilled. +// +// The node will not be zero-filled. However its header bits will be +// guaranteed to be zero--which is the same as the state of all freed nodes. +// Callers likely want to change this to not be zero, so that zero can be +// used to recognize freed nodes if they enumerate the pool themselves. +// +// All nodes are 64-bit aligned. This way, data allocated in nodes can be +// structured to know where legal 64-bit alignment points would be. This +// is required for correct functioning of some types. (See notes on +// alignment in %sys-rebval.h.) +// +void *Make_Node(REBCNT pool_id) { - int n; + REBPOL *pool = &Mem_Pools[pool_id]; + if (pool->first == NULL) + Fill_Pool(pool); + + assert(pool->first != NULL); + + REBNOD *node = pool->first; - for (n = 0; n <= 4 * MEM_BIG_SIZE + 1; n++) - if (FIND_POOL(n) != Find_Pool(n)) - Debug_Fmt("%d: %d %d", n, FIND_POOL(n), Find_Pool(n)); + pool->first = node->next_if_free; + if (node == pool->last) + pool->last = NULL; + + pool->free--; + + assert(cast(REBUPT, node) % sizeof(REBI64) == 0); + assert(IS_FREE_NODE(node)); // client needs to change to non-zero + + return cast(void *, node); } -*/ -#endif -/*********************************************************************** -** -*/ static void Fill_Pool(REBPOL *pool) -/* -** Allocate memory for a pool. The amount allocated will be -** determined from the size and units specified when the -** pool header was created. The nodes of the pool are linked -** to the free list. -** -***********************************************************************/ +// +// Free_Node: C +// +// Free a node, returning it to its pool. Once it is freed, its header will +// be set to 0. This will identify the node as not in use to anyone who +// enumerates the nodes in the pool (such as the garbage collector). +// +void Free_Node(REBCNT pool_id, void *p) { - REBSEG *seg; - REBNOD *node; - REBYTE *next; - REBCNT units = pool->units; -#ifdef MUNGWALL - REBCNT mem_size = (pool->wide + 2 * MUNG_SIZE) * units + sizeof(REBSEG); + REBNOD *node = NOD(p); + + // See Init_Endlike_Header() for why we do this + // + struct Reb_Header *alias = &node->header; + alias->bits = FLAGBYTE_FIRST(FREED_SERIES_BYTE); + + REBPOL *pool = &Mem_Pools[pool_id]; + +#ifdef NDEBUG + node->next_if_free = pool->first; + pool->first = node; #else - REBCNT mem_size = pool->wide * units + sizeof(REBSEG); + // !!! In R3-Alpha, the most recently freed node would become the first + // node to hand out. This is a simple and likely good strategy for + // cache usage, but makes the "poisoning" nearly useless. + // + // This code was added to insert an empty segment, such that this node + // won't be picked by the next Make_Node. That enlongates the poisonous + // time of this area to catch stale pointers. But doing this in the + // debug build only creates a source of variant behavior. + + if (pool->last == NULL) // Fill pool if empty + Fill_Pool(pool); + + assert(pool->last != NULL); + + pool->last->next_if_free = node; + pool->last = node; + node->next_if_free = NULL; + #endif - seg = (REBSEG *) Make_Mem(mem_size); - if (!seg) Crash(RP_NO_MEMORY, mem_size); - - CLEAR(seg, mem_size); // needed to clear series nodes - seg->size = mem_size; - seg->next = pool->segs; - pool->segs = seg; - pool->free += units; - pool->has += units; - - // Add new nodes to the end of free list: - for (node = (REBNOD *)&pool->first; *node; node = *node); // goto end - -#ifdef MUNGWALL - for (next = (REBYTE *)(seg + 1); units > 0; units--) { - memcpy(next,MUNG_PATTERN1,MUNG_SIZE); - memcpy(next+MUNG_SIZE+pool->wide,MUNG_PATTERN2,MUNG_SIZE); - *node = (REBNOD) (next+MUNG_SIZE); - node = *node; - next+=pool->wide+2*MUNG_SIZE; - } -#else - for (next = (REBYTE *)(seg + 1); units > 0; units--, next += pool->wide) { - *node = (REBNOD) next; - node = *node; - } + pool->free++; +} + + +// +// Series_Data_Alloc: C +// +// Allocates element array for an already allocated REBSER node structure. +// Resets the bias and tail to zero, and sets the new width. Flags like +// SERIES_FLAG_FIXED_SIZE are left as they were, and other fields in the +// series structure are untouched. +// +// This routine can thus be used for an initial construction or an operation +// like expansion. Currently not exported from this file. +// +static REBOOL Series_Data_Alloc(REBSER *s, REBCNT length) { + // + // Data should have not been allocated yet OR caller has extracted it + // and nulled it to indicate taking responsibility for freeing it. + // + assert(s->content.dynamic.data == NULL); + + REBYTE wide = SER_WIDE(s); + assert(wide != 0); + + REBCNT size; // size of allocation (possibly bigger than we need) + + REBCNT pool_num = FIND_POOL(length * wide); + if (pool_num < SYSTEM_POOL) { + // ...there is a pool designated for allocations of this size range + s->content.dynamic.data = cast(REBYTE*, Make_Node(pool_num)); + if (s->content.dynamic.data == NULL) + return FALSE; + + // The pooled allocation might wind up being larger than we asked. + // Don't waste the space...mark as capacity the series could use. + size = Mem_Pools[pool_num].wide; + assert(size >= length * wide); + + // We don't round to power of 2 for allocations in memory pools + CLEAR_SER_FLAG(s, SERIES_FLAG_POWER_OF_2); + } + else { + // ...the allocation is too big for a pool. But instead of just + // doing an unpooled allocation to give you the size you asked + // for, the system does some second-guessing to align to 2Kb + // boundaries (or choose a power of 2, if requested). + + size = length * wide; + if (GET_SER_FLAG(s, SERIES_FLAG_POWER_OF_2)) { + REBCNT len = 2048; + while(len < size) + len *= 2; + size = len; + + // Clear the power of 2 flag if it isn't necessary, due to even + // divisibility by the item width. + // + if (size % wide == 0) + CLEAR_SER_FLAG(s, SERIES_FLAG_POWER_OF_2); + } + + s->content.dynamic.data = ALLOC_N(REBYTE, size); + if (s->content.dynamic.data == NULL) + return FALSE; + + Mem_Pools[SYSTEM_POOL].has += size; + Mem_Pools[SYSTEM_POOL].free++; + } + + // Note: Bias field may contain other flags at some point. Because + // SER_SET_BIAS() uses bit masking on an existing value, we are sure + // here to clear out the whole value for starters. + // + s->content.dynamic.bias = 0; + + // The allocation may have returned more than we requested, so we note + // that in 'rest' so that the series can expand in and use the space. + // Note that it wastes remainder if size % wide != 0 :-( + // + s->content.dynamic.rest = size / wide; + + // We set the tail of all series to zero initially, but currently do + // leave series termination to callers. (This is under review.) + // + s->content.dynamic.len = 0; + + // Currently once a series becomes dynamic, it never goes back. There is + // no shrinking process that will pare it back to fit completely inside + // the REBSER node. + // + SET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC); + + // See if allocation tripped our need to queue a garbage collection + + if ((GC_Ballast -= size) <= 0) + SET_SIGNAL(SIG_RECYCLE); + +#if !defined(NDEBUG) + if (pool_num >= SYSTEM_POOL) + assert(Series_Allocation_Unpooled(s) == size); #endif - *node = 0; + + if (GET_SER_FLAG(s, SERIES_FLAG_ARRAY)) { + assert(wide == sizeof(REBVAL)); + + REBCNT n; + + #if !defined(NDEBUG) + PG_Reb_Stats->Blocks++; + #endif + + // For REBVAL-valued-arrays, we mark as trash to mark the "settable" + // bit, heeded by both SET_END() and RESET_HEADER(). See remarks on + // VALUE_FLAG_CELL for why this is done. + // + // Note that the "len" field of the series (its number of valid + // elements as maintained by the client) will be 0. As far as this + // layer is concerned, we've given back `length` entries for the + // caller to manage...they do not know about the ->rest + // + for (n = 0; n < length; n++) + INIT_CELL(ARR_AT(ARR(s), n)); + + // !!! We should intentionally mark the overage range as not having + // NODE_FLAG_CELL in the debug build. Then have the series go through + // an expansion to overrule it. + // + // That's complicated logic that is likely best done in the context of + // a simplifying review of the series mechanics themselves. So + // for now we just use ordinary trash...which means we don't get + // as much potential debug warning as we might when writing into + // bias or tail capacity. + // + // !!! Also, should the release build do the NODE_FLAG_CELL setting + // up front, or only on expansions? + // + for(; n < s->content.dynamic.rest - 1; n++) { + INIT_CELL(ARR_AT(ARR(s), n)); + } + + // The convention is that the *last* cell in the allocated capacity + // is an unwritable end. This may be located arbitrarily beyond the + // capacity the user requested, if a pool unit was used that was + // bigger than they asked for...but this will be used in expansion. + // + // Having an unwritable END in that spot paves the way for more forms + // of implicit termination. In theory one should not need 5 cells + // to hold an array of length 4...the 5th header position can merely + // mark termination with the low bit clear. + // + // Currently only singular arrays exploit this, but since they exist + // they must be accounted for. Because callers cannot write past the + // capacity they requested, they must use TERM_ARRAY_LEN(), which + // avoids writing the unwritable locations by checking for END first. + // + RELVAL *ultimate = ARR_AT(ARR(s), s->content.dynamic.rest - 1); + Init_Endlike_Header(&ultimate->header, 0); + #if !defined(NDEBUG) + Set_Track_Payload_Debug(ultimate, __FILE__, __LINE__); + #endif + } + + return TRUE; } -/*********************************************************************** -** -*/ void *Make_Node(REBCNT pool_id) -/* -** Allocate a node from a pool. The node will NOT be cleared. -** If the pool has run out of nodes, it will be refilled. -** -***********************************************************************/ +#if !defined(NDEBUG) + +// +// Try_Find_Containing_Series_Debug: C +// +// This debug-build-only routine will look to see if it can find what series +// a data pointer lives in. It returns NULL if it can't find one. It's very +// slow, because it has to look at all the series. Use sparingly! +// +REBSER *Try_Find_Containing_Series_Debug(const void *p) { - REBNOD *node; - REBPOL *pool; - - pool = &Mem_Pools[pool_id]; - if (!pool->first) Fill_Pool(pool); - node = pool->first; - pool->first = *node; - pool->free--; - return (void *)node; + REBSEG *seg; + + for (seg = Mem_Pools[SER_POOL].segs; seg; seg = seg->next) { + REBSER *s = cast(REBSER*, seg + 1); + REBCNT n; + for (n = Mem_Pools[SER_POOL].units; n > 0; --n, ++s) { + if (IS_FREE_NODE(s)) + continue; + + if (s->header.bits & NODE_FLAG_CELL) { // a pairing, REBSER is REBVAL[2] + if ((p >= cast(void*, s)) && (p < cast(void*, s + 1))) { + printf("pointer found in 'pairing' series"); + printf("not a real REBSER, no information available"); + assert(FALSE); + } + continue; + } + + if (NOT(GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC))) { + if ( + p >= cast(void*, &s->content) + && p < cast(void*, &s->content + 1) + ){ + return s; + } + continue; + } + + if (p < cast(void*, + s->content.dynamic.data - (SER_WIDE(s) * SER_BIAS(s)) + )) { + // The memory lies before the series data allocation. + // + continue; + } + + if (p >= cast(void*, s->content.dynamic.data + + (SER_WIDE(s) * SER_REST(s)) + )) { + // The memory lies after the series capacity. + // + continue; + } + + // We now have a bad condition, in that the pointer is known to + // be inside a series data allocation. But it could be doubly + // bad if the pointer is in the extra head or tail capacity, + // because that's effectively free data. Since we're already + // going to be asserting if we get here, go ahead and pay to + // check if either of those is the case. + + if (p < cast(void*, s->content.dynamic.data)) { + printf("Pointer found in freed head capacity of series\n"); + fflush(stdout); + return s; + } + + if (p >= cast(void*, + s->content.dynamic.data + + (SER_WIDE(s) * SER_LEN(s)) + )) { + printf("Pointer found in freed tail capacity of series\n"); + fflush(stdout); + return s; + } + + return s; + } + } + + return NULL; // not found } +#endif -/*********************************************************************** -** -*/ void Free_Node(REBCNT pool_id, REBNOD *node) -/* -** Free a node, returning it to its pool. -** -***********************************************************************/ + +// +// Series_Allocation_Unpooled: C +// +// When we want the actual memory accounting for a series, the whole story may +// not be told by the element size multiplied by the capacity. The series may +// have been allocated from a pool where it was rounded up to the pool size, +// and elements may not fit evenly in that space. Or it may be allocated from +// the "system pool" via Alloc_Mem, but rounded up to a power of 2. +// +// (Note: It's necessary to know the size because Free_Mem requires it, as +// Rebol's allocator doesn't remember the size of system pool allocations for +// you. It also needs it in order to keep track of GC boundaries and memory +// use quotas.) +// +// Rather than pay for the cost on every series of an "actual allocation size", +// the optimization choice is to only pay for a "rounded up to power of 2" bit. +// +REBCNT Series_Allocation_Unpooled(REBSER *series) { - MUNG_CHECK(pool_id, node, Mem_Pools[pool_id].wide); - *node = Mem_Pools[pool_id].first; - Mem_Pools[pool_id].first = node; - Mem_Pools[pool_id].free++; + REBCNT total = SER_TOTAL(series); + + if (GET_SER_FLAG(series, SERIES_FLAG_POWER_OF_2)) { + REBCNT len = 2048; + while(len < total) + len *= 2; + return len; + } + + return total; } -/*********************************************************************** -** -*/ REBSER *Make_Series_Data(REBSER *series, REBCNT length) -/* -** Allocates memory for series data of the given width -** and length (number of units). -** -** Can be used by Make_Series below once we measure to -** determine performance impact. !!! -** -***********************************************************************/ +// +// Make_Series_Core: C +// +// Make a series of a given capacity and width (unit size). +// If the data is tiny enough, it will be fit into the series node itself. +// Small series will be allocated from a memory pool. +// Large series will be allocated from system memory. +// The series will be zero length to start with. +// +REBSER *Make_Series_Core(REBCNT capacity, REBYTE wide, REBUPT flags) { - REBNOD *node; - REBPOL *pool; - REBCNT pool_num; - -// if (GC_TRIGGER) Recycle(); - - length *= SERIES_WIDE(series); - pool_num = FIND_POOL(length); - if (pool_num < SYSTEM_POOL) { - pool = &Mem_Pools[pool_num]; - if (!pool->first) Fill_Pool(pool); - node = pool->first; - pool->first = *node; - pool->free--; - length = pool->wide; - } else { - length = ALIGN(length, 2048); -#ifdef DEBUGGING - Debug_Fmt_Num("Alloc1:", length); + assert(wide != 0 && capacity != 0); // not allowed + + if (cast(REBU64, capacity) * wide > MAX_I32) + fail (Error_No_Memory(cast(REBU64, capacity) * wide)); + +#if !defined(NDEBUG) + PG_Reb_Stats->Series_Made++; + PG_Reb_Stats->Series_Memory += capacity * wide; #endif -#ifdef MUNGWALL - node = (REBNOD *) Make_Mem(length+2*MUNG_SIZE); -#else - node = (REBNOD *) Make_Mem(length); + + REBSER *s = cast(REBSER*, Make_Node(SER_POOL)); + + // Header bits can't be zero. NODE_FLAG_NODE is sufficient to identify + // this as a REBSER node that is not GC managed. + // + s->header.bits = NODE_FLAG_NODE | flags; + + if ((GC_Ballast -= sizeof(REBSER)) <= 0) + SET_SIGNAL(SIG_RECYCLE); + +#if !defined(NDEBUG) + // + // For debugging purposes, it's nice to be able to crash on some + // kind of guard for tracking the call stack at the point of allocation + // if we find some undesirable condition that we want a trace from + // + s->guard = cast(int*, malloc(sizeof(*s->guard))); + free(s->guard); + + TRASH_POINTER_IF_DEBUG(s->link.keylist); + TRASH_POINTER_IF_DEBUG(s->misc.canon); + + // It's necessary to have another value in order to round out the size of + // the pool node so pointer-aligned entries are given out, so might as well + // make that hold a useful value--the tick count when the series was made + // + s->do_count = TG_Do_Count; #endif - if (!node) Trap0(RE_NO_MEMORY); -#ifdef MUNGWALL - memcpy((REBYTE *)node,MUNG_PATTERN1,MUNG_SIZE); - memcpy(((REBYTE *)node)+length+MUNG_SIZE,MUNG_PATTERN2,MUNG_SIZE); - node=(REBNOD *)(((REBYTE *)node)+MUNG_SIZE); + + // The info bits must be able to implicitly terminate the `content`, + // so that if a REBVAL is in slot [0] then it would appear terminated + // if the [1] slot was read. + // + Init_Endlike_Header(&s->info, 0); // acts as unwritable END marker + assert(IS_END(&s->content.values[1])); // test by using Reb_Value pointer + + s->content.dynamic.data = NULL; + + assert(wide != 0); + SER_SET_WIDE(s, wide); + + if ((flags & SERIES_FLAG_ARRAY) && capacity <= 2) { + // + // An array requested of capacity 2 actually means one cell of data + // and one cell that can serve as an END marker. The invariant that + // is guaranteed is that the final slot will already be written as + // an END, and that the caller must never write it...hence it can + // be less than a full cell's size. + // + assert(NOT_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC)); + INIT_CELL(&s->content.values[0]); + } + else if (capacity * wide <= sizeof(s->content)) { + assert(NOT_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC)); + } + else { + // Allocate the actual data blob that holds the series elements + + if (!Series_Data_Alloc(s, capacity)) { + Free_Node(SER_POOL, s); + fail (Error_No_Memory(capacity * wide)); + } + + // <> - The capacity that will be given back as the ->rest + // field may be larger than the requested size. The memory pool API + // is able to give back the size of the actual allocated block--which + // includes any overage. So to keep that from going to waste it is + // recorded as the block's capacity, in case it ever needs to grow + // it might be able to save on a reallocation. + } + + // It is possible for a series to start out unmanaged and then be + // transitioned to managed, or it may start off in a managed state. It + // is more efficient if you know a series is going to be managed to + // create it in the managed state (it doesn't have to be added and + // removed from a manuals list). But be sure no evaluations are called + // in that case before the places that will hold it live are set up. + // + // Note: The call to create GC_Manuals itself lies and says it is managed, + // just for the moment of set up, so it doesn't try to add itself to the + // manuals list! It removes the managed flag after the create. + // + if (NOT(flags & NODE_FLAG_MANAGED)) { + assert(GET_SER_INFO(GC_Manuals, SERIES_INFO_HAS_DYNAMIC)); + + if (SER_FULL(GC_Manuals)) + Extend_Series(GC_Manuals, 8); + + cast(REBSER**, GC_Manuals->content.dynamic.data)[ + GC_Manuals->content.dynamic.len++ + ] = s; + } + + // Since we're not the scanner, the only way we can attribute a file and + // a line number to a series created at runtime is to examine the frame + // stack and propagate whatever file and line number information it might + // know about from the source it's running onto this series. + // + if (flags & SERIES_FLAG_FILE_LINE) { + // + // !!! Feature TBD. Until then take off the flag since leaving it on + // and not setting the fields would crash the GC. + // + // s->link.filename = ??? + // s->misc.line = ???; + // + CLEAR_SER_FLAG(s, SERIES_FLAG_FILE_LINE); + } + + assert(s->info.bits & NODE_FLAG_END); + assert(NOT(s->info.bits & NODE_FLAG_CELL)); + assert(SER_LEN(s) == 0); + return s; +} + + +// +// Alloc_Pairing: C +// +// Allocate a paired set of values. The "key" is in the cell *before* the +// returned pointer. +// +// Because pairings are created in large numbers and left outstanding, they +// are not put into any tracking lists by default. This means that if there +// is a fail(), they will leak--unless whichever API client that is using +// them ensures they are cleaned up. So in C++, this is done with exception +// handling. +// +// However, untracked/unmanaged pairings have a special ability. It's +// possible for them to be "owned" by a FRAME!, which sits in the first cell. +// This provides an alternate mechanism for plain C code to do cleanup besides +// handlers based on PUSH_TRAP(). +// +REBVAL *Alloc_Pairing(REBCTX *opt_owning_frame) { + REBSER *s = cast(REBSER*, Make_Node(SER_POOL)); // 2x REBVAL size + + REBVAL *key = cast(REBVAL*, s); + REBVAL *paired = key + 1; + + INIT_CELL(key); + if (opt_owning_frame) { + Init_Any_Context(key, REB_FRAME, opt_owning_frame); + SET_VAL_FLAGS( + key, ANY_CONTEXT_FLAG_OWNS_PAIRED | NODE_FLAG_ROOT + ); + } + else { + // Client will need to put *something* in the key slot (accessed with + // PAIRING_KEY). Whatever they end up writing should be acceptable + // to avoid a GC, since the header is not purely 0...and it works out + // that all "ordinary" values will just act as unmanaged metadata. + // + TRASH_CELL_IF_DEBUG(key); + } + + INIT_CELL(paired); + TRASH_CELL_IF_DEBUG(paired); + +#if !defined(NDEBUG) + s->guard = cast(int*, malloc(sizeof(*s->guard))); + free(s->guard); + + s->do_count = TG_Do_Count; #endif - Mem_Pools[SYSTEM_POOL].has += length; - Mem_Pools[SYSTEM_POOL].free++; - } -#ifdef CHAFF - memset((REBYTE *)node, 0xff, length); + + return paired; +} + + +// +// Manage_Pairing: C +// +// GC management is a one-way street in Ren-C, and the paired management +// status is handled by bits directly in the first (or key's) REBVAL header. +// Switching to managed mode means the key can no longer be changed--only +// the value. +// +// !!! a const_Pairing_Key() accessor should help enforce the rule, only +// allowing const access if managed. +// +void Manage_Pairing(REBVAL *paired) { + REBVAL *key = PAIRING_KEY(paired); + SET_VAL_FLAG(key, NODE_FLAG_MANAGED); +} + + +// +// Free_Pairing: C +// +void Free_Pairing(REBVAL *paired) { + REBVAL *key = PAIRING_KEY(paired); + assert(NOT_VAL_FLAG(key, NODE_FLAG_MANAGED)); + REBSER *series = cast(REBSER*, key); + TRASH_CELL_IF_DEBUG(paired); + Free_Node(SER_POOL, series); + +#if !defined(NDEBUG) + series->do_count = TG_Do_Count; #endif - series->tail = 0; - SERIES_REST(series) = length / SERIES_WIDE(series); - series->data = (REBYTE *)node; - if ((GC_Ballast -= length) <= 0) SET_SIGNAL(SIG_RECYCLE); - return series; } -/*********************************************************************** -** -*/ REBSER *Make_Series(REBCNT length, REBCNT wide, REBOOL powerof2) -/* -** Make a series of a given length and width (unit size). -** Small series will be allocated from a REBOL pool. -** Large series will be allocated from system memory. -** A width of zero is not allowed. -** -***********************************************************************/ +// +// Swap_Underlying_Series_Data: C +// +void Swap_Underlying_Series_Data(REBSER *s1, REBSER *s2) +{ + assert(SER_WIDE(s1) == SER_WIDE(s2)); + assert( + GET_SER_FLAG(s1, SERIES_FLAG_ARRAY) + == GET_SER_FLAG(s2, SERIES_FLAG_ARRAY) + ); + + REBSER temp = *s1; + *s1 = *s2; + *s2 = temp; +} + + +// +// Free_Unbiased_Series_Data: C +// +// Routines that are part of the core series implementation +// call this, including Expand_Series. It requires a low-level +// awareness that the series data pointer cannot be freed +// without subtracting out the "biasing" which skips the pointer +// ahead to account for unused capacity at the head of the +// allocation. They also must know the total allocation size. +// +static void Free_Unbiased_Series_Data(REBYTE *unbiased, REBCNT size_unpooled) +{ + REBCNT pool_num = FIND_POOL(size_unpooled); + REBPOL *pool; + + if (pool_num < SYSTEM_POOL) { + // + // The series data does not honor "node protocol" when it is in use + // The pools are not swept the way the REBSER pool is, so only the + // free nodes have significance to their headers. Use a cast and not + // NOD() because that assumes NOT(NODE_FLAG_FREE) + // + REBNOD *node = cast(REBNOD*, unbiased); + + assert(Mem_Pools[pool_num].wide >= size_unpooled); + + pool = &Mem_Pools[pool_num]; + node->next_if_free = pool->first; + pool->first = node; + pool->free++; + + // See Init_Endlike_Header() for why we do this + // + struct Reb_Header *alias = &node->header; + alias->bits = FLAGBYTE_FIRST(FREED_SERIES_BYTE); + } + else { + FREE_N(REBYTE, size_unpooled, unbiased); + Mem_Pools[SYSTEM_POOL].has -= size_unpooled; + Mem_Pools[SYSTEM_POOL].free++; + } +} + + +// +// Expand_Series: C +// +// Expand a series at a particular index point by the number +// number of units specified by delta. +// +// index - where space is expanded (but not cleared) +// delta - number of UNITS to expand (keeping terminator) +// tail - will be updated +// +// |<---rest--->| +// <-bias->|<-tail->| | +// +--------------------+ +// | abcdefghi | +// +--------------------+ +// | | +// data index +// +// If the series has enough space within it, then it will be used, +// otherwise the series data will be reallocated. +// +// When expanded at the head, if bias space is available, it will +// be used (if it provides enough space). +// +// !!! It seems the original intent of this routine was +// to be used with a group of other routines that were "Noterm" +// and do not terminate. However, Expand_Series assumed that +// the capacity of the original series was at least (tail + 1) +// elements, and would include the terminator when "sliding" +// the data in the update. This makes the other Noterm routines +// seem a bit high cost for their benefit. If this were to be +// changed to Expand_Series_Noterm it would put more burden +// on the clients...for a *potential* benefit in being able to +// write just an END marker into the terminal REBVAL vs. copying +// the entire value cell. (Of course, with a good memcpy it +// might be an irrelevant difference.) For the moment we reverse +// the burden by enforcing the assumption that the incoming series +// was already terminated. That way our "slide" of the data via +// memcpy will keep it terminated. +// +// WARNING: never use direct pointers into the series data, as the +// series data can be relocated in memory. +// +void Expand_Series(REBSER *s, REBCNT index, REBCNT delta) { - REBSER *series; - REBNOD *node; - REBPOL *pool; - REBCNT pool_num; - - CHECK_STACK(&series); - - if (((REBU64)length * wide) > MAX_I32) Trap0(RE_NO_MEMORY); - - PG_Reb_Stats->Series_Made++; - PG_Reb_Stats->Series_Memory += length * wide; - - ASSERT(wide != 0, RP_BAD_SERIES); - -// if (GC_TRIGGER) Recycle(); - - series = (REBSER *)Make_Node(SERIES_POOL); - length *= wide; - pool_num = FIND_POOL(length); - if (pool_num < SYSTEM_POOL) { - pool = &Mem_Pools[pool_num]; - if (!pool->first) Fill_Pool(pool); - node = pool->first; - pool->first = *node; - pool->free--; - length = pool->wide; - } else { - if (powerof2) { - // !!! WHO added this and why??? Just use a left shift and mask! - REBCNT len=2048; - while(len= delta) { + + //=//// HEAD INSERTION OPTIMIZATION ///////////////////////////////////=// + + s->content.dynamic.data -= wide * delta; + s->content.dynamic.len += delta; + s->content.dynamic.rest += delta; + SER_SUB_BIAS(s, delta); + + #if !defined(NDEBUG) + if (GET_SER_FLAG(s, SERIES_FLAG_ARRAY)) { + // + // When the bias region was marked, it was made "unsettable" if + // this was a debug build. Now that the memory is included in + // the array again, we want it to be "settable", but still trash + // until the caller puts something there. + // + // !!! The unsettable feature is currently not implemented, + // but when it is this will be useful. + // + for (index = 0; index < delta; index++) + INIT_CELL(ARR_AT(ARR(s), index)); + } + #endif + return; + } + + // Width adjusted variables: + + REBCNT start = index * wide; + REBCNT extra = delta * wide; + REBCNT size = SER_LEN(s) * wide; + + // + wide for terminator + if ((size + extra + wide) <= SER_REST(s) * SER_WIDE(s)) { + // + // No expansion was needed. Slide data down if necessary. Note that + // the tail is not moved and instead the termination is done + // separately with TERM_SERIES (in case it reaches an implicit + // termination that is not a full-sized cell). + + memmove( + SER_DATA_RAW(s) + start + extra, + SER_DATA_RAW(s) + start, + size - start + ); + + SET_SERIES_LEN(s, len_old + delta); + assert( + !was_dynamic || + ( + (SER_LEN(s) + SER_BIAS(s)) * wide + < SER_TOTAL(s) + ) + ); + + TERM_SERIES(s); + + #if !defined(NDEBUG) + if (GET_SER_FLAG(s, SERIES_FLAG_ARRAY)) { + // + // The opened up area needs to be set to "settable" trash in the + // debug build. This takes care of making "unsettable" values + // settable (if part of the expansion is in what was formerly the + // ->rest), as well as just making sure old data which was in + // the expanded region doesn't get left over on accident. + // + // !!! The unsettable feature is not currently implemented, but + // when it is this will be useful. + // + while (delta != 0) { + --delta; + INIT_CELL(ARR_AT(ARR(s), index + delta)); + } + } + #endif + + return; + } + +//=//// INSUFFICIENT CAPACITY, NEW ALLOCATION REQUIRED ////////////////////=// + + if (GET_SER_FLAG(s, SERIES_FLAG_FIXED_SIZE)) + fail (Error_Locked_Series_Raw()); + +#ifndef NDEBUG + if (Reb_Opts->watch_expand) { + printf( + "Expand %p wide: %d tail: %d delta: %d\n", + cast(void*, s), + cast(int, wide), + cast(int, len_old), + cast(int, delta) + ); + fflush(stdout); + } #endif -#ifdef MUNGWALL - node = (REBNOD *) Make_Mem(length+2*MUNG_SIZE); -#else - node = (REBNOD *) Make_Mem(length); + + // Have we recently expanded the same series? + + REBCNT x = 1; + REBUPT n_available = 0; + REBUPT n_found; + for (n_found = 0; n_found < MAX_EXPAND_LIST; n_found++) { + if (Prior_Expand[n_found] == s) { + x = SER_LEN(s) + delta + 1; // Double the size + break; + } + if (!Prior_Expand[n_found]) + n_available = n_found; + } + +#ifndef NDEBUG + if (Reb_Opts->watch_expand) { + // Print_Num("Expand:", series->tail + delta + 1); + } +#endif + + // !!! The protocol for doing new allocations currently mandates that the + // dynamic content area be cleared out. But the data lives in the content + // area if there's no dynamic portion. The in-REBSER content has to be + // copied to preserve the data. This could be generalized so that the + // routines that do calculations operate on the content as a whole, not + // the REBSER node, so the content is extracted either way. + // + union Reb_Series_Content content_old; + REBINT bias_old; + REBCNT size_old; + REBYTE *data_old; + if (was_dynamic) { + data_old = s->content.dynamic.data; + bias_old = SER_BIAS(s); + size_old = Series_Allocation_Unpooled(s); + } + else { + content_old = s->content; // may be raw bits + data_old = cast(REBYTE*, &content_old); + } + + // The new series will *always* be dynamic, because it would not be + // expanding if a fixed size allocation was sufficient. + + s->content.dynamic.data = NULL; + SET_SER_FLAG(s, SERIES_FLAG_POWER_OF_2); + if (!Series_Data_Alloc(s, len_old + delta + x)) + fail (Error_No_Memory((len_old + delta + x) * wide)); + + assert(s->content.dynamic.data != NULL); + + // If necessary, add series to the recently expanded list + // + if (n_found >= MAX_EXPAND_LIST) + Prior_Expand[n_available] = s; + + // Copy the series up to the expansion point + // + memcpy(s->content.dynamic.data, data_old, start); + + // Copy the series after the expansion point. + // + memcpy( + s->content.dynamic.data + start + extra, + data_old + start, + size - start + ); + s->content.dynamic.len = len_old + delta; + + TERM_SERIES(s); + + if (was_dynamic) { + // + // We have to de-bias the data pointer before we can free it. + // + assert(SER_BIAS(s) == 0); // should be reset + Free_Unbiased_Series_Data(data_old - (wide * bias_old), size_old); + } + +#if !defined(NDEBUG) + PG_Reb_Stats->Series_Expanded++; #endif - if (!node) { - Free_Node(SERIES_POOL, (REBNOD *)series); - Trap0(RE_NO_MEMORY); - } -#ifdef MUNGWALL - memcpy((REBYTE *)node,MUNG_PATTERN1,MUNG_SIZE); - memcpy(((REBYTE *)node)+length+MUNG_SIZE,MUNG_PATTERN2,MUNG_SIZE); - node=(REBNOD *)(((REBYTE *)node)+MUNG_SIZE); + + assert(NOT_SER_FLAG(s, NODE_FLAG_MARKED)); +} + + +// +// Remake_Series: C +// +// Reallocate a series as a given maximum size. Content in the retained +// portion of the length will be preserved if NODE_FLAG_NODE is passed in. +// +void Remake_Series(REBSER *s, REBCNT units, REBYTE wide, REBUPT flags) +{ + // !!! This routine is being scaled back in terms of what it's allowed to + // do for the moment; so the method of passing in flags is a bit strange. + // + assert((flags & ~(NODE_FLAG_NODE | SERIES_FLAG_POWER_OF_2)) == 0); + + REBOOL preserve = LOGICAL(flags & NODE_FLAG_NODE); + + REBCNT len_old = SER_LEN(s); + REBYTE wide_old = SER_WIDE(s); + +#if !defined(NDEBUG) + if (preserve) + assert(wide == wide_old); // can't change width if preserving #endif - Mem_Pools[SYSTEM_POOL].has += length; - Mem_Pools[SYSTEM_POOL].free++; - } -#ifdef CHAFF - memset((REBYTE *)node, 0xff, length); + + assert(NOT_SER_FLAG(s, SERIES_FLAG_FIXED_SIZE)); + + REBOOL was_dynamic = GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC); + + REBINT bias_old; + REBINT size_old; + + // Extract the data pointer to take responsibility for it. (The pointer + // may have already been extracted if the caller is doing their own + // updating preservation.) + + REBYTE *data_old; + union Reb_Series_Content content_old; + if (was_dynamic) { + assert(s->content.dynamic.data != NULL); + data_old = s->content.dynamic.data; + bias_old = SER_BIAS(s); + size_old = Series_Allocation_Unpooled(s); + } + else { + content_old = s->content; + data_old = cast(REBYTE*, &content_old); + } + + // We don't want to update the header bits to reflect a new state of the + // SERIES_FLAG_POWER_OF_2 until *after* Series_Allocation_Unpooled + // was able to take the old state into account. + // + SER_SET_WIDE(s, wide); + s->header.bits |= flags; + + // !!! Currently the remake won't make a series that fits in the size of + // a REBSER. All series code needs a general audit, so that should be one + // of the things considered. + + s->content.dynamic.data = NULL; + + if (!Series_Data_Alloc(s, units + 1)) { + // Put series back how it was (there may be extant references) + s->content.dynamic.data = data_old; + fail (Error_No_Memory((units + 1) * wide)); + } + assert(s->content.dynamic.data != NULL); + + if (preserve) { + // Preserve as much data as possible (if it was requested, some + // operations may extract the data pointer ahead of time and do this + // more selectively) + + s->content.dynamic.len = MIN(len_old, units); + memcpy( + s->content.dynamic.data, + data_old, + s->content.dynamic.len * wide + ); + } else + s->content.dynamic.len = 0; + + if (GET_SER_FLAG(s, SERIES_FLAG_ARRAY)) + TERM_ARRAY_LEN(ARR(s), SER_LEN(s)); + else + TERM_SEQUENCE(s); + + if (was_dynamic) + Free_Unbiased_Series_Data(data_old - (wide_old * bias_old), size_old); +} + + +// +// GC_Kill_Series: C +// +// Only the garbage collector should be calling this routine. +// It frees a series even though it is under GC management, +// because the GC has figured out no references exist. +// +void GC_Kill_Series(REBSER *s) +{ + assert(!IS_FREE_NODE(s)); + assert(NOT(s->header.bits & NODE_FLAG_CELL)); // use Free_Paired + + if (GET_SER_FLAG(s, SERIES_FLAG_UTF8_STRING)) + GC_Kill_Interning(s); // needs special handling to adjust canons + + // Remove series from expansion list, if found: + REBCNT n; + for (n = 1; n < MAX_EXPAND_LIST; n++) { + if (Prior_Expand[n] == s) Prior_Expand[n] = 0; + } + + if (GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC)) { + REBCNT size = SER_TOTAL(s); + + REBYTE wide = SER_WIDE(s); + REBCNT bias = SER_BIAS(s); + s->content.dynamic.data -= wide * bias; + Free_Unbiased_Series_Data( + s->content.dynamic.data, + Series_Allocation_Unpooled(s) + ); + + // !!! This indicates reclaiming of the space, not for the series + // nodes themselves...have they never been accounted for, e.g. in + // R3-Alpha? If not, they should be...additional sizeof(REBSER), + // also tracking overhead for that. Review the question of how + // the GC watermarks interact with Alloc_Mem and the "higher + // level" allocations. + + i32 tmp; + GC_Ballast = REB_I32_ADD_OF(GC_Ballast, size, &tmp) ? MAX_I32 : tmp; + } + else { + // Special GC processing for HANDLE! when the handle is implemented as + // a singular array, so that if the handle represents a resource, it + // may be freed. + // + // Note that not all singular arrays containing a HANDLE! should be + // interpreted that when the array is freed the handle is freed (!) + // Only when the handle array pointer in the freed singular + // handle matches the REBARR being freed. (It may have been just a + // singular array that happened to contain a handle, otherwise, as + // opposed to the specific singular made for the handle's GC awareness) + + if (GET_SER_FLAG(s, SERIES_FLAG_ARRAY)) { + RELVAL *v = ARR_HEAD(ARR(s)); + if (NOT_END(v) && IS_HANDLE(v)) { + if (v->extra.singular == ARR(s)) { + (s->misc.cleaner)(KNOWN(v)); + } + } + } + } + +#if !defined(NDEBUG) + s->info.bits = 0; // makes it look like width is 0 #endif - series->tail = series->size = 0; - SERIES_REST(series) = length / wide; - series->data = (REBYTE *)node; - series->info = wide; // also clears flags - LABEL_SERIES(series, "make"); - if ((GC_Ballast -= length) <= 0) SET_SIGNAL(SIG_RECYCLE); + TRASH_POINTER_IF_DEBUG(s->link.keylist); + + Free_Node(SER_POOL, s); - // Keep the last few series in the nursery, safe from GC: - if (GC_Last_Infant >= MAX_SAFE_SERIES) GC_Last_Infant = 0; - GC_Infants[GC_Last_Infant++] = series; + // GC may no longer be necessary: + if (GC_Ballast > 0) CLR_SIGNAL(SIG_RECYCLE); - CHECK_MEMORY(2); +#if !defined(NDEBUG) + PG_Reb_Stats->Series_Freed++; - return series; + // Update the do count to be the count on which the series was freed + // + s->do_count = TG_Do_Count; +#endif } -/*********************************************************************** -** -*/ void Free_Series_Data(REBSER *series, REBOOL protect) -/* -** Free series data, but leave series header. Protect flag -** can be used to prevent GC away from the data field. -** -***********************************************************************/ +inline static void Drop_Manual_Series(REBSER *s) { - REBNOD *node; - REBPOL *pool; - REBCNT pool_num; - REBCNT size; - - // !!!! Dump_Series(series, "Free-Data"); - - if (SERIES_FREED(series) || series->data == BAD_MEM_PTR) return; // No free twice. - if (IS_EXT_SERIES(series)) goto clear_header; // Must be library related - - size = SERIES_TOTAL(series); - if ((GC_Ballast += size) > VAL_INT32(TASK_BALLAST)) - GC_Ballast = VAL_INT32(TASK_BALLAST); - - // GC may no longer be necessary: - if (GC_Ballast > 0) CLR_SIGNAL(SIG_RECYCLE); - - series->data -= SERIES_WIDE(series) * SERIES_BIAS(series); - node = (REBNOD *)series->data; - pool_num = FIND_POOL(size); - - if (GC_Stay_Dirty) { - memset(series->data, 0xbb, size); - return; - } - - // Verify that size matches pool size: - if (pool_num < SERIES_POOL) { - ASSERT(Mem_Pools[pool_num].wide == size, RP_FREE_NODE_SIZE); - } - MUNG_CHECK(pool_num,node, size); - - if (pool_num < SYSTEM_POOL) { - pool = &Mem_Pools[pool_num]; - *node = pool->first; - pool->first = node; - pool->free++; - } else { -#ifdef MUNGWALL - Free_Mem(((REBYTE *)node)-MUNG_SIZE, size + MUNG_SIZE*2); -#else - Free_Mem(node, size); + REBSER ** const last_ptr + = &cast(REBSER**, GC_Manuals->content.dynamic.data)[ + GC_Manuals->content.dynamic.len - 1 + ]; + + assert(GC_Manuals->content.dynamic.len >= 1); + if (*last_ptr != s) { + // + // If the series is not the last manually added series, then + // find where it is, then move the last manually added series + // to that position to preserve it when we chop off the tail + // (instead of keeping the series we want to free). + // + REBSER **current_ptr = last_ptr - 1; + while (*current_ptr != s) { + #if !defined(NDEBUG) + if ( + current_ptr + <= cast(REBSER**, GC_Manuals->content.dynamic.data) + ){ + printf("Series not in list of last manually added series\n"); + panic(s); + } + #endif + --current_ptr; + } + *current_ptr = *last_ptr; + } + + // !!! Should GC_Manuals ever shrink or save memory? + // + GC_Manuals->content.dynamic.len--; +} + + +// +// Free_Series: C +// +// Free a series, returning its memory for reuse. You can only +// call this on series that are not managed by the GC. +// +void Free_Series(REBSER *s) +{ +#if !defined(NDEBUG) + // + // If a series has already been freed, we'll find out about that + // below indirectly, so better in the debug build to get a clearer + // error that won't be conflated with a possible tracking problem + // + if (IS_FREE_NODE(s)) { + printf("Trying to Free_Series() on an already freed series\n"); + panic (s); + } + + // We can only free a series that is not under management by the + // garbage collector + // + if (IS_SERIES_MANAGED(s)) { + printf("Trying to Free_Series() on a series managed by GC.\n"); + panic (s); + } #endif - Mem_Pools[SYSTEM_POOL].has -= size; - Mem_Pools[SYSTEM_POOL].free--; - } - CHECK_MEMORY(2); + Drop_Manual_Series(s); -clear_header: - if (protect) { - series->data = BAD_MEM_PTR; // force bad references to trap - series->info = 0; // indicates series deallocated (wide = 0) - } + // With bookkeeping done, use the same routine the GC uses to free + // + GC_Kill_Series(s); } -/*********************************************************************** -** -*/ void Free_Series(REBSER *series) -/* -** Free a series, returning its memory for reuse. -** -***********************************************************************/ +// +// Widen_String: C +// +// Widen string from 1 byte to 2 bytes. +// +// NOTE: allocates new memory. Cached pointers are invalid. +// +void Widen_String(REBSER *s, REBOOL preserve) { - REBCNT n; + REBCNT len_old = SER_LEN(s); + + REBYTE wide_old = SER_WIDE(s); + assert(wide_old == 1); + + REBOOL was_dynamic = GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC); + + REBCNT bias_old; + REBCNT size_old; + REBYTE *data_old; + union Reb_Series_Content content_old; + if (was_dynamic) { + data_old = s->content.dynamic.data; + bias_old = SER_BIAS(s); + size_old = Series_Allocation_Unpooled(s); + } + else { + content_old = s->content; + data_old = cast(REBYTE*, &content_old); + } + +#if !defined(NDEBUG) + // We may be resizing a partially constructed series, or otherwise + // not want to preserve the previous contents + if (preserve) + ASSERT_SERIES(s); +#endif + + s->content.dynamic.data = NULL; - PG_Reb_Stats->Series_Freed++; + SER_SET_WIDE(s, cast(REBYTE, sizeof(REBUNI))); + if (!Series_Data_Alloc(s, len_old + 1)) { + // Put series back how it was (there may be extant references) + s->content.dynamic.data = data_old; + fail (Error_No_Memory((len_old + 1) * sizeof(REBUNI))); + } - // Remove series from expansion list, if found: - for (n = 1; n < MAX_EXPAND_LIST; n++) { - if (Prior_Expand[n] == series) Prior_Expand[n] = 0; - } + if (preserve) { + REBYTE *bp = data_old; + REBUNI *up = UNI_HEAD(s); - Free_Series_Data(series, TRUE); - series->info = 0; // includes width - //series->data = BAD_MEM_PTR; - //series->tail = 0xBAD2BAD2; - //series->size = 0xBAD3BAD3; + REBCNT n; + for (n = 0; n <= len_old; n++) up[n] = bp[n]; // includes terminator + s->content.dynamic.len = len_old; + } + else { + s->content.dynamic.len = 0; + TERM_SEQUENCE(s); + } - Free_Node(SERIES_POOL, (REBNOD *)series); + if (was_dynamic) + Free_Unbiased_Series_Data(data_old - (wide_old * bias_old), size_old); -/* Old torture mode: - if (!SERIES_FREED(series)) { // Don't try to free twice. - MUNG_CHECK(SERIES_POOL, (REBNOD *)series, Mem_Pools[SERIES_POOL].wide); - FREE_SERIES(series); // special GC mark as freed - } -*/ + ASSERT_SERIES(s); } -/*********************************************************************** -** -*/ void Free_Gob(REBGOB *gob) -/* -** Free a gob, returning its memory for reuse. -** -***********************************************************************/ +// +// Manage_Series: C +// +// When a series is first created, it is in a state of being +// manually memory managed. Thus, you can call Free_Series on +// it if you are sure you do not need it. This will transition +// a manually managed series to be one managed by the GC. There +// is no way to transition it back--once a series has become +// managed, only the GC can free it. +// +// All series that wind up in user-visible values *must* be +// managed, because the user can make copies of values +// containing that series. When these copies are made, it's +// no longer safe to assume it's okay to free the original. +// +void Manage_Series(REBSER *s) { - FREE_GOB(gob); +#if !defined(NDEBUG) + if (IS_SERIES_MANAGED(s)) { + printf("Attempt to manage already managed series\n"); + panic (s); + } +#endif + + s->header.bits |= NODE_FLAG_MANAGED; - Free_Node(GOB_POOL, (REBNOD *)gob); + Drop_Manual_Series(s); } -/*********************************************************************** -** -*/ void Prop_Series(REBSER *newser, REBSER *oldser) -/* -** Propagate a series from another. -** -***********************************************************************/ +// +// Is_Value_Managed: C +// +// Determines if a value would be visible to the garbage collector or not. +// Defaults to the answer of TRUE if the value has nothing the GC cares if +// it sees or not. +// +// Note: Avoid causing conditional behavior on this casually. It's really +// for GC internal use and ASSERT_VALUE_MANAGED. Most code should work +// with either managed or unmanaged value states for variables w/o needing +// this test to know which it has.) +// +REBOOL Is_Value_Managed(const RELVAL *value) { - newser->info = oldser->info; - newser->size = oldser->size; -#ifdef SERIES_LABELS - newser->label = oldser->label; + assert(!THROWN(value)); + + if (ANY_CONTEXT(value)) { + REBCTX *context = VAL_CONTEXT(value); + if (IS_ARRAY_MANAGED(CTX_VARLIST(context))) { + ASSERT_ARRAY_MANAGED(CTX_KEYLIST(context)); + return TRUE; + } + assert(NOT(IS_ARRAY_MANAGED(CTX_KEYLIST(context)))); // !!! untrue? + return FALSE; + } + + if (ANY_SERIES(value)) + return IS_SERIES_MANAGED(VAL_SERIES(value)); + + return TRUE; +} + + +// +// Detect_Rebol_Pointer: C +// +// See the elaborate explanation in %m-gc.c for how this works! It is a +// trustworthy method for "sniffing" pointers and discerning whether it is a +// REBSER*, a REBVAL*, or a UTF-8 character string. +// +enum Reb_Pointer_Detect Detect_Rebol_Pointer(const void *p) { + const REBYTE *bp = cast(const REBYTE*, p); + REBYTE left_4_bits = *bp >> 4; + +#if !defined(NDEBUG) + REBUPT cell_flag = NODE_FLAG_CELL; + assert(LEFT_8_BITS(cell_flag) == 0x1); + REBUPT end_flag = NODE_FLAG_END; + assert(LEFT_8_BITS(end_flag) == 0x8); #endif + + switch (left_4_bits) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + return DETECTED_AS_UTF8; // ASCII codepoints 0 - 127 + + // v-- bit sequences starting with `10` (continuation bytes, so not + // valid starting points for a UTF-8 string) + + case 8: // 0xb1000 + if (*bp & 0x8) + return DETECTED_AS_END; // may be end cell or "endlike" header + if (*bp & 0x1) + return DETECTED_AS_VALUE; // unmanaged + return DETECTED_AS_SERIES; // unmanaged + + case 9: // 0xb1001 + if (*bp & 0x8) + return DETECTED_AS_END; // has to be an "endlike" header + panic (p); // would be "marked and unmanaged", not legal + + case 10: // 0b1010 + case 11: // 0b1011 + if (*bp & 0x8) + return DETECTED_AS_END; + if (*bp & 0x1) + return DETECTED_AS_VALUE; // managed, marked if `case 11` + return DETECTED_AS_SERIES; // managed, marked if `case 11` + + // v-- bit sequences starting with `11` are usually legal multi-byte + // valid starting points for UTF-8, with only the exceptions made for + // the illegal 192 and 193 bytes which represent freed series and trash. + + case 12: // 0b1100 + if (*bp == FREED_SERIES_BYTE) + return DETECTED_AS_FREED_SERIES; + + if (*bp == TRASH_CELL_BYTE) + return DETECTED_AS_TRASH_CELL; + + return DETECTED_AS_UTF8; + + case 13: // 0b1101 + case 14: // 0b1110 + case 15: // 0b1111 + return DETECTED_AS_UTF8; + } + + DEAD_END; } -/*********************************************************************** -** -*/ REBFLG Series_In_Pool(REBSER *series) -/* -** Confirm that the series value is in the series pool. -** -***********************************************************************/ +#if !defined(NDEBUG) + +// +// Assert_Pointer_Detection_Working: C +// +void Assert_Pointer_Detection_Working(void) { - REBSEG *seg; - REBSER *start; + assert(Detect_Rebol_Pointer("") == DETECTED_AS_UTF8); + assert(Detect_Rebol_Pointer("asdf") == DETECTED_AS_UTF8); + + assert(Detect_Rebol_Pointer(EMPTY_ARRAY) == DETECTED_AS_SERIES); + assert(Detect_Rebol_Pointer(BLANK_VALUE) == DETECTED_AS_VALUE); + + DECLARE_LOCAL (trash_cell); + assert(Detect_Rebol_Pointer(trash_cell) == DETECTED_AS_TRASH_CELL); + + DECLARE_LOCAL (end_cell); + SET_END(end_cell); + assert(Detect_Rebol_Pointer(end_cell) == DETECTED_AS_END); + assert(Detect_Rebol_Pointer(END) == DETECTED_AS_END); + + // It's not generally known that an Init_Endlike_Header() header will + // not be managed. But the canon END is not managed, and end cells can + // be either managed or unmanaged...but by default, not. + // + assert(NOT(end_cell->header.bits & NODE_FLAG_MANAGED)); + assert(NOT(END->header.bits & NODE_FLAG_MANAGED)); + + REBSER *series = Make_Series(1, sizeof(char)); + assert(Detect_Rebol_Pointer(series) == DETECTED_AS_SERIES); + Free_Series(series); + assert(Detect_Rebol_Pointer(series) == DETECTED_AS_FREED_SERIES); + + // Sanity check the flags used for the Init_Endlike_Header trick + // + assert( + SERIES_INFO_0_IS_TRUE == NODE_FLAG_NODE + && SERIES_INFO_1_IS_FALSE == NODE_FLAG_FREE + && SERIES_INFO_4_IS_TRUE == NODE_FLAG_END + && SERIES_INFO_7_IS_FALSE == NODE_FLAG_CELL + ); + assert( + DO_FLAG_0_IS_TRUE == NODE_FLAG_NODE + && DO_FLAG_1_IS_FALSE == NODE_FLAG_FREE + && DO_FLAG_4_IS_TRUE == NODE_FLAG_END + && DO_FLAG_7_IS_FALSE == NODE_FLAG_CELL + ); +} - // Scan all series headers to check that series->size is correct: - for (seg = Mem_Pools[SERIES_POOL].segs; seg; seg = seg->next) { - start = (REBSER *) (seg + 1); - if (series >= start && series <= (REBSER*)((REBYTE*)start + seg->size - sizeof(REBSER))) - return TRUE; - } - return FALSE; +// +// Check_Memory_Debug: C +// +// Traverse the free lists of all pools -- just to prove we can. +// +// Note: This was useful in R3-Alpha for finding corruption from bad memory +// writes, because a write past the end of a node destroys the pointer for the +// next free area. The Always_Malloc option for Ren-C leverages the faster +// checking built into Valgrind or Address Sanitizer for the same problem. +// However, a call to this is kept in the debug build on init and shutdown +// just to keep it working as a sanity check. +// +REBCNT Check_Memory_Debug(void) +{ + REBOOL expansion_null_found = FALSE; + + REBSEG *seg; + for (seg = Mem_Pools[SER_POOL].segs; seg; seg = seg->next) { + REBSER *s = cast(REBSER*, seg + 1); + + REBCNT n; + for (n = Mem_Pools[SER_POOL].units; n > 0; --n, ++s) { + if (IS_FREE_NODE(s)) + continue; + + if (GET_SER_FLAG(s, NODE_FLAG_CELL)) + continue; // a pairing + + if (NOT(GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC))) + continue; // data lives in the series node itself + + if (SER_REST(s) == 0) + panic (s); // zero size allocations not legal + + if (s->content.dynamic.data == NULL) { + // + // !!! legal during the moment of series expansion only; e.g. + // can only be true for one series at a time (current invariant + // which was needed as a patch so Check_Memory could be called + // during Make_Node()...hacky, should be rethought) + // + if (expansion_null_found) + panic (s); + + expansion_null_found = TRUE; + } + + REBCNT pool_num = FIND_POOL(SER_TOTAL(s)); + if (pool_num >= SER_POOL) + continue; // size doesn't match a known pool + + if (Mem_Pools[pool_num].wide != SER_TOTAL(s)) + panic (s); + } + } + + REBCNT total_free_nodes = 0; + + REBCNT pool_num; + for (pool_num = 0; pool_num < SYSTEM_POOL; pool_num++) { + REBCNT pool_free_nodes = 0; + + REBNOD *node = Mem_Pools[pool_num].first; + for (; node != NULL; node = node->next_if_free) { + ++pool_free_nodes; + + REBOOL found = FALSE; + seg = Mem_Pools[pool_num].segs; + for (; seg != NULL; seg = seg->next) { + if ( + cast(REBUPT, node) > cast(REBUPT, seg) + && ( + cast(REBUPT, node) + < cast(REBUPT, seg) + cast(REBUPT, seg->size) + ) + ){ + if (found) + panic ("node belongs to more than one segment"); + + found = TRUE; + } + } + + if (NOT(found)) + panic ("node does not belong to one of the pool's segments"); + } + + if (Mem_Pools[pool_num].free != pool_free_nodes) + panic ("actual free node count does not agree with pool header"); + + total_free_nodes += pool_free_nodes; + } + + return total_free_nodes; } -/*********************************************************************** -** -*/ REBCNT Check_Memory(void) -/* -** FOR DEBUGGING ONLY: -** Traverse the free lists of all pools -- just to prove we can. -** This is useful for finding corruption from bad memory writes, -** because a write past the end of a node will destory the pointer -** for the next free area. -** -***********************************************************************/ +// +// Dump_All_Series_Of_Size: C +// +void Dump_All_Series_Of_Size(REBCNT size) { - REBCNT pool_num; - REBNOD *node; - REBNOD *pnode; - REBCNT count = 0; - REBSEG *seg; - REBSER *series; - - //Debug_Str(""); - PG_Reb_Stats->Free_List_Checked++; - - // Scan all series headers to check that series->size is correct: - for (seg = Mem_Pools[SERIES_POOL].segs; seg; seg = seg->next) { - series = (REBSER *) (seg + 1); - for (count = Mem_Pools[SERIES_POOL].units; count > 0; count--) { - SKIP_WALL(series); - MUNG_CHECK(SERIES_POOL, series, sizeof(*series)); - if (!SERIES_FREED(series)) { - if (!SERIES_REST(series) || !series->data) - goto crash; - // Does the size match a known pool? - pool_num = FIND_POOL(SERIES_TOTAL(series)); - // Just to be sure the pool matches the allocation: - if (pool_num < SERIES_POOL && Mem_Pools[pool_num].wide != SERIES_TOTAL(series)) - goto crash; - } - series++; - SKIP_WALL(series); - } - } - - // Scan each memory pool: - for (pool_num = 0; pool_num < SYSTEM_POOL; pool_num++) { - count = 0; - // Check each free node in the memory pool: - for (node = Mem_Pools[pool_num].first; node; node = *node) { - count++; - // The node better belong to one of the pool's segments: - for (seg = Mem_Pools[pool_num].segs; seg; seg = seg->next) { - if ((int)node > (int)seg && (int)node < (int)seg + (int)seg->size) break; - } - if (!seg) goto crash; - pnode = node; // for debugger - } - // The number of free nodes must agree with header: - if ( - (Mem_Pools[pool_num].free != count) || - (Mem_Pools[pool_num].free == 0 && Mem_Pools[pool_num].first != 0) - ) - goto crash; - } - - return count; -crash: - Crash(RP_CORRUPT_MEMORY); - return 0; // for compiler only + REBCNT count = 0; + + REBSEG *seg; + for (seg = Mem_Pools[SER_POOL].segs; seg; seg = seg->next) { + REBSER *s = cast(REBSER*, seg + 1); + REBCNT n; + for (n = Mem_Pools[SER_POOL].units; n > 0; --n, ++s) { + if (IS_FREE_NODE(s)) + continue; + + if (SER_WIDE(s) == size) { + ++count; + printf( + "%3d %4d %4d\n", + cast(int, count), + cast(int, SER_LEN(s)), + cast(int, SER_REST(s)) + ); + } + fflush(stdout); + } + } } -/*********************************************************************** -** -*/ void Dump_All(REBINT size) -/* -** Dump all series of a given size. -** -***********************************************************************/ +// +// Dump_Series_In_Pool: C +// +// Dump all series in pool @pool_id, UNKNOWN (-1) for all pools +// +void Dump_Series_In_Pool(REBCNT pool_id) { - REBSEG *seg; - REBSER *series; - REBCNT count; - REBCNT n = 0; - - for (seg = Mem_Pools[SERIES_POOL].segs; seg; seg = seg->next) { - series = (REBSER *) (seg + 1); - for (count = Mem_Pools[SERIES_POOL].units; count > 0; count--) { - SKIP_WALL(series); - if (!SERIES_FREED(series)) { - if (SERIES_WIDE(series) == size && SERIES_GET_FLAG(series, SER_MON)) { - //Debug_Fmt("%3d %4d %4d = \"%s\"", n++, series->tail, SERIES_TOTAL(series), series->data); - Debug_Fmt("%3d %4d %4d = \"%s\"", n++, series->tail, SERIES_REST(series), (SERIES_LABEL(series) ? SERIES_LABEL(series) : "-")); - } - } - series++; - SKIP_WALL(series); - } - } + REBSEG *seg; + for (seg = Mem_Pools[SER_POOL].segs; seg; seg = seg->next) { + REBSER *s = cast(REBSER*, seg + 1); + REBCNT n = 0; + for (n = Mem_Pools[SER_POOL].units; n > 0; --n, ++s) { + if (IS_FREE_NODE(s)) + continue; + + if (GET_SER_FLAG(s, NODE_FLAG_CELL)) + continue; // pairing + + if ( + pool_id == UNKNOWN + || ( + GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC) + && FIND_POOL(SER_TOTAL(s)) == pool_id + ) + ) { + Dump_Series(s, "Dump_Series_In_Pool"); + } + + } + } } -/*********************************************************************** -** -*/ static void Dump_Pools(void) -/* -** Print statistics about all memory pools. -** -***********************************************************************/ +// +// Dump_Pools: C +// +// Print statistics about all memory pools. +// +void Dump_Pools(void) { - REBSEG *seg; - REBCNT segs; - REBCNT size; - REBCNT used; - REBCNT total = 0; - REBCNT tused = 0; - REBCNT n; - - FOREACH(n, SYSTEM_POOL) { - size = segs = 0; - - for (seg = Mem_Pools[n].segs; seg; seg = seg->next, segs++) - size += seg->size; - - used = Mem_Pools[n].has - Mem_Pools[n].free; - Debug_Fmt("Pool[%-2d] %-4dB %-5d/%-5d:%-4d (%-2d%%) %-2d segs, %-07d total", - n, - Mem_Pools[n].wide, - used, - Mem_Pools[n].has, - Mem_Pools[n].units, - Mem_Pools[n].has ? ((used * 100) / Mem_Pools[n].has) : 0, - segs, - size - ); - - tused += used * Mem_Pools[n].wide; - total += size; - } - Debug_Fmt("Pools used %d of %d (%2d%%)", tused, total, (tused*100) / total); - Debug_Fmt("System pool used %d", Mem_Pools[SYSTEM_POOL].has); - //Debug_Fmt("Raw allocator reports %d", PG_Mem_Usage); + REBCNT total = 0; + REBCNT tused = 0; + + REBCNT n; + for (n = 0; n < SYSTEM_POOL; n++) { + REBCNT segs = 0; + REBCNT size = 0; + + size = segs = 0; + + REBSEG *seg; + for (seg = Mem_Pools[n].segs; seg; seg = seg->next, segs++) + size += seg->size; + + REBCNT used = Mem_Pools[n].has - Mem_Pools[n].free; + printf( + "Pool[%-2d] %5dB %-5d/%-5d:%-4d (%3d%%) ", + cast(int, n), + cast(int, Mem_Pools[n].wide), + cast(int, used), + cast(int, Mem_Pools[n].has), + cast(int, Mem_Pools[n].units), + cast(int, + Mem_Pools[n].has != 0 ? ((used * 100) / Mem_Pools[n].has) : 0 + ) + ); + printf("%-2d segs, %-7d total\n", cast(int, segs), cast(int, size)); + + tused += used * Mem_Pools[n].wide; + total += size; + } + + printf( + "Pools used %d of %d (%2d%%)\n", + cast(int, tused), + cast(int, total), + cast(int, (tused * 100) / total) + ); + printf("System pool used %d\n", cast(int, Mem_Pools[SYSTEM_POOL].has)); + printf("Raw allocator reports %lu\n", cast(unsigned long, PG_Mem_Usage)); + + fflush(stdout); } -/*********************************************************************** -** -*/ REBU64 Inspect_Series(REBCNT flags) -/* -***********************************************************************/ +// +// Inspect_Series: C +// +// !!! This is an old routine which was exposed through STATS to "expert +// users". Its purpose is to calculate the total amount of memory currently +// in use by series, but it could also print out a breakdown of categories. +// +REBU64 Inspect_Series(REBOOL show) { - REBSEG *seg; - REBSER *series; - REBCNT segs, n, tot, blks, strs, unis, nons, odds, fre; - REBCNT str_size, uni_size, blk_size, odd_size, seg_size, fre_size; - REBFLG f = 0; - REBINT pool_num; -#ifdef SERIES_LABELS - REBYTE *kind; -#endif - REBU64 tot_size; - - segs = tot = blks = strs = unis = nons = odds = fre = 0; - seg_size = str_size = uni_size = blk_size = odd_size = fre_size = 0; - tot_size = 0; - DS_TERMINATE; - - for (seg = Mem_Pools[SERIES_POOL].segs; seg; seg = seg->next) { - - seg_size += seg->size; - segs++; - - series = (REBSER *) (seg + 1); - - for (n = Mem_Pools[SERIES_POOL].units; n > 0; n--) { - SKIP_WALL(series); - MUNG_CHECK(SERIES_POOL, series, sizeof(*series)); - - if (SERIES_WIDE(series)) { - tot++; - tot_size += SERIES_TOTAL(series); - f = 0; - } else { - fre++; - } - -#ifdef SERIES_LABELS - kind = "----"; - if (SERIES_GET_FLAG(series, SER_KEEP)) kind = "KEEP"; - //if (Find_Root(series)) kind = "ROOT"; - if (!SERIES_FREED(series) && series->label) { - Debug_Fmt_("%08x: %16s %s ", series, series->label, kind); - f = 1; - } else if (!SERIES_FREED(series) && (flags & 0x100)) { - Debug_Fmt_("%08x: %s ", series, kind); - f = 1; - } -#endif - if (SERIES_WIDE(series) == sizeof(REBVAL)) { - blks++; - blk_size += SERIES_TOTAL(series); - if (f) Debug_Fmt_("BLOCK "); - } - else if (SERIES_WIDE(series) == 1) { - strs++; - str_size += SERIES_TOTAL(series); - if (f) Debug_Fmt_("STRING"); - } - else if (SERIES_WIDE(series) == sizeof(REBUNI)) { - unis++; - uni_size += SERIES_TOTAL(series); - if (f) Debug_Fmt_("UNICOD"); - } - else if (SERIES_WIDE(series)) { - odds++; - odd_size += SERIES_TOTAL(series); - if (f) Debug_Fmt_("ODD[%d]", SERIES_WIDE(series)); - } - if (f && SERIES_WIDE(series)) { - Debug_Fmt(" units: %-5d tail: %-5d bytes: %-7d", SERIES_REST(series), SERIES_TAIL(series), SERIES_TOTAL(series)); - } - - series++; - SKIP_WALL(series); - } - } - - // Size up unused memory: - for (pool_num = 0; pool_num < SYSTEM_POOL; pool_num++) { - fre_size += Mem_Pools[pool_num].free * Mem_Pools[pool_num].wide; - } - - if (flags & 1) { - Debug_Fmt( - "Series Memory Info:\n" - " node size = %d\n" - " series size = %d\n" - " %-6d segs = %-7d bytes - headers\n" - " %-6d blks = %-7d bytes - blocks\n" - " %-6d strs = %-7d bytes - byte strings\n" - " %-6d unis = %-7d bytes - unicode strings\n" - " %-6d odds = %-7d bytes - odd series\n" - " %-6d used = %-7d bytes - total used\n" - " %-6d free / %-7d bytes - free headers / node-space\n" - , - sizeof(REBVAL), - sizeof(REBSER), - segs, seg_size, - blks, blk_size, - strs, str_size, - unis, uni_size, - odds, odd_size, - tot, tot_size, - fre, fre_size // the 2 are not related - ); - } - - if (flags & 2) Dump_Pools(); - - return tot_size; + REBCNT segs = 0; + REBCNT tot = 0; + REBCNT blks = 0; + REBCNT strs = 0; + REBCNT unis = 0; + REBCNT odds = 0; + REBCNT fre = 0; + + REBCNT seg_size = 0; + REBCNT str_size = 0; + REBCNT uni_size = 0; + REBCNT blk_size = 0; + REBCNT odd_size = 0; + + REBU64 tot_size = 0; + + REBSEG *seg; + for (seg = Mem_Pools[SER_POOL].segs; seg; seg = seg->next) { + + seg_size += seg->size; + segs++; + + REBSER *s = cast(REBSER*, seg + 1); + + REBCNT n; + for (n = Mem_Pools[SER_POOL].units; n > 0; n--) { + if (IS_FREE_NODE(s)) { + ++fre; + continue; + } + + ++tot; + + if (GET_SER_FLAG(s, NODE_FLAG_CELL)) + continue; + + tot_size += SER_TOTAL_IF_DYNAMIC(s); // else 0 + + if (GET_SER_FLAG(s, SERIES_FLAG_ARRAY)) { + blks++; + blk_size += SER_TOTAL_IF_DYNAMIC(s); + } + else if (SER_WIDE(s) == 1) { + strs++; + str_size += SER_TOTAL_IF_DYNAMIC(s); + } + else if (SER_WIDE(s) == sizeof(REBUNI)) { + unis++; + uni_size += SER_TOTAL_IF_DYNAMIC(s); + } + else if (SER_WIDE(s)) { + odds++; + odd_size += SER_TOTAL_IF_DYNAMIC(s); + } + + ++s; + } + } + + // Size up unused memory: + // + REBU64 fre_size = 0; + REBINT pool_num; + for (pool_num = 0; pool_num < SYSTEM_POOL; pool_num++) { + fre_size += Mem_Pools[pool_num].free * Mem_Pools[pool_num].wide; + } + + if (show) { + printf("Series Memory Info:\n"); + printf(" REBVAL size = %lu\n", cast(unsigned long, sizeof(REBVAL))); + printf(" REBSER size = %lu\n", cast(unsigned long, sizeof(REBSER))); + printf( + " %-6d segs = %-7d bytes - headers\n", + cast(int, segs), + cast(int, seg_size) + ); + printf( + " %-6d blks = %-7d bytes - blocks\n", + cast(int, blks), + cast(int, blk_size) + ); + printf( + " %-6d strs = %-7d bytes - byte strings\n", + cast(int, strs), + cast(int, str_size) + ); + printf( + " %-6d unis = %-7d bytes - uni strings\n", + cast(int, unis), + cast(int, uni_size) + ); + printf( + " %-6d odds = %-7d bytes - odd series\n", + cast(int, odds), + cast(int, odd_size) + ); + printf( + " %-6d used = %lu bytes - total used\n", + cast(int, tot), + cast(unsigned long, tot_size) + ); + printf(" %lu free headers\n", cast(unsigned long, fre)); + printf(" %lu bytes node-space\n", cast(unsigned long, fre_size)); + printf("\n"); + } + + fflush(stdout); + + return tot_size; } +#endif diff --git a/src/core/m-series.c b/src/core/m-series.c index c7ac0fa0fb..78edc1f9c6 100644 --- a/src/core/m-series.c +++ b/src/core/m-series.c @@ -1,534 +1,525 @@ -/*********************************************************************** -** -** REBOL Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: m-series.c -** Summary: implements REBOL's series concept -** Section: memory -** Author: Carl Sassenrath -** -***********************************************************************/ +// +// File: %m-series.c +// Summary: "implements REBOL's series concept" +// Section: memory +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" +#include "mem-series.h" // low-level series memory access +#include "sys-int-funcs.h" -/*********************************************************************** -** -*/ void Expand_Series(REBSER *series, REBCNT index, REBCNT delta) -/* -** Expand a series at a particular index point by the number -** number of units specified by delta. -** -** index - where space is expanded (but not cleared) -** delta - number of UNITS to expand (keeping terminator) -** tail - will be updated -** -** |<---rest--->| -** <-bias->|<-tail->| | -** +--------------------+ -** | abcdefghi | -** +--------------------+ -** | | -** data index -** -** If the series has enough space within it, then it will be used, -** otherwise the series data will be reallocated. -** -** When expanded at the head, if bias space is available, it will -** be used (if it provides enough space). -** -** WARNING: never use direct pointers into the series data, as the -** series data can be relocated in memory. -** -***********************************************************************/ -{ - REBCNT start; - REBCNT size; - REBCNT extra; - REBCNT wide; - REBSER *newser, swap; - REBCNT n; - REBCNT x; - - if (delta == 0) return; - - // Optimized case of head insertion: - if (index == 0 && SERIES_BIAS(series) >= delta) { - series->data -= SERIES_WIDE(series) * delta; - SERIES_TAIL(series) += delta; - SERIES_REST(series) += delta; - SERIES_SUB_BIAS(series, delta); - return; - } - - // Range checks: - if (delta & 0x80000000) Trap0(RE_PAST_END); // 2GB max - if (index > series->tail) index = series->tail; // clip - - // Width adjusted variables: - wide = SERIES_WIDE(series); - start = index * wide; - extra = delta * wide; - size = (series->tail + 1) * wide; - - // Do we need to expand the current series allocation? - // WARNING: Do not use ">=" below or newser size may be the same! - if ((size + extra) > SERIES_SPACE(series)) { - if (IS_LOCK_SERIES(series)) Crash(RP_LOCKED_SERIES); - //DISABLE_GC; // Don't let GC occur just for an expansion. - - if (Reb_Opts->watch_expand) { - Debug_Fmt("Expand %x wide: %d tail: %d delta: %d", series, wide, series->tail, delta); - } - - // Create a new series that is bigger. - // Have we recently expanded the same series? - x = 1; - n = (REBCNT)(Prior_Expand[0]); - do { - if (Prior_Expand[n] == series) { - x = series->tail + delta + 1; // Double the size - break; - } - if (++n >= MAX_EXPAND_LIST) n = 1; - } while (n != (REBCNT)(Prior_Expand[0])); -#ifdef DEBUGGING - Print_Num("Expand:", series->tail + delta + 1); -#endif - newser = Make_Series(series->tail + delta + x, wide, TRUE); - // If necessary, add series to the recently expanded list: - if (Prior_Expand[n] != series) { - n = (REBCNT)(Prior_Expand[0]) + 1; - if (n >= MAX_EXPAND_LIST) n = 1; - Prior_Expand[n] = series; - } - Prior_Expand[0] = (REBSER*)n; // start next search here - Prop_Series(newser, series); - //ENABLE_GC; - - // Copy the series up to the expansion point: - memcpy(newser->data, series->data, start); - - // Copy the series after the expansion point: - // In AT_TAIL cases, this just moves the terminator to the new tail. - memcpy(newser->data + start + extra, series->data + start, size - start); - - newser->tail = series->tail + delta; - - // Swap new and old series, then free the old one. - // This seems silly, but this method isolates us from - // needing to know the internals series headers. - swap = *series; - *series = *newser; - *newser = swap; - Free_Series(newser); - SERIES_SET_BIAS(series, 0); // be sure it is reset - - PG_Reb_Stats->Series_Expanded++; // Metric - CHECK_MEMORY(3); - return; - } - - // No expansion was need. Slide data down if necessary. - // Note that the tail is always moved here. This is probably faster - // than doing the computation to determine if it is needs to be done. - memmove(series->data + start + extra, series->data + start, size - start); - series->tail += delta; - - if ((SERIES_TAIL(series) + SERIES_BIAS(series)) * wide >= SERIES_TOTAL(series)) { - Dump_Series(series, "Overflow"); - ASSERT(0, RP_OVER_SERIES); - } - - CHECK_MEMORY(3); -} - -/*********************************************************************** -** -*/ void Extend_Series(REBSER *series, REBCNT delta) -/* -** Extend a series at its end without affecting its tail index. -** -***********************************************************************/ +// +// Extend_Series: C +// +// Extend a series at its end without affecting its tail index. +// +void Extend_Series(REBSER *s, REBCNT delta) { - REBCNT tail = series->tail; // maintain tail position - EXPAND_SERIES_TAIL(series, delta); - series->tail = tail; + REBCNT len_old = SER_LEN(s); + EXPAND_SERIES_TAIL(s, delta); + SET_SERIES_LEN(s, len_old); } -/*********************************************************************** -** -*/ REBCNT Insert_Series(REBSER *series, REBCNT index, REBYTE *data, REBCNT len) -/* -** Insert a series of values (bytes, longs, reb-vals) into the -** series at the given index. Expand it if necessary. Does -** not add a terminator to tail. -** -***********************************************************************/ -{ - if (index > series->tail) index = series->tail; - Expand_Series(series, index, len); // tail += len - //Print("i: %d t: %d l: %d x: %d s: %d", index, series->tail, len, (series->tail + 1) * SERIES_WIDE(series), series->size); - memcpy(series->data + (SERIES_WIDE(series) * index), data, SERIES_WIDE(series) * len); - //*(int *)(series->data + (series->tail-1) * SERIES_WIDE(series)) = 5; // for debug purposes - return index + len; +// +// Insert_Series: C +// +// Insert a series of values (bytes, longs, reb-vals) into the +// series at the given index. Expand it if necessary. Does +// not add a terminator to tail. +// +REBCNT Insert_Series( + REBSER *s, + REBCNT index, + const REBYTE *data, + REBCNT len +) { + if (index > SER_LEN(s)) + index = SER_LEN(s); + + Expand_Series(s, index, len); // tail += len + + memcpy( + SER_DATA_RAW(s) + (SER_WIDE(s) * index), + data, + SER_WIDE(s) * len + ); + + return index + len; } -/*********************************************************************** -** -*/ void Append_Series(REBSER *series, REBYTE *data, REBCNT len) -/* -** Append value(s) onto the tail of a series. The len is -** the number of units (bytes, REBVALS, etc.) of the data, -** and does not include the terminator (which will be added). -** The new tail position will be returned as the result. -** A terminator will be added to the end of the appended data. -** -***********************************************************************/ +// +// Append_Series: C +// +// Append value(s) onto the tail of a series. The len is +// the number of units (bytes, REBVALS, etc.) of the data, +// and does not include the terminator (which will be added). +// The new tail position will be returned as the result. +// A terminator will be added to the end of the appended data. +// +void Append_Series(REBSER *s, const REBYTE *data, REBCNT len) { - REBCNT tail = series->tail; - REBCNT wide = SERIES_WIDE(series); + REBCNT len_old = SER_LEN(s); + REBYTE wide = SER_WIDE(s); - EXPAND_SERIES_TAIL(series, len); - memcpy(series->data + (wide * tail), data, wide * len); - CLEAR(series->data + (wide * series->tail), wide); // terminator -} + assert(NOT_SER_FLAG(s, SERIES_FLAG_ARRAY)); + EXPAND_SERIES_TAIL(s, len); + memcpy(SER_DATA_RAW(s) + (wide * len_old), data, wide * len); -/*********************************************************************** -** -*/ void Append_Mem_Extra(REBSER *series, REBYTE *data, REBCNT len, REBCNT extra) -/* -** An optimized function for appending raw memory bytes to -** a byte-sized series. The series will be expanded if room -** is needed. A zero terminator will be added at the tail. -** The extra size will be assured in the series, but is not -** part of the appended length. (Allows adding additional bytes.) -** -***********************************************************************/ -{ - REBCNT tail = series->tail; - - if ((tail + len + extra + 1) >= SERIES_REST(series)) { - Expand_Series(series, tail, len+extra); // series->tail changed - series->tail -= extra; - } - else { - series->tail += len; - } - - memcpy(series->data + tail, data, len); - STR_TERM(series); + TERM_SERIES(s); } -/*********************************************************************** -** -*/ REBSER *Copy_Series(REBSER *source) -/* -** Copy any series, including terminator for it. -** -***********************************************************************/ +// +// Append_Values_Len: C +// +// Append value(s) onto the tail of an array. The len is +// the number of units and does not include the terminator +// (which will be added). +// +void Append_Values_Len(REBARR *a, const REBVAL head[], REBCNT len) { - REBCNT len = source->tail + 1; - REBSER *series = Make_Series(len, SERIES_WIDE(source), FALSE); + REBCNT old_len = ARR_LEN(a); - memcpy(series->data, source->data, len * SERIES_WIDE(source)); - series->tail = source->tail; - return series; -} + // updates tail, which could move data storage. + // + EXPAND_SERIES_TAIL(SER(a), len); + memcpy(ARR_AT(a, old_len), head, sizeof(REBVAL) * len); -/*********************************************************************** -** -*/ REBSER *Copy_Series_Part(REBSER *source, REBCNT index, REBCNT length) -/* -** Copy any subseries, including terminator for it. -** -***********************************************************************/ -{ - REBSER *series = Make_Series(length+1, SERIES_WIDE(source), FALSE); - - memcpy(series->data, source->data + index * SERIES_WIDE(source), (length+1) * SERIES_WIDE(source)); - series->tail = length; - return series; + TERM_ARRAY_LEN(a, ARR_LEN(a)); } -/*********************************************************************** -** -*/ REBSER *Copy_Series_Value(REBVAL *value) -/* -** Copy a series from its value structure. -** Index does not need to be at head location. -** -***********************************************************************/ +// +// Copy_Sequence: C +// +// Copy any series that *isn't* an "array" (such as STRING!, +// BINARY!, BITSET!, VECTOR!...). Includes the terminator. +// +// Use Copy_Array routines (which specify Shallow, Deep, etc.) for +// greater detail needed when expressing intent for Rebol Arrays. +// +// Note: No suitable name for "non-array-series" has been picked. +// "Sequence" is used for now because Copy_Non_Array() doesn't +// look good and lots of things aren't "Rebol Arrays" that aren't +// series. The main idea was just to get rid of the generic +// Copy_Series() routine, which doesn't call any attention +// to the importance of stating one's intentions specifically +// about semantics when copying an array. +// +REBSER *Copy_Sequence(REBSER *original) { - return Copy_Series_Part(VAL_SERIES(value), VAL_INDEX(value), VAL_LEN(value)); + assert(NOT_SER_FLAG(original, SERIES_FLAG_ARRAY)); + + REBCNT len = SER_LEN(original); + REBSER *copy = Make_Series(len + 1, SER_WIDE(original)); + + memcpy( + SER_DATA_RAW(copy), + SER_DATA_RAW(original), + len * SER_WIDE(original) + ); + TERM_SEQUENCE_LEN(copy, SER_LEN(original)); + return copy; } -#ifdef NOT_USED -/*********************************************************************** -** -*/ REBINT Clone_Series(REBVAL *dst, REBVAL *src) -/* -** Properly deep copy all types of series. -** Return TRUE if BLOCK type. -** -***********************************************************************/ +// +// Copy_Sequence_At_Len: C +// +// Copy a subseries out of a series that is not an array. +// Includes the terminator for it. +// +// Use Copy_Array routines (which specify Shallow, Deep, etc.) for +// greater detail needed when expressing intent for Rebol Arrays. +// +REBSER *Copy_Sequence_At_Len(REBSER *original, REBCNT index, REBCNT len) { - Check_Stack(); - if (VAL_TYPE(src) < REB_BLOCK) { - if (VAL_SERIES_WIDTH(src) == 4) - VAL_SERIES(dst) = Make_Quad(VAL_BIN(src), VAL_TAIL(src)); - else - VAL_SERIES(dst) = Copy_String(VAL_SERIES(src)); - return FALSE; - } else { - - VAL_SERIES(dst) = Clone_Block(VAL_SERIES(src)); - if (IS_HASH(dst) || IS_LIST(dst)) - VAL_SERIES_SIDE(dst) = Copy_Side_Series(VAL_SERIES_SIDE(dst)); - return TRUE; - } + assert(NOT_SER_FLAG(original, SERIES_FLAG_ARRAY)); + + REBSER *copy = Make_Series(len + 1, SER_WIDE(original)); + memcpy( + SER_DATA_RAW(copy), + SER_DATA_RAW(original) + index * SER_WIDE(original), + (len + 1) * SER_WIDE(original) + ); + TERM_SEQUENCE_LEN(copy, len); + return copy; } -#endif -/*********************************************************************** -** -*/ void Remove_Series(REBSER *series, REBCNT index, REBINT len) -/* -** Remove a series of values (bytes, longs, reb-vals) from the -** series at the given index. -** -***********************************************************************/ +// +// Copy_Sequence_At_Position: C +// +// Copy a non-array series from its value structure, using the +// value's index as the location to start copying the data. +// +REBSER *Copy_Sequence_At_Position(const REBVAL *position) { - REBCNT start; - REBCNT length; - REBYTE *data; - - if (len <= 0) return; - - // Optimized case of head removal: - if (index == 0) { - if ((REBCNT)len > series->tail) len = series->tail; - SERIES_TAIL(series) -= len; - if (SERIES_TAIL(series) == 0) { - // Reset bias to zero: - len = SERIES_BIAS(series); - SERIES_SET_BIAS(series, 0); - SERIES_REST(series) += len; - series->data -= SERIES_WIDE(series) * len; - CLEAR(series->data, SERIES_WIDE(series)); // terminate - } else { - // Add bias to head: - SERIES_ADD_BIAS(series, len); - SERIES_REST(series) -= len; - series->data += SERIES_WIDE(series) * len; - if (NZ(start = SERIES_BIAS(series))) { - // If more than half biased: - if (start >= MAX_SERIES_BIAS || start > SERIES_REST(series)) - Reset_Bias(series); - } - } - return; - } - - if (index >= series->tail) return; - - start = index * SERIES_WIDE(series); - - // Clip if past end and optimize the remove operation: - if (len + index >= series->tail) { - series->tail = index; - CLEAR(series->data + start, SERIES_WIDE(series)); - return; - } - - length = SERIES_LEN(series) * SERIES_WIDE(series); - series->tail -= (REBCNT)len; - len *= SERIES_WIDE(series); - data = series->data + start; - memmove(data, data + len, length - (start + len)); - - CHECK_MEMORY(5); + return Copy_Sequence_At_Len( + VAL_SERIES(position), VAL_INDEX(position), VAL_LEN_AT(position) + ); } -/*********************************************************************** -** -*/ void Remove_Last(REBSER *series) -/* -** Remove last value from a series. -** -***********************************************************************/ +// +// Remove_Series: C +// +// Remove a series of values (bytes, longs, reb-vals) from the +// series at the given index. +// +void Remove_Series(REBSER *s, REBCNT index, REBINT len) { - if (series->tail == 0) return; - series->tail--; - CLEAR(series->data + SERIES_WIDE(series) * series->tail, SERIES_WIDE(series)); + if (len <= 0) return; + + REBOOL is_dynamic = GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC); + REBCNT len_old = SER_LEN(s); + + REBCNT start = index * SER_WIDE(s); + + // Optimized case of head removal. For a dynamic series this may just + // add "bias" to the head...rather than move any bytes. + + if (is_dynamic && index == 0) { + if (cast(REBCNT, len) > len_old) + len = len_old; + + s->content.dynamic.len -= len; + if (s->content.dynamic.len == 0) { + // Reset bias to zero: + len = SER_BIAS(s); + SER_SET_BIAS(s, 0); + s->content.dynamic.rest += len; + s->content.dynamic.data -= SER_WIDE(s) * len; + TERM_SERIES(s); + } + else { + // Add bias to head: + u32 bias = SER_BIAS(s); + if (REB_U32_ADD_OF(bias, len, &bias)) + fail (Error_Overflow_Raw()); + + if (bias > 0xffff) { //bias is 16-bit, so a simple SER_ADD_BIAS could overflow it + REBYTE *data = s->content.dynamic.data; + + data += SER_WIDE(s) * len; + s->content.dynamic.data -= SER_WIDE(s) * SER_BIAS(s); + + s->content.dynamic.rest += SER_BIAS(s); + SER_SET_BIAS(s, 0); + + memmove( + s->content.dynamic.data, + data, + SER_LEN(s) * SER_WIDE(s) + ); + TERM_SERIES(s); + } + else { + SER_SET_BIAS(s, bias); + s->content.dynamic.rest -= len; + s->content.dynamic.data += SER_WIDE(s) * len; + if ((start = SER_BIAS(s)) != 0) { + // If more than half biased: + if (start >= MAX_SERIES_BIAS || start > SER_REST(s)) + Unbias_Series(s, TRUE); + } + } + } + return; + } + + if (index >= len_old) return; + + // Clip if past end and optimize the remove operation: + + if (len + index >= len_old) { + SET_SERIES_LEN(s, index); + TERM_SERIES(s); + return; + } + + // The terminator is not included in the length, because termination may + // be implicit (e.g. there may not be a full SER_WIDE() worth of data + // at the termination location). Use TERM_SERIES() instead. + // + REBCNT length = SER_LEN(s) * SER_WIDE(s); + SET_SERIES_LEN(s, len_old - cast(REBCNT, len)); + len *= SER_WIDE(s); + + REBYTE *data = SER_DATA_RAW(s) + start; + memmove(data, data + len, length - (start + len)); + TERM_SERIES(s); } -/*********************************************************************** -** -*/ void Reset_Bias(REBSER *series) -/* -** Reset series bias. -** -***********************************************************************/ +// +// Unbias_Series: C +// +// Reset series bias. +// +void Unbias_Series(REBSER *s, REBOOL keep) { - REBCNT len; - REBYTE *data = series->data; + REBCNT len = SER_BIAS(s); + if (len == 0) + return; + + REBYTE *data = s->content.dynamic.data; - len = SERIES_BIAS(series); - SERIES_SET_BIAS(series, 0); - SERIES_REST(series) += len; - series->data -= SERIES_WIDE(series) * len; + SER_SET_BIAS(s, 0); + s->content.dynamic.rest += len; + s->content.dynamic.data -= SER_WIDE(s) * len; - memmove(series->data, data, SERIES_USED(series)); + if (keep) { + memmove(s->content.dynamic.data, data, SER_LEN(s) * SER_WIDE(s)); + TERM_SERIES(s); + } } -/*********************************************************************** -** -*/ void Reset_Series(REBSER *series) -/* -** Reset series to empty. Reset bias, tail, and termination. -** The tail is reset to zero. -** -***********************************************************************/ +// +// Reset_Sequence: C +// +// Reset series to empty. Reset bias, tail, and termination. +// The tail is reset to zero. +// +void Reset_Sequence(REBSER *s) { - series->tail = 0; - if (SERIES_BIAS(series)) Reset_Bias(series); - CLEAR(series->data, SERIES_WIDE(series)); // re-terminate + assert(NOT_SER_FLAG(s, SERIES_FLAG_ARRAY)); + if (GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC)) { + Unbias_Series(s, FALSE); + s->content.dynamic.len = 0; + TERM_SEQUENCE(s); + } + else + TERM_SEQUENCE_LEN(s, 0); } -/*********************************************************************** -** -*/ void Clear_Series(REBSER *series) -/* -** Clear an entire series to zero. Resets bias and tail. -** The tail is reset to zero. -** -***********************************************************************/ +// +// Reset_Array: C +// +// Reset series to empty. Reset bias, tail, and termination. +// The tail is reset to zero. +// +void Reset_Array(REBARR *a) { - series->tail = 0; - if (SERIES_BIAS(series)) Reset_Bias(series); - CLEAR(series->data, SERIES_SPACE(series)); + if (GET_SER_INFO(a, SERIES_INFO_HAS_DYNAMIC)) + Unbias_Series(SER(a), FALSE); + TERM_ARRAY_LEN(a, 0); } -/*********************************************************************** -** -*/ void Resize_Series(REBSER *series, REBCNT size) -/* -** Reset series and expand it to required size. -** The tail is reset to zero. -** -***********************************************************************/ +// +// Clear_Series: C +// +// Clear an entire series to zero. Resets bias and tail. +// The tail is reset to zero. +// +void Clear_Series(REBSER *s) { - series->tail = 0; - if (SERIES_BIAS(series)) Reset_Bias(series); - EXPAND_SERIES_TAIL(series, size); - series->tail = 0; - CLEAR(series->data, SERIES_WIDE(series)); // re-terminate + assert(!Is_Series_Read_Only(s)); + + if (GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC)) { + Unbias_Series(s, FALSE); + CLEAR(s->content.dynamic.data, SER_REST(s) * SER_WIDE(s)); + } + else + CLEAR(cast(REBYTE*, &s->content), sizeof(s->content)); + + TERM_SERIES(s); } -/*********************************************************************** -** -*/ void Terminate_Series(REBSER *series) -/* -** Put terminator at tail of the series. -** -***********************************************************************/ +// +// Resize_Series: C +// +// Reset series and expand it to required size. +// The tail is reset to zero. +// +void Resize_Series(REBSER *s, REBCNT size) { - CLEAR(series->data + SERIES_WIDE(series) * series->tail, SERIES_WIDE(series)); + if (GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC)) { + s->content.dynamic.len = 0; + Unbias_Series(s, TRUE); + } + else + SET_SERIES_LEN(s, 0); + + EXPAND_SERIES_TAIL(s, size); + SET_SERIES_LEN(s, 0); + TERM_SERIES(s); } -/*********************************************************************** -** -*/ void Shrink_Series(REBSER *series, REBCNT units) -/* -** Shrink a series back to a given maximum size. All -** content is deleted and tail is reset. -** -** WARNING: This should only be used for strings or other -** series that cannot contain internally referenced values. -** -***********************************************************************/ +// +// Reset_Buffer: C +// +// Setup to reuse a shared buffer. Expand it if needed. +// +// NOTE: The length will be set to the supplied value, but the series will +// not be terminated. +// +REBYTE *Reset_Buffer(REBSER *buf, REBCNT len) { - if (SERIES_REST(series) <= units) return; - //DISABLE_GC; - Free_Series_Data(series, FALSE); - Make_Series_Data(series, units); - //ENABLE_GC; + if (buf == NULL) + panic ("buffer not yet allocated"); + + SET_SERIES_LEN(buf, 0); + Unbias_Series(buf, TRUE); + Expand_Series(buf, 0, len); // sets new tail + + return SER_DATA_RAW(buf); } -/*********************************************************************** -** -*/ REBYTE *Reset_Buffer(REBSER *buf, REBCNT len) -/* -** Setup to reuse a shared buffer. Expand it if needed. -** -** NOTE:The tail is set to the length position. -** -***********************************************************************/ +// +// Copy_Buffer: C +// +// Copy a shared buffer, starting at index. Set tail and termination. +// +REBSER *Copy_Buffer(REBSER *buf, REBCNT index, void *end) { - if (!buf) Crash(RP_NO_BUFFER); + assert(NOT_SER_FLAG(buf, SERIES_FLAG_ARRAY)); + + REBCNT len = BYTE_SIZE(buf) + ? cast(REBYTE*, end) - BIN_HEAD(buf) + : cast(REBUNI*, end) - UNI_HEAD(buf); + + if (index) len -= index; + + REBSER *copy = Make_Series(len + 1, SER_WIDE(buf)); + + memcpy( + SER_DATA_RAW(copy), + SER_DATA_RAW(buf) + index * SER_WIDE(buf), + SER_WIDE(buf) * len + ); + TERM_SEQUENCE_LEN(copy, len); + + return copy; +} - RESET_TAIL(buf); - if (SERIES_BIAS(buf)) Reset_Bias(buf); - Expand_Series(buf, 0, len); // sets new tail - return BIN_DATA(buf); +#if !defined(NDEBUG) + +// +// Assert_Series_Term_Core: C +// +void Assert_Series_Term_Core(REBSER *s) +{ + if (GET_SER_FLAG(s, SERIES_FLAG_ARRAY)) { + // + // END values aren't canonized to zero bytes, check IS_END explicitly + // + RELVAL *tail = ARR_TAIL(ARR(s)); + if (NOT_END(tail)) + panic (tail); + } + else { + // If they are terminated, then non-REBVAL-bearing series must have + // their terminal element as all 0 bytes (to use this check) + // + REBCNT len = SER_LEN(s); + REBCNT wide = SER_WIDE(s); + REBCNT n; + for (n = 0; n < wide; n++) { + if (0 != SER_DATA_RAW(s)[(len * wide) + n]) + panic (s); + } + } } -/*********************************************************************** -** -*/ REBSER *Copy_Buffer(REBSER *buf, void *end) -/* -** Copy a shared buffer. Set tail and termination. -** -***********************************************************************/ +// +// Assert_Series_Core: C +// +void Assert_Series_Core(REBSER *s) { - REBSER *ser; - REBCNT len; + if (IS_FREE_NODE(s)) + panic (s); - len = BYTE_SIZE(buf) ? ((REBYTE *)end) - BIN_HEAD(buf) - : ((REBUNI *)end) - UNI_HEAD(buf); + assert( + GET_SER_INFO(s, SERIES_INFO_0_IS_TRUE) // @ NODE_FLAG_NODE + && NOT_SER_INFO(s, SERIES_INFO_1_IS_FALSE) // @ NOT(NODE_FLAG_FREE) + && GET_SER_INFO(s, SERIES_INFO_4_IS_TRUE) // @ NODE_FLAG_END + && NOT_SER_INFO(s, SERIES_INFO_7_IS_FALSE) // @ NODE_FLAG_CELL + ); - ser = Make_Series(len+1, SERIES_WIDE(buf), FALSE); + assert(SER_LEN(s) < SER_REST(s)); + + Assert_Series_Term_Core(s); +} - memcpy(ser->data, buf->data, SERIES_WIDE(buf) * len); - ser->tail = len; - TERM_SERIES(ser); - return ser; +// +// Panic_Series_Debug: C +// +// The goal of this routine is to progressively reveal as much diagnostic +// information about a series as possible. Since the routine will ultimately +// crash anyway, it is okay if the diagnostics run code which might be +// risky in an unstable state...though it is ideal if it can run to the end +// so it can trigger Address Sanitizer or Valgrind's internal stack dump. +// +ATTRIBUTE_NO_RETURN void Panic_Series_Debug(REBSER *s) +{ + fflush(stdout); + fflush(stderr); + + if (s->header.bits & NODE_FLAG_MANAGED) + printf("managed"); + else + printf("unmanaged"); + printf(" series was likely "); + if (s->header.bits & NODE_FLAG_FREE) + printf("freed"); + else + printf("created"); + printf(" during evaluator tick: %lu\n", cast(unsigned long, s->do_count)); + + fflush(stdout); + + if (*s->guard == 1020) // should make valgrind or asan alert + panic ("series guard didn't trigger ASAN/valgrind trap"); + + OS_CRASH( + cb_cast("series guard didn't trigger ASAN/Valgrind trap\n"), + cb_cast("either not a REBSER, or you're not running ASAN/Valgrind\n") + ); + + while (TRUE) + NOOP; // just in case it didn't crash, don't return + + DEAD_END; } + +#endif diff --git a/src/core/m-stacks.c b/src/core/m-stacks.c new file mode 100644 index 0000000000..7b7acb5407 --- /dev/null +++ b/src/core/m-stacks.c @@ -0,0 +1,349 @@ +// +// File: %m-stack.c +// Summary: "data and function call stack implementation" +// Section: memory +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + +#include "sys-core.h" + + +// +// Startup_Stacks: C +// +void Startup_Stacks(REBCNT size) +{ + // We always keep one chunker around for the first chunk push, and prep + // one chunk so that the push and drop routines never worry about testing + // for the empty case. + + TG_Root_Chunker = cast( + struct Reb_Chunker*, + Alloc_Mem(BASE_CHUNKER_SIZE + CS_CHUNKER_PAYLOAD) + ); + +#if !defined(NDEBUG) + memset(TG_Root_Chunker, 0xBD, sizeof(struct Reb_Chunker)); +#endif + + TG_Root_Chunker->next = NULL; + TG_Root_Chunker->size = CS_CHUNKER_PAYLOAD; + TG_Top_Chunk = cast(struct Reb_Chunk*, &TG_Root_Chunker->payload); + TG_Top_Chunk->prev = NULL; + + // Zero values for initial chunk, also sets offset to 0 + // + Init_Endlike_Header(&TG_Top_Chunk->header, 0); + TG_Top_Chunk->offset = 0; + TG_Top_Chunk->size = BASE_CHUNK_SIZE; + + // Implicit termination trick, see notes on NODE_FLAG_END + // + Init_Endlike_Header( + &cast( + struct Reb_Chunk*, cast(REBYTE*, TG_Top_Chunk) + BASE_CHUNK_SIZE + )->header, + 0 + ); + assert(IS_END(&TG_Top_Chunk->values[0])); + + // Start the data stack out with just one element in it, and make it an + // unreadable blank in the debug build. This helps avoid accidental + // reads and is easy to notice when it is overwritten. It also means + // that indices into the data stack can be unsigned (no need for -1 to + // mean empty, because 0 can) + // + // DS_PUSH checks what you're pushing isn't void, as most arrays can't + // contain them. But DS_PUSH_MAYBE_VOID allows you to, in case you + // are building a context varlist or similar. + // + DS_Array = Make_Array_Core(1, ARRAY_FLAG_VOIDS_LEGAL); + SET_UNREADABLE_BLANK(ARR_HEAD(DS_Array)); + + // The END marker will signal DS_PUSH that it has run out of space, + // and it will perform the allocation at that time. + // + TERM_ARRAY_LEN(DS_Array, 1); + ASSERT_ARRAY(DS_Array); + + // Reuse the expansion logic that happens on a DS_PUSH to get the + // initial stack size. It requires you to be on an END to run. + // + DS_Index = 1; + DS_Movable_Base = KNOWN(ARR_HEAD(DS_Array)); // can't push RELVALs + Expand_Data_Stack_May_Fail(size); + + // Now drop the hypothetical thing pushed that triggered the expand. + // + DS_DROP; + + // Call stack (includes pending functions, parens...anything that sets + // up a `REBFRM` and calls Do_Core()) Singly linked. + // + TG_Frame_Stack = NULL; +} + + +// +// Shutdown_Stacks: C +// +void Shutdown_Stacks(void) +{ + assert(FS_TOP == NULL); + assert(DSP == 0); + assert(IS_UNREADABLE_IF_DEBUG(ARR_HEAD(DS_Array))); + + Free_Array(DS_Array); + + assert(TG_Top_Chunk == cast(struct Reb_Chunk*, &TG_Root_Chunker->payload)); + + // Because we always keep one chunker of headroom allocated, and the + // push/drop is not designed to manage the last chunk, we *might* have + // that next chunk of headroom still allocated. + // + if (TG_Root_Chunker->next) + Free_Mem(TG_Root_Chunker->next, TG_Root_Chunker->next->size + BASE_CHUNKER_SIZE); + + // OTOH we always have to free the root chunker. + // + Free_Mem(TG_Root_Chunker, TG_Root_Chunker->size + BASE_CHUNKER_SIZE); +} + + +// +// Expand_Data_Stack_May_Fail: C +// +// The data stack maintains an invariant that you may never push an END to it. +// So each push looks to see if it's pushing to a cell that contains an END +// and if so requests an expansion. +// +// WARNING: This will invalidate any extant pointers to REBVALs living in +// the stack. It is for this reason that stack access should be done by +// REBDSP "data stack pointers" and not by REBVAL* across *any* operation +// which could do a push or pop. (Currently stable w.r.t. pop but there may +// be compaction at some point.) +// +void Expand_Data_Stack_May_Fail(REBCNT amount) +{ + REBCNT len_old = ARR_LEN(DS_Array); + + // The current requests for expansion should only happen when the stack + // is at its end. Sanity check that. + // +#if !defined(NDEBUG) + // + // Note: DS_TOP or DS_AT(DSP) would assert on END, calculate directly + // + REBVAL *end_top = DS_Movable_Base + DSP; + assert(IS_END(end_top)); + assert(end_top == KNOWN(ARR_TAIL(DS_Array))); // can't push RELVALs + assert(end_top - KNOWN(ARR_HEAD(DS_Array)) == cast(int, len_old)); +#endif + + // If adding in the requested amount would overflow the stack limit, then + // give a data stack overflow error. + // + if (SER_REST(SER(DS_Array)) + amount >= STACK_LIMIT) + Trap_Stack_Overflow(); + + Extend_Series(SER(DS_Array), amount); + + // Update the global pointer representing the base of the stack that + // likely was moved by the above allocation. (It's not necessarily a + // huge win to cache it, but it turns data stack access from a double + // dereference into a single dereference in the common case, and it was + // how R3-Alpha did it). + // + DS_Movable_Base = KNOWN(ARR_HEAD(DS_Array)); // must do before using DS_TOP + + // We fill in the data stack with "GC safe trash" (which is void in the + // release build, but will raise an alarm if VAL_TYPE() called on it in + // the debug build). In order to serve as a marker for the stack slot + // being available, it merely must not be IS_END()... + + // again, DS_TOP or DS_AT(DSP) would assert on END, calculate directly + // + REBVAL *value = DS_Movable_Base + DSP; + + REBCNT len_new = len_old + amount; + REBCNT n; + for (n = len_old; n < len_new; ++n) { + SET_UNREADABLE_BLANK(value); + ++value; + } + + // Update the end marker to serve as the indicator for when the next + // stack push would need to expand. + // + TERM_ARRAY_LEN(DS_Array, len_new); + assert(value == ARR_TAIL(DS_Array)); + + ASSERT_ARRAY(DS_Array); +} + + +// +// Pop_Stack_Values: C +// +// Pops computed values from the stack to make a new ARRAY. +// +REBARR *Pop_Stack_Values_Core(REBDSP dsp_start, REBUPT flags) +{ + REBARR *array = Copy_Values_Len_Shallow_Core( + DS_AT(dsp_start + 1), // start somewhere in the stack, end at DS_TOP + SPECIFIED, // data stack should be fully specified--no relative values + DSP - dsp_start, // len + flags + ); + + DS_DROP_TO(dsp_start); + return array; +} + + +// +// Pop_Stack_Values_Reversed: C +// +// Pops computed values from the stack to make a new ARRAY, but reverses the +// data so the last pushed item is the first in the array. +// +REBARR *Pop_Stack_Values_Reversed(REBDSP dsp_start) +{ + REBARR *array = Copy_Values_Len_Reversed_Shallow( + DS_TOP, // start at DS_TOP, work backwards somewhere in the stack + SPECIFIED, // data stack should be fully specified--no relative values + DSP - dsp_start // len + ); + + DS_DROP_TO(dsp_start); + return array; +} + + +// +// Pop_Stack_Values_Into: C +// +// Pops computed values from the stack into an existing ANY-ARRAY. The +// index of that array will be updated to the insertion tail (/INTO protocol) +// +void Pop_Stack_Values_Into(REBVAL *into, REBDSP dsp_start) { + REBCNT len = DSP - dsp_start; + REBVAL *values = KNOWN(ARR_AT(DS_Array, dsp_start + 1)); + + assert(ANY_ARRAY(into)); + FAIL_IF_READ_ONLY_ARRAY(VAL_ARRAY(into)); + + VAL_INDEX(into) = Insert_Series( + SER(VAL_ARRAY(into)), + VAL_INDEX(into), + cast(REBYTE*, values), // stack only holds fully specified REBVALs + len // multiplied by width (sizeof(REBVAL)) in Insert_Series + ); + + DS_DROP_TO(dsp_start); +} + + +// +// Reify_Frame_Context_Maybe_Fulfilling: C +// +// A Reb_Frame does not allocate a REBSER for its frame to be used in the +// context by default. But one can be allocated on demand, even for a NATIVE! +// in order to have a binding location for the debugger (for instance). +// If it becomes necessary to create words bound into the frame that is +// another case where the frame needs to be brought into existence. +// +// If there's already a frame this will return it, otherwise create it. +// +void Reify_Frame_Context_Maybe_Fulfilling(REBFRM *f) { + assert(Is_Any_Function_Frame(f)); // varargs reifies while still pending + + if (f->varlist != NULL) { + // + // We have our function call's args in an array, but it is not yet + // a context. !!! Really this cannot reify if we're in arg gathering + // mode, calling MANAGE_ARRAY is illegal -- need test for that !!! + // + assert(NOT_SER_FLAG(f->varlist, ARRAY_FLAG_VARLIST)); + SET_SER_FLAG(f->varlist, ARRAY_FLAG_VARLIST); + + assert(IS_TRASH_DEBUG(ARR_AT(f->varlist, 0))); // we fill this in + assert(GET_SER_INFO(f->varlist, SERIES_INFO_HAS_DYNAMIC)); + } + else { + f->varlist = Alloc_Singular_Array_Core(ARRAY_FLAG_VARLIST); + SET_SER_INFO(f->varlist, CONTEXT_INFO_STACK); + } + + REBCTX *c = CTX(f->varlist); + + // We do not Manage_Context, because we are reusing a word series here + // that has already been managed. The arglist array was managed when + // created and kept alive by Mark_Call_Frames + // + INIT_CTX_KEYLIST_SHARED(c, FUNC_PARAMLIST(FRM_UNDERLYING(f))); + ASSERT_ARRAY_MANAGED(CTX_KEYLIST(c)); + + // When in ET_FUNCTION or ET_LOOKBACK, the arglist will be marked safe from + // GC. It is managed because the pointer makes its way into bindings that + // ANY-WORD! values may have, and they need to not crash. + // + // !!! Note that theoretically pending mode arrays do not need GC + // access as no running code could get them, but the debugger is + // able to access this information. This is under review for how it + // might be stopped. + // + REBVAL *rootvar = SINK(ARR_HEAD(f->varlist)); + VAL_RESET_HEADER(rootvar, REB_FRAME); + rootvar->payload.any_context.varlist = f->varlist; + rootvar->payload.any_context.phase = f->phase; + rootvar->extra.binding = f->binding; + + SER(f->varlist)->misc.f = f; + + // A reification of a frame for native code should not allow changing + // the values out from under it, because that could cause it to crash + // the interpreter. (Generally speaking, modification should only be + // possible in the debugger anyway.) For now, mark the array as + // running...which should not stop FRM_ARG from working in the native + // itself, but should stop modifications from user code. + // + if (f->flags.bits & DO_FLAG_NATIVE_HOLD) + SET_SER_INFO(f->varlist, SERIES_INFO_RUNNING); + + MANAGE_ARRAY(f->varlist); + +#if !defined(NDEBUG) + // + // Variadics will reify the varlist even when the data is not quite + // ready; these need special handling in the GC code for marking frames. + // By the time the function actually runs, the data should be good. + // + if (NOT(Is_Function_Frame_Fulfilling(f))) + ASSERT_CONTEXT(c); + assert(NOT(CTX_VARS_UNAVAILABLE(c))); +#endif +} diff --git a/src/core/n-control.c b/src/core/n-control.c old mode 100644 new mode 100755 index 61e3fe0b5d..1c60886ce3 --- a/src/core/n-control.c +++ b/src/core/n-control.c @@ -1,774 +1,790 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: n-control.c -** Summary: native functions for control flow -** Section: natives -** Author: Carl Sassenrath -** Notes: -** Warning: Do not cache pointer to stack ARGS (stack may expand). -** -***********************************************************************/ +// +// File: %n-control.c +// Summary: "native functions for control flow" +// Section: natives +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Control constructs in Ren-C differ from R3-Alpha in some ways: +// +// * If they do not run their body, they evaluate to void ("unset!") and not +// blank ("none!"). Otherwise the last result of the body evaluation, as +// in R3-Alpha and Rebol2...but this is forced to blank if it was void, +// so that THEN and ELSE can distinguish whether a condition ran. +// +// * It is possible to ask the return result to not be "blankified", but +// return the true value, with the /OPT refinement. This is specialized +// as functions ending in *. (IF*, EITHER*, CASE*, SWITCH*...) +// +// * Other specializations exist returning a logic of whether the body ever +// ran by using the /? refinement. So CASE? does not return the branch +// values, just true or false based on whether a branch ran. This is +// based on testing the result for void. +// +// * Zero-arity function values used as branches will be executed. Future +// plans may allow for single-arity functions to be passed the condition, +// see Run_Branch_Throws() for notes. +// +// * The /ONLY option suppresses execution of either FUNCTION! branches or +// BLOCK! branches, instead evaluating to the raw function or block value. +// #include "sys-core.h" -// Local flags used for Protect functions below: -enum { - PROT_SET, - PROT_DEEP, - PROT_HIDE, - PROT_WORD, -}; +// +// if: native [ +// +// {If TRUE? condition, return evaluation of branch value.} +// +// return: [ any-value!] +// {void on FALSE?, branch result if TRUE? or BLANK! if void} +// condition [any-value!] +// branch [ any-value!] +// {Evaluated if block or function and not /ONLY} +// /only +// "Return block and function branches instead of evaluating them" +// /opt +// "If branch runs and returns void, do not convert it to BLANK!" +// ] +// +REBNATIVE(if) +{ + INCLUDE_PARAMS_OF_IF; + + // Test is "safe", e.g. no literal blocks like `if [x] [...]` + // + if (IS_CONDITIONAL_TRUE_SAFE(ARG(condition))) { + if (Run_Branch_Throws(D_OUT, ARG(branch), REF(only))) + return R_OUT_IS_THROWN; + + if (REF(opt)) + return R_OUT; + return R_OUT_BLANK_IF_VOID; + } + + return R_VOID; +} + + +// +// unless: native [ +// +// {If FALSE? condition, return evaluation of branch value.} +// +// return: [ any-value!] +// {Void on FALSE?, branch result if TRUE? condition (may be void)} +// condition [any-value!] +// branch [ any-value!] +// {Evaluated if block or function and not /ONLY} +// /only +// "Return block and function branches instead of evaluating them" +// /opt +// "If branch runs and returns void, do not convert it to BLANK!" +// ] +// +REBNATIVE(unless) +{ + INCLUDE_PARAMS_OF_UNLESS; + + // Test is "safe", e.g. no literal blocks like `unless [x] [...]` + // + if (NOT(IS_CONDITIONAL_TRUE_SAFE(ARG(condition)))) { + if (Run_Branch_Throws(D_OUT, ARG(branch), REF(only))) + return R_OUT_IS_THROWN; + + if (REF(opt)) + return R_OUT; + return R_OUT_BLANK_IF_VOID; + } + + return R_VOID; +} + + +// +// either: native [ +// +// {If TRUE condition?, evaluate first branch, else evaluate second branch.} +// +// return: [ any-value!] +// condition [any-value!] +// true-branch [ any-value!] +// false-branch [ any-value!] +// /only +// "Return block and function branches instead of evaluating them" +// /opt +// "Do not convert void branch results to BLANK!" +// ] +// +REBNATIVE(either) +{ + INCLUDE_PARAMS_OF_EITHER; + + // Test is "safe", e.g. no literal blocks like `either [x] [...] [...]` + // + if (IS_CONDITIONAL_TRUE_SAFE(ARG(condition))) { + if (Run_Branch_Throws(D_OUT, ARG(true_branch), REF(only))) + return R_OUT_IS_THROWN; + } + else { + if (Run_Branch_Throws(D_OUT, ARG(false_branch), REF(only))) + return R_OUT_IS_THROWN; + } + + if (REF(opt)) + return R_OUT; + return R_OUT_BLANK_IF_VOID; +} + + +// +// all: native [ +// +// {Short-circuiting variant of AND, using a block of expressions as input.} +// +// return: [ any-value!] +// {Product of last evaluation if all TRUE?, else a BLANK! value.} +// block [block!] +// "Block of expressions. Void evaluations are ignored." +// ] +// +REBNATIVE(all) +{ + INCLUDE_PARAMS_OF_ALL; + + assert(IS_END(D_OUT)); // guaranteed by the evaluator + + DECLARE_FRAME (f); + Push_Frame(f, ARG(block)); + + while (NOT_END(f->value)) { + if (Do_Next_In_Frame_Throws(D_CELL, f)) { + Drop_Frame(f); + Move_Value(D_OUT, D_CELL); + return R_OUT_IS_THROWN; + } + + if (IS_VOID(D_CELL)) // voids do not "vote" true or false + continue; + + if (IS_CONDITIONAL_FALSE(D_CELL)) { // a failed ALL returns BLANK! + Drop_Frame(f); + return R_BLANK; + } + + Move_Value(D_OUT, D_CELL); // preserve (not overwritten by later voids) + } + + Drop_Frame(f); - -/*********************************************************************** -** -*/ void Protected(REBVAL *word) -/* -** Throw an error if word is protected. -** -***********************************************************************/ -{ - REBSER *frm; - REBINT index = VAL_WORD_INDEX(word); - - if (index > 0) { - frm = VAL_WORD_FRAME(word); - if (VAL_PROTECTED(FRM_WORDS(frm)+index)) - Trap1(RE_LOCKED_WORD, word); - } - else if (index == 0) Trap0(RE_SELF_PROTECTED); -} - - -/*********************************************************************** -** -*/ static void Protect_Word(REBVAL *value, REBCNT flags) -/* -***********************************************************************/ -{ - if (GET_FLAG(flags, PROT_WORD)) { - if (GET_FLAG(flags, PROT_SET)) VAL_SET_OPT(value, OPTS_LOCK); - else VAL_CLR_OPT(value, OPTS_LOCK); - } - - if (GET_FLAG(flags, PROT_HIDE)) { - if GET_FLAG(flags, PROT_SET) VAL_SET_OPT(value, OPTS_HIDE); - else VAL_CLR_OPT(value, OPTS_HIDE); - } -} - - -/*********************************************************************** -** -*/ static void Protect_Value(REBVAL *value, REBCNT flags) -/* -** Anything that calls this must call Unmark() when done. -** -***********************************************************************/ -{ - if (ANY_SERIES(value) || IS_MAP(value)) - Protect_Series(value, flags); - else if (IS_OBJECT(value) || IS_MODULE(value)) - Protect_Object(value, flags); -} - - -/*********************************************************************** -** -*/ void Protect_Series(REBVAL *val, REBCNT flags) -/* -** Anything that calls this must call Unmark() when done. -** -***********************************************************************/ -{ - REBSER *series = VAL_SERIES(val); - - if (IS_MARK_SERIES(series)) return; // avoid loop - - if (GET_FLAG(flags, PROT_SET)) - PROTECT_SERIES(series); - else - UNPROTECT_SERIES(series); - - if (!ANY_BLOCK(val) || !GET_FLAG(flags, PROT_DEEP)) return; - - MARK_SERIES(series); // recursion protection - - for (val = VAL_BLK_DATA(val); NOT_END(val); val++) { - Protect_Value(val, flags); - } -} - - -/*********************************************************************** -** -*/ void Protect_Object(REBVAL *value, REBCNT flags) -/* -** Anything that calls this must call Unmark() when done. -** -***********************************************************************/ -{ - REBSER *series = VAL_OBJ_FRAME(value); - - if (IS_MARK_SERIES(series)) return; // avoid loop - - if (GET_FLAG(flags, PROT_SET)) PROTECT_SERIES(series); - else UNPROTECT_SERIES(series); - - for (value = FRM_WORDS(series)+1; NOT_END(value); value++) { - Protect_Word(value, flags); - } - - if (!GET_FLAG(flags, PROT_DEEP)) return; - - MARK_SERIES(series); // recursion protection - - for (value = FRM_VALUES(series)+1; NOT_END(value); value++) { - Protect_Value(value, flags); - } -} - - -/*********************************************************************** -** -*/ static void Protect_Word_Value(REBVAL *word, REBCNT flags) -/* -***********************************************************************/ -{ - REBVAL *wrd; - REBVAL *val; - - if (ANY_WORD(word) && HAS_FRAME(word) && VAL_WORD_INDEX(word) > 0) { - wrd = FRM_WORDS(VAL_WORD_FRAME(word))+VAL_WORD_INDEX(word); - Protect_Word(wrd, flags); - if (GET_FLAG(flags, PROT_DEEP)) { - val = Get_Var(word); - Protect_Value(val, flags); - Unmark(val); - } - } - else if (ANY_PATH(word)) { - REBCNT index; - REBSER *obj; - if (NZ(obj = Resolve_Path(word, &index))) { - wrd = FRM_WORD(obj, index); - Protect_Word(wrd, flags); - if (GET_FLAG(flags, PROT_DEEP)) { - Protect_Value(val = FRM_VALUE(obj, index), flags); - Unmark(val); - } - } - } + // If IS_END(out), no successes or failures found (all opt-outs) + // + return R_OUT_VOID_IF_UNWRITTEN; } -/*********************************************************************** -** -*/ static int Protect(REBVAL *ds, REBCNT flags) -/* -** 1: value -** 2: /deep - recursive -** 3: /words - list of words -** 4: /values - list of values -** 5: /hide - hide variables -** -***********************************************************************/ -{ - REBVAL *val = D_ARG(1); - - // flags has PROT_SET bit (set or not) - - Check_Security(SYM_PROTECT, POL_WRITE, val); - - if (D_REF(2)) SET_FLAG(flags, PROT_DEEP); - //if (D_REF(3)) SET_FLAG(flags, PROT_WORD); - - if (D_REF(5)) SET_FLAG(flags, PROT_HIDE); - else SET_FLAG(flags, PROT_WORD); // there is no unhide - - if (IS_WORD(val) || IS_PATH(val)) { - Protect_Word_Value(val, flags); // will unmark if deep - return R_ARG1; - } - - if (IS_BLOCK(val)) { - if (D_REF(3)) { // /words - for (val = VAL_BLK_DATA(val); NOT_END(val); val++) - Protect_Word_Value(val, flags); // will unmark if deep - return R_ARG1; - } - if (D_REF(4)) { // /values - REBVAL *val2; - for (val = VAL_BLK_DATA(val); NOT_END(val); val++) { - Protect_Value(val2 = Get_Any_Var(val), flags); - if (GET_FLAG(flags, PROT_DEEP)) Unmark(val2); - } - return R_ARG1; - } - } - - if (GET_FLAG(flags, PROT_HIDE)) Trap0(RE_BAD_REFINES); - - Protect_Value(val, flags); - - if (GET_FLAG(flags, PROT_DEEP)) Unmark(val); - - return R_ARG1; -} +// +// any: native [ +// +// {Short-circuiting version of OR, using a block of expressions as input.} +// +// return: [ any-value!] +// {The first TRUE? evaluative result, or BLANK! value if all FALSE?} +// block [block!] +// "Block of expressions. Void evaluations are ignored." +// ] +// +REBNATIVE(any) +{ + INCLUDE_PARAMS_OF_ANY; + + DECLARE_FRAME (f); + Push_Frame(f, ARG(block)); + + REBOOL voted = FALSE; + + while (NOT_END(f->value)) { + if (Do_Next_In_Frame_Throws(D_OUT, f)) { + Drop_Frame(f); + return R_OUT_IS_THROWN; + } + + if (IS_VOID(D_OUT)) // voids do not "vote" true or false + continue; + + if (IS_CONDITIONAL_TRUE(D_OUT)) { // successful ANY returns the value + Drop_Frame(f); + return R_OUT; + } + voted = TRUE; // signal at least one non-void result was seen + } -/*********************************************************************** -** -*/ REBNATIVE(also) -/* -***********************************************************************/ -{ - return R_ARG1; -} + Drop_Frame(f); + if (voted) + return R_BLANK; -/*********************************************************************** -** -*/ REBNATIVE(all) -/* -***********************************************************************/ -{ - REBSER *block = VAL_SERIES(D_ARG(1)); - REBCNT index = VAL_INDEX(D_ARG(1)); - - ds = 0; - while (index < SERIES_TAIL(block)) { - index = Do_Next(block, index, 0); // stack volatile - ds = DS_POP; // volatile stack reference - if (IS_FALSE(ds)) return R_NONE; - if (THROWN(ds)) break; - } - if (ds == 0) return R_TRUE; - return R_TOS1; -} - - -/*********************************************************************** -** -*/ REBNATIVE(any) -/* -***********************************************************************/ -{ - REBSER *block = VAL_SERIES(D_ARG(1)); - REBCNT index = VAL_INDEX(D_ARG(1)); - - while (index < SERIES_TAIL(block)) { - index = Do_Next(block, index, 0); // stack volatile - ds = DS_POP; // volatile stack reference - if (!IS_FALSE(ds) && !IS_UNSET(ds)) return R_TOS1; - } - return R_NONE; -} - - -/*********************************************************************** -** -*/ REBNATIVE(apply) -/* -***********************************************************************/ -{ - Apply_Block(D_ARG(1), D_ARG(2), !D_REF(3)); // stack volatile - return R_TOS; -} - - -/*********************************************************************** -** -*/ REBNATIVE(attempt) -/* -***********************************************************************/ -{ - Try_Block(VAL_SERIES(D_ARG(1)), VAL_INDEX(D_ARG(1))); - if (IS_ERROR(DS_NEXT) && !IS_THROW(DS_NEXT)) return R_NONE; - return R_TOS1; + return R_VOID; // all opt-outs } -/*********************************************************************** -** -*/ REBNATIVE(break) -/* -***********************************************************************/ -{ - REBVAL *value = 0; - - if (D_REF(1)) value = D_ARG(2); // /return - SET_THROW(ds, RE_BREAK, value); - return R_RET; -} - - -/*********************************************************************** -** -*/ REBNATIVE(case) -/* -***********************************************************************/ -{ - REBSER *block = VAL_SERIES(D_ARG(1)); - REBCNT index = VAL_INDEX(D_ARG(1)); - REBFLG all_flag = D_REF(2); - - while (index < SERIES_TAIL(block)) { - //DSP = top; // reset stack -- not needed? - index = Do_Next(block, index, 0); - ds = DS_POP; // volatile stack reference - if (IS_FALSE(ds)) index++; - else { - if (IS_UNSET(ds)) Trap0(RE_NO_RETURN); - if (THROWN(ds)) return R_TOS1; - if (index >= SERIES_TAIL(block)) return R_TRUE; - index = Do_Next(block, index, 0); - ds = DS_POP; // volatile stack reference - if (IS_BLOCK(ds)) { - ds = DO_BLK(ds); - if (IS_UNSET(ds) && !all_flag) return R_TRUE; - } - if (THROWN(ds) || !all_flag || index >= SERIES_TAIL(block)) - return R_TOS1; - } - } - return R_NONE; -} - - -/*********************************************************************** -** -*/ REBNATIVE(catch) -/* -***********************************************************************/ -{ - REBVAL *val; - REBVAL *ret; - REBCNT sym; - - if (D_REF(4)) { //QUIT - if (Try_Block_Halt(VAL_SERIES(D_ARG(1)), VAL_INDEX(D_ARG(1)))) { - // We are here because of a QUIT/HALT condition. - ret = DS_NEXT; - if (VAL_ERR_NUM(ret) == RE_QUIT) - ret = VAL_ERR_VALUE(ret); - else if (VAL_ERR_NUM(ret) == RE_HALT) - Halt_Code(RE_HALT, 0); - else - Crash(RP_NO_CATCH); - *DS_RETURN = *ret; - return R_RET; - } - return R_TOS1; - } - - // Evaluate the block: - ret = DO_BLK(D_ARG(1)); - - // If it is a throw, process it: - if (IS_ERROR(ret) && VAL_ERR_NUM(ret) == RE_THROW) { - - // If a named throw, then check it: - if (D_REF(2)) { // /name - - sym = VAL_ERR_SYM(ret); - val = D_ARG(3); // name symbol - - // If name is the same word: - if (IS_WORD(val) && sym == VAL_WORD_CANON(val)) goto got_err; - - // If it is a block of words: - else if (IS_BLOCK(val)) { - for (val = VAL_BLK_DATA(val); NOT_END(val); val++) { - if (IS_WORD(val) && sym == VAL_WORD_CANON(val)) goto got_err; - } - } - } else { -got_err: - *ds = *(VAL_ERR_VALUE(ret)); - return R_RET; - } - } - - return R_TOS1; -} - - -/*********************************************************************** -** -*/ REBNATIVE(throw) -/* -***********************************************************************/ -{ - SET_THROW(ds, RE_THROW, D_ARG(1)); - if (D_REF(2)) // /name - VAL_ERR_SYM(ds) = VAL_WORD_SYM(D_ARG(3)); - return R_RET; -} - +// +// none: native [ +// +// {Short circuiting version of NOR, using a block of expressions as input.} +// +// return: [ bar! blank!] +// {TRUE if all expressions are FALSE?, or BLANK if any are TRUE?} +// block [block!] +// "Block of expressions. Void evaluations are ignored." +// ] +// +REBNATIVE(none) +// +// !!! In order to reduce confusion and accidents in the near term, the +// %mezz-legacy.r renames this to NONE-OF and makes NONE report an error. +{ + INCLUDE_PARAMS_OF_NONE; + + DECLARE_FRAME (f); + Push_Frame(f, ARG(block)); -#ifdef not_used -/*********************************************************************** -** -*/ REBNATIVE(cause) -/* -***********************************************************************/ -{ - Throw_Error(VAL_ERR_OBJECT(D_ARG(1))); - DEAD_END; -} -#endif - - -/*********************************************************************** -** -*/ REBNATIVE(comment) -/* -***********************************************************************/ -{ - return R_UNSET; -} - - -/*********************************************************************** -** -*/ REBNATIVE(compose) -/* -** {Evaluates a block of expressions, only evaluating parens, and returns a block.} -** value "Block to compose" -** /deep "Compose nested blocks" -** /only "Inserts a block value as a block" -** -***********************************************************************/ -{ - REBVAL *value = D_ARG(1); - - if (!IS_BLOCK(value)) return R_ARG1; - Compose_Block(value, D_REF(2), D_REF(3), D_REF(4) ? D_ARG(5) : 0); - return R_TOS; -} - - -/*********************************************************************** -** -*/ REBNATIVE(continue) -/* -***********************************************************************/ -{ - SET_THROW(ds, RE_CONTINUE, NONE_VALUE); - return R_RET; -} - -#ifdef removed -/*********************************************************************** -** -*/ REBNATIVE(disarm) -/* -***********************************************************************/ -{ - return R_ARG1; -/* - REBVAL *value = D_ARG(1); - - if (IS_ERROR(value)) VAL_SET(value, REB_OBJECT); - *ds = *value; - return R_RET; -*/ -} -#endif - -/*********************************************************************** -** -*/ REBNATIVE(do) -/* -***********************************************************************/ -{ - REBVAL *value = D_ARG(1); - - switch (VAL_TYPE(value)) { - - case REB_BLOCK: - case REB_PAREN: - if (D_REF(4)) { // next - VAL_INDEX(value) = Do_Next(VAL_SERIES(value), VAL_INDEX(value), 0); - if (VAL_INDEX(value) == END_FLAG) { - VAL_INDEX(value) = VAL_TAIL(value); - Set_Var(D_ARG(5), value); - return R_UNSET; - } - Set_Var(D_ARG(5), value); // "continuation" of block - return R_TOS; - } - else DO_BLK(value); - return R_TOS1; - - case REB_NATIVE: - case REB_ACTION: - case REB_COMMAND: - case REB_REBCODE: - case REB_OP: - case REB_CLOSURE: - case REB_FUNCTION: - VAL_SET_OPT(value, OPTS_REVAL); - return R_ARG1; - -// case REB_PATH: ? is it used? - - case REB_WORD: - case REB_GET_WORD: - *D_RET = *Get_Var(value); - return R_RET; - - case REB_LIT_WORD: - *D_RET = *value; - SET_TYPE(D_RET, REB_WORD); - return R_RET; - - case REB_ERROR: - if (IS_THROW(value)) return R_ARG1; - Throw_Error(VAL_ERR_OBJECT(value)); - - case REB_BINARY: - case REB_STRING: - case REB_URL: - case REB_FILE: - // DO native and sys/do* must use same arg list: - Do_Sys_Func(SYS_CTX_DO_P, value, D_ARG(2), D_ARG(3), D_ARG(4), D_ARG(5), 0); - return R_TOS1; - - case REB_TASK: - Do_Task(value); - return R_ARG1; - - case REB_SET_WORD: - Trap_Arg(value); - - default: - return R_ARG1; - } -} - - -#ifdef removed_b1505 -/*********************************************************************** -** -*/ REBNATIVE(eval) -/* -***********************************************************************/ -{ - REBVAL *value = D_ARG(1); - - if (IS_BLOCK(value)) {DO_BLK(value); return R_TOS1;} - if (IS_TASK(value)) Do_Task(value); - return R_ARG1; -} -#endif - -/*********************************************************************** -** -*/ REBNATIVE(either) -/* -***********************************************************************/ -{ - REBCNT argnum = IS_FALSE(D_ARG(1)) ? 3 : 2; - - if (IS_BLOCK(D_ARG(argnum)) && !D_REF(4) /* not using /ONLY */) { - DO_BLK(D_ARG(argnum)); - return R_TOS1; - } else { - return argnum == 2 ? R_ARG2 : R_ARG3; - } -} - - -/*********************************************************************** -** -*/ REBNATIVE(exit) -/* -***********************************************************************/ -{ - SET_THROW(ds, RE_RETURN, 0); - return R_RET; -} - - -/*********************************************************************** -** -*/ REBNATIVE(if) -/* -***********************************************************************/ -{ - if (IS_FALSE(D_ARG(1))) return R_NONE; - if (IS_BLOCK(D_ARG(2)) && !D_REF(3) /* not using /ONLY */) { - DO_BLK(D_ARG(2)); - return R_TOS1; - } else { - return R_ARG2; - } -} - - -/*********************************************************************** -** -*/ REBNATIVE(protect) -/* -***********************************************************************/ -{ - return Protect(ds, 1); // PROT_SET -} - - -/*********************************************************************** -** -*/ REBNATIVE(unprotect) -/* -***********************************************************************/ -{ - SET_NONE(D_ARG(5)); // necessary, bogus, but no harm to stack - return Protect(ds, 0); -} - - -/*********************************************************************** -** -*/ REBNATIVE(reduce) -/* -***********************************************************************/ -{ - if (IS_BLOCK(D_ARG(1))) { - REBSER *ser = VAL_SERIES(D_ARG(1)); - REBCNT index = VAL_INDEX(D_ARG(1)); - REBVAL *val = D_REF(5) ? D_ARG(6) : 0; - - if (D_REF(2)) - Reduce_Block_No_Set(ser, index, val); - else if (D_REF(3)) - Reduce_Only(ser, index, D_ARG(4), val); - else - Reduce_Block(ser, index, val); - return R_TOS; - } - - return R_ARG1; -} - - -/*********************************************************************** -** -*/ REBNATIVE(return) -/* -** Returns a value from the current function. The error value -** is built in the RETURN slot, with the arg being kept in -** the ARG1 slot on the stack. As long as DSP is greater, both -** values are safe from GC. -** -***********************************************************************/ -{ - REBVAL *arg = D_ARG(1); - - SET_THROW(ds, RE_RETURN, arg); - return R_RET; -} - - -/*********************************************************************** -** -*/ REBNATIVE(switch) -/* -** value -** cases [block!] -** /default -** case -** /all {Check all cases} -** -***********************************************************************/ -{ - REBVAL *blk = VAL_BLK_DATA(D_ARG(2)); - REBVAL *result; - REBOOL all = D_REF(5); - REBOOL found = FALSE; - - // Find value in case block... - for (; NOT_END(blk); blk++) { - if (!IS_BLOCK(blk) && 0 == Cmp_Value(DS_ARG(1), blk, FALSE)) { // avoid stack move - // Skip forward to block... - for (; !IS_BLOCK(blk) && NOT_END(blk); blk++); - if (IS_END(blk)) break; - found = TRUE; - // Evaluate the case block - result = DO_BLK(blk); - if (!all) return R_TOS1; - if (THROWN(result) && Check_Error(result) >= 0) break; - } - } - - if (!found && IS_BLOCK(result = D_ARG(4))) { - DO_BLK(result); - return R_TOS1; - } - - return R_NONE; -} - - -/*********************************************************************** -** -*/ REBNATIVE(try) -/* -***********************************************************************/ -{ - REBVAL value = *D_ARG(3); // TRY exception will trim the stack - REBFLG except = D_REF(2); - - if (Try_Block(VAL_SERIES(D_ARG(1)), VAL_INDEX(D_ARG(1)))) { - if (except) { - if (IS_BLOCK(&value)) { - DO_BLK(&value); - } - else { // do func[error] arg - REBVAL arg = *DS_NEXT; // will get overwritten - Apply_Func(0, &value, &arg, 0); - } - } - } - - return R_TOS1; -} - - -/*********************************************************************** -** -*/ REBNATIVE(unless) -/* -***********************************************************************/ -{ - if (IS_FALSE(D_ARG(1))) { - if (IS_BLOCK(D_ARG(2)) && !D_REF(3) /* not using /ONLY */) { - DO_BLK(D_ARG(2)); - return R_TOS1; - } else { - return R_ARG2; - } - } - return R_NONE; + REBOOL voted = FALSE; + + while (NOT_END(f->value)) { + if (Do_Next_In_Frame_Throws(D_OUT, f)) { + Drop_Frame(f); + return R_OUT_IS_THROWN; + } + + if (IS_VOID(D_OUT)) // voids do not "vote" true or false + continue; + + if (IS_CONDITIONAL_TRUE(D_OUT)) { // any true results mean failure + Drop_Frame(f); + return R_BLANK; + } + + voted = TRUE; // signal that at least one non-void result was seen + } + + Drop_Frame(f); + + if (voted) + return R_BAR; + + return R_VOID; // all opt-outs +} + + +// +// case: native [ +// +// {Evaluates each condition, and when true, evaluates what follows it.} +// +// return: [ any-value!] +// {Last matched case evaluation, or void if no cases matched} +// cases [block!] +// "Block of cases (conditions followed by branches)" +// /all +// {Evaluate all cases (do not stop at first TRUE? case)} +// /only +// "Return block and function branches instead of evaluating them" +// /opt +// "If branch runs and returns void, do not convert it to BLANK!" +// ] +// +REBNATIVE(case) +{ + INCLUDE_PARAMS_OF_CASE; // ? is renamed as "q" + + DECLARE_FRAME (f); + Push_Frame(f, ARG(cases)); + + // With the block argument pushed in the enumerator, that frame slot is + // available for scratch space in the rest of the routine. + + while (NOT_END(f->value)) { + if (IS_BAR(f->value)) { // interstitial BAR! legal, `case [1 2 | 3 4]` + Fetch_Next_In_Frame(f); + continue; + } + + // Perform a DO/NEXT's worth of evaluation on a "condition" to test + + if (Do_Next_In_Frame_Throws(D_CELL, f)) { + Move_Value(D_OUT, D_CELL); + goto return_thrown; + } + + if (IS_VOID(D_CELL)) // no void conditions allowed (as with IF) + fail (Error_No_Return_Raw()); + + if (IS_END(f->value)) // require conditions and branches in pairs + fail (Error_Past_End_Raw()); + + if (IS_BAR(f->value)) // BAR! out of sync between condition and branch + fail (Error_Bar_Hit_Mid_Case_Raw()); + + // Regardless of whether a "condition" was true or false, it's + // necessary to evaluate the next "branch" to know how far to skip: + // + // condition: true + // case [condition 10 + 20 true {hello}] ;-- returns 30 + // + // condition: false + // case [condition 10 + 20 true {hello}] ;-- returns {hello} + // + // This uses the safe form, so you can't say `case [[x] [y]]` because + // the [x] condition is a literal block. However you can say + // `foo: [x] | case [foo [y]]`, since it is evaluated, or use a + // GROUP! as in `case [([x]) [y]]`. + // + if (NOT(IS_CONDITIONAL_TRUE_SAFE(D_CELL))) { + if (Do_Next_In_Frame_Throws(D_CELL, f)) { + Move_Value(D_OUT, D_CELL); + goto return_thrown; + } + + continue; + } + + // When the condition is TRUE?, CASE actually does a double evaluation + // if a block is yielded as the branch: + // + // stuff: [print "This will be printed"] + // case [true stuff] + // + // Similar to IF TRUE STUFF, so CASE can act like many IFs at once. + + if (Do_Next_In_Frame_Throws(D_CELL, f)) { + Move_Value(D_OUT, D_CELL); + goto return_thrown; + } + + // !!! Optimization note: if the previous evaluation had gone into + // D_OUT directly it could just stay there in some cases; and even + // block evaluation doesn't need the copy. Review how this shared + // code might get more efficient if the data were already in D_OUT. + // + if (Run_Branch_Throws(D_OUT, D_CELL, REF(only))) + goto return_thrown; + + if (NOT(REF(all))) + goto return_matched; + + // keep matching if /ALL + } + + goto return_maybe_matched; + +return_maybe_matched: // CASE/ALL can get here even if D_OUT not written + Drop_Frame(f); + if (REF(opt)) + return R_OUT_VOID_IF_UNWRITTEN; // user wants voids as-is + return R_OUT_VOID_IF_UNWRITTEN_BLANK_IF_VOID; + +return_matched: + Drop_Frame(f); + if (REF(opt)) + return R_OUT; // user wants voids as-is + return R_OUT_BLANK_IF_VOID; + +return_thrown: + Drop_Frame(f); + return R_OUT_IS_THROWN; +} + + +// +// switch: native [ +// +// {Selects a choice and evaluates the block that follows it.} +// +// return: [ any-value!] +// {Last case evaluation, or void if no cases matched} +// value [any-value!] +// "Target value" +// cases [block!] +// "Block of cases (comparison lists followed by block branches)" +// /default +// "Default case if no others found" +// default-case +// "Block to execute (or value to return)" +// /all +// "Evaluate all matches (not just first one)" +// /strict +// {Use STRICT-EQUAL? when comparing cases instead of EQUAL?} +// /opt +// "If branch runs and returns void, do not convert it to BLANK!" +// ] +// +REBNATIVE(switch) +{ + INCLUDE_PARAMS_OF_SWITCH; // ? is renamed as "q" + + DECLARE_FRAME (f); + Push_Frame(f, ARG(cases)); + + // The evaluator always initializes the out slot to an END marker. That + // makes sure it gets overwritten with a value (or void) before returning. + // But here SWITCH also lets END indicate no matching cases ran yet. + + assert(IS_END(D_OUT)); + + REBVAL *value = ARG(value); + + // For safety, notice if someone wrote `switch [x] [...]` with a literal + // block in source, as that is likely a mistake. + // + if (IS_BLOCK(value) && GET_VAL_FLAG(value, VALUE_FLAG_UNEVALUATED)) + fail (Error_Block_Switch_Raw(value)); + + // Frame's extra D_CELL is free since the function has > 1 arg. Reuse it + // as a temporary GC-safe location for holding evaluations. This + // holds the last test so that `switch 9 [1 ["a"] 2 ["b"] "c"]` is "c". + + Init_Void(D_CELL); // used for "fallout" + + while (NOT_END(f->value)) { + + // If a block is seen at this point, it doesn't correspond to any + // condition to match. If no more tests are run, let it suppress the + // feature of the last value "falling out" the bottom of the switch + + if (IS_BLOCK(f->value)) { + Init_Void(D_CELL); + goto continue_loop; + } + + // GROUP!, GET-WORD! and GET-PATH! are evaluated in Ren-C's SWITCH + // All other types are seen as-is (hence words act "quoted") + + if ( + IS_GROUP(f->value) + || IS_GET_WORD(f->value) + || IS_GET_PATH(f->value) + ){ + if (Eval_Value_Core_Throws(D_CELL, f->value, f->specifier)) { + Move_Value(D_OUT, D_CELL); + goto return_thrown; + } + } + else + Derelativize(D_CELL, f->value, f->specifier); + + // It's okay that we are letting the comparison change `value` + // here, because equality is supposed to be transitive. So if it + // changes 0.01 to 1% in order to compare it, anything 0.01 would + // have compared equal to so will 1%. (That's the idea, anyway, + // required for `a = b` and `b = c` to properly imply `a = c`.) + // + // !!! This means fallout can be modified from its intent. Rather + // than copy here, this is a reminder to review the mechanism by + // which equality is determined--and why it has to mutate. + + if (!Compare_Modify_Values(ARG(value), D_CELL, REF(strict) ? 1 : 0)) + goto continue_loop; + + // Skip ahead to try and find a block, to treat as code for the match + + do { + Fetch_Next_In_Frame(f); + if (IS_END(f->value)) + goto return_defaulted; + } while (!IS_BLOCK(f->value)); + + // Run the code if it was found. Because it writes D_OUT with a value + // (or void), it won't be END--so we'll know at least one case has run. + + REBSPC *derived; // goto would cross initialization + derived = Derive_Specifier(VAL_SPECIFIER(ARG(cases)), f->value); + if (Do_At_Throws( + D_OUT, + VAL_ARRAY(f->value), + VAL_INDEX(f->value), + derived + )) { + goto return_thrown; + } + + // Only keep processing if the /ALL refinement was specified + + if (NOT(REF(all))) + goto return_matched; + + continue_loop: + Fetch_Next_In_Frame(f); + } + + if (NOT_END(D_OUT)) // at least one case body's DO ran and overwrote D_OUT + goto return_matched; + +return_defaulted: + Drop_Frame(f); + + if (REF(default)) { + const REBOOL only = FALSE; // !!! Should it use REF(only)? + + if (Run_Branch_Throws(D_OUT, ARG(default_case), only)) + goto return_thrown; + + if (REF(opt)) + return R_OUT; + return R_OUT_BLANK_IF_VOID; + } + + Move_Value(D_OUT, D_CELL); // last test "falls out", might be void + return R_OUT; + +return_matched: + Drop_Frame(f); + + if (REF(opt)) + return R_OUT; + return R_OUT_BLANK_IF_VOID; + +return_thrown: + Drop_Frame(f); + return R_OUT_IS_THROWN; +} + + +// +// catch: native [ +// +// {Catches a throw from a block and returns its value.} +// +// return: [ any-value!] +// block [block!] "Block to evaluate" +// /name +// "Catches a named throw" ;-- should it be called /named ? +// names [block! word! function! object!] +// "Names to catch (single name if not block)" +// /quit +// "Special catch for QUIT native" +// /any +// {Catch all throws except QUIT (can be used with /QUIT)} +// /with +// "Handle thrown case with code" +// handler [block! function!] +// "If FUNCTION!, spec matches [value name]" +// /? +// "Instead of result or catch, return LOGIC! of if a catch occurred" +// ] +// +REBNATIVE(catch) +// +// There's a refinement for catching quits, and CATCH/ANY will not alone catch +// it (you have to CATCH/ANY/QUIT). Currently the label for quitting is the +// NATIVE! function value for QUIT. +{ + INCLUDE_PARAMS_OF_CATCH; // ? is renamed as "q" + + // /ANY would override /NAME, so point out the potential confusion + // + if (REF(any) && REF(name)) + fail (Error_Bad_Refines_Raw()); + + if (Do_Any_Array_At_Throws(D_OUT, ARG(block))) { + if ( + ( + REF(any) + && (!IS_FUNCTION(D_OUT) || VAL_FUNC_DISPATCHER(D_OUT) != &N_quit) + ) + || ( + REF(quit) + && (IS_FUNCTION(D_OUT) && VAL_FUNC_DISPATCHER(D_OUT) == &N_quit) + ) + ) { + goto was_caught; + } + + if (REF(name)) { + // + // We use equal? by way of Compare_Modify_Values, and re-use the + // refinement slots for the mutable space + + REBVAL *temp1 = ARG(quit); + REBVAL *temp2 = ARG(any); + + // !!! The reason we're copying isn't so the VALUE_FLAG_THROWN bit + // won't confuse the equality comparison...but would it have? + + if (IS_BLOCK(ARG(names))) { + // + // Test all the words in the block for a match to catch + + RELVAL *candidate = VAL_ARRAY_AT(ARG(names)); + for (; NOT_END(candidate); candidate++) { + // + // !!! Should we test a typeset for illegal name types? + // + if (IS_BLOCK(candidate)) + fail (ARG(names)); + + Derelativize(temp1, candidate, VAL_SPECIFIER(ARG(names))); + Move_Value(temp2, D_OUT); + + // Return the THROW/NAME's arg if the names match + // !!! 0 means equal?, but strict-equal? might be better + // + if (Compare_Modify_Values(temp1, temp2, 0)) + goto was_caught; + } + } + else { + Move_Value(temp1, ARG(names)); + Move_Value(temp2, D_OUT); + + // Return the THROW/NAME's arg if the names match + // !!! 0 means equal?, but strict-equal? might be better + // + if (Compare_Modify_Values(temp1, temp2, 0)) + goto was_caught; + } + } + else { + // Return THROW's arg only if it did not have a /NAME supplied + // + if (IS_BLANK(D_OUT)) + goto was_caught; + } + + // Throw name is in D_OUT, thrown value is held task local + // + return R_OUT_IS_THROWN; + } + + if (REF(q)) return R_FALSE; + + return R_OUT; + +was_caught: + if (REF(with)) { + REBVAL *handler = ARG(handler); + + // We again re-use the refinement slots, but this time as mutable + // space protected from GC for the handler's arguments + // + REBVAL *thrown_arg = ARG(any); + REBVAL *thrown_name = ARG(quit); + + CATCH_THROWN(thrown_arg, D_OUT); + Move_Value(thrown_name, D_OUT); // THROWN bit cleared by CATCH_THROWN + + if (IS_BLOCK(handler)) { + // + // There's no way to pass args to a block (so just DO it) + // + if (Do_Any_Array_At_Throws(D_OUT, ARG(handler))) + return R_OUT_IS_THROWN; + + if (REF(q)) return R_TRUE; + + return R_OUT; + } + else if (IS_FUNCTION(handler)) { + // + // This calls the function but only does a DO/NEXT. Hence the + // function might be arity 0, arity 1, or arity 2. If it has + // greater arity it will process more arguments. + // + if (Apply_Only_Throws( + D_OUT, + FALSE, // do not alert if handler doesn't consume all args + handler, + thrown_arg, + thrown_name, + END + )){ + return R_OUT_IS_THROWN; + } + + if (REF(q)) return R_TRUE; + + return R_OUT; + } + } + + // If no handler, just return the caught thing + // + CATCH_THROWN(D_OUT, D_OUT); + + if (REF(q)) return R_TRUE; + + return R_OUT; +} + + +// +// throw: native [ +// +// "Throws control back to a previous catch." +// +// value [ any-value!] +// "Value returned from catch" +// /name +// "Throws to a named catch" +// name-value [word! function! object!] +// ] +// +REBNATIVE(throw) +// +// Choices are currently limited for what one can use as a "name" of a THROW. +// Note blocks as names would conflict with the `name_list` feature in CATCH. +// +// !!! Should parameters be /NAMED and NAME ? +{ + INCLUDE_PARAMS_OF_THROW; + + REBVAL *value = ARG(value); + + if (IS_ERROR(value)) { + // + // We raise an alert from within the implementation of throw for + // trying to use it to trigger errors, because if THROW just didn't + // take errors in the spec it wouldn't guide what *to* use. + // + fail (Error_Use_Fail_For_Error_Raw(value)); + + // Note: Caller can put the ERROR! in a block or use some other + // such trick if it wants to actually throw an error. + // (Better than complicating via THROW/ERROR-IS-INTENTIONAL!) + } + + if (REF(name)) + Move_Value(D_OUT, ARG(name_value)); + else { + // Blank values serve as representative of THROWN() means "no name" + // + Init_Blank(D_OUT); + } + + CONVERT_NAME_TO_THROWN(D_OUT, value); + return R_OUT_IS_THROWN; } diff --git a/src/core/n-data.c b/src/core/n-data.c old mode 100644 new mode 100755 index 4463705930..032e241d81 --- a/src/core/n-data.c +++ b/src/core/n-data.c @@ -1,1073 +1,1462 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: n-data.c -** Summary: native functions for data and context -** Section: natives -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %n-data.c +// Summary: "native functions for data and context" +// Section: natives +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -#ifdef REMOVED -// Removed because it causes more trouble than the benefits it provides. -/*********************************************************************** -** -*/ REBNATIVE(alias) -/* -***********************************************************************/ + +static REBOOL Check_Char_Range(REBVAL *val, REBINT limit) { - REBVAL *word = D_ARG(1); // word to alias - REBVAL *alias = D_ARG(2); // new string (word does not work due to RESOLVE) - REBCNT sym; - REBVAL *wrd; - - // Make new word or use existing word: -// if (IS_STRING(alias)) { - REBYTE *bp; - bp = Qualify_String(alias, 255, &sym, TRUE); // sym = len - sym = Scan_Word(bp, sym); -// } -// else -// sym = VAL_WORD_SYM(alias); - - // Word cannot already be used: - wrd = BLK_SKIP(PG_Word_Table.series, sym); - if (sym != VAL_SYM_CANON(wrd)) Trap1(RE_ALREADY_USED, alias); - - // Change the new word's canon pointer to the word provided: - VAL_SYM_CANON(wrd) = VAL_WORD_CANON(word); - VAL_SYM_ALIAS(wrd) = 0; - - // Return new word with prior word's same bindings: - VAL_WORD_SYM(word) = sym; - return R_ARG1; + if (IS_CHAR(val)) { + if (VAL_CHAR(val) > limit) return FALSE; + return TRUE; + } + + if (IS_INTEGER(val)) { + if (VAL_INT64(val) > limit) return FALSE; + return TRUE; + } + + REBCNT len = VAL_LEN_AT(val); + if (VAL_BYTE_SIZE(val)) { + REBYTE *bp = VAL_BIN_AT(val); + if (limit == 0xff) return TRUE; // by definition + for (; len > 0; len--, bp++) + if (*bp > limit) return FALSE; + } + else { + REBUNI *up = VAL_UNI_AT(val); + for (; len > 0; len--, up++) + if (*up > limit) return FALSE; + } + + return TRUE; } -#endif -static int Check_Char_Range(REBVAL *val, REBINT limit) + +// +// ascii?: native [ +// +// {Returns TRUE if value or string is in ASCII character range (below 128).} +// +// value [any-string! char! integer!] +// ] +// +REBNATIVE(ascii_q) { - REBCNT len; - - if (IS_CHAR(val)) { - if (VAL_CHAR(val) > limit) return R_FALSE; - return R_TRUE; - } - - if (IS_INTEGER(val)) { - if (VAL_INT64(val) > limit) return R_FALSE; - return R_TRUE; - } - - len = VAL_LEN(val); - if (VAL_BYTE_SIZE(val)) { - REBYTE *bp = VAL_BIN_DATA(val); - if (limit == 0xff) return R_TRUE; // by definition - for (; len > 0; len--, bp++) - if (*bp > limit) return R_FALSE; - } else { - REBUNI *up = VAL_UNI_DATA(val); - for (; len > 0; len--, up++) - if (*up > limit) return R_FALSE; - } - - return R_TRUE; + INCLUDE_PARAMS_OF_ASCII_Q; + + return R_FROM_BOOL(Check_Char_Range(ARG(value), 0x7f)); } -/*********************************************************************** -** -*/ REBNATIVE(asciiq) -/* -***********************************************************************/ +// +// latin1?: native [ +// +// {Returns TRUE if value or string is in Latin-1 character range (below 256).} +// +// value [any-string! char! integer!] +// ] +// +REBNATIVE(latin1_q) { - return Check_Char_Range(D_ARG(1), 0x7f); + INCLUDE_PARAMS_OF_LATIN1_Q; + + return R_FROM_BOOL(Check_Char_Range(ARG(value), 0xff)); } -/*********************************************************************** -** -*/ REBNATIVE(latin1q) -/* -***********************************************************************/ +// +// verify: native [ +// +// {Ensure conditions are TRUE?, even when not debugging (see also: ASSERT)} +// +// return: [] +// conditions [logic! block!] +// {Block of conditions to evaluate, void and FALSE? trigger alerts} +// ] +// +REBNATIVE(verify) { - return Check_Char_Range(D_ARG(1), 0xff); + INCLUDE_PARAMS_OF_VERIFY; + + if (IS_LOGIC(ARG(conditions))) { + if (VAL_LOGIC(ARG(conditions))) + return R_VOID; + + fail (Error_Verify_Failed_Raw(FALSE_VALUE)); + } + + DECLARE_FRAME (f); + Push_Frame(f, ARG(conditions)); + + DECLARE_LOCAL (temp); + + while (NOT_END(f->value)) { + const RELVAL *start = f->value; + if (Do_Next_In_Frame_Throws(D_OUT, f)) { + Drop_Frame(f); + return R_OUT_IS_THROWN; + } + + if (!IS_VOID(D_OUT) && IS_CONDITIONAL_TRUE(D_OUT)) + continue; + + Init_Block( + temp, + Copy_Values_Len_Shallow(start, f->specifier, f->value - start) + ); + + if (IS_VOID(D_OUT)) + fail (Error_Verify_Void_Raw(temp)); + + fail (Error_Verify_Failed_Raw(temp)); + } + + Drop_Frame(f); + return R_VOID; } -/*********************************************************************** -** -*/ static REBOOL Is_Of_Type(REBVAL *value, REBVAL *types) -/* -** Types can be: word or block. Each element must be either -** a datatype or a typeset. -** -***********************************************************************/ -{ - REBVAL *val; - - val = IS_WORD(types) ? Get_Var(types) : types; - - if (IS_DATATYPE(val)) { - return (VAL_DATATYPE(val) == (REBINT)VAL_TYPE(value)); - } - - if (IS_TYPESET(val)) { - return (TYPE_CHECK(val, VAL_TYPE(value))); - } - - if (IS_BLOCK(val)) { - for (types = VAL_BLK_DATA(val); NOT_END(types); types++) { - val = IS_WORD(types) ? Get_Var(types) : types; - if (IS_DATATYPE(val)) - if (VAL_DATATYPE(val) == (REBINT)VAL_TYPE(value)) return TRUE; - else if (IS_TYPESET(val)) - if (TYPE_CHECK(val, VAL_TYPE(value))) return TRUE; - else - Trap1(RE_INVALID_TYPE, Of_Type(val)); - } - return FALSE; - } - - Trap_Arg(types); - - return 0; // for compiler +// Test used iteratively by MAYBE native. Returns R_BLANK if the test fails, +// R_OUT if success, or R_OUT_IS_THROWN if a test throws. +// +inline static REB_R Do_Test_For_Maybe( + REBVAL *out, + const REBVAL *value, + const RELVAL *test +) { + if (IS_DATATYPE(test)) { + if (VAL_TYPE_KIND(test) != VAL_TYPE(value)) + return R_BLANK; + Move_Value(out, value); + return R_OUT; + } + + if (IS_TYPESET(test)) { + if (!TYPE_CHECK(test, VAL_TYPE(value))) + return R_BLANK; + Move_Value(out, value); + return R_OUT; + } + + if (IS_FUNCTION(test)) { + if (Apply_Only_Throws(out, TRUE, const_KNOWN(test), value, END)) + return R_OUT_IS_THROWN; + + if (IS_VOID(out)) + fail (Error_No_Return_Raw()); + + if (IS_CONDITIONAL_FALSE(out)) + return R_BLANK; + + Move_Value(out, value); + return R_OUT; + } + + fail (Error_Invalid_Type(VAL_TYPE(test))); } -/*********************************************************************** -** -*/ REBNATIVE(assert) -/* -***********************************************************************/ +// +// maybe: native [ +// +// {Check value using tests (match types, TRUE? or FALSE?, filter function)} +// +// return: [ any-value!] +// {The input value or BLANK! if no match, void if FALSE? and matched} +// test [function! datatype! typeset! block! logic!] +// value [ any-value!] +// /? +// "Return LOGIC! of match vs. pass-through of value or blank" +// ] +// +REBNATIVE(maybe) { - REBVAL *value = D_ARG(1); // block, logic, or none - - if (!D_REF(2)) { - REBSER *block = VAL_SERIES(value); - REBCNT index = VAL_INDEX(value); - REBCNT i; - - ds = 0; - while (index < SERIES_TAIL(block)) { - index = Do_Next(block, i = index, 0); // stack volatile - ds = DS_POP; // volatile stack reference - if (IS_FALSE(ds)) { - Set_Block(ds, Copy_Block_Len(block, i, 3)); - Trap1(RE_ASSERT_FAILED, ds); - } - if (THROWN(ds)) return R_TOS1; - } - } - else { - // /types [var1 integer! var2 [integer! decimal!]] - REBVAL *val; - REBVAL *type; - - for (value = VAL_BLK_DATA(value); NOT_END(value); value += 2) { - if (IS_WORD(value)) { - val = Get_Var(value); - } - else if (IS_PATH(value)) { - val = value; - Do_Path(&val, 0); - val = DS_POP; // volatile stack reference - } - else Trap_Arg(value); - - type = value+1; - if (IS_END(type)) Trap0(RE_MISSING_ARG); - if (IS_BLOCK(type) || IS_WORD(type) || IS_TYPESET(type) || IS_DATATYPE(type)) { - if (!Is_Of_Type(val, type)) - Trap1(RE_WRONG_TYPE, value); - } - else Trap_Arg(type); - } - } - - return R_TRUE; + INCLUDE_PARAMS_OF_MAYBE; // ? is renamed as "q" + + REBVAL *test = ARG(test); + REBVAL *value = ARG(value); + + if (IS_LOGIC(test)) { + if (!IS_VOID(value) && VAL_LOGIC(test) == IS_CONDITIONAL_TRUE(value)) + goto type_matched; + return REF(q) ? R_FALSE : R_BLANK; + } + + REB_R r; + if (IS_BLOCK(test)) { + // + // !!! What should the behavior for `MAYBE [] ...` be? Should that be + // an error? People wouldn't write it literally, but could wind up + // with an empty array as the product of a COMPOSE or something. + // Consider it ambiguous for now and give back void... + // + r = R_VOID; + + const RELVAL *item; + for (item = VAL_ARRAY_AT(test); NOT_END(item); ++item) { + r = Do_Test_For_Maybe( + D_OUT, + value, + IS_WORD(item) + ? Get_Opt_Var_May_Fail(item, VAL_SPECIFIER(test)) + : item + ); + + if (r != R_BLANK) + goto type_matched; + } + } + else + r = Do_Test_For_Maybe(D_OUT, value, test); + + if (r == R_OUT_IS_THROWN) + return r; + + if (REF(q)) + return r == R_BLANK ? R_FALSE : R_TRUE; + + if (r == R_BLANK) + return r; + + assert(r == R_OUT); // must have matched! + +type_matched: + if (REF(q)) + return R_TRUE; + + // Because there may be usages like `if maybe logic! x [print "logic!"]`, + // it would be bad to take in a FALSE and pass back a FALSE. This is + // why /? (and its specialization MAYBE?) exist, but to help avoid + // likely mistakes this returns a void. + // + if (IS_CONDITIONAL_FALSE(value)) + return R_VOID; + + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(as_pair) -/* -***********************************************************************/ +// +// as-pair: native [ +// +// "Combine X and Y values into a pair." +// +// x [any-number!] +// y [any-number!] +// ] +// +REBNATIVE(as_pair) { - REBVAL *val = D_ARG(1); - - VAL_SET(D_RET, REB_PAIR); - - if (IS_INTEGER(val)) { - VAL_PAIR_X(D_RET) = (REBD32)VAL_INT64(val); - } - else { - VAL_PAIR_X(D_RET) = (REBD32)VAL_DECIMAL(val); - } - - val = D_ARG(2); - if (IS_INTEGER(val)) { - VAL_PAIR_Y(D_RET) = (REBD32)VAL_INT64(val); - } - else { - VAL_PAIR_Y(D_RET) = (REBD32)VAL_DECIMAL(val); - } - - return R_RET; + INCLUDE_PARAMS_OF_AS_PAIR; + + REBVAL *x = ARG(x); + REBVAL *y = ARG(y); + + SET_PAIR( + D_OUT, + IS_INTEGER(x) ? VAL_INT64(x) : VAL_DECIMAL(x), + IS_INTEGER(y) ? VAL_INT64(y) : VAL_DECIMAL(y) + ); + + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(bind) -/* -** 1 words -** 2 context | word -** 3 /copy -** 4 /only -** 5 /new -** 6 /set -** -***********************************************************************/ +// +// bind: native [ +// +// "Binds words or words in arrays to the specified context." +// +// value [any-array! any-word!] +// "A word or array (modified) (returned)" +// target [any-word! any-context!] +// "The target context or a word whose binding should be the target" +// /copy +// "Bind and return a deep copy of a block, don't modify original" +// /only +// "Bind only first block (not deep)" +// /new +// "Add to context any new words found" +// /set +// "Add to context any new set-words found" +// ] +// +REBNATIVE(bind) { - REBVAL *arg; - REBSER *blk; - REBSER *frame; - REBCNT flags; - REBFLG rel = FALSE; - - flags = D_REF(4) ? 0 : BIND_DEEP; - if (D_REF(5)) flags |= BIND_ALL; - if (D_REF(6)) flags |= BIND_SET; - - // Get context from a word, object (or port); - arg = D_ARG(2); - if (IS_OBJECT(arg) || IS_MODULE(arg) || IS_PORT(arg)) - frame = VAL_OBJ_FRAME(arg); - else { // word - rel = (VAL_WORD_INDEX(arg) < 0); - frame = VAL_WORD_FRAME(arg); - if (!frame) Trap1(RE_NOT_DEFINED, arg); - } - - // Block or word to bind: - arg = D_ARG(1); - - // Bind single word: - if (ANY_WORD(arg)) { - if (rel) { - Bind_Stack_Word(frame, arg); - return R_ARG1; - } - if (!Bind_Word(frame, arg)) { - if (flags & BIND_ALL) - Append_Frame(frame, arg, 0); // not in context, so add it. - else - Trap1(RE_NOT_IN_CONTEXT, arg); - } - return R_ARG1; - } - - // Copy block if necessary (/copy): - blk = D_REF(3) ? Clone_Block_Value(arg) : VAL_SERIES(arg); -// if (D_REF(3)) blk = Copy_Block_Deep(blk, VAL_INDEX(arg), VAL_TAIL(arg), COPY_DEEP); - Set_Block_Index(D_RET, blk, D_REF(3) ? 0 : VAL_INDEX(arg)); - - if (rel) - Bind_Stack_Block(frame, blk); //!! needs deep - else - Bind_Block(frame, BLK_HEAD(blk), flags); - - return R_RET; + INCLUDE_PARAMS_OF_BIND; + + REBVAL *value = ARG(value); + REBVAL *target = ARG(target); + + REBCTX *context; + + REBARR *array; + REBCNT flags = REF(only) ? BIND_0 : BIND_DEEP; + + REBU64 bind_types = TS_ANY_WORD; + + REBU64 add_midstream_types; + if (REF(new)) { + add_midstream_types = TS_ANY_WORD; + } + else if (REF(set)) { + add_midstream_types = FLAGIT_KIND(REB_SET_WORD); + } + else + add_midstream_types = 0; + + if (ANY_CONTEXT(target)) { + // + // Get target from an OBJECT!, ERROR!, PORT!, MODULE!, FRAME! + // + context = VAL_CONTEXT(target); + } + else { + // + // Extract target from whatever word we were given + // + assert(ANY_WORD(target)); + if (IS_WORD_UNBOUND(target)) + fail (Error_Not_Bound_Raw(target)); + + // The word in hand may be a relatively bound one. To return a + // specific frame, this needs to ensure that the Reb_Frame's data + // is a real context, not just a chunk of data. + // + context = VAL_WORD_CONTEXT(target); + } + + if (ANY_WORD(value)) { + // + // Bind a single word + + if (Try_Bind_Word(context, value)) { + Move_Value(D_OUT, value); + return R_OUT; + } + + // not in context, bind/new means add it if it's not. + // + if (REF(new) || (IS_SET_WORD(value) && REF(set))) { + Append_Context(context, value, NULL); + Move_Value(D_OUT, value); + return R_OUT; + } + + fail (Error_Not_In_Context_Raw(ARG(value))); + } + + // Copy block if necessary (/copy) + // + // !!! NOTE THIS IS IGNORING THE INDEX! If you ask to bind, it should + // bind forward only from the index you specified, leaving anything + // ahead of that point alone. Not changing it now when finding it + // because there could be code that depends on the existing (mis)behavior + // but it should be followed up on. + // + Move_Value(D_OUT, value); + if (REF(copy)) { + array = Copy_Array_At_Deep_Managed( + VAL_ARRAY(value), VAL_INDEX(value), VAL_SPECIFIER(value) + ); + INIT_VAL_ARRAY(D_OUT, array); // warning: macro copies args + } + else + array = VAL_ARRAY(value); + + Bind_Values_Core( + ARR_HEAD(array), + context, + bind_types, + add_midstream_types, + flags + ); + + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(boundq) -/* -***********************************************************************/ +// +// context-of: native [ +// +// "Returns the context in which a word is bound." +// +// word [any-word!] +// ] +// +REBNATIVE(context_of) { - REBVAL *word = D_ARG(1); + INCLUDE_PARAMS_OF_CONTEXT_OF; + + if (IS_WORD_UNBOUND(ARG(word))) return R_BLANK; + + // Requesting the context of a word that is relatively bound may result + // in that word having a FRAME! incarnated as a REBSER node (if it + // was not already reified.) + // + // !!! Mechanically it is likely that in the future, all FRAME!s for + // user functions will be reified from the moment of invocation. + // + Move_Value(D_OUT, CTX_VALUE(VAL_WORD_CONTEXT(ARG(word)))); - if (!HAS_FRAME(word)) return R_NONE; - if (VAL_WORD_INDEX(word) < 0) return R_TRUE; - SET_OBJECT(D_RET, VAL_WORD_FRAME(word)); - return R_RET; + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(unbind) -/* -** word | context -** /deep -** -***********************************************************************/ +// +// any-value?: native [ +// +// "Returns whether a data cell contains a value." +// +// cell [ any-value!] +// ] +// +REBNATIVE(any_value_q) { - REBVAL *word = D_ARG(1); + INCLUDE_PARAMS_OF_ANY_VALUE_Q; - if (ANY_WORD(word)) { - UNBIND(word); - } - else { - Unbind_Block(VAL_BLK_DATA(word), D_REF(2) != 0); - } - - return R_ARG1; + if (IS_VOID(ARG(cell))) + return R_FALSE; + return R_TRUE; } -/*********************************************************************** -** -*/ REBNATIVE(collect_words) -/* -** 1 block -** 3 /deep -** 4 /set -** 4 /ignore -** 5 object | block -** -***********************************************************************/ +// +// unbind: native [ +// +// "Unbinds words from context." +// +// word [block! any-word!] +// "A word or block (modified) (returned)" +// /deep +// "Process nested blocks" +// ] +// +REBNATIVE(unbind) { - REBSER *words; - REBCNT modes = 0; - REBVAL *prior = 0; - REBVAL *block; - REBVAL *obj; - - block = VAL_BLK_DATA(D_ARG(1)); - - if (D_REF(2)) modes |= BIND_DEEP; - if (!D_REF(3)) modes |= BIND_ALL; - - // If ignore, then setup for it: - if (D_REF(4)) { - obj = D_ARG(5); - if (ANY_OBJECT(obj)) - prior = BLK_SKIP(VAL_OBJ_WORDS(obj), 1); - else if (IS_BLOCK(obj)) - prior = VAL_BLK_DATA(obj); - // else stays 0 - } - - words = Collect_Block_Words(block, prior, modes); - Set_Block(D_RET, words); - return R_RET; + INCLUDE_PARAMS_OF_UNBIND; + + REBVAL *word = ARG(word); + + if (ANY_WORD(word)) + Unbind_Any_Word(word); + else + Unbind_Values_Core(VAL_ARRAY_AT(word), NULL, REF(deep)); + + Move_Value(D_OUT, word); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(get) -/* -***********************************************************************/ +// +// collect-words: native [ +// +// {Collect unique words used in a block (used for context construction).} +// +// block [block!] +// /deep +// "Include nested blocks" +// /set +// "Only include set-words" +// /ignore +// "Ignore prior words" +// hidden [any-context! block!] +// "Words to ignore" +// ] +// +REBNATIVE(collect_words) { - REBVAL *word = D_ARG(1); - REBVAL *val; - - if (ANY_WORD(word)) { - val = Get_Var(word); - if (IS_FRAME(val)) { - Init_Obj_Value(D_RET, VAL_WORD_FRAME(word)); - return R_RET; - } - if (!D_REF(2) && !IS_SET(val)) Trap1(RE_NO_VALUE, word); - } - else if (ANY_PATH(word)) { - val = Do_Path(&word, 0); - if (!val) val = DS_POP; // resides on stack - if (!D_REF(2) && !IS_SET(val)) Trap1(RE_NO_VALUE, word); //!!!! word is modified - } - else if (IS_OBJECT(word)) { - Assert_Public_Object(word); - Set_Block(D_RET, Copy_Block(VAL_OBJ_FRAME(word), 1)); - return R_RET; - } - else val = word; // all other values - - *D_RET = *val; - return R_RET; + INCLUDE_PARAMS_OF_COLLECT_WORDS; + + REBARR *words; + REBCNT modes; + RELVAL *values = VAL_ARRAY_AT(ARG(block)); + RELVAL *prior_values; + + if (REF(set)) + modes = COLLECT_ONLY_SET_WORDS; + else + modes = COLLECT_ANY_WORD; + + if (REF(deep)) modes |= COLLECT_DEEP; + + // If ignore, then setup for it: + if (REF(ignore)) { + if (ANY_CONTEXT(ARG(hidden))) { + // + // !!! These are typesets and not words. Is Collect_Words able + // to handle that? + // + prior_values = CTX_KEYS_HEAD(VAL_CONTEXT(ARG(hidden))); + } + else { + assert(IS_BLOCK(ARG(hidden))); + prior_values = VAL_ARRAY_AT(ARG(hidden)); + } + } + else + prior_values = NULL; + + words = Collect_Words(values, prior_values, modes); + Init_Block(D_OUT, words); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(in) -/* -***********************************************************************/ +// +// get: native [ +// +// {Gets the value of a word or path, or values of a context.} +// +// return: [ any-value!] +// {If the source looks up to a value, that value--else void} +// source [blank! any-word! any-path! block!] +// {Word or path to get, or block of words or paths (blank is no-op)} +// /opt +// {Return void if no value instead of blank} +// ] +// +REBNATIVE(get) +// +// Note: GET* cannot be the fundamental operation, because GET could not be +// written for blocks (since voids can't be put in blocks, so they couldn't +// be "blankified") { - REBVAL *val = D_ARG(1); // object, error, port, block - REBVAL *word = D_ARG(2); - REBCNT index; - REBSER *frame; - - if (IS_BLOCK(val) || IS_PAREN(val)) { - if (IS_WORD(word)) { - REBVAL *v; - REBCNT i; - for (i = VAL_INDEX(val); i < VAL_TAIL(val); i++) { - v = VAL_BLK_SKIP(val, i); - v = Get_Simple_Value(v); - if (IS_OBJECT(v)) { - frame = VAL_OBJ_FRAME(v); - index = Find_Word_Index(frame, VAL_WORD_SYM(word), FALSE); - if (index > 0) { - VAL_WORD_INDEX(word) = (REBCNT)index; - VAL_WORD_FRAME(word) = frame; - *D_RET = *word; - return R_RET; - } - } - } - return R_NONE; - } - else Trap_Arg(word); - } - - frame = IS_ERROR(val) ? VAL_ERR_OBJECT(val) : VAL_OBJ_FRAME(val); - - // Special form: IN object block - if (IS_BLOCK(word) || IS_PAREN(word)) { - Bind_Block(frame, VAL_BLK(word), BIND_DEEP); - return R_ARG2; - } - - index = Find_Word_Index(frame, VAL_WORD_SYM(word), FALSE); - - if (index > 0) { - VAL_WORD_INDEX(word) = (REBCNT)index; - VAL_WORD_FRAME(word) = frame; - *D_RET = *word; - } else - return R_NONE; - return R_RET; + INCLUDE_PARAMS_OF_GET; + + RELVAL *source; + REBVAL *dest; + REBSPC *specifier; + + REBARR *results; + + if (IS_BLOCK(ARG(source))) { + // + // If a BLOCK! of gets are performed, voids cannot be put into the + // resulting BLOCK!. Hence for /OPT to be legal, it would have to + // give back a BLANK! or other placeholder. However, since GET-VALUE + // is built on GET/OPT, we defer the error until we actually encounter + // an unset variable...which produces that error case that could not + // be done by "checking the block for voids" + + source = VAL_ARRAY_AT(ARG(source)); + specifier = VAL_SPECIFIER(ARG(source)); + + results = Make_Array(VAL_LEN_AT(ARG(source))); + TERM_ARRAY_LEN(results, VAL_LEN_AT(ARG(source))); + dest = SINK(ARR_HEAD(results)); + } + else { + // Move the argument into the single cell in the frame if it's not a + // block, so the same enumeration-up-to-an-END marker can work on it + // as for handling a block of items. + // + Move_Value(D_CELL, ARG(source)); + source = D_CELL; + specifier = SPECIFIED; + dest = D_OUT; + results = NULL; // wasteful but avoids maybe-used-uninitalized warning + } + + DECLARE_LOCAL (get_path_hack); // runs prep code, don't put inside loop + + for (; NOT_END(source); ++source, ++dest) { + if (IS_BAR(source)) { + // + // `a: 10 | b: 20 | get [a | b]` will give back `[10 | 20]`. + // While seemingly not a very useful feature standalone, this + // compatibility with SET could come in useful so that blocks + // don't have to be rearranged to filter out BAR!s. + // + Init_Bar(dest); + } + else if (IS_BLANK(source)) { + Init_Void(dest); // may be turned to blank after loop, or error + } + else if (ANY_WORD(source)) { + Copy_Opt_Var_May_Fail(dest, source, specifier); + } + else if (ANY_PATH(source)) { + // + // Make sure the path does not contain any GROUP!s, because that + // would trigger evaluations. GET does not sound like something + // that should have such a side-effect, the user should go with + // a REDUCE operation if that's what they want. + // + RELVAL *temp = VAL_ARRAY_AT(source); + for (; NOT_END(temp); ++temp) + if (IS_GROUP(temp)) + fail ("GROUP! can't be in paths with GET, use REDUCE"); + + // Piggy-back on the GET-PATH! mechanic by copying to a temp + // value and changing its type bits. + // + // !!! Review making a more efficient method of doing this. + // + Derelativize(get_path_hack, source, specifier); + VAL_SET_TYPE_BITS(get_path_hack, REB_GET_PATH); + + // Here we DO it, which means that `get 'foo/bar` will act the + // same as `:foo/bar` for all types. + // + if (Do_Path_Throws_Core( + dest, + NULL, + get_path_hack, + SPECIFIED, + NULL + )){ + // Should not be possible if there's no GROUP! + // + fail (Error_No_Catch_For_Throw(dest)); + } + } + + if (IS_VOID(dest)) { + if (REF(opt)) { + if (IS_BLOCK(ARG(source))) // can't put voids in blocks + fail (Error_No_Value_Core(source, specifier)); + } + else + Init_Blank(dest); + } + } + + if (IS_BLOCK(ARG(source))) + Init_Block(D_OUT, results); + + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(not) -/* -***********************************************************************/ +// +// to-value: native [ +// +// {Turns voids into blanks, with ANY-VALUE! passing through. (See: OPT)} +// +// return: [any-value!] +// value [ any-value!] +// ] +// +REBNATIVE(to_value) { - return (IS_FALSE(D_ARG(1)) ? R_TRUE : R_FALSE); + INCLUDE_PARAMS_OF_TO_VALUE; + + if (IS_VOID(ARG(value))) + return R_BLANK; + + Move_Value(D_OUT, ARG(value)); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(resolve) -/* -** 3 /only -** 4 from -** 5 /all -** 6 /expand -** -***********************************************************************/ +// +// opt: native [ +// +// {Convert blanks to optionals. (See Also: TO-VALUE)} +// +// return: [ any-value!] +// {void if input was a BLANK!, or original value otherwise} +// value [ any-value!] +// ] +// +REBNATIVE(opt) { - REBSER *target = VAL_OBJ_FRAME(D_ARG(1)); - REBSER *source = VAL_OBJ_FRAME(D_ARG(2)); - if (IS_INTEGER(D_ARG(4))) Int32s(D_ARG(4), 1); // check range and sign - Resolve_Context(target, source, D_ARG(4), D_REF(5), D_REF(6)); // /from /all /expand - return R_ARG1; + INCLUDE_PARAMS_OF_OPT; + + if (IS_BLANK(ARG(value))) + return R_VOID; + + Move_Value(D_OUT, ARG(value)); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(set) -/* -** word [any-word! block! object!] {Word or words to set} -** value [any-type!] {Value or block of values} -** /any {Allows setting words to any value.} -** /pad {For objects, if block is too short, remaining words are set to NONE.} -** -***********************************************************************/ +// +// in: native [ +// +// "Returns the word or block bound into the given context." +// +// context [any-context! block!] +// word [any-word! block! group!] "(modified if series)" +// ] +// +REBNATIVE(in) +// +// !!! The argument names here are bad... not necessarily a context and not +// necessarily a word. `code` or `source` to be bound in a `target`, perhaps? { - REBVAL *word = D_ARG(1); - REBVAL *val = D_ARG(2); - REBVAL *tmp = NULL; - REBOOL not_any = !D_REF(3); - REBOOL is_blk = FALSE; - - if (not_any && !IS_SET(val)) - Trap1(RE_NEED_VALUE, word); - - if (ANY_WORD(word)) { - Set_Var(word, val); - return R_ARG2; - } - - if (ANY_PATH(word)) { - Do_Path(&word, val); - return R_ARG2; - } - - // Is value a block? - if (IS_BLOCK(val)) { - val = VAL_BLK_DATA(val); - if (IS_END(val)) val = NONE_VALUE; - else is_blk = TRUE; - } - - // Is target an object? - if (IS_OBJECT(word)) { - Assert_Public_Object(word); - // Check for protected or unset before setting anything. - for (tmp = val, word = VAL_OBJ_WORD(word, 1); NOT_END(word); word++) { // skip self - if (VAL_PROTECTED(word)) Trap1(RE_LOCKED_WORD, word); - if (not_any && is_blk && !IS_END(tmp) && IS_UNSET(tmp++)) // won't advance past end - Trap1(RE_NEED_VALUE, word); - } - for (word = VAL_OBJ_VALUES(D_ARG(1)) + 1; NOT_END(word); word++) { // skip self - // WARNING: Unwinds that make it here are assigned. All unwinds - // should be screened earlier (as is done in e.g. REDUCE, or for - // function arguments) so they don't even get into this function. - *word = *val; - if (is_blk) { - val++; - if (IS_END(val)) { - if (!D_REF(4)) break; // /pad not provided - is_blk = FALSE; - val = NONE_VALUE; - } - } - } - } else { // Set block of words: - if (not_any && is_blk) { // Check for unset before setting anything. - for (tmp = val, word = VAL_BLK_DATA(word); NOT_END(word) && NOT_END(tmp); word++, tmp++) { - switch (VAL_TYPE(word)) { - case REB_WORD: - case REB_SET_WORD: - case REB_LIT_WORD: - if (!IS_SET(tmp)) Trap1(RE_NEED_VALUE, word); - break; - case REB_GET_WORD: - if (!IS_SET(IS_WORD(tmp) ? Get_Var(tmp) : tmp)) Trap1(RE_NEED_VALUE, word); - } - } - } - for (word = VAL_BLK_DATA(D_ARG(1)); NOT_END(word); word++) { - if (IS_WORD(word) || IS_SET_WORD(word) || IS_LIT_WORD(word)) Set_Var(word, val); - else if (IS_GET_WORD(word)) - Set_Var(word, IS_WORD(val) ? Get_Var(val) : val); - else Trap_Arg(word); - if (is_blk) { - val++; - if (IS_END(val)) is_blk = FALSE, val = NONE_VALUE; - } - } - } - - return R_ARG2; + INCLUDE_PARAMS_OF_IN; + + REBVAL *val = ARG(context); // object, error, port, block + REBVAL *word = ARG(word); + + DECLARE_LOCAL (safe); + + if (IS_BLOCK(val) || IS_GROUP(val)) { + if (IS_WORD(word)) { + const REBVAL *v; + REBCNT i; + for (i = VAL_INDEX(val); i < VAL_LEN_HEAD(val); i++) { + Get_Simple_Value_Into( + safe, + VAL_ARRAY_AT_HEAD(val, i), + VAL_SPECIFIER(val) + ); + + v = safe; + if (IS_OBJECT(v)) { + REBCTX *context = VAL_CONTEXT(v); + REBCNT index = Find_Canon_In_Context( + context, VAL_WORD_CANON(word), FALSE + ); + if (index != 0) { + CLEAR_VAL_FLAG(word, VALUE_FLAG_RELATIVE); + SET_VAL_FLAG(word, WORD_FLAG_BOUND); + INIT_WORD_CONTEXT(word, context); + INIT_WORD_INDEX(word, index); + Move_Value(D_OUT, word); + return R_OUT; + } + } + } + return R_BLANK; + } + + fail (word); + } + + REBCTX *context = VAL_CONTEXT(val); + + // Special form: IN object block + if (IS_BLOCK(word) || IS_GROUP(word)) { + Bind_Values_Deep(VAL_ARRAY_HEAD(word), context); + Move_Value(D_OUT, word); + return R_OUT; + } + + REBCNT index = Find_Canon_In_Context(context, VAL_WORD_CANON(word), FALSE); + if (index == 0) + return R_BLANK; + + Init_Any_Word_Bound( + D_OUT, + VAL_TYPE(word), + VAL_WORD_SPELLING(word), + context, + index + ); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(typeq) -/* -***********************************************************************/ +// +// resolve: native [ +// +// {Copy context by setting values in the target from those in the source.} +// +// target [any-context!] "(modified)" +// source [any-context!] +// /only +// "Only specific words (exports) or new words in target" +// from [block! integer!] +// "(index to tail)" +// /all +// "Set all words, even those in the target that already have a value" +// /extend +// "Add source words to the target if necessary" +// ] +// +REBNATIVE(resolve) { - REBCNT type = VAL_TYPE(D_ARG(1)); - - if (D_REF(2)) // /word - Init_Word(D_RET, type+1); - else - Set_Datatype(D_RET, type); - return R_RET; + INCLUDE_PARAMS_OF_RESOLVE; + + if (IS_INTEGER(ARG(from))) { + // check range and sign + Int32s(ARG(from), 1); + } + + UNUSED(REF(only)); // handled by noticing if ARG(from) is void + Resolve_Context( + VAL_CONTEXT(ARG(target)), + VAL_CONTEXT(ARG(source)), + ARG(from), + REF(all), + REF(extend) + ); + + Move_Value(D_OUT, ARG(target)); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(unset) -/* -***********************************************************************/ +// +// set: native [ +// +// {Sets a word, path, or block of words and paths to specified value(s).} +// +// return: [ any-value!] +// {Will be the values set to, or void if any set values are void} +// target [blank! any-word! any-path! block!] +// {Word or path, or block of words and paths (blanks are no-ops)} +// value [ any-value!] +// "Value or block of values" +// /only +// {If target and value are blocks, set each item to the same value} +// /opt +// {Treat void values as unsetting the target instead of an error} +// /some +// {Blank values (or values past end of block) are not set.} +// /lookback +// {Function uses evaluator lookahead to "look back" (see ENFIX)} +// ] +// +REBNATIVE(set) +// +// !!! Note that r3-legacy has a SET which overrides this one at the moment +// +// Blocks are supported as: +// +// >> set [a b] [1 2] +// >> print a +// 1 +// >> print b +// 2 +// +// !!! Should the /LOOKBACK refinement be called /ENFIX? { - REBVAL *word = D_ARG(1); - REBVAL *value; - - if (IS_WORD(word)) { - if (VAL_WORD_FRAME(word)) { - Protected(word); - value = Get_Var(word); - SET_UNSET(value); - } - } else { - for (word = VAL_BLK_DATA(word); NOT_END(word); word++) { - if (IS_WORD(word) && VAL_WORD_FRAME(word)) { - Protected(word); - value = Get_Var(word); - SET_UNSET(value); - } - } - } - return R_RET; + INCLUDE_PARAMS_OF_SET; + + const RELVAL *value; + REBSPC *value_specifier; + + const RELVAL *target; + REBSPC *target_specifier; + + REBOOL only; + if (IS_BLOCK(ARG(target))) { + // + // R3-Alpha and Red let you write `set [a b] 10`, since the thing + // you were setting to was not a block, would assume you meant to set + // all the values to that. BUT since you can set things to blocks, + // this has a bad characteristic of `set [a b] [10]` being treated + // differently, which can bite you if you `set [a b] value` for some + // generic value. + // + if (IS_BLOCK(ARG(value)) && NOT(REF(only))) { + // + // There is no need to check values for voidness in this case, + // since arrays cannot contain voids. + // + value = VAL_ARRAY_AT(ARG(value)); + value_specifier = VAL_SPECIFIER(ARG(value)); + only = FALSE; + } + else { + if (IS_VOID(ARG(value)) && NOT(REF(opt))) + fail (Error_No_Value(ARG(value))); + + value = ARG(value); + value_specifier = SPECIFIED; + only = TRUE; + } + + target = VAL_ARRAY_AT(ARG(target)); + target_specifier = VAL_SPECIFIER(ARG(target)); + } + else { + // Use the fact that D_CELL is implicitly terminated so that the + // loop below can share code between `set [a b] x` and `set a x`, by + // incrementing the target pointer and hitting an END marker + // + assert( + ANY_WORD(ARG(target)) + || ANY_PATH(ARG(target)) + || IS_BLANK(ARG(target)) + ); + + Move_Value(D_CELL, ARG(target)); + target = D_CELL; + target_specifier = SPECIFIED; + + if (IS_VOID(ARG(value)) && NOT(REF(opt))) + fail (Error_No_Value(ARG(value))); + + value = ARG(value); + value_specifier = SPECIFIED; + only = TRUE; + } + + DECLARE_LOCAL (get_path_hack); // runs prep code, don't put inside loop + + for ( + ; + NOT_END(target); + ++target, only || IS_END(value) ? NOOP : (++value, NOOP) + ){ + if (REF(some)) { + if (IS_END(value)) + break; // won't be setting any further values + if (IS_BLANK(value)) + continue; + } + + if (IS_BAR(target)) { + if (NOT_END(value) || NOT(IS_BAR(value))) + fail ("BAR! can only line up with other BAR! in SET"); + } + else if (IS_BLANK(target)) { + // + // Just skip it + } + else if (ANY_WORD(target)) { + if (REF(lookback) && NOT(IS_FUNCTION(ARG(value)))) + fail ("Attempt to SET/LOOKBACK on a non-function"); + + REBVAL *var = Sink_Var_May_Fail(target, target_specifier); + Derelativize( + var, + IS_END(value) ? BLANK_VALUE : value, + value_specifier + ); + if (REF(lookback)) + SET_VAL_FLAG(var, VALUE_FLAG_ENFIXED); + } + else if (ANY_PATH(target)) { + // + // Make sure the path does not contain any GROUP!s, because that + // would trigger evaluations. SET does sound like it has a + // side effect (unlike GET), but you don't expect the side effect + // to do things like PRINT, which arbitrary code can do. + // + RELVAL *temp = VAL_ARRAY_AT(target); + for (; NOT_END(temp); ++temp) + if (IS_GROUP(temp)) + fail ("GROUP! can't be in paths with SET"); + + // !!! For starters, just the word form is supported for lookback. + // Though you can't dispatch a lookback from a path, you should be + // able to set a word in a context to one. + // + if (REF(lookback)) + fail ("Cannot currently SET/LOOKBACK on a PATH!"); + + DECLARE_LOCAL (specific); + if (IS_END(value)) + Init_Blank(specific); + else + Derelativize(specific, value, value_specifier); + + // Currently we have to tweak the bits of the path so that it's a + // GET-PATH!, since Do_Path is sensitive to the path type, and we + // want all to act the same. + // + Derelativize(get_path_hack, target, target_specifier); + VAL_SET_TYPE_BITS(get_path_hack, REB_GET_PATH); + + if ( + Do_Path_Throws_Core( + D_OUT, + NULL, + get_path_hack, + SPECIFIED, + specific + ) + ){ + fail (Error_No_Catch_For_Throw(D_OUT)); + } + + // If not a throw, then there is no result out of a setting a path + } + else + fail (Error_Invalid_Arg_Core(target, target_specifier)); + } + + Move_Value(D_OUT, ARG(value)); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(valueq) -/* -***********************************************************************/ +// +// type-of: native [ +// +// "Returns the datatype of a value." +// +// value [ any-value!] +// ] +// +REBNATIVE(type_of) { - REBVAL *value = D_ARG(1); + INCLUDE_PARAMS_OF_TYPE_OF; + + enum Reb_Kind kind = VAL_TYPE(ARG(value)); + if (kind == REB_MAX_VOID) + return R_BLANK; - if (ANY_WORD(value) && !(value = Get_Var_No_Trap(value))) return R_FALSE; - if (IS_UNSET(value)) return R_FALSE; - return R_TRUE; + Val_Init_Datatype(D_OUT, kind); + return R_OUT; } -//** SERIES ************************************************************ -static int Do_Ordinal(REBVAL *ds, REBINT n) +// +// unset: native [ +// +// {Unsets the value of a word (in its current context.)} +// +// return: [] +// target [any-word! block!] +// "Word or block of words" +// ] +// +REBNATIVE(unset) { - // Is only valid when returned from ACTION function itself. - REBACT action = Value_Dispatch[VAL_TYPE(D_ARG(1))]; - DS_PUSH_INTEGER(n); - //DSF_FUNC(ds) // needs to be set to PICK action! - return action(ds, A_PICK); // returns R_RET and other cases -} + INCLUDE_PARAMS_OF_UNSET; -/*********************************************************************** -** -*/ REBNATIVE(first) -/* -***********************************************************************/ -{ - return Do_Ordinal(ds, 1); -} + REBVAL *target = ARG(target); + if (ANY_WORD(target)) { + REBVAL *var = Sink_Var_May_Fail(target, SPECIFIED); + Init_Void(var); + return R_VOID; + } -/*********************************************************************** -** -*/ REBNATIVE(second) -/* -***********************************************************************/ -{ - return Do_Ordinal(ds, 2); -} + assert(IS_BLOCK(target)); + RELVAL *word; + for (word = VAL_ARRAY_AT(target); NOT_END(word); ++word) { + if (!ANY_WORD(word)) + fail (Error_Invalid_Arg_Core(word, VAL_SPECIFIER(target))); -/*********************************************************************** -** -*/ REBNATIVE(third) -/* -***********************************************************************/ -{ - return Do_Ordinal(ds, 3); + REBVAL *var = Sink_Var_May_Fail(word, VAL_SPECIFIER(target)); + Init_Void(var); + } + + return R_VOID; } -/*********************************************************************** -** -*/ REBNATIVE(fourth) -/* -***********************************************************************/ +// +// lookback?: native [ +// +// {TRUE if looks up to a function and gets first argument before the call} +// +// source [any-word! any-path!] +// ] +// +REBNATIVE(lookback_q) { - return Do_Ordinal(ds, 4); -} + INCLUDE_PARAMS_OF_LOOKBACK_Q; + REBVAL *source = ARG(source); -/*********************************************************************** -** -*/ REBNATIVE(fifth) -/* -***********************************************************************/ -{ - return Do_Ordinal(ds, 5); -} + if (ANY_WORD(source)) { + const REBVAL *var = Get_Var_Core( + source, SPECIFIED, GETVAR_READ_ONLY // may fail() + ); + if (!IS_FUNCTION(var)) + return R_FALSE; -/*********************************************************************** -** -*/ REBNATIVE(sixth) -/* -***********************************************************************/ -{ - return Do_Ordinal(ds, 6); + return R_FROM_BOOL(GET_VAL_FLAG(var, VALUE_FLAG_ENFIXED)); + } + else { + assert(ANY_PATH(source)); + + // Not implemented yet... + + fail ("LOOKBACK? testing is not currently implemented on PATH!"); + } } -/*********************************************************************** -** -*/ REBNATIVE(seventh) -/* -***********************************************************************/ +// +// semiquoted?: native [ +// +// {Discern if a function parameter came from an "active" evaluation.} +// +// parameter [word!] +// ] +// +REBNATIVE(semiquoted_q) +// +// This operation is somewhat dodgy. So even though the flag is carried by +// all values, and could be generalized in the system somehow to query on +// anything--we don't. It's strictly for function parameters, and +// even then it should be restricted to functions that have labeled +// themselves as absolutely needing to do this for ergonomic reasons. { - return Do_Ordinal(ds, 7); + INCLUDE_PARAMS_OF_SEMIQUOTED_Q; + + // !!! TBD: Enforce this is a function parameter (specific binding branch + // makes the test different, and easier) + + const REBVAL *var = Get_Var_Core( // may fail + ARG(parameter), SPECIFIED, GETVAR_READ_ONLY + ); + return R_FROM_BOOL(GET_VAL_FLAG(var, VALUE_FLAG_UNEVALUATED)); } -/*********************************************************************** -** -*/ REBNATIVE(eighth) -/* -***********************************************************************/ +// +// semiquote: native [ +// +// {Marks a function argument to be treated as if it had been literal source} +// +// value [any-value!] +// ] +// +REBNATIVE(semiquote) { - return Do_Ordinal(ds, 8); + INCLUDE_PARAMS_OF_SEMIQUOTE; + + Move_Value(D_OUT, ARG(value)); + + // We cannot set the VALUE_FLAG_UNEVALUATED bit here and make it stick, + // because the bit would just get cleared off by Do_Core when the + // function finished. So ask the evaluator to set the bit for us. + + return R_OUT_UNEVALUATED; } -/*********************************************************************** -** -*/ REBNATIVE(ninth) -/* -***********************************************************************/ +// +// as: native [ +// +// {Aliases the underlying data of one series to act as another of same class} +// +// type [datatype!] +// value [any-series! any-word!] +// ] +// +REBNATIVE(as) { - return Do_Ordinal(ds, 9); + INCLUDE_PARAMS_OF_AS; + + enum Reb_Kind kind = VAL_TYPE_KIND(ARG(type)); + REBVAL *value = ARG(value); + + switch (kind) { + case REB_BLOCK: + case REB_GROUP: + case REB_PATH: + case REB_LIT_PATH: + case REB_GET_PATH: + if (!ANY_ARRAY(value)) + fail (Error_Bad_Cast_Raw(value, ARG(type))); + break; + + case REB_STRING: + case REB_TAG: + case REB_FILE: + case REB_URL: + if (!ANY_BINSTR(value) || IS_BINARY(value)) + fail (Error_Bad_Cast_Raw(value, ARG(type))); + break; + + case REB_WORD: + case REB_GET_WORD: + case REB_SET_WORD: + case REB_LIT_WORD: + case REB_ISSUE: + case REB_REFINEMENT: + if (!ANY_WORD(value)) + fail (value); + break; + + default: + fail (Error_Bad_Cast_Raw(value, ARG(type))); // all applicable types should be handled above + } + + VAL_SET_TYPE_BITS(value, kind); + Move_Value(D_OUT, value); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(tenth) -/* -***********************************************************************/ +// +// aliases?: native [ +// +// {Return whether or not the underlying data of one value aliases another} +// +// value1 [any-series!] +// value2 [any-series!] +// ] +// +REBNATIVE(aliases_q) { - return Do_Ordinal(ds, 10); + INCLUDE_PARAMS_OF_ALIASES_Q; + + if (VAL_SERIES(ARG(value1)) == VAL_SERIES(ARG(value2))) + return R_TRUE; + + return R_FALSE; } -/*********************************************************************** -** -*/ REBNATIVE(last) -/* -***********************************************************************/ +// Common routine for both SET? and UNSET? Note that location is modified +// into a GET-PATH! value if it is originally a path (okay for the natives, +// since they can modify values in their frames.) +// +inline static REBOOL Is_Set_Modifies(REBVAL *location) { - REBVAL *val = D_ARG(1); - REBACT action; - REBCNT t; - - action = Value_Dispatch[VAL_TYPE(val)]; - if (ANY_SERIES(val)) { - t = VAL_TAIL(val); - VAL_INDEX(val) = 0; - } - else if (IS_TUPLE(val)) t = VAL_TUPLE_LEN(val); - else if (IS_GOB(val)) { - t = GOB_PANE(VAL_GOB(val)) ? GOB_TAIL(VAL_GOB(val)) : 0; - VAL_GOB_INDEX(val) = 0; - } - else t = 0; // let the action throw the error - DS_PUSH_INTEGER(t); - return action(ds, A_PICK); + if (ANY_WORD(location)) { + // + // Note this will fail if unbound + // + const RELVAL *var = Get_Opt_Var_May_Fail(location, SPECIFIED); + if (IS_VOID(var)) + return FALSE; + } + else { + assert(ANY_PATH(location)); + + #if !defined(NDEBUG) + REBDSP dsp_orig = DSP; + #endif + + // !!! We shouldn't be evaluating but currently the path machinery + // doesn't "turn off" GROUP! evaluations for GET-PATH!. + // + VAL_SET_TYPE_BITS(location, REB_GET_PATH); + + DECLARE_LOCAL (temp); + if (Do_Path_Throws_Core( + temp, NULL, location, VAL_SPECIFIER(location), NULL + )) { + // !!! Shouldn't be evaluating, much less throwing--so fail + // + fail (Error_No_Catch_For_Throw(temp)); + } + + // We did not pass in a symbol ID + // + assert(DSP == dsp_orig); + if (IS_VOID(temp)) + return FALSE; + } + + return TRUE; } -/*********************************************************************** -** -*/ REBNATIVE(first_add) -/* -***********************************************************************/ +// +// set?: native/body [ +// +// "Whether a bound word or path is set (!!! shouldn't eval GROUP!s)" +// +// location [any-word! any-path!] +// ][ +// any-value? get/opt location +// ] +// +REBNATIVE(set_q) { - REBVAL *value; - REBCNT index; - REBCNT tail; - - value = Get_Var(D_ARG(1)); - - if (ANY_SERIES(value)) { - tail = VAL_TAIL(value); - } - else if (IS_GOB(value)) { - tail = GOB_PANE(VAL_GOB(value)) ? GOB_TAIL(VAL_GOB(value)) : 0; - } - else - Trap_Arg(D_ARG(1)); // !! need better msg - - *D_ARG(1) = *value; - index = VAL_INDEX(value); // same for VAL_GOB_INDEX - if (index < tail) VAL_INDEX(value) = index + 1; - return Do_Ordinal(ds, 1); + INCLUDE_PARAMS_OF_SET_Q; + + return R_FROM_BOOL(Is_Set_Modifies(ARG(location))); } -/*********************************************************************** -** -*/ REBNATIVE(_add_add) -/* -** i: ++ int -** s: ++ series -** -***********************************************************************/ +// +// unset?: native/body [ +// +// "Whether a bound word or path is unset (!!! shouldn't eval GROUP!s)" +// +// location [any-word! any-path!] +// ][ +// void? get/opt location +// ] +// +REBNATIVE(unset_q) { - REBVAL *value; - REBCNT n; - REBVAL *word = D_ARG(1); - - value = Get_Var_Safe(word); // throws error - - *D_RET = *value; - - if (IS_INTEGER(value)) { - VAL_INT64(value)++; - } - else if (ANY_SERIES(value)) { - n = VAL_INDEX(value); - if (n < VAL_TAIL(value)) VAL_INDEX(value) = n + 1; - } - else if (IS_DECIMAL(value)) { - VAL_DECIMAL(value) += 1.0; - } - else - Trap_Arg(D_ARG(1)); - - return R_RET; + INCLUDE_PARAMS_OF_UNSET_Q; + + return R_FROM_BOOL(NOT(Is_Set_Modifies(ARG(location)))); } -/*********************************************************************** -** -*/ REBNATIVE(__) -/* -** i: -- int -** s: -- series -** -***********************************************************************/ +// +// true?: native/body [ +// +// "Returns true if a value can be used as true." +// +// value [any-value!] ; Note: No [ any-value!] - void must fail +// ][ +// not not :val +// ] +// +REBNATIVE(true_q) { - REBVAL *value; - REBCNT n; - REBVAL *word = D_ARG(1); - - value = Get_Var_Safe(word); // throws error - - *D_RET = *value; - - if (IS_INTEGER(value)) { - VAL_INT64(value)--; - } - else if (ANY_SERIES(value)) { - n = VAL_INDEX(value); - if (n > 0) VAL_INDEX(value) = n - 1; - } - else if (IS_DECIMAL(value)) { - VAL_DECIMAL(value) -= 1.0; - } - else - Trap_Arg(D_ARG(1)); - - return R_RET; + INCLUDE_PARAMS_OF_TRUE_Q; + + return R_FROM_BOOL(IS_CONDITIONAL_TRUE(ARG(value))); } -/*********************************************************************** -** -*/ REBNATIVE(dump) -/* -***********************************************************************/ +// +// false?: native/body [ +// +// "Returns false if a value is either LOGIC! false or a NONE!." +// +// value [any-value!] ; Note: No [ any-value!] - void must fail. +// ][ +// either any [ +// blank? :value +// :value = false +// ][ +// true +// ][ +// false +// ] +// ] +// +REBNATIVE(false_q) { -#ifdef _DEBUG - REBVAL *arg = D_ARG(1); - - if (ANY_SERIES(arg)) - Dump_Series(VAL_SERIES(arg), "=>"); - else - Dump_Values(arg, 1); -#endif - return R_ARG1; + INCLUDE_PARAMS_OF_FALSE_Q; + + return R_FROM_BOOL(IS_CONDITIONAL_FALSE(ARG(value))); } -#ifdef not_fast_enough -/*********************************************************************** -** -**/ REBNATIVE(replace_all) -/* -***********************************************************************/ +// +// quote: native/body [ +// +// "Returns the value passed to it without evaluation." +// +// return: [any-value!] +// :value [any-value!] +// ][ +// if bar? :value [ +// fail "Cannot quote expression barrier" ;-- not actual error +// ] +// :value ;-- actually also sets unevaluated bit, how could a user do so? +// ] +// +REBNATIVE(quote) { -#define BIT_CHAR(c) (((REBU64)1) << (c % 64)) - REBVAL *a1 = D_ARG(1); - REBVAL *a2 = D_ARG(2); - REBSER *ser = VAL_SERIES(a1); - REBCNT tail = ser->tail; - REBVAL *pats = VAL_BLK(a2); - REBCNT tail2 = VAL_TAIL(a2); - REBSER *outs; - REBUNI chr; - REBU64 chash = 0; - REBCNT i, n; - REBVAL *val; - - // Check substitution strings, and compute hash and size diff. - n = 0; - for (val = VAL_BLK(a2); NOT_END(val); val += 2) { - if (VAL_TYPE(a1) != VAL_TYPE(val)) Trap0(RE_NOT_SAME_TYPE); // !! would be good to show it - if (IS_END(val+1)) Trap0(RE_MISSING_ARG); - if (VAL_TYPE(a1) != VAL_TYPE(val+1)) Trap0(RE_NOT_SAME_TYPE); // !! would be good to show it - chr = GET_ANY_CHAR(VAL_SERIES(val), 0); - chash |= BIT_CHAR(chr); - n += 3 * (VAL_LEN(val+1) - VAL_LEN(val)); // assume it occurs three times - } - - outs = Make_Unicode(VAL_LEN(a1) + n); - - for (i = VAL_INDEX(a1); i < tail; i++) { - chr = GET_ANY_CHAR(ser, i); - val = 0; // default for check below - if (BIT_CHAR(chr) & chash) { - for (val = VAL_BLK(a2); NOT_END(val); val += 2) { - if (NOT_FOUND != Find_Str_Str(ser, 0, i, tail, 0, VAL_SERIES(val), 0, VAL_TAIL(val), AM_FIND_MATCH)) { - Insert_String(outs, SERIES_TAIL(outs), VAL_SERIES(val+1), 0, VAL_TAIL(val+1), 0); - i += VAL_TAIL(val) - 1; - break; - } - } - if (IS_END(val)) val = 0; // for test below - } - // If not found, just copy the character: - if (!val) { - n = SERIES_TAIL(outs); - EXPAND_SERIES_TAIL(outs, 1); - *UNI_SKIP(outs, n) = chr; - } - } - UNI_TERM(outs); // Because we don't do it for single chars. - Set_String(D_RET, outs); - return R_RET; + INCLUDE_PARAMS_OF_QUOTE; + + // Generally speaking, a hard quoting operation is permitted to quote + // BAR! if it really wants to. The general advice is to fail in this + // case, but it is not enforced. + // + if (IS_BAR(ARG(value))) + fail (Error_Expression_Barrier_Raw()); + + Move_Value(D_OUT, ARG(value)); + + // We cannot set the VALUE_FLAG_UNEVALUATED bit here and make it stick, + // because the bit would just get cleared off by Do_Core when the + // function finished. Ask evaluator to add the bit for us. + + return R_OUT_UNEVALUATED; } -#endif -/*********************************************************************** -** -*/ static REBGOB *Map_Gob_Inner(REBGOB *gob, REBXYF *offset) -/* -** Map a higher level gob coordinate to a lower level. -** Returns GOB and sets new offset pair. -** -***********************************************************************/ +// +// void?: native/body [ +// +// "Tells you if the argument is not a value (e.g. `void? do []` is TRUE)" +// +// value [ any-value!] +// ][ +// blank? type-of :value +// ] +// +REBNATIVE(void_q) { - REBD32 xo = offset->x; - REBD32 yo = offset->y; - REBINT n; - REBINT len; - REBGOB **gop; - REBD32 x = 0; - REBD32 y = 0; - REBINT max_depth = 1000; // avoid infinite loops - - while (GOB_PANE(gob) && (max_depth-- > 0)) { - len = GOB_TAIL(gob); - gop = GOB_HEAD(gob) + len - 1; - for (n = 0; n < len; n++, gop--) { - if ( - (xo >= x + GOB_X(*gop)) && - (xo < x + GOB_X(*gop) + GOB_W(*gop)) && - (yo >= y + GOB_Y(*gop)) && - (yo < y + GOB_Y(*gop) + GOB_H(*gop)) - ){ - x += GOB_X(*gop); - y += GOB_Y(*gop); - gob = *gop; - break; - } - } - if (n >= len) break; // not found - } - - offset->x -= x; - offset->y -= y; - - return gob; + INCLUDE_PARAMS_OF_VOID_Q; + + return R_FROM_BOOL(IS_VOID(ARG(value))); } -/*********************************************************************** -** -*/ REBNATIVE(map_event) -/* -***********************************************************************/ +// +// void: native/body [ +// +// "Function returning no result (alternative for `()` or `do []`)" +// +// return: [] ;-- how to say no-value! ? +// ][ +// ] +// +REBNATIVE(void) { - REBVAL *val = D_ARG(1); - REBGOB *gob = VAL_EVENT_SER(val); - REBXYF xy; - - if (gob && GET_FLAG(VAL_EVENT_FLAGS(val), EVF_HAS_XY)) { - xy.x = (REBD32)VAL_EVENT_X(val); - xy.y = (REBD32)VAL_EVENT_Y(val); - VAL_EVENT_SER(val) = Map_Gob_Inner(gob, &xy); - SET_EVENT_XY(val, ROUND_TO_INT(xy.x), ROUND_TO_INT(xy.y)); - } - return R_ARG1; + UNUSED(frame_); + return R_VOID; } -/*********************************************************************** -** -*/ static void Return_Gob_Pair(REBVAL *ds, REBGOB *gob, REBD32 x, REBD32 y) -/* -***********************************************************************/ +// +// nothing?: native/body [ +// +// "Returns TRUE if argument is either a NONE! or no value is passed in" +// +// value [ any-value!] +// ][ +// any [ +// void? :value +// blank? :value +// ] +// ] +// +REBNATIVE(nothing_q) { - REBSER *blk; - REBVAL *val; - - blk = Make_Block(2); - Set_Series(REB_BLOCK, ds, blk); - val = Append_Value(blk); - SET_GOB(val, gob); - val = Append_Value(blk); - VAL_SET(val, REB_PAIR); - VAL_PAIR_X(val) = x; - VAL_PAIR_Y(val) = y; + INCLUDE_PARAMS_OF_NOTHING_Q; + + return R_FROM_BOOL( + LOGICAL(IS_BLANK(ARG(value)) || IS_VOID(ARG(value))) + ); } -/*********************************************************************** -** -*/ REBNATIVE(map_gob_offset) -/* -***********************************************************************/ +// +// something?: native/body [ +// +// "Returns TRUE if a value is passed in and it isn't a NONE!" +// +// value [ any-value!] +// ][ +// all [ +// any-value? :value +// not blank? value +// ] +// ] +// +REBNATIVE(something_q) { - REBGOB *gob = VAL_GOB(D_ARG(1)); - REBD32 xo = VAL_PAIR_X(D_ARG(2)); - REBD32 yo = VAL_PAIR_Y(D_ARG(2)); - - if (D_REF(3)) { // reverse - REBINT max_depth = 1000; // avoid infinite loops - while (GOB_PARENT(gob) && (max_depth-- > 0) && - !GET_GOB_FLAG(gob, GOBF_WINDOW)){ - xo += GOB_X(gob); - yo += GOB_Y(gob); - gob = GOB_PARENT(gob); - } - } - else { - REBXYF xy; - xy.x = VAL_PAIR_X(D_ARG(2)); - xy.y = VAL_PAIR_Y(D_ARG(2)); - gob = Map_Gob_Inner(gob, &xy); - xo = xy.x; - yo = xy.y; - } - - Return_Gob_Pair(ds, gob, xo, yo); - - return R_RET; + INCLUDE_PARAMS_OF_SOMETHING_Q; + + return R_FROM_BOOL( + NOT(IS_BLANK(ARG(value)) || IS_VOID(ARG(value))) + ); } diff --git a/src/core/n-do.c b/src/core/n-do.c new file mode 100644 index 0000000000..664319ea1e --- /dev/null +++ b/src/core/n-do.c @@ -0,0 +1,539 @@ +// +// File: %n-do.c +// Summary: "native functions for DO, EVAL, APPLY" +// Section: natives +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Ren-C's philosophy of DO is that the argument to it represents a place to +// find source code. Hence `DO 3` does not evaluate to the number 3, any +// more than `DO "print hello"` would evaluate to `"print hello"`. If a +// generalized evaluator is needed, use the special-purpose function EVAL. +// +// Note that although the code for running blocks and frames is implemented +// here as C, the handler for processing STRING!, FILE!, TAG!, URL!, etc. is +// dispatched out to some Rebol code. See `system/intrinsic/do*`. +// + +#include "sys-core.h" + + +// +// eval: native [ +// +// {(Special) Process received value *inline* as the evaluator loop would.} +// +// value [ any-value!] +// {BLOCK! passes-thru, FUNCTION! runs, SET-WORD! assigns...} +// /only +// {Suppress evaluation on any ensuing arguments value consumes} +// ] +// +REBNATIVE(eval) +{ + INCLUDE_PARAMS_OF_EVAL; + + REBFRM *f = frame_; // implicit parameter to every dispatcher/native + + Move_Value(&f->cell, ARG(value)); + + // Save the prefetched f->value for what would be the usual next + // item (including if it was an END marker) into f->pending. + // Then make f->value the address of the eval result. + // + // Since the evaluation result is a REBVAL and not a RELVAL, it + // is specific. This means the `f->specifier` (which can only + // specify values from the source array) won't ever be applied + // to it, since it only comes into play for IS_RELATIVE values. + // + f->pending = f->value; + SET_FRAME_VALUE(f, &f->cell); // SPECIFIED + f->eval_type = VAL_TYPE(f->value); + + // The f->gotten (if any) was the fetch for the f->value we just + // put in pending...not the f->value we just set. Not only is + // it more expensive to hold onto that cache than to lose it, + // but an eval can do anything...so the f->gotten might wind + // up being completely different after the eval. So forget it. + // + f->gotten = END; + + return REF(only) ? R_REEVALUATE_ONLY : R_REEVALUATE; +} + + +// +// do: native [ +// +// {Evaluates a block of source code (directly or fetched according to type)} +// +// return: [ any-value!] +// source [ +// ;-- should DO accept an optional argument (chaining?) +// blank! ;-- same question... necessary, or not? +// block! ;-- source code in block form +// string! ;-- source code in text form +// binary! ;-- treated as UTF-8 +// url! ;-- load code from URL via protocol +// file! ;-- load code from file on local disk +// tag! ;-- proposed as module library tag name, hacked as demo +// error! ;-- should use FAIL instead +// function! ;-- will only run arity 0 functions (avoids DO variadic) +// frame! ;-- acts like APPLY (voids are optionals, not unspecialized) +// ] +// /args +// {If value is a script, this will set its system/script/args} +// arg +// "Args passed to a script (normally a string)" +// /next +// {Do next expression only, return it, update block variable} +// var [any-word! blank!] +// "If not blank, then a variable updated with new block position" +// /only +// "Don't catch QUIT (default behavior for BLOCK!)" +// ] +// +REBNATIVE(do) +{ + INCLUDE_PARAMS_OF_DO; + + REBVAL *source = ARG(source); + + switch (VAL_TYPE(source)) { + case REB_MAX_VOID: + // useful for `do if ...` types of scenarios + return R_VOID; + + case REB_BLANK: + // useful for `do all ...` types of scenarios + return R_BLANK; + + case REB_BLOCK: + case REB_GROUP: + if (REF(next)) { + REBIXO indexor = DO_NEXT_MAY_THROW( + D_OUT, + VAL_ARRAY(source), + VAL_INDEX(source), + VAL_SPECIFIER(source) + ); + + if (indexor == THROWN_FLAG) { + // + // the throw should make the value irrelevant, but if caught + // then have it indicate the start of the thrown expression + // + if (!IS_BLANK(ARG(var))) { + Move_Value( + Sink_Var_May_Fail(ARG(var), SPECIFIED), + source + ); + } + + return R_OUT_IS_THROWN; + } + + if (!IS_BLANK(ARG(var))) { + // + // "continuation" of block...turn END_FLAG into the end so it + // can test TAIL? as true to know the evaluation finished. + // + // !!! Is there merit to setting to NONE! instead? Easier to + // test and similar to FIND. On the downside, "lossy" in + // that after the DOs are finished the var can't be used to + // recover the series again...you'd have to save it. + // + if (indexor == END_FLAG) + VAL_INDEX(source) = VAL_LEN_HEAD(source); + else + VAL_INDEX(source) = cast(REBCNT, indexor); + + Move_Value( + Sink_Var_May_Fail(ARG(var), SPECIFIED), + ARG(source) + ); + } + + return R_OUT; + } + + if (Do_Any_Array_At_Throws(D_OUT, source)) + return R_OUT_IS_THROWN; + + return R_OUT; + + case REB_BINARY: + case REB_STRING: + case REB_URL: + case REB_FILE: + case REB_TAG: { + // + // See code called in system/intrinsic/do* + // + const REBOOL fully = TRUE; // error if not all arguments consumed + if (Apply_Only_Throws( + D_OUT, + fully, + Sys_Func(SYS_CTX_DO_P), + source, + REF(args) ? TRUE_VALUE : FALSE_VALUE, + REF(args) ? ARG(arg) : BLANK_VALUE, // can't put void in block + REF(next) ? TRUE_VALUE : FALSE_VALUE, + REF(next) ? ARG(var) : BLANK_VALUE, // can't put void in block + REF(only) ? TRUE_VALUE : FALSE_VALUE, + END + )) { + return R_OUT_IS_THROWN; + } + return R_OUT; } + + case REB_ERROR: + // + // FAIL is the preferred operation for triggering errors, as it has + // a natural behavior for blocks passed to construct readable messages + // and "FAIL X" more clearly communicates a failure than "DO X" + // does. However DO of an ERROR! would have to raise an error + // anyway, so it might as well raise the one it is given...and this + // allows the more complex logic of FAIL to be written in Rebol code. + // + fail (VAL_CONTEXT(source)); + + case REB_FUNCTION: { + // + // Ren-C will only run arity 0 functions from DO, otherwise EVAL + // must be used. Look for the first non-local parameter to tell. + // + REBVAL *param = FUNC_PARAMS_HEAD(VAL_FUNC(source)); + while ( + NOT_END(param) + && (VAL_PARAM_CLASS(param) == PARAM_CLASS_LOCAL) + ) { + ++param; + } + if (NOT_END(param)) + fail (Error_Use_Eval_For_Eval_Raw()); + + if (Eval_Value_Throws(D_OUT, source)) + return R_OUT_IS_THROWN; + return R_OUT; + } + + case REB_FRAME: { + REBCTX *c = VAL_CONTEXT(source); + + // To allow efficient applications, this does not make a copy of the + // FRAME!. This means the frame must not be currently running + // on the stack. + // + // !!! It may come to pass that a trick lets you reuse a stack context + // and unwind it as a kind of tail recursion to reuse it. But one would + // not want that strange voodoo to be what DO does on a FRAME!, + // it would have to be another operation (REDO ?) + // + if (CTX_FRAME_IF_ON_STACK(c) != NULL) + fail (Error_Do_Running_Frame_Raw()); + + // Right now all stack based contexts are either running (stopped by + // the above) or expired (in which case their values are unavailable). + // + if (CTX_VARS_UNAVAILABLE(c)) + fail (Error_Do_Expired_Frame_Raw()); + + DECLARE_FRAME (f); + + // Apply_Frame_Core sets up most of the Reb_Frame, but expects these + // arguments to be filled in. + // + f->out = D_OUT; + f->gotten = CTX_FRAME_FUNC_VALUE(VAL_CONTEXT(source)); + f->original = f->phase = VAL_FUNC(f->gotten); + f->binding = VAL_BINDING(source); + + f->varlist = CTX_VARLIST(VAL_CONTEXT(source)); // need w/NULL def + SER(f->varlist)->misc.f = f; + + return Apply_Frame_Core(f, Canon(SYM___ANONYMOUS__), NULL); } + + default: + break; + } + + // Note: it is not possible to write a wrapper function in Rebol + // which can do what EVAL can do for types that consume arguments + // (like SET-WORD!, SET-PATH! and FUNCTION!). DO used to do this for + // functions only, EVAL generalizes it. + // + fail (Error_Use_Eval_For_Eval_Raw()); +} + + +// +// do-all: native [ +// +// {Execute a series of BAR!-separated statements with error/quit recovery.} +// +// return: [ any-value!] +// block [block!] +// ] +// +REBNATIVE(do_all) +// +// !!! The name of this construct is under review, as well as whether it +// should be a block-of-blocks or use BAR!. It was added to try and solve +// a problem, but then not used--however some variant of this feature is +// useful. +{ + INCLUDE_PARAMS_OF_DO_ALL; + + // Holds either an error value that is raised, or the thrown value. + // + DECLARE_LOCAL (arg_or_error); + SET_END(arg_or_error); + PUSH_GUARD_VALUE(arg_or_error); + + // If arg_or_error is not end, but thrown_name is an end, a throw tried + // to propagate, but was caught...but if thrown_name is an end and the + // arg_or_error is also not, it is an error which tried to propagate. + // + DECLARE_LOCAL (thrown_name); + SET_END(thrown_name); + PUSH_GUARD_VALUE(thrown_name); + + DECLARE_FRAME (f); + Push_Frame(f, ARG(block)); + + // The trap must be pushed *after* the frame has been pushed, so that + // when a fail() happens it won't pop the running frame. + // + struct Reb_State state; + REBCTX *error; + +repush: + PUSH_TRAP(&error, &state); + + // The first time through the following code 'error' will be NULL, but... + // `fail` can longjmp here, so 'error' won't be NULL *if* that happens! + + if (error) { + if (NOT_END(arg_or_error)) { // already a throw or fail pending! + DECLARE_LOCAL (arg1); + if (IS_END(thrown_name)) { + assert(IS_ERROR(arg_or_error)); + Move_Value(arg1, arg_or_error); + } + else { + CONVERT_NAME_TO_THROWN(thrown_name, arg_or_error); + Init_Error(arg1, Error_No_Catch_For_Throw(thrown_name)); + } + + DECLARE_LOCAL (arg2); + Init_Error(arg2, error); + + fail (Error_Multiple_Do_Errors_Raw(arg1, arg2)); + } + + f->eval_type = REB_0; // invariant of Do_Next_In_Frame + + assert(IS_END(thrown_name)); + Init_Error(arg_or_error, error); + + while (NOT_END(f->value) && NOT(IS_BAR(f->value))) + Fetch_Next_In_Frame(f); + + goto repush; + } + + Init_Void(D_OUT); // default return result of DO-ALL [] + + while (NOT_END(f->value)) { + if (IS_BAR(f->value)) { + // + // BAR! is handled explicitly, because you might have f->value as + // the BAR! in `| asdf`, call into the evaluator and get an error, + // yet then come back and still have f->value positioned at the + // BAR!. This comes from how child frames and optimizations work. + // Hence it's not easy to know where to skip forward to in case + // of an error. + // + // !!! Review if the invariant of Do_Next_In_Frame_Throws() + // should be changed. So far, this is the only routine affected, + // because no other functions try and "resume" a throwing/failing + // frame--as that's not generically possible unless you skip to + // the next BAR!, as this routine does. + // + Init_Void(D_OUT); + Fetch_Next_In_Frame(f); + continue; + } + + if (Do_Next_In_Frame_Throws(D_OUT, f)) { + if (NOT_END(arg_or_error)) { // already a throw or fail pending! + DECLARE_LOCAL (arg1); + if (IS_END(thrown_name)) { + assert(IS_ERROR(arg_or_error)); + Move_Value(arg1, arg_or_error); + } + else { + CONVERT_NAME_TO_THROWN(thrown_name, arg_or_error); + Init_Error(arg1, Error_No_Catch_For_Throw(thrown_name)); + } + + DECLARE_LOCAL (arg2); + Init_Error(arg2, Error_No_Catch_For_Throw(D_OUT)); + + // We're still inside the pushed trap for this throw. Have + // to drop the trap to avoid transmitting the error to the + // `if (error)` longjmp branch above! + // + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + + fail (Error_Multiple_Do_Errors_Raw(arg1, arg2)); + } + + CATCH_THROWN(arg_or_error, D_OUT); + Move_Value(thrown_name, D_OUT); // THROWN cleared by CATCH_THROWN + + while (NOT_END(f->value) && NOT(IS_BAR(f->value))) + Fetch_Next_In_Frame(f); + } + } + + Drop_Frame(f); + + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + + DROP_GUARD_VALUE(thrown_name); // no GC (via Do_Core()) after this point + DROP_GUARD_VALUE(arg_or_error); + + if (IS_END(arg_or_error)) { // no throws or errors tried to propagate + assert(IS_END(thrown_name)); + return R_OUT; + } + + if (NOT_END(thrown_name)) { // throw tried propagating, re-throw it + Move_Value(D_OUT, thrown_name); + CONVERT_NAME_TO_THROWN(D_OUT, arg_or_error); + return R_OUT_IS_THROWN; + } + + assert(IS_ERROR(arg_or_error)); + fail (VAL_CONTEXT(arg_or_error)); // error tried propagating, re-raise it +} + + +// +// apply: native [ +// +// {Invoke a function with all required arguments specified.} +// +// return: [ any-value!] +// value [function! any-word! any-path!] +// {Function or specifying word (preserves word name for debug info)} +// def [block!] +// {Frame definition block (will be bound and evaluated)} +// ] +// +REBNATIVE(apply) +{ + INCLUDE_PARAMS_OF_APPLY; + + REBVAL *def = ARG(def); + + DECLARE_FRAME (f); + +#if !defined(NDEBUG) + RELVAL *first_def = VAL_ARRAY_AT(def); + + // !!! Because APPLY has changed, help warn legacy usages by alerting + // if the first element of the block is not a SET-WORD!. A BAR! can + // subvert the warning: `apply :foo [| comment {This is a new APPLY} ...]` + // + if (NOT_END(first_def)) { + if (!IS_SET_WORD(first_def) && !IS_BAR(first_def)) { + fail (Error_Apply_Has_Changed_Raw()); + } + } +#endif + + // We don't limit to taking a FUNCTION! value directly, because that loses + // the symbol (for debugging, errors, etc.) If caller passes a WORD! + // then we lookup the variable to get the function, but save the symbol. + // + REBSTR *name; + Get_If_Word_Or_Path_Arg(D_OUT, &name, ARG(value)); + if (name == NULL) + name = Canon(SYM___ANONYMOUS__); // Do_Core requires non-NULL symbol + + if (!IS_FUNCTION(D_OUT)) + fail (Error_Apply_Non_Function_Raw(ARG(value))); // for SPECIALIZE too + + f->gotten = D_OUT; + f->out = D_OUT; + + return Apply_Frame_Core(f, name, def); +} + + +// +// also: native [ +// +// {Returns the first value, but also evaluates the second.} +// +// return: [ any-value!] +// returned [ any-value!] +// evaluated [ any-value!] +// ] +// +REBNATIVE(also) +{ + INCLUDE_PARAMS_OF_ALSO; + + UNUSED(PAR(evaluated)); // not used (but was evaluated) + Move_Value(D_OUT, ARG(returned)); + return R_OUT; +} + + +// +// comment: native [ +// +// {Ignores the argument value.} +// +// return: [] +// {Nothing.} +// :value [block! any-string! binary! any-scalar!] +// "Literal value to be ignored." +// ] +// +REBNATIVE(comment) +{ + INCLUDE_PARAMS_OF_COMMENT; + + // All the work was already done (at the cost of setting up + // state that would just have to be torn down). + + UNUSED(PAR(value)); // avoid unused variable warning + return R_VOID; +} diff --git a/src/core/n-error.c b/src/core/n-error.c new file mode 100644 index 0000000000..a6122fcb8f --- /dev/null +++ b/src/core/n-error.c @@ -0,0 +1,197 @@ +// +// File: %n-error.c +// Summary: "native functions for raising and trapping errors" +// Section: natives +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Note that the mechanism by which errors are raised is based on longjmp(), +// and thus can interrupt stacks in progress. Trapping errors is only done +// by those levels of the stack that have done a PUSH_TRAP (as opposed to +// detecting thrown values, that is "cooperative" and "bubbles" up through +// every stack level in its return slot, with no longjmp()). +// + +#include "sys-core.h" + + +// +// trap: native [ +// +// {Tries to DO a block, trapping error as return value (if one is raised).} +// +// return: [ any-value!] +// block [block!] +// /with +// "Handle error case with code" +// handler [block! function!] +// "If FUNCTION!, spec allows [error [error!]]" +// /? +// "Instead of result or error, return LOGIC! of if a trap occurred" +// ] +// +REBNATIVE(trap) +{ + INCLUDE_PARAMS_OF_TRAP; // ? is renamed as "q" + + struct Reb_State state; + REBCTX *error; + + PUSH_TRAP(&error, &state); + + // The first time through the following code 'error' will be NULL, but... + // `fail` can longjmp here, so 'error' won't be NULL *if* that happens! + + if (error) { + if (REF(with)) { + REBVAL *handler = ARG(handler); + + if (IS_BLOCK(handler)) { + // There's no way to pass 'error' to a block (so just DO it) + if (Do_Any_Array_At_Throws(D_OUT, ARG(handler))) + return R_OUT_IS_THROWN; + + if (REF(q)) + return R_TRUE; + + return R_OUT; + } + else { + assert (IS_FUNCTION(handler)); + + DECLARE_LOCAL (arg); + Init_Error(arg, error); + + // Try passing the handler the ERROR! we trapped. Passing + // FALSE for `fully` means it will not raise an error if + // the handler happens to be arity 0. + // + if (Apply_Only_Throws(D_OUT, FALSE, handler, arg, END)) + return R_OUT_IS_THROWN; + + if (REF(q)) + return R_TRUE; + + return R_OUT; + } + } + + if (REF(q)) return R_TRUE; + + Init_Error(D_OUT, error); + return R_OUT; + } + + if (Do_Any_Array_At_Throws(D_OUT, ARG(block))) { + // Note that we are interested in when errors are raised, which + // causes a tricky C longjmp() to the code above. Yet a THROW + // is different from that, and offers an opportunity to each + // DO'ing stack level along the way to CATCH the thrown value + // (with no need for something like the PUSH_TRAP above). + // + // We're being given that opportunity here, but doing nothing + // and just returning the THROWN thing for other stack levels + // to look at. For the construct which does let you catch a + // throw, see REBNATIVE(catch), which has code for this case. + + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + return R_OUT_IS_THROWN; + } + + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + + if (REF(q)) return R_FALSE; + + return R_OUT; +} + + +// +// set-location-of-error: native [ +// +// {Sets the WHERE, NEAR, FILE, and LINE fields of an error} +// +// return: [] +// error [error!] +// location [frame! any-word!] +// ] +// +REBNATIVE(set_location_of_error) +{ + INCLUDE_PARAMS_OF_SET_LOCATION_OF_ERROR; + + REBCTX *context; + if (IS_WORD(ARG(location))) + context = VAL_WORD_CONTEXT(ARG(location)); + else + context = VAL_CONTEXT(ARG(location)); + + REBFRM *where = CTX_FRAME_IF_ON_STACK(context); + if (where == NULL) + fail (Error_Frame_Not_On_Stack_Raw()); + + REBCTX *error = VAL_CONTEXT(ARG(error)); + Set_Location_Of_Error(error, where); + + return R_VOID; +} + + +// +// attempt: native [ +// +// {Tries to evaluate a block and returns result or NONE on error.} +// +// return: [ any-value!] +// block [block!] +// ] +// +REBNATIVE(attempt) +{ + INCLUDE_PARAMS_OF_ATTEMPT; + + REBVAL *block = ARG(block); + + struct Reb_State state; + REBCTX *error; + + PUSH_TRAP(&error, &state); + + // The first time through the following code 'error' will be NULL, but... + // `fail` can longjmp here, so 'error' won't be NULL *if* that happens! + + if (error) return R_BLANK; + + if (Do_Any_Array_At_Throws(D_OUT, block)) { + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + + // Throw name is in D_OUT, thrown value is held task local + return R_OUT_IS_THROWN; + } + + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + + return R_OUT; +} diff --git a/src/core/n-function.c b/src/core/n-function.c new file mode 100644 index 0000000000..c797b481dd --- /dev/null +++ b/src/core/n-function.c @@ -0,0 +1,797 @@ +// +// File: %n-function.c +// Summary: "native functions for creating and interacting with functions" +// Section: natives +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Ren-C follows a concept of a single FUNCTION! type, instead of the +// subcategories from Rebol2 and R3-Alpha. This simplifies matters from the +// user's point of view, and also moves to the idea of a different C native +// "dispatcher" functions which are attached to the function's definition +// itself. Not only does this allow a variety of performant customized +// native dispatchers, but having the dispatcher accessed through an indirect +// pointer instead of in the function REBVALs themselves lets them be +// dynamically changed. This is used by HIJACK and by user natives. +// + +#include "sys-core.h" + +// +// func: native [ +// +// "Defines a user function with given spec and body." +// +// return: [function!] +// spec [block!] +// {Help string (opt) followed by arg words (and opt type + string)} +// body [block!] +// "The body block of the function" +// ] +// +REBNATIVE(func) +// +// Native optimized implementation of a "definitional return" function +// generator. See comments on Make_Function_May_Fail for full notes. +{ + INCLUDE_PARAMS_OF_FUNC; + + REBFUN *fun = Make_Interpreted_Function_May_Fail( + ARG(spec), ARG(body), MKF_RETURN | MKF_KEYWORDS + ); + + Move_Value(D_OUT, FUNC_VALUE(fun)); + return R_OUT; +} + + +// +// proc: native [ +// +// "Defines a user function with given spec and body and no return result." +// +// return: [function!] +// spec [block!] +// {Help string (opt) followed by arg words (and opt type + string)} +// body [block!] +// "The body block of the function, use LEAVE to exit" +// ] +// +REBNATIVE(proc) +// +// Short for "PROCedure"; inspired by the Pascal language's discernment in +// terminology of a routine that returns a value vs. one that does not. +// Provides convenient interface similar to FUNC that will not accidentally +// leak values to the caller. +{ + INCLUDE_PARAMS_OF_PROC; + + REBFUN *fun = Make_Interpreted_Function_May_Fail( + ARG(spec), ARG(body), MKF_LEAVE | MKF_KEYWORDS + ); + + Move_Value(D_OUT, FUNC_VALUE(fun)); + return R_OUT; +} + + +// +// Make_Thrown_Exit_Value: C +// +// This routine will generate a THROWN() value that can be used to indicate +// a desire to exit from a particular level in the stack with a value (or void) +// +// It is used in the implementation of the EXIT native. +// +void Make_Thrown_Exit_Value( + REBVAL *out, + const REBVAL *level, // FRAME!, FUNCTION! (or INTEGER! relative to frame) + const REBVAL *value, + REBFRM *frame // only required if level is INTEGER! +) { + Move_Value(out, NAT_VALUE(exit)); + + if (IS_INTEGER(level)) { + REBCNT count = VAL_INT32(level); + if (count <= 0) + fail (Error_Invalid_Exit_Raw()); + + REBFRM *f = frame->prior; + for (; TRUE; f = f->prior) { + if (f == NULL) + fail (Error_Invalid_Exit_Raw()); + + if (NOT(Is_Any_Function_Frame(f))) continue; // only exit functions + + if (Is_Function_Frame_Fulfilling(f)) continue; // not ready to exit + + #if !defined(NDEBUG) + if (LEGACY(OPTIONS_DONT_EXIT_NATIVES)) + if (NOT(IS_FUNCTION_INTERPRETED(FUNC_VALUE(f->phase)))) + continue; // R3-Alpha would exit the first user function + #endif + + --count; + + if (count == 0) { + // + // We want the integer-based exits to identify frames uniquely. + // Without a context varlist, a frame can't be unique. + // + Context_For_Frame_May_Reify_Managed(f); + assert(f->varlist); + out->extra.binding = f->varlist; + break; + } + } + } + else if (IS_FRAME(level)) { + out->extra.binding = CTX_VARLIST(VAL_CONTEXT(level)); + } + else { + assert(IS_FUNCTION(level)); + out->extra.binding = VAL_FUNC_PARAMLIST(level); + } + + CONVERT_NAME_TO_THROWN(out, value); +} + + +// +// exit: native [ +// +// {Leave enclosing function, or jump /FROM.} +// +// /with +// "Result for enclosing state (default is no value)" +// value [any-value!] +// /from +// "Jump the stack to return from a specific frame or call" +// level [frame! function! integer!] +// "Frame, function, or stack index to exit from" +// ] +// +REBNATIVE(exit) +// +// EXIT is implemented via a THROWN() value that bubbles up through the stack. +// Using EXIT's function REBVAL with a target `binding` field is the +// protocol understood by Do_Core to catch a throw itself. +// +// !!! Allowing to pass an INTEGER! to exit from a function based on its +// BACKTRACE number is a bit low-level, and perhaps should be restricted to +// a debugging mode (though it is a useful tool in "code golf"). +{ + INCLUDE_PARAMS_OF_EXIT; + + UNUSED(REF(with)); // implied by non-void value + + if (NOT(REF(from))) + Init_Integer(ARG(level), 1); // default--exit one function stack level + + Make_Thrown_Exit_Value(D_OUT, ARG(level), ARG(value), frame_); + + return R_OUT_IS_THROWN; +} + + +// +// return: native [ +// +// "Returns a value from a function." +// +// value [ any-value!] +// ] +// +REBNATIVE(return) +{ + INCLUDE_PARAMS_OF_RETURN; + + REBVAL *value = ARG(value); + REBFRM *f = frame_; // implicit parameter to REBNATIVE() + + if (f->binding == NULL) // raw native, not a variant FUNCTION made + fail (Error_Return_Archetype_Raw()); + + // The frame this RETURN is being called from may well not be the target + // function of the return (that's why it's a "definitional return"). So + // examine the binding. Currently it can be either a FRAME!'s varlist or + // a FUNCTION! paramlist. + + REBFUN *target = + IS_FUNCTION(ARR_HEAD(f->binding)) + ? AS_FUNC(f->binding) + : AS_FUNC(CTX_KEYLIST(CTX(f->binding))); + + REBVAL *typeset = FUNC_PARAM(target, FUNC_NUM_PARAMS(target)); + assert(VAL_PARAM_SYM(typeset) == SYM_RETURN); + + // Check to make sure the types match. If it were not done here, then + // the error would not point out the bad call...just the function that + // wound up catching it. + // + if (!TYPE_CHECK(typeset, VAL_TYPE(value))) + fail (Error_Bad_Return_Type( + f->label, // !!! Should climb stack to get real label? + VAL_TYPE(value) + )); + + Move_Value(D_OUT, NAT_VALUE(exit)); // see also Make_Thrown_Exit_Value + D_OUT->extra.binding = f->binding; + + CONVERT_NAME_TO_THROWN(D_OUT, value); + return R_OUT_IS_THROWN; +} + + +// +// leave: native [ +// +// "Leaves a procedure, giving no result to the caller." +// +// ] +// +REBNATIVE(leave) +// +// See notes on REBNATIVE(return) +{ + if (frame_->binding == NULL) // raw native, not a variant PROCEDURE made + fail (Error_Return_Archetype_Raw()); + + Move_Value(D_OUT, NAT_VALUE(exit)); // see also Make_Thrown_Exit_Value + D_OUT->extra.binding = frame_->binding; + + CONVERT_NAME_TO_THROWN(D_OUT, VOID_CELL); + return R_OUT_IS_THROWN; +} + + +// +// typechecker: native [ +// +// {Function generator for an optimized typechecking routine.} +// +// return: [function!] +// type [datatype! typeset!] +// ] +// +REBNATIVE(typechecker) +{ + INCLUDE_PARAMS_OF_TYPECHECKER; + + REBVAL *type = ARG(type); + + REBARR *paramlist = Make_Array_Core(2, ARRAY_FLAG_PARAMLIST); + + REBVAL *archetype = Alloc_Tail_Array(paramlist); + VAL_RESET_HEADER(archetype, REB_FUNCTION); + archetype->payload.function.paramlist = paramlist; + archetype->extra.binding = NULL; + + REBVAL *param = Alloc_Tail_Array(paramlist); + Init_Typeset(param, ALL_64, Canon(SYM_VALUE)); + INIT_VAL_PARAM_CLASS(param, PARAM_CLASS_NORMAL); + + MANAGE_ARRAY(paramlist); + + // for now, no help...use REDESCRIBE + + SER(paramlist)->link.meta = NULL; + + REBFUN *fun = Make_Function( + paramlist, + IS_DATATYPE(type) + ? &Datatype_Checker_Dispatcher + : &Typeset_Checker_Dispatcher, + NULL, // this is fundamental (no distinct underlying function) + NULL // not providing a specialization + ); + + *FUNC_BODY(fun) = *type; + + Move_Value(D_OUT, FUNC_VALUE(fun)); + + return R_OUT; +} + + +// +// specialize: native [ +// +// {Create a new function through partial or full specialization of another} +// +// return: [function!] +// value [function! any-word! any-path!] +// {Function or specifying word (preserves word name for debug info)} +// def [block!] +// {Definition for FRAME! fields for args and refinements} +// ] +// +REBNATIVE(specialize) +{ + INCLUDE_PARAMS_OF_SPECIALIZE; + + REBSTR *opt_name; + + // We don't limit to taking a FUNCTION! value directly, because that loses + // the symbol (for debugging, errors, etc.) If caller passes a WORD! + // then we lookup the variable to get the function, but save the symbol. + // + DECLARE_LOCAL (specializee); + Get_If_Word_Or_Path_Arg(specializee, &opt_name, ARG(value)); + + if (!IS_FUNCTION(specializee)) + fail (Error_Apply_Non_Function_Raw(ARG(value))); // for APPLY too + + if (Specialize_Function_Throws(D_OUT, specializee, opt_name, ARG(def))) + return R_OUT_IS_THROWN; + + return R_OUT; +} + + +// +// chain: native [ +// +// {Create a processing pipeline of functions that consume the last's result} +// +// return: [function!] +// pipeline [block!] +// {List of functions to apply. Reduced by default.} +// /quote +// {Do not reduce the pipeline--use the values as-is.} +// ] +// +REBNATIVE(chain) +{ + INCLUDE_PARAMS_OF_CHAIN; + + REBVAL *out = D_OUT; // plan ahead for factoring into Chain_Function(out.. + + REBVAL *pipeline = ARG(pipeline); + REBARR *chainees; + if (REF(quote)) { + chainees = COPY_ANY_ARRAY_AT_DEEP_MANAGED(pipeline); + } + else { + if (Reduce_Any_Array_Throws(out, pipeline, REDUCE_FLAG_DROP_BARS)) + return R_OUT_IS_THROWN; + + chainees = VAL_ARRAY(out); // should be all specific values + ASSERT_ARRAY_MANAGED(chainees); + } + + REBVAL *first = KNOWN(ARR_HEAD(chainees)); + + // !!! Current validation is that all are functions. Should there be other + // checks? (That inputs match outputs in the chain?) Should it be + // a dialect and allow things other than functions? + // + REBVAL *check = first; + while (NOT_END(check)) { + if (!IS_FUNCTION(check)) + fail (check); + ++check; + } + + // The paramlist needs to be unique to designate this function, but + // will be identical typesets to the first function in the chain. It's + // [0] element must identify the function we're creating vs the original, + // however. + // + REBARR *paramlist = Copy_Array_Shallow( + VAL_FUNC_PARAMLIST(ARR_HEAD(chainees)), SPECIFIED + ); + ARR_HEAD(paramlist)->payload.function.paramlist = paramlist; + SET_SER_FLAG(paramlist, ARRAY_FLAG_PARAMLIST); + MANAGE_ARRAY(paramlist); + + // See %sysobj.r for `chained-meta:` object template + + REBVAL *std_meta = Get_System(SYS_STANDARD, STD_CHAINED_META); + REBCTX *meta = Copy_Context_Shallow(VAL_CONTEXT(std_meta)); + + Init_Void(CTX_VAR(meta, STD_CHAINED_META_DESCRIPTION)); // default + Init_Block(CTX_VAR(meta, STD_CHAINED_META_CHAINEES), chainees); + // + // !!! There could be a system for preserving names in the chain, by + // accepting lit-words instead of functions--or even by reading the + // GET-WORD!s in the block. Consider for the future. + // + Init_Void(CTX_VAR(meta, STD_CHAINED_META_CHAINEE_NAMES)); + + MANAGE_ARRAY(CTX_VARLIST(meta)); + SER(paramlist)->link.meta = meta; + + REBFUN *fun = Make_Function( + paramlist, + &Chainer_Dispatcher, + VAL_FUNC(first), // cache in paramlist + NULL // not changing the specialization + ); + + // "body" is the chainees array, available to the dispatcher when called + // + Init_Block(FUNC_BODY(fun), chainees); + + Move_Value(D_OUT, FUNC_VALUE(fun)); + assert(VAL_BINDING(D_OUT) == NULL); + + return R_OUT; +} + + +// +// adapt: native [ +// +// {Create a variant of a function that preprocesses its arguments} +// +// return: [function!] +// adaptee [function! any-word! any-path!] +// {Function or specifying word (preserves word name for debug info)} +// prelude [block!] +// {Code to run in constructed frame before adapted function runs} +// ] +// +REBNATIVE(adapt) +{ + INCLUDE_PARAMS_OF_ADAPT; + + REBVAL *adaptee = ARG(adaptee); + + REBSTR *opt_adaptee_name; + Get_If_Word_Or_Path_Arg(D_OUT, &opt_adaptee_name, adaptee); + if (!IS_FUNCTION(D_OUT)) + fail (Error_Apply_Non_Function_Raw(adaptee)); + + Move_Value(adaptee, D_OUT); + + // For the binding to be correct, the indices that the words use must be + // the right ones for the frame pushed. So if you adapt a specialization + // that has one parameter, and the function that underlies that has + // 10 parameters and the one parameter you're adapting to is it's 10th + // and not its 1st...that has to be taken into account. + // + // Hence you must bind relative to that deeper function...e.g. the function + // behind the frame of the specialization which gets pushed. + // + REBFUN *underlying = FUNC_UNDERLYING(VAL_FUNC(adaptee)); + + // !!! In a future branch it may be possible that specific binding allows + // a read-only input to be "viewed" with a relative binding, and no copy + // would need be made if input was R/O. For now, we copy to relativize. + // + REBARR *prelude = Copy_And_Bind_Relative_Deep_Managed( + ARG(prelude), + FUNC_PARAMLIST(underlying), + TS_ANY_WORD + ); + + // The paramlist needs to be unique to designate this function, but + // will be identical typesets to the original. It's [0] element must + // identify the function we're creating vs the original, however. + // + REBARR *paramlist = Copy_Array_Shallow( + VAL_FUNC_PARAMLIST(adaptee), SPECIFIED + ); + ARR_HEAD(paramlist)->payload.function.paramlist = paramlist; + SET_SER_FLAG(paramlist, ARRAY_FLAG_PARAMLIST); + MANAGE_ARRAY(paramlist); + + // See %sysobj.r for `adapted-meta:` object template + + REBVAL *example = Get_System(SYS_STANDARD, STD_ADAPTED_META); + + REBCTX *meta = Copy_Context_Shallow(VAL_CONTEXT(example)); + Init_Void(CTX_VAR(meta, STD_ADAPTED_META_DESCRIPTION)); // default + Move_Value(CTX_VAR(meta, STD_ADAPTED_META_ADAPTEE), adaptee); + if (opt_adaptee_name == NULL) + Init_Void(CTX_VAR(meta, STD_ADAPTED_META_ADAPTEE_NAME)); + else + Init_Word( + CTX_VAR(meta, STD_ADAPTED_META_ADAPTEE_NAME), + opt_adaptee_name + ); + + MANAGE_ARRAY(CTX_VARLIST(meta)); + SER(paramlist)->link.meta = meta; + + REBFUN *fun = Make_Function( + paramlist, + &Adapter_Dispatcher, + underlying, // cache in paramlist + NULL // not changing the specialization + ); + + // We need to store the 2 values describing the adaptation so that the + // dispatcher knows what to do when it gets called and inspects FUNC_BODY. + // + // [0] is the prelude BLOCK!, [1] is the FUNCTION! we've adapted. + // + REBARR *adaptation = Make_Array(2); + + REBVAL *block = Alloc_Tail_Array(adaptation); + VAL_RESET_HEADER_EXTRA(block, REB_BLOCK, VALUE_FLAG_RELATIVE); + INIT_VAL_ARRAY(block, prelude); + VAL_INDEX(block) = 0; + INIT_RELATIVE(block, underlying); + + Append_Value(adaptation, adaptee); + + RELVAL *body = FUNC_BODY(fun); + VAL_RESET_HEADER_EXTRA(body, REB_BLOCK, VALUE_FLAG_RELATIVE); + INIT_VAL_ARRAY(body, adaptation); + VAL_INDEX(body) = 0; + INIT_RELATIVE(body, underlying); + MANAGE_ARRAY(adaptation); + + Move_Value(D_OUT, FUNC_VALUE(fun)); + assert(VAL_BINDING(D_OUT) == NULL); + + return R_OUT; +} + + +// +// hijack: native [ +// +// {Cause all existing references to a function to invoke another function.} +// +// return: [function! blank!] +// {The hijacked function value, blank if self-hijack (no-op).} +// victim [function! any-word! any-path!] +// {Function value whose references are to be affected.} +// hijacker [function! any-word! any-path!] +// {The function to run in its place.} +// ] +// +REBNATIVE(hijack) +// +// The HIJACK operation replaces one function completely with another, such +// that references to the old function value will now call a new one. +// +// Hijacking a function does not change its interface--and cannot. While +// it may seem tempting to use low-level tricks to keep the same paramlist +// but add or remove parameters, parameter lists can be referenced many +// places in the system (frames, specializations, adaptations) and can't +// be corrupted...or the places that rely on their properties (number and +// types of parameters) would get out of sync. +// +{ + INCLUDE_PARAMS_OF_HIJACK; + + DECLARE_LOCAL (victim); + REBSTR *opt_victim_name; + Get_If_Word_Or_Path_Arg(victim, &opt_victim_name, ARG(victim)); + if (!IS_FUNCTION(victim)) + fail ("Victim of HIJACK must be a FUNCTION!"); + + DECLARE_LOCAL (hijacker); + REBSTR *opt_hijacker_name; + Get_If_Word_Or_Path_Arg(hijacker, &opt_hijacker_name, ARG(hijacker)); + if (!IS_FUNCTION(hijacker)) + fail ("Hijacker in HIJACK must be a FUNCTION!"); + + if (VAL_FUNC(victim) == VAL_FUNC(hijacker)) { + // + // Permitting a no-op hijack has some applications...but offer a + // distinguished result for those who want to detect the condition. + // + return R_BLANK; + } + + REBARR *victim_paramlist = VAL_FUNC_PARAMLIST(victim); + REBARR *hijacker_paramlist = VAL_FUNC_PARAMLIST(hijacker); + + if ( + LOGICAL( + FUNC_UNDERLYING(VAL_FUNC(hijacker)) + == FUNC_UNDERLYING(VAL_FUNC(victim)) + ) + ){ + // Should the underlying functions of the hijacker and victim match, + // that means any ADAPT or CHAIN or SPECIALIZE of the victim can + // work equally well if we just use the hijacker's dispatcher + // directly. This is a reasonably common case, and especially + // common when putting the originally hijacked function back. + + SER(victim_paramlist)->misc.facade = + SER(hijacker_paramlist)->misc.facade; + SER(victim->payload.function.body_holder)->link.exemplar = + SER(hijacker->payload.function.body_holder)->link.exemplar; + + *VAL_FUNC_BODY(victim) = *VAL_FUNC_BODY(hijacker); + SER(victim->payload.function.body_holder)->misc.dispatcher = + SER(hijacker->payload.function.body_holder)->misc.dispatcher; + } + else { + // A mismatch means there could be someone out there pointing at this + // function who expects it to have a different frame than it does. + // In case that someone needs to run the function with that frame, + // a proxy "shim" is needed. + // + // !!! It could be possible to do things here like test to see if + // frames were compatible in some way that could accelerate the + // process of building a new frame. But in general one basically + // needs to do a new function call. + // + Move_Value(VAL_FUNC_BODY(victim), hijacker); + SER(victim->payload.function.body_holder)->misc.dispatcher = + &Hijacker_Dispatcher; + } + + // Proxy the meta information from the hijacker onto the paramlist + // + // !!! Should this add a note about the hijacking? + // + SER(victim_paramlist)->link.meta = + SER(hijacker_paramlist)->link.meta; + + Move_Value(D_OUT, victim); + D_OUT->extra.binding = hijacker->extra.binding; + + return R_OUT; +} + + +// +// variadic?: native [ +// +// {Returns TRUE if a function may take a variable number of arguments.} +// +// func [function!] +// ] +// +REBNATIVE(variadic_q) +{ + INCLUDE_PARAMS_OF_VARIADIC_Q; + + REBVAL *param = VAL_FUNC_PARAMS_HEAD(ARG(func)); + for (; NOT_END(param); ++param) { + if (GET_VAL_FLAG(param, TYPESET_FLAG_VARIADIC)) + return R_TRUE; + } + + return R_FALSE; +} + + +// +// tighten: native [ +// +// {Returns alias of a function whose "normal" args are gathered "tightly"} +// +// return: [function!] +// action [function!] +// ] +// +REBNATIVE(tighten) +// +// This routine exists to avoid the overhead of a user-function stub where +// all the parameters are #tight, e.g. the behavior of R3-Alpha's OP!s. +// So `+: enfix tighten :add` is a faster equivalent of: +// +// +: enfix func [#arg1 [any-value!] #arg2 [any-value!] [ +// add :arg1 :arg2 +// ] +// +// But also, the parameter types and help notes are kept in sync. +// +{ + INCLUDE_PARAMS_OF_TIGHTEN; + + REBFUN *original = VAL_FUNC(ARG(action)); + + // Copy the paramlist, which serves as the function's unique identity, + // and set the tight flag on all the parameters. + + REBARR *paramlist = Copy_Array_Shallow( + FUNC_PARAMLIST(original), + SPECIFIED // no relative values in parameter lists + ); + SET_SER_FLAG(paramlist, ARRAY_FLAG_PARAMLIST); // flags not auto-copied + + RELVAL *param = ARR_AT(paramlist, 1); // first parameter (0 is FUNCTION!) + for (; NOT_END(param); ++param) { + enum Reb_Param_Class pclass = VAL_PARAM_CLASS(param); + if (pclass == PARAM_CLASS_NORMAL) + INIT_VAL_PARAM_CLASS(param, PARAM_CLASS_TIGHT); + } + + RELVAL *rootparam = ARR_HEAD(paramlist); + CLEAR_VAL_FLAGS(rootparam, FUNC_FLAG_CACHED_MASK); + rootparam->payload.function.paramlist = paramlist; + rootparam->extra.binding = NULL; + + // !!! This does not make a unique copy of the meta information context. + // Hence updates to the title/parameter-descriptions/etc. of the tightened + // function will affect the original, and vice-versa. + // + SER(paramlist)->link.meta = FUNC_META(original); + + MANAGE_ARRAY(paramlist); + + REBFUN *fun = Make_Function( + paramlist, + FUNC_DISPATCHER(original), + original, // used to set the initial facade (overridden below) + NULL // don't add any specialization beyond the original + ); + + // We're reusing the original dispatcher, so we also reuse the original + // function body. + // + *FUNC_BODY(fun) = *FUNC_BODY(original); + + // Our function has a new identity, but we don't want to be using that + // identity for the pushed frame. If we did that, then if the underlying + // function were interpreted, we would have to make a copy of its body + // and rebind it to the new paramlist. HOWEVER we want the new tightened + // parameter specification to take effect--and that's not reflected in + // the original paramlist, e.g. the one to which that block is bound. + // + // So here's the clever part: functions allow you to offer a "facade" + // which is an array compatible with the original underlying function, + // but with stricter parameter types and different parameter classes. + // So just as the paramlist got transformed, transform the facade. + + REBARR *facade = Copy_Array_Shallow( + FUNC_FACADE(original), + SPECIFIED // no relative values in facades, either + ); + RELVAL *facade_param = ARR_AT(facade, 1); + for (; NOT_END(facade_param); ++facade_param) { + // + // !!! Technically we probably shouldn't be modifying the parameter + // classes of any arguments that were specialized out or otherwise + // not present in the original; but it shouldn't really matter. + // Once this function's layer has finished, the lower levels will + // refer to their own facades. + // + enum Reb_Param_Class pclass = VAL_PARAM_CLASS(facade_param); + if (pclass == PARAM_CLASS_NORMAL) + INIT_VAL_PARAM_CLASS(facade_param, PARAM_CLASS_TIGHT); + } + + MANAGE_ARRAY(facade); + + // Note: Do NOT set the ARRAY_FLAG_PARAMLIST on this facade. It holds + // whatever function value in the [0] slot the original had, and that is + // used for the identity of the "underlying function". (In order to make + // this a real FUNCTION!'s paramlist, the paramlist in the [0] slot would + // have to be equal to the facade's pointer.) + // + SER(paramlist)->misc.facade = facade; + + Move_Value(D_OUT, FUNC_VALUE(fun)); + + // Currently esoteric case if someone chose to tighten a definitional + // return, so `return 1 + 2` would return 1 instead of 3. Would need to + // preserve the binding of the incoming value, which is never present in + // the canon value of the function. + // + D_OUT->extra.binding = ARG(action)->extra.binding; + + return R_OUT; +} diff --git a/src/core/n-graphics.c b/src/core/n-graphics.c deleted file mode 100644 index 9653f87735..0000000000 --- a/src/core/n-graphics.c +++ /dev/null @@ -1,300 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: n-graphics.c -** Summary: native functions for graphical objects -** Section: natives -** Author: Carl Sassenrath -** Notes: -** Obsolete: Graphics is now implemented as an R3 extension. -** -***********************************************************************/ - -#include "sys-core.h" - - -#ifdef MOVED_TO_EXTENSION -/*********************************************************************** -** -*/ REBFLG Find_Gob_Tree(REBGOB *gob, REBGOB *tgob) -/* -** Scan a gob tree for the target gob. Return true or false. -** Gob MUST have a pane (check for it first). -** -***********************************************************************/ -{ - REBGOB **gp; - REBINT n; - - gp = GOB_HEAD(gob); - - for (n = GOB_TAIL(gob); n > 0; n--, gp++) { - if (*gp == tgob) return TRUE; - if (GOB_PANE(*gp) && Find_Gob_Tree(*gp, tgob)) return TRUE; - } - - return FALSE; -} - -/*********************************************************************** -** -*/ REBNATIVE(show) -/* -***********************************************************************/ -{ -#ifndef NO_GRAPHICS - REBVAL *val = D_ARG(1); - REBVAL *val2; - REBVAL *head; - REBINT dsp; // temporarily store it on stack - - if (IS_GOB(val)) OS_SHOW_GOB(VAL_GOB(val)); - else if (IS_BLOCK(val)) { - dsp = DSP + 1; - - // Reduce all words and paths of the GOB type. - // THIS STAYS ON THE STACK (because stack is not used) - Reduce_Type_Stack(VAL_SERIES(val), VAL_INDEX(val), REB_GOB); - - head = DS_GET(dsp); - - // Optimize: remove any gobs that are in subgobs. - // Set all gobs in block as showable: - for (val = head; NOT_END(val); val++) { - if (IS_GOB(val)) VAL_SET_OPT(val, OPTS_TEMP); - } - - // Foreach gob in block, scan for it in all gobs: - for (val = head; NOT_END(val); val++) { - if (IS_GOB(val) - && GOB_PANE(VAL_GOB(val)) - && VAL_GET_OPT(val, OPTS_TEMP) - ) { - for (val2 = head; NOT_END(val2); val2++) { - if (val != val2 - && IS_GOB(val2) - && VAL_GET_OPT(val2, OPTS_TEMP) - && Find_Gob_Tree(VAL_GOB(val), VAL_GOB(val2)) - ) { - VAL_CLR_OPT(val2, OPTS_TEMP); // do not show it - // break; // Keep going, can be: show [A B C A] (rare) - } - } - } - } - // Show those that are left: - for (val = head; NOT_END(val); val++) { - if (VAL_GET_OPT(val, OPTS_TEMP)) { - OS_SHOW_GOB(VAL_GOB(val)); - VAL_CLR_OPT(val, OPTS_TEMP); - } -// else { -// Print("No-show: %r", val); -// } - } - DSP = dsp - 1; // reset stack - } - else OS_SHOW_GOB(0); -#endif - - return R_ARG1; -} - - -/*********************************************************************** -** -*/ REBNATIVE (size_text) -/* -***********************************************************************/ -{ -#ifndef NO_GRAPHICS - REBGOB *gob = VAL_GOB(D_ARG(1)); - REBPAR xy; - - if (!IS_GOB_TEXT(gob) && !IS_GOB_STRING(gob)) Trap_Arg(D_ARG(1)); //!!! better error - - OS_SIZE_TEXT(gob, &xy); - - VAL_SET(D_RET, REB_PAIR); - VAL_PAIR(D_RET) = xy; -#endif - return R_RET; -} - - -/*********************************************************************** -** -*/ REBNATIVE (caret_to_offset) -/* -***********************************************************************/ -{ -#ifndef NO_GRAPHICS - REBGOB *gob = VAL_GOB(D_ARG(1)); - REBVAL *arg1 = D_ARG(2); - REBVAL *arg2 = D_ARG(3); - REBPAR xy; - REBINT element; - REBINT position; - - if (!IS_GOB_TEXT(gob) && !IS_GOB_STRING(gob)) Trap_Arg(D_ARG(1)); //!!! better error - - if (IS_INTEGER(arg1)) - element = Int32(arg1); - else if (IS_BLOCK(arg1)) - element = VAL_INDEX(arg1); - - if (IS_INTEGER(arg2)) - position = Int32(arg2); - else if (IS_STRING(arg2)) - position = VAL_INDEX(arg2); - - if (element < 0) Trap_Arg(arg1); - if (position < 0) Trap_Arg(arg2); - - OS_CARET_TO_OFFSET(gob, &xy, element, position); - - VAL_SET(D_RET, REB_PAIR); - VAL_PAIR(D_RET) = xy; -#endif - return R_RET; -} - - -/*********************************************************************** -** -*/ REBNATIVE (offset_to_caret) -/* -***********************************************************************/ -{ -#ifndef NO_GRAPHICS - REBGOB *gob = VAL_GOB(D_ARG(1)); - REBPAR xy = VAL_PAIR(D_ARG(2)); - REBINT element; - REBINT position; - REBSER *ser; - REBVAL *val; - - if (!IS_GOB_TEXT(gob) && !IS_GOB_STRING(gob)) Trap_Arg(D_ARG(1)); //!!! better error - - OS_OFFSET_TO_CARET(gob, xy, &element, &position); - - if (IS_GOB_STRING(gob)) { - Set_Series(REB_STRING, D_RET, GOB_CONTENT(gob)); - VAL_INDEX(D_RET) = position; - } else { - ser = Copy_Block(GOB_CONTENT(gob), 0); - Set_Series(REB_BLOCK, D_RET, ser); - if ((REBCNT)element < ser->tail) { - VAL_INDEX(D_RET) = element; - val = BLK_SKIP(ser, element); - if (IS_WORD(val)) { - val = Get_Var(val); - *BLK_SKIP(ser, element) = *val; - val = BLK_SKIP(ser, element); - } - if (ANY_STR(val)) { - VAL_INDEX(val) = position; - return R_RET; - } - } - return R_NONE; - } -#endif - return R_RET; -} - - -/*********************************************************************** -** -*/ REBNATIVE(draw) -/* -***********************************************************************/ -{ -#ifndef NO_GRAPHICS - REBVAL *arg = D_ARG(1); - REBVAL *image; - REBINT err; - REBSER *ser; - - if (IS_IMAGE(arg)) image = arg; - else image = Make_Image(VAL_PAIR_X(arg), VAL_PAIR_Y(arg)); - - *D_RET = *image; - if (err = OS_DRAW_IMAGE(VAL_SERIES(image), ser = At_Head(D_ARG(2)))) { - Trap_Word(RE_DIALECT, SYM_DRAW, BLK_SKIP(ser, (-err)-1)); - } -#endif - return R_RET; -} - - -/*********************************************************************** -** -*/ REBNATIVE(effect) -/* -***********************************************************************/ -{ -#ifndef NO_GRAPHICS - REBVAL *arg = D_ARG(1); - REBVAL *image; - REBINT err; - REBSER *ser; - - if (IS_IMAGE(arg)) image = arg; - else image = Make_Image(VAL_PAIR_X(arg), VAL_PAIR_Y(arg)); - - *D_RET = *image; - if (err = OS_EFFECT_IMAGE(VAL_SERIES(image), ser = At_Head(D_ARG(2)))) { - Trap_Word(RE_DIALECT, SYM_DRAW, BLK_SKIP(ser, (-err)-1)); - } -#endif - return R_RET; -} - - -/*********************************************************************** -** -*/ REBNATIVE(cursor) -/* -***********************************************************************/ -{ -#ifndef NO_GRAPHICS - REBVAL *arg = D_ARG(1); - REBINT n = 0; - - if (IS_INTEGER(arg)) n = Int32(arg); - else if (IS_NONE(arg)) n = 0; - else n = -1; - - OS_CURSOR_IMAGE(n, (n < 0) ? VAL_SERIES(arg) : 0); -#endif - return R_UNSET; -} -#endif - -/*********************************************************************** -** -*/ void Trap_Image() -/* -***********************************************************************/ -{ - Trap0(RE_BAD_MEDIA); -} diff --git a/src/core/n-io.c b/src/core/n-io.c old mode 100644 new mode 100755 index 08dd50e842..105aa1ee7d --- a/src/core/n-io.c +++ b/src/core/n-io.c @@ -1,833 +1,930 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: n-io.c -** Summary: native functions for input and output -** Section: natives -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %n-io.c +// Summary: "native functions for input and output" +// Section: natives +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -//#define HELPER - /** Helper Functions **************************************************/ -#ifdef HELPER -// Used for file loading during very early development. -static REBSER *Read_All_File(char *fname) -{ - REBREQ file; - REBSER *ser = 0; - - CLEAR(&file, sizeof(file)); - - file.clen = sizeof(file); - file.device = RDI_FILE; - file.file.path = fname; - - SET_FLAG(file.modes, RFM_READ); - - OS_DO_DEVICE(&file, RDC_OPEN); - - if (file.error) return 0; - ser = Make_Binary((REBCNT)(file.file.size)); - - file.data = BIN_DATA(ser); - file.length = (REBCNT)(file.file.size); +// +// form: native [ +// +// "Converts a value to a human-readable string." +// +// value [ any-value!] +// "The value to form" +// ] +// +REBNATIVE(form) +{ + INCLUDE_PARAMS_OF_FORM; - OS_DO_DEVICE(&file, RDC_READ); + REBVAL *value = ARG(value); - if (file.error) { - ser = 0; - } - else { - ser->tail = file.actual; - STR_TERM(ser); - } + Init_String(D_OUT, Copy_Form_Value(value, 0)); - OS_DO_DEVICE(&file, RDC_CLOSE); - return ser; + return R_OUT; } -#endif -/*********************************************************************** -** -*/ REBNATIVE(echo) -/* -***********************************************************************/ +// +// mold: native [ +// +// "Converts a value to a REBOL-readable string." +// +// value [any-value!] +// "The value to mold" +// /only +// {For a block value, mold only its contents, no outer []} +// /all +// "Use construction syntax" +// /flat +// "No indentation" +// ] +// +REBNATIVE(mold) { - REBVAL *val = D_ARG(1); - REBSER *ser = 0; + INCLUDE_PARAMS_OF_MOLD; - Echo_File(0); + REB_MOLD mo; + CLEARS(&mo); + if (REF(all)) SET_FLAG(mo.opts, MOPT_MOLD_ALL); + if (REF(flat)) SET_FLAG(mo.opts, MOPT_INDENT); - if (IS_FILE(val)) - ser = Value_To_OS_Path(val); - else if (IS_LOGIC(val) && IS_TRUE(val)) - ser = To_Local_Path("output.txt", 10, FALSE, TRUE); + Push_Mold(&mo); - if (ser) { - if (!Echo_File((REBCHR*)(ser->data))) Trap1(RE_CANNOT_OPEN, val); - } + if (REF(only) && IS_BLOCK(ARG(value))) SET_FLAG(mo.opts, MOPT_ONLY); - return R_RET; -} + Mold_Value(&mo, ARG(value), TRUE); + Init_String(D_OUT, Pop_Molded_String(&mo)); -/*********************************************************************** -** -*/ REBNATIVE(form) -/* -** Converts a value to a REBOL readable string. -** value "The value to mold" -** /only "For a block value, give only contents, no outer [ ]" -** /all "Mold in serialized format" -** /flat "No line indentation" -** -***********************************************************************/ -{ - Set_String(D_RET, Copy_Form_Value(D_ARG(1), 0)); - return R_RET; + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(mold) -/* -** Converts a value to a REBOL readable string. -** value "The value to mold" -** /only "For a block value, give only contents, no outer [ ]" -** /all "Mold in serialized format" -** /flat "No line indentation" -** -***********************************************************************/ +// +// write-stdout: native [ +// +// "Write text to standard output, or raw BINARY! (for control codes / CGI)" +// +// return: [] +// value [string! char! binary!] +// "Text to write, if a STRING! or CHAR! is converted to OS format" +// ] +// +REBNATIVE(write_stdout) { - REBVAL *val = D_ARG(1); - REB_MOLD mo = {0}; + INCLUDE_PARAMS_OF_WRITE_STDOUT; + + REBVAL *v = ARG(value); + + if (IS_BINARY(v)) { // raw output + Prin_OS_String(VAL_BIN_AT(v), VAL_LEN_AT(v), OPT_ENC_RAW); + } + else if (IS_CHAR(v)) { // useful for `write-stdout newline`, etc. + Prin_OS_String(&VAL_CHAR(v), 1, OPT_ENC_UNISRC | OPT_ENC_CRLF_MAYBE); + } + else { // string output translated to OS format + assert(IS_STRING(v)); + if (VAL_BYTE_SIZE(v)) + Prin_OS_String(VAL_BIN_AT(v), VAL_LEN_AT(v), OPT_ENC_CRLF_MAYBE); + else + Prin_OS_String( + VAL_UNI_AT(v), + VAL_LEN_AT(v), + OPT_ENC_UNISRC | OPT_ENC_CRLF_MAYBE + ); + } + + return R_VOID; +} - if (D_REF(3)) SET_FLAG(mo.opts, MOPT_MOLD_ALL); - if (D_REF(4)) SET_FLAG(mo.opts, MOPT_INDENT); - Reset_Mold(&mo); +// +// new-line: native [ +// +// {Sets or clears the new-line marker within a block or group.} +// +// position [block! group!] +// "Position to change marker (modified)" +// mark +// "Set TRUE for newline" +// /all +// "Set/clear marker to end of series" +// /skip +// {Set/clear marker periodically to the end of the series} +// size [integer!] +// ] +// +REBNATIVE(new_line) +{ + INCLUDE_PARAMS_OF_NEW_LINE; - if (D_REF(2) && IS_BLOCK(val)) SET_FLAG(mo.opts, MOPT_ONLY); + RELVAL *value = VAL_ARRAY_AT(ARG(position)); + REBOOL mark = IS_CONDITIONAL_TRUE(ARG(mark)); + REBINT skip = 0; + REBCNT n; - Mold_Value(&mo, val, TRUE); + if (REF(all)) skip = 1; - Set_String(D_RET, Copy_String(mo.series, 0, -1)); + if (REF(skip)) { + skip = Int32s(ARG(size), 1); + if (skip < 1) skip = 1; + } - return R_RET; -} + for (n = 0; NOT_END(value); n++, value++) { + if ((skip != 0) && (n % skip != 0)) continue; + if (mark) + SET_VAL_FLAG(value, VALUE_FLAG_LINE); + else + CLEAR_VAL_FLAG(value, VALUE_FLAG_LINE); -/*********************************************************************** -** -*/ REBNATIVE(print) -/* -***********************************************************************/ -{ - REBVAL *value = D_ARG(1); + if (skip == 0) break; + } - if (IS_BLOCK(value)) Reduce_Block(VAL_SERIES(value), VAL_INDEX(value), 0); - Print_Value(DS_TOP, 0, 0); - return R_UNSET; // reloads ds + Move_Value(D_OUT, ARG(position)); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(prin) -/* -***********************************************************************/ +// +// new-line?: native [ +// +// {Returns the state of the new-line marker within a block or group.} +// +// position [block! group!] "Position to check marker" +// ] +// +REBNATIVE(new_line_q) { - REBVAL *value = D_ARG(1); - - if (IS_BLOCK(value)) Reduce_Block(VAL_SERIES(value), VAL_INDEX(value), 0); - Prin_Value(DS_TOP, 0, 0); - return R_UNSET; // reloads ds -} + INCLUDE_PARAMS_OF_NEW_LINE_Q; + if (GET_VAL_FLAG(VAL_ARRAY_AT(ARG(position)), VALUE_FLAG_LINE)) + return R_TRUE; -/*********************************************************************** -** -*/ REBNATIVE(new_line) -/* -***********************************************************************/ -{ - REBVAL *value = D_ARG(1); - REBVAL *val; - REBOOL cond = IS_TRUE(D_ARG(2)); - REBCNT n; - REBINT skip = -1; - - val = VAL_BLK_DATA(value); - if (D_REF(3)) skip = 1; // all - if (D_REF(4)) { // skip - skip = Int32s(D_ARG(5), 1); // size - if (skip < 1) skip = 1; - } - for (n = 0; NOT_END(val); n++, val++) { - if (cond ^ (n % skip != 0)) - VAL_SET_LINE(val); - else - VAL_CLR_LINE(val); - if (skip < 0) break; - } - - return R_ARG1; + return R_FALSE; } -/*********************************************************************** -** -*/ REBNATIVE(new_lineq) -/* -***********************************************************************/ +// +// now: native [ +// +// "Returns current date and time with timezone adjustment." +// +// /year +// "Returns year only" +// /month +// "Returns month only" +// /day +// "Returns day of the month only" +// /time +// "Returns time only" +// /zone +// "Returns time zone offset from UCT (GMT) only" +// /date +// "Returns date only" +// /weekday +// {Returns day of the week as integer (Monday is day 1)} +// /yearday +// "Returns day of the year (Julian)" +// /precise +// "High precision time" +// /utc +// "Universal time (no zone)" +// ] +// +REBNATIVE(now) { - if VAL_GET_LINE(VAL_BLK_DATA(D_ARG(1))) return R_TRUE; - return R_FALSE; + INCLUDE_PARAMS_OF_NOW; + + REBVAL *ret = D_OUT; + OS_GET_TIME(D_OUT); + + if (NOT(REF(precise))) { + // + // The "time" field is measured in nanoseconds, and the historical + // meaning of not using precise measurement was to use only the + // seconds portion (with the nanoseconds set to 0). This achieves + // that by extracting the seconds and then multiplying by nanoseconds. + // + VAL_NANO(ret) = SECS_TO_NANO(VAL_SECS(ret)); + } + + if (REF(utc)) { + VAL_ZONE(ret) = 0; + } + else { + if ( + REF(year) + || REF(month) + || REF(day) + || REF(time) + || REF(date) + || REF(weekday) + || REF(yearday) + ){ + Adjust_Date_Zone(ret, FALSE); // Add time zone, adjust date/time + } + } + + REBINT n = -1; + + if (REF(date)) { + VAL_NANO(ret) = NO_TIME; + VAL_ZONE(ret) = 0; + } + else if (REF(time)) { + VAL_RESET_HEADER(ret, REB_TIME); + } + else if (REF(zone)) { + VAL_RESET_HEADER(ret, REB_TIME); + VAL_NANO(ret) = VAL_ZONE(ret) * ZONE_MINS * MIN_SEC; + } + else if (REF(weekday)) + n = Week_Day(VAL_DATE(ret)); + else if (REF(yearday)) + n = Julian_Date(VAL_DATE(ret)); + else if (REF(year)) + n = VAL_YEAR(ret); + else if (REF(month)) + n = VAL_MONTH(ret); + else if (REF(day)) + n = VAL_DAY(ret); + + if (n > 0) + Init_Integer(ret, n); + + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(now) -/* -** Return the current date and time with timezone adjustment. -** -** 1 /year {Returns year only.} -** 2 /month {Returns month only.} -** 3 /day {Returns day of the month only.} -** 4 /time {Returns time only.} -** 5 /zone {Returns time zone offset from GMT only.} -** 6 /date {Returns date only.} -** 7 /weekday {Returns day of the week as integer (Monday is day 1).} -** 8 /yearday {Returns day of the year (Julian)} -** 9 /precise {Higher precision} -** 10 /utc {Universal time (no zone)} -** -***********************************************************************/ -{ - REBOL_DAT dat; - REBINT n = -1; - REBVAL *ret = D_RET; - - OS_GET_TIME(&dat); - if (!D_REF(9)) dat.nano = 0; // Not /precise - Set_Date(ret, &dat); - Current_Year = dat.year; - - if (D_REF(10)) { // UTC - VAL_ZONE(ret) = 0; - } - else { - if (D_REF(1) || D_REF(2) || D_REF(3) || D_REF(4) - || D_REF(6) || D_REF(7) || D_REF(8)) - Adjust_Date_Zone(ret, FALSE); // Add time zone, adjust date and time - } - - // Check for /date, /time, /zone - if (D_REF(6)) { // date - VAL_TIME(ret) = NO_TIME; - VAL_ZONE(ret) = 0; - } - else if (D_REF(4)) { // time - //if (dat.time == ???) SET_NONE(ret); - VAL_SET(ret, REB_TIME); - } - else if (D_REF(5)) { // zone - VAL_SET(ret, REB_TIME); - VAL_TIME(ret) = VAL_ZONE(ret) * ZONE_MINS * MIN_SEC; - } - else if (D_REF(7)) n = Week_Day(VAL_DATE(ret)); - else if (D_REF(8)) n = Julian_Date(VAL_DATE(ret)); - else if (D_REF(1)) n = VAL_YEAR(ret); - else if (D_REF(2)) n = VAL_MONTH(ret); - else if (D_REF(3)) n = VAL_DAY(ret); - - if (n > 0) SET_INTEGER(ret, n); - - return R_RET; -} - +// +// Milliseconds_From_Value: C +// +// Note that this routine is used by the SLEEP extension, as well as by WAIT. +// +REBCNT Milliseconds_From_Value(const RELVAL *v) { + REBINT msec; -#ifdef HELPER -/*********************************************************************** -** -*/ REBNATIVE(read_file) -/* -***********************************************************************/ -{ - REBSER *ser; + switch (VAL_TYPE(v)) { + case REB_INTEGER: + msec = 1000 * Int32(v); + break; - ser = VAL_SERIES(D_ARG(1)); + case REB_DECIMAL: + msec = cast(REBINT, 1000 * VAL_DECIMAL(v)); + break; - ser = Read_All_File(STR_HEAD(ser)); - if (!ser) Trap1(RE_CANNOT_OPEN, D_ARG(1)); + case REB_TIME: + msec = cast(REBINT, VAL_NANO(v) / (SEC_SEC / 1000)); + break; - Set_Binary(D_RET, ser); + default: + panic (NULL); // avoid uninitialized msec warning + } - return R_RET; + if (msec < 0) + fail (Error_Out_Of_Range(const_KNOWN(v))); -#ifdef unused - if (D_REF(2)) // /binary - Set_Binary(ret, ser); - else { - SERIES_TAIL(ser) = Convert_CRLF(STR_HEAD(ser), SERIES_TAIL(ser)); - if (D_REF(3)) // /lines - Set_Block(ret, Convert_Lines(ser)); - else - Set_String(ret, ser); - } - return R_RET; -#endif + return cast(REBCNT, msec); } -#endif -/*********************************************************************** -** -*/ REBNATIVE(wait) -/* -***********************************************************************/ +// +// wait: native [ +// +// "Waits for a duration, port, or both." +// +// value [any-number! time! port! block! blank!] +// /all +// "Returns all in a block" +// /only +// {only check for ports given in the block to this function} +// ] +// +REBNATIVE(wait) { - REBVAL *val = D_ARG(1); - REBINT timeout = 0; // in milliseconds - REBSER *ports = 0; - REBINT n = 0; - - SET_NONE(D_RET); - - if (IS_BLOCK(val)) { - Reduce_Block(VAL_SERIES(val), VAL_INDEX(val), 0); // [stack-move] - ports = VAL_SERIES(DS_TOP); // volatile after - DS_RELOAD(ds); - for (val = BLK_HEAD(ports); NOT_END(val); val++) { // find timeout - if (Pending_Port(val)) n++; - if (IS_INTEGER(val) || IS_DECIMAL(val)) break; - } - if (IS_END(val)) { - if (n == 0) return R_NONE; // has no pending ports! - // SET_NONE(val); // no timeout -- BUG: unterminated block in GC - } - } - - switch (VAL_TYPE(val)) { - case REB_INTEGER: - timeout = 1000 * Int32(val); - goto chk_neg; - - case REB_DECIMAL: - timeout = (REBINT)(1000 * VAL_DECIMAL(val)); - goto chk_neg; - - case REB_TIME: - timeout = (REBINT) (VAL_TIME(val) / (SEC_SEC / 1000)); -chk_neg: - if (timeout < 0) Trap_Range(val); - break; - - case REB_PORT: - if (!Pending_Port(val)) return R_NONE; - ports = Make_Block(1); - Append_Val(ports, val); - // fall thru... - case REB_NONE: - case REB_END: - timeout = ALL_BITS; // wait for all windows - break; - - default: - Trap_Arg(val); - } - - // Prevent GC on temp port block: - // Note: Port block is always a copy of the block. - if (ports) Set_Block(D_RET, ports); - - // Process port events [stack-move]: - if (!Wait_Ports(ports, timeout)) return R_NONE; - if (!ports) return R_NONE; - DS_RELOAD(ds); - - // Determine what port(s) waked us: - Sieve_Ports(ports); - - if (!D_REF(2)) { // not /all ports - val = BLK_HEAD(ports); - if (IS_PORT(val)) *D_RET = *val; - else SET_NONE(D_RET); - } - - return R_RET; + INCLUDE_PARAMS_OF_WAIT; + + REBCNT timeout = 0; // in milliseconds + REBARR *ports = NULL; + REBINT n = 0; + + Init_Blank(D_OUT); + + RELVAL *val; + if (IS_BLOCK(ARG(value))) { + DECLARE_LOCAL (unsafe); // temporary not safe from GC + + if (Reduce_Any_Array_Throws( + unsafe, ARG(value), REDUCE_FLAG_DROP_BARS + )){ + Move_Value(D_OUT, unsafe); + return R_OUT_IS_THROWN; + } + + ports = VAL_ARRAY(unsafe); + for (val = ARR_HEAD(ports); NOT_END(val); val++) { // find timeout + if (Pending_Port(KNOWN(val))) n++; + if (IS_INTEGER(val) + || IS_DECIMAL(val) + || IS_TIME(val) + ) + break; + } + if (IS_END(val)) { + if (n == 0) return R_BLANK; // has no pending ports! + else timeout = ALL_BITS; // no timeout provided + // Init_Blank(val); // no timeout -- BUG: unterminated block in GC + } + } + else + val = ARG(value); + + if (NOT_END(val)) { + switch (VAL_TYPE(val)) { + case REB_INTEGER: + case REB_DECIMAL: + case REB_TIME: + timeout = Milliseconds_From_Value(val); + break; + + case REB_PORT: + if (!Pending_Port(KNOWN(val))) return R_BLANK; + ports = Make_Array(1); + Append_Value(ports, KNOWN(val)); + // fall thru... + case REB_BLANK: + timeout = ALL_BITS; // wait for all windows + break; + + default: + fail (Error_Invalid_Arg_Core(val, SPECIFIED)); + } + } + + // Prevent GC on temp port block: + // Note: Port block is always a copy of the block. + if (ports) + Init_Block(D_OUT, ports); + + // Process port events [stack-move]: + if (!Wait_Ports(ports, timeout, REF(only))) { + Sieve_Ports(NULL); // just reset the waked list + return R_BLANK; + } + if (!ports) return R_BLANK; + + // Determine what port(s) waked us: + Sieve_Ports(ports); + + if (NOT(REF(all))) { + val = ARR_HEAD(ports); + if (IS_PORT(val)) + Move_Value(D_OUT, KNOWN(val)); + else + Init_Blank(D_OUT); + } + + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(wake_up) -/* -** Calls port update for native actors. -** Calls port awake function. -** -***********************************************************************/ +// +// wake-up: native [ +// +// "Awake and update a port with event." +// +// port [port!] +// event [event!] +// ] +// +REBNATIVE(wake_up) +// +// Calls port update for native actors. +// Calls port awake function. { - REBVAL *val = D_ARG(1); - REBSER *port = VAL_PORT(val); - - if (SERIES_TAIL(port) < STD_PORT_MAX) Crash(9910); - - val = OFV(port, STD_PORT_ACTOR); - if (IS_NATIVE(val)) { - Do_Port_Action(port, A_UPDATE); // uses current stack frame - } - - val = OFV(port, STD_PORT_AWAKE); - if (ANY_FUNC(val)) { - val = Apply_Func(0, val, D_ARG(2), 0); - if (!(IS_LOGIC(val) && VAL_LOGIC(val))) return R_FALSE; - } - return R_TRUE; // wake it up -} + INCLUDE_PARAMS_OF_WAKE_UP; + REBCTX *port = VAL_CONTEXT(ARG(port)); + FAIL_IF_BAD_PORT(port); -/*********************************************************************** -** -*/ REBNATIVE(to_rebol_file) -/* -***********************************************************************/ -{ - REBVAL *arg = D_ARG(1); - REBSER *ser; + REBVAL *actor = CTX_VAR(port, STD_PORT_ACTOR); + if (Is_Native_Port_Actor(actor)) { + // + // We don't pass `actor` or `event` in, because we just pass the + // current call info. The port action can re-read the arguments. + // + Do_Port_Action(frame_, port, SYM_UPDATE); + } - ser = Value_To_REBOL_Path(arg, 0); - if (!ser) Trap_Arg(arg); - Set_Series(REB_FILE, D_RET, ser); + REBOOL woke_up = TRUE; // start by assuming success - return R_RET; -} + REBVAL *awake = CTX_VAR(port, STD_PORT_AWAKE); + if (IS_FUNCTION(awake)) { + const REBOOL fully = TRUE; // error if not all arguments consumed + if (Apply_Only_Throws(D_OUT, fully, awake, ARG(event), END)) + fail (Error_No_Catch_For_Throw(D_OUT)); -/*********************************************************************** -** -*/ REBNATIVE(to_local_file) -/* -***********************************************************************/ -{ - REBVAL *arg = D_ARG(1); - REBSER *ser; - - ser = Value_To_Local_Path(arg, D_REF(2)); - if (!ser) Trap_Arg(arg); - Set_Series(REB_STRING, D_RET, ser); + if (NOT(IS_LOGIC(D_OUT) && VAL_LOGIC(D_OUT))) + woke_up = FALSE; + } - return R_RET; + return R_FROM_BOOL(woke_up); } -/*********************************************************************** -** -*/ REBNATIVE(what_dir) -/* -***********************************************************************/ +// +// to-rebol-file: native [ +// +// {Converts a local system file path to a REBOL file path.} +// +// path [file! string!] +// ] +// +REBNATIVE(to_rebol_file) { - REBSER *ser; - REBCHR *lpath; - REBINT len; + INCLUDE_PARAMS_OF_TO_REBOL_FILE; - len = OS_GET_CURRENT_DIR(&lpath); - ser = To_REBOL_Path(lpath, len, OS_WIDE, TRUE); // allocates extra for end / - ASSERT1(ser, RP_MISC); // should never happen - OS_FREE(lpath); - Set_Series(REB_FILE, D_RET, ser); + REBVAL *arg = ARG(path); - return R_RET; + REBSER *ser = Value_To_REBOL_Path(arg, FALSE); + if (ser == NULL) + fail (arg); + + Init_File(D_OUT, ser); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(change_dir) -/* -***********************************************************************/ +// +// to-local-file: native [ +// +// {Converts a REBOL file path to the local system file path.} +// +// path [file! string!] +// /full +// {Prepends current dir for full path (for relative paths only)} +// ] +// +REBNATIVE(to_local_file) { - REBVAL *arg = D_ARG(1); - REBSER *ser; - REBINT n; - REBVAL val; - - ser = Value_To_OS_Path(arg); - if (!ser) Trap_Arg(arg); // !!! ERROR MSG + INCLUDE_PARAMS_OF_TO_LOCAL_FILE; - Set_String(&val, ser); // may be unicode or utf-8 - Check_Security(SYM_FILE, POL_EXEC, &val); + REBVAL *arg = ARG(path); - n = OS_SET_CURRENT_DIR((void*)ser->data); // use len for bool - if (!n) Trap_Arg(arg); // !!! ERROR MSG + REBSER *ser = Value_To_Local_Path(arg, REF(full)); + if (ser == NULL) + fail (arg); - return R_ARG1; + Init_String(D_OUT, ser); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(browse) -/* -***********************************************************************/ +// +// what-dir: native [ +// "Returns the current directory path." +// ; No arguments +// ] +// +REBNATIVE(what_dir) { - REBINT r; - REBCHR *url = 0; - REBVAL *arg = D_ARG(1); + REBVAL *current_path = Get_System(SYS_OPTIONS, OPTIONS_CURRENT_PATH); + + if (IS_FILE(current_path) || IS_BLANK(current_path)) { + // + // !!! Because of the need to track a notion of "current path" which + // could be a URL! as well as a FILE!, the state is stored in the + // system options. For now--however--it is "duplicate" in the case + // of a FILE!, because the OS has its own tracked state. We let the + // OS state win for files if they have diverged somehow--because the + // code was already here and it would be more compatible. But + // reconsider the duplication. + + REBCHR *lpath; + REBINT len = OS_GET_CURRENT_DIR(&lpath); + + // allocates extra for end `/` + REBSER *ser = To_REBOL_Path( + lpath, len, PATH_OPT_SRC_IS_DIR | (OS_WIDE ? PATH_OPT_UNI_SRC : 0) + ); + + OS_FREE(lpath); + + Init_File(current_path, ser); // refresh in case they diverged + } + else if (NOT(IS_URL(current_path))) { + // + // Lousy error, but ATM the user can directly edit system/options. + // They shouldn't be able to (or if they can, it should be validated) + // + fail (current_path); + } + + // Note the expectation is that WHAT-DIR will return a value that can be + // mutated by the caller without affecting future calls to WHAT-DIR, so + // the variable holding the current path must be copied. + // + Init_Any_Series_At( + D_OUT, + VAL_TYPE(current_path), + Copy_Sequence(VAL_SERIES(current_path)), + VAL_INDEX(current_path) + ); + + return R_OUT; +} - Check_Security(SYM_BROWSE, POL_EXEC, arg); - if (IS_NONE(arg)) - return R_UNSET; +// +// change-dir: native [ +// +// {Changes the current path (where scripts with relative paths will be run).} +// +// path [file! url!] +// ] +// +REBNATIVE(change_dir) +{ + INCLUDE_PARAMS_OF_CHANGE_DIR; - url = Val_Str_To_OS(arg); + REBVAL *arg = ARG(path); + REBVAL *current_path = Get_System(SYS_OPTIONS, OPTIONS_CURRENT_PATH); - r = OS_BROWSE(url, 0); + if (IS_URL(arg)) { + // There is no directory listing protocol for HTTP (although this + // needs to be methodized to work for SFTP etc.) So this takes + // your word for it for the moment that it's a valid "directory". + // + // !!! Should it at least check for a trailing `/`? + } + else { + assert(IS_FILE(arg)); - if (r == 0) Trap1(RE_CALL_FAIL, Make_OS_Error()); + REBSER *ser = Value_To_OS_Path(arg, TRUE); + if (ser == NULL) + fail (arg); - return R_UNSET; -} + DECLARE_LOCAL (val); + Init_String(val, ser); // may be unicode or utf-8 + Check_Security(Canon(SYM_FILE), POL_EXEC, val); + if (!OS_SET_CURRENT_DIR(SER_HEAD(REBCHR, ser))) + fail (arg); + } -/*********************************************************************** -** -*/ REBNATIVE(call) -/* -***********************************************************************/ -{ - REBINT r; - REBCHR *cmd; - REBVAL *arg = D_ARG(1); - - Check_Security(SYM_CALL, POL_EXEC, arg); - - cmd = Val_Str_To_OS(arg); - r = OS_CREATE_PROCESS(cmd, D_REF(2) ? 1 : 0); - - if (D_REF(2)) { - SET_INTEGER(D_RET, r); - return R_RET; - } - - if (r < 0) Trap1(RE_CALL_FAIL, Make_OS_Error()); - return R_NONE; + Move_Value(current_path, arg); + + Move_Value(D_OUT, ARG(path)); + return R_OUT; } -#ifdef not_used -/*********************************************************************** -** -*/ REBNATIVE(launch) -/* -***********************************************************************/ +// +// String_List_To_Array: C +// +// Convert a series of null terminated strings to an array of strings +// separated with '='. +// +static REBARR *String_List_To_Array(REBCHR *str) { - REBVAL *val = Get_System(SYS_OPTIONS, OPTIONS_BOOT); - REBVAL *script = D_ARG(1); - REBSER *cmd; - REBINT r; - - Check_Security(SYM_CALL, POL_EXEC, script); - - if (ANY_STR(val)) { - cmd = Make_Binary(VAL_LEN(val) + VAL_LEN(script) + 4); - Append_Byte(cmd, '"'); - Append_Bytes(cmd, VAL_BIN_DATA(val)); - Append_Byte(cmd, '"'); - if (!IS_NONE(script)) { - Append_Byte(cmd, ' '); - Append_Bytes(cmd, VAL_BIN_DATA(script)); // !!! convert file - } - if (D_REF(2)) { - Append_Byte(cmd, ' '); - Append_Bytes(cmd, VAL_BIN_DATA(D_ARG(3))); - } - Print("Launching: %s", STR_HEAD(cmd)); - r = OS_CREATE_PROCESS(STR_HEAD(cmd), 0); - if (r < 0) Trap1(RE_CALL_FAIL, Make_OS_Error()); - } - return R_NONE; + REBCNT n; + REBCNT len = 0; + REBCHR *start = str; + REBCHR *eq; + REBARR *array; + + while ((n = OS_STRLEN(str))) { + len++; + str += n + 1; // next + } + + array = Make_Array(len * 2); + + str = start; + while ((eq = OS_STRCHR(str+1, '=')) && (n = OS_STRLEN(str))) { + Init_String(Alloc_Tail_Array(array), Copy_OS_Str(str, eq - str)); + Init_String( + Alloc_Tail_Array(array), Copy_OS_Str(eq + 1, n - (eq - str) - 1) + ); + str += n + 1; // next + } + + return array; } -#endif -/*********************************************************************** -** -*/ static REBSER *String_List_To_Block(REBCHR *str) -/* -** Convert a series of null terminated strings to -** a block of strings separated with '='. -** -***********************************************************************/ +// +// Block_To_String_List: C +// +// Convert block of values to a string that holds +// a series of null terminated strings, followed +// by a final terminating string. +// +REBSER *Block_To_String_List(REBVAL *blk) { - REBCNT n; - REBCNT len = 0; - REBCHR *start = str; - REBCHR *eq; - REBSER *blk; - - while (n = LEN_STR(str)) { - len++; - str += n + 1; // next - } + RELVAL *value; - blk = Make_Block(len*2); + REB_MOLD mo; + CLEARS(&mo); - str = start; - while (NZ(eq = FIND_CHR(str+1, '=')) && NZ(n = LEN_STR(str))) { - Set_Series(REB_STRING, Append_Value(blk), Copy_OS_Str(str, eq-str)); - Set_Series(REB_STRING, Append_Value(blk), Copy_OS_Str(eq+1, n-(eq-str)-1)); - str += n + 1; // next - } + Push_Mold(&mo); - Block_As_Map(blk); + for (value = VAL_ARRAY_AT(blk); NOT_END(value); value++) { + Mold_Value(&mo, value, FALSE); + Append_Codepoint_Raw(mo.series, '\0'); + } + Append_Codepoint_Raw(mo.series, '\0'); - return blk; + return Pop_Molded_String(&mo); } -#ifdef TO_WIN32 -/*********************************************************************** -** -*/ REBSER *Block_To_String_List(REBVAL *blk) -/* -** Convert block of values to a string that holds -** a series of null terminated strings, followed -** by a final terminating string. -** -***********************************************************************/ +// +// File_List_To_Array: C +// +// Convert file directory and file name list to block. +// +static REBARR *File_List_To_Array(const REBCHR *str) { - REB_MOLD mo = {0}; - REBVAL *value; - - Reset_Mold(&mo); - - for (value = VAL_BLK_DATA(blk); NOT_END(value); value++) { - Mold_Value(&mo, value, 0); - Append_Byte(mo.series, 0); - } - Append_Byte(mo.series, 0); + REBCNT n; + REBCNT len = 0; + const REBCHR *start = str; + REBARR *blk; + REBSER *dir; + + while ((n = OS_STRLEN(str))) { + len++; + str += n + 1; // next + } + + blk = Make_Array(len); + + // First is a dir path or full file path: + str = start; + n = OS_STRLEN(str); + + if (len == 1) { // First is full file path + dir = To_REBOL_Path(str, n, (OS_WIDE ? PATH_OPT_UNI_SRC : 0)); + Init_File(Alloc_Tail_Array(blk), dir); + } + else { // First is dir path for the rest of the files +#ifdef TO_WINDOWS /* directory followed by files */ + assert(sizeof(wchar_t) == sizeof(REBCHR)); + dir = To_REBOL_Path( + str, + n, + PATH_OPT_UNI_SRC | PATH_OPT_FORCE_UNI_DEST | PATH_OPT_SRC_IS_DIR + ); + str += n + 1; // next + len = SER_LEN(dir); + while ((n = OS_STRLEN(str))) { + SET_SERIES_LEN(dir, len); + Append_Uni_Uni(dir, cast(const REBUNI*, str), n); + Init_File(Alloc_Tail_Array(blk), Copy_String_Slimming(dir, 0, -1)); + str += n + 1; // next + } +#else /* absolute pathes already */ + str += n + 1; + while ((n = OS_STRLEN(str))) { + dir = To_REBOL_Path(str, n, (OS_WIDE ? PATH_OPT_UNI_SRC : 0)); + Init_File(Alloc_Tail_Array(blk), Copy_String_Slimming(dir, 0, -1)); + str += n + 1; // next + } +#endif + } - return Copy_Series(mo.series); // Unicode + return blk; } -/*********************************************************************** -** -*/ static REBSER *File_List_To_Block(REBCHR *str) -/* -** Convert file directory and file name list to block. -** -***********************************************************************/ +// +// request-file: native [ +// +// {Asks user to select a file and returns full file path (or block of paths).} +// +// /save +// "File save mode" +// /multi +// {Allows multiple file selection, returned as a block} +// /file +// name [file!] +// "Default file name or directory" +// /title +// text [string!] +// "Window title" +// /filter +// list [block!] +// "Block of filters (filter-name filter)" +// ] +// +REBNATIVE(request_file) { - REBCNT n; - REBCNT len = 0; - REBCHR *start = str; - REBSER *blk; - REBSER *dir; - - while (n = LEN_STR(str)) { - len++; - str += n + 1; // next - } - - blk = Make_Block(len); - - // First is a dir path or full file path: - str = start; - n = LEN_STR(str); - - if (len == 1) { // First is full file path - dir = To_REBOL_Path(str, n, -1, 0); - Set_Series(REB_FILE, Append_Value(blk), dir); - } - else { // First is dir path for the rest of the files - dir = To_REBOL_Path(str, n, -1, TRUE); - str += n + 1; // next - len = dir->tail; - while (n = LEN_STR(str)) { - dir->tail = len; - Append_Uni_Uni(dir, str, n); - Set_Series(REB_FILE, Append_Value(blk), Copy_String(dir, 0, -1)); - str += n + 1; // next - } - } - - return blk; + INCLUDE_PARAMS_OF_REQUEST_FILE; + + // !!! This routine used to have an ENABLE_GC and DISABLE_GC + // reference. It is not clear what that was protecting, but + // this code should be reviewed with GC "torture mode", and + // if any values are being created which cannot be GC'd then + // they should be created without handing them over to GC with + // MANAGE_SERIES() instead. + + REBRFR fr; + CLEARS(&fr); + fr.files = OS_ALLOC_N(REBCHR, MAX_FILE_REQ_BUF); + fr.len = MAX_FILE_REQ_BUF/sizeof(REBCHR) - 2; + fr.files[0] = OS_MAKE_CH('\0'); + + if (REF(save)) + SET_FLAG(fr.flags, FRF_SAVE); + if (REF(multi)) + SET_FLAG(fr.flags, FRF_MULTI); + + if (REF(file)) { + REBSER *ser = Value_To_OS_Path(ARG(name), TRUE); + REBINT n = SER_LEN(ser); + + fr.dir = SER_HEAD(REBCHR, ser); + + if (OS_CH_VALUE(fr.dir[n - 1]) != OS_DIR_SEP) { + if (n + 2 > fr.len) + n = fr.len - 2; + OS_STRNCPY( + cast(REBCHR*, fr.files), + SER_HEAD(REBCHR, ser), + n + ); + fr.files[n] = OS_MAKE_CH('\0'); + } + } + + if (REF(filter)) { + REBSER *ser = Block_To_String_List(ARG(list)); + fr.filter = SER_HEAD(REBCHR, ser); + } + + if (REF(title)) { + // !!! By passing NULL we don't get backing series to protect! + fr.title = Val_Str_To_OS_Managed(NULL, ARG(text)); + } + + if (OS_REQUEST_FILE(&fr)) { + if (GET_FLAG(fr.flags, FRF_MULTI)) { + REBARR *array = File_List_To_Array(fr.files); + Init_Block(D_OUT, array); + } + else { + REBSER *ser = To_REBOL_Path( + fr.files, OS_STRLEN(fr.files), (OS_WIDE ? PATH_OPT_UNI_SRC : 0) + ); + Init_File(D_OUT, ser); + } + } else + Init_Blank(D_OUT); + + OS_FREE(fr.files); + + return R_OUT; } -#endif -/*********************************************************************** -** -*/ REBNATIVE(request_file) -/* -***********************************************************************/ +// +// get-env: native [ +// +// {Returns the value of an OS environment variable (for current process).} +// +// return: [string! blank!] +// {The string of the environment variable, or blank if not set} +// var [any-string! any-word!] +// ] +// +REBNATIVE(get_env) { -#ifdef TO_WIN32 - REBRFR fr = {0}; - REBSER *ser; - REBINT n; - - fr.files = OS_MAKE(MAX_FILE_REQ_BUF); - fr.len = MAX_FILE_REQ_BUF/sizeof(REBCHR) - 2; - fr.files[0] = 0; - - DISABLE_GC; - - if (D_REF(ARG_REQUEST_FILE_SAVE)) SET_FLAG(fr.flags, FRF_SAVE); - if (D_REF(ARG_REQUEST_FILE_MULTI)) SET_FLAG(fr.flags, FRF_MULTI); - - if (D_REF(ARG_REQUEST_FILE_FILE)) { - ser = Value_To_OS_Path(D_ARG(ARG_REQUEST_FILE_NAME)); - fr.dir = (REBCHR*)(ser->data); - n = ser->tail; - if (fr.dir[n-1] != OS_DIR_SEP) { - if (n+2 > fr.len) n = fr.len - 2; - COPY_STR(fr.files, (REBCHR*)(ser->data), n); - fr.files[n] = 0; - } - } - - if (D_REF(ARG_REQUEST_FILE_FILTER)) { - ser = Block_To_String_List(D_ARG(ARG_REQUEST_FILE_LIST)); - fr.filter = (REBCHR*)(ser->data); - } - - if (D_REF(ARG_REQUEST_FILE_TITLE)) - fr.title = Val_Str_To_OS(D_ARG(ARG_REQUEST_FILE_TEXT)); - - if (OS_REQUEST_FILE(&fr)) { - if (GET_FLAG(fr.flags, FRF_MULTI)) { - ser = File_List_To_Block(fr.files); - Set_Block(D_RET, ser); - } - else { - ser = To_REBOL_Path(fr.files, LEN_STR(fr.files), OS_WIDE, 0); - Set_Series(REB_FILE, D_RET, ser); - } - } else - ser = 0; - - ENABLE_GC; - OS_FREE(fr.files); - - return ser ? R_RET : R_NONE; -#else - return R_NONE; -#endif -} + INCLUDE_PARAMS_OF_GET_ENV; + REBVAL *var = ARG(var); -/*********************************************************************** -** -*/ REBNATIVE(get_env) -/* -***********************************************************************/ -{ - REBCHR *cmd; - REBINT lenplus; - REBCHR *buf; - REBVAL *arg = D_ARG(1); + Check_Security(Canon(SYM_ENVR), POL_READ, var); - Check_Security(SYM_ENVR, POL_READ, arg); + if (ANY_WORD(var)) { + REBSER *copy = Copy_Form_Value(var, 0); + Init_String(var, copy); + } - if (ANY_WORD(arg)) Set_String(arg, Copy_Form_Value(arg, 0)); - cmd = Val_Str_To_OS(arg); + // !!! By passing NULL we don't get backing series to protect! + REBCHR *os_var = Val_Str_To_OS_Managed(NULL, var); - lenplus = OS_GET_ENV(cmd, (REBCHR*)0, 0); - if (lenplus == 0) return R_NONE; - if (lenplus < 0) return R_UNSET; + REBINT lenplus = OS_GET_ENV(NULL, os_var, 0); + if (lenplus < 0) + return R_BLANK; + if (lenplus == 0) { + Init_String(D_OUT, Copy_Sequence(VAL_SERIES(EMPTY_STRING))); + return R_OUT; + } - // Two copies...is there a better way? - buf = MAKE_STR(lenplus); - OS_GET_ENV(cmd, buf, lenplus); - Set_String(D_RET, Copy_OS_Str(buf, lenplus - 1)); - FREE_MEM(buf); + // Two copies...is there a better way? + REBCHR *buf = ALLOC_N(REBCHR, lenplus); + OS_GET_ENV(buf, os_var, lenplus); + Init_String(D_OUT, Copy_OS_Str(buf, lenplus - 1)); + FREE_N(REBCHR, lenplus, buf); - return R_RET; + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(set_env) -/* -***********************************************************************/ +// +// set-env: native [ +// +// {Sets value of operating system environment variable for current process.} +// +// var [any-string! any-word!] +// "Variable to set" +// value [string! blank!] +// "Value to set, or a BLANK! to unset it" +// ] +// +REBNATIVE(set_env) { - REBCHR *cmd; - REBVAL *arg1 = D_ARG(1); - REBVAL *arg2 = D_ARG(2); - REBOOL success; - - Check_Security(SYM_ENVR, POL_WRITE, arg1); - - if (ANY_WORD(arg1)) Set_String(arg1, Copy_Form_Value(arg1, 0)); - cmd = Val_Str_To_OS(arg1); - - if (ANY_STR(arg2)) { - REBCHR *value = Val_Str_To_OS(arg2); - success = OS_SET_ENV(cmd, value); - if (success) { - // What function could reuse arg2 as-is? - Set_String(D_RET, Copy_OS_Str(value, LEN_STR(value))); - return R_RET; - } - return R_UNSET; - } - - if (IS_NONE(arg2)) { - success = OS_SET_ENV(cmd, 0); - if (success) - return R_NONE; - return R_UNSET; - } - - // is there any checking that native interface has not changed - // out from under the expectations of the code? - - return R_UNSET; + INCLUDE_PARAMS_OF_SET_ENV; + + REBVAL *var = ARG(var); + REBVAL *value = ARG(value); + + Check_Security(Canon(SYM_ENVR), POL_WRITE, var); + + if (ANY_WORD(var)) { + REBSER *copy = Copy_Form_Value(var, 0); + Init_String(var, copy); + } + + // !!! By passing NULL we don't get backing series to protect! + REBCHR *os_var = Val_Str_To_OS_Managed(NULL, var); + + if (ANY_STRING(value)) { + // !!! By passing NULL we don't get backing series to protect! + REBCHR *os_value = Val_Str_To_OS_Managed(NULL, value); + if (OS_SET_ENV(os_var, os_value)) { + // What function could reuse arg2 as-is? + Init_String(D_OUT, Copy_OS_Str(os_value, OS_STRLEN(os_value))); + return R_OUT; + } + return R_VOID; + } + + assert(IS_BLANK(value)); + + if (OS_SET_ENV(os_var, NULL)) + return R_BLANK; + return R_VOID; } -/*********************************************************************** -** -*/ REBNATIVE(list_env) -/* -***********************************************************************/ +// +// list-env: native [ +// +// {Returns a map of OS environment variables (for current process).} +// +// ; No arguments +// ] +// +REBNATIVE(list_env) { - REBCHR *result = OS_LIST_ENV(); - - Set_Series(REB_MAP, D_RET, String_List_To_Block(result)); + REBARR *array = String_List_To_Array(OS_LIST_ENV()); + REBMAP *map = Mutate_Array_Into_Map(array); + Init_Map(D_OUT, map); - return R_RET; + return R_OUT; } + diff --git a/src/core/n-loop.c b/src/core/n-loop.c old mode 100644 new mode 100755 index 18c1ae7efb..5576bafb0a --- a/src/core/n-loop.c +++ b/src/core/n-loop.c @@ -1,686 +1,1317 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: n-loop.c -** Summary: native functions for loops -** Section: natives -** Author: Carl Sassenrath -** Notes: -** Warning: Do not cache pointer to stack ARGS (stack may expand). -** -***********************************************************************/ +// +// File: %n-loop.c +// Summary: "native functions for loops" +// Section: natives +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" +#include "sys-int-funcs.h" //REB_I64_ADD_OF + +typedef enum { + LOOP_FOR_EACH, + LOOP_REMOVE_EACH, + LOOP_MAP_EACH, + LOOP_EVERY +} LOOP_MODE; + + +// +// Catching_Break_Or_Continue: C +// +// Determines if a thrown value is either a break or continue. If so, +// modifies `val` to be the throw's argument, sets `stop` flag if it +// was a BREAK or BREAK/WITH, and returns TRUE. +// +// If FALSE is returned then the throw name `val` was not a break +// or continue, and needs to be bubbled up or handled another way. +// +REBOOL Catching_Break_Or_Continue(REBVAL *val, REBOOL *stop) +{ + assert(THROWN(val)); + + // Throw /NAME-s used by CONTINUE and BREAK are the actual native + // function values of the routines themselves. + if (!IS_FUNCTION(val)) + return FALSE; + + if (VAL_FUNC_DISPATCHER(val) == &N_break) { + *stop = TRUE; // was BREAK or BREAK/WITH + CATCH_THROWN(val, val); // will be void if no /WITH was used + return TRUE; + } + + if (VAL_FUNC_DISPATCHER(val) == &N_continue) { + *stop = FALSE; // was CONTINUE or CONTINUE/WITH + CATCH_THROWN(val, val); // will be void if no /WITH was used + return TRUE; + } + + // Else: Let all other thrown values bubble up. + return FALSE; +} -/*********************************************************************** -** -*/ static REBSER *Init_Loop(REBVAL *spec, REBVAL *body_blk, REBSER **fram) -/* -** Initialize standard for loops (copy block, make frame, bind). -** Spec: WORD or [WORD ...] -** -***********************************************************************/ +// +// break: native [ +// +// {Exit the current iteration of a loop and stop iterating further.} +// +// /with +// {Act as if loop body finished current evaluation with a value} +// value [any-value!] +// ] +// +REBNATIVE(break) +// +// BREAK is implemented via a THROWN() value that bubbles up through +// the stack. It uses the value of its own native function as the +// name of the throw, like `throw/name value :break`. { - REBSER *frame; - REBINT len; - REBVAL *word; - REBVAL *vals; - REBSER *body; - - // For :WORD format, get the var's value: - if (IS_GET_WORD(spec)) spec = Get_Var(spec); - - // Hand-make a FRAME (done for for speed): - len = IS_BLOCK(spec) ? VAL_LEN(spec) : 1; - if (len == 0) Trap_Arg(spec); - frame = Make_Frame(len); - SET_SELFLESS(frame); - SERIES_TAIL(frame) = len+1; - SERIES_TAIL(FRM_WORD_SERIES(frame)) = len+1; - - // Setup for loop: - word = FRM_WORD(frame, 1); // skip SELF - vals = BLK_SKIP(frame, 1); - if (IS_BLOCK(spec)) spec = VAL_BLK_DATA(spec); - - // Optimally create the FOREACH frame: - while (len-- > 0) { - if (!IS_WORD(spec) && !IS_SET_WORD(spec)) { - // Prevent inconsistent GC state: - Free_Series(FRM_WORD_SERIES(frame)); - Free_Series(frame); - Trap_Arg(spec); - } - VAL_SET(word, VAL_TYPE(spec)); - VAL_BIND_SYM(word) = VAL_WORD_SYM(spec); - VAL_BIND_TYPESET(word) = ALL_64; - word++; - SET_NONE(vals); - vals++; - spec++; - } - SET_END(word); - SET_END(vals); - - body = Clone_Block_Value(body_blk); - Bind_Block(frame, BLK_HEAD(body), BIND_DEEP); - - *fram = frame; - - return body; + INCLUDE_PARAMS_OF_BREAK; + + Move_Value(D_OUT, NAT_VALUE(break)); + + CONVERT_NAME_TO_THROWN(D_OUT, REF(with) ? ARG(value) : VOID_CELL); + + return R_OUT_IS_THROWN; } -/*********************************************************************** -** -*/ static void Loop_Series(REBVAL *var, REBSER* body, REBVAL *start, REBINT ei, REBINT ii) -/* -***********************************************************************/ +// +// continue: native [ +// +// "Throws control back to top of loop for next iteration." +// +// /with +// {Act as if loop body finished current evaluation with a value} +// value [any-value!] +// ] +// +REBNATIVE(continue) +// +// CONTINUE is implemented via a THROWN() value that bubbles up through +// the stack. It uses the value of its own native function as the +// name of the throw, like `throw/name value :continue`. { - REBVAL *result; - REBINT si = VAL_INDEX(start); - REBCNT type = VAL_TYPE(start); - - *var = *start; - - if (ei >= (REBINT)VAL_TAIL(start)) ei = (REBINT)VAL_TAIL(start); - if (ei < 0) ei = 0; - - for (; (ii > 0) ? si <= ei : si >= ei; si += ii) { - VAL_INDEX(var) = si; - result = Do_Blk(body, 0); - if (THROWN(result) && Check_Error(result) >= 0) break; - if (VAL_TYPE(var) != type) Trap1(RE_INVALID_TYPE, var); - si = VAL_INDEX(var); - } + INCLUDE_PARAMS_OF_CONTINUE; + + Move_Value(D_OUT, NAT_VALUE(continue)); + + CONVERT_NAME_TO_THROWN(D_OUT, REF(with) ? ARG(value) : VOID_CELL); + + return R_OUT_IS_THROWN; } -/*********************************************************************** -** -*/ static void Loop_Integer(REBVAL *var, REBSER* body, REBI64 start, REBI64 end, REBI64 incr) -/* -***********************************************************************/ -{ - REBVAL *result; - - VAL_SET(var, REB_INTEGER); - - for (; (incr > 0) ? start <= end : start >= end; start += incr) { - VAL_INT64(var) = start; - result = Do_Blk(body, 0); - if (THROWN(result) && Check_Error(result) >= 0) break; - if (!IS_INTEGER(var)) Trap_Type(var); - start = VAL_INT64(var); - } +// +// Copy_Body_Deep_Bound_To_New_Context: C +// +// Looping constructs which are parameterized by WORD!s to set each time +// through the loop must copy the body in R3-Alpha's model. For instance: +// +// for-each [x y] [1 2 3] [print ["this body must be copied for" x y]] +// +// The reason is because the context in which X and Y live does not exist +// prior to the execution of the FOR-EACH. And if the body were destructively +// rebound, then this could mutate and disrupt bindings of code that was +// intended to be reused. +// +// (Note that R3-Alpha was somewhat inconsistent on the idea of being +// sensitive about non-destructively binding arguments in this way. +// MAKE OBJECT! purposefully mutated bindings in the passed-in block.) +// +// The context is effectively an ordinary object, and outlives the loop: +// +// x-word: none +// for-each x [1 2 3] [x-word: 'x | break] +// get x-word ;-- returns 1 +// +// !!! Ren-C managed to avoid deep copying function bodies yet still get +// "specific binding" by means of "relative values" (RELVALs) and specifiers. +// Extending this approach is hoped to be able to avoid the deep copy. It +// may also be that the underlying data of the +// +// !!! With stack-backed contexts in Ren-C, it may be the case that the +// chunk stack is used as backing memory for the loop, so it can be freed +// when the loop is over and word lookups will error. +// +// Note that because we are copying the block in order to rebind it, the +// ensuing loop code will `Do_At_Throws(out, body, 0);`. Starting at +// zero is correct because the duplicate body has already had the +// items before its VAL_INDEX() omitted. +// +static REBARR *Copy_Body_Deep_Bound_To_New_Context( + REBCTX **context_out, + const REBVAL *spec, + REBVAL *body +) { + assert(IS_BLOCK(body)); + + REBINT num_vars = IS_BLOCK(spec) ? VAL_LEN_AT(spec) : 1; + if (num_vars == 0) + fail (spec); + + REBCTX *context = Alloc_Context(REB_OBJECT, num_vars); + TERM_ARRAY_LEN(CTX_VARLIST(context), num_vars + 1); + TERM_ARRAY_LEN(CTX_KEYLIST(context), num_vars + 1); + + REBVAL *key = CTX_KEYS_HEAD(context); + REBVAL *var = CTX_VARS_HEAD(context); + + const RELVAL *item; + REBSPC *specifier; + if (IS_BLOCK(spec)) { + item = VAL_ARRAY_AT(spec); + specifier = VAL_SPECIFIER(spec); + } + else { + item = spec; + specifier = SPECIFIED; + } + + while (num_vars-- > 0) { + if (!IS_WORD(item) && !IS_SET_WORD(item)) + fail (Error_Invalid_Arg_Core(item, specifier)); + + Init_Typeset(key, ALL_64, VAL_WORD_SPELLING(item)); + key++; + + Init_Void(var); + var++; + + ++item; + } + + assert(IS_END(key)); // set above by TERM_ARRAY_LEN + assert(IS_END(var)); // ...same + + REBARR *body_out = Copy_Array_At_Deep_Managed( + VAL_ARRAY(body), VAL_INDEX(body), VAL_SPECIFIER(body) + ); + Bind_Values_Deep(ARR_HEAD(body_out), context); + + *context_out = context; + + return body_out; } -/*********************************************************************** -** -*/ static void Loop_Number(REBVAL *var, REBSER* body, REBVAL *start, REBVAL *end, REBVAL *incr) -/* -***********************************************************************/ -{ - REBVAL *result; - REBDEC s; - REBDEC e; - REBDEC i; - - if (IS_INTEGER(start)) s = (REBDEC)VAL_INT64(start); - else if (IS_DECIMAL(start) || IS_PERCENT(start)) s = VAL_DECIMAL(start); - else Trap_Arg(start); - - if (IS_INTEGER(end)) e = (REBDEC)VAL_INT64(end); - else if (IS_DECIMAL(end) || IS_PERCENT(end)) e = VAL_DECIMAL(end); - else Trap_Arg(end); - - if (IS_INTEGER(incr)) i = (REBDEC)VAL_INT64(incr); - else if (IS_DECIMAL(incr) || IS_PERCENT(incr)) i = VAL_DECIMAL(incr); - else Trap_Arg(incr); - - VAL_SET(var, REB_DECIMAL); - - for (; (i > 0.0) ? s <= e : s >= e; s += i) { - VAL_DECIMAL(var) = s; - result = Do_Blk(body, 0); - if (THROWN(result) && Check_Error(result) >= 0) break; - if (!IS_DECIMAL(var)) Trap_Type(var); - s = VAL_DECIMAL(var); - } +// +// Loop_Series_Common: C +// +static REB_R Loop_Series_Common( + REBVAL *out, + REBVAL *var, + REBARR *body, + REBVAL *start, + REBINT ei, + REBINT ii +) { + assert(IS_END(out)); + + REBINT si = VAL_INDEX(start); + enum Reb_Kind type = VAL_TYPE(start); + + Move_Value(var, start); + + if (ei >= cast(REBINT, VAL_LEN_HEAD(start))) + ei = cast(REBINT, VAL_LEN_HEAD(start)); + + if (ei < 0) ei = 0; + + for (; (ii > 0) ? si <= ei : si >= ei; si += ii) { + VAL_INDEX(var) = si; + + // loop bodies are copies at the moment, so fully specified; there + // may be a point to making it more efficient by not always copying + // + if (Do_At_Throws(out, body, 0, SPECIFIED)) { + REBOOL stop; + if (Catching_Break_Or_Continue(out, &stop)) { + if (stop) + return R_BLANK; + goto next_iteration; + } + return R_OUT_IS_THROWN; + } + + next_iteration: + if (VAL_TYPE(var) != type) fail (Error_Invalid_Type(VAL_TYPE(var))); + si = VAL_INDEX(var); + } + + return R_OUT_VOID_IF_UNWRITTEN_TRUTHIFY; } -/*********************************************************************** -** -*/ static int Loop_All(REBVAL *ds, REBINT mode) -/* -** 0: forall -** 1: forskip -** -***********************************************************************/ -{ - REBVAL *var; - REBSER *body; - REBCNT bodi; - REBSER *dat; - REBINT idx; - REBINT inc = 1; - REBCNT type; - - var = Get_Var(D_ARG(1)); - if (IS_NONE(var)) return R_NONE; - - // Save the starting var value: - *D_ARG(1) = *var; - - SET_NONE(D_RET); - - if (mode == 1) inc = Int32(D_ARG(2)); - - type = VAL_TYPE(var); - body = VAL_SERIES(D_ARG(mode+2)); - bodi = VAL_INDEX(D_ARG(mode+2)); - - // Starting location when past end with negative skip: - if (inc < 0 && VAL_INDEX(var) >= (REBINT)VAL_TAIL(var)) { - VAL_INDEX(var) = (REBINT)VAL_TAIL(var) + inc; - } - - // NOTE: This math only works for index in positive ranges! - - if (ANY_SERIES(var)) { - while (TRUE) { - dat = VAL_SERIES(var); - idx = (REBINT)VAL_INDEX(var); - if (idx < 0) break; - if (idx >= (REBINT)SERIES_TAIL(dat)) { - if (inc >= 0) break; - idx = (REBINT)SERIES_TAIL(dat) + inc; // negative - if (idx < 0) break; - VAL_INDEX(var) = idx; - } - - ds = Do_Blk(body, bodi); // (may move stack) - - if (THROWN(ds)) { // Break, throw, continue, error. - if (Check_Error(ds) >= 0) { - *DS_RETURN = *DS_NEXT; - break; - } - } - *DS_RETURN = *ds; - - if (VAL_TYPE(var) != type) Trap_Arg(var); - - VAL_INDEX(var) += inc; - } - } - else Trap_Arg(var); - - // !!!!! ???? allowed to write VAR???? - *var = *DS_ARG(1); - - return R_RET; +// +// Loop_Integer_Common: C +// +static REB_R Loop_Integer_Common( + REBVAL *out, + REBVAL *var, + REBARR *body, + REBI64 start, + REBI64 end, + REBI64 incr +) { + assert(IS_END(out)); + + VAL_RESET_HEADER(var, REB_INTEGER); + + while ((incr > 0) ? start <= end : start >= end) { + VAL_INT64(var) = start; + + if (Do_At_Throws(out, body, 0, SPECIFIED)) { + REBOOL stop; + if (Catching_Break_Or_Continue(out, &stop)) { + if (stop) + return R_BLANK; + goto next_iteration; + } + return R_OUT_IS_THROWN; + } + + next_iteration: + if (!IS_INTEGER(var)) + fail (Error_Invalid_Type(VAL_TYPE(var))); + + start = VAL_INT64(var); + + if (REB_I64_ADD_OF(start, incr, &start)) + fail (Error_Overflow_Raw()); + } + + return R_OUT_VOID_IF_UNWRITTEN_TRUTHIFY; } -/*********************************************************************** -** -*/ static int Loop_Each(REBVAL *ds, REBINT mode) -/* -** Supports these natives (modes): -** 0: foreach -** 1: remove-each -** 2: map -** -***********************************************************************/ -{ - REBSER *body; - REBVAL *vars; - REBVAL *words; - REBSER *frame; - REBVAL *value; - REBSER *series; - REBSER *out; // output block (for MAP, mode = 2) - - REBINT index; // !!!! should these be REBCNT? - REBINT tail; - REBINT windex; // write - REBINT rindex; // read - REBINT err; - REBCNT i; - REBCNT j; - - value = D_ARG(2); // series - if (IS_NONE(value)) return R_NONE; - - body = Init_Loop(D_ARG(1), D_ARG(3), &frame); // vars, body - SET_OBJECT(D_ARG(1), frame); // keep GC safe - Set_Block(D_ARG(3), body); // keep GC safe - - SET_NONE(D_RET); - SET_NONE(DS_NEXT); - - // If it's MAP, create result block: - if (mode == 2) { - out = Make_Block(VAL_LEN(value)); - Set_Block(D_RET, out); - } - - // Get series info: - if (ANY_OBJECT(value)) { - series = VAL_OBJ_FRAME(value); - out = FRM_WORD_SERIES(series); // words (the out local reused) - index = 1; - //if (frame->tail > 3) Trap_Arg(FRM_WORD(frame, 3)); - } - else if (IS_MAP(value)) { - series = VAL_SERIES(value); - index = 0; - //if (frame->tail > 3) Trap_Arg(FRM_WORD(frame, 3)); - } - else { - series = VAL_SERIES(value); - index = VAL_INDEX(value); - if (index >= (REBINT)SERIES_TAIL(series)) { - if (mode == 1) { - SET_INTEGER(D_RET, 0); - } - return R_RET; - } - } - - windex = index; - - // Iterate over each value in the series block: - while (index < (tail = SERIES_TAIL(series))) { - - rindex = index; // remember starting spot - j = 0; - - // Set the FOREACH loop variables from the series: - for (i = 1; i < frame->tail; i++) { - - vars = FRM_VALUE(frame, i); - words = FRM_WORD(frame, i); - - // var spec is WORD - if (IS_WORD(words)) { - - if (index < tail) { - - if (ANY_BLOCK(value)) { - *vars = *BLK_SKIP(series, index); - } - - else if (ANY_OBJECT(value)) { - if (!VAL_GET_OPT(BLK_SKIP(out, index), OPTS_HIDE)) { - // Alternate between word and value parts of object: - if (j == 0) { - Set_Word(vars, VAL_WORD_SYM(BLK_SKIP(out, index)), series, index); - if (NOT_END(vars+1)) index--; // reset index for the value part - } - else if (j == 1) - *vars = *BLK_SKIP(series, index); - else - Trap_Arg(words); - j++; - } - else { - // Do not evaluate this iteration - index++; - goto skip_hidden; - } - } - - else if (IS_VECTOR(value)) { - Set_Vector_Value(vars, series, index); - } - - else if (IS_MAP(value)) { - REBVAL *val = BLK_SKIP(series, index | 1); - if (!IS_NONE(val)) { - if (j == 0) { - *vars = *BLK_SKIP(series, index & ~1); - if (IS_END(vars+1)) index++; // only words - } - else if (j == 1) - *vars = *BLK_SKIP(series, index); - else - Trap_Arg(words); - j++; - } - else { - index += 2; - goto skip_hidden; - } - } - - else { // A string or binary - if (IS_BINARY(value)) { - SET_INTEGER(vars, (REBI64)(BIN_HEAD(series)[index])); - } - else if (IS_IMAGE(value)) { - Set_Tuple_Pixel(BIN_SKIP(series, index), vars); - } - else { - VAL_SET(vars, REB_CHAR); - VAL_CHAR(vars) = GET_ANY_CHAR(series, index); - } - } - index++; - } - else SET_NONE(vars); - } - - // var spec is WORD: - else if (IS_SET_WORD(words)) { - if (ANY_OBJECT(value) || IS_MAP(value)) { - *vars = *value; - } else { - VAL_SET(vars, REB_BLOCK); - VAL_SERIES(vars) = series; - VAL_INDEX(vars) = index; - } - //if (index < tail) index++; // do not increment block. - } - else Trap_Arg(words); - } - - ds = Do_Blk(body, 0); - - if (THROWN(ds)) { - if ((err = Check_Error(ds)) >= 0) break; - // else CONTINUE: - if (mode == 1) SET_FALSE(ds); // keep the value (for mode == 1) - } else { - err = 0; // prevent later test against uninitialized value - } - - if (mode > 0) { - //if (ANY_OBJECT(value)) Trap_Types(words, REB_BLOCK, VAL_TYPE(value)); //check not needed - - // If FALSE return, copy values to the write location: - if (mode == 1) { // remove-each - if (IS_FALSE(ds)) { - REBCNT wide = SERIES_WIDE(series); - // memory areas may overlap, so use memmove and not memcpy! - memmove(series->data + (windex * wide), series->data + (rindex * wide), (index - rindex) * wide); - windex += index - rindex; - // old: while (rindex < index) *BLK_SKIP(series, windex++) = *BLK_SKIP(series, rindex++); - } - } - else - if (!IS_UNSET(ds)) Append_Val(out, ds); // (mode == 2) - } -skip_hidden: ; - } +// +// Loop_Number_Common: C +// +static REB_R Loop_Number_Common( + REBVAL *out, + REBVAL *var, + REBARR *body, + REBVAL *start, + REBVAL *end, + REBVAL *incr +) { + assert(IS_END(out)); + + REBDEC s; + if (IS_INTEGER(start)) + s = cast(REBDEC, VAL_INT64(start)); + else if (IS_DECIMAL(start) || IS_PERCENT(start)) + s = VAL_DECIMAL(start); + else + fail (start); + + REBDEC e; + if (IS_INTEGER(end)) + e = cast(REBDEC, VAL_INT64(end)); + else if (IS_DECIMAL(end) || IS_PERCENT(end)) + e = VAL_DECIMAL(end); + else + fail (end); + + REBDEC i; + if (IS_INTEGER(incr)) + i = cast(REBDEC, VAL_INT64(incr)); + else if (IS_DECIMAL(incr) || IS_PERCENT(incr)) + i = VAL_DECIMAL(incr); + else + fail (incr); + + VAL_RESET_HEADER(var, REB_DECIMAL); + + for (; (i > 0.0) ? s <= e : s >= e; s += i) { + VAL_DECIMAL(var) = s; + + if (Do_At_Throws(out, body, 0, SPECIFIED)) { + REBOOL stop; + if (Catching_Break_Or_Continue(out, &stop)) { + if (stop) + return R_BLANK; + goto next_iteration; + } + return R_OUT_IS_THROWN; + } + + next_iteration: + if (!IS_DECIMAL(var)) + fail (Error_Invalid_Type(VAL_TYPE(var))); + + s = VAL_DECIMAL(var); + } + + return R_OUT_VOID_IF_UNWRITTEN_TRUTHIFY; +} - // Finish up: - if (mode == 1) { - // Remove hole (updates tail): - if (windex < index) Remove_Series(series, windex, index - windex); - SET_INTEGER(DS_RETURN, index - windex); - return R_RET; - } - // If MAP and not BREAK/RETURN: - if (mode == 2 && err != 2) return R_RET; +// +// Loop_Each: C +// +// Common implementation code of FOR-EACH, REMOVE-EACH, MAP-EACH, and EVERY. +// +// !!! This routine has been slowly clarifying since R3-Alpha, and can +// likely be factored in a better way...pushing more per-native code into the +// natives themselves. +// +static REB_R Loop_Each(REBFRM *frame_, LOOP_MODE mode) +{ + INCLUDE_PARAMS_OF_FOR_EACH; + + REBVAL *data = ARG(data); + assert(!IS_VOID(data)); + + if (IS_BLANK(data)) + return R_VOID; + + REBOOL stop = FALSE; + REBOOL threw = FALSE; // did a non-BREAK or non-CONTINUE throw occur + + assert(IS_END(D_OUT)); + if (mode == LOOP_EVERY) + SET_END(D_CELL); // Final result is in D_CELL (last TRUE? or a BLANK!) + + REBCTX *context; + REBARR *body_copy = Copy_Body_Deep_Bound_To_New_Context( + &context, + ARG(vars), + ARG(body) + ); + Init_Object(ARG(vars), context); // keep GC safe + Init_Block(ARG(body), body_copy); // keep GC safe + + // Currently the data stack is only used by MAP-EACH to accumulate results + // but it's faster to just save it than test the loop mode. + // + REBDSP dsp_orig = DSP; + + // Extract the series and index being enumerated, based on data type + + REBSER *series; + REBCNT index; + if (ANY_CONTEXT(data)) { + series = SER(CTX_VARLIST(VAL_CONTEXT(data))); + index = 1; + } + else if (IS_MAP(data)) { + series = VAL_SERIES(data); + index = 0; + } + else if (IS_DATATYPE(data)) { + // + // !!! Snapshotting the state is not particularly efficient. However, + // bulletproofing an enumeration of the system against possible GC + // would be difficult. And this is really just a debug/instrumentation + // feature anyway. + // + switch (VAL_TYPE_KIND(data)) { + case REB_FUNCTION: + series = SER(Snapshot_All_Functions()); + index = 0; + PUSH_GUARD_ARRAY_CONTENTS(ARR(series)); + break; + + default: + fail ("FUNCTION! is the only datatype with global enumeration"); + } + } + else { + series = VAL_SERIES(data); + index = VAL_INDEX(data); + if (index >= SER_LEN(series)) { + if (mode == LOOP_REMOVE_EACH) { + Init_Integer(D_OUT, 0); + return R_OUT; + } + else if (mode == LOOP_MAP_EACH) { + Init_Block(D_OUT, Make_Array(0)); + return R_OUT; + } + return R_VOID; + } + } + + REBCNT write_index = index; + + // Iterate over each value in the data series block: + + REBCNT tail; + while (index < (tail = SER_LEN(series))) { + REBCNT i; + REBCNT j = 0; + + REBVAL *key = CTX_KEY(context, 1); + REBVAL *var = CTX_VAR(context, 1); + + REBCNT read_index; + + read_index = index; // remember starting spot + + // Set the FOREACH loop variables from the series: + for (i = 1; NOT_END(key); i++, key++, var++) { + + if (index >= tail) { + Init_Blank(var); + continue; + } + + if (ANY_ARRAY(data)) { + Derelativize( + var, + ARR_AT(ARR(series), index), + VAL_SPECIFIER(data) // !!! always matches series? + ); + } + else if (IS_DATATYPE(data)) { + Derelativize( + var, + ARR_AT(ARR(series), index), + SPECIFIED // array generated via data stack, all specific + ); + } + else if (ANY_CONTEXT(data)) { + if (GET_VAL_FLAG( + VAL_CONTEXT_KEY(data, index), TYPESET_FLAG_HIDDEN + )) { + // Do not evaluate this iteration + index++; + goto skip_hidden; + } + + // Alternate between word and value parts of object: + if (j == 0) { + Init_Any_Word_Bound( + var, + REB_WORD, + CTX_KEY_SPELLING(VAL_CONTEXT(data), index), + CTX(series), + index + ); + if (NOT_END(var + 1)) { + // reset index for the value part + index--; + } + } + else if (j == 1) { + Derelativize( + var, + ARR_AT(ARR(series), index), + SPECIFIED // !!! it's a varlist + ); + } + else { + // !!! Review this error (and this routine...) + DECLARE_LOCAL (key_name); + Init_Word(key_name, VAL_KEY_SPELLING(key)); + + fail (key_name); + } + j++; + } + else if (IS_VECTOR(data)) { + Set_Vector_Value(var, series, index); + } + else if (IS_MAP(data)) { + // + // MAP! does not store RELVALs + // + REBVAL *val = KNOWN(ARR_AT(ARR(series), index | 1)); + if (!IS_VOID(val)) { + if (j == 0) { + Derelativize( + var, + ARR_AT(ARR(series), index & ~1), + SPECIFIED // maps always specified + ); + + if (IS_END(var + 1)) index++; // only words + } + else if (j == 1) { + Derelativize( + var, + ARR_AT(ARR(series), index), + SPECIFIED // maps always specified + ); + } + else { + // !!! Review this error (and this routine...) + DECLARE_LOCAL (key_name); + Init_Word(key_name, VAL_KEY_SPELLING(key)); + + fail (key_name); + } + j++; + } + else { + index += 2; + goto skip_hidden; + } + } + else if (IS_BINARY(data)) { + Init_Integer(var, (REBI64)(BIN_HEAD(series)[index])); + } + else if (IS_IMAGE(data)) { + Set_Tuple_Pixel(BIN_AT(series, index), var); + } + else { + assert(IS_STRING(data)); + VAL_RESET_HEADER(var, REB_CHAR); + VAL_CHAR(var) = GET_ANY_CHAR(series, index); + } + index++; + } + + assert(IS_END(key) && IS_END(var)); + + if (index == read_index) { + // the word block has only set-words: for-each [a:] [1 2 3][] + index++; + } + + if (Do_At_Throws(D_OUT, body_copy, 0, SPECIFIED)) { // copy, specified + if (!Catching_Break_Or_Continue(D_OUT, &stop)) { + // A non-loop throw, we should be bubbling up + threw = TRUE; + break; + } + + // Fall through and process the D_OUT (unset if no /WITH) for + // this iteration. `stop` flag will be checked ater that. + } + + switch (mode) { + case LOOP_FOR_EACH: + // no action needed after body is run + break; + + case LOOP_REMOVE_EACH: + // + // If body evaluates to FALSE, preserve the slot. Do the same + // for a void body, since that should have the same behavior as + // a CONTINUE with no /WITH (which most sensibly does not do + // a removal.) + // + if (IS_VOID(D_OUT) || IS_CONDITIONAL_FALSE(D_OUT)) { + // + // memory areas may overlap, so use memmove and not memcpy! + // + // !!! This seems a slow way to do it, but there's probably + // not a lot that can be done as the series is expected to + // be in a good state for the next iteration of the body. :-/ + // + memmove( + SER_AT_RAW(SER_WIDE(series), series, write_index), + SER_AT_RAW(SER_WIDE(series), series, read_index), + (index - read_index) * SER_WIDE(series) + ); + write_index += index - read_index; + } + break; + + case LOOP_MAP_EACH: + // anything that's not void will be added to the result + if (!IS_VOID(D_OUT)) + DS_PUSH(D_OUT); + break; + + case LOOP_EVERY: + if (IS_VOID(D_OUT)) { + // Unsets "opt out" of the vote, as with ANY and ALL + } + else if (IS_CONDITIONAL_FALSE(D_OUT)) + Init_Blank(D_CELL); // at least one false means blank result + else if (IS_END(D_CELL) || !IS_BLANK(D_CELL)) + Move_Value(D_CELL, D_OUT); + break; + default: + assert(FALSE); + } + + if (stop) { + Init_Blank(D_OUT); + break; + } - return R_TOS1; +skip_hidden: ; + } + + if (IS_DATATYPE(data)) { + // + // If asked to enumerate a datatype, we allocated a temporary array + // of all instances of that datatype. It has to be freed. + // + DROP_GUARD_ARRAY_CONTENTS(ARR(series)); + Free_Array(ARR(series)); + } + + if (threw) { + // a non-BREAK and non-CONTINUE throw overrides any other return + // result we might give (generic THROW, RETURN, QUIT, etc.) + + if (mode == LOOP_MAP_EACH) + DS_DROP_TO(dsp_orig); + + return R_OUT_IS_THROWN; + } + + // Note: This finalization will be run by finished loops as well as + // interrupted ones. So: + // + // map-each x [1 2 3 4] [if x = 3 [break]] => [1 2] + // + // map-each x [1 2 3 4] [if x = 3 [break/with "A"]] => [1 2 "A"] + // + // every x [1 3 6 12] [if x = 6 [break/with 7] even? x] => 7 + // + // This provides the most flexibility in the loop's processing, because + // "override" logic already exists in the form of CATCH & THROW. + +#if !defined(NDEBUG) + if (LEGACY(OPTIONS_BREAK_WITH_OVERRIDES)) { + // In legacy R3-ALPHA, BREAK without a provided value did *not* + // override the result. It returned the partial results. + if (stop && NOT_END(D_OUT)) + return R_OUT; + } +#endif + + if (stop) + return R_BLANK; + + switch (mode) { + case LOOP_FOR_EACH: + return R_OUT_VOID_IF_UNWRITTEN_TRUTHIFY; + + case LOOP_REMOVE_EACH: + // Remove hole (updates tail): + if (write_index < index) + Remove_Series(series, write_index, index - write_index); + Init_Integer(D_OUT, index - write_index); + return R_OUT; + + case LOOP_MAP_EACH: + Init_Block(D_OUT, Pop_Stack_Values(dsp_orig)); + return R_OUT; + + case LOOP_EVERY: + if (threw) + return R_OUT_IS_THROWN; + + if (IS_END(D_CELL)) + return R_VOID; // all evaluations opted out + + Move_Value(D_OUT, D_CELL); + return R_OUT; // should it be like R_OUT_VOID_IF_UNWRITTEN_TRUTHIFY? + + default: + assert(FALSE); + } + + DEAD_END; } -/*********************************************************************** -** -*/ REBNATIVE(for) -/* -** FOR var start end bump [ body ] -** -***********************************************************************/ +// +// for: native [ +// +// {Evaluate a block over a range of values. (See also: REPEAT)} +// +// return: [ any-value!] +// 'word [word!] +// "Variable to hold current value" +// start [any-series! any-number!] +// "Starting value" +// end [any-series! any-number!] +// "Ending value" +// bump [any-number!] +// "Amount to skip each time" +// body [block!] +// "Block to evaluate" +// ] +// +REBNATIVE(for) { - REBSER *body; - REBSER *frame; - REBVAL *var; - REBVAL *start = D_ARG(2); - REBVAL *end = D_ARG(3); - REBVAL *incr = D_ARG(4); - - // Copy body block, make a frame, bind loop var to it: - body = Init_Loop(D_ARG(1), D_ARG(5), &frame); - var = FRM_VALUE(frame, 1); // safe: not on stack - SET_OBJECT(D_ARG(1), frame); // keep GC safe - Set_Block(D_ARG(5), body); // keep GC safe - - SET_NONE(DS_NEXT); // in case nothing below happens - - // NOTE: during loop, stack may expand, so references to stack - // values must not be absolute. - - if (IS_INTEGER(start) && IS_INTEGER(end) && IS_INTEGER(incr)) { - Loop_Integer(var, body, VAL_INT64(start), - IS_DECIMAL(end) ? (REBI64)VAL_DECIMAL(end) : VAL_INT64(end), VAL_INT64(incr)); - } - else if (ANY_SERIES(start)) { - // Check that start and end are same type and series: - //if (ANY_SERIES(end) && VAL_SERIES(start) != VAL_SERIES(end)) Trap_Arg(end); - Loop_Series(var, body, start, ANY_SERIES(end) ? VAL_INDEX(end) : (Int32s(end, 1) - 1), Int32(incr)); - } - else - Loop_Number(var, body, start, end, incr); - - return R_TOS1; + INCLUDE_PARAMS_OF_FOR; + + REBCTX *context; + REBARR *body_copy = Copy_Body_Deep_Bound_To_New_Context( + &context, + ARG(word), + ARG(body) + ); + Init_Object(ARG(word), context); // keep GC safe + Init_Block(ARG(body), body_copy); // keep GC safe + + REBVAL *var = CTX_VAR(context, 1); + + if ( + IS_INTEGER(ARG(start)) + && IS_INTEGER(ARG(end)) + && IS_INTEGER(ARG(bump)) + ) { + return Loop_Integer_Common( + D_OUT, + var, + body_copy, + VAL_INT64(ARG(start)), + IS_DECIMAL(ARG(end)) + ? (REBI64)VAL_DECIMAL(ARG(end)) + : VAL_INT64(ARG(end)), + VAL_INT64(ARG(bump)) + ); + } + else if (ANY_SERIES(ARG(start))) { + if (ANY_SERIES(ARG(end))) { + return Loop_Series_Common( + D_OUT, + var, + body_copy, + ARG(start), + VAL_INDEX(ARG(end)), + Int32(ARG(bump)) + ); + } + else { + return Loop_Series_Common( + D_OUT, + var, + body_copy, + ARG(start), + Int32s(ARG(end), 1) - 1, + Int32(ARG(bump)) + ); + } + } + + return Loop_Number_Common( + D_OUT, var, body_copy, ARG(start), ARG(end), ARG(bump) + ); + } -/*********************************************************************** -** -*/ REBNATIVE(forall) -/* -***********************************************************************/ +// +// for-skip: native [ +// +// "Evaluates a block for periodic values in a series" +// +// return: [ any-value!] +// {Last body result or BREAK value, will also be void if never run} +// 'word [word! blank!] +// "Word that refers to the series, set to positions in the series" +// skip [integer!] +// "Number of positions to skip each time" +// body [block!] +// "Block to evaluate each time" +// ] +// +REBNATIVE(for_skip) +// +// !!! Should this fail on 0? It could be that the loop will break for some +// other reason, and the author didn't wish to special case to rule out zero... +// generality may dictate allowing it. { - return Loop_All(ds, 0); + INCLUDE_PARAMS_OF_FOR_SKIP; + + REBVAL *word = ARG(word); + + // Though we can only iterate on a series, BLANK! is used as a way of + // opting out. This could be useful, e.g. `for-next x (any ...) [...]` + // + if (IS_BLANK(word)) + return R_VOID; + + REBVAL *var = Get_Mutable_Var_May_Fail(word, SPECIFIED); + + if (NOT(ANY_SERIES(var))) + fail (var); + + REBINT skip = Int32(ARG(skip)); + + // Save the starting var value, assume `word` is a GC protected slot + // + Move_Value(word, var); + + // Starting location when past end with negative skip: + // + if (skip < 0 && VAL_INDEX(var) >= VAL_LEN_HEAD(var)) + VAL_INDEX(var) = VAL_LEN_HEAD(var) + skip; + + while (TRUE) { + REBINT len = VAL_LEN_HEAD(var); // VAL_LEN_HEAD() always >= 0 + REBINT index = VAL_INDEX(var); // (may have been set to < 0 below) + + if (index < 0) break; + if (index >= len) { + if (skip >= 0) break; + index = len + skip; // negative + if (index < 0) break; + VAL_INDEX(var) = index; + } + + if (Do_Any_Array_At_Throws(D_OUT, ARG(body))) { + REBOOL stop; + if (Catching_Break_Or_Continue(D_OUT, &stop)) { + if (stop) { + Move_Value(var, word); + return R_BLANK; + } + goto next_iteration; + } + return R_OUT_IS_THROWN; + } + + next_iteration: + // + // !!! The code in the body is allowed to modify the var. However, + // R3-Alpha checked to make sure that the type of the var did not + // change. This seemed like an arbitrary limitation and Ren-C + // removed it, only checking that it's a series. + // + if (IS_BLANK(var)) + return R_OUT; + + if (NOT(ANY_SERIES(var))) + fail (var); + + VAL_INDEX(var) += skip; + } + + Move_Value(var, word); + return R_OUT_VOID_IF_UNWRITTEN_TRUTHIFY; } -/*********************************************************************** -** -*/ REBNATIVE(forskip) -/* -***********************************************************************/ +// +// forever: native [ +// +// "Evaluates a block endlessly, until an interrupting throw/error/break." +// +// return: [ any-value!] +// {Void if plain BREAK, or arbitrary value using BREAK/WITH} +// body [block! function!] +// "Block or function to evaluate each time" +// ] +// +REBNATIVE(forever) { - return Loop_All(ds, 1); + INCLUDE_PARAMS_OF_FOREVER; + + do { + const REBOOL only = FALSE; + if (Run_Branch_Throws(D_OUT, ARG(body), only)) { + REBOOL stop; + if (Catching_Break_Or_Continue(D_OUT, &stop)) { + if (stop) + return R_BLANK; + continue; + } + return R_OUT_IS_THROWN; + } + } while (TRUE); + + DEAD_END; } -/*********************************************************************** -** -*/ REBNATIVE(forever) -/* -***********************************************************************/ +// +// for-each: native [ +// +// "Evaluates a block for each value(s) in a series." +// +// return: [ any-value!] +// {Last body result or BREAK value, will also be void if never run} +// 'vars [word! block!] +// "Word or block of words to set each time (local)" +// data [any-series! any-context! map! blank! datatype!] +// "The series to traverse" +// body [block!] +// "Block to evaluate each time" +// ] +// +REBNATIVE(for_each) { - REBVAL *result; - - SET_NONE(DS_NEXT); + return Loop_Each(frame_, LOOP_FOR_EACH); +} - while (1) { - result = DO_BLK(DS_ARG(1)); - if (THROWN(result) && Check_Error(result) >= 0) break; - } - return R_TOS1; +// +// remove-each: native [ +// +// {Removes values for each block that returns true; returns removal count.} +// +// 'vars [word! block!] +// "Word or block of words to set each time (local)" +// data [any-series!] +// "The series to traverse (modified)" +// body [block!] +// "Block to evaluate (return TRUE to remove)" +// ] +// +REBNATIVE(remove_each) +{ + return Loop_Each(frame_, LOOP_REMOVE_EACH); } -/*********************************************************************** -** -*/ REBNATIVE(foreach) -/* -** {Evaluates a block for each value(s) in a series.} -** 'word [get-word! word! block!] {Word or block of words} -** data [series!] {The series to traverse} -** body [block!] {Block to evaluate each time} -** -***********************************************************************/ +// +// map-each: native [ +// +// {Evaluate a block for each value(s) in a series and collect as a block.} +// +// return: [block!] +// {Collected block (BREAK/WITH can add a final result to block)} +// 'vars [word! block!] +// "Word or block of words to set each time (local)" +// data [block! vector!] +// "The series to traverse" +// body [block!] +// "Block to evaluate each time" +// ] +// +REBNATIVE(map_each) { - return Loop_Each(ds, 0); + return Loop_Each(frame_, LOOP_MAP_EACH); } -/*********************************************************************** -** -*/ REBNATIVE(remove_each) -/* -** 'word [get-word! word! block!] {Word or block of words} -** data [series!] {The series to traverse} -** body [block!] {Block to evaluate each time} -** -***********************************************************************/ +// +// every: native [ +// +// {Returns last TRUE? value if evaluating a block over a series is all TRUE?} +// +// return: [ any-value!] +// {TRUE or BLANK! collected, or BREAK value, TRUE if never run.} +// 'vars [word! block!] +// "Word or block of words to set each time (local)" +// data [any-series! any-context! map! blank! datatype!] +// "The series to traverse" +// body [block!] +// "Block to evaluate each time" +// ] +// +REBNATIVE(every) { - return Loop_Each(ds, 1); + return Loop_Each(frame_, LOOP_EVERY); } -/*********************************************************************** -** -*/ REBNATIVE(map_each) -/* -** 'word [get-word! word! block!] {Word or block of words} -** data [series!] {The series to traverse} -** body [block!] {Block to evaluate each time} -** -***********************************************************************/ +// +// loop: native [ +// +// "Evaluates a block a specified number of times." +// +// return: [ any-value!] +// {Last body result or BREAK value, will also be void if never run} +// count [any-number! logic! blank!] +// "Repetitions (true loops infinitely, FALSE? doesn't run)" +// body [block! function!] +// "Block to evaluate or function to run (may be a BRANCHER)." +// ] +// +REBNATIVE(loop) { - return Loop_Each(ds, 2); + INCLUDE_PARAMS_OF_LOOP; + + REBI64 count; + + if (IS_CONDITIONAL_FALSE(ARG(count))) { + // + // A NONE! or LOGIC! FALSE means don't run the loop at all. + // + return R_VOID; + } + + if (IS_LOGIC(ARG(count))) { + // + // (Must be TRUE). Run forever. As a micro-optimization we don't + // complicate the condition checking in the loop, but seed with a + // *very* large integer. In the off chance that we exhaust it, the + // code jumps up here, re-seeds it, and loops again. + // + restart: + count = MAX_I64; + } + else + count = Int64(ARG(count)); + + for (; count > 0; count--) { + const REBOOL only = FALSE; + if (Run_Branch_Throws(D_OUT, ARG(body), only)) { + REBOOL stop; + if (Catching_Break_Or_Continue(D_OUT, &stop)) { + if (stop) + return R_BLANK; + continue; + } + return R_OUT_IS_THROWN; + } + } + + if (IS_LOGIC(ARG(count))) { + // + // Rare case, "infinite" loop exhausted MAX_I64 steps... + // + goto restart; + } + + return R_OUT_VOID_IF_UNWRITTEN_TRUTHIFY; } -/*********************************************************************** -** -*/ REBNATIVE(loop) -/* -***********************************************************************/ +// +// repeat: native [ +// +// {Evaluates a block a number of times or over a series.} +// +// return: [ any-value!] +// {Last body result or BREAK value, will also be void if never run} +// 'word [word!] +// "Word to set each time" +// value [any-number! any-series! blank!] +// "Maximum number or series to traverse" +// body [block!] +// "Block to evaluate each time" +// ] +// +REBNATIVE(repeat) { - REBI64 count = Int64(D_ARG(1)); - REBSER *block = VAL_SERIES(D_ARG(2)); - REBCNT index = VAL_INDEX(D_ARG(2)); - - ds = 0; - for (; count > 0; count--) { - ds = Do_Blk(block, index); - if (THROWN(ds)) { - if (Check_Error(ds) >= 0) break; - } - } - if (ds) return R_TOS1; - return R_NONE; + INCLUDE_PARAMS_OF_REPEAT; + + REBVAL *value = ARG(value); + + if (IS_BLANK(value)) + return R_VOID; + + if (IS_DECIMAL(value) || IS_PERCENT(value)) + Init_Integer(value, Int64(value)); + + REBCTX *context; + REBARR *copy = Copy_Body_Deep_Bound_To_New_Context( + &context, + ARG(word), + ARG(body) + ); + + REBVAL *var = CTX_VAR(context, 1); + + Init_Object(ARG(word), context); // keep GC safe + Init_Block(ARG(body), copy); // keep GC safe + + if (ANY_SERIES(value)) { + return Loop_Series_Common( + D_OUT, var, copy, value, VAL_LEN_HEAD(value) - 1, 1 + ); + } + + assert(IS_INTEGER(value)); + + return Loop_Integer_Common(D_OUT, var, copy, 1, VAL_INT64(value), 1); } -/*********************************************************************** -** -*/ REBNATIVE(repeat) -/* -** REPEAT var 123 [ body ] -** -***********************************************************************/ +// Common code for LOOP-WHILE & LOOP-UNTIL (same frame param layout) +// +inline static REB_R Loop_While_Until_Core(REBFRM *frame_, REBOOL trigger) { - REBSER *body; - REBSER *frame; - REBVAL *var; - REBVAL *count = D_ARG(2); + INCLUDE_PARAMS_OF_LOOP_WHILE; + + do { + skip_check:; + + const REBOOL only = FALSE; + if (Run_Branch_Throws(D_OUT, ARG(body), only)) { + REBOOL stop; + if (Catching_Break_Or_Continue(D_OUT, &stop)) { + if (stop) + return R_BLANK; + + // LOOP-UNTIL and LOOP-WITH follow the precedent that the way + // a CONTINUE/WITH works is to act as if the loop body + // returned the value passed to the WITH...and that a CONTINUE + // lacking a WITH acts as if the body returned a void. + // + // Since the condition and body are the same in this case, + // the implications are a little strange (though logical). + // CONTINUE/WITH FALSE will break a LOOP-WHILE, and + // CONTINUE/WITH TRUE breaks a LOOP-UNTIL. + // + if (IS_VOID(D_OUT)) + goto skip_check; + + goto perform_check; + } + return R_OUT_IS_THROWN; + } + + // Since CONTINUE acts like reaching the end of the loop body with a + // void, the logical consequence is that reaching the end of *either* + // a LOOP-WHILE or a LOOP-UNTIL with a void just keeps going. This + // means that `loop-until [print "hi"]` and `loop-while [print "hi"]` + // are both infinite loops. + // + if (IS_VOID(D_OUT)) + goto skip_check; + + perform_check:; + } while (IS_CONDITIONAL_TRUE(D_OUT) == trigger); + + // Though LOOP-UNTIL will always have a truthy result, LOOP-WHILE never + // will, and needs to have the result overwritten with something TRUE? + // so BAR! is used. + // + if (trigger == TRUE) + return R_BAR; + + assert(IS_CONDITIONAL_TRUE(D_OUT)); + return R_OUT; +} - if (IS_NONE(count)) return R_NONE; - if (IS_DECIMAL(count) || IS_PERCENT(count)) { - VAL_INT64(count) = Int64(count); - VAL_SET(count, REB_INTEGER); - } +// +// loop-while: native [ +// +// "Evaluates a block while it is TRUE?" +// +// return: [ any-value!] +// {Last body result or BREAK value.} +// body [block! function!] +// ] +// +REBNATIVE(loop_while) +{ + return Loop_While_Until_Core(frame_, TRUE); +} - body = Init_Loop(D_ARG(1), D_ARG(3), &frame); - var = FRM_VALUE(frame, 1); // safe: not on stack - SET_OBJECT(D_ARG(1), frame); // keep GC safe - Set_Block(D_ARG(3), body); // keep GC safe - SET_NONE(DS_NEXT); // in case nothing below happens +// +// loop-until: native [ +// +// "Evaluates a block until it is TRUE?" +// +// return: [ any-value!] +// {Last body result or BREAK value.} +// body [block! function!] +// ] +// +REBNATIVE(loop_until) +// +// !!! This function used to be called just UNTIL, but Ren-C retakes that for +// the arity-2 complement to WHILE. +{ + return Loop_While_Until_Core(frame_, FALSE); +} - if (ANY_SERIES(count)) { - Loop_Series(var, body, count, VAL_TAIL(count)-1, 1); - } - else if (IS_INTEGER(count)) { - Loop_Integer(var, body, 1, VAL_INT64(count), 1); - } - return R_TOS1; +// Common code for WHILE & UNTIL (same frame param layout) +// +inline static REB_R While_Until_Core(REBFRM *frame_, REBOOL trigger) +{ + INCLUDE_PARAMS_OF_WHILE; + + const REBOOL only = FALSE; // while/only [cond] [body] is meaningless + + assert(IS_END(D_OUT)); // guaranteed by the evaluator + + do { + if (Run_Branch_Throws(D_CELL, ARG(condition), only)) { + // + // A while loop should only look for breaks and continues in its + // body, not in its condition. So `while [break] []` is a + // request to break the enclosing loop (or error if there is + // nothing to catch that break). Hence we bubble up the throw. + // + Move_Value(D_OUT, D_CELL); + return R_OUT_IS_THROWN; + } + + if (IS_VOID(D_CELL)) + fail (Error_No_Return_Raw()); + + if (IS_CONDITIONAL_TRUE(D_CELL) != trigger) { + // + // Successfully completed loops aren't allowed to return a + // FALSE? value, so they get BAR! as a truthy-result if the + // loop body ever ran... or void if it never did. + // + return R_OUT_VOID_IF_UNWRITTEN_TRUTHIFY; + } + + if (Run_Branch_Throws(D_OUT, ARG(body), only)) { + REBOOL stop; + if (Catching_Break_Or_Continue(D_OUT, &stop)) { + if (stop) + return R_BLANK; + + continue; + } + return R_OUT_IS_THROWN; + } + + } while (TRUE); } -/*********************************************************************** -** -*/ REBNATIVE(until) -/* -***********************************************************************/ +// +// while: native [ +// +// {While a condition block is TRUE?, evaluates another block.} +// +// return: [ any-value!] +// {Last body result or BREAK value, will also be void if never run} +// condition [block! function!] +// body [block! function!] +// ] +// +REBNATIVE(while) { - REBSER *b1 = VAL_SERIES(D_ARG(1)); - REBCNT i1 = VAL_INDEX(D_ARG(1)); - - do { -utop: - ds = Do_Blk(b1, i1); - if (IS_UNSET(ds)) Trap0(RE_NO_RETURN); - if (THROWN(ds)) { - if (Check_Error(ds) >= 0) break; - goto utop; - } - } while (IS_FALSE(ds)); // Break, return errors fall out. - return R_TOS1; + return While_Until_Core(frame_, TRUE); } -/*********************************************************************** -** -*/ REBNATIVE(while) -/* -***********************************************************************/ +// +// until: native [ +// +// {Until a condition block is TRUE?, evaluates another block.} +// +// return: [ any-value!] +// {Last body result or BREAK value, will also be void if never run} +// condition [block! function!] +// body [block! function!] +// ] +// +REBNATIVE(until) +// +// !!! This arity-2 form of UNTIL is aliased to UNTIL-2 in the bootstrap, and +// UNTIL is left undefined. { - REBSER *b1 = VAL_SERIES(D_ARG(1)); - REBCNT i1 = VAL_INDEX(D_ARG(1)); - REBSER *b2 = VAL_SERIES(D_ARG(2)); - REBCNT i2 = VAL_INDEX(D_ARG(2)); - - SET_NONE(D_RET); - - do { - ds = Do_Blk(b1, i1); - if (IS_UNSET(ds) || IS_ERROR(ds)) { // Unset, break, throw, error. - if (Check_Error(ds) >= 0) return R_TOS1; - } - if (!IS_TRUE(ds)) return R_RET; - ds = Do_Blk(b2, i2); - *DS_RETURN = *ds; // save here (to avoid GC during error handling) - if (THROWN(ds)) { // Break, throw, continue, error. - if (Check_Error(ds) >= 0) return R_TOS1; - *DS_RETURN = *ds; // Check_Error modified it - } - } while (TRUE); + return While_Until_Core(frame_, FALSE); } diff --git a/src/core/n-math.c b/src/core/n-math.c index 80a2afc9ef..6365f23295 100644 --- a/src/core/n-math.c +++ b/src/core/n-math.c @@ -1,68 +1,68 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: n-math.c -** Summary: native functions for math -** Section: natives -** Author: Carl Sassenrath -** Notes: See also: the numeric datatypes -** -***********************************************************************/ +// +// File: %n-math.c +// Summary: "native functions for math" +// Section: natives +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// See also: the numeric datatypes +// #include "sys-core.h" -#include "tmp-comptypes.h" #include "sys-deci-funcs.h" #include #include -#define LOG2 0.6931471805599453 -#define EPS 2.718281828459045235360287471 +#define LOG2 0.6931471805599453 +#define EPS 2.718281828459045235360287471 -const double pi1 = 3.14159265358979323846; -const double pi2 = 2.0 * 3.14159265358979323846; +#ifndef PI + #define PI 3.14159265358979323846E0 +#endif #ifndef DBL_EPSILON -#define DBL_EPSILON 2.2204460492503131E-16 + #define DBL_EPSILON 2.2204460492503131E-16 #endif -#define AS_DECIMAL(n) (IS_INTEGER(n) ? (REBDEC)VAL_INT64(n) : VAL_DECIMAL(n)) +#define AS_DECIMAL(n) (IS_INTEGER(n) ? (REBDEC)VAL_INT64(n) : VAL_DECIMAL(n)) enum {SINE, COSINE, TANGENT}; -/*********************************************************************** -** -*/ static REBDEC Trig_Value(REBVAL *ds, REBCNT which) -/* -** Convert integer arg, if present, to decimal and convert to radians -** if necessary. Clip ranges for correct REBOL behavior. -** -***********************************************************************/ +// +// Trig_Value: C +// +// Convert integer arg, if present, to decimal and convert to radians +// if necessary. Clip ranges for correct REBOL behavior. +// +static REBDEC Trig_Value(const REBVAL *value, REBOOL degrees, REBCNT which) { - REBDEC dval; - - dval = AS_DECIMAL(D_ARG(1)); + REBDEC dval = AS_DECIMAL(value); - if (!D_REF(2)) { + if (degrees) { /* get dval between -360.0 and 360.0 */ dval = fmod (dval, 360.0); @@ -75,498 +75,875 @@ enum {SINE, COSINE, TANGENT}; /* get dval between -90.0 and 90.0 */ if (fabs (dval) > 90.0) dval = (dval < 0.0 ? -180.0 : 180.0) - dval; } - dval = dval * pi1 / 180.0; // to radians + dval = dval * PI / 180.0; // to radians } - return dval; + return dval; } -/*********************************************************************** -** -*/ static void Arc_Trans(REBVAL *ds, REBCNT kind) -/* -***********************************************************************/ +// +// Arc_Trans: C +// +static void Arc_Trans(REBVAL *out, const REBVAL *value, REBOOL degrees, REBCNT kind) { - REBDEC dval; + REBDEC dval = AS_DECIMAL(value); + if (kind != TANGENT && (dval < -1 || dval > 1)) fail (Error_Overflow_Raw()); - dval = AS_DECIMAL(D_ARG(1)); - if (kind != TANGENT && (dval < -1 || dval > 1)) Trap0(RE_OVERFLOW); + if (kind == SINE) dval = asin(dval); + else if (kind == COSINE) dval = acos(dval); + else dval = atan(dval); - if (kind == SINE) dval = asin(dval); - else if (kind == COSINE) dval = acos(dval); - else dval = atan(dval); + if (degrees) + dval = dval * 180.0 / PI; // to degrees - if (!D_REF(2)) dval = dval * 180.0 / pi1; // to degrees - - SET_DECIMAL(D_RET, dval); + Init_Decimal(out, dval); } -/*********************************************************************** -** -*/ REBNATIVE(cosine) -/* -***********************************************************************/ +// +// cosine: native [ +// +// "Returns the trigonometric cosine." +// +// value [any-number!] +// "In degrees by default" +// /radians +// "Value is specified in radians" +// ] +// +REBNATIVE(cosine) { - REBDEC dval = cos(Trig_Value(ds, COSINE)); - if (fabs(dval) < DBL_EPSILON) dval = 0.0; - SET_DECIMAL(D_RET, dval); - return R_RET; + INCLUDE_PARAMS_OF_COSINE; + + REBDEC dval = cos(Trig_Value(ARG(value), NOT(REF(radians)), COSINE)); + if (fabs(dval) < DBL_EPSILON) dval = 0.0; + Init_Decimal(D_OUT, dval); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(sine) -/* -***********************************************************************/ +// +// sine: native [ +// +// "Returns the trigonometric sine." +// +// value [any-number!] +// "In degrees by default" +// /radians +// "Value is specified in radians" +// ] +// +REBNATIVE(sine) { - REBDEC dval = sin(Trig_Value(ds, SINE)); - if (fabs(dval) < DBL_EPSILON) dval = 0.0; - SET_DECIMAL(D_RET, dval); - return R_RET; + INCLUDE_PARAMS_OF_SINE; + + REBDEC dval = sin(Trig_Value(ARG(value), NOT(REF(radians)), SINE)); + if (fabs(dval) < DBL_EPSILON) dval = 0.0; + Init_Decimal(D_OUT, dval); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(tangent) -/* -***********************************************************************/ +// +// tangent: native [ +// +// "Returns the trigonometric tangent." +// +// value [any-number!] +// "In degrees by default" +// /radians +// "Value is specified in radians" +// ] +// +REBNATIVE(tangent) { - REBDEC dval = Trig_Value(ds, TANGENT); - if (Eq_Decimal(fabs(dval), pi1 / 2.0)) Trap0(RE_OVERFLOW); - SET_DECIMAL(D_RET, tan(dval)); - return R_RET; -} + INCLUDE_PARAMS_OF_TANGENT; + REBDEC dval = Trig_Value(ARG(value), NOT(REF(radians)), TANGENT); + if (Eq_Decimal(fabs(dval), PI / 2.0)) + fail (Error_Overflow_Raw()); -/*********************************************************************** -** -*/ REBNATIVE(arccosine) -/* -***********************************************************************/ -{ - Arc_Trans(ds, COSINE); - return R_RET; + Init_Decimal(D_OUT, tan(dval)); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(arcsine) -/* -***********************************************************************/ +// +// arccosine: native [ +// +// {Returns the trigonometric arccosine (in degrees by default).} +// +// value [any-number!] +// /radians +// "Returns result in radians" +// ] +// +REBNATIVE(arccosine) { - Arc_Trans(ds, SINE); - return R_RET; + INCLUDE_PARAMS_OF_ARCCOSINE; + + Arc_Trans(D_OUT, ARG(value), NOT(REF(radians)), COSINE); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(arctangent) -/* -***********************************************************************/ +// +// arcsine: native [ +// +// {Returns the trigonometric arcsine (in degrees by default).} +// +// value [any-number!] +// /radians +// "Returns result in radians" +// ] +// +REBNATIVE(arcsine) { - Arc_Trans(ds, TANGENT); - return R_RET; + INCLUDE_PARAMS_OF_ARCSINE; + + Arc_Trans(D_OUT, ARG(value), NOT(REF(radians)), SINE); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(exp) -/* -***********************************************************************/ +// +// arctangent: native [ +// +// {Returns the trigonometric arctangent (in degrees by default).} +// +// value [any-number!] +// /radians +// "Returns result in radians" +// ] +// +REBNATIVE(arctangent) { - REBDEC dval = AS_DECIMAL(D_ARG(1)); - static REBDEC eps = EPS; + INCLUDE_PARAMS_OF_ARCTANGENT; - dval = pow(eps, dval); -//!!!! Check_Overflow(dval); - SET_DECIMAL(D_RET, dval); - return R_RET; + Arc_Trans(D_OUT, ARG(value), NOT(REF(radians)), TANGENT); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(log_10) -/* -***********************************************************************/ +// +// exp: native [ +// +// {Raises E (the base of natural logarithm) to the power specified} +// +// power [any-number!] +// ] +// +REBNATIVE(exp) { - REBDEC dval = AS_DECIMAL(D_ARG(1)); - if (dval <= 0) Trap0(RE_POSITIVE); - SET_DECIMAL(D_RET, log10(dval)); - return R_RET; + INCLUDE_PARAMS_OF_EXP; + + REBDEC dval = AS_DECIMAL(ARG(power)); + static REBDEC eps = EPS; + + dval = pow(eps, dval); +//!!!! Check_Overflow(dval); + Init_Decimal(D_OUT, dval); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(log_2) -/* -***********************************************************************/ +// +// log-10: native [ +// +// "Returns the base-10 logarithm." +// +// value [any-number!] +// ] +// +REBNATIVE(log_10) { - REBDEC dval = AS_DECIMAL(D_ARG(1)); - if (dval <= 0) Trap0(RE_POSITIVE); - SET_DECIMAL(D_RET, log(dval) / LOG2); - return R_RET; + INCLUDE_PARAMS_OF_LOG_10; + + REBDEC dval = AS_DECIMAL(ARG(value)); + if (dval <= 0) fail (Error_Positive_Raw()); + Init_Decimal(D_OUT, log10(dval)); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(log_e) -/* -***********************************************************************/ +// +// log-2: native [ +// +// "Return the base-2 logarithm." +// +// value [any-number!] +// ] +// +REBNATIVE(log_2) { - REBDEC dval = AS_DECIMAL(D_ARG(1)); - if (dval <= 0) Trap0(RE_POSITIVE); - SET_DECIMAL(D_RET, log(dval)); - return R_RET; + INCLUDE_PARAMS_OF_LOG_2; + + REBDEC dval = AS_DECIMAL(ARG(value)); + if (dval <= 0) fail (Error_Positive_Raw()); + Init_Decimal(D_OUT, log(dval) / LOG2); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(square_root) -/* -***********************************************************************/ +// +// log-e: native [ +// +// {Returns the natural (base-E) logarithm of the given value} +// +// value [any-number!] +// ] +// +REBNATIVE(log_e) { - REBDEC dval = AS_DECIMAL(D_ARG(1)); - if (dval < 0) Trap0(RE_POSITIVE); - SET_DECIMAL(D_RET, sqrt(dval)); - return R_RET; + INCLUDE_PARAMS_OF_LOG_E; + + REBDEC dval = AS_DECIMAL(ARG(value)); + if (dval <= 0) fail (Error_Positive_Raw()); + Init_Decimal(D_OUT, log(dval)); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(shift) -/* -** shift int bits arithmetic or logical -** -***********************************************************************/ +// +// square-root: native [ +// +// "Returns the square root of a number." +// +// value [any-number!] +// ] +// +REBNATIVE(square_root) { - REBI64 b = VAL_INT64(D_ARG(2)); - REBVAL *a = D_ARG(1); - REBU64 c, d; - - if (b < 0) { - // this is defined: - c = -(REBU64)b; - if (c >= 64) { - if (D_REF(3)) VAL_INT64(a) = 0; - else VAL_INT64(a) >>= 63; - } else { - if (D_REF(3)) VAL_UNT64(a) >>= c; - else VAL_INT64(a) >>= (REBI64)c; - } - } else { - if (b >= 64) { - if (D_REF(3)) VAL_INT64(a) = 0; - else if (VAL_INT64(a)) Trap0(RE_OVERFLOW); - } else - if (D_REF(3)) VAL_UNT64(a) <<= b; - else { - c = (REBU64)MIN_I64 >> b; - d = VAL_INT64(a) < 0 ? -VAL_UNT64(a) : VAL_UNT64(a); - if (c <= d) - if ((c < d) || (VAL_INT64(a) >= 0)) Trap0(RE_OVERFLOW); - else VAL_INT64(a) = MIN_I64; - else - VAL_INT64(a) <<= b; - } - } - return R_ARG1; + INCLUDE_PARAMS_OF_SQUARE_ROOT; + + REBDEC dval = AS_DECIMAL(ARG(value)); + if (dval < 0) fail (Error_Positive_Raw()); + Init_Decimal(D_OUT, sqrt(dval)); + return R_OUT; } -/*********************************************************************** -** -*/ REBINT Compare_Values(REBVAL *a, REBVAL *b, REBINT strictness) -/* -** Compare 2 values depending on level of strictness. -** NOTE: MODIFIES a and b args. -** -** Strictness: -** 0 - coersed equality -** 1 - equivalence -** 2 - strict equality -** 3 - same (identical bits) -** -** -1 - greater or equal -** -2 - greater -** -***********************************************************************/ + +// +// The SHIFT native uses negation of an unsigned number. Although the +// operation is well-defined in the C language, it is usually a mistake. +// MSVC warns about it, so temporarily disable that. +// +// !!! The usage of negation of unsigned in SHIFT is from R3-Alpha. Should it +// be rewritten another way? +// +// http://stackoverflow.com/a/36349666/211160 +// +#if defined(_MSC_VER) && _MSC_VER > 1800 + #pragma warning (disable : 4146) +#endif + + +// +// shift: native [ +// +// {Shifts an integer left or right by a number of bits.} +// +// value [integer!] +// bits [integer!] +// "Positive for left shift, negative for right shift" +// /logical +// "Logical shift (sign bit ignored)" +// ] +// +REBNATIVE(shift) { - REBCNT ta = VAL_TYPE(a); - REBCNT tb = VAL_TYPE(b); - REBCTF code; - REBINT result; - - if (ta != tb) { - if (strictness > 1) return FALSE; - - switch (ta) { - case REB_INTEGER: - if (tb == REB_DECIMAL || tb == REB_PERCENT) { - SET_DECIMAL(a, (REBDEC)VAL_INT64(a)); - goto compare; - } - else if (tb == REB_MONEY) { - SET_MONEY(a, int_to_deci(VAL_INT64(a))); - goto compare; - } - else if (tb == REB_INTEGER) // special negative?, zero?, ... - goto compare; - break; - - case REB_DECIMAL: - case REB_PERCENT: - if (tb == REB_INTEGER) { - SET_DECIMAL(b, (REBDEC)VAL_INT64(b)); - goto compare; - } - else if (tb == REB_MONEY) { - SET_MONEY(a, decimal_to_deci(VAL_DECIMAL(a))); - goto compare; - } - else if (tb == REB_DECIMAL || tb == REB_PERCENT) // equivalent types - goto compare; - break; - - case REB_MONEY: - if (tb == REB_INTEGER) { - SET_MONEY(b, int_to_deci(VAL_INT64(b))); - goto compare; - } - if (tb == REB_DECIMAL || tb == REB_PERCENT) { - SET_MONEY(b, decimal_to_deci(VAL_DECIMAL(b))); - goto compare; - } - break; - - case REB_WORD: - case REB_SET_WORD: - case REB_GET_WORD: - case REB_LIT_WORD: - case REB_REFINEMENT: - case REB_ISSUE: - if (ANY_WORD(b)) goto compare; - break; - - case REB_STRING: - case REB_FILE: - case REB_EMAIL: - case REB_URL: - case REB_TAG: - if (ANY_STR(b)) goto compare; - break; - } - - if (strictness == 0 || strictness == 1) return FALSE; - //if (strictness >= 2) - Trap2(RE_INVALID_COMPARE, Of_Type(a), Of_Type(b)); - } + INCLUDE_PARAMS_OF_SHIFT; + + REBI64 b = VAL_INT64(ARG(bits)); + REBVAL *a = ARG(value); + + if (b < 0) { + REBU64 c = - cast(REBU64, b); // defined, see note on #pragma above + if (c >= 64) { + if (REF(logical)) + VAL_INT64(a) = 0; + else + VAL_INT64(a) >>= 63; + } + else { + if (REF(logical)) + VAL_INT64(a) = cast(REBU64, VAL_INT64(a)) >> c; + else + VAL_INT64(a) >>= cast(REBI64, c); + } + } + else { + if (b >= 64) { + if (REF(logical)) + VAL_INT64(a) = 0; + else if (VAL_INT64(a) != 0) + fail (Error_Overflow_Raw()); + } + else { + if (REF(logical)) + VAL_INT64(a) = cast(REBU64, VAL_INT64(a)) << b; + else { + REBU64 c = cast(REBU64, MIN_I64) >> b; + REBU64 d = VAL_INT64(a) < 0 + ? - cast(REBU64, VAL_INT64(a)) // again, see #pragma + : cast(REBU64, VAL_INT64(a)); + if (c <= d) { + if ((c < d) || (VAL_INT64(a) >= 0)) + fail (Error_Overflow_Raw()); + + VAL_INT64(a) = MIN_I64; + } + else + VAL_INT64(a) <<= b; + } + } + } -compare: - // At this point, both args are of the same datatype. - if (!(code = Compare_Types[VAL_TYPE(a)])) return FALSE; - result = code(a, b, strictness); - if (result < 0) Trap2(RE_INVALID_COMPARE, Of_Type(a), Of_Type(b)); - return result; + Move_Value(D_OUT, ARG(value)); + return R_OUT; } -// EQUAL? < EQUIV? < STRICT-EQUAL? < SAME? +// See above for the temporary disablement and reasoning. +// +#if defined(_MSC_VER) && _MSC_VER > 1800 + #pragma warning (default : 4146) +#endif + -/*********************************************************************** -** -*/ REBNATIVE(equalq) -/* -***********************************************************************/ +// CT_Fail: C +// +REBINT CT_Fail(const RELVAL *a, const RELVAL *b, REBINT mode) { - if (Compare_Values(D_ARG(1), D_ARG(2), 0)) return R_TRUE; - return R_FALSE; + UNUSED(a); + UNUSED(b); + UNUSED(mode); + + fail ("Datatype does not have type comparison handler registered"); } -/*********************************************************************** -** -*/ REBNATIVE(not_equalq) -/* -***********************************************************************/ + +// +// Compare_Modify_Values: C +// +// Compare 2 values depending on level of strictness. It leans +// upon the per-type comparison functions (that have a more typical +// interface of returning [1, 0, -1] and taking a CASE parameter) +// but adds a layer of being able to check for specific types +// of equality...which those comparison functions do not discern. +// +// Strictness: +// 0 - coerced equality +// 1 - strict equality +// +// -1 - greater or equal +// -2 - greater +// +// !!! This routine (may) modify the value cells for 'a' and 'b' in +// order to coerce them for easier comparison. Most usages are +// in native code that can overwrite its argument values without +// that being a problem, so it doesn't matter. +// +REBINT Compare_Modify_Values(RELVAL *a, RELVAL *b, REBINT strictness) { - if (Compare_Values(D_ARG(1), D_ARG(2), 0)) return R_FALSE; - return R_TRUE; + REBCNT ta = VAL_TYPE(a); + REBCNT tb = VAL_TYPE(b); + REBCTF code; + REBINT result; + + if (ta != tb) { + if (strictness == 1) return 0; + + switch (ta) { + case REB_MAX_VOID: + return 0; // nothing coerces to void + + case REB_INTEGER: + if (tb == REB_DECIMAL || tb == REB_PERCENT) { + REBDEC dec_a = cast(REBDEC, VAL_INT64(a)); + Init_Decimal(a, dec_a); + goto compare; + } + else if (tb == REB_MONEY) { + deci amount = int_to_deci(VAL_INT64(a)); + Init_Money(a, amount); + goto compare; + } + break; + + case REB_DECIMAL: + case REB_PERCENT: + if (tb == REB_INTEGER) { + REBDEC dec_b = cast(REBDEC, VAL_INT64(b)); + Init_Decimal(b, dec_b); + goto compare; + } + else if (tb == REB_MONEY) { + Init_Money(a, decimal_to_deci(VAL_DECIMAL(a))); + goto compare; + } + else if (tb == REB_DECIMAL || tb == REB_PERCENT) // equivalent types + goto compare; + break; + + case REB_MONEY: + if (tb == REB_INTEGER) { + Init_Money(b, int_to_deci(VAL_INT64(b))); + goto compare; + } + if (tb == REB_DECIMAL || tb == REB_PERCENT) { + Init_Money(b, decimal_to_deci(VAL_DECIMAL(b))); + goto compare; + } + break; + + case REB_WORD: + case REB_SET_WORD: + case REB_GET_WORD: + case REB_LIT_WORD: + case REB_REFINEMENT: + case REB_ISSUE: + if (ANY_WORD(b)) goto compare; + break; + + case REB_STRING: + case REB_FILE: + case REB_EMAIL: + case REB_URL: + case REB_TAG: + if (ANY_STRING(b)) goto compare; + break; + } + + if (strictness == 0) return 0; + + fail (Error_Invalid_Compare_Raw(Type_Of(a), Type_Of(b))); + } + + if (ta == REB_MAX_VOID) return 1; // voids always equal + +compare: + // At this point, both args are of the same datatype. + if (!(code = Compare_Types[VAL_TYPE(a)])) return 0; + result = code(a, b, strictness); + if (result < 0) fail (Error_Invalid_Compare_Raw(Type_Of(a), Type_Of(b))); + return result; } -/*********************************************************************** -** -*/ REBNATIVE(equivq) -/* -***********************************************************************/ + +// EQUAL? < EQUIV? < STRICT-EQUAL? < SAME? + +// +// equal?: native [ +// +// "Returns TRUE if the values are equal." +// +// value1 [ any-value!] +// value2 [ any-value!] +// ] +// +REBNATIVE(equal_q) { - if (Compare_Values(D_ARG(1), D_ARG(2), 1)) return R_TRUE; - return R_FALSE; + INCLUDE_PARAMS_OF_EQUAL_Q; + + if (Compare_Modify_Values(ARG(value1), ARG(value2), 0)) + return R_TRUE; + + return R_FALSE; } -/*********************************************************************** -** -*/ REBNATIVE(not_equivq) -/* -***********************************************************************/ + +// +// not-equal?: native [ +// +// "Returns TRUE if the values are not equal." +// +// value1 [ any-value!] +// value2 [ any-value!] +// ] +// +REBNATIVE(not_equal_q) { - if (Compare_Values(D_ARG(1), D_ARG(2), 1)) return R_FALSE; - return R_TRUE; + INCLUDE_PARAMS_OF_NOT_EQUAL_Q; + + if (Compare_Modify_Values(ARG(value1), ARG(value2), 0)) + return R_FALSE; + + return R_TRUE; } -/*********************************************************************** -** -*/ REBNATIVE(strict_equalq) -/* -***********************************************************************/ + +// +// strict-equal?: native [ +// +// "Returns TRUE if the values are strictly equal." +// +// value1 [ any-value!] +// value2 [ any-value!] +// ] +// +REBNATIVE(strict_equal_q) { - if (Compare_Values(D_ARG(1), D_ARG(2), 2)) return R_TRUE; - return R_FALSE; + INCLUDE_PARAMS_OF_STRICT_EQUAL_Q; + + if (Compare_Modify_Values(ARG(value1), ARG(value2), 1)) + return R_TRUE; + + return R_FALSE; } -/*********************************************************************** -** -*/ REBNATIVE(strict_not_equalq) -/* -***********************************************************************/ + +// +// strict-not-equal?: native [ +// +// "Returns TRUE if the values are not strictly equal." +// +// value1 [ any-value!] +// value2 [ any-value!] +// ] +// +REBNATIVE(strict_not_equal_q) { - if (Compare_Values(D_ARG(1), D_ARG(2), 2)) return R_FALSE; - return R_TRUE; + INCLUDE_PARAMS_OF_STRICT_NOT_EQUAL_Q; + + if (Compare_Modify_Values(ARG(value1), ARG(value2), 1)) + return R_FALSE; + + return R_TRUE; } -/*********************************************************************** -** -*/ REBNATIVE(sameq) -/* -***********************************************************************/ + +// +// same?: native [ +// +// "Returns TRUE if the values are identical." +// +// value1 [ any-value!] +// value2 [ any-value!] +// ] +// +REBNATIVE(same_q) +// +// This used to be "strictness mode 3" of Compare_Modify_Values. However, +// folding SAME?-ness in required the comparisons to take REBVALs instead +// of just REBVALs, when only a limited number of types supported it. +// Rather than incur a cost for all comparisons, this handles the issue +// specially for those types which support it. { - if (Compare_Values(D_ARG(1), D_ARG(2), 3)) return R_TRUE; - return R_FALSE; + INCLUDE_PARAMS_OF_SAME_Q; + + REBVAL *value1 = ARG(value1); + REBVAL *value2 = ARG(value2); + + if (VAL_TYPE(value1) != VAL_TYPE(value2)) + return R_FALSE; // can't be "same" value if not same type + + if (IS_BITSET(value1)) { + // + // BITSET! only has a series, no index. + // + if (VAL_SERIES(value1) != VAL_SERIES(value2)) + return R_FALSE; + return R_TRUE; + } + + if (ANY_SERIES(value1) || IS_IMAGE(value1)) { + // + // ANY-SERIES! can only be the same if pointers and indices match. + // + if (VAL_SERIES(value1) != VAL_SERIES(value2)) + return R_FALSE; + if (VAL_INDEX(value1) != VAL_INDEX(value2)) + return R_FALSE; + return R_TRUE; + } + + if (ANY_CONTEXT(value1)) { + // + // ANY-CONTEXT! are the same if the varlists match. + // + if (VAL_CONTEXT(value1) != VAL_CONTEXT(value2)) + return R_FALSE; + return R_TRUE; + } + + if (IS_MAP(value1)) { + // + // MAP! will be the same if the map pointer matches. + // + if (VAL_MAP(value1) != VAL_MAP(value2)) + return R_FALSE; + return R_TRUE; + } + + if (ANY_WORD(value1)) { + // + // ANY-WORD! must match in binding as well as be otherwise equal. + // + if (VAL_WORD_SPELLING(value1) != VAL_WORD_SPELLING(value2)) + return R_FALSE; + if (IS_WORD_BOUND(value1) != IS_WORD_BOUND(value2)) + return R_FALSE; + if (IS_WORD_BOUND(value1)) { + REBCTX *ctx1 = VAL_WORD_CONTEXT(value1); + REBCTX *ctx2 = VAL_WORD_CONTEXT(value2); + if (ctx1 != ctx2) + return R_FALSE; + } + return R_TRUE; + } + + if (IS_DECIMAL(value1) || IS_PERCENT(value1)) { + // + // The tolerance on strict-equal? for decimals is apparently not + // a requirement of exactly the same bits. + // + if ( + memcmp( + &VAL_DECIMAL(value1), &VAL_DECIMAL(value2), sizeof(REBDEC) + ) == 0 + ){ + return R_TRUE; + } + + return R_FALSE; + } + + if (IS_MONEY(value1)) { + // + // There is apparently a distinction between "strict equal" and "same" + // when it comes to the MONEY! type: + // + // >> strict-equal? $1 $1.0 + // == true + // + // >> same? $1 $1.0 + // == false + // + if (deci_is_same(VAL_MONEY_AMOUNT(value1), VAL_MONEY_AMOUNT(value2))) + return R_TRUE; + return R_FALSE; + } + + // For other types, just fall through to strict equality comparison + // + if (Compare_Modify_Values(value1, value2, 1)) + return R_TRUE; + + return R_FALSE; } -/*********************************************************************** -** -*/ REBNATIVE(lesserq) -/* -***********************************************************************/ + +// +// lesser?: native [ +// +// {Returns TRUE if the first value is less than the second value.} +// +// value1 value2 +// ] +// +REBNATIVE(lesser_q) { - if (Compare_Values(D_ARG(1), D_ARG(2), -1)) return R_FALSE; - return R_TRUE; + INCLUDE_PARAMS_OF_LESSER_Q; + + if (Compare_Modify_Values(ARG(value1), ARG(value2), -1)) + return R_FALSE; + + return R_TRUE; } -/*********************************************************************** -** -*/ REBNATIVE(lesser_or_equalq) -/* -***********************************************************************/ + +// +// lesser-or-equal?: native [ +// +// {Returns TRUE if the first value is less than or equal to the second value.} +// +// value1 value2 +// ] +// +REBNATIVE(lesser_or_equal_q) { - if (Compare_Values(D_ARG(1), D_ARG(2), -2)) return R_FALSE; - return R_TRUE; + INCLUDE_PARAMS_OF_LESSER_OR_EQUAL_Q; + + if (Compare_Modify_Values(ARG(value1), ARG(value2), -2)) + return R_FALSE; + + return R_TRUE; } -/*********************************************************************** -** -*/ REBNATIVE(greaterq) -/* -***********************************************************************/ + +// +// greater?: native [ +// +// {Returns TRUE if the first value is greater than the second value.} +// +// value1 value2 +// ] +// +REBNATIVE(greater_q) { - if (Compare_Values(D_ARG(1), D_ARG(2), -2)) return R_TRUE; - return R_FALSE; + INCLUDE_PARAMS_OF_GREATER_Q; + + if (Compare_Modify_Values(ARG(value1), ARG(value2), -2)) + return R_TRUE; + + return R_FALSE; } -/*********************************************************************** -** -*/ REBNATIVE(greater_or_equalq) -/* -***********************************************************************/ + +// +// greater-or-equal?: native [ +// +// {Returns TRUE if the first value is greater than or equal to the second value.} +// +// value1 value2 +// ] +// +REBNATIVE(greater_or_equal_q) { - if (Compare_Values(D_ARG(1), D_ARG(2), -1)) return R_TRUE; - return R_FALSE; + INCLUDE_PARAMS_OF_GREATER_OR_EQUAL_Q; + + if (Compare_Modify_Values(ARG(value1), ARG(value2), -1)) + return R_TRUE; + + return R_FALSE; } -/*********************************************************************** -** -*/ REBNATIVE(maximum) -/* -***********************************************************************/ + +// +// maximum: native [ +// +// "Returns the greater of the two values." +// +// value1 [any-scalar! date! any-series!] +// value2 [any-scalar! date! any-series!] +// ] +// +REBNATIVE(maximum) { - REBVAL a, b; + INCLUDE_PARAMS_OF_MAXIMUM; - if (IS_PAIR(D_ARG(1)) || IS_PAIR(D_ARG(2))) - return Min_Max_Pair(ds, 1); + const REBVAL *value1 = ARG(value1); + const REBVAL *value2 = ARG(value2); - a = *D_ARG(1); - b = *D_ARG(2); - if (Compare_Values(&a, &b, -1)) return R_ARG1; - return R_ARG2; + if (IS_PAIR(value1) || IS_PAIR(value2)) { + Min_Max_Pair(D_OUT, value1, value2, TRUE); + } + else { + DECLARE_LOCAL (coerced1); + Move_Value(coerced1, value1); + DECLARE_LOCAL (coerced2); + Move_Value(coerced2, value2); + + if (Compare_Modify_Values(coerced1, coerced2, -1)) + Move_Value(D_OUT, value1); + else + Move_Value(D_OUT, value2); + } + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(minimum) -/* -***********************************************************************/ + +// +// minimum: native [ +// +// "Returns the lesser of the two values." +// +// value1 [any-scalar! date! any-series!] +// value2 [any-scalar! date! any-series!] +// ] +// +REBNATIVE(minimum) { - REBVAL a, b; + INCLUDE_PARAMS_OF_MINIMUM; - if (IS_PAIR(D_ARG(1)) || IS_PAIR(D_ARG(2))) - return Min_Max_Pair(ds, 0); + const REBVAL *value1 = ARG(value1); + const REBVAL *value2 = ARG(value2); - a = *D_ARG(1); - b = *D_ARG(2); - if (Compare_Values(&a, &b, -1)) return R_ARG2; - return R_ARG1; + if (IS_PAIR(ARG(value1)) || IS_PAIR(ARG(value2))) { + Min_Max_Pair(D_OUT, ARG(value1), ARG(value2), FALSE); + } + else { + DECLARE_LOCAL (coerced1); + Move_Value(coerced1, value1); + DECLARE_LOCAL (coerced2); + Move_Value(coerced2, value2); + + if (Compare_Modify_Values(coerced1, coerced2, -1)) + Move_Value(D_OUT, value2); + else + Move_Value(D_OUT, value1); + } + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(negativeq) -/* -***********************************************************************/ + +// +// negative?: native [ +// +// "Returns TRUE if the number is negative." +// +// number [any-number! money! time! pair!] +// ] +// +REBNATIVE(negative_q) { - REBVAL *val = &DS_Base[++DSP]; - CLEARS(val); - VAL_SET(val, VAL_TYPE(D_ARG(1))); - if (Compare_Values(D_ARG(1), D_ARG(2), -1)) return R_FALSE; - return R_TRUE; + INCLUDE_PARAMS_OF_NEGATIVE_Q; + + DECLARE_LOCAL (zero); + SET_ZEROED(zero, VAL_TYPE(ARG(number))); + + if (Compare_Modify_Values(ARG(number), zero, -1)) + return R_FALSE; + + return R_TRUE; } -/*********************************************************************** -** -*/ REBNATIVE(positiveq) -/* -***********************************************************************/ + +// +// positive?: native [ +// +// "Returns TRUE if the value is positive." +// +// number [any-number! money! time! pair!] +// ] +// +REBNATIVE(positive_q) { - REBVAL *val = &DS_Base[++DSP]; - CLEARS(val); - VAL_SET(val, VAL_TYPE(D_ARG(1))); - if (Compare_Values(D_ARG(1), D_ARG(2), -2)) return R_TRUE; - return R_FALSE; + INCLUDE_PARAMS_OF_POSITIVE_Q; + + DECLARE_LOCAL (zero); + SET_ZEROED(zero, VAL_TYPE(ARG(number))); + + if (Compare_Modify_Values(ARG(number), zero, -2)) + return R_TRUE; + + return R_FALSE; } -/*********************************************************************** -** -*/ REBNATIVE(zeroq) -/* -***********************************************************************/ + +// +// zero?: native [ +// +// {Returns TRUE if the value is zero (for its datatype).} +// +// value +// ] +// +REBNATIVE(zero_q) { - REBCNT type = VAL_TYPE(D_ARG(1)); - - if (type >= REB_INTEGER && type <= REB_TIME) { - REBVAL *val = &DS_Base[++DSP]; - CLEARS(val); - VAL_SET(val, type); - if (Compare_Values(D_ARG(1), D_ARG(2), 1)) return R_TRUE; - } - return R_FALSE; + INCLUDE_PARAMS_OF_ZERO_Q; + + enum Reb_Kind type = VAL_TYPE(ARG(value)); + + if (type >= REB_INTEGER && type <= REB_TIME) { + DECLARE_LOCAL (zero); + SET_ZEROED(zero, type); + + if (Compare_Modify_Values(ARG(value), zero, 1)) + return R_TRUE; + } + return R_FALSE; } diff --git a/src/core/n-native.c b/src/core/n-native.c new file mode 100644 index 0000000000..c70a87a391 --- /dev/null +++ b/src/core/n-native.c @@ -0,0 +1,679 @@ +// +// File: %n-native.c +// Summary: {Implementation of "user natives" using an embedded C compiler} +// Section: natives +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2016 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// A user native is a FUNCTION! whose body is not a Rebol block, but a textual +// string of C code. It is compiled on the fly by an embedded C compiler +// which is linked in with those Rebol builds supporting user natives: +// +// http://bellard.org/tcc +// +// Once the user native is compiled, it works exactly the same as the built-in +// natives. However, the user can change the implementations without +// rebuilding the interpreter itself. This makes it easier to just implement +// part of a Rebol script in C for better performance. +// +// The preprocessed internal header file %sys-core.h will be inserted into +// user source code, which makes all internal functions / macros available. +// However, to use C runtime functions such as memcpy() etc, the library +// libtcc1.a must be included. This library must be available in addition +// to the interpreter executable. +// +// External libraries can also be used if proper 'library-path' and +// 'library' are specified. +// + +#include "sys-core.h" + +#if defined(WITH_TCC) +// +// libtcc provides the following functions: +// +// https://github.com/metaeducation/tcc/blob/mob/libtcc.h +// +// For a very simple example of usage of libtcc, see: +// +// https://github.com/metaeducation/tcc/blob/mob/tests/libtcc_test.c +// +#include "libtcc.h" + +extern const REBYTE core_header_source[]; + +struct rebol_sym_func_t { + const char *name; + CFUNC *func; +}; + +struct rebol_sym_data_t { + const char *name; + void *data; +}; + +extern const struct rebol_sym_func_t rebol_sym_funcs[]; +extern const struct rebol_sym_data_t rebol_sym_data[]; +extern +#ifdef __cplusplus +"C" +#endif +const void *r3_libtcc1_symbols[]; + +#define CHAR_HEAD(x) cs_cast(BIN_HEAD(x)) + + +static void tcc_error_report(void *ignored, const char *msg) +{ + UNUSED(ignored); + + DECLARE_LOCAL (err); + REBSER *ser = Make_Binary(strlen(msg) + 2); + Append_Series(ser, cb_cast(msg), strlen(msg)); + Init_String(err, ser); + fail (Error_Tcc_Error_Warn_Raw(err)); +} + + +static int do_add_path( + TCCState *state, + const RELVAL *path, + int (*add)(TCCState *, const char *) +) { + if (!VAL_BYTE_SIZE(path)) + return -1; + + int ret; + if (IS_FILE(path)) { + REBSER *lp = Value_To_Local_Path(KNOWN(m_cast(RELVAL*,path)), TRUE); + REBSER *bin = Make_UTF8_Binary( + UNI_HEAD(lp), SER_LEN(lp), 2, OPT_ENC_UNISRC + ); + Free_Series(lp); + assert(SER_WIDE(bin) == 1); + ret = add(state, CHAR_HEAD(bin)); + Free_Series(bin); + } + else { + assert(IS_STRING(path)); + ret = add(state, CHAR_HEAD(VAL_SERIES(path))); + } + return ret; +} + + +static void do_set_path( + TCCState *state, + const RELVAL *path, + void (*set)(TCCState *, const char *) +) { + if (!VAL_BYTE_SIZE(path)) + return; + + if (IS_FILE(path)) { + REBSER *lp = Value_To_Local_Path(KNOWN(m_cast(RELVAL*, path)), TRUE); + REBSER *bin = Make_UTF8_Binary( + UNI_HEAD(lp), SER_LEN(lp), 2, OPT_ENC_UNISRC + ); + Free_Series(lp); + assert(SER_WIDE(bin) == 1); + set(state, CHAR_HEAD(bin)); + Free_Series(bin); + } + else { + assert(IS_STRING(path)); + set(state, CHAR_HEAD(VAL_SERIES(path))); + } +} + + +static REBCTX* add_path( + TCCState *state, + const RELVAL *path, + int (*add)(TCCState *, const char *), + enum REBOL_Errors err_code +) { + if (path) { + if (IS_FILE(path) || IS_STRING(path)) { + if (do_add_path(state, path, add) < 0) + return Error(err_code, path); + } + else { + assert(IS_BLOCK(path)); + + RELVAL *item; + for (item = VAL_ARRAY_AT(path); NOT_END(item); ++item) { + if (!IS_FILE(item) && !IS_STRING(item)) + return Error(err_code, item); + + if (do_add_path(state, item, add) < 0) + return Error(err_code, item); + } + } + } + + return NULL; +} + + +static void cleanup(const REBVAL *val) +{ + TCCState *state = VAL_HANDLE_POINTER(TCCState, val); + assert(state != NULL); + tcc_delete(state); +} + + +// +// Pending_Native_Dispatcher: C +// +// The MAKE-NATIVE command doesn't actually compile the function directly. +// Instead the source code is held onto, so that several user natives can +// be compiled together by COMPILE. +// +// However, as a convenience, calling a pending user native will trigger a +// simple COMPILE for just that one function, using default options. +// +REB_R Pending_Native_Dispatcher(REBFRM *f) { + REBARR *array = Make_Array(1); + Append_Value(array, FUNC_VALUE(f->phase)); + + DECLARE_LOCAL (natives); + Init_Block(natives, array); + + assert(FUNC_DISPATCHER(f->phase) == &Pending_Native_Dispatcher); + + if (Do_Va_Throws(f->out, NAT_VALUE(compile), &natives, END)) + return R_OUT_IS_THROWN; + + // Today's COMPILE doesn't return a result on success (just fails on + // errors), but if it changes to return one consider what to do with it. + // + assert(IS_VOID(f->out)); + + // Now that it's compiled, it should have replaced the dispatcher with a + // function pointer that lives in the TCC_State. Use REDO, and don't + // bother re-checking the argument types. + // + assert(FUNC_DISPATCHER(f->phase) != &Pending_Native_Dispatcher); + return R_REDO_UNCHECKED; +} + +#endif + + +// +// make-native: native [ +// +// {Create a "user native" function compiled from C source} +// +// return: [function!] +// "Function value, will be compiled on demand or by COMPILE" +// spec [block!] +// "The spec of the native" +// source [string!] +// "C source of the native implementation" +// /linkname +// "Provide a specific linker name" +// name [string!] +// "Legal C identifier (default will be auto-generated)" +// ] +// +REBNATIVE(make_native) +{ + INCLUDE_PARAMS_OF_MAKE_NATIVE; + +#if !defined(WITH_TCC) + UNUSED(ARG(spec)); + UNUSED(ARG(source)); + UNUSED(REF(linkname)); + UNUSED(ARG(name)); + + fail (Error_Not_Tcc_Build_Raw()); +#else + REBVAL *source = ARG(source); + + if (VAL_LEN_AT(source) == 0) + fail (Error_Tcc_Empty_Source_Raw()); + + REBFUN *fun = Make_Function( + Make_Paramlist_Managed_May_Fail(ARG(spec), MKF_NONE), + &Pending_Native_Dispatcher, // will be replaced e.g. by COMPILE + NULL, // no underlying function, this is fundamental + NULL // not providing a specialization + ); + + REBARR *info = Make_Array(3); // [source name tcc_state] + + if (Is_Series_Frozen(VAL_SERIES(source))) + Append_Value(info, source); // no need to copy it... + else { + // have to copy it (might change before COMPILE is called) + // + Init_String( + Alloc_Tail_Array(info), + Copy_String_Slimming( + VAL_SERIES(source), + VAL_INDEX(source), + VAL_LEN_AT(source) + ) + ); + } + + if (REF(linkname)) { + REBVAL *name = ARG(name); + + if (Is_Series_Frozen(VAL_SERIES(name))) + Append_Value(info, name); + else { + Init_String( + Alloc_Tail_Array(info), + Copy_String_Slimming( + VAL_SERIES(name), + VAL_INDEX(name), + VAL_LEN_AT(name) + ) + ); + } + } + else { + // Auto-generate a linker name based on the numeric value of the + // function pointer. Just "N_" followed by the hexadecimal value. + // So 2 chars per byte, plus 2 for "N_", and account for the + // terminator (even though it's set implicitly). + + REBCNT len = 2 + sizeof(REBFUN*) * 2; + REBSER *bin = Make_Binary(len + 1); + const char *src = cast(const char*, &fun); + REBYTE *dest = BIN_HEAD(bin); + + *dest ='N'; + ++dest; + *dest = '_'; + ++dest; + + REBCNT n = 0; + while (n < sizeof(REBFUN*)) { + Form_Hex2(dest, *src); // terminates each time + ++src; + dest += 2; + ++n; + } + TERM_BIN_LEN(bin, len); + + Init_String(Alloc_Tail_Array(info), bin); + } + + Init_Blank(Alloc_Tail_Array(info)); // no TCC_State, yet... + + Init_Block(FUNC_BODY(fun), info); + + // We need to remember this is a user native, because we won't over the + // long run be able to tell it is when the dispatcher is replaced with an + // arbitrary compiled function pointer! + // + SET_VAL_FLAG(FUNC_VALUE(fun), FUNC_FLAG_USER_NATIVE); + + Move_Value(D_OUT, FUNC_VALUE(fun)); + return R_OUT; +#endif +} + + +// +// compile: native [ +// +// {Compiles one or more native functions at the same time, with options.} +// +// return: [] +// natives [block!] +// {Functions from MAKE-NATIVE or STRING!s of code.} +// /options +// flags [block!] +// { +// The block supports the following dialect: +// include [block! path!] +// "include path" +// define [block!] +// {define preprocessor symbols as "VAR=VAL" or "VAR"} +// debug +// "Add debugging information to the generated code?" +// } +// ] +// +REBNATIVE(compile) +{ + INCLUDE_PARAMS_OF_COMPILE; + +#if !defined(WITH_TCC) + UNUSED(ARG(natives)); + UNUSED(REF(options)); + UNUSED(ARG(flags)); + + fail (Error_Not_Tcc_Build_Raw()); +#else + REBVAL *natives = ARG(natives); + + REBOOL debug = FALSE; // !!! not implemented yet + + if (VAL_LEN_AT(ARG(natives)) == 0) + fail (Error_Tcc_Empty_Spec_Raw()); + + RELVAL *inc = NULL; + RELVAL *lib = NULL; + RELVAL *libdir = NULL; + RELVAL *options = NULL; + RELVAL *rundir = NULL; + + if (REF(options)) { + RELVAL *val = VAL_ARRAY_AT(ARG(flags)); + + for (; NOT_END(val); ++val) { + if (!IS_WORD(val)) + fail (Error_Tcc_Expect_Word_Raw(val)); + + switch (VAL_WORD_SYM(val)) { + case SYM_INCLUDE: + ++val; + if (!(IS_BLOCK(val) || IS_FILE(val) || ANY_STRING(val))) + fail (Error_Tcc_Invalid_Include_Raw(val)); + inc = val; + break; + + case SYM_DEBUG: + debug = TRUE; + break; + + case SYM_OPTIONS: + ++val; + if (!ANY_STRING(val) || !VAL_BYTE_SIZE(val)) + fail (Error_Tcc_Invalid_Options_Raw(val)); + options = val; + break; + + case SYM_RUNTIME_PATH: + ++val; + if (!(IS_FILE(val) || IS_STRING(val))) + fail (Error_Tcc_Invalid_Library_Path_Raw(val)); + rundir = val; + break; + + case SYM_LIBRARY_PATH: + ++val; + if (!(IS_BLOCK(val) || IS_FILE(val) || ANY_STRING(val))) + fail (Error_Tcc_Invalid_Library_Path_Raw(val)); + libdir = val; + break; + + case SYM_LIBRARY: + ++val; + if (!(IS_BLOCK(val) || IS_FILE(val) || ANY_STRING(val))) + fail (Error_Tcc_Invalid_Library_Raw(val)); + lib = val; + break; + + default: + fail (Error_Tcc_Not_Supported_Opt_Raw(val)); + } + } + } + + if (debug) + fail ("Debug builds of user natives are not yet implemented."); + + // Using the "hot" mold buffer allows us to build the combined source in + // memory that is generally preallocated. This makes it not necessary + // to say in advance how large the buffer needs to be. However, currently + // the mold buffer is REBUNI wide characters, while TCC expects ASCII. + // Hence it has to be "popped" as UTF8 into a fresh series. + // + // !!! Future plans are to use "UTF-8 Everywhere", which would mean the + // mold buffer's data could be used directly. + // + // !!! Investigate how much UTF-8 support there is in TCC for strings/etc + // + REB_MOLD mo; + CLEARS(&mo); + Push_Mold(&mo); + + // The core_header_source is %sys-core.h with all include files expanded + // + Append_Unencoded(mo.series, cs_cast(core_header_source)); + + // This prolog resets the line number count to 0 where the user source + // starts, in order to give more meaningful line numbers in errors + // + Append_Unencoded(mo.series, "\n# 0 \"user-source\" 1\n"); + + REBDSP dsp_orig = DSP; + + // The user code is added next + // + RELVAL *item; + for (item = VAL_ARRAY_AT(natives); NOT_END(item); ++item) { + const RELVAL *var = item; + if (IS_WORD(item) || IS_GET_WORD(item)) { + var = Get_Opt_Var_May_Fail(item, VAL_SPECIFIER(natives)); + if (IS_VOID(var)) + fail (Error_No_Value_Core(item, VAL_SPECIFIER(natives))); + } + + if (IS_FUNCTION(var)) { + assert(GET_VAL_FLAG(var, FUNC_FLAG_USER_NATIVE)); + + // Remember this function, because we're going to need to come + // back and fill in its dispatcher and TCC_State after the + // compilation... + // + DS_PUSH(const_KNOWN(var)); + + RELVAL *info = VAL_FUNC_BODY(var); + RELVAL *source = VAL_ARRAY_AT_HEAD(info, 0); + RELVAL *name = VAL_ARRAY_AT_HEAD(info, 1); + + Append_Unencoded(mo.series, "REB_R "); + Append_String( + mo.series, + VAL_SERIES(name), + VAL_INDEX(name), + VAL_LEN_AT(name) + ); + Append_Unencoded(mo.series, "(REBFRM *frame_)\n{\n"); + + REBVAL *param = VAL_FUNC_PARAMS_HEAD(var); + REBCNT num = 1; + for (; NOT_END(param); ++param) { + REBSTR *spelling = VAL_PARAM_SPELLING(param); + + enum Reb_Param_Class pclass = VAL_PARAM_CLASS(param); + switch (pclass) { + case PARAM_CLASS_LOCAL: + case PARAM_CLASS_RETURN: + case PARAM_CLASS_LEAVE: + assert(FALSE); // natives shouldn't generally use these... + break; + + case PARAM_CLASS_REFINEMENT: + case PARAM_CLASS_NORMAL: + case PARAM_CLASS_SOFT_QUOTE: + case PARAM_CLASS_HARD_QUOTE: + Append_Unencoded(mo.series, " "); + if (pclass == PARAM_CLASS_REFINEMENT) + Append_Unencoded(mo.series, "REFINE("); + else + Append_Unencoded(mo.series, "PARAM("); + Append_Int(mo.series, num); + ++num; + Append_Unencoded(mo.series, ", "); + Append_Unencoded(mo.series, cs_cast(STR_HEAD(spelling))); + Append_Unencoded(mo.series, ");\n"); + break; + + default: + assert(FALSE); + } + } + if (num != 1) + Append_Unencoded(mo.series, "\n"); + + Append_String( + mo.series, + VAL_SERIES(source), + VAL_INDEX(source), + VAL_LEN_AT(source) + ); + Append_Unencoded(mo.series, "\n}\n\n"); + } + else if (IS_STRING(var)) { + // + // A string is treated as just a fragment of code. This allows + // for writing things like C functions or macros that are shared + // between multiple user natives. + // + Append_String( + mo.series, + VAL_SERIES(var), + VAL_INDEX(var), + VAL_LEN_AT(var) + ); + Append_Unencoded(mo.series, "\n"); + } + else { + assert(FALSE); + } + } + + REBSER *combined_src = Pop_Molded_UTF8(&mo); + + TCCState *state = tcc_new(); + if (!state) + fail (Error_Tcc_Construction_Raw()); + + tcc_set_error_func(state, NULL, tcc_error_report); + + if (options) { + tcc_set_options(state, CHAR_HEAD(VAL_SERIES(options))); + } + + REBCTX *err = NULL; + + if ((err = add_path(state, inc, tcc_add_include_path, RE_TCC_INCLUDE))) + fail (err); + + if (tcc_set_output_type(state, TCC_OUTPUT_MEMORY) < 0) + fail (Error_Tcc_Output_Type_Raw()); + + if (tcc_compile_string(state, CHAR_HEAD(combined_src)) < 0) + fail (Error_Tcc_Compile_Raw(natives)); + + Free_Series(combined_src); + + // It is technically possible for ELF binaries to "--export-dynamic" (or + // -rdynamic in CMake) and make executables embed symbols for functions + // in them "like a DLL". However, we would like to make API symbols for + // Rebol available to the dynamically loaded code on all platforms, so + // this uses `tcc_add_symbol()` to work the same way on Windows/Linux/OSX + // + const struct rebol_sym_data_t *sym_data = &rebol_sym_data[0]; + for (; sym_data->name != NULL; sym_data ++) { + if (tcc_add_symbol(state, sym_data->name, sym_data->data) < 0) + fail (Error_Tcc_Relocate_Raw()); + } + + const struct rebol_sym_func_t *sym_func = &rebol_sym_funcs[0]; + for (; sym_func->name != NULL; sym_func ++) { + // ISO C++ forbids casting between pointer-to-function and + // pointer-to-object, use memcpy to circumvent. + void *ptr; + assert(sizeof(ptr) == sizeof(sym_func->func)); + memcpy(&ptr, &sym_func->func, sizeof(ptr)); + if (tcc_add_symbol(state, sym_func->name, ptr) < 0) + fail (Error_Tcc_Relocate_Raw()); + } + + // Add symbols in libtcc1, to avoid bundling with libtcc1.a + const void **sym = &r3_libtcc1_symbols[0]; + for (; *sym != NULL; sym += 2) { + if (tcc_add_symbol(state, cast(const char*, *sym), *(sym + 1)) < 0) + fail (Error_Tcc_Relocate_Raw()); + } + + if ((err = add_path( + state, libdir, tcc_add_library_path, RE_TCC_LIBRARY_PATH + ))) { + fail (err); + } + + if ((err = add_path(state, lib, tcc_add_library, RE_TCC_LIBRARY))) + fail(err); + + if (rundir) + do_set_path(state, rundir, tcc_set_lib_path); + + if (tcc_relocate(state, TCC_RELOCATE_AUTO) < 0) + fail (Error_Tcc_Relocate_Raw()); + + DECLARE_LOCAL (handle); + Init_Handle_Managed( + handle, + state, // "data" pointer + 0, + cleanup // called upon GC + ); + + // With compilation complete, find the matching linker names and get + // their function pointers to substitute in for the dispatcher. + // + while (DSP != dsp_orig) { + REBVAL *var = DS_TOP; + + assert(IS_FUNCTION(var)); + assert(GET_VAL_FLAG(var, FUNC_FLAG_USER_NATIVE)); + + RELVAL *info = VAL_FUNC_BODY(var); + RELVAL *name = VAL_ARRAY_AT_HEAD(info, 1); + RELVAL *stored_state = VAL_ARRAY_AT_HEAD(info, 2); + + REBCNT index; + REBSER *utf8 = Temp_Bin_Str_Managed(name, &index, 0); + + void *sym = tcc_get_symbol(state, cs_cast(BIN_AT(utf8, index))); + if (sym == NULL) + fail (Error_Tcc_Sym_Not_Found_Raw(name)); + + // ISO C++ forbids casting between pointer-to-function and + // pointer-to-object, use memcpy to circumvent. + REBNAT c_func; + assert(sizeof(c_func) == sizeof(void*)); + memcpy(&c_func, &sym, sizeof(c_func)); + + FUNC_DISPATCHER(VAL_FUNC(var)) = c_func; + Move_Value(stored_state, handle); + + DS_DROP; + } + + return R_VOID; +#endif +} diff --git a/src/core/n-protect.c b/src/core/n-protect.c new file mode 100644 index 0000000000..42580dd62e --- /dev/null +++ b/src/core/n-protect.c @@ -0,0 +1,490 @@ +// +// File: %n-protect.c +// Summary: "native functions for series and object field protection" +// Section: natives +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + +#include "sys-core.h" + + +// +// Protect_Key: C +// +static void Protect_Key(REBCTX *context, REBCNT index, REBFLGS flags) +{ + REBVAL *var = CTX_VAR(context, index); + + // Due to the fact that not all the bits in a value header are copied when + // Move_Value is done, it's possible to set the protection status of a + // variable on the value vs. the key. This means the keylist does not + // have to be modified, and hence it doesn't have to be made unique + // from any objects that were sharing it. + // + if (GET_FLAG(flags, PROT_WORD)) { + if (GET_FLAG(flags, PROT_SET)) + SET_VAL_FLAG(var, VALUE_FLAG_PROTECTED); + else + CLEAR_VAL_FLAG(var, VALUE_FLAG_PROTECTED); + } + + if (GET_FLAG(flags, PROT_HIDE)) { + // + // !!! For the moment, hiding is still implemented via typeset flags. + // Since PROTECT/HIDE is something of an esoteric feature, keep it + // that way for now, even though it means the keylist has to be + // made unique. + // + Ensure_Keylist_Unique_Invalidated(context); + + REBVAL *key = CTX_KEY(context, index); + + if (GET_FLAG(flags, PROT_SET)) + SET_VAL_FLAGS(key, TYPESET_FLAG_HIDDEN | TYPESET_FLAG_UNBINDABLE); + else + CLEAR_VAL_FLAGS( + key, TYPESET_FLAG_HIDDEN | TYPESET_FLAG_UNBINDABLE + ); + } +} + + +// +// Protect_Value: C +// +// Anything that calls this must call Uncolor() when done. +// +void Protect_Value(RELVAL *value, REBFLGS flags) +{ + if (ANY_SERIES(value) || IS_MAP(value)) + Protect_Series(VAL_SERIES(value), VAL_INDEX(value), flags); + else if (ANY_CONTEXT(value)) + Protect_Context(VAL_CONTEXT(value), flags); +} + + +// +// Protect_Series: C +// +// Anything that calls this must call Uncolor() when done. +// +void Protect_Series(REBSER *s, REBCNT index, REBFLGS flags) +{ + if (Is_Series_Black(s)) + return; // avoid loop + + if (GET_FLAG(flags, PROT_SET)) { + if (GET_FLAG(flags, PROT_FREEZE)) { + assert(GET_FLAG(flags, PROT_DEEP)); + SET_SER_INFO(s, SERIES_INFO_FROZEN); + } + else + SET_SER_INFO(s, SERIES_INFO_PROTECTED); + } + else { + assert(!GET_FLAG(flags, PROT_FREEZE)); + CLEAR_SER_INFO(s, SERIES_INFO_PROTECTED); + } + + if (NOT_SER_FLAG(s, SERIES_FLAG_ARRAY) || !GET_FLAG(flags, PROT_DEEP)) + return; + + Flip_Series_To_Black(s); // recursion protection + + RELVAL *val = ARR_AT(ARR(s), index); + for (; NOT_END(val); val++) + Protect_Value(val, flags); +} + + +// +// Protect_Context: C +// +// Anything that calls this must call Uncolor() when done. +// +void Protect_Context(REBCTX *c, REBFLGS flags) +{ + if (Is_Series_Black(SER(CTX_VARLIST(c)))) + return; // avoid loop + + if (GET_FLAG(flags, PROT_SET)) { + if (GET_FLAG(flags, PROT_FREEZE)) { + assert(GET_FLAG(flags, PROT_DEEP)); + SET_SER_INFO(CTX_VARLIST(c), SERIES_INFO_FROZEN); + } + else + SET_SER_INFO(CTX_VARLIST(c), SERIES_INFO_PROTECTED); + } + else { + assert(!GET_FLAG(flags, PROT_FREEZE)); + CLEAR_SER_INFO(CTX_VARLIST(c), SERIES_INFO_PROTECTED); + } + + if (!GET_FLAG(flags, PROT_DEEP)) return; + + Flip_Series_To_Black(SER(CTX_VARLIST(c))); // for recursion + + REBVAL *var = CTX_VARS_HEAD(c); + for (; NOT_END(var); ++var) + Protect_Value(var, flags); +} + + +// +// Protect_Word_Value: C +// +static void Protect_Word_Value(REBVAL *word, REBFLGS flags) +{ + if (ANY_WORD(word) && IS_WORD_BOUND(word)) { + Protect_Key(VAL_WORD_CONTEXT(word), VAL_WORD_INDEX(word), flags); + if (GET_FLAG(flags, PROT_DEEP)) { + // + // Ignore existing mutability state so that it may be modified. + // Most routines should NOT do this! + // + REBVAL *var = Get_Var_Core( + word, + SPECIFIED, + GETVAR_READ_ONLY + ); + Protect_Value(var, flags); + Uncolor(var); + } + } + else if (ANY_PATH(word)) { + REBCNT index; + REBCTX *context = Resolve_Path(word, &index); + + if (context != NULL) { + Protect_Key(context, index, flags); + if (GET_FLAG(flags, PROT_DEEP)) { + REBVAL *var = CTX_VAR(context, index); + Protect_Value(var, flags); + Uncolor(var); + } + } + } +} + + +// +// Protect_Unprotect_Core: C +// +// Common arguments between protect and unprotect: +// +static REB_R Protect_Unprotect_Core(REBFRM *frame_, REBFLGS flags) +{ + INCLUDE_PARAMS_OF_PROTECT; + + UNUSED(PAR(hide)); // unused here, but processed in caller + + REBVAL *value = ARG(value); + + // flags has PROT_SET bit (set or not) + + Check_Security(Canon(SYM_PROTECT), POL_WRITE, value); + + if (REF(deep)) SET_FLAG(flags, PROT_DEEP); + //if (REF(words)) SET_FLAG(flags, PROT_WORD); + + if (IS_WORD(value) || IS_PATH(value)) { + Protect_Word_Value(value, flags); // will unmark if deep + goto return_value_arg; + } + + if (IS_BLOCK(value)) { + if (REF(words)) { + RELVAL *val; + for (val = VAL_ARRAY_AT(value); NOT_END(val); val++) { + DECLARE_LOCAL (word); // need binding, can't pass RELVAL + Derelativize(word, val, VAL_SPECIFIER(value)); + Protect_Word_Value(word, flags); // will unmark if deep + } + goto return_value_arg; + } + if (REF(values)) { + REBVAL *var; + RELVAL *item; + + DECLARE_LOCAL (safe); + + for (item = VAL_ARRAY_AT(value); NOT_END(item); ++item) { + if (IS_WORD(item)) { + // + // Since we *are* PROTECT we allow ourselves to get mutable + // references to even protected values to protect them. + // + var = Get_Var_Core( + item, + VAL_SPECIFIER(value), + GETVAR_READ_ONLY + ); + } + else if (IS_PATH(value)) { + if (Do_Path_Throws_Core( + safe, NULL, value, SPECIFIED, NULL + )) + fail (Error_No_Catch_For_Throw(safe)); + + var = safe; + } + else { + Move_Value(safe, value); + var = safe; + } + + Protect_Value(var, flags); + if (GET_FLAG(flags, PROT_DEEP)) + Uncolor(var); + } + goto return_value_arg; + } + } + + if (GET_FLAG(flags, PROT_HIDE)) fail (Error_Bad_Refines_Raw()); + + Protect_Value(value, flags); + + if (GET_FLAG(flags, PROT_DEEP)) + Uncolor(value); + +return_value_arg: + Move_Value(D_OUT, ARG(value)); + return R_OUT; +} + + +// +// protect: native [ +// +// {Protect a series or a variable from being modified.} +// +// value [word! any-series! bitset! map! object! module!] +// /deep +// "Protect all sub-series/objects as well" +// /words +// "Process list as words (and path words)" +// /values +// "Process list of values (implied GET)" +// /hide +// "Hide variables (avoid binding and lookup)" +// ] +// +REBNATIVE(protect) +{ + INCLUDE_PARAMS_OF_PROTECT; + + // Avoid unused parameter warnings (core routine handles them via frame) + // + UNUSED(PAR(value)); + UNUSED(PAR(deep)); + UNUSED(PAR(words)); + UNUSED(PAR(values)); + + REBFLGS flags = FLAGIT(PROT_SET); + + if (REF(hide)) + SET_FLAG(flags, PROT_HIDE); + else + SET_FLAG(flags, PROT_WORD); // there is no unhide + + return Protect_Unprotect_Core(frame_, flags); +} + + +// +// unprotect: native [ +// +// {Unprotect a series or a variable (it can again be modified).} +// +// value [word! any-series! bitset! map! object! module!] +// /deep +// "Protect all sub-series as well" +// /words +// "Block is a list of words" +// /values +// "Process list of values (implied GET)" +// /hide +// "HACK to make PROTECT and UNPROTECT have the same signature" +// ] +// +REBNATIVE(unprotect) +{ + INCLUDE_PARAMS_OF_UNPROTECT; + + // Avoid unused parameter warnings (core handles them via frame) + // + UNUSED(PAR(value)); + UNUSED(PAR(deep)); + UNUSED(PAR(words)); + UNUSED(PAR(values)); + + if (REF(hide)) + fail ("Cannot un-hide an object field once hidden"); + + return Protect_Unprotect_Core(frame_, FLAGIT(PROT_WORD)); +} + + +// +// Is_Value_Immutable: C +// +REBOOL Is_Value_Immutable(const RELVAL *v) { + if ( + IS_BLANK(v) + || IS_BAR(v) + || IS_LIT_BAR(v) + || ANY_SCALAR(v) + || ANY_WORD(v) + ){ + return TRUE; + } + + if (ANY_ARRAY(v)) + return Is_Array_Deeply_Frozen(VAL_ARRAY(v)); + + if (ANY_CONTEXT(v)) + return Is_Context_Deeply_Frozen(VAL_CONTEXT(v)); + + if (ANY_SERIES(v)) + return Is_Series_Frozen(VAL_SERIES(v)); + + return FALSE; +} + + +// +// locked?: native [ +// +// {Determine if the value is locked (deeply and permanently immutable)} +// +// return: [logic!] +// value [any-value!] +// ] +// +REBNATIVE(locked_q) +{ + INCLUDE_PARAMS_OF_LOCKED_Q; + + return R_FROM_BOOL(Is_Value_Immutable(ARG(value))); +} + + +// +// Ensure_Value_Immutable: C +// +void Ensure_Value_Immutable(REBVAL *v) { + if (Is_Value_Immutable(v)) + return; + + if (ANY_ARRAY(v)) + Deep_Freeze_Array(VAL_ARRAY(v)); + else if (ANY_CONTEXT(v)) + Deep_Freeze_Context(VAL_CONTEXT(v)); + else if (ANY_SERIES(v)) + Freeze_Sequence(VAL_SERIES(v)); + else + fail (Error_Invalid_Type(VAL_TYPE(v))); // not yet implemented +} + + +// +// lock: native [ +// +// {Permanently lock values (if applicable) so they can be immutably shared.} +// +// value [any-value!] +// {Value to lock (will be locked deeply if an ANY-ARRAY!)} +// /clone +// {Will lock a clone of the original (if not already immutable)} +// ] +// +REBNATIVE(lock) +// +// !!! COPY in Rebol truncates before the index. You can't `y: copy next x` +// and then `first back y` to get at a copy of the the original `first x`. +// +// This locking operation is opportunistic in terms of whether it actually +// copies the data or not. But if it did just a normal COPY, it'd truncate, +// while if it just passes the value through it does not truncate. So +// `lock/copy x` wouldn't be semantically equivalent to `lock copy x` :-/ +// +// So the strategy here is to go with a different option, CLONE. CLONE was +// already being considered as an operation due to complaints about backward +// compatibility if COPY were changed to /DEEP by default. +// +// The "freezing" bit can only be used on deep copies, so it would not make +// sense to use with a shallow one. However, a truncating COPY/DEEP could +// be made to have a version operating on read only data that reused a +// subset of the data. This would use a "slice"; letting one series refer +// into another, with a different starting point. That would complicate the +// garbage collector because multiple REBSER would be referring into the same +// data. So that's a possibility. +{ + INCLUDE_PARAMS_OF_LOCK; + + REBVAL *v = ARG(value); + + if (!REF(clone)) + Move_Value(D_OUT, v); + else { + if (ANY_ARRAY(v)) { + Init_Any_Array_At( + D_OUT, + VAL_TYPE(v), + Copy_Array_Deep_Managed( + VAL_ARRAY(v), + VAL_SPECIFIER(v) + ), + VAL_INDEX(v) + ); + } + else if (ANY_CONTEXT(v)) { + const REBOOL deep = TRUE; + const REBU64 types = TS_STD_SERIES; + + Init_Any_Context( + D_OUT, + VAL_TYPE(v), + Copy_Context_Core(VAL_CONTEXT(v), deep, types) + ); + } + else if (ANY_SERIES(v)) { + Init_Any_Series_At( + D_OUT, + VAL_TYPE(v), + Copy_Sequence(VAL_SERIES(v)), + VAL_INDEX(v) + ); + } + else + fail (Error_Invalid_Type(VAL_TYPE(v))); // not yet implemented + } + + Ensure_Value_Immutable(D_OUT); + + return R_OUT; +} diff --git a/src/core/n-reduce.c b/src/core/n-reduce.c new file mode 100644 index 0000000000..b977c03f97 --- /dev/null +++ b/src/core/n-reduce.c @@ -0,0 +1,416 @@ +// +// File: %n-reduce.h +// Summary: {REDUCE and COMPOSE natives and associated service routines} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + +#include "sys-core.h" + +// +// Reduce_Any_Array_Throws: C +// +// Reduce array from the index position specified in the value. +// +// If `into` then splice into the existing `out`. Otherwise, overwrite the +// `out` with all values collected from the stack, into an array matching the +// type of the input. So [1 + 1 2 + 2] => [3 4], and 1/+/1/2/+/2 => 3/4 +// +// !!! This is not necessarily the best answer, it's just the mechanically +// most obvious one. +// +REBOOL Reduce_Any_Array_Throws( + REBVAL *out, + REBVAL *any_array, + REBFLGS flags +) { + assert( + NOT(flags & REDUCE_FLAG_KEEP_BARS) + == LOGICAL(flags & REDUCE_FLAG_DROP_BARS) + ); // only one should be true, but caller should be explicit of which + + REBDSP dsp_orig = DSP; + + DECLARE_FRAME (f); + Push_Frame(f, any_array); + + DECLARE_LOCAL (reduced); + + while (NOT_END(f->value)) { + if (IS_BAR(f->value)) { + if (flags & REDUCE_FLAG_KEEP_BARS) { + DS_PUSH_TRASH; + Quote_Next_In_Frame(DS_TOP, f); + } + else + Fetch_Next_In_Frame(f); + + continue; + } + + if (Do_Next_In_Frame_Throws(reduced, f)) { + Move_Value(out, reduced); + DS_DROP_TO(dsp_orig); + Drop_Frame(f); + return TRUE; + } + + if (IS_VOID(reduced)) { + // + // !!! Review if there should be a form of reduce which allows + // void expressions. The general feeling is that it shouldn't + // be allowed by default, since N expressions would not make N + // results...and reduce is often used for positional purposes. + // Substituting anything (like a NONE!, or anything else) would + // perhaps be disingenuous. + // + fail (Error_Reduce_Made_Void_Raw()); + } + + DS_PUSH(reduced); + } + + if (flags & REDUCE_FLAG_INTO) + Pop_Stack_Values_Into(out, dsp_orig); + else + Init_Any_Array(out, VAL_TYPE(any_array), Pop_Stack_Values(dsp_orig)); + + Drop_Frame(f); + return FALSE; +} + + +// +// reduce: native [ +// +// {Evaluates expressions and returns multiple results.} +// +// return: [ any-value!] +// value [ any-value!] +// {If BLOCK!, expressions are reduced, otherwise single value.} +// /into +// {Output results into a series with no intermediate storage} +// target [any-array!] +// ] +// +REBNATIVE(reduce) +{ + INCLUDE_PARAMS_OF_REDUCE; + + REBVAL *value = ARG(value); + + if (IS_VOID(value)) + return R_VOID; // !!! Should this be allowed? (Red allows it) + + if (IS_BLOCK(value)) { + if (REF(into)) + Move_Value(D_OUT, ARG(target)); + + if (Reduce_Any_Array_Throws( + D_OUT, + value, + REF(into) + ? REDUCE_FLAG_INTO | REDUCE_FLAG_KEEP_BARS + : REDUCE_FLAG_KEEP_BARS + )){ + return R_OUT_IS_THROWN; + } + + return R_OUT; + } + + // A single element should do what is effectively an evaluation but with + // no arguments. This is a change in behavior from R3-Alpha, which would + // just return the input as is, e.g. `reduce quote (1 + 2)` => (1 + 2). + // + // !!! Should the error be more "reduce-specific" if args were required? + // + if (Eval_Value_Throws(D_OUT, value)) + return R_OUT_IS_THROWN; + + if (NOT(REF(into))) + return R_OUT; // just return the evaluated item if no /INTO target + + REBVAL *into = ARG(target); + assert(ANY_ARRAY(into)); + FAIL_IF_READ_ONLY_ARRAY(VAL_ARRAY(into)); + + // Insert the single item into the target array at its current position, + // and return the position after the insertion (the /INTO convention) + + VAL_INDEX(into) = Insert_Series( + SER(VAL_ARRAY(into)), + VAL_INDEX(into), + cast(REBYTE*, D_OUT), + 1 // multiplied by width (sizeof(REBVAL)) in Insert_Series + ); + + Move_Value(D_OUT, into); + return R_OUT; +} + + +// +// Compose_Any_Array_Throws: C +// +// Compose a block from a block of un-evaluated values and GROUP! arrays that +// are evaluated. This calls into Do_Core, so if 'into' is provided, then its +// series must be protected from garbage collection. +// +// deep - recurse into sub-blocks +// only - parens that return blocks are kept as blocks +// +// Writes result value at address pointed to by out. +// +REBOOL Compose_Any_Array_Throws( + REBVAL *out, + const REBVAL *any_array, + REBOOL deep, + REBOOL only, + REBOOL into +) { + REBDSP dsp_orig = DSP; + + DECLARE_FRAME (f); + Push_Frame(f, any_array); + + DECLARE_LOCAL (composed); + DECLARE_LOCAL (specific); + + while (NOT_END(f->value)) { + if (IS_GROUP(f->value)) { + // + // Evaluate the GROUP! at current position into `composed` cell. + // + REBSPC *derived = Derive_Specifier(f->specifier, f->value); + if (Do_At_Throws( + composed, + VAL_ARRAY(f->value), + VAL_INDEX(f->value), + derived + )){ + Move_Value(out, composed); + DS_DROP_TO(dsp_orig); + Drop_Frame(f); + return TRUE; + } + + Fetch_Next_In_Frame(f); + + if (IS_BLOCK(composed) && !only) { + // + // compose [blocks ([a b c]) merge] => [blocks a b c merge] + // + RELVAL *push = VAL_ARRAY_AT(composed); + while (NOT_END(push)) { + // + // `evaluated` is known to be specific, but its specifier + // may be needed to derelativize its children. + // + DS_PUSH_RELVAL(push, VAL_SPECIFIER(composed)); + push++; + } + } + else if (!IS_VOID(composed)) { + // + // compose [(1 + 2) inserts as-is] => [3 inserts as-is] + // compose/only [([a b c]) unmerged] => [[a b c] unmerged] + // + DS_PUSH(composed); + } + else { + // + // compose [(print "Voids *vanish*!")] => [] + // + } + } + else if (deep) { + if (IS_BLOCK(f->value)) { + // + // compose/deep [does [(1 + 2)] nested] => [does [3] nested] + + Derelativize(specific, f->value, f->specifier); + + if (Compose_Any_Array_Throws( + composed, + specific, + TRUE, + only, + into + )) { + Move_Value(out, composed); + DS_DROP_TO(dsp_orig); + Drop_Frame(f); + return TRUE; + } + + DS_PUSH(composed); + } + else { + if (ANY_ARRAY(f->value)) { + // + // compose [copy/(orig) (copy)] => [copy/(orig) (copy)] + // !!! path and second group are copies, first group isn't + // + REBSPC *derived = Derive_Specifier(f->specifier, f->value); + REBARR *copy = Copy_Array_Shallow( + VAL_ARRAY(f->value), + derived + ); + DS_PUSH_TRASH; + Init_Any_Array_At( + DS_TOP, VAL_TYPE(f->value), copy, VAL_INDEX(f->value) + ); // ...manages + } + else + DS_PUSH_RELVAL(f->value, f->specifier); + } + Fetch_Next_In_Frame(f); + } + else { + // + // compose [[(1 + 2)] (reverse "wollahs")] => [[(1 + 2)] "shallow"] + // + DS_PUSH_RELVAL(f->value, f->specifier); + Fetch_Next_In_Frame(f); + } + } + + if (into) + Pop_Stack_Values_Into(out, dsp_orig); + else + Init_Any_Array(out, VAL_TYPE(any_array), Pop_Stack_Values(dsp_orig)); + + Drop_Frame(f); + return FALSE; +} + + +// +// compose: native [ +// +// {Evaluates only the GROUP!s in a block of expressions, returning a block.} +// +// value +// "Block to compose (or any other type evaluates to itself)" +// ; ^-- is this sensible? +// /deep +// "Compose nested blocks" +// /only +// {Insert a block as a single value (not the contents of the block)} +// /into +// {Output results into a series with no intermediate storage} +// out [any-array! any-string! binary!] +// ] +// +REBNATIVE(compose) +{ + INCLUDE_PARAMS_OF_COMPOSE; + + // !!! Should 'compose quote (a (1 + 2) b)' give back '(a 3 b)' ? + // What about 'compose quote a/(1 + 2)/b' ? + // + if (!IS_BLOCK(ARG(value))) { + Move_Value(D_OUT, ARG(value)); + return R_OUT; + } + + // Compose_Values_Throws() expects `out` to contain the target if it is + // passed TRUE as the `into` flag. + // + if (REF(into)) + Move_Value(D_OUT, ARG(out)); + else + assert(IS_END(D_OUT)); // !!! guaranteed, better signal than `into`? + + if (Compose_Any_Array_Throws( + D_OUT, + ARG(value), + REF(deep), + REF(only), + REF(into) + )) { + return R_OUT_IS_THROWN; + } + + return R_OUT; +} + + +enum FLATTEN_LEVEL { + FLATTEN_NOT, + FLATTEN_ONCE, + FLATTEN_DEEP +}; + + +static void Flatten_Core( + RELVAL head[], + REBSPC *specifier, + enum FLATTEN_LEVEL level +) { + RELVAL *item = head; + for (; NOT_END(item); ++item) { + if (IS_BLOCK(item) && level != FLATTEN_NOT) { + REBSPC *derived = Derive_Specifier(specifier, item); + Flatten_Core( + VAL_ARRAY_AT(item), + derived, + level == FLATTEN_ONCE ? FLATTEN_NOT : FLATTEN_DEEP + ); + } + else + DS_PUSH_RELVAL(item, specifier); + } +} + + +// +// flatten: native [ +// +// {Flattens a block of blocks.} +// +// return: [block!] +// {The flattened result block} +// block [block!] +// {The nested source block} +// /deep +// ] +// +REBNATIVE(flatten) +{ + INCLUDE_PARAMS_OF_FLATTEN; + + REBDSP dsp_orig = DSP; + + Flatten_Core( + VAL_ARRAY_AT(ARG(block)), + VAL_SPECIFIER(ARG(block)), + REF(deep) ? FLATTEN_DEEP : FLATTEN_ONCE + ); + + Init_Block(D_OUT, Pop_Stack_Values(dsp_orig)); + return R_OUT; +} diff --git a/src/core/n-sets.c b/src/core/n-sets.c index feede66255..caddca48a1 100644 --- a/src/core/n-sets.c +++ b/src/core/n-sets.c @@ -1,289 +1,535 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: n-sets.c -** Summary: native functions for data sets -** Section: natives -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %n-sets.c +// Summary: "native functions for data sets" +// Section: natives +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" enum { - SOP_BOTH, // combine and interate over both series - SOP_CHECK, // check other series for value existence - SOP_INVERT, // invert the result of the search + SOP_NONE = 0, // used by UNIQUE (other flags do not apply) + SOP_FLAG_BOTH = 1 << 0, // combine and interate over both series + SOP_FLAG_CHECK = 1 << 1, // check other series for value existence + SOP_FLAG_INVERT = 1 << 2 // invert the result of the search }; -#define SET_OP_UNIQUE 0 -#define SET_OP_UNION FLAGIT(SOP_BOTH) -#define SET_OP_INTERSECT FLAGIT(SOP_CHECK) -#define SET_OP_EXCLUDE (FLAGIT(SOP_CHECK) | FLAGIT(SOP_INVERT)) -#define SET_OP_DIFFERENCE (FLAGIT(SOP_BOTH) | FLAGIT(SOP_CHECK) | FLAGIT(SOP_INVERT)) - -/*********************************************************************** -** -*/ static REBINT Do_Set_Operation(REBVAL *ds, REBCNT flags) -/* -** Do set operations on a series. -** -***********************************************************************/ -{ - REBVAL *val; - REBVAL *val1; - REBVAL *val2 = 0; - REBSER *ser; - REBSER *hser = 0; // hash table for series - REBSER *retser; // return series - REBSER *hret; // hash table for return series - REBCNT i; - REBINT h = TRUE; - REBCNT skip = 1; // record size - REBCNT cased = 0; // case sensitive when TRUE - - SET_NONE(D_RET); - val1 = D_ARG(1); - i = 2; - - // Check for second series argument: - if (flags != SET_OP_UNIQUE) { - val2 = D_ARG(i++); - if (VAL_TYPE(val1) != VAL_TYPE(val2)) { - Trap_Types(RE_EXPECT_VAL, VAL_TYPE(val1), VAL_TYPE(val2)); - } - } - - // Refinements /case and /skip N - cased = D_REF(i++); // cased - if (D_REF(i++)) skip = Int32s(D_ARG(i), 1); - - switch (VAL_TYPE(val1)) { - - case REB_BLOCK: - i = VAL_LEN(val1); - // Setup result block: - if (GET_FLAG(flags, SOP_BOTH)) i += VAL_LEN(val2); - retser = BUF_EMIT; // use preallocated shared block - Resize_Series(retser, i); - hret = Make_Hash_Array(i); // allocated - - // Optimization note: !! - // This code could be optimized for small blocks by not hashing them - // and extending Find_Key to do a FIND on the value itself w/o the hash. - - do { - // Check what is in series1 but not in series2: - if (GET_FLAG(flags, SOP_CHECK)) - hser = Hash_Block(val2, cased); - - // Iterate over first series: - ser = VAL_SERIES(val1); - i = VAL_INDEX(val1); - FOR_SER(ser, val, i, skip) { - if (GET_FLAG(flags, SOP_CHECK)) { - h = Find_Key(VAL_SERIES(val2), hser, val, skip, cased, 1) >= 0; - if (GET_FLAG(flags, SOP_INVERT)) h = !h; - } - if (h) Find_Key(retser, hret, val, skip, cased, 2); - } - - // Iterate over second series? - if (NZ(i = GET_FLAG(flags, SOP_BOTH))) { - val = val1; - val1 = val2; - val2 = val; - CLR_FLAG(flags, SOP_BOTH); - } - } while (i); - - Set_Block(D_RET, Copy_Series(retser)); - RESET_TAIL(retser); // required - allow reuse - - break; - - case REB_BINARY: - cased = TRUE; - SET_TYPE(D_RET, REB_BINARY); - case REB_STRING: - i = VAL_LEN(val1); - // Setup result block: - if (GET_FLAG(flags, SOP_BOTH)) i += VAL_LEN(val2); - - retser = BUF_MOLD; - Reset_Buffer(retser, i); - RESET_TAIL(retser); - - do { - REBUNI uc; - - cased = cased ? AM_FIND_CASE : 0; - - // Iterate over first series: - ser = VAL_SERIES(val1); - i = VAL_INDEX(val1); - FOR_SER(ser, val, i, skip) { - uc = GET_ANY_CHAR(ser, i); - if (GET_FLAG(flags, SOP_CHECK)) { - h = Find_Str_Char(VAL_SERIES(val2), 0, VAL_INDEX(val2), VAL_TAIL(val2), skip, uc, cased) != NOT_FOUND; - if (GET_FLAG(flags, SOP_INVERT)) h = !h; - } - if (h && (Find_Str_Char(retser, 0, 0, SERIES_TAIL(retser), skip, uc, cased) == NOT_FOUND)) { - Append_String(retser, ser, i, skip); - } - } - - // Iterate over second series? - if (NZ(i = GET_FLAG(flags, SOP_BOTH))) { - val = val1; - val1 = val2; - val2 = val; - CLR_FLAG(flags, SOP_BOTH); - } - } while (i); - - ser = Copy_String(retser, 0, -1); - if (IS_BINARY(D_RET)) - Set_Binary(D_RET, ser); - else - Set_String(D_RET, ser); - break; - - case REB_BITSET: - switch (flags) { - case SET_OP_UNIQUE: - return R_ARG1; - case SET_OP_UNION: - i = A_OR; - break; - case SET_OP_INTERSECT: - i = A_AND; - break; - case SET_OP_DIFFERENCE: - i = A_XOR; - break; - case SET_OP_EXCLUDE: - i = 0; // special case - break; - } - ser = Xandor_Binary(i, val1, val2); - Set_Series(REB_BITSET, D_RET, ser); - break; - - case REB_TYPESET: - switch (flags) { - case SET_OP_UNIQUE: - break; - case SET_OP_UNION: - VAL_TYPESET(val1) |= VAL_TYPESET(val2); - break; - case SET_OP_INTERSECT: - VAL_TYPESET(val1) &= VAL_TYPESET(val2); - break; - case SET_OP_DIFFERENCE: - VAL_TYPESET(val1) ^= VAL_TYPESET(val2); - break; - case SET_OP_EXCLUDE: - VAL_TYPESET(val1) &= ~VAL_TYPESET(val2); - break; - } - return R_ARG1; - - default: - Trap_Arg(val1); - } - - return R_RET; +// +// Make_Set_Operation_Series: C +// +// Do set operations on a series. Case-sensitive if `cased` is TRUE. +// `skip` is the record size. +// +static REBSER *Make_Set_Operation_Series( + const REBVAL *val1, + const REBVAL *val2, + REBFLGS flags, + REBOOL cased, + REBCNT skip +) { + REBCNT i; + REBINT h = 1; // used for both logic true/false and hash check + REBOOL first_pass = TRUE; // are we in the first pass over the series? + REBSER *out_ser; + + assert(ANY_SERIES(val1)); + + if (val2) { + assert(ANY_SERIES(val2)); + + if (ANY_ARRAY(val1)) { + if (!ANY_ARRAY(val2)) + fail (Error_Unexpected_Type(VAL_TYPE(val1), VAL_TYPE(val2))); + + // As long as they're both arrays, we're willing to do: + // + // >> union quote (a b c) 'b/d/e + // (a b c d e) + // + // The type of the result will match the first value. + } + else if (!IS_BINARY(val1)) { + + // We will similarly do any two ANY-STRING! types: + // + // >> union "bde" + // + + if (IS_BINARY(val2)) + fail (Error_Unexpected_Type(VAL_TYPE(val1), VAL_TYPE(val2))); + } + else { + // Binaries only operate with other binaries + + if (!IS_BINARY(val2)) + fail (Error_Unexpected_Type(VAL_TYPE(val1), VAL_TYPE(val2))); + } + } + + // Calculate `i` as maximum length of result block. The temporary buffer + // will be allocated at this size, but copied out at the exact size of + // the actual result. + // + i = VAL_LEN_AT(val1); + if (flags & SOP_FLAG_BOTH) i += VAL_LEN_AT(val2); + + if (ANY_ARRAY(val1)) { + REBSER *hser = 0; // hash table for series + REBSER *hret; // hash table for return series + + // The buffer used for building the return series. This creates + // a new buffer every time, but reusing one might be slightly more + // efficient. + // + REBSER *buffer = SER(Make_Array(i)); + hret = Make_Hash_Sequence(i); // allocated + + // Optimization note: !! + // This code could be optimized for small blocks by not hashing them + // and extending Find_Key to FIND on the value itself w/o the hash. + + do { + REBARR *array1 = VAL_ARRAY(val1); // val1 and val2 swapped 2nd pass! + + // Check what is in series1 but not in series2 + // + if (flags & SOP_FLAG_CHECK) + hser = Hash_Block(val2, skip, cased); + + // Iterate over first series + // + i = VAL_INDEX(val1); + for (; i < ARR_LEN(array1); i += skip) { + RELVAL *item = ARR_AT(array1, i); + if (flags & SOP_FLAG_CHECK) { + h = Find_Key_Hashed( + VAL_ARRAY(val2), + hser, + item, + VAL_SPECIFIER(val1), + skip, + cased, + 1 + ); + h = (h >= 0); + if (flags & SOP_FLAG_INVERT) h = !h; + } + if (h) { + Find_Key_Hashed( + ARR(buffer), + hret, + item, + VAL_SPECIFIER(val1), + skip, + cased, + 2 + ); + } + } + + if (i != ARR_LEN(array1)) { + // + // In the current philosophy, the semantics of what to do + // with things like `intersect/skip [1 2 3] [7] 2` is too + // shaky to deal with, so an error is reported if it does + // not work out evenly to the skip size. + // + fail (Error_Block_Skip_Wrong_Raw()); + } + + if (flags & SOP_FLAG_CHECK) + Free_Series(hser); + + if (!first_pass) break; + first_pass = FALSE; + + // Iterate over second series? + // + if ((i = ((flags & SOP_FLAG_BOTH) != 0))) { + const REBVAL *temp = val1; + val1 = val2; + val2 = temp; + } + } while (i); + + if (hret) + Free_Series(hret); + + // The buffer may have been allocated too large, so copy it at the + // used capacity size + // + out_ser = SER(Copy_Array_Shallow(ARR(buffer), SPECIFIED)); + Free_Array(ARR(buffer)); + } + else { + REB_MOLD mo; + CLEARS(&mo); + + if (IS_BINARY(val1)) { + // + // All binaries use "case-sensitive" comparison (e.g. each byte + // is treated distinctly) + // + cased = TRUE; + } + + // ask mo.series to have at least `i` capacity beyond mo.start + // + mo.opts = MOPT_RESERVE; + mo.reserve = i; + Push_Mold(&mo); + + do { + REBSER *ser = VAL_SERIES(val1); // val1 and val2 swapped 2nd pass! + REBUNI uc; + + // Iterate over first series + // + i = VAL_INDEX(val1); + for (; i < SER_LEN(ser); i += skip) { + uc = GET_ANY_CHAR(ser, i); + if (flags & SOP_FLAG_CHECK) { + h = (NOT_FOUND != Find_Str_Char( + uc, + VAL_SERIES(val2), + 0, + VAL_INDEX(val2), + VAL_LEN_HEAD(val2), + skip, + cased ? AM_FIND_CASE : 0 + )); + + if (flags & SOP_FLAG_INVERT) h = !h; + } + + if (!h) continue; + + if ( + NOT_FOUND == Find_Str_Char( + uc, // c2 (the character to find) + mo.series, // ser + mo.start, // head + mo.start, // index + SER_LEN(mo.series), // tail + skip, // skip + cased ? AM_FIND_CASE : 0 // flags + ) + ) { + Append_String(mo.series, ser, i, skip); + } + } + + if (!first_pass) break; + first_pass = FALSE; + + // Iterate over second series? + // + if ((i = ((flags & SOP_FLAG_BOTH) != 0))) { + const REBVAL *temp = val1; + val1 = val2; + val2 = temp; + } + } while (i); + + out_ser = Pop_Molded_String(&mo); + } + + return out_ser; } -/*********************************************************************** -** -*/ REBNATIVE(difference) -/* -** Set functions use this arg pattern: -** -** set1 [ series! bitset! date! ] "first set" -** set2 [ series! bitset! date! ] "second set" -** /case "case sensitive" -** /skip "treat the series as records of fixed size" -** size [integer!] -** -***********************************************************************/ +// +// difference: native [ +// +// "Returns the special difference of two values." +// +// series1 [any-array! any-string! binary! bitset! date! typeset!] +// series2 [any-array! any-string! binary! bitset! date! typeset!] +// /case +// "Uses case-sensitive comparison" +// /skip +// "Treat the series as records of fixed size" +// size [integer!] +// ] +// +REBNATIVE(difference) { - REBVAL *val1, *val2; - - val1 = D_ARG(1); - val2 = D_ARG(2); - - if (IS_DATE(val1) || IS_DATE(val2)) { - if (!IS_DATE(val1)) Trap_Arg(val1); - if (!IS_DATE(val2)) Trap_Arg(val2); - Subtract_Date(val1, val2, D_RET); - return R_RET; - } - - return Do_Set_Operation(ds, SET_OP_DIFFERENCE); + INCLUDE_PARAMS_OF_DIFFERENCE; + + REBVAL *val1 = ARG(series1); + REBVAL *val2 = ARG(series2); + + // Plain SUBTRACT on dates has historically given a count of days. + // DIFFERENCE has been the way to get the time difference. + // !!! Is this sensible? + // + if (IS_DATE(val1) || IS_DATE(val2)) { + if (VAL_TYPE(val1) != VAL_TYPE(val2)) + fail (Error_Unexpected_Type(VAL_TYPE(val1), VAL_TYPE(val2))); + + Subtract_Date(val1, val2, D_OUT); + return R_OUT; + } + + if (IS_BITSET(val1) || IS_BITSET(val2)) { + if (VAL_TYPE(val1) != VAL_TYPE(val2)) + fail (Error_Unexpected_Type(VAL_TYPE(val1), VAL_TYPE(val2))); + + Init_Bitset(D_OUT, Xandor_Binary(SYM_XOR_T, val1, val2)); + return R_OUT; + } + + if (IS_TYPESET(val1) || IS_TYPESET(val2)) { + if (VAL_TYPE(val1) != VAL_TYPE(val2)) + fail (Error_Unexpected_Type(VAL_TYPE(val1), VAL_TYPE(val2))); + + Move_Value(D_OUT, val1); + VAL_TYPESET_BITS(D_OUT) ^= VAL_TYPESET_BITS(val2); + return R_OUT; + } + + Init_Any_Series( + D_OUT, + VAL_TYPE(val1), + Make_Set_Operation_Series( + val1, + val2, + SOP_FLAG_BOTH | SOP_FLAG_CHECK | SOP_FLAG_INVERT, + REF(case), + REF(skip) ? Int32s(ARG(size), 1) : 1 + ) + ); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(exclude) -/* -***********************************************************************/ +// +// exclude: native [ +// +// {Returns the first data set less the second data set.} +// +// series [any-array! any-string! binary! bitset! typeset!] +// "original data" +// exclusions [any-array! any-string! binary! bitset! typeset!] +// "data to exclude from series" +// /case +// "Uses case-sensitive comparison" +// /skip +// "Treat the series as records of fixed size" +// size [integer!] +// ] +// +REBNATIVE(exclude) { - return Do_Set_Operation(ds, SET_OP_EXCLUDE); + INCLUDE_PARAMS_OF_EXCLUDE; + + REBVAL *val1 = ARG(series); + REBVAL *val2 = ARG(exclusions); + + if (IS_BITSET(val1) || IS_BITSET(val2)) { + if (VAL_TYPE(val1) != VAL_TYPE(val2)) + fail (Error_Unexpected_Type(VAL_TYPE(val1), VAL_TYPE(val2))); + + // !!! 0 was said to be a "special case" in original code + // + Init_Bitset(D_OUT, Xandor_Binary(0, val1, val2)); + return R_OUT; + } + + if (IS_TYPESET(val1) || IS_TYPESET(val2)) { + if (VAL_TYPE(val1) != VAL_TYPE(val2)) + fail (Error_Unexpected_Type(VAL_TYPE(val1), VAL_TYPE(val2))); + + Move_Value(D_OUT, val1); + VAL_TYPESET_BITS(D_OUT) &= ~VAL_TYPESET_BITS(val2); + return R_OUT; + } + + Init_Any_Series( + D_OUT, + VAL_TYPE(val1), + Make_Set_Operation_Series( + val1, + val2, + SOP_FLAG_CHECK | SOP_FLAG_INVERT, + REF(case), + REF(skip) ? Int32s(ARG(size), 1) : 1 + ) + ); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(intersect) -/* -***********************************************************************/ +// +// intersect: native [ +// +// "Returns the intersection of two data series." +// +// series1 [any-array! any-string! binary! bitset! typeset!] +// series2 [any-array! any-string! binary! bitset! typeset!] +// /case +// "Uses case-sensitive comparison" +// /skip +// "Treat the series as records of fixed size" +// size [integer!] +// ] +// +REBNATIVE(intersect) { - return Do_Set_Operation(ds, SET_OP_INTERSECT); + INCLUDE_PARAMS_OF_INTERSECT; + + REBVAL *val1 = ARG(series1); + REBVAL *val2 = ARG(series2); + + if (IS_BITSET(val1) || IS_BITSET(val2)) { + if (VAL_TYPE(val1) != VAL_TYPE(val2)) + fail (Error_Unexpected_Type(VAL_TYPE(val1), VAL_TYPE(val2))); + + Init_Bitset(D_OUT, Xandor_Binary(SYM_AND_T, val1, val2)); + return R_OUT; + } + + if (IS_TYPESET(val1) || IS_TYPESET(val2)) { + if (VAL_TYPE(val1) != VAL_TYPE(val2)) + fail (Error_Unexpected_Type(VAL_TYPE(val1), VAL_TYPE(val2))); + + Move_Value(D_OUT, val1); + VAL_TYPESET_BITS(D_OUT) &= VAL_TYPESET_BITS(val2); + return R_OUT; + } + + Init_Any_Series( + D_OUT, + VAL_TYPE(val1), + Make_Set_Operation_Series( + val1, + val2, + SOP_FLAG_CHECK, + REF(case), + REF(skip) ? Int32s(ARG(size), 1) : 1 + ) + ); + + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(union) -/* -***********************************************************************/ +// +// union: native [ +// +// "Returns the union of two data series." +// +// series1 [any-array! any-string! binary! bitset! typeset!] +// series2 [any-array! any-string! binary! bitset! typeset!] +// /case +// "Use case-sensitive comparison" +// /skip +// "Treat the series as records of fixed size" +// size [integer!] +// ] +// +REBNATIVE(union) { - return Do_Set_Operation(ds, SET_OP_UNION); + INCLUDE_PARAMS_OF_UNION; + + REBVAL *val1 = ARG(series1); + REBVAL *val2 = ARG(series2); + + if (IS_BITSET(val1) || IS_BITSET(val2)) { + if (VAL_TYPE(val1) != VAL_TYPE(val2)) + fail (Error_Unexpected_Type(VAL_TYPE(val1), VAL_TYPE(val2))); + + Init_Bitset(D_OUT, Xandor_Binary(SYM_OR_T, val1, val2)); + return R_OUT; + } + + if (IS_TYPESET(val1) || IS_TYPESET(val2)) { + if (VAL_TYPE(val1) != VAL_TYPE(val2)) + fail (Error_Unexpected_Type(VAL_TYPE(val1), VAL_TYPE(val2))); + + Move_Value(D_OUT, val1); + VAL_TYPESET_BITS(D_OUT) |= VAL_TYPESET_BITS(val2); + return R_OUT; + } + + Init_Any_Series( + D_OUT, + VAL_TYPE(val1), + Make_Set_Operation_Series( + val1, + val2, + SOP_FLAG_BOTH, + REF(case), + REF(skip) ? Int32s(ARG(size), 1) : 1 + ) + ); + + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(unique) -/* -***********************************************************************/ +// +// unique: native [ +// +// "Returns the data set with duplicates removed." +// +// series [any-array! any-string! binary! bitset! typeset!] +// /case +// "Use case-sensitive comparison (except bitsets)" +// /skip +// "Treat the series as records of fixed size" +// size [integer!] +// ] +// +REBNATIVE(unique) { - return Do_Set_Operation(ds, SET_OP_UNIQUE); + INCLUDE_PARAMS_OF_UNIQUE; + + REBVAL *val = ARG(series); + + if (IS_BITSET(val) || IS_TYPESET(val)) { + // + // Bitsets and typesets already unique (by definition) + // + Move_Value(D_OUT, ARG(series)); + return R_OUT; + } + + Init_Any_Series( + D_OUT, + VAL_TYPE(val), + Make_Set_Operation_Series( + val, + NULL, + SOP_NONE, + REF(case), + REF(skip) ? Int32s(ARG(size), 1) : 1 + ) + ); + + return R_OUT; } - - diff --git a/src/core/n-strings.c b/src/core/n-strings.c index f0aeb834ad..2c31a448e4 100644 --- a/src/core/n-strings.c +++ b/src/core/n-strings.c @@ -1,746 +1,1125 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: n-strings.c -** Summary: native functions for strings -** Section: natives -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %n-strings.c +// Summary: "native functions for strings" +// Section: natives +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" #include "sys-deci-funcs.h" +#include "sys-zlib.h" /*********************************************************************** ** -** Hash Function Externs +** Hash Function Externs ** ***********************************************************************/ -#ifndef SHA_DEFINED -#ifdef HAS_SHA1 -REBYTE *SHA1(REBYTE *, REBCNT, REBYTE *); -void SHA1_Init(void *c); -void SHA1_Update(void *c, REBYTE *data, REBCNT len); -void SHA1_Final(REBYTE *md, void *c); -int SHA1_CtxSize(void); -#endif -#endif +#if !defined(SHA_DEFINED) && defined(HAS_SHA1) + // make-headers.r outputs a prototype already, because it is used by cloak + // (triggers warning -Wredundant-decls) + // REBYTE *SHA1(REBYTE *, REBCNT, REBYTE *); -#ifndef MD5_DEFINED -#ifdef HAS_MD5 -REBYTE *MD5(REBYTE *, REBCNT, REBYTE *); -void MD5_Init(void *c); -void MD5_Update(void *c, REBYTE *data, REBCNT len); -void MD5_Final(REBYTE *md, void *c); -int MD5_CtxSize(void); + EXTERN_C void SHA1_Init(void *c); + EXTERN_C void SHA1_Update(void *c, REBYTE *data, REBCNT len); + EXTERN_C void SHA1_Final(REBYTE *md, void *c); + EXTERN_C int SHA1_CtxSize(void); #endif + +#if !defined(MD5_DEFINED) && defined(HAS_MD5) + EXTERN_C void MD5_Init(void *c); + EXTERN_C void MD5_Update(void *c, REBYTE *data, REBCNT len); + EXTERN_C void MD5_Final(REBYTE *md, void *c); + EXTERN_C int MD5_CtxSize(void); #endif #ifdef HAS_MD4 -REBYTE *MD4(REBYTE *, REBCNT, REBYTE *); -void MD4_Init(void *c); -void MD4_Update(void *c, REBYTE *data, REBCNT len); -void MD4_Final(REBYTE *md, void *c); -int MD4_CtxSize(void); + REBYTE *MD4(REBYTE *, REBCNT, REBYTE *); + + EXTERN_C void MD4_Init(void *c); + EXTERN_C void MD4_Update(void *c, REBYTE *data, REBCNT len); + EXTERN_ void MD4_Final(REBYTE *md, void *c); + EXTERN_C int MD4_CtxSize(void); #endif + // Table of has functions and parameters: -static struct digest { - REBYTE *(*digest)(REBYTE *, REBCNT, REBYTE *); - void (*init)(void *); - void (*update)(void *, REBYTE *, REBCNT); - void (*final)(REBYTE *, void *); - int (*ctxsize)(void); - REBINT index; - REBINT len; - REBINT hmacblock; +static struct { + REBYTE *(*digest)(REBYTE *, REBCNT, REBYTE *); + void (*init)(void *); + void (*update)(void *, REBYTE *, REBCNT); + void (*final)(REBYTE *, void *); + int (*ctxsize)(void); + REBSYM sym; + REBINT len; + REBINT hmacblock; } digests[] = { #ifdef HAS_SHA1 - {SHA1, SHA1_Init, SHA1_Update, SHA1_Final, SHA1_CtxSize, SYM_SHA1, 20, 64}, + {SHA1, SHA1_Init, SHA1_Update, SHA1_Final, SHA1_CtxSize, SYM_SHA1, 20, 64}, #endif #ifdef HAS_MD4 - {MD4, MD4_Init, MD4_Update, MD4_Final, MD4_CtxSize, SYM_MD4, 16, 64}, + {MD4, MD4_Init, MD4_Update, MD4_Final, MD4_CtxSize, SYM_MD4, 16, 64}, #endif #ifdef HAS_MD5 - {MD5, MD5_Init, MD5_Update, MD5_Final, MD5_CtxSize, SYM_MD5, 16, 64}, + {MD5, MD5_Init, MD5_Update, MD5_Final, MD5_CtxSize, SYM_MD5, 16, 64}, #endif - {0} + {NULL, NULL, NULL, NULL, NULL, SYM_0, 0, 0} }; -/*********************************************************************** -** -*/ REBNATIVE(ajoin) -/* -***********************************************************************/ +// +// delimit: native [ +// +// {Joins a block of values into a new string with delimiters.} +// +// return: [string!] +// block [block!] +// delimiter [blank! char! string!] +// ] +// +REBNATIVE(delimit) { - REBSER *str; - - str = Form_Reduce(VAL_SERIES(D_ARG(1)), VAL_INDEX(D_ARG(1))); - if (!str) return R_TOS; + INCLUDE_PARAMS_OF_DELIMIT; + + REBVAL *block = ARG(block); + REBVAL *delimiter = ARG(delimiter); + + if (Form_Reduce_Throws( + D_OUT, + VAL_ARRAY(block), + VAL_INDEX(block), + VAL_SPECIFIER(block), + delimiter + )) { + return R_OUT_IS_THROWN; + } + + return R_OUT; +} - Set_String(DS_RETURN, str); // not D_RET (stack modified) - return R_RET; +// +// spelling-of: native [ +// +// {Gives the delimiter-less spelling of words or strings} +// +// value [any-word! any-string!] +// ] +// +REBNATIVE(spelling_of) +{ + INCLUDE_PARAMS_OF_SPELLING_OF; + + REBVAL *value = ARG(value); + + REBSER *series; + + if (ANY_BINSTR(value)) { + assert(!IS_BINARY(value)); // Shouldn't accept binary types... + + // Grab the data out of all string types, which has no delimiters + // included (they are added in the forming process) + // + series = Copy_String_Slimming(VAL_SERIES(value), VAL_INDEX(value), -1); + } + else { + // turn all words into regular words so they'll have no delimiters + // during the FORMing process. Use SET_TYPE and not reset header + // because the binding bits need to stay consistent + // + VAL_SET_TYPE_BITS(value, REB_WORD); + series = Copy_Mold_Value(value, 0 /* opts... MOPT_0? */); + } + + Init_String(D_OUT, series); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(as_binary) -/* -***********************************************************************/ +// +// checksum: native [ +// +// "Computes a checksum, CRC, or hash." +// +// data [binary!] +// "Bytes to checksum" +// /part +// limit +// "Length of data" +// /tcp +// "Returns an Internet TCP 16-bit checksum" +// /secure +// "Returns a cryptographically secure checksum" +// /hash +// "Returns a hash value" +// size [integer!] +// "Size of the hash table" +// /method +// "Method to use" +// word [word!] +// "Methods: SHA1 MD5 CRC32" +// /key +// "Returns keyed HMAC value" +// key-value [any-string!] +// "Key to use" +// ] +// +REBNATIVE(checksum) { - Trap0(RE_DEPRECATED); -// *D_RET = *D_ARG(1); -// VAL_SET(D_RET, REB_BINARY); - return R_RET; + INCLUDE_PARAMS_OF_CHECKSUM; + + REBVAL *arg = ARG(data); + REBYTE *data = VAL_RAW_DATA_AT(arg); + REBCNT wide = SER_WIDE(VAL_SERIES(arg)); + REBCNT len = 0; + + UNUSED(REF(part)); // checked by if limit is void + Partial1(arg, ARG(limit), &len); + + REBSYM sym; + if (REF(method)) { + sym = VAL_WORD_SYM(ARG(word)); + if (sym == SYM_0) // not in %words.r, no SYM_XXX constant + fail (ARG(word)); + } + else + sym = SYM_SHA1; + + // If method, secure, or key... find matching digest: + if (REF(method) || REF(secure) || REF(key)) { + if (sym == SYM_CRC32) { + if (REF(secure) || REF(key)) + fail (Error_Bad_Refines_Raw()); + + // The CRC32() routine returns an unsigned 32-bit number and uses + // the full range of values. Yet Rebol chose to export this as + // a signed integer via checksum. Perhaps (?) to generate a value + // that could also be used by Rebol2, as it only had 32-bit + // signed INTEGER! available. + // + REBINT crc32 = cast(REBINT, CRC32(data, len)); + Init_Integer(D_OUT, crc32); + return R_OUT; + } + + if (sym == SYM_ADLER32) { + if (REF(secure) || REF(key)) + fail (Error_Bad_Refines_Raw()); + + // adler32() is a Saphirion addition since 64-bit INTEGER! was + // available in Rebol3, and did not convert the unsigned result + // of the adler calculation to a signed integer. + // + uLong adler = z_adler32(0L, data, len); + Init_Integer(D_OUT, adler); + return R_OUT; + } + + REBCNT i; + for (i = 0; i < sizeof(digests) / sizeof(digests[0]); i++) { + if (!SAME_SYM_NONZERO(digests[i].sym, sym)) + continue; + + REBSER *digest = Make_Series(digests[i].len + 1, sizeof(char)); + + if (NOT(REF(key))) + digests[i].digest(data, len, BIN_HEAD(digest)); + else { + REBVAL *key = ARG(key_value); + + int blocklen = digests[i].hmacblock; + + REBYTE tmpdigest[20]; // size must be max of all digest[].len + REBYTE *keycp = VAL_BIN_AT(key); + int keylen = VAL_LEN_AT(key); + if (keylen > blocklen) { + digests[i].digest(keycp,keylen,tmpdigest); + keycp = tmpdigest; + keylen = digests[i].len; + } + + REBYTE ipad[64]; // size must be max of all digest[].hmacblock + memset(ipad, 0, blocklen); + memcpy(ipad, keycp, keylen); + + REBYTE opad[64]; // size must be max of all digest[].hmacblock + memset(opad, 0, blocklen); + memcpy(opad, keycp, keylen); + + REBINT j; + for (j = 0; j < blocklen; j++) { + ipad[j] ^= 0x36; // !!! why do people write this kind of + opad[j] ^= 0x5c; // thing without a comment? !!! :-( + } + + char *ctx = ALLOC_N(char, digests[i].ctxsize()); + digests[i].init(ctx); + digests[i].update(ctx,ipad,blocklen); + digests[i].update(ctx, data, len); + digests[i].final(tmpdigest,ctx); + digests[i].init(ctx); + digests[i].update(ctx,opad,blocklen); + digests[i].update(ctx,tmpdigest,digests[i].len); + digests[i].final(BIN_HEAD(digest),ctx); + + FREE_N(char, digests[i].ctxsize(), ctx); + } + + TERM_BIN_LEN(digest, digests[i].len); + Init_Binary(D_OUT, digest); + + return R_OUT; + } + + fail (ARG(word)); + } + else if (REF(tcp)) { + REBINT ipc = Compute_IPC(data, len); + Init_Integer(D_OUT, ipc); + } + else if (REF(hash)) { + REBINT sum = VAL_INT32(ARG(size)); + if (sum <= 1) + sum = 1; + + REBINT hash = Hash_String(data, len, wide) % sum; + Init_Integer(D_OUT, hash); + } + else { + REBINT crc = Compute_CRC(data, len); + Init_Integer(D_OUT, crc); + } + + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(as_string) -/* -***********************************************************************/ +// +// compress: native [ +// +// "Compresses a string series and returns it." +// +// return: [binary!] +// data [binary! string!] +// "If string, it will be UTF8 encoded" +// /part +// limit +// "Length of data (elements)" +// /gzip +// "Use GZIP checksum" +// /only +// {Do not store header or envelope information ("raw")} +// ] +// +REBNATIVE(compress) { - Trap0(RE_DEPRECATED); -// *D_RET = *D_ARG(1); -// VAL_SET(D_RET, REB_STRING); - return R_RET; + INCLUDE_PARAMS_OF_COMPRESS; + + REBCNT len; + UNUSED(PAR(part)); // checked by if limit is void + Partial1(ARG(data), ARG(limit), &len); + + REBCNT index; + REBSER *ser = Temp_Bin_Str_Managed(ARG(data), &index, &len); + + Init_Binary(D_OUT, Compress(ser, index, len, REF(gzip), REF(only))); + + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(checksum) -/* -** Computes checksum or hash value. -** -** Note: Currently BINARY only. -** -** Args: -** -** data [any-string!] {Data to checksum} -** /part length -** /tcp {Returns an Internet TCP 16-bit checksum.} -** /secure {Returns a cryptographically secure checksum.} -** /hash {Returns a hash value} -** size [integer!] {Size of the hash table} -** /method {Method to use} -** word [word!] {Method: SHA1 MD5} -** /key {Returns keyed HMAC value} -** key-value [any-string!] {Key to use} -** -***********************************************************************/ +// +// decompress: native [ +// +// "Decompresses data." +// +// return: [binary!] +// data [binary!] +// "Data to decompress" +// /part +// lim ;-- /limit was a legacy name for a refinement +// "Length of compressed data (must match end marker)" +// /gzip +// "Use GZIP checksum" +// /limit +// max +// "Error out if result is larger than this" +// /only +// {Do not look for header or envelope information ("raw")} +// ] +// +REBNATIVE(decompress) { - REBVAL *arg = D_ARG(ARG_CHECKSUM_DATA); - REBINT sum; - REBINT i; - REBINT j; - REBSER *digest; - REBINT sym = SYM_SHA1; - REBCNT len; - REBYTE *data = VAL_BIN_DATA(arg); - - len = Partial1(arg, D_ARG(ARG_CHECKSUM_LENGTH)); - - // Method word: - if (D_REF(ARG_CHECKSUM_METHOD)) sym = VAL_WORD_CANON(D_ARG(ARG_CHECKSUM_WORD)); - - // If method, secure, or key... find matching digest: - if (D_REF(ARG_CHECKSUM_METHOD) || D_REF(ARG_CHECKSUM_SECURE) || D_REF(ARG_CHECKSUM_KEY)) { - - if (sym == SYM_CRC32) { - if (D_REF(ARG_CHECKSUM_SECURE) || D_REF(ARG_CHECKSUM_KEY)) Trap0(RE_BAD_REFINES); - i = CRC32(data, len); - DS_RET_INT(i); - return R_RET; - } - - for (i = 0; i < sizeof(digests) / sizeof(digests[0]); i++) { - - if (digests[i].index == sym) { - - digest = Make_Series(digests[i].len, 1, FALSE); - LABEL_SERIES(digest, "checksum digest"); - - if (D_REF(ARG_CHECKSUM_KEY)) { - REBYTE tmpdigest[20]; // Size must be max of all digest[].len; - REBYTE ipad[64],opad[64]; // Size must be max of all digest[].hmacblock; - void *ctx = Make_Mem(digests[i].ctxsize()); - REBVAL *key = D_ARG(ARG_CHECKSUM_KEY_VALUE); - REBYTE *keycp = VAL_BIN_DATA(key); - int keylen = VAL_LEN(key); - int blocklen = digests[i].hmacblock; - - if (keylen > blocklen) { - digests[i].digest(keycp,keylen,tmpdigest); - keycp = tmpdigest; - keylen = digests[i].len; - } - - memset(ipad, 0, blocklen); - memset(opad, 0, blocklen); - memcpy(ipad, keycp, keylen); - memcpy(opad, keycp, keylen); - - for (j = 0; j < blocklen; j++) { - ipad[j]^=0x36; - opad[j]^=0x5c; - } - - digests[i].init(ctx); - digests[i].update(ctx,ipad,blocklen); - digests[i].update(ctx, data, len); - digests[i].final(tmpdigest,ctx); - digests[i].init(ctx); - digests[i].update(ctx,opad,blocklen); - digests[i].update(ctx,tmpdigest,digests[i].len); - digests[i].final(BIN_HEAD(digest),ctx); - - Free_Mem(ctx, digests[i].ctxsize()); - - } else { - digests[i].digest(data, len, BIN_HEAD(digest)); - } - - SERIES_TAIL(digest) = digests[i].len; - Set_Series(REB_BINARY, DS_RETURN, digest); - - return 0; - } - } - - Trap_Arg(D_ARG(ARG_CHECKSUM_WORD)); - } - else if (D_REF(ARG_CHECKSUM_TCP)) { // /tcp - i = Compute_IPC(data, len); - } - else if (D_REF(ARG_CHECKSUM_HASH)) { // /hash - sum = VAL_INT32(D_ARG(ARG_CHECKSUM_SIZE)); // /size - if (sum <= 1) sum = 1; - i = Hash_String(data, len) % sum; - } - else { - i = Compute_CRC(data, len); - } - - DS_RET_INT(i); - - return R_RET; + INCLUDE_PARAMS_OF_DECOMPRESS; + + REBVAL *data = ARG(data); + + REBINT max; + if (REF(limit)) { + max = Int32s(ARG(max), 1); + if (max < 0) + return R_BLANK; // !!! Should negative limit be an error instead? + } + else + max = -1; + + REBCNT len; + UNUSED(REF(part)); // implied by non-void lim + Partial1(data, ARG(lim), &len); + + // This truncation rule used to be in Decompress, which passed len + // in as an extra parameter. This was the only call that used it. + // + if (len > BIN_LEN(VAL_SERIES(data))) + len = BIN_LEN(VAL_SERIES(data)); + + + Init_Binary(D_OUT, Decompress( + BIN_HEAD(VAL_SERIES(data)) + VAL_INDEX(data), + len, + max, + REF(gzip), + REF(only) + )); + + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(compress) -/* -** Binary and string (gets UTF8 converted). -** -***********************************************************************/ +// +// debase: native [ +// +// {Decodes binary-coded string (BASE-64 default) to binary value.} +// +// return: [binary!] +// ;-- Comment said "we don't know the encoding" of the return binary +// value [binary! string!] +// "The string to decode" +// /base +// "Binary base to use" +// base-value [integer!] +// "The base to convert from: 64, 16, or 2" +// ] +// +REBNATIVE(debase) { - REBSER *ser; - REBCNT index; - REBINT len; + INCLUDE_PARAMS_OF_DEBASE; - len = Partial1(D_ARG(1), D_ARG(3)); + REBCNT index; + REBCNT len = 0; + REBSER *ser = Temp_Bin_Str_Managed(ARG(value), &index, &len); - ser = Prep_Bin_Str(D_ARG(1), &index, &len); // result may be a SHARED BUFFER! + REBINT base = 64; + if (REF(base)) + base = VAL_INT32(ARG(base_value)); + else + base = 64; - Set_Binary(D_RET, Compress(ser, index, len, D_REF(4))); // /gzip + if (!Decode_Binary(D_OUT, BIN_AT(ser, index), len, base, 0)) + fail (Error_Invalid_Data_Raw(ARG(value))); - return R_RET; + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(decompress) -/* -** Binary only. -** -***********************************************************************/ +// +// enbase: native [ +// +// {Encodes data into a binary, hexadecimal, or base-64 ASCII string.} +// +// return: [string!] +// value [binary! string!] +// "If string, will be UTF8 encoded" +// /base +// "Binary base to use (BASE-64 default)" +// base-value [integer!] +// "The base to convert to: 64, 16, or 2" +// ] +// +REBNATIVE(enbase) { - REBVAL *arg = D_ARG(1); - REBINT limit = 0; - REBINT len; + INCLUDE_PARAMS_OF_ENBASE; + + REBINT base; + if (REF(base)) + base = VAL_INT32(ARG(base_value)); + else + base = 64; - len = Partial1(D_ARG(1), D_ARG(3)); + REBVAL *arg = ARG(value); - if (D_REF(5)) limit = Int32s(D_ARG(6), 1); // /limit size - - Set_Binary(D_RET, Decompress(VAL_SERIES(arg), VAL_INDEX(arg), len, limit, D_REF(4))); // /gzip + // Will convert STRING!s to UTF-8 if necessary. + // + REBCNT index; + REBSER *temp = Temp_Bin_Str_Managed(arg, &index, NULL); + Init_Any_Series_At(arg, REB_BINARY, temp, index); - return R_RET; + REBSER *ser; + switch (base) { + case 64: + ser = Encode_Base64(arg, 0, FALSE); + break; + + case 16: + ser = Encode_Base16(arg, 0, FALSE); + break; + + case 2: + ser = Encode_Base2(arg, 0, FALSE); + break; + + default: + fail (ARG(base_value)); + } + + Init_String(D_OUT, ser); + + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(construct) -/* -***********************************************************************/ +// +// dehex: native [ +// +// "Converts URL-style hex encoded (%xx) strings." +// +// value [any-string!] "The string to dehex" +// ] +// +REBNATIVE(dehex) { - REBVAL *value = D_ARG(1); - REBSER *parent = 0; - REBSER *frame; + INCLUDE_PARAMS_OF_DEHEX; + + REBCNT len = VAL_LEN_AT(ARG(value)); + REBUNI uni; + REBSER *ser; + + if (VAL_BYTE_SIZE(ARG(value))) { + REBYTE *bp = VAL_BIN_AT(ARG(value)); + REBYTE *dp = Reset_Buffer(BYTE_BUF, len); + + for (; len > 0; len--) { + if (*bp == '%' && len > 2 && Scan_Hex2(bp + 1, &uni, FALSE)) { + *dp++ = cast(REBYTE, uni); + bp += 3; + len -= 2; + } + else *dp++ = *bp++; + } + + *dp = '\0'; + ser = Copy_String_Slimming(BYTE_BUF, 0, dp - BIN_HEAD(BYTE_BUF)); + } + else { + REBUNI *up = VAL_UNI_AT(ARG(value)); + REBUNI *dp; + REB_MOLD mo; + CLEARS(&mo); + + Push_Mold(&mo); + + // Do a conservative expansion, assuming there are no %NNs in the + // series and the output string will be the same length as input + // + Expand_Series(mo.series, mo.start, len); + + dp = UNI_AT(mo.series, mo.start); // Expand_Series may change pointer + + for (; len > 0; len--) { + if ( + *up == '%' + && len > 2 + && Scan_Hex2(cast(REBYTE*, up + 1), dp, TRUE) + ) { + dp++; + up += 3; + len -= 2; + } + else *dp++ = *up++; + } + + *dp = '\0'; + + // The delta in dp from the original pointer position tells us the + // actual size after the %NNs have been accounted for. + // + ser = Pop_Molded_String_Len( + &mo, cast(REBCNT, dp - UNI_AT(mo.series, mo.start)) + ); + } + + Init_Any_Series(D_OUT, VAL_TYPE(ARG(value)), ser); + + return R_OUT; +} - if (IS_STRING(value) || IS_BINARY(value)) { - REBCNT index; - // Just a guess at size: - frame = Make_Block(10); // Use a std BUF_? - Set_Block(D_RET, frame); // Keep safe +// +// deline: native [ +// +// {Converts string terminators to standard format, e.g. CRLF to LF.} +// +// string [any-string!] +// "Will be modified (unless /LINES used)" +// /lines +// {Return block of lines (works for LF, CR, CR-LF endings)} +// ] +// +REBNATIVE(deline) +{ + INCLUDE_PARAMS_OF_DELINE; + + REBVAL *val = ARG(string); - // Convert string if necessary. Store back for safety. - VAL_SERIES(value) = Prep_Bin_Str(value, &index, 0); + if (REF(lines)) { + Init_Block(D_OUT, Split_Lines(val)); + return R_OUT; + } - // !issue! Is this what we really want here? - Scan_Net_Header(frame, VAL_BIN(value) + index); - value = D_RET; - } + REBINT len = VAL_LEN_AT(val); - if (D_REF(2)) parent = VAL_OBJ_FRAME(D_ARG(3)); + REBINT n; + if (VAL_BYTE_SIZE(val)) { + REBYTE *bp = VAL_BIN_AT(val); + n = Deline_Bytes(bp, len); + } else { + REBUNI *up = VAL_UNI_AT(val); + n = Deline_Uni(up, len); + } - frame = Construct_Object(parent, VAL_BLK_DATA(value), D_REF(4)); - SET_OBJECT(D_RET, frame); + SET_SERIES_LEN(VAL_SERIES(val), VAL_LEN_HEAD(val) - (len - n)); - return R_RET; + Move_Value(D_OUT, ARG(string)); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(debase) -/* -** Converts a binary base representation string to binary. -** Input is a STRING, but BINARY is also accepted. -** BINARY is returned. We don't know the encoding. -** -***********************************************************************/ +// +// enline: native [ +// +// {Converts string terminators to native OS format, e.g. LF to CRLF.} +// +// series [any-string! block!] "(modified)" +// ] +// +REBNATIVE(enline) { - REBINT base = 64; - REBSER *ser; - REBCNT index; - REBCNT len = 0; - - ser = Prep_Bin_Str(D_ARG(1), &index, &len); // result may be a SHARED BUFFER! + INCLUDE_PARAMS_OF_ENLINE; - if (D_REF(2)) base = VAL_INT32(D_ARG(3)); // /base + REBVAL *val = ARG(series); + REBSER *ser = VAL_SERIES(val); - if (!Decode_Binary(D_RET, BIN_SKIP(ser, index), len, base, 0)) - Trap1(RE_INVALID_DATA, D_ARG(1)); + if (SER_LEN(ser)) { + if (VAL_BYTE_SIZE(val)) + Enline_Bytes(ser, VAL_INDEX(val), VAL_LEN_AT(val)); + else + Enline_Uni(ser, VAL_INDEX(val), VAL_LEN_AT(val)); + } - return R_RET; + Move_Value(D_OUT, ARG(series)); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(enbase) -/* -** Converts a binary to a binary base representation STRING. -** Input is BINARY or STRING (UTF8 encoded). -** -***********************************************************************/ +// +// entab: native [ +// +// "Converts spaces to tabs (default tab size is 4)." +// +// string [any-string!] +// "(modified)" +// /size +// "Specifies the number of spaces per tab" +// number [integer!] +// ] +// +REBNATIVE(entab) { - REBINT base = 64; - REBSER *ser; - REBCNT index; - REBVAL *arg = D_ARG(1); + INCLUDE_PARAMS_OF_ENTAB; + + REBVAL *val = ARG(string); - Set_Binary(arg, Prep_Bin_Str(arg, &index, 0)); // may be SHARED buffer - VAL_INDEX(arg) = index; + REBCNT len = VAL_LEN_AT(val); - if (D_REF(2)) base = VAL_INT32(D_ARG(3)); + REBINT tabsize; + if (REF(size)) + tabsize = Int32s(ARG(number), 1); + else + tabsize = TAB_SIZE; - switch (base) { - case 64: - ser = Encode_Base64(arg, 0, FALSE); - break; - case 16: - ser = Encode_Base16(arg, 0, FALSE); - break; - case 2: - ser = Encode_Base2(arg, 0, FALSE); - break; - default: - Trap_Arg(D_ARG(3)); - } + REBSER *ser; + if (VAL_BYTE_SIZE(val)) + ser = Entab_Bytes(VAL_BIN(val), VAL_INDEX(val), len, tabsize); + else + ser = Entab_Unicode(VAL_UNI(val), VAL_INDEX(val), len, tabsize); - Set_String(D_RET, ser); + Init_Any_Series(D_OUT, VAL_TYPE(val), ser); - return R_RET; + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(decloak) -/* -** Input is BINARY only. Modifies input. -** -***********************************************************************/ +// +// detab: native [ +// +// "Converts tabs to spaces (default tab size is 4)." +// +// string [any-string!] +// "(modified)" +// /size +// "Specifies the number of spaces per tab" +// number [integer!] +// ] +// +REBNATIVE(detab) { - REBVAL *data = D_ARG(1); - REBVAL *key = D_ARG(2); + INCLUDE_PARAMS_OF_DETAB; - if (!Cloak(TRUE, VAL_BIN_DATA(data), VAL_LEN(data), (REBYTE*)key, 0, D_REF(3))) - Trap_Arg(key); + REBVAL *val = ARG(string); - return R_ARG1; -} + REBCNT len = VAL_LEN_AT(val); + REBINT tabsize; + if (REF(size)) + tabsize = Int32s(ARG(number), 1); + else + tabsize = TAB_SIZE; -/*********************************************************************** -** -*/ REBNATIVE(encloak) -/* -** Input is BINARY only. Modifies input. -** -***********************************************************************/ -{ - REBVAL *data = D_ARG(1); - REBVAL *key = D_ARG(2); + REBSER *ser; + if (VAL_BYTE_SIZE(val)) + ser = Detab_Bytes(VAL_BIN(val), VAL_INDEX(val), len, tabsize); + else + ser = Detab_Unicode(VAL_UNI(val), VAL_INDEX(val), len, tabsize); - if (!Cloak(FALSE, VAL_BIN_DATA(data), VAL_LEN(data), (REBYTE*)key, 0, D_REF(3))) - Trap_Arg(key); + Init_Any_Series(D_OUT, VAL_TYPE(val), ser); - return R_ARG1; + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(dehex) -/* -** Works for any string. -** -***********************************************************************/ +// +// lowercase: native [ +// +// "Converts string of characters to lowercase." +// +// string [any-string! char!] +// "(modified if series)" +// /part +// "Limits to a given length or position" +// limit [any-number! any-string!] +// ] +// +REBNATIVE(lowercase) { - REBVAL *arg = D_ARG(1); - REBINT len = (REBINT)VAL_LEN(arg); // due to len -= 2 below - REBUNI n; - REBSER *ser; - - if (VAL_BYTE_SIZE(arg)) { - REBYTE *bp = VAL_BIN_DATA(arg); - REBYTE *dp = Reset_Buffer(BUF_FORM, len); + INCLUDE_PARAMS_OF_LOWERCASE; - for (; len > 0; len--) { - if (*bp == '%' && len > 2 && Scan_Hex2(bp+1, &n, FALSE)) { - *dp++ = (REBYTE)n; - bp += 3; - len -= 2; - } - else *dp++ = *bp++; - } + UNUSED(REF(part)); // checked by if limit is void + Change_Case(D_OUT, ARG(string), ARG(limit), FALSE); + return R_OUT; +} - *dp = 0; - ser = Copy_String(BUF_FORM, 0, dp - BIN_HEAD(BUF_FORM)); - } - else { - REBUNI *up = VAL_UNI_DATA(arg); - REBUNI *dp = (REBUNI*)Reset_Buffer(BUF_MOLD, len); - for (; len > 0; len--) { - if (*up == '%' && len > 2 && Scan_Hex2((REBYTE*)(up+1), &n, TRUE)) { - *dp++ = (REBUNI)n; - up += 3; - len -= 2; - } - else *dp++ = *up++; - } +// +// uppercase: native [ +// +// "Converts string of characters to uppercase." +// +// string [any-string! char!] +// "(modified if series)" +// /part +// "Limits to a given length or position" +// limit [any-number! any-string!] +// ] +// +REBNATIVE(uppercase) +{ + INCLUDE_PARAMS_OF_UPPERCASE; - *dp = 0; - ser = Copy_String(BUF_MOLD, 0, dp - UNI_HEAD(BUF_MOLD)); - } + UNUSED(REF(part)); // checked by if limit is void + Change_Case(D_OUT, ARG(string), ARG(limit), TRUE); + return R_OUT; +} - Set_Series(VAL_TYPE(arg), D_RET, ser); - return R_RET; +// +// to-hex: native [ +// +// {Converts numeric value to a hex issue! datatype (with leading # and 0's).} +// +// value [integer! tuple!] +// "Value to be converted" +// /size +// "Specify number of hex digits in result" +// len [integer!] +// ] +// +REBNATIVE(to_hex) +{ + INCLUDE_PARAMS_OF_TO_HEX; + + REBVAL *arg = ARG(value); + + REBYTE buffer[(MAX_TUPLE * 2) + 4]; // largest value possible + + REBYTE *buf = &buffer[0]; + + REBINT len; + if (REF(size)) { + len = cast(REBINT, VAL_INT64(ARG(len))); + if (len < 0) + fail (ARG(len)); + } + else + len = -1; + + if (IS_INTEGER(arg)) { + if (len < 0 || len > MAX_HEX_LEN) + len = MAX_HEX_LEN; + + Form_Hex_Pad(buf, VAL_INT64(arg), len); + } + else if (IS_TUPLE(arg)) { + REBINT n; + if ( + len < 0 + || len > 2 * cast(REBINT, MAX_TUPLE) + || len > 2 * VAL_TUPLE_LEN(arg) + ){ + len = 2 * VAL_TUPLE_LEN(arg); + } + for (n = 0; n < VAL_TUPLE_LEN(arg); n++) + buf = Form_Hex2(buf, VAL_TUPLE(arg)[n]); + for (; n < 3; n++) + buf = Form_Hex2(buf, 0); + *buf = 0; + } + else + fail (arg); + + if (NULL == Scan_Issue(D_OUT, &buffer[0], len)) + fail (arg); + + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(deline) -/* -** Convert CR or CRLF strings to just LF strings. -** -***********************************************************************/ +// +// find-script: native [ +// +// {Find a script header within a binary string. Returns starting position.} +// +// script [binary!] +// ] +// +REBNATIVE(find_script) { - REBVAL *val = D_ARG(1); - REBINT len = VAL_LEN(val); - REBINT n; + INCLUDE_PARAMS_OF_FIND_SCRIPT; + + REBVAL *arg = ARG(script); + + REBINT n = What_UTF(VAL_BIN_AT(arg), VAL_LEN_AT(arg)); + + if (n != 0 && n != 8) + return R_BLANK; // UTF8 only - if (D_REF(2)) { //lines - Set_Block(D_RET, Split_Lines(val)); - return R_RET; - } + if (n == 8) + VAL_INDEX(arg) += 3; // BOM8 length - if (VAL_BYTE_SIZE(val)) { - REBYTE *bp = VAL_BIN_DATA(val); - n = Deline_Bytes(bp, len); - } else { - REBUNI *up = VAL_UNI_DATA(val); - n = Deline_Uni(up, len); - } + REBINT offset = Scan_Header(VAL_BIN_AT(arg), VAL_LEN_AT(arg)); + if (offset == -1) + return R_BLANK; - VAL_TAIL(val) -= (len - n); + VAL_INDEX(arg) += offset; - return R_ARG1; + Move_Value(D_OUT, ARG(script)); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(enline) -/* -** Convert LF to CRLF or native format. -** -***********************************************************************/ +// +// utf?: native [ +// +// {Returns UTF BOM (byte order marker) encoding; + for BE, - for LE.} +// +// data [binary!] +// ] +// +REBNATIVE(utf_q) { - REBVAL *val = D_ARG(1); - REBSER *ser = VAL_SERIES(val); - - if (SERIES_TAIL(ser)) { - if (VAL_BYTE_SIZE(val)) - Enline_Bytes(ser, VAL_INDEX(val), VAL_LEN(val)); - else - Enline_Uni(ser, VAL_INDEX(val), VAL_LEN(val)); - } + INCLUDE_PARAMS_OF_UTF_Q; - return R_ARG1; + REBINT utf = What_UTF(VAL_BIN_AT(ARG(data)), VAL_LEN_AT(ARG(data))); + Init_Integer(D_OUT, utf); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(entab) -/* -** Modifies input. -** -***********************************************************************/ +// +// invalid-utf8?: native [ +// +// {Checks UTF-8 encoding; if correct, returns blank else position of error.} +// +// data [binary!] +// ] +// +REBNATIVE(invalid_utf8_q) { - REBVAL *val = D_ARG(1); - REBINT tabsize = TAB_SIZE; - REBSER *ser; - REBCNT len = VAL_LEN(val); + INCLUDE_PARAMS_OF_INVALID_UTF8_Q; + + REBVAL *arg = ARG(data); - if (D_REF(2)) tabsize = Int32s(D_ARG(3), 1); + REBYTE *bp = Check_UTF8(VAL_BIN_AT(arg), VAL_LEN_AT(arg)); + if (bp == 0) + return R_BLANK; - // Set up the copy buffer: - if (VAL_BYTE_SIZE(val)) - ser = Entab_Bytes(VAL_BIN(val), VAL_INDEX(val), len, tabsize); - else - ser = Entab_Unicode(VAL_UNI(val), VAL_INDEX(val), len, tabsize); + VAL_INDEX(arg) = bp - VAL_BIN_HEAD(arg); - Set_Series(VAL_TYPE(val), D_RET, ser); - - return R_RET; + Move_Value(D_OUT, arg); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(detab) -/* -***********************************************************************/ +#ifndef NDEBUG +// +// b_cast_: C +// +// Debug-only version of b_cast() that does type checking. +// If you get a complaint you probably meant to use cb_cast(). +// +REBYTE *b_cast_(char *s) { - REBVAL *val = D_ARG(1); - REBINT tabsize = TAB_SIZE; - REBSER *ser; - REBCNT len = VAL_LEN(val); - - if (D_REF(2)) tabsize = Int32s(D_ARG(3), 1); + return cast(REBYTE *, s); +} - // Set up the copy buffer: - if (VAL_BYTE_SIZE(val)) - ser = Detab_Bytes(VAL_BIN(val), VAL_INDEX(val), len, tabsize); - else - ser = Detab_Unicode(VAL_UNI(val), VAL_INDEX(val), len, tabsize); - Set_Series(VAL_TYPE(val), D_RET, ser); - - return R_RET; +// +// cb_cast_: C +// +// Debug-only version of cb_cast() that does type checking. +// If you get a complaint you probably meant to use b_cast(). +// +const REBYTE *cb_cast_(const char *s) +{ + return cast(const REBYTE *, s); } -/*********************************************************************** -** -*/ REBNATIVE(lowercase) -/* -***********************************************************************/ +// +// s_cast_: C +// +// Debug-only version of s_cast() that does type checking. +// If you get a complaint you probably meant to use cs_cast(). +// +char *s_cast_(REBYTE *s) { - Change_Case(ds, D_ARG(1), D_ARG(3), FALSE); - return R_RET; + return cast(char*, s); } -/*********************************************************************** -** -*/ REBNATIVE(uppercase) -/* -***********************************************************************/ +// +// cs_cast_: C +// +// Debug-only version of cs_cast() that does type checking. +// If you get a complaint you probably meant to use s_cast(). +// +const char *cs_cast_(const REBYTE *s) { - Change_Case(ds, D_ARG(1), D_ARG(3), TRUE); - return R_RET; + return cast(const char *, s); } -/*********************************************************************** -** -*/ REBNATIVE(to_hex) -/* -***********************************************************************/ +// +// COPY_BYTES_: C +// +// Debug-only REBYTE-checked substitute for COPY_BYTES macro +// If you meant characters, consider if you wanted strncpy() +// +REBYTE *COPY_BYTES_(REBYTE *dest, const REBYTE *src, size_t count) { - REBVAL *arg = D_ARG(1); - REBINT len; -// REBSER *series; - REBYTE buffer[MAX_TUPLE*2+4]; // largest value possible - REBYTE *buf; - -#ifdef removed - if (IS_INTEGER(arg)) len = MAX_HEX_LEN; - else if (IS_TUPLE(arg)) { - len = VAL_TUPLE_LEN(arg); - if (len < 3) len = 3; - len *= 2; - } - else Trap_Arg(arg); - - else if (IS_DECIMAL(arg)) len = MAX_HEX_LEN; - else if (IS_MONEY(arg)) len = 24; - else if (IS_CHAR(arg)) len = (VAL_CHAR(arg) > 0x7f) ? 4 : 2; -#endif - - buf = &buffer[0]; - - len = -1; - if (D_REF(2)) { // /size - len = (REBINT) VAL_INT64(D_ARG(3)); - if (len < 0) Trap_Arg(D_ARG(3)); - } - if (IS_INTEGER(arg)) { // || IS_DECIMAL(arg)) { - if (len < 0 || len > MAX_HEX_LEN) len = MAX_HEX_LEN; - Form_Hex_Pad(buf, VAL_INT64(arg), len); - } - else if (IS_TUPLE(arg)) { - REBINT n; - if (len < 0 || len > 2 * MAX_TUPLE || len > 2 * VAL_TUPLE_LEN(arg)) - len = 2 * VAL_TUPLE_LEN(arg); - for (n = 0; n < VAL_TUPLE_LEN(arg); n++) - buf = Form_Hex2(buf, VAL_TUPLE(arg)[n]); - for (; n < 3; n++) - buf = Form_Hex2(buf, 0); - *buf = 0; - } - else Trap_Arg(arg); - -#ifdef removed - else if (IS_CHAR(arg)) { - REBSER *ser = Make_Binary(6); - ser->tail = xEncode_UTF8_Char(BIN_HEAD(ser), VAL_CHAR(arg)); - for (len = 0; len < (signed)(ser->tail); len++) - buf = Form_Hex2(buf, *BIN_SKIP(ser, len)); - len = ser->tail * 2; - //Form_Hex_Pad(buf, VAL_CHAR(arg), len); - } - else if (IS_MONEY(arg)) { - REBYTE tmp[12]; - deci_to_binary(&tmp[0], VAL_DECI(arg)); - for (len = 0; len < 12; len++) - buf = Form_Hex2(buf, tmp[len]); - len = 24; - } -#endif + return b_cast(strncpy(s_cast(dest), cs_cast(src), count)); +} -// SERIES_TAIL(series) = len; -// Set_Series(REB_ISSUE, D_RET, series); - Init_Word(D_RET, Scan_Issue(&buffer[0], len)); - VAL_SET(D_RET, REB_ISSUE); - return R_RET; +// +// LEN_BYTES_: C +// +// Debug-only REBYTE-checked substitute for LEN_BYTES macro +// If you meant characters, consider if you wanted strlen() +// +size_t LEN_BYTES_(const REBYTE *str) +{ + return strlen(cs_cast(str)); } -/*********************************************************************** -** -*/ REBNATIVE(find_script) -/* -***********************************************************************/ +// +// COMPARE_BYTES_: C +// +// Debug-only REBYTE-checked function for COMPARE_BYTES macro +// If you meant characters, consider if you wanted strcmp() +// +int COMPARE_BYTES_(const REBYTE *lhs, const REBYTE *rhs) { - REBVAL *arg = D_ARG(1); - REBINT n; + return strcmp(cs_cast(lhs), cs_cast(rhs)); +} - n = What_UTF(VAL_BIN_DATA(arg), VAL_LEN(arg)); - if (n != 0 && n != 8) return R_NONE; // UTF8 only +// +// APPEND_BYTES_LIMIT_: C +// +// REBYTE-checked function for APPEND_BYTES_LIMIT macro in Debug +// If you meant characters, you'll have to use strncat()/strlen() +// (there's no single entry point for this purpose) +// +REBYTE *APPEND_BYTES_LIMIT_(REBYTE *dest, const REBYTE *src, size_t max) +{ + return b_cast(strncat( + s_cast(dest), cs_cast(src), MAX(max - LEN_BYTES(dest) - 1, 0) + )); +} - if (n == 8) VAL_INDEX(arg) += 3; // BOM8 length - n = Scan_Header(VAL_BIN_DATA(arg), VAL_LEN(arg)); // returns offset +// +// OS_STRNCPY_: C +// +// Debug-only REBCHR-checked substitute for OS_STRNCPY macro +// +REBCHR *OS_STRNCPY_(REBCHR *dest, const REBCHR *src, size_t count) +{ +#ifdef OS_WIDE_CHAR + return cast(REBCHR*, + wcsncpy(cast(wchar_t*, dest), cast(const wchar_t*, src), count) + ); +#else + #ifdef TO_OPENBSD + return cast(REBCHR*, + strlcpy(cast(char*, dest), cast(const char*, src), count) + ); + #else + return cast(REBCHR*, + strncpy(cast(char*, dest), cast(const char*, src), count) + ); + #endif +#endif +} + - if (n == -1) return R_NONE; +// +// OS_STRNCAT_: C +// +// Debug-only REBCHR-checked function for OS_STRNCAT macro +// +REBCHR *OS_STRNCAT_(REBCHR *dest, const REBCHR *src, size_t max) +{ +#ifdef OS_WIDE_CHAR + return cast(REBCHR*, + wcsncat(cast(wchar_t*, dest), cast(const wchar_t*, src), max) + ); +#else + #ifdef TO_OPENBSD + return cast(REBCHR*, + strlcat(cast(char*, dest), cast(const char*, src), max) + ); + #else + return cast(REBCHR*, + strncat(cast(char*, dest), cast(const char*, src), max) + ); + #endif +#endif +} - VAL_INDEX(arg) += n; - return R_ARG1; +// +// OS_STRNCMP_: C +// +// Debug-only REBCHR-checked substitute for OS_STRNCMP macro +// +int OS_STRNCMP_(const REBCHR *lhs, const REBCHR *rhs, size_t max) +{ +#ifdef OS_WIDE_CHAR + return wcsncmp(cast(const wchar_t*, lhs), cast(const wchar_t*, rhs), max); +#else + return strncmp(cast(const char*, lhs), cast (const char*, rhs), max); +#endif } -/*********************************************************************** -** -*/ REBNATIVE(utfq) -/* -***********************************************************************/ +// +// OS_STRLEN_: C +// +// Debug-only REBCHR-checked substitute for OS_STRLEN macro +// +size_t OS_STRLEN_(const REBCHR *str) { - REBINT utf = What_UTF(VAL_BIN_DATA(D_ARG(1)), VAL_LEN(D_ARG(1))); - DS_RET_INT(utf); - return R_RET; +#ifdef OS_WIDE_CHAR + return wcslen(cast(const wchar_t*, str)); +#else + return strlen(cast(const char*, str)); +#endif } -/*********************************************************************** -** -*/ REBNATIVE(invalid_utfq) -/* -***********************************************************************/ +// +// OS_STRCHR_: C +// +// Debug-only REBCHR-checked function for OS_STRCHR macro +// +REBCHR *OS_STRCHR_(const REBCHR *str, REBCNT ch) { - REBVAL *arg = D_ARG(1); - REBYTE *bp; + // We have to m_cast because C++ actually has a separate overloads of + // wcschr and strchr which will return a const pointer if the in pointer + // was const. +#ifdef OS_WIDE_CHAR + return cast(REBCHR*, + m_cast(wchar_t*, wcschr(cast(const wchar_t*, str), ch)) + ); +#else + return cast(REBCHR*, + m_cast(char*, strchr(cast(const char*, str), ch)) + ); +#endif +} - bp = Check_UTF8(VAL_BIN_DATA(arg), VAL_LEN(arg)); - if (bp == 0) return R_NONE; - VAL_INDEX(arg) = bp - VAL_BIN_HEAD(arg); - return R_ARG1; +// +// OS_MAKE_CH_: C +// +// Debug-only REBCHR-checked function for OS_MAKE_CH macro +// +REBCHR OS_MAKE_CH_(REBCNT ch) +{ + REBCHR result; + result.num = ch; + return result; } + +#endif diff --git a/src/core/n-system.c b/src/core/n-system.c index 624d5ba7e6..3b11dc7fa4 100644 --- a/src/core/n-system.c +++ b/src/core/n-system.c @@ -1,498 +1,428 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: n-system.c -** Summary: native functions for system operations -** Section: natives -** Author: Carl Sassenrath -** Notes: -** GC WARNING: Do not cache pointer to stack ARGS (stack may expand). -** -***********************************************************************/ +// +// File: %n-system.c +// Summary: "native functions for system operations" +// Section: natives +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/*********************************************************************** -** -*/ REBNATIVE(halt) -/* -***********************************************************************/ +// +// halt: native [ +// +// "Stops evaluation and returns to the input prompt." +// +// ; No arguments +// ] +// +REBNATIVE(halt) { - Halt_Code(RE_HALT, 0); - DEAD_END; + UNUSED(frame_); + fail (VAL_CONTEXT(TASK_HALT_ERROR)); } -/*********************************************************************** -** -*/ REBNATIVE(quit) -/* -** 1: /return -** 2: value -** 3: /now -** -***********************************************************************/ +// +// quit: native [ +// +// {Stop evaluating and return control to command shell or calling script.} +// +// /with +// {Yield a result (mapped to an integer if given to shell)} +// value [any-value!] +// "See: http://en.wikipedia.org/wiki/Exit_status" +// ] +// +REBNATIVE(quit) +// +// QUIT is implemented via a THROWN() value that bubbles up through +// the stack. It uses the value of its own native function as the +// name of the throw, like `throw/name value :quit`. { - REBVAL *val = D_ARG(2); - - if (D_REF(3)) { - REBINT n = 0; - if (D_REF(1)) { - if (IS_INTEGER(val)) n = Int32(val); - else if (IS_TRUE(val)) n = 100; - } - OS_EXIT(n); - } - - Halt_Code(RE_QUIT, val); // NONE if /return not set - DEAD_END; -} - - -/*********************************************************************** -** -*/ REBNATIVE(recycle) -/* -***********************************************************************/ -{ - REBCNT count; - - if (D_REF(1)) { // /off - GC_Active = FALSE; - return R_UNSET; - } + INCLUDE_PARAMS_OF_QUIT; - if (D_REF(2)) {// /on - GC_Active = TRUE; - SET_INT32(TASK_BALLAST, VAL_INT32(TASK_MAX_BALLAST)); - } + Move_Value(D_OUT, NAT_VALUE(quit)); - if (D_REF(3)) {// /ballast - *TASK_MAX_BALLAST = *D_ARG(4); - SET_INT32(TASK_BALLAST, VAL_INT32(TASK_MAX_BALLAST)); - } + if (REF(with)) + CONVERT_NAME_TO_THROWN(D_OUT, ARG(value)); + else { + // Chosen to do it this way because returning to a calling script it + // will be no value by default, for parity with BREAK and EXIT without + // a /WITH. Long view would have RETURN work this way too: CC#2241 - if (D_REF(5)) { // torture - GC_Active = TRUE; - SET_INT32(TASK_BALLAST, 0); - } + // void translated to 0 if it gets caught for the shell, see #2241 - count = Recycle(); + CONVERT_NAME_TO_THROWN(D_OUT, VOID_CELL); + } - DS_Ret_Int(count); - return R_RET; + return R_OUT_IS_THROWN; } -/*********************************************************************** -** -*/ REBNATIVE(stats) -/* -***********************************************************************/ +// +// exit-rebol: native [ +// +// {Stop the current Rebol interpreter, cannot be caught by CATCH/QUIT.} +// +// /with +// {Yield a result (mapped to an integer if given to shell)} +// value [any-value!] +// "See: http://en.wikipedia.org/wiki/Exit_status" +// ] +// +REBNATIVE(exit_rebol) { - REBI64 n; - REBCNT flags = 0; - REBVAL *stats; - - if (D_REF(3)) { - VAL_TIME(ds) = OS_DELTA_TIME(PG_Boot_Time, 0) * 1000; - VAL_SET(ds, REB_TIME); - return R_RET; - } - - if (D_REF(4)) { - n = Eval_Cycles + Eval_Dose - Eval_Count; - SET_INTEGER(ds, n); - return R_RET; - } - - if (D_REF(2)) { - stats = Get_System(SYS_STANDARD, STD_STATS); - *ds = *stats; - if (IS_OBJECT(stats)) { - stats = Get_Object(stats, 1); - - VAL_TIME(stats) = OS_DELTA_TIME(PG_Boot_Time, 0) * 1000; - VAL_SET(stats, REB_TIME); - stats++; - SET_INTEGER(stats, Eval_Cycles + Eval_Dose - Eval_Count); - stats++; - SET_INTEGER(stats, Eval_Natives); - stats++; - SET_INTEGER(stats, Eval_Functions); - - stats++; - SET_INTEGER(stats, PG_Reb_Stats->Series_Made); - stats++; - SET_INTEGER(stats, PG_Reb_Stats->Series_Freed); - stats++; - SET_INTEGER(stats, PG_Reb_Stats->Series_Expanded); - stats++; - SET_INTEGER(stats, PG_Reb_Stats->Series_Memory); - stats++; - SET_INTEGER(stats, PG_Reb_Stats->Recycle_Series_Total); - - stats++; - SET_INTEGER(stats, PG_Reb_Stats->Blocks); - stats++; - SET_INTEGER(stats, PG_Reb_Stats->Objects); - - stats++; - SET_INTEGER(stats, PG_Reb_Stats->Recycle_Counter); - } - return R_RET; - } - - if (D_REF(1)) flags = 3; - n = Inspect_Series(flags); - - DS_RET_INT(n); - - return R_RET; -} + INCLUDE_PARAMS_OF_EXIT_REBOL; -REBYTE *evoke_help = "Evoke values:\n" - "[stack-size n] crash-dump delect\n" - "watch-recycle watch-obj-copy crash\n" - "1: watch expand\n" - "2: check memory pools\n" - "3: check bind table\n" -; - -/*********************************************************************** -** -*/ REBNATIVE(evoke) -/* -***********************************************************************/ -{ - REBVAL *arg = D_ARG(1); - REBCNT len; - - Check_Security(SYM_DEBUG, POL_READ, 0); - - if (IS_BLOCK(arg)) { - len = VAL_LEN(arg); - arg = VAL_BLK_DATA(arg); - } - else len = 1; - - for (; len > 0; len--, arg++) { - if (IS_WORD(arg)) { - switch (VAL_WORD_CANON(arg)) { - case SYM_DELECT: - Trace_Delect(1); - break; - case SYM_CRASH_DUMP: - Reb_Opts->crash_dump = TRUE; - break; - case SYM_WATCH_RECYCLE: - Reb_Opts->watch_recycle = !Reb_Opts->watch_recycle; - break; - case SYM_WATCH_OBJ_COPY: - Reb_Opts->watch_obj_copy = !Reb_Opts->watch_obj_copy; - break; - case SYM_STACK_SIZE: - arg++; - Expand_Stack(Int32s(arg, 1)); - break; - case SYM_CRASH: - Crash(9999); - break; - default: - Out_Str(evoke_help, 1); - } - } - if (IS_INTEGER(arg)) { - switch (Int32(arg)) { - case 0: - Check_Memory(); - Check_Bind_Table(); - break; - case 1: - Reb_Opts->watch_expand = TRUE; - break; - case 2: - Check_Memory(); - break; - case 3: - Check_Bind_Table(); - break; - default: - Out_Str(evoke_help, 1); - } - } - } - - return R_UNSET; -} + int code; + if (REF(with)) + code = VAL_INT32(ARG(value)); + else + code = EXIT_SUCCESS; -#ifdef NOT_USED -/*********************************************************************** -** -*/ REBNATIVE(in_context) -/* -***********************************************************************/ -{ - REBVAL *value; - value = D_ARG(1); - VAL_OBJ_FRAME(ROOT_USER_CONTEXT) = VAL_OBJ_FRAME(value); - return R_UNSET; + exit(code); } -#endif -/*********************************************************************** -** -*/ REBNATIVE(limit_usage) -/* -***********************************************************************/ -{ - REBCNT sym; - sym = VAL_WORD_CANON(D_ARG(1)); - - // Only gets set once: - if (sym == SYM_EVAL) { - if (Eval_Limit == 0) Eval_Limit = Int64(D_ARG(2)); - } else if (sym == SYM_MEMORY) { - if (PG_Mem_Limit == 0) PG_Mem_Limit = Int64(D_ARG(2)); - } - return R_UNSET; -} - - -/*********************************************************************** -** -*/ REBNATIVE(stack) -/* -** stack: native [ -** {Returns stack backtrace or other values.} -** offset [integer!] "Relative backward offset" -** /block "Block evaluation position" -** /word "Function or object name, if known" -** /func "Function value" -** /args "Block of args (may be modified)" -** /size "Current stack size (in value units)" -** /depth "Stack depth (frames)" -** /limit "Stack bounds (auto expanding)" -** ] -** -***********************************************************************/ +// +// recycle: native [ +// +// "Recycles unused memory." +// +// return: [ integer!] +// {Number of series nodes recycled (if applicable)} +// /off +// "Disable auto-recycling" +// /on +// "Enable auto-recycling" +// /ballast +// "Trigger for auto-recycle (memory used)" +// size [integer!] +// /torture +// "Constant recycle (for internal debugging)" +// /verbose +// "Dump out information about series being recycled" +// ] +// +REBNATIVE(recycle) { - REBINT index = VAL_INT32(D_ARG(1)); - REBVAL *sp; - REBCNT len; - - Check_Security(SYM_DEBUG, POL_READ, 0); - - sp = Stack_Frame(index); - if (!sp) return R_NONE; - - if (D_REF(2)) *D_RET = sp[1]; // block - else if (D_REF(3)) Init_Word(D_RET, VAL_WORD_SYM(sp+2)); // word - else if (D_REF(4)) *D_RET = sp[3]; // func - else if (D_REF(5)) { // args - len = 0; - if (ANY_FUNC(sp+3)) len = VAL_FUNC_ARGC(sp+3)-1; - sp += 4; - Set_Block(D_RET, Copy_Values(sp, len)); - } - else if (D_REF(6)) { // size - SET_INTEGER(D_RET, DSP+1); - } - else if (D_REF(7)) { // depth - SET_INTEGER(D_RET, Stack_Depth()); - } - else if (D_REF(8)) { // limit - SET_INTEGER(D_RET, SERIES_REST(DS_Series) + SERIES_BIAS(DS_Series)); - } - else { - Set_Block(D_RET, Make_Backtrace(index)); - } - - return R_RET; + INCLUDE_PARAMS_OF_RECYCLE; + + if (REF(off)) { + GC_Disabled = TRUE; + return R_VOID; + } + + if (REF(on)) { + GC_Disabled = FALSE; + VAL_INT64(TASK_BALLAST) = VAL_INT32(TASK_MAX_BALLAST); + } + + if (REF(ballast)) { + Move_Value(TASK_MAX_BALLAST, ARG(size)); + VAL_INT64(TASK_BALLAST) = VAL_INT32(TASK_MAX_BALLAST); + } + + if (REF(torture)) { + GC_Disabled = TRUE; + VAL_INT64(TASK_BALLAST) = 0; + } + + if (GC_Disabled) + return R_VOID; // don't give back misleading "0", since no recycle ran + + REBCNT count; + + if (REF(verbose)) { + #if defined(NDEBUG) + fail (Error_Debug_Only_Raw()); + #else + REBSER *sweeplist = Make_Series(100, sizeof(REBNOD*)); + count = Recycle_Core(FALSE, sweeplist); + assert(count == SER_LEN(sweeplist)); + + REBCNT index = 0; + for (index = 0; index < count; ++index) { + REBNOD *node = *SER_AT(REBNOD*, sweeplist, index); + PROBE(node); + } + + Free_Series(sweeplist); + + REBCNT recount = Recycle_Core(FALSE, NULL); + assert(recount == count); + #endif + } + else { + count = Recycle(); + } + + Init_Integer(D_OUT, count); + return R_OUT; } -/*********************************************************************** -** -*/ REBNATIVE(check) -/* -***********************************************************************/ +// +// stats: native [ +// +// {Provides status and statistics information about the interpreter.} +// +// /show +// "Print formatted results to console" +// /profile +// "Returns profiler object" +// /timer +// "High resolution time difference from start" +// /evals +// "Number of values evaluated by interpreter" +// /dump-series +// "Dump all series in pool" +// pool-id [integer!] +// "-1 for all pools" +// ] +// +REBNATIVE(stats) { - REBVAL *val; - REBSER *ser; - REBCNT n; - - ser = VAL_SERIES(val = D_ARG(1)); - *D_RET = *val; - - if (ANY_BLOCK(val)) { - for (n = 0; n < SERIES_TAIL(ser); n++) { - if (IS_END(BLK_SKIP(ser, n))) goto err; - } - if (!IS_END(BLK_SKIP(ser, n))) goto err; - } - else { - for (n = 0; n < SERIES_TAIL(ser); n++) { - if (!*STR_SKIP(ser, n)) goto err; - } - if (*STR_SKIP(ser, n)) goto err; - } - return R_RET; -err: - Trap0(RE_BAD_SERIES); - DEAD_END; + INCLUDE_PARAMS_OF_STATS; + + if (REF(timer)) { + VAL_RESET_HEADER(D_OUT, REB_TIME); + VAL_NANO(D_OUT) = OS_DELTA_TIME(PG_Boot_Time, 0) * 1000; + return R_OUT; + } + + if (REF(evals)) { + REBI64 n = Eval_Cycles + Eval_Dose - Eval_Count; + Init_Integer(D_OUT, n); + return R_OUT; + } + +#ifdef NDEBUG + UNUSED(REF(show)); + UNUSED(REF(profile)); + UNUSED(REF(dump_series)); + UNUSED(ARG(pool_id)); + + fail (Error_Debug_Only_Raw()); +#else + if (REF(profile)) { + Move_Value(D_OUT, Get_System(SYS_STANDARD, STD_STATS)); + if (IS_OBJECT(D_OUT)) { + REBVAL *stats = VAL_CONTEXT_VAR(D_OUT, 1); + + VAL_RESET_HEADER(stats, REB_TIME); + VAL_NANO(stats) = OS_DELTA_TIME(PG_Boot_Time, 0) * 1000; + stats++; + Init_Integer(stats, Eval_Cycles + Eval_Dose - Eval_Count); + stats++; + Init_Integer(stats, 0); // no such thing as natives, only functions + stats++; + Init_Integer(stats, Eval_Functions); + + stats++; + Init_Integer(stats, PG_Reb_Stats->Series_Made); + stats++; + Init_Integer(stats, PG_Reb_Stats->Series_Freed); + stats++; + Init_Integer(stats, PG_Reb_Stats->Series_Expanded); + stats++; + Init_Integer(stats, PG_Reb_Stats->Series_Memory); + stats++; + Init_Integer(stats, PG_Reb_Stats->Recycle_Series_Total); + + stats++; + Init_Integer(stats, PG_Reb_Stats->Blocks); + stats++; + Init_Integer(stats, PG_Reb_Stats->Objects); + + stats++; + Init_Integer(stats, PG_Reb_Stats->Recycle_Counter); + } + + return R_OUT; + } + + if (REF(dump_series)) { + REBVAL *pool_id = ARG(pool_id); + Dump_Series_In_Pool(VAL_INT32(pool_id)); + return R_BLANK; + } + + Init_Integer(D_OUT, Inspect_Series(REF(show))); + + if (REF(show)) + Dump_Pools(); + + return R_OUT; +#endif } -/*********************************************************************** -** -*/ REBNATIVE(ds) -/* -***********************************************************************/ +// +// evoke: native [ +// +// "Special guru meditations. (Not for beginners.)" +// +// chant [word! block! integer!] +// "Single or block of words ('? to list)" +// ] +// +REBNATIVE(evoke) { - Dump_Stack(0, 0); - return R_UNSET; - - Dump_All(sizeof(REBVAL)); - return R_RET; + INCLUDE_PARAMS_OF_EVOKE; + +#ifdef NDEBUG + UNUSED(ARG(chant)); + + fail (Error_Debug_Only_Raw()); +#else + RELVAL *arg = ARG(chant); + REBCNT len; + + Check_Security(Canon(SYM_DEBUG), POL_READ, 0); + + if (IS_BLOCK(arg)) { + len = VAL_LEN_AT(arg); + arg = VAL_ARRAY_AT(arg); + } + else len = 1; + + for (; len > 0; len--, arg++) { + if (IS_WORD(arg)) { + switch (VAL_WORD_SYM(arg)) { + case SYM_CRASH_DUMP: + Reb_Opts->crash_dump = TRUE; + break; + + case SYM_WATCH_RECYCLE: + Reb_Opts->watch_recycle = NOT(Reb_Opts->watch_recycle); + break; + + case SYM_CRASH: + panic ("evoke 'crash was executed"); + + default: + Debug_Fmt(RM_EVOKE_HELP); + } + } + if (IS_INTEGER(arg)) { + switch (Int32(arg)) { + case 0: + Check_Memory_Debug(); + break; + + case 1: + Reb_Opts->watch_expand = TRUE; + break; + + default: + Debug_Fmt(RM_EVOKE_HELP); + } + } + } + + return R_VOID; +#endif } -/*********************************************************************** -** -*/ REBNATIVE(do_codec) -/* -** Calls a codec handle with specific data: -** -** Args: -** 1: codec: handle! -** 2: action: word! (identify, decode, encode) -** 3: data: binary! image! sound! -** 4: option: (optional) -** -***********************************************************************/ +// +// limit-usage: native [ +// +// "Set a usage limit only once (used for SECURE)." +// +// field [word!] +// "eval (count) or memory (bytes)" +// limit [any-number!] +// ] +// +REBNATIVE(limit_usage) { - REBCDI codi; - REBVAL *val; - REBINT result; - REBSER *ser; - - CLEAR(&codi, sizeof(codi)); - - codi.action = CODI_DECODE; - - val = D_ARG(3); - - switch (VAL_WORD_SYM(D_ARG(2))) { - - case SYM_IDENTIFY: - codi.action = CODI_IDENTIFY; - case SYM_DECODE: - if (!IS_BINARY(val)) Trap1(RE_INVALID_ARG, val); - codi.data = VAL_BIN_DATA(D_ARG(3)); - codi.len = VAL_LEN(D_ARG(3)); - break; - - case SYM_ENCODE: - codi.action = CODI_ENCODE; - if (IS_IMAGE(val)) { - codi.bits = VAL_IMAGE_BITS(val); - codi.w = VAL_IMAGE_WIDE(val); - codi.h = VAL_IMAGE_HIGH(val); - codi.alpha = Image_Has_Alpha(val, 0); - } - else - Trap1(RE_INVALID_ARG, val); - break; - - default: - Trap1(RE_INVALID_ARG, D_ARG(2)); - } - - // Nasty alias, but it must be done: - // !!! add a check to validate the handle as a codec!!!! - result = ((codo) (VAL_HANDLE(D_ARG(1))))(&codi); - - if (codi.error != 0) { - if (result == CODI_CHECK) return R_FALSE; - Trap0(RE_BAD_MEDIA); // need better!!! - } - - switch (result) { - - case CODI_CHECK: - return R_TRUE; - - case CODI_BINARY: //used on encode - case CODI_TEXT: //used on decode - ser = Make_Binary(codi.len); - ser->tail = codi.len; - memcpy(BIN_HEAD(ser), codi.data, codi.len); - Set_Binary(D_RET, ser); - if (result != CODI_BINARY) VAL_SET(D_RET, REB_STRING); - - //don't free the text binary input buffer during decode (it's the 3rd arg value in fact) - if (result == CODI_BINARY) - // See notice in reb-codec.h on reb_codec_image - Free_Mem(codi.data, codi.len); - break; - - case CODI_IMAGE: //used on decode - ser = Make_Image(codi.w, codi.h, TRUE); // Puts it into RETURN stack position - memcpy(IMG_DATA(ser), codi.bits, codi.w * codi.h * 4); - SET_IMAGE(D_RET, ser); - - // See notice in reb-codec.h on reb_codec_image - Free_Mem(codi.bits, codi.w * codi.h * 4); - break; - - case CODI_BLOCK: - Set_Block(D_RET, codi.other); - break; - - default: - Trap0(RE_BAD_MEDIA); // need better!!! - } - - return R_RET; + INCLUDE_PARAMS_OF_LIMIT_USAGE; + + REBSYM sym = VAL_WORD_SYM(ARG(field)); + + // !!! comment said "Only gets set once"...why? + // + if (sym == SYM_EVAL) { + if (Eval_Limit == 0) + Eval_Limit = Int64(ARG(limit)); + } + else if (sym == SYM_MEMORY) { + if (PG_Mem_Limit == 0) + PG_Mem_Limit = Int64(ARG(limit)); + } + else + fail (ARG(field)); + + return R_VOID; } -/*********************************************************************** -** -*/ REBNATIVE(selflessq) -/* -***********************************************************************/ +// +// check: native [ +// +// "Run an integrity check on a value in debug builds of the interpreter" +// +// value [ any-value!] +// {System will terminate abnormally if this value is corrupt.} +// ] +// +REBNATIVE(check) +// +// This forces an integrity check to run on a series. In R3-Alpha there was +// no debug build, so this was a simple validity check and it returned an +// error on not passing. But Ren-C is designed to have a debug build with +// checks that aren't designed to fail gracefully. So this just runs that +// assert rather than replicating code here that can "tolerate" a bad series. +// Review the necessity of this native. { - REBVAL *val = D_ARG(1); - REBSER *frm; - - if (ANY_WORD(val)) { - if (VAL_WORD_INDEX(val) < 0) return R_TRUE; - frm = VAL_WORD_FRAME(val); - if (!frm) Trap1(RE_NOT_DEFINED, val); - } - else frm = VAL_OBJ_FRAME(D_ARG(1)); - - return IS_SELFLESS(frm) ? R_TRUE : R_FALSE; + INCLUDE_PARAMS_OF_CHECK; + +#ifdef NDEBUG + UNUSED(ARG(value)); + + fail (Error_Debug_Only_Raw()); +#else + REBVAL *value = ARG(value); + + // !!! Should call generic ASSERT_VALUE macro with more cases + // + if (ANY_SERIES(value)) { + ASSERT_SERIES(VAL_SERIES(value)); + } + else if (ANY_CONTEXT(value)) { + ASSERT_CONTEXT(VAL_CONTEXT(value)); + } + else if (IS_FUNCTION(value)) { + ASSERT_ARRAY(VAL_FUNC_PARAMLIST(value)); + ASSERT_ARRAY(VAL_ARRAY(VAL_FUNC_BODY(value))); + } + + return R_TRUE; +#endif } diff --git a/src/core/n-textcodecs.c b/src/core/n-textcodecs.c new file mode 100644 index 0000000000..82272cf545 --- /dev/null +++ b/src/core/n-textcodecs.c @@ -0,0 +1,356 @@ +// +// File: %n-textcodec.c +// Summary: "Native text codecs" +// Section: natives +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// R3-Alpha had an incomplete model for doing codecs, that required C coding +// to implement...even though the input and output types to DO-CODEC were +// Rebol values. Under Ren-C these are done as plain FUNCTION!s, which can +// be coded in either C as natives or Rebol. +// +// A few text codecs were included in R3-Alpha and kept for testing. They +// were converted here into groups of native functions, but should be further +// moved into an extension so they can be optional in the build. +// + +#include "sys-core.h" + + +// +// identify-text?: native [ +// +// {Codec for identifying BINARY! data for a .TXT file} +// +// return: [logic!] +// data [binary!] +// ] +// +REBNATIVE(identify_text_q) +{ + INCLUDE_PARAMS_OF_IDENTIFY_TEXT_Q; + + UNUSED(ARG(data)); // see notes on decode-text + + return R_TRUE; +} + + +// +// decode-text: native [ +// +// {Codec for decoding BINARY! data for a .TXT file} +// +// return: [string!] +// data [binary!] +// ] +// +REBNATIVE(decode_text) +{ + INCLUDE_PARAMS_OF_DECODE_TEXT; + + // !!! The original code for R3-Alpha would simply alias the incoming + // binary as a string. This is essentially a Latin1 interpretation. + // For the moment that behavior is preserved, but what is *not* preserved + // is the idea of reusing the BINARY!--a copy is made. + // + // A more "intelligent" codec would do some kind of detection here, to + // figure out what format the text file was in. While Ren-C's commitment + // is to UTF-8 for source code, a .TXT file is a different beast, so + // having wider format support might be a good thing. + + Init_String(D_OUT, Copy_Sequence_At_Position(ARG(data))); + return R_OUT; +} + + +// +// encode-text: native [ +// +// {Codec for encoding a .TXT file} +// +// return: [binary!] +// string [string!] +// ] +// +REBNATIVE(encode_text) +{ + INCLUDE_PARAMS_OF_ENCODE_TEXT; + + if (NOT(VAL_BYTE_SIZE(ARG(string)))) { + // + // For the moment, only write out strings to .txt if they are Latin1. + // (Other support was unimplemented in R3-Alpha, and would just wind + // up writing garbage.) + // + fail ("Can only write out strings to .txt if they are Latin1."); + } + + Init_Binary(D_OUT, Copy_Sequence_At_Position(ARG(string))); + return R_OUT; +} + + +static void Encode_Utf16_Core( + REBVAL *out, + const void *data, // may be REBYTE* or REBUNI*, depending on width + REBCNT len, + REBYTE wide, + REBOOL little_endian +){ + REBSER *bin = Make_Binary(sizeof(u16) * len); + u16* up = cast(u16*, BIN_HEAD(bin)); + + if (wide == 1) { // Latin1 + REBCNT i = 0; + for (i = 0; i < len; i ++) { + #ifdef ENDIAN_LITTLE + if (little_endian) { + up[i] = cast(const char*, data)[i]; + } else { + up[i] = cast(const char*, data)[i] << 8; + } + #elif defined (ENDIAN_BIG) + if (little_endian) { + up[i] = cast(const char*, data)[i] << 8; + } else { + up[i] = cast(const char*, data)[i]; + } + #else + #error "Unsupported CPU endian" + #endif + } + } + else if (wide == 2) { // UCS2, which is close to UTF16 :-/ + #ifdef ENDIAN_LITTLE + if (little_endian) { + memcpy(up, data, len * sizeof(u16)); + } else { + REBCNT i = 0; + for (i = 0; i < len; i ++) { + REBUNI uni = cast(const REBUNI*, data)[i]; + up[i] = ((uni & 0xff) << 8) | ((uni & 0xff00) >> 8); + } + } + #elif defined (ENDIAN_BIG) + if (little_endian) { + REBCNT i = 0; + for (i = 0; i < len; i ++) { + REBUNI uni = cast(const REBUNI*, data)[i]; + up[i] = ((uni & 0xff) << 8) | ((uni & 0xff00) >> 8); + } + } else { + memcpy(up, data, len * sizeof(u16)); + } + #else + #error "Unsupported CPU endian" + #endif + } + else { + fail ("Unicode width > 2 reserved for future expansion."); + } + + TERM_BIN_LEN(bin, len * sizeof(u16)); + Init_Binary(out, bin); +} + + +static void Decode_Utf16_Core( + REBVAL *out, + const REBYTE *data, + REBCNT len, + REBOOL little_endian +){ + REBSER *ser = Make_Unicode(len); // 2x too big (?) + + REBINT size = Decode_UTF16( + UNI_HEAD(ser), data, len, little_endian, FALSE + ); + SET_SERIES_LEN(ser, size); + + if (size < 0) { // ASCII + size = -size; + + REBSER *dst = Make_Binary(size); + Append_Uni_Bytes(dst, UNI_HEAD(ser), size); + Free_Series(ser); + + ser = dst; + } + + Init_String(out, ser); +} + + +// +// identify-utf16le?: native [ +// +// {Codec for identifying BINARY! data for a little-endian UTF16 file} +// +// return: [logic!] +// data [binary!] +// ] +// +REBNATIVE(identify_utf16le_q) +{ + INCLUDE_PARAMS_OF_IDENTIFY_UTF16LE_Q; + + UNUSED(ARG(data)); // R3-Alpha just said it matched if extension matched + + return R_TRUE; +} + + +// +// decode-utf16le: native [ +// +// {Codec for decoding BINARY! data for a little-endian UTF16 file} +// +// return: [string!] +// data [binary!] +// ] +// +REBNATIVE(decode_utf16le) +{ + INCLUDE_PARAMS_OF_DECODE_UTF16LE; + + REBYTE *data = VAL_BIN_AT(ARG(data)); + REBCNT len = VAL_LEN_AT(ARG(data)); + + const REBOOL little_endian = TRUE; + + Decode_Utf16_Core(D_OUT, data, len, little_endian); + return R_OUT; +} + + +// +// encode-utf16le: native [ +// +// {Codec for encoding a little-endian UTF16 file} +// +// return: [binary!] +// string [string!] +// ] +// +REBNATIVE(encode_utf16le) +{ + INCLUDE_PARAMS_OF_ENCODE_UTF16LE; + + void *data; + REBYTE wide; + if (VAL_BYTE_SIZE(ARG(string))) { + data = VAL_BIN_AT(ARG(string)); + wide = 1; + } + else { + data = VAL_UNI_AT(ARG(string)); + wide = 2; + } + + REBCNT len = VAL_LEN_AT(ARG(string)); + + const REBOOL little_endian = TRUE; + + Encode_Utf16_Core(D_OUT, data, len, wide, little_endian); + return R_OUT; +} + + + +// +// identify-utf16be?: native [ +// +// {Codec for identifying BINARY! data for a big-endian UTF16 file} +// +// return: [logic!] +// data [binary!] +// ] +// +REBNATIVE(identify_utf16be_q) +{ + INCLUDE_PARAMS_OF_IDENTIFY_UTF16BE_Q; + + UNUSED(ARG(data)); // R3-Alpha just said it matched if extension matched + + return R_TRUE; +} + + +// +// decode-utf16be: native [ +// +// {Codec for decoding BINARY! data for a big-endian UTF16 file} +// +// return: [string!] +// data [binary!] +// ] +// +REBNATIVE(decode_utf16be) +{ + INCLUDE_PARAMS_OF_DECODE_UTF16BE; + + REBYTE *data = VAL_BIN_AT(ARG(data)); + REBCNT len = VAL_LEN_AT(ARG(data)); + + const REBOOL little_endian = FALSE; + + Decode_Utf16_Core(D_OUT, data, len, little_endian); + return R_OUT; +} + + +// +// encode-utf16be: native [ +// +// {Codec for encoding a big-endian UTF16 file} +// +// return: [binary!] +// string [string!] +// ] +// +REBNATIVE(encode_utf16be) +{ + INCLUDE_PARAMS_OF_ENCODE_UTF16BE; + + void *data; + REBYTE wide; + if (VAL_BYTE_SIZE(ARG(string))) { + data = VAL_BIN_AT(ARG(string)); + wide = 1; + } + else { + data = VAL_UNI_AT(ARG(string)); + wide = 2; + } + + REBCNT len = VAL_LEN_AT(ARG(string)); + + const REBOOL little_endian = FALSE; + + Encode_Utf16_Core(D_OUT, data, len, wide, little_endian); + return R_OUT; +} diff --git a/src/core/p-clipboard.c b/src/core/p-clipboard.c index 3ae6e995f5..f327b5e98e 100644 --- a/src/core/p-clipboard.c +++ b/src/core/p-clipboard.c @@ -1,159 +1,255 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: p-clipboard.c -** Summary: clipboard port interface -** Section: ports -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %p-clipboard.c +// Summary: "clipboard port interface" +// Section: ports +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/*********************************************************************** -** -*/ static int Clipboard_Actor(REBVAL *ds, REBSER *port, REBCNT action) -/* -***********************************************************************/ +// +// Clipboard_Actor: C +// +static REB_R Clipboard_Actor(REBFRM *frame_, REBCTX *port, REBSYM action) { - REBREQ *req; - REBINT result; - REBVAL *arg; - REBCNT refs; // refinement argument flags - REBINT len; - REBSER *ser; - - Validate_Port(port, action); - - arg = D_ARG(2); - - req = Use_Port_State(port, RDI_CLIPBOARD, sizeof(REBREQ)); - - switch (action) { - - case A_READ: - // This device is opened on the READ: - if (!IS_OPEN(req)) { - if (OS_DO_DEVICE(req, RDC_OPEN)) Trap_Port(RE_CANNOT_OPEN, port, req->error); - } - // Issue the read request: - CLR_FLAG(req->flags, RRF_WIDE); // allow byte or wide chars - result = OS_DO_DEVICE(req, RDC_READ); - if (result < 0) Trap_Port(RE_READ_ERROR, port, req->error); - - // Copy and set the string result: - arg = OFV(port, STD_PORT_DATA); - - // If wide, correct length: - len = req->actual; - if (GET_FLAG(req->flags, RRF_WIDE)) len /= sizeof(REBUNI); - - // Copy the string (convert to latin-8 if it fits): - Set_String(arg, Copy_OS_Str(req->data, len)); - - OS_FREE(req->data); // release the copy buffer - req->data = 0; - *D_RET = *arg; - return R_RET; - - case A_WRITE: - if (!IS_STRING(arg) && !IS_BINARY(arg)) Trap1(RE_INVALID_PORT_ARG, arg); - // This device is opened on the WRITE: - if (!IS_OPEN(req)) { - if (OS_DO_DEVICE(req, RDC_OPEN)) Trap_Port(RE_CANNOT_OPEN, port, req->error); - } - - refs = Find_Refines(ds, ALL_WRITE_REFS); - - // Handle /part refinement: - len = VAL_LEN(arg); - if (refs & AM_WRITE_PART && VAL_INT32(D_ARG(ARG_WRITE_LENGTH)) < len) - len = VAL_INT32(D_ARG(ARG_WRITE_LENGTH)); - - // If bytes, see if we can fit it: - if (SERIES_WIDE(VAL_SERIES(arg)) == 1) { + REBINT result; + REBVAL *arg; + REBINT len; + REBSER *ser; + + arg = D_ARGC > 1 ? D_ARG(2) : NULL; + + REBREQ *req = Ensure_Port_State(port, RDI_CLIPBOARD); + + switch (action) { + case SYM_UPDATE: + // Update the port object after a READ or WRITE operation. + // This is normally called by the WAKE-UP function. + arg = CTX_VAR(port, STD_PORT_DATA); + if (req->command == RDC_READ) { + // this could be executed twice: + // once for an event READ, once for the CLOSE following the READ + if (!req->common.data) return R_BLANK; + len = req->actual; + if (GET_FLAG(req->flags, RRF_WIDE)) { + // convert to UTF8, so that it can be converted back to string! + Init_Binary(arg, Make_UTF8_Binary( + req->common.data, + len / sizeof(REBUNI), + 0, + OPT_ENC_UNISRC + )); + } + else { + REBSER *ser = Make_Binary(len); + memcpy(BIN_HEAD(ser), req->common.data, len); + SET_SERIES_LEN(ser, len); + Init_Binary(arg, ser); + } + OS_FREE(req->common.data); // release the copy buffer + req->common.data = 0; + } + else if (req->command == RDC_WRITE) { + Init_Blank(arg); // Write is done. + } + return R_BLANK; + + case SYM_READ: { + INCLUDE_PARAMS_OF_READ; + + UNUSED(PAR(source)); // already accounted for + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(seek)) { + UNUSED(ARG(index)); + fail (Error_Bad_Refines_Raw()); + } + UNUSED(PAR(string)); // handled in dispatcher + UNUSED(PAR(lines)); // handled in dispatcher + + // This device is opened on the READ: + if (!IS_OPEN(req)) { + if (OS_DO_DEVICE(req, RDC_OPEN)) + fail (Error_On_Port(RE_CANNOT_OPEN, port, req->error)); + } + // Issue the read request: + CLR_FLAG(req->flags, RRF_WIDE); // allow byte or wide chars + result = OS_DO_DEVICE(req, RDC_READ); + if (result < 0) fail (Error_On_Port(RE_READ_ERROR, port, req->error)); + if (result > 0) return R_BLANK; /* pending */ + + // Copy and set the string result: + arg = CTX_VAR(port, STD_PORT_DATA); + + len = req->actual; + if (GET_FLAG(req->flags, RRF_WIDE)) { + // convert to UTF8, so that it can be converted back to string! + Init_Binary(arg, Make_UTF8_Binary( + req->common.data, + len / sizeof(REBUNI), + 0, + OPT_ENC_UNISRC + )); + } + else { + REBSER *ser = Make_Binary(len); + memcpy(BIN_HEAD(ser), req->common.data, len); + SET_SERIES_LEN(ser, len); + Init_Binary(arg, ser); + } + + Move_Value(D_OUT, arg); + return R_OUT; } + + case SYM_WRITE: { + INCLUDE_PARAMS_OF_WRITE; + + UNUSED(PAR(destination)); + UNUSED(PAR(data)); // used as arg + + if (REF(seek)) { + UNUSED(ARG(index)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(append)) + fail (Error_Bad_Refines_Raw()); + if (REF(allow)) { + UNUSED(ARG(access)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(lines)) + fail (Error_Bad_Refines_Raw()); + + if (!IS_STRING(arg) && !IS_BINARY(arg)) + fail (Error_Invalid_Port_Arg_Raw(arg)); + + // This device is opened on the WRITE: + if (!IS_OPEN(req)) { + if (OS_DO_DEVICE(req, RDC_OPEN)) + fail (Error_On_Port(RE_CANNOT_OPEN, port, req->error)); + } + + // Handle /part refinement: + len = VAL_LEN_AT(arg); + if (REF(part) && VAL_INT32(ARG(limit)) < len) + len = VAL_INT32(ARG(limit)); + + // If bytes, see if we can fit it: + if (SER_WIDE(VAL_SERIES(arg)) == 1) { #ifdef ARG_STRINGS_ALLOWED - if (Is_Not_ASCII(VAL_BIN_DATA(arg), len)) { - Set_String(arg, Copy_Bytes_To_Unicode(VAL_BIN_DATA(arg), len)); - } else - req->data = VAL_BIN_DATA(arg); + if (!All_Bytes_ASCII(VAL_BIN_AT(arg), len)) { + REBSER *copy = Copy_Bytes_To_Unicode(VAL_BIN_AT(arg), len); + Init_String(arg, copy); + } else + req->common.data = VAL_BIN_AT(arg); #endif - // Temp conversion:!!! - ser = Make_Unicode(len); - len = Decode_UTF8(UNI_HEAD(ser), VAL_BIN_DATA(arg), len, FALSE); - SERIES_TAIL(ser) = len = abs(len); - UNI_TERM(ser); - Set_String(arg, ser); - req->data = (REBYTE*) UNI_HEAD(ser); - SET_FLAG(req->flags, RRF_WIDE); - } - else - // If unicode (may be from above conversion), handle it: - if (SERIES_WIDE(VAL_SERIES(arg)) == sizeof(REBUNI)) { - req->data = (REBYTE *)VAL_UNI_DATA(arg); - SET_FLAG(req->flags, RRF_WIDE); - } - - // Temp!!! - req->length = len * sizeof(REBUNI); - - // Setup the write: - *OFV(port, STD_PORT_DATA) = *arg; // keep it GC safe - req->actual = 0; - - result = OS_DO_DEVICE(req, RDC_WRITE); - SET_NONE(OFV(port, STD_PORT_DATA)); // GC can collect it - - if (result < 0) Trap_Port(RE_WRITE_ERROR, port, req->error); - //if (result == DR_DONE) SET_NONE(OFV(port, STD_PORT_DATA)); - break; - - case A_OPEN: - if (OS_DO_DEVICE(req, RDC_OPEN)) Trap_Port(RE_CANNOT_OPEN, port, req->error); - break; - - case A_CLOSE: - OS_DO_DEVICE(req, RDC_CLOSE); - break; - - case A_OPENQ: - if (IS_OPEN(req)) return R_TRUE; - return R_FALSE; - - default: - Trap_Action(REB_PORT, action); - } - - return R_ARG1; // port + // Temp conversion:!!! + ser = Make_Unicode(len); + len = Decode_UTF8_Negative_If_Latin1( + UNI_HEAD(ser), VAL_BIN_AT(arg), len, FALSE + ); + len = abs(len); + TERM_UNI_LEN(ser, len); + Init_String(arg, ser); + req->common.data = cast(REBYTE*, UNI_HEAD(ser)); + SET_FLAG(req->flags, RRF_WIDE); + } + else + // If unicode (may be from above conversion), handle it: + if (SER_WIDE(VAL_SERIES(arg)) == sizeof(REBUNI)) { + req->common.data = cast(REBYTE *, VAL_UNI_AT(arg)); + SET_FLAG(req->flags, RRF_WIDE); + } + + // Temp!!! + req->length = len * sizeof(REBUNI); + + // Setup the write: + Move_Value(CTX_VAR(port, STD_PORT_DATA), arg); // keep it GC safe + req->actual = 0; + + result = OS_DO_DEVICE(req, RDC_WRITE); + Init_Blank(CTX_VAR(port, STD_PORT_DATA)); // GC can collect it + + if (result < 0) fail (Error_On_Port(RE_WRITE_ERROR, port, req->error)); + //if (result == DR_DONE) Init_Blank(CTX_VAR(port, STD_PORT_DATA)); + break; } + + case SYM_OPEN: { + INCLUDE_PARAMS_OF_OPEN; + + UNUSED(PAR(spec)); + if (REF(new)) + fail (Error_Bad_Refines_Raw()); + if (REF(read)) + fail (Error_Bad_Refines_Raw()); + if (REF(write)) + fail (Error_Bad_Refines_Raw()); + if (REF(seek)) + fail (Error_Bad_Refines_Raw()); + if (REF(allow)) { + UNUSED(ARG(access)); + fail (Error_Bad_Refines_Raw()); + } + + if (OS_DO_DEVICE(req, RDC_OPEN)) + fail (Error_On_Port(RE_CANNOT_OPEN, port, req->error)); + break; } + + case SYM_CLOSE: + OS_DO_DEVICE(req, RDC_CLOSE); + break; + + case SYM_OPEN_Q: + if (IS_OPEN(req)) return R_TRUE; + return R_FALSE; + + default: + fail (Error_Illegal_Action(REB_PORT, action)); + } + + Move_Value(D_OUT, D_ARG(1)); // port + return R_OUT; } -/*********************************************************************** -** -*/ void Init_Clipboard_Scheme(void) -/* -***********************************************************************/ +// +// get-clipboard-actor-handle: native [ +// +// {Retrieve handle to the native actor for clipboard} +// +// return: [handle!] +// ] +// +REBNATIVE(get_clipboard_actor_handle) { - Register_Scheme(SYM_CLIPBOARD, 0, Clipboard_Actor); + Make_Port_Actor_Handle(D_OUT, &Clipboard_Actor); + return R_OUT; } diff --git a/src/core/p-console.c b/src/core/p-console.c index 23ae491226..a35af87696 100644 --- a/src/core/p-console.c +++ b/src/core/p-console.c @@ -1,31 +1,32 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: p-console.c -** Summary: console port interface -** Section: ports -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %p-console.c +// Summary: "console port interface" +// Section: ports +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" @@ -39,108 +40,99 @@ #define MAKE_OS_BUFFER Make_Binary #endif -/*********************************************************************** -** -*/ static int Console_Actor(REBVAL *ds, REBSER *port, REBCNT action) -/* -***********************************************************************/ +// +// Console_Actor: C +// +static REB_R Console_Actor(REBFRM *frame_, REBCTX *port, REBSYM action) { - REBREQ *req; - REBINT result; - REBVAL *arg = D_ARG(2); - REBSER *ser; - - Validate_Port(port, action); - - arg = D_ARG(2); - *D_RET = *D_ARG(1); - - req = Use_Port_State(port, RDI_STDIO, sizeof(REBREQ)); - - switch (action) { - - case A_READ: - - // If not open, open it: - if (!IS_OPEN(req)) { - if (OS_DO_DEVICE(req, RDC_OPEN)) Trap_Port(RE_CANNOT_OPEN, port, req->error); - } - - // If no buffer, create a buffer: - arg = OFV(port, STD_PORT_DATA); - if (!IS_STRING(arg) && !IS_BINARY(arg)) { - Set_Binary(arg, MAKE_OS_BUFFER(OUT_BUF_SIZE)); - } - ser = VAL_SERIES(arg); - RESET_SERIES(ser); - - req->data = BIN_HEAD(ser); - req->length = SERIES_AVAIL(ser); - -#ifdef nono - // Is the buffer large enough? - req->length = SERIES_AVAIL(ser); // space available - if (req->length < OUT_BUF_SIZE/2) Extend_Series(ser, OUT_BUF_SIZE); - req->length = SERIES_AVAIL(ser); - - // Don't make buffer too large: Bug #174 ????? - if (req->length > 1024) req->length = 1024; //??? - req->data = STR_TAIL(ser); // write at tail //??? - if (SERIES_TAIL(ser) == 0) req->actual = 0; //??? -#endif - - result = OS_DO_DEVICE(req, RDC_READ); - if (result < 0) Trap_Port(RE_READ_ERROR, port, req->error); - -#ifdef nono - // Does not belong here!! - // Remove or replace CRs: - result = 0; - for (n = 0; n < req->actual; n++) { - chr = GET_ANY_CHAR(ser, n); - if (chr == CR) { - chr = LF; - // Skip LF if it follows: - if ((n+1) < req->actual && - LF == GET_ANY_CHAR(ser, n+1)) n++; - } - SET_ANY_CHAR(ser, result, chr); - result++; - } -#endif - // Another copy??? - //Set_String(ds, Copy_OS_Str((void *)(ser->data), result)); - Set_Binary(ds, Copy_Bytes(req->data, req->actual)); - break; - - case A_OPEN: - // ?? why??? - //if (OS_DO_DEVICE(req, RDC_OPEN)) Trap_Port(RE_CANNOT_OPEN, port); - SET_OPEN(req); - break; - - case A_CLOSE: - SET_CLOSED(req); - //OS_DO_DEVICE(req, RDC_CLOSE); - break; - - case A_OPENQ: - if (IS_OPEN(req)) return R_TRUE; - return R_FALSE; - - default: - Trap_Action(REB_PORT, action); - } - - return R_RET; + REBINT result; + REBVAL *arg; + REBSER *ser; + + arg = D_ARGC > 1 ? D_ARG(2) : NULL; + Move_Value(D_OUT, D_ARG(1)); + + REBREQ *req = Ensure_Port_State(port, RDI_STDIO); + + switch (action) { + + case SYM_READ: { + INCLUDE_PARAMS_OF_READ; + + UNUSED(PAR(source)); + + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(seek)) { + UNUSED(ARG(index)); + fail (Error_Bad_Refines_Raw()); + } + UNUSED(PAR(string)); // handled in dispatcher + UNUSED(PAR(lines)); // handled in dispatcher + + // If not open, open it: + if (!IS_OPEN(req)) { + if (OS_DO_DEVICE(req, RDC_OPEN)) + fail (Error_On_Port(RE_CANNOT_OPEN, port, req->error)); + } + + // If no buffer, create a buffer: + arg = CTX_VAR(port, STD_PORT_DATA); + if (!IS_STRING(arg) && !IS_BINARY(arg)) { + Init_Binary(arg, Make_Binary(OUT_BUF_SIZE)); + } + ser = VAL_SERIES(arg); + SET_SERIES_LEN(ser, 0); + TERM_SERIES(ser); + + // !!! May be a 2-byte wide series on Windows for wide chars, in + // which case the length is not bytes?? (Can't use BIN_DATA here + // because that asserts width is 1...) + // + req->common.data = SER_DATA_RAW(ser); + req->length = SER_AVAIL(ser); + + result = OS_DO_DEVICE(req, RDC_READ); + if (result < 0) fail (Error_On_Port(RE_READ_ERROR, port, req->error)); + + // !!! Among many confusions in this file, it said "Another copy???" + //Init_String(D_OUT, Copy_OS_Str(ser->data, result)); + Init_Binary(D_OUT, Copy_Bytes(req->common.data, req->actual)); + break; } + + case SYM_OPEN: { + SET_OPEN(req); + break; } + + case SYM_CLOSE: + SET_CLOSED(req); + //OS_DO_DEVICE(req, RDC_CLOSE); + break; + + case SYM_OPEN_Q: + if (IS_OPEN(req)) return R_TRUE; + return R_FALSE; + + default: + fail (Error_Illegal_Action(REB_PORT, action)); + } + + return R_OUT; } -/*********************************************************************** -** -*/ void Init_Console_Scheme(void) -/* -***********************************************************************/ +// +// get-console-actor-handle: native [ +// +// {Retrieve handle to the native actor for console} +// +// return: [handle!] +// ] +// +REBNATIVE(get_console_actor_handle) { - Register_Scheme(SYM_CONSOLE, 0, Console_Actor); + Make_Port_Actor_Handle(D_OUT, &Console_Actor); + return R_OUT; } diff --git a/src/core/p-dir.c b/src/core/p-dir.c index 2984e68fdc..dd0bc2c604 100644 --- a/src/core/p-dir.c +++ b/src/core/p-dir.c @@ -1,31 +1,32 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: p-dir.c -** Summary: file directory port interface -** Section: ports -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %p-dir.c +// Summary: "file directory port interface" +// Section: ports +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" @@ -33,330 +34,332 @@ #define REMOVE_TAIL_SLASH (1<<10) -/*********************************************************************** -** -*/ static int Read_Dir(REBREQ *dir, REBSER *files) -/* -** Provide option to get file info too. -** Provide option to prepend dir path. -** Provide option to use wildcards. -** -***********************************************************************/ +// +// Read_Dir: C +// +// Provide option to get file info too. +// Provide option to prepend dir path. +// Provide option to use wildcards. +// +static int Read_Dir(struct devreq_file *dir, REBARR *files) { - REBINT result; - REBCNT len; - REBSER *fname; - REBSER *name; - REBREQ file; - - RESET_TAIL(files); - CLEARS(&file); - - // Temporary filename storage: - fname = BUF_OS_STR; - file.file.path = (REBCHR*)Reset_Buffer(fname, MAX_FILE_NAME); - - SET_FLAG(dir->modes, RFM_DIR); - - dir->data = (REBYTE*)(&file); - - while ((result = OS_DO_DEVICE(dir, RDC_READ)) == 0 && !GET_FLAG(dir->flags, RRF_DONE)) { - len = LEN_STR(file.file.path); - if (GET_FLAG(file.modes, RFM_DIR)) len++; - name = Copy_OS_Str(file.file.path, len); - if (GET_FLAG(file.modes, RFM_DIR)) - SET_ANY_CHAR(name, name->tail-1, '/'); - Set_Series(REB_FILE, Append_Value(files), name); - } - - if (result < 0 && dir->error != -RFE_OPEN_FAIL - && (FIND_CHR(dir->file.path, '*') || FIND_CHR(dir->file.path, '?'))) - result = 0; // no matches found, but not an error - - return result; + REBINT result; + REBCNT len; + REBSER *fname; + REBSER *name; + struct devreq_file file; + REBREQ *req = AS_REBREQ(dir); + + TERM_ARRAY_LEN(files, 0); + CLEARS(&file); + + // Temporary filename storage; native OS API character size (REBCHR) varies + // + fname = Make_Series(MAX_FILE_NAME, sizeof(REBCHR)); + file.path = SER_HEAD(REBCHR, fname); + + SET_FLAG(req->modes, RFM_DIR); + + req->common.data = cast(REBYTE*, &file); + + while ( + (result = OS_DO_DEVICE(req, RDC_READ)) == 0 + && !GET_FLAG(req->flags, RRF_DONE) + ) { + len = OS_STRLEN(file.path); + if (GET_FLAG(file.devreq.modes, RFM_DIR)) len++; + name = Copy_OS_Str(file.path, len); + if (GET_FLAG(file.devreq.modes, RFM_DIR)) + SET_ANY_CHAR(name, SER_LEN(name) - 1, '/'); + Init_File(Alloc_Tail_Array(files), name); + } + + if (result < 0 && req->error != -RFE_OPEN_FAIL + && ( + OS_STRCHR(dir->path, '*') + || OS_STRCHR(dir->path, '?') + ) + ) { + result = 0; // no matches found, but not an error + } + + Free_Series(fname); + + return result; } -#ifdef REMOVED -// It's problematic. See blog. Moved to mezz. - -/*********************************************************************** -** -*/ REBNATIVE(dirq) -/* -** Refinements: -** /any -- allow * and ? wildcards -** -** Patterns: -** abc/ is true -** abc/*.r is true -** abc/?.r is true -** abc - ask the file system -** -***********************************************************************/ -{ - REBVAL *path = D_ARG(1); - REBINT len; - REBINT i; - REBCNT dot; - REBUNI c; - REBSER *ser = VAL_SERIES(path); - - if (!ANY_STR(path)) return R_FALSE; - - len = (REBINT)VAL_LEN(path); - if (len == 0) return R_FALSE; - - // We cannot tell from above, so we must check it (if file): - if (IS_FILE(path)) { - REBSER *ser; - REBREQ file; - - CLEARS(&file); - ser = Value_To_OS_Path(path); - file.file.path = (REBCHR*)(ser->data); - file.device = RDI_FILE; - len = OS_DO_DEVICE(&file, RDC_QUERY); - FREE_SERIES(ser); - if (len == DR_DONE && GET_FLAG(file.modes, RFM_DIR)) return R_TRUE; - } - - // Search backward for abc/, abc/def, abc/*, etc: - len = (REBINT)VAL_LEN(path); - dot = 0; - for (i = 0; i < len; i++) { - c = GET_ANY_CHAR(ser, VAL_TAIL(path)-1-i); - if (c == '/' || c == '\\') { - if (i == 0 || dot) return R_TRUE; - break; - } - if (c == '.') { - if (i == 0 || dot) dot = 1; - } - else dot = 0; - if ((c == '*' || c == '?') && D_REF(2)) return R_TRUE; - } - - return R_FALSE; -} -#endif - - -/*********************************************************************** -** -*/ static void Init_Dir_Path(REBREQ *dir, REBVAL *path, REBINT wild, REBCNT policy) -/* -** Convert REBOL dir path to file system path. -** On Windows, we will also need to append a * if necessary. -** -** ARGS: -** Wild: -** 0 - no wild cards, path must end in / else error -** 1 - accept wild cards * and ?, and * if need -** -1 - not wild, if path does not end in /, add it -** -***********************************************************************/ +// +// Init_Dir_Path: C +// +// Convert REBOL dir path to file system path. +// On Windows, we will also need to append a * if necessary. +// +// ARGS: +// Wild: +// 0 - no wild cards, path must end in / else error +// 1 - accept wild cards * and ?, and * if need +// -1 - not wild, if path does not end in /, add it +// +static void Init_Dir_Path(struct devreq_file *dir, REBVAL *path, REBINT wild, REBCNT policy) { - REBINT len; - REBSER *ser; - //REBYTE *flags; - - SET_FLAG(dir->modes, RFM_DIR); - - // We depend on To_Local_Path giving us 2 extra chars for / and * - ser = Value_To_OS_Path(path); - len = ser->tail; - dir->file.path = (REBCHR*)(ser->data); - - Secure_Port(SYM_FILE, dir, path, ser); - - if (len == 1 && dir->file.path[0] == '.') { - if (wild > 0) { - dir->file.path[0] = '*'; - dir->file.path[1] = 0; - } - } - else if (len == 2 && dir->file.path[0] == '.' && dir->file.path[1] == '.') { - // Insert * if needed: - if (wild > 0) { - dir->file.path[len++] = '/'; - dir->file.path[len++] = '*'; - dir->file.path[len] = 0; - } - } - else if (dir->file.path[len-1] == '/' || dir->file.path[len-1] == '\\') { - if (policy & REMOVE_TAIL_SLASH) { - dir->file.path[len-1] = 0; - } - else { - // Insert * if needed: - if (wild > 0) { - dir->file.path[len++] = '*'; - dir->file.path[len] = 0; - } - } - } else { - // Path did not end with /, so we better be wild: - if (wild == 0) { - ///OS_FREE(dir->file.path); - Trap1(RE_BAD_FILE_PATH, path); - } - else if (wild < 0) { - dir->file.path[len++] = OS_DIR_SEP; - dir->file.path[len] = 0; - } - } + REBINT len; + REBSER *ser; + //REBYTE *flags; + REBREQ *req = AS_REBREQ(dir); + + SET_FLAG(req->modes, RFM_DIR); + + // We depend on To_Local_Path giving us 2 extra chars for / and * + ser = Value_To_OS_Path(path, TRUE); + len = SER_LEN(ser); + dir->path = SER_HEAD(REBCHR, ser); + + Secure_Port(SYM_FILE, req, path, ser); + + if (len == 1 && OS_CH_EQUAL(dir->path[0], '.')) { + if (wild > 0) { + dir->path[0] = OS_MAKE_CH('*'); + dir->path[1] = OS_MAKE_CH('\0'); + } + } + else if ( + len == 2 + && OS_CH_EQUAL(dir->path[0], '.') + && OS_CH_EQUAL(dir->path[1], '.') + ) { + // Insert * if needed: + if (wild > 0) { + dir->path[len++] = OS_MAKE_CH('/'); + dir->path[len++] = OS_MAKE_CH('*'); + dir->path[len] = OS_MAKE_CH('\0'); + } + } + else if ( + OS_CH_EQUAL(dir->path[len-1], '/') + || OS_CH_EQUAL(dir->path[len-1], '\\') + ) { + if ((policy & REMOVE_TAIL_SLASH) && len > 1) { + dir->path[len-1] = OS_MAKE_CH('\0'); + } + else { + // Insert * if needed: + if (wild > 0) { + dir->path[len++] = OS_MAKE_CH('*'); + dir->path[len] = OS_MAKE_CH('\0'); + } + } + } else { + // Path did not end with /, so we better be wild: + if (wild == 0) { + // !!! Comment said `OS_FREE(dir->path);` (needed?) + fail (Error_Bad_File_Path_Raw(path)); + } + else if (wild < 0) { + dir->path[len++] = OS_MAKE_CH(OS_DIR_SEP); + dir->path[len] = OS_MAKE_CH('\0'); + } + } } -/*********************************************************************** -** -*/ static int Dir_Actor(REBVAL *ds, REBSER *port, REBCNT action) -/* -** Internal port handler for file directories. -** -***********************************************************************/ +// +// Dir_Actor: C +// +// Internal port handler for file directories. +// +static REB_R Dir_Actor(REBFRM *frame_, REBCTX *port, REBSYM action) { - REBVAL *spec; - REBVAL *path; - REBVAL *state; - REBREQ dir; - REBCNT args = 0; - REBINT result; - REBCNT len; - //REBYTE *flags; - - Validate_Port(port, action); - - *D_RET = *D_ARG(1); - CLEARS(&dir); - - // Validate and fetch relevant PORT fields: - spec = BLK_SKIP(port, STD_PORT_SPEC); - if (!IS_OBJECT(spec)) Trap1(RE_INVALID_SPEC, spec); - path = Obj_Value(spec, STD_PORT_SPEC_HEAD_REF); - if (!path) Trap1(RE_INVALID_SPEC, spec); - - if (IS_URL(path)) path = Obj_Value(spec, STD_PORT_SPEC_HEAD_PATH); - else if (!IS_FILE(path)) Trap1(RE_INVALID_SPEC, path); - - state = BLK_SKIP(port, STD_PORT_STATE); // if block, then port is open. - - //flags = Security_Policy(SYM_FILE, path); - - // Get or setup internal state data: - dir.port = port; - dir.device = RDI_FILE; - - switch (action) { - - case A_READ: - //Trap_Security(flags[POL_READ], POL_READ, path); - args = Find_Refines(ds, ALL_READ_REFS); - if (!IS_BLOCK(state)) { // !!! ignores /SKIP and /PART, for now - Init_Dir_Path(&dir, path, 1, POL_READ); - Set_Block(state, Make_Block(7)); // initial guess - result = Read_Dir(&dir, VAL_SERIES(state)); - ///OS_FREE(dir.file.path); - if (result < 0) Trap_Port(RE_CANNOT_OPEN, port, dir.error); - *D_RET = *state; - SET_NONE(state); - } else { - len = VAL_BLK_LEN(state); - // !!? Why does this need to copy the block?? - Set_Block(D_RET, Copy_Block_Values(VAL_SERIES(state), 0, len, TS_STRING)); - } - break; - - case A_CREATE: - //Trap_Security(flags[POL_WRITE], POL_WRITE, path); - if (IS_BLOCK(state)) Trap1(RE_ALREADY_OPEN, path); // already open -create: - Init_Dir_Path(&dir, path, 0, POL_WRITE | REMOVE_TAIL_SLASH); // Sets RFM_DIR too - result = OS_DO_DEVICE(&dir, RDC_CREATE); - ///OS_FREE(dir.file.path); - if (result < 0) Trap1(RE_NO_CREATE, path); - if (action == A_CREATE) return R_ARG2; - SET_NONE(state); - break; - - case A_RENAME: - if (IS_BLOCK(state)) Trap1(RE_ALREADY_OPEN, path); // already open - else { - REBSER *target; - - Init_Dir_Path(&dir, path, 0, POL_WRITE | REMOVE_TAIL_SLASH); // Sets RFM_DIR too - // Convert file name to OS format: - if (!(target = Value_To_OS_Path(D_ARG(2)))) Trap1(RE_BAD_FILE_PATH, D_ARG(2)); - dir.data = BIN_DATA(target); - OS_DO_DEVICE(&dir, RDC_RENAME); - Free_Series(target); - if (dir.error) Trap1(RE_NO_RENAME, path); - } - break; - - case A_DELETE: - //Trap_Security(flags[POL_WRITE], POL_WRITE, path); - SET_NONE(state); - Init_Dir_Path(&dir, path, 0, POL_WRITE); - // !!! add *.r deletion - // !!! add recursive delete (?) - result = OS_DO_DEVICE(&dir, RDC_DELETE); - ///OS_FREE(dir.file.path); - if (result < 0) Trap1(RE_NO_DELETE, path); - return R_ARG2; - - case A_OPEN: - // !! If open fails, what if user does a READ w/o checking for error? - if (IS_BLOCK(state)) Trap1(RE_ALREADY_OPEN, path); // already open - //Trap_Security(flags[POL_READ], POL_READ, path); - args = Find_Refines(ds, ALL_OPEN_REFS); - if (args & AM_OPEN_NEW) goto create; - //if (args & ~AM_OPEN_READ) Trap1(RE_INVALID_SPEC, path); - Set_Block(state, Make_Block(7)); - Init_Dir_Path(&dir, path, 1, POL_READ); - result = Read_Dir(&dir, VAL_SERIES(state)); - ///OS_FREE(dir.file.path); - if (result < 0) Trap_Port(RE_CANNOT_OPEN, port, dir.error); - break; - - case A_OPENQ: - if (IS_BLOCK(state)) return R_TRUE; - return R_FALSE; - - case A_CLOSE: - SET_NONE(state); - break; - - case A_QUERY: - //Trap_Security(flags[POL_READ], POL_READ, path); - SET_NONE(state); - Init_Dir_Path(&dir, path, -1, REMOVE_TAIL_SLASH | POL_READ); - if (OS_DO_DEVICE(&dir, RDC_QUERY) < 0) return R_NONE; - Ret_Query_File(port, &dir, D_RET); - ///OS_FREE(dir.file.path); - break; - - //-- Port Series Actions (only called if opened as a port) - - case A_LENGTHQ: - len = IS_BLOCK(state) ? VAL_BLK_LEN(state) : 0; - SET_INTEGER(D_RET, len); - break; - - default: - Trap_Action(REB_PORT, action); - } - - return R_RET; + REBVAL *spec; + REBVAL *path; + REBVAL *state; + struct devreq_file dir; + REBINT result; + REBCNT len; + //REBYTE *flags; + + Move_Value(D_OUT, D_ARG(1)); + CLEARS(&dir); + + // Validate and fetch relevant PORT fields: + spec = CTX_VAR(port, STD_PORT_SPEC); + if (!IS_OBJECT(spec)) fail (Error_Invalid_Spec_Raw(spec)); + path = Obj_Value(spec, STD_PORT_SPEC_HEAD_REF); + if (!path) fail (Error_Invalid_Spec_Raw(spec)); + + if (IS_URL(path)) path = Obj_Value(spec, STD_PORT_SPEC_HEAD_PATH); + else if (!IS_FILE(path)) fail (Error_Invalid_Spec_Raw(path)); + + state = CTX_VAR(port, STD_PORT_STATE); // if block, then port is open. + + //flags = Security_Policy(SYM_FILE, path); + + // Get or setup internal state data: + dir.devreq.port = port; + dir.devreq.device = RDI_FILE; + + switch (action) { + + case SYM_READ: { + INCLUDE_PARAMS_OF_READ; + + UNUSED(PAR(source)); + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(seek)) { + UNUSED(ARG(index)); + fail (Error_Bad_Refines_Raw()); + } + UNUSED(PAR(string)); // handled in dispatcher + UNUSED(PAR(lines)); // handled in dispatcher + + if (!IS_BLOCK(state)) { // !!! ignores /SKIP and /PART, for now + Init_Dir_Path(&dir, path, 1, POL_READ); + Init_Block(state, Make_Array(7)); // initial guess + result = Read_Dir(&dir, VAL_ARRAY(state)); + if (result < 0) + fail (Error_On_Port(RE_CANNOT_OPEN, port, dir.devreq.error)); + Move_Value(D_OUT, state); + Init_Blank(state); + } + else { + // !!! This copies the strings in the block, shallowly. What is + // the purpose of doing this? Why copy at all? + Init_Block( + D_OUT, + Copy_Array_Core_Managed( + VAL_ARRAY(state), + 0, // at + VAL_SPECIFIER(state), + VAL_ARRAY_LEN_AT(state), // tail + 0, // extra + FALSE, // !deep + TS_STRING // types + ) + ); + } + break; } + + case SYM_CREATE: + if (IS_BLOCK(state)) + fail (Error_Already_Open_Raw(path)); + create: + Init_Dir_Path( + &dir, path, 0, POL_WRITE | REMOVE_TAIL_SLASH + ); // Sets RFM_DIR too + result = OS_DO_DEVICE(&dir.devreq, RDC_CREATE); + if (result < 0) + fail (Error_No_Create_Raw(path)); + if (action == SYM_CREATE) { + Move_Value(D_OUT, D_ARG(1)); + return R_OUT; + } + Init_Blank(state); + break; + + case SYM_RENAME: + if (IS_BLOCK(state)) fail (Error_Already_Open_Raw(path)); + else { + REBSER *target; + + Init_Dir_Path(&dir, path, 0, POL_WRITE | REMOVE_TAIL_SLASH); // Sets RFM_DIR too + // Convert file name to OS format: + if (!(target = Value_To_OS_Path(D_ARG(2), TRUE))) + fail (Error_Bad_File_Path_Raw(D_ARG(2))); + dir.devreq.common.data = BIN_HEAD(target); + OS_DO_DEVICE(&dir.devreq, RDC_RENAME); + Free_Series(target); + if (dir.devreq.error) fail (Error_No_Rename_Raw(path)); + } + break; + + case SYM_DELETE: + //Trap_Security(flags[POL_WRITE], POL_WRITE, path); + Init_Blank(state); + Init_Dir_Path(&dir, path, 0, POL_WRITE); + // !!! add *.r deletion + // !!! add recursive delete (?) + result = OS_DO_DEVICE(&dir.devreq, RDC_DELETE); + ///OS_FREE(dir.file.path); + if (result < 0) fail (Error_No_Delete_Raw(path)); + // !!! Returned D_ARG(2) before, but there is no second argument :-/ + Move_Value(D_OUT, D_ARG(1)); + return R_OUT; + + case SYM_OPEN: { + INCLUDE_PARAMS_OF_OPEN; + + UNUSED(PAR(spec)); + if (REF(read)) + fail (Error_Bad_Refines_Raw()); + if (REF(write)) + fail (Error_Bad_Refines_Raw()); + if (REF(seek)) + fail (Error_Bad_Refines_Raw()); + if (REF(allow)) { + UNUSED(ARG(access)); + fail (Error_Bad_Refines_Raw()); + } + + // !! If open fails, what if user does a READ w/o checking for error? + if (IS_BLOCK(state)) + fail (Error_Already_Open_Raw(path)); + + if (REF(new)) + goto create; + + Init_Block(state, Make_Array(7)); + Init_Dir_Path(&dir, path, 1, POL_READ); + result = Read_Dir(&dir, VAL_ARRAY(state)); + + if (result < 0) + fail (Error_On_Port(RE_CANNOT_OPEN, port, dir.devreq.error)); + break; } + + case SYM_OPEN_Q: + if (IS_BLOCK(state)) return R_TRUE; + return R_FALSE; + + case SYM_CLOSE: + Init_Blank(state); + break; + + case SYM_QUERY: + //Trap_Security(flags[POL_READ], POL_READ, path); + Init_Blank(state); + Init_Dir_Path(&dir, path, -1, REMOVE_TAIL_SLASH | POL_READ); + if (OS_DO_DEVICE(&dir.devreq, RDC_QUERY) < 0) return R_BLANK; + Ret_Query_File(port, &dir, D_OUT); + ///OS_FREE(dir.file.path); + break; + + //-- Port Series Actions (only called if opened as a port) + + case SYM_LENGTH_OF: + len = IS_BLOCK(state) ? VAL_ARRAY_LEN_AT(state) : 0; + Init_Integer(D_OUT, len); + break; + + default: + fail (Error_Illegal_Action(REB_PORT, action)); + } + + return R_OUT; } -/*********************************************************************** -** -*/ void Init_Dir_Scheme(void) -/* -***********************************************************************/ +// +// get-dir-actor-handle: native [ +// +// {Retrieve handle to the native actor for directories} +// +// return: [handle!] +// ] +// +REBNATIVE(get_dir_actor_handle) { - Register_Scheme(SYM_DIR, 0, Dir_Actor); + Make_Port_Actor_Handle(D_OUT, &Dir_Actor); + return R_OUT; } diff --git a/src/core/p-dns.c b/src/core/p-dns.c index 1367074719..d270146baf 100644 --- a/src/core/p-dns.c +++ b/src/core/p-dns.c @@ -1,144 +1,210 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: p-dns.c -** Summary: DNS port interface -** Section: ports -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %p-dns.c +// Summary: "DNS port interface" +// Section: ports +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" #include "reb-net.h" -/*********************************************************************** -** -*/ static int DNS_Actor(REBVAL *ds, REBSER *port, REBCNT action) -/* -***********************************************************************/ +// +// DNS_Actor: C +// +static REB_R DNS_Actor(REBFRM *frame_, REBCTX *port, REBSYM action) { - REBVAL *spec; - REBREQ *sock; - REBINT result; - REBVAL *arg; - REBCNT len; - REBOOL sync = FALSE; // act synchronously - REBVAL tmp; - - Validate_Port(port, action); - - arg = D_ARG(2); - *D_RET = *D_ARG(1); - - sock = Use_Port_State(port, RDI_DNS, sizeof(*sock)); - spec = OFV(port, STD_PORT_SPEC); - if (!IS_OBJECT(spec)) Trap0(RE_INVALID_PORT); - - sock->timeout = 4000; // where does this go? !!! - - switch (action) { - - case A_READ: - if (!IS_OPEN(sock)) { - if (OS_DO_DEVICE(sock, RDC_OPEN)) Trap_Port(RE_CANNOT_OPEN, port, sock->error); - sync = TRUE; - } - - arg = Obj_Value(spec, STD_PORT_SPEC_NET_HOST); - - if (IS_TUPLE(arg) && Scan_Tuple(VAL_BIN(arg), strlen(VAL_BIN(arg)), &tmp)) { - SET_FLAG(sock->modes, RST_REVERSE); - memcpy(&sock->net.remote_ip, VAL_TUPLE(&tmp), 4); - } - else if (IS_STRING(arg)) { - sock->data = VAL_BIN(arg); - } - else Trap_Port(RE_INVALID_SPEC, port, -10); - - result = OS_DO_DEVICE(sock, RDC_READ); - if (result < 0) Trap_Port(RE_READ_ERROR, port, sock->error); - - // Wait for it... - if (sync && result == DR_PEND) { - for (len = 0; GET_FLAG(sock->flags, RRF_PENDING) && len < 10; len++) { - OS_WAIT(2000, 0); - } - len = 1; - goto pick; - } - if (result == DR_DONE) { - len = 1; - goto pick; - } - break; - - case A_PICK: // FIRST - return result - if (!IS_OPEN(sock)) Trap_Port(RE_NOT_OPEN, port, -12); - len = Get_Num_Arg(arg); // Position -pick: - if (len == 1) { - if (!sock->net.host_info || !GET_FLAG(sock->flags, RRF_DONE)) return R_NONE; - if (sock->error) { - OS_DO_DEVICE(sock, RDC_CLOSE); - Trap_Port(RE_READ_ERROR, port, sock->error); - } - if (GET_FLAG(sock->modes, RST_REVERSE)) { - Set_String(D_RET, Copy_Bytes(sock->data, LEN_BYTES(sock->data))); - } else { - Set_Tuple(D_RET, (REBYTE*)&sock->net.remote_ip, 4); - } - OS_DO_DEVICE(sock, RDC_CLOSE); - } else Trap_Range(arg); - break; - - case A_OPEN: - if (OS_DO_DEVICE(sock, RDC_OPEN)) Trap_Port(RE_CANNOT_OPEN, port, -12); - break; - - case A_CLOSE: - OS_DO_DEVICE(sock, RDC_CLOSE); - break; - - case A_OPENQ: - if (IS_OPEN(sock)) return R_TRUE; - return R_FALSE; - - case A_UPDATE: - return R_NONE; - - default: - Trap_Action(REB_PORT, action); - } - - return R_RET; + REBVAL *spec; + REBINT result; + REBVAL *arg; + REBCNT len; + REBOOL sync = FALSE; // act synchronously + + arg = D_ARGC > 1 ? D_ARG(2) : NULL; + Move_Value(D_OUT, D_ARG(1)); + + REBREQ *sock = Ensure_Port_State(port, RDI_DNS); + spec = CTX_VAR(port, STD_PORT_SPEC); + if (!IS_OBJECT(spec)) fail (Error_Invalid_Port_Raw()); + + sock->timeout = 4000; // where does this go? !!! + + switch (action) { + + case SYM_READ: { + INCLUDE_PARAMS_OF_READ; + + UNUSED(PAR(source)); + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + + if (REF(seek)) { + UNUSED(ARG(index)); + fail (Error_Bad_Refines_Raw()); + } + + UNUSED(PAR(string)); // handled in dispatcher + UNUSED(PAR(lines)); // handled in dispatcher + + if (!IS_OPEN(sock)) { + if (OS_DO_DEVICE(sock, RDC_OPEN)) + fail (Error_On_Port(RE_CANNOT_OPEN, port, sock->error)); + sync = TRUE; + } + + arg = Obj_Value(spec, STD_PORT_SPEC_NET_HOST); + + // A DNS read e.g. of `read dns://66.249.66.140` should do a reverse + // lookup. The scheme handler may pass in either a TUPLE! or a string + // that scans to a tuple, at this time (currently uses a string) + // + if (IS_TUPLE(arg)) { + SET_FLAG(sock->modes, RST_REVERSE); + memcpy(&(DEVREQ_NET(sock)->remote_ip), VAL_TUPLE(arg), 4); + } + else if (IS_STRING(arg)) { + REBCNT index = VAL_INDEX(arg); + REBCNT len = VAL_LEN_AT(arg); + REBSER *utf8 = Temp_Bin_Str_Managed(arg, &index, &len); + + DECLARE_LOCAL (tmp); + if (Scan_Tuple(tmp, BIN_AT(utf8, index), len) != NULL) { + SET_FLAG(sock->modes, RST_REVERSE); + memcpy(&(DEVREQ_NET(sock)->remote_ip), VAL_TUPLE(tmp), 4); + } + else + sock->common.data = VAL_BIN(arg); // lookup string's IP address + } + else + fail (Error_On_Port(RE_INVALID_SPEC, port, -10)); + + result = OS_DO_DEVICE(sock, RDC_READ); + if (result < 0) + fail (Error_On_Port(RE_READ_ERROR, port, sock->error)); + + if (sync && result == DR_PEND) { + assert(FALSE); // asynchronous R3-Alpha DNS code removed + len = 0; + for (; GET_FLAG(sock->flags, RRF_PENDING) && len < 10; ++len) { + OS_WAIT(2000, 0); + } + len = 1; + goto pick; + } + if (result == DR_DONE) { + len = 1; + goto pick; + } + break; } + + case SYM_PICK_P: // FIRST - return result + if (!IS_OPEN(sock)) + fail (Error_On_Port(RE_NOT_OPEN, port, -12)); + + len = Get_Num_From_Arg(arg); // Position + pick: + if (len != 1) + fail (Error_Out_Of_Range(arg)); + + assert(GET_FLAG(sock->flags, RRF_DONE)); // R3-Alpha async DNS removed + + if (sock->error) { + OS_DO_DEVICE(sock, RDC_CLOSE); + fail (Error_On_Port(RE_READ_ERROR, port, sock->error)); + } + + if (DEVREQ_NET(sock)->host_info == NULL) { + Init_Blank(D_OUT); // HOST_NOT_FOUND or NO_ADDRESS blank vs. error + return R_OUT; // READ action currently required to use R_OUTs + } + + if (GET_FLAG(sock->modes, RST_REVERSE)) { + Init_String( + D_OUT, + Copy_Bytes(sock->common.data, LEN_BYTES(sock->common.data)) + ); + } + else { + Set_Tuple(D_OUT, cast(REBYTE*, &DEVREQ_NET(sock)->remote_ip), 4); + } + OS_DO_DEVICE(sock, RDC_CLOSE); + break; + + case SYM_OPEN: { + INCLUDE_PARAMS_OF_OPEN; + + UNUSED(PAR(spec)); + if (REF(new)) + fail (Error_Bad_Refines_Raw()); + if (REF(read)) + fail (Error_Bad_Refines_Raw()); + if (REF(write)) + fail (Error_Bad_Refines_Raw()); + if (REF(seek)) + fail (Error_Bad_Refines_Raw()); + if (REF(allow)) { + UNUSED(ARG(access)); + fail (Error_Bad_Refines_Raw()); + } + + if (OS_DO_DEVICE(sock, RDC_OPEN)) + fail (Error_On_Port(RE_CANNOT_OPEN, port, -12)); + break; } + + case SYM_CLOSE: + OS_DO_DEVICE(sock, RDC_CLOSE); + break; + + case SYM_OPEN_Q: + if (IS_OPEN(sock)) return R_TRUE; + return R_FALSE; + + case SYM_UPDATE: + return R_BLANK; + + default: + fail (Error_Illegal_Action(REB_PORT, action)); + } + + return R_OUT; } -/*********************************************************************** -** -*/ void Init_DNS_Scheme(void) -/* -***********************************************************************/ +// +// get-dns-actor-handle: native [ +// +// {Retrieve handle to the native actor for DNS} +// +// return: [handle!] +// ] +// +REBNATIVE(get_dns_actor_handle) { - Register_Scheme(SYM_DNS, 0, DNS_Actor); + Make_Port_Actor_Handle(D_OUT, &DNS_Actor); + return R_OUT; } diff --git a/src/core/p-event.c b/src/core/p-event.c index 0a32a1f162..e5cc1657dd 100644 --- a/src/core/p-event.c +++ b/src/core/p-event.c @@ -1,202 +1,278 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: p-event.c -** Summary: event port interface -** Section: ports -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %p-event.c +// Summary: "event port interface" +// Section: ports +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// /* Basics: - Ports use requests to control devices. - Devices do their best, and return when no more is possible. - Progs call WAIT to check if devices have changed. - If devices changed, modifies request, and sends event. - If no devices changed, timeout happens. - On REBOL side, we scan event queue. - If we find an event, we call its port/awake function. + Ports use requests to control devices. + Devices do their best, and return when no more is possible. + Progs call WAIT to check if devices have changed. + If devices changed, modifies request, and sends event. + If no devices changed, timeout happens. + On REBOL side, we scan event queue. + If we find an event, we call its port/awake function. - Different cases exist: + Different cases exist: - 1. wait for time only + 1. wait for time only - 2. wait for ports and time. Need a master wait list to - merge with the list provided this function. + 2. wait for ports and time. Need a master wait list to + merge with the list provided this function. - 3. wait for windows to close - check each time we process - a close event. + 3. wait for windows to close - check each time we process + a close event. - 4. what to do on console ESCAPE interrupt? Can use catch it? + 4. what to do on console ESCAPE interrupt? Can use catch it? - 5. how dow we relate events back to their ports? + 5. how dow we relate events back to their ports? - 6. async callbacks + 6. async callbacks */ #include "sys-core.h" -REBREQ *req; //!!! move this global - - -/*********************************************************************** -** -*/ REBVAL *Append_Event() -/* -** Append an event to the end of the current event port queue. -** Return a pointer to the event value. -** -** Note: this function may be called from out of environment, -** so do NOT extend the event queue here. If it does not have -** space, return 0. (Should it overwrite or wrap???) -** -***********************************************************************/ +REBREQ *req; //!!! move this global + +#define EVENTS_LIMIT 0xFFFF //64k +#define EVENTS_CHUNK 128 + +// +// Append_Event: C +// +// Append an event to the end of the current event port queue. +// Return a pointer to the event value. +// +// Note: this function may be called from out of environment, +// so do NOT extend the event queue here. If it does not have +// space, return 0. (Should it overwrite or wrap???) +// +REBVAL *Append_Event(void) { - REBVAL *port; - REBVAL *value; - REBVAL *state; + REBVAL *port = Get_System(SYS_PORTS, PORTS_SYSTEM); + if (!IS_PORT(port)) return 0; // verify it is a port object - port = Get_System(SYS_PORTS, PORTS_SYSTEM); - if (!IS_PORT(port)) return 0; // verify it is a port object + // Get queue block: + REBVAL *state = VAL_CONTEXT_VAR(port, STD_PORT_STATE); + if (!IS_BLOCK(state)) return 0; - // Get queue block: - state = VAL_BLK_SKIP(port, STD_PORT_STATE); - if (!IS_BLOCK(state)) return 0; + // Append to tail if room: + if (SER_FULL(VAL_SERIES(state))) { + if (VAL_LEN_HEAD(state) > EVENTS_LIMIT) + panic (state); - // Append to tail if room: - if (SERIES_FULL(VAL_SERIES(state))) Crash(RP_MAX_EVENTS); - VAL_TAIL(state)++; - value = VAL_BLK_TAIL(state); - SET_END(value); - value--; - SET_NONE(value); + Extend_Series(VAL_SERIES(state), EVENTS_CHUNK); + } + TERM_ARRAY_LEN(VAL_ARRAY(state), VAL_LEN_HEAD(state) + 1); - //Dump_Series(VAL_SERIES(state), "state"); - //Print("Tail: %d %d", VAL_TAIL(state), nn++); + REBVAL *value = SINK(ARR_LAST(VAL_ARRAY(state))); + Init_Blank(value); - return value; + return value; } -/*********************************************************************** -** -*/ static int Event_Actor(REBVAL *ds, REBSER *port, REBCNT action) -/* -** Internal port handler for events. -** -***********************************************************************/ +// +// Find_Last_Event: C +// +// Find the last event in the queue by the model +// Check its type, if it matches, then return the event or NULL +// +REBVAL *Find_Last_Event(REBINT model, REBINT type) { - REBVAL *spec; - REBVAL *state; - REBCNT result; - REBVAL *arg; - REBVAL save_port; - - Validate_Port(port, action); - - arg = D_ARG(2); - *D_RET = *D_ARG(1); - - // Validate and fetch relevant PORT fields: - state = BLK_SKIP(port, STD_PORT_STATE); - spec = BLK_SKIP(port, STD_PORT_SPEC); - if (!IS_OBJECT(spec)) Trap1(RE_INVALID_SPEC, spec); - - // Get or setup internal state data: - if (!IS_BLOCK(state)) Set_Block(state, Make_Block(127)); - - switch (action) { - - case A_UPDATE: - return R_NONE; - - // Normal block actions done on events: - case A_POKE: - if (!IS_EVENT(D_ARG(3))) Trap_Arg(D_ARG(3)); - goto act_blk; - case A_INSERT: - case A_APPEND: - //case A_PATH: // not allowed: port/foo is port object field access - //case A_PATH_SET: // not allowed: above - if (!IS_EVENT(arg)) Trap_Arg(arg); - case A_PICK: + REBVAL *port; + RELVAL *value; + REBVAL *state; + + port = Get_System(SYS_PORTS, PORTS_SYSTEM); + if (!IS_PORT(port)) return NULL; // verify it is a port object + + // Get queue block: + state = VAL_CONTEXT_VAR(port, STD_PORT_STATE); + if (!IS_BLOCK(state)) return NULL; + + value = VAL_ARRAY_TAIL(state) - 1; + for (; value >= VAL_ARRAY_HEAD(state); --value) { + if (VAL_EVENT_MODEL(value) == model) { + if (VAL_EVENT_TYPE(value) == type) { + return KNOWN(value); + } else { + return NULL; + } + } + } + + return NULL; +} + +// +// Event_Actor: C +// +// Internal port handler for events. +// +static REB_R Event_Actor(REBFRM *frame_, REBCTX *port, REBSYM action) +{ + REBVAL *spec; + REBVAL *state; + REB_R result; + REBVAL *arg; + + DECLARE_LOCAL (save_port); + + arg = D_ARGC > 1 ? D_ARG(2) : NULL; + Move_Value(D_OUT, D_ARG(1)); + + // Validate and fetch relevant PORT fields: + state = CTX_VAR(port, STD_PORT_STATE); + spec = CTX_VAR(port, STD_PORT_SPEC); + if (!IS_OBJECT(spec)) fail (Error_Invalid_Spec_Raw(spec)); + + // Get or setup internal state data: + if (!IS_BLOCK(state)) + Init_Block(state, Make_Array(EVENTS_CHUNK - 1)); + + switch (action) { + + case SYM_UPDATE: + return R_BLANK; + + // Normal block actions done on events: + case SYM_POKE: + if (!IS_EVENT(D_ARG(3))) + fail (D_ARG(3)); + goto act_blk; + case SYM_INSERT: + case SYM_APPEND: + //case A_PATH: // not allowed: port/foo is port object field access + //case A_PATH_SET: // not allowed: above + if (!IS_EVENT(arg)) + fail (arg); + // falls through + case SYM_PICK_P: act_blk: - save_port = *D_ARG(1); // save for return - *D_ARG(1) = *state; - result = T_Block(ds, action); - SET_SIGNAL(SIG_EVENT_PORT); - if (action == A_INSERT || action == A_APPEND || action == A_REMOVE) { - *D_RET = save_port; - break; - } - return result; // return condition - - case A_CLEAR: - VAL_TAIL(state) = 0; - VAL_BLK_TERM(state); - CLR_SIGNAL(SIG_EVENT_PORT); - break; - - case A_LENGTHQ: - SET_INTEGER(D_RET, VAL_TAIL(state)); - break; - - case A_OPEN: - if (!req) { //!!! - req = OS_MAKE_DEVREQ(RDI_EVENT); - if (req) { - SET_OPEN(req); - OS_DO_DEVICE(req, RDC_CONNECT); // stays queued - } - } - break; - - case A_CLOSE: - OS_ABORT_DEVICE(req); - OS_DO_DEVICE(req, RDC_CLOSE); - // free req!!! - SET_CLOSED(req); - req = 0; - break; - - case A_FIND: // add it - - default: - Trap_Action(REB_PORT, action); - } - - return R_RET; + Move_Value(save_port, D_ARG(1)); // save for return + Move_Value(D_ARG(1), state); + result = T_Array(frame_, action); + SET_SIGNAL(SIG_EVENT_PORT); + if ( + action == SYM_INSERT + || action == SYM_APPEND + || action == SYM_REMOVE + ){ + Move_Value(D_OUT, save_port); + break; + } + return result; // return condition + + case SYM_CLEAR: + TERM_ARRAY_LEN(VAL_ARRAY(state), 0); + CLR_SIGNAL(SIG_EVENT_PORT); + break; + + case SYM_LENGTH_OF: + Init_Integer(D_OUT, VAL_LEN_HEAD(state)); + break; + + case SYM_OPEN: { + INCLUDE_PARAMS_OF_OPEN; + + UNUSED(PAR(spec)); + if (REF(new)) + fail (Error_Bad_Refines_Raw()); + if (REF(read)) + fail (Error_Bad_Refines_Raw()); + if (REF(write)) + fail (Error_Bad_Refines_Raw()); + if (REF(seek)) + fail (Error_Bad_Refines_Raw()); + if (REF(allow)) { + UNUSED(ARG(access)); + fail (Error_Bad_Refines_Raw()); + } + + if (!req) { //!!! + req = OS_MAKE_DEVREQ(RDI_EVENT); + if (req) { + SET_OPEN(req); + OS_DO_DEVICE(req, RDC_CONNECT); // stays queued + } + } + break; } + + case SYM_CLOSE: + OS_ABORT_DEVICE(req); + OS_DO_DEVICE(req, RDC_CLOSE); + // free req!!! + SET_CLOSED(req); + req = 0; + break; + + case SYM_FIND: // add it + + default: + fail (Error_Illegal_Action(REB_PORT, action)); + } + + return R_OUT; } -/*********************************************************************** -** -*/ void Init_Event_Scheme(void) -/* -***********************************************************************/ +// +// Startup_Event_Scheme: C +// +void Startup_Event_Scheme(void) +{ + req = 0; // move to port struct +} + + +// +// Shutdown_Event_Scheme: C +// +void Shutdown_Event_Scheme(void) +{ + if (req) { + OS_FREE(req); + req = NULL; + } +} + + +// +// get-event-actor-handle: native [ +// +// {Retrieve handle to the native actor for events (system, event, callback)} +// +// return: [handle!] +// ] +// +REBNATIVE(get_event_actor_handle) { - req = 0; // move to port struct - Register_Scheme(SYM_SYSTEM, 0, Event_Actor); - Register_Scheme(SYM_EVENT, 0, Event_Actor); - Register_Scheme(SYM_CALLBACK, 0, Event_Actor); + Make_Port_Actor_Handle(D_OUT, &Event_Actor); + return R_OUT; } diff --git a/src/core/p-file.c b/src/core/p-file.c index 5532746c48..a3035aaf9b 100644 --- a/src/core/p-file.c +++ b/src/core/p-file.c @@ -1,31 +1,32 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: p-file.c -** Summary: file port interface -** Section: ports -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %p-file.c +// Summary: "file port interface" +// Section: ports +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" @@ -36,107 +37,113 @@ #define MAX_READ_MASK 0x7FFFFFFF // max size per chunk -/*********************************************************************** -** -*/ static void Setup_File(REBREQ *file, REBCNT args, REBVAL *path) -/* -** Convert native action refinements to file modes. -** -***********************************************************************/ +// +// Setup_File: C +// +// Convert native action refinements to file modes. +// +static void Setup_File(struct devreq_file *file, REBFLGS flags, REBVAL *path) { - REBSER *ser; + REBSER *ser; + REBREQ *req = AS_REBREQ(file); - if (args & AM_OPEN_WRITE) SET_FLAG(file->modes, RFM_WRITE); - if (args & AM_OPEN_READ) SET_FLAG(file->modes, RFM_READ); - if (args & AM_OPEN_SEEK) SET_FLAG(file->modes, RFM_SEEK); + if (flags & AM_OPEN_WRITE) SET_FLAG(req->modes, RFM_WRITE); + if (flags & AM_OPEN_READ) SET_FLAG(req->modes, RFM_READ); + if (flags & AM_OPEN_SEEK) SET_FLAG(req->modes, RFM_SEEK); - if (args & AM_OPEN_NEW) { - SET_FLAG(file->modes, RFM_NEW); - if (!(args & AM_OPEN_WRITE)) Trap1(RE_BAD_FILE_MODE, path); - } + if (flags & AM_OPEN_NEW) { + SET_FLAG(req->modes, RFM_NEW); + if (NOT(flags & AM_OPEN_WRITE)) + fail (Error_Bad_File_Mode_Raw(path)); + } - // Convert file name to OS format, let it GC later. - if (!(ser = Value_To_OS_Path(path))) - Trap1(RE_BAD_FILE_PATH, path); - - file->file.path = (REBCHR*)(ser->data); + if (!(ser = Value_To_OS_Path(path, TRUE))) + fail (Error_Bad_File_Path_Raw(path)); - SET_FLAG(file->modes, RFM_NAME_MEM); + // !!! Original comment said "Convert file name to OS format, let + // it GC later." Then it grabs the series data from inside of it. + // It's not clear what lifetime req->file.path is supposed to have, + // and saying "good until whenever the GC runs" is not rigorous. + // The series should be kept manual and freed when the data is + // no longer used, or the managed series saved in a GC-safe place + // as long as the bytes are needed. + // + MANAGE_SERIES(ser); - Secure_Port(SYM_FILE, file, path, ser); -} + file->path = SER_HEAD(REBCHR, ser); + SET_FLAG(req->modes, RFM_NAME_MEM); -/*********************************************************************** -** -*/ static void Cleanup_File(REBREQ *file) -/* -***********************************************************************/ -{ - if (GET_FLAG(file->modes, RFM_NAME_MEM)) { - //NOTE: file->file.path will get GC'd - file->file.path = 0; - CLR_FLAG(file->modes, RFM_NAME_MEM); - } - SET_CLOSED(file); + Secure_Port(SYM_FILE, req, path, ser); } -/*********************************************************************** -** -*/ static void Set_File_Date(REBREQ *file, REBVAL *val) -/* -** Set a value with the UTC date of a file. -** -***********************************************************************/ +// +// Cleanup_File: C +// +static void Cleanup_File(struct devreq_file *file) { - REBOL_DAT dat; - - OS_FILE_TIME(file, &dat); - Set_Date(val, &dat); + REBREQ *req = AS_REBREQ(file); + + if (GET_FLAG(req->modes, RFM_NAME_MEM)) { + //NOTE: file->path will get GC'd + file->path = 0; + CLR_FLAG(req->modes, RFM_NAME_MEM); + } + SET_CLOSED(req); } -/*********************************************************************** -** -*/ void Ret_Query_File(REBSER *port, REBREQ *file, REBVAL *ret) -/* -** Query file and set RET value to resulting STD_FILE_INFO object. -** -***********************************************************************/ +// +// Ret_Query_File: C +// +// Query file and set RET value to resulting STD_FILE_INFO object. +// +void Ret_Query_File(REBCTX *port, struct devreq_file *file, REBVAL *ret) { - REBVAL *info = In_Object(port, STD_PORT_SCHEME, STD_SCHEME_INFO, 0); - REBSER *obj; - REBSER *ser; + REBREQ *req = AS_REBREQ(file); - if (!info || !IS_OBJECT(info)) Trap_Port(RE_INVALID_SPEC, port, -10); + REBVAL *info = In_Object(port, STD_PORT_SCHEME, STD_SCHEME_INFO, 0); - obj = CLONE_OBJECT(VAL_OBJ_FRAME(info)); + if (!info || !IS_OBJECT(info)) + fail (Error_On_Port(RE_INVALID_SPEC, port, -10)); - SET_OBJECT(ret, obj); - Init_Word(OFV(obj, STD_FILE_INFO_TYPE), GET_FLAG(file->modes, RFM_DIR) ? SYM_DIR : SYM_FILE); - SET_INTEGER(OFV(obj, STD_FILE_INFO_SIZE), file->file.size); - Set_File_Date(file, OFV(obj, STD_FILE_INFO_DATE)); + REBCTX *context = Copy_Context_Shallow(VAL_CONTEXT(info)); - ser = To_REBOL_Path(file->file.path, 0, OS_WIDE, 0); + Init_Object(ret, context); + Init_Word( + CTX_VAR(context, STD_FILE_INFO_TYPE), + GET_FLAG(req->modes, RFM_DIR) ? Canon(SYM_DIR) : Canon(SYM_FILE) + ); + Init_Integer( + CTX_VAR(context, STD_FILE_INFO_SIZE), file->size + ); + OS_FILE_TIME(CTX_VAR(context, STD_FILE_INFO_DATE), file); - Set_Series(REB_FILE, OFV(obj, STD_FILE_INFO_NAME), ser); + REBSER *ser = To_REBOL_Path( + file->path, 0, (OS_WIDE ? PATH_OPT_UNI_SRC : 0) + ); + + Init_File(CTX_VAR(context, STD_FILE_INFO_NAME), ser); } -/*********************************************************************** -** -*/ static void Open_File_Port(REBSER *port, REBREQ *file, REBVAL *path) -/* -** Open a file port. -** -***********************************************************************/ +// +// Open_File_Port: C +// +// Open a file port. +// +static void Open_File_Port(REBCTX *port, struct devreq_file *file, REBVAL *path) { - if (Is_Port_Open(port)) Trap1(RE_ALREADY_OPEN, path); + REBREQ *req = AS_REBREQ(file); + + if (Is_Port_Open(port)) + fail (Error_Already_Open_Raw(path)); - if (OS_DO_DEVICE(file, RDC_OPEN) < 0) Trap_Port(RE_CANNOT_OPEN, port, file->error); + if (OS_DO_DEVICE(req, RDC_OPEN) < 0) + fail (Error_On_Port(RE_CANNOT_OPEN, port, req->error)); - Set_Port_Open(port, TRUE); + Set_Port_Open(port, TRUE); } @@ -150,445 +157,482 @@ REBINT Mode_Syms[] = { SYM_WORLD_READ, SYM_WORLD_WRITE, SYM_WORLD_EXECUTE, - 0 + 0 }; -/*********************************************************************** -** -*/ static REBCNT Get_Mode_Id(REBVAL *word) -/* -***********************************************************************/ -{ - REBCNT id = 0; - if (IS_WORD(word)) { - id = Find_Int(&Mode_Syms[0], VAL_WORD_CANON(word)); - if (id == NOT_FOUND) Trap_Arg(word); - } - return id; -} +// +// Read_File_Port: C +// +// Read from a file port. +// +static void Read_File_Port( + REBVAL *out, + REBCTX *port, + struct devreq_file *file, + REBVAL *path, + REBFLGS flags, + REBCNT len +) { +#ifdef NDEBUG + UNUSED(path); +#else + assert(IS_FILE(path)); +#endif + UNUSED(flags); + REBREQ *req = AS_REBREQ(file); -/*********************************************************************** -** -*/ static REBCNT Set_Mode_Value(REBREQ *file, REBCNT mode, REBVAL *val) -/* -***********************************************************************/ -{ - return 0; -} + REBSER *ser = Make_Binary(len); // read result buffer + Init_Binary(out, ser); + // Do the read, check for errors: + req->common.data = BIN_HEAD(ser); + req->length = len; + if (OS_DO_DEVICE(req, RDC_READ) < 0) + fail (Error_On_Port(RE_READ_ERROR, port, req->error)); -/*********************************************************************** -** -*/ static void Read_File_Port(REBSER *port, REBREQ *file, REBVAL *path, REBCNT args, REBCNT len) -/* -** Read from a file port. -** -***********************************************************************/ -{ - REBSER *ser; - REBVAL *ds = DS_RETURN; - - // Allocate read result buffer: - ser = Make_Binary(len); - Set_Series(REB_BINARY, ds, ser); //??? what if already set? - - // Do the read, check for errors: - file->data = BIN_HEAD(ser); - file->length = len; - if (OS_DO_DEVICE(file, RDC_READ) < 0) Trap_Port(RE_READ_ERROR, port, file->error); - SERIES_TAIL(ser) = file->actual; - STR_TERM(ser); - - // Convert to string or block of strings. - // NOTE: This code is incorrect for files read in chunks!!! - if (args & (AM_READ_STRING | AM_READ_LINES)) { - ser = Decode_UTF_String(BIN_HEAD(ser), file->actual, -1); - Set_String(ds, ser); - if (args & AM_READ_LINES) Set_Block(ds, Split_Lines(ds)); - } + SET_SERIES_LEN(ser, req->actual); + TERM_SEQUENCE(ser); } -/*********************************************************************** -** -*/ static void Write_File_Port(REBREQ *file, REBVAL *data, REBCNT len, REBCNT args) -/* -***********************************************************************/ +// +// Write_File_Port: C +// +static void Write_File_Port(struct devreq_file *file, REBVAL *data, REBCNT len, REBOOL lines) { - REBSER *ser; - - if (IS_BLOCK(data)) { - // Form the values of the block - // !! Could be made more efficient if we broke the FORM - // into 32K chunks for writing. - REB_MOLD mo = {0}; - Reset_Mold(&mo); - if (args & AM_WRITE_LINES) { - mo.opts = 1 << MOPT_LINES; - } - Mold_Value(&mo, data, 0); - Set_String(data, mo.series); // fall into next section - len = SERIES_TAIL(mo.series); - } - - // Auto convert string to UTF-8 - if (IS_STRING(data)) { - ser = Encode_UTF8_Value(data, len, ENCF_OS_CRLF); - file->data = ser? BIN_HEAD(ser) : VAL_BIN_DATA(data); // No encoding may be needed - len = SERIES_TAIL(ser); - } - else { - file->data = VAL_BIN_DATA(data); - } - file->length = len; - OS_DO_DEVICE(file, RDC_WRITE); + REBSER *ser; + REBREQ *req = AS_REBREQ(file); + + if (IS_BLOCK(data)) { + // Form the values of the block + // !! Could be made more efficient if we broke the FORM + // into 32K chunks for writing. + REB_MOLD mo; + CLEARS(&mo); + Push_Mold(&mo); + if (lines) + mo.opts = 1 << MOPT_LINES; + Mold_Value(&mo, data, FALSE); + Init_String(data, Pop_Molded_String(&mo)); // fall to next section + len = VAL_LEN_HEAD(data); + } + + // Auto convert string to UTF-8 + if (IS_STRING(data)) { + ser = Make_UTF8_From_Any_String(data, len, OPT_ENC_CRLF_MAYBE); + MANAGE_SERIES(ser); + req->common.data = BIN_HEAD(ser); + len = SER_LEN(ser); + } + else { + req->common.data = VAL_BIN_AT(data); + } + req->length = len; + OS_DO_DEVICE(req, RDC_WRITE); } -/*********************************************************************** -** -*/ static REBCNT Set_Length(const REBVAL *ds, const REBREQ *file, const REBCNT arg) -/* -** Computes the length of data based on the argument number -** provided for the ARG_*_PART stack value (which, when there, -** is always followed by the size). -** -** Note: converts 64bit number to 32bit. The requested size -** can never be greater than 4GB. -** -***********************************************************************/ +// +// Set_Length: C +// +// Note: converts 64bit number to 32bit. The requested size +// can never be greater than 4GB. If limit isn't negative it +// constrains the size of the requested read. +// +static REBCNT Set_Length(const struct devreq_file *file, REBI64 limit) { - REBI64 len; // maximum size - REBI64 cnt; - int what_if_it_changed; - - // Compute and bound bytes remaining: - len = file->file.size - file->file.index; // already read - if (len < 0) return 0; - len &= MAX_READ_MASK; // limit the size - - // Return requested length: - if (!D_REF(arg)) return (REBCNT)len; - - // Limit size of requested read: - cnt = VAL_INT64(D_ARG(arg+1)); - if (cnt > len) return (REBCNT)len; - return (REBCNT)cnt; + REBI64 len; + + // Compute and bound bytes remaining: + len = file->size - file->index; // already read + if (len < 0) return 0; + len &= MAX_READ_MASK; // limit the size + + // Return requested length: + if (limit < 0) return (REBCNT)len; + + // Limit size of requested read: + if (limit > len) return cast(REBCNT, len); + return cast(REBCNT, limit); } -/*********************************************************************** -** -*/ static void Set_Seek(REBREQ *file, REBVAL *arg) -/* -** Computes the number of bytes that should be skipped. -** -***********************************************************************/ +// +// Set_Seek: C +// +// Computes the number of bytes that should be skipped. +// +static void Set_Seek(struct devreq_file *file, REBVAL *arg) { - REBI64 cnt; + REBI64 cnt; + REBREQ *req = AS_REBREQ(file); - cnt = Int64s(arg, 0); + cnt = Int64s(arg, 0); - if (cnt > file->file.size) cnt = file->file.size; + if (cnt > file->size) cnt = file->size; - file->file.index = cnt; + file->index = cnt; - SET_FLAG(file->modes, RFM_RESEEK); // force a seek + SET_FLAG(req->modes, RFM_RESEEK); // force a seek } -/*********************************************************************** -** -*/ static int File_Actor(REBVAL *ds, REBSER *port, REBCNT action) -/* -** Internal port handler for files. -** -***********************************************************************/ +// +// File_Actor: C +// +// Internal port handler for files. +// +static REB_R File_Actor(REBFRM *frame_, REBCTX *port, REBSYM action) { - REBVAL *spec; - REBVAL *path; - REBREQ *file = 0; - REBCNT args = 0; - REBCNT len; - REBOOL opened = FALSE; // had to be opened (shortcut case) - - //Print("FILE ACTION: %r", Get_Action_Word(action)); - - Validate_Port(port, action); - - *D_RET = *D_ARG(1); - - // Validate PORT fields: - spec = BLK_SKIP(port, STD_PORT_SPEC); - if (!IS_OBJECT(spec)) Trap1(RE_INVALID_SPEC, spec); - path = Obj_Value(spec, STD_PORT_SPEC_HEAD_REF); - if (!path) Trap1(RE_INVALID_SPEC, spec); - - if (IS_URL(path)) path = Obj_Value(spec, STD_PORT_SPEC_HEAD_PATH); - else if (!IS_FILE(path)) Trap1(RE_INVALID_SPEC, path); - - // Get or setup internal state data: - file = (REBREQ*)Use_Port_State(port, RDI_FILE, sizeof(*file)); - - switch (action) { - - case A_READ: - args = Find_Refines(ds, ALL_READ_REFS); - - // Handle the READ %file shortcut case: - if (!IS_OPEN(file)) { - REBCNT nargs = AM_OPEN_READ; - if (args & AM_READ_SEEK) nargs |= AM_OPEN_SEEK; - Setup_File(file, nargs, path); - Open_File_Port(port, file, path); - opened = TRUE; - } - - if (args & AM_READ_SEEK) Set_Seek(file, D_ARG(ARG_READ_INDEX)); - len = Set_Length(ds, file, ARG_READ_PART); - Read_File_Port(port, file, path, args, len); - - if (opened) { - OS_DO_DEVICE(file, RDC_CLOSE); - Cleanup_File(file); - Free_Port_State(port); - } - - if (file->error) Trap_Port(RE_READ_ERROR, port, file->error); - break; - - case A_APPEND: - file->file.index = file->file.size; - SET_FLAG(file->modes, RFM_RESEEK); - - case A_WRITE: - args = Find_Refines(ds, ALL_WRITE_REFS); - spec = D_ARG(2); // data (binary, string, or block) - - // Handle the READ %file shortcut case: - if (!IS_OPEN(file)) { - REBCNT nargs = AM_OPEN_WRITE; - if (args & AM_WRITE_SEEK || args & AM_WRITE_APPEND) nargs |= AM_OPEN_SEEK; - else nargs |= AM_OPEN_NEW; - Setup_File(file, nargs, path); - Open_File_Port(port, file, path); - opened = TRUE; - } - else { - if (!GET_FLAG(file->modes, RFM_WRITE)) Trap1(RE_READ_ONLY, path); - } - - // Setup for /append or /seek: - if (args & AM_WRITE_APPEND) { - file->file.index = -1; // append - SET_FLAG(file->modes, RFM_RESEEK); - } - if (args & AM_WRITE_SEEK) Set_Seek(file, D_ARG(ARG_WRITE_INDEX)); - - // Determine length. Clip /PART to size of string if needed. - len = VAL_LEN(spec); - if (args & AM_WRITE_PART) { - REBCNT n = Int32s(D_ARG(ARG_WRITE_LENGTH), 0); - if (n <= len) len = n; - } - - Write_File_Port(file, spec, len, args); - - if (opened) { - OS_DO_DEVICE(file, RDC_CLOSE); - Cleanup_File(file); - Free_Port_State(port); - } - - if (file->error) Trap1(RE_WRITE_ERROR, path); - break; - - case A_OPEN: - args = Find_Refines(ds, ALL_OPEN_REFS); - // Default file modes if not specified: - if (!(args & (AM_OPEN_READ | AM_OPEN_WRITE))) args |= (AM_OPEN_READ | AM_OPEN_WRITE); - Setup_File(file, args, path); - Open_File_Port(port, file, path); // !!! needs to change file modes to R/O if necessary - break; - - case A_COPY: - if (!IS_OPEN(file)) Trap1(RE_NOT_OPEN, path); //!!!! wrong msg - len = Set_Length(ds, file, 2); - Read_File_Port(port, file, path, args, len); - break; - - case A_OPENQ: - if (IS_OPEN(file)) return R_TRUE; - return R_FALSE; - - case A_CLOSE: - if (IS_OPEN(file)) { - OS_DO_DEVICE(file, RDC_CLOSE); - Cleanup_File(file); - Free_Port_State(port); - } - break; - - case A_DELETE: - if (IS_OPEN(file)) Trap1(RE_NO_DELETE, path); - Setup_File(file, 0, path); - if (OS_DO_DEVICE(file, RDC_DELETE) < 0 ) Trap1(RE_NO_DELETE, path); - break; - - case A_RENAME: - if (IS_OPEN(file)) Trap1(RE_NO_RENAME, path); - else { - REBSER *target; - - Setup_File(file, 0, path); - - // Convert file name to OS format: - if (!(target = Value_To_OS_Path(D_ARG(2)))) - Trap1(RE_BAD_FILE_PATH, D_ARG(2)); - file->data = BIN_DATA(target); - OS_DO_DEVICE(file, RDC_RENAME); - Free_Series(target); - if (file->error) Trap1(RE_NO_RENAME, path); - } - break; - - case A_CREATE: - // !!! should it leave file open??? - if (!IS_OPEN(file)) { - Setup_File(file, AM_OPEN_WRITE | AM_OPEN_NEW, path); - if (OS_DO_DEVICE(file, RDC_CREATE) < 0) Trap_Port(RE_CANNOT_OPEN, port, file->error); - OS_DO_DEVICE(file, RDC_CLOSE); - } - break; - - case A_QUERY: - if (!IS_OPEN(file)) { - Setup_File(file, 0, path); - if (OS_DO_DEVICE(file, RDC_QUERY) < 0) return R_NONE; - } - Ret_Query_File(port, file, D_RET); - // !!! free file path? - break; - - case A_MODIFY: - Set_Mode_Value(file, Get_Mode_Id(D_ARG(2)), D_ARG(3)); - if (!IS_OPEN(file)) { - Setup_File(file, 0, path); - if (OS_DO_DEVICE(file, RDC_MODIFY) < 0) return R_NONE; - } - return R_TRUE; - break; - - case A_INDEXQ: - SET_INTEGER(D_RET, file->file.index + 1); - break; - - case A_LENGTHQ: - SET_INTEGER(D_RET, file->file.size - file->file.index); // !clip at zero - break; - - case A_HEAD: - file->file.index = 0; - goto seeked; - - case A_TAIL: - file->file.index = file->file.size; - goto seeked; - - case A_NEXT: - file->file.index++; - goto seeked; - - case A_BACK: - if (file->file.index > 0) file->file.index--; - goto seeked; - - case A_SKIP: - file->file.index += Get_Num_Arg(D_ARG(2)); - goto seeked; - - case A_HEADQ: - DECIDE(file->file.index == 0); - - case A_TAILQ: - DECIDE(file->file.index >= file->file.size); - - case A_PASTQ: - DECIDE(file->file.index > file->file.size); - - case A_CLEAR: - // !! check for write enabled? - SET_FLAG(file->modes, RFM_RESEEK); - SET_FLAG(file->modes, RFM_TRUNCATE); - file->length = 0; - if (OS_DO_DEVICE(file, RDC_WRITE) < 0) Trap1(RE_WRITE_ERROR, path); - break; - - /* Not yet implemented: - A_AT, // 38 - A_PICK, // 41 - A_PATH, // 42 - A_PATH_SET, // 43 - A_FIND, // 44 - A_SELECT, // 45 - A_TAKE, // 49 - A_INSERT, // 50 - A_REMOVE, // 52 - A_CHANGE, // 53 - A_POKE, // 54 - A_QUERY, // 64 - A_FLUSH, // 65 - */ - - default: - Trap_Action(REB_PORT, action); - } - - return R_RET; - -seeked: - SET_FLAG(file->modes, RFM_RESEEK); - return R_ARG1; - -is_true: - return R_TRUE; - -is_false: - return R_FALSE; + REBVAL *spec = CTX_VAR(port, STD_PORT_SPEC); + if (!IS_OBJECT(spec)) + fail (Error_Invalid_Spec_Raw(spec)); + + REBVAL *path = Obj_Value(spec, STD_PORT_SPEC_HEAD_REF); + if (!path) + fail (Error_Invalid_Spec_Raw(spec)); + + if (IS_URL(path)) + path = Obj_Value(spec, STD_PORT_SPEC_HEAD_PATH); + else if (!IS_FILE(path)) + fail (Error_Invalid_Spec_Raw(path)); + + REBREQ *req = Ensure_Port_State(port, RDI_FILE); + struct devreq_file *file = DEVREQ_FILE(req); + + // !!! R3-Alpha never implemented quite a number of operations on files, + // including FLUSH, POKE, etc. + + switch (action) { + + case SYM_READ: { + INCLUDE_PARAMS_OF_READ; + + UNUSED(PAR(source)); + UNUSED(PAR(string)); // handled in dispatcher + UNUSED(PAR(lines)); // handled in dispatcher + + REBFLGS flags = 0; + + // Handle the READ %file shortcut case, where the FILE! has been + // converted into a PORT! but has not been opened yet. + + REBOOL opened; + if (IS_OPEN(req)) + opened = FALSE; // was already open + else { + REBCNT nargs = AM_OPEN_READ; + if (REF(seek)) + nargs |= AM_OPEN_SEEK; + Setup_File(file, nargs, path); + Open_File_Port(port, file, path); + opened = TRUE; // had to be opened (shortcut case) + } + + if (REF(seek)) + Set_Seek(file, ARG(index)); + + REBCNT len = Set_Length(file, REF(part) ? VAL_INT64(ARG(limit)) : -1); + Read_File_Port(D_OUT, port, file, path, flags, len); + + if (opened) { + OS_DO_DEVICE(req, RDC_CLOSE); + Cleanup_File(file); + } + + if (req->error) + fail (Error_On_Port(RE_READ_ERROR, port, req->error)); + + return R_OUT; } + + case SYM_APPEND: + // + // !!! This is hacky, but less hacky than falling through to SYM_WRITE + // assuming the frame is the same for APPEND and WRITE (which is what + // R3-Alpha did). Review. + // + return Retrigger_Append_As_Write(frame_); + + case SYM_WRITE: { + INCLUDE_PARAMS_OF_WRITE; + + UNUSED(PAR(destination)); + + if (REF(allow)) { + UNUSED(ARG(access)); + fail (Error_Bad_Refines_Raw()); + } + + REBVAL *data = ARG(data); // binary, string, or block + + // Handle the WRITE %file shortcut case, where the FILE! is converted + // to a PORT! but it hasn't been opened yet. + + REBOOL opened; + if (IS_OPEN(req)) { + if (!GET_FLAG(req->modes, RFM_WRITE)) + fail (Error_Read_Only_Raw(path)); + + opened = FALSE; // already open + } + else { + REBCNT nargs = AM_OPEN_WRITE; + if (REF(seek) || REF(append)) + nargs |= AM_OPEN_SEEK; + else + nargs |= AM_OPEN_NEW; + Setup_File(file, nargs, path); + Open_File_Port(port, file, path); + opened = TRUE; + } + + if (REF(append)) { + file->index = -1; // append + SET_FLAG(req->modes, RFM_RESEEK); + } + if (REF(seek)) + Set_Seek(file, ARG(index)); + + // Determine length. Clip /PART to size of string if needed. + REBCNT len = VAL_LEN_AT(data); + if (REF(part)) { + REBCNT n = Int32s(ARG(limit), 0); + if (n <= len) len = n; + } + + Write_File_Port(file, data, len, REF(lines)); + + if (opened) { + OS_DO_DEVICE(req, RDC_CLOSE); + Cleanup_File(file); + } + + if (req->error) { + DECLARE_LOCAL(i); + Init_Integer(i, req->error); + fail (Error_Write_Error_Raw(path, i)); + } + + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; } + + case SYM_OPEN: { + INCLUDE_PARAMS_OF_OPEN; + + UNUSED(PAR(spec)); + if (REF(allow)) { + UNUSED(ARG(access)); + fail (Error_Bad_Refines_Raw()); + } + + REBFLGS flags = ( + (REF(new) ? AM_OPEN_NEW : 0) + | (REF(read) || NOT(REF(write)) ? AM_OPEN_READ : 0) + | (REF(write) || NOT(REF(read)) ? AM_OPEN_WRITE : 0) + | (REF(seek) ? AM_OPEN_SEEK : 0) + | (REF(allow) ? AM_OPEN_ALLOW : 0) + ); + Setup_File(file, flags, path); + + // !!! need to change file modes to R/O if necessary + + Open_File_Port(port, file, path); + + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; } + + case SYM_COPY: { + INCLUDE_PARAMS_OF_COPY; + + UNUSED(PAR(value)); + if (REF(deep)) + fail (Error_Bad_Refines_Raw()); + if (REF(types)) { + UNUSED(ARG(kinds)); + fail (Error_Bad_Refines_Raw()); + } + + if (!IS_OPEN(req)) + fail (Error_Not_Open_Raw(path)); // !!! wrong msg + + REBCNT len = Set_Length(file, REF(part) ? VAL_INT64(ARG(limit)) : -1); + REBFLGS flags = 0; + Read_File_Port(D_OUT, port, file, path, flags, len); + return R_OUT; } + + case SYM_OPEN_Q: + return R_FROM_BOOL(IS_OPEN(req)); + + case SYM_CLOSE: { + INCLUDE_PARAMS_OF_CLOSE; + UNUSED(PAR(port)); + + if (IS_OPEN(req)) { + OS_DO_DEVICE(req, RDC_CLOSE); + Cleanup_File(file); + } + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; } + + case SYM_DELETE: { + INCLUDE_PARAMS_OF_DELETE; + UNUSED(PAR(port)); + + if (IS_OPEN(req)) + fail (Error_No_Delete_Raw(path)); + Setup_File(file, 0, path); + if (OS_DO_DEVICE(req, RDC_DELETE) < 0) + fail (Error_No_Delete_Raw(path)); + + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; } + + case SYM_RENAME: { + INCLUDE_PARAMS_OF_RENAME; + + if (IS_OPEN(req)) + fail (Error_No_Rename_Raw(path)); + + Setup_File(file, 0, path); + + // Convert file name to OS format: + // + REBSER *target = Value_To_OS_Path(ARG(to), TRUE); + if (target == NULL) + fail (Error_Bad_File_Path_Raw(ARG(to))); + req->common.data = BIN_HEAD(target); + OS_DO_DEVICE(req, RDC_RENAME); + Free_Series(target); + if (req->error) + fail (Error_No_Rename_Raw(path)); + + Move_Value(D_OUT, ARG(from)); + return R_OUT; } + + case SYM_CREATE: { + if (!IS_OPEN(req)) { + Setup_File(file, AM_OPEN_WRITE | AM_OPEN_NEW, path); + if (OS_DO_DEVICE(req, RDC_CREATE) < 0) + fail (Error_On_Port(RE_CANNOT_OPEN, port, req->error)); + OS_DO_DEVICE(req, RDC_CLOSE); + } + + // !!! should it leave file open??? + + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; } + + case SYM_QUERY: { + INCLUDE_PARAMS_OF_QUERY; + + UNUSED(PAR(target)); + if (REF(mode)) { + UNUSED(ARG(field)); + fail (Error_Bad_Refines_Raw()); + } + + if (!IS_OPEN(req)) { + Setup_File(file, 0, path); + if (OS_DO_DEVICE(req, RDC_QUERY) < 0) return R_BLANK; + } + Ret_Query_File(port, file, D_OUT); + + // !!! free file path? + + return R_OUT; } + + case SYM_MODIFY: { + INCLUDE_PARAMS_OF_MODIFY; + + UNUSED(PAR(target)); + UNUSED(PAR(field)); + UNUSED(PAR(value)); + + // !!! Set_Mode_Value() was called here, but a no-op in R3-Alpha + if (!IS_OPEN(req)) { + Setup_File(file, 0, path); + if (OS_DO_DEVICE(req, RDC_MODIFY) < 0) return R_BLANK; + } + return R_TRUE; } + + case SYM_INDEX_OF: + Init_Integer(D_OUT, file->index + 1); + return R_OUT; + + case SYM_LENGTH_OF: + // + // Comment said "clip at zero" + /// + Init_Integer(D_OUT, file->size - file->index); + return R_OUT; + + case SYM_HEAD_OF: { + file->index = 0; + SET_FLAG(req->modes, RFM_RESEEK); + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; } + + case SYM_TAIL_OF: { + file->index = file->size; + SET_FLAG(req->modes, RFM_RESEEK); + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; } + + case SYM_SKIP: { + INCLUDE_PARAMS_OF_SKIP; + + UNUSED(PAR(series)); + + file->index += Get_Num_From_Arg(ARG(offset)); + SET_FLAG(req->modes, RFM_RESEEK); + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; } + + case SYM_HEAD_Q: + return R_FROM_BOOL(LOGICAL(file->index == 0)); + + case SYM_TAIL_Q: + return R_FROM_BOOL( + LOGICAL(file->index >= file->size) + ); + + case SYM_PAST_Q: + return R_FROM_BOOL( + LOGICAL(file->index > file->size) + ); + + case SYM_CLEAR: + // !! check for write enabled? + SET_FLAG(req->modes, RFM_RESEEK); + SET_FLAG(req->modes, RFM_TRUNCATE); + req->length = 0; + if (OS_DO_DEVICE(req, RDC_WRITE) < 0) { + DECLARE_LOCAL(i); + Init_Integer(i, req->error); + fail (Error_Write_Error_Raw(path, i)); + } + return R_OUT; + + default: + break; + } + + fail (Error_Illegal_Action(REB_PORT, action)); } -/*********************************************************************** -** -*/ void Init_File_Scheme(void) -/* -** Associate the FILE:// scheme with the above native -** actions. This will later be used by SET-SCHEME when -** the scheme is initialized. -** -***********************************************************************/ +// +// get-file-actor-handle: native [ +// +// {Retrieve handle to the native actor for files} +// +// return: [handle!] +// ] +// +REBNATIVE(get_file_actor_handle) { - Register_Scheme(SYM_FILE, 0, File_Actor); + Make_Port_Actor_Handle(D_OUT, &File_Actor); + return R_OUT; } - - -#ifdef low_usage - // was in Read_File above... - if (args & AM_READ_LINES) { - REBYTE *bp = BIN_HEAD(ser); - REBYTE *lp; - REBSER *blk = Make_Block(1 + Count_Lines(bp, len)); - REBVAL *val = Append_Value(blk); - Set_Binary(val, ser); // temp - keep it save from GC - Set_Block(ds, blk); // accounts for GC - while (*bp) { - lp = bp; - len = Next_Line(&bp); - val = Append_Value(blk); - Set_String(val, Decode_UTF8_Series(lp, len)); - } - Remove_Series(blk, 0, 1); // remove temp binary - } -#endif diff --git a/src/core/p-net.c b/src/core/p-net.c index 54c0ab9ebe..688ac7c0a2 100644 --- a/src/core/p-net.c +++ b/src/core/p-net.c @@ -1,31 +1,32 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: p-net.c -** Summary: network port interface -** Section: ports -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %p-net.c +// Summary: "network port interface" +// Section: ports +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" @@ -34,271 +35,512 @@ #define NET_BUF_SIZE 32*1024 -/*********************************************************************** -** -*/ static void Ret_Query_Net(REBSER *port, REBREQ *sock, REBVAL *ret) -/* -***********************************************************************/ +enum Transport_Types { + TRANSPORT_TCP, + TRANSPORT_UDP +}; + +// +// Ret_Query_Net: C +// +static void Ret_Query_Net(REBCTX *port, struct devreq_net *sock, REBVAL *out) +{ + REBVAL *std_info = In_Object(port, STD_PORT_SCHEME, STD_SCHEME_INFO, 0); + REBCTX *info; + + if (!std_info || !IS_OBJECT(std_info)) + fail (Error_On_Port(RE_INVALID_SPEC, port, -10)); + + info = Copy_Context_Shallow(VAL_CONTEXT(std_info)); + + Set_Tuple( + CTX_VAR(info, STD_NET_INFO_LOCAL_IP), + cast(REBYTE*, &sock->local_ip), + 4 + ); + Init_Integer( + CTX_VAR(info, STD_NET_INFO_LOCAL_PORT), + sock->local_port + ); + + Set_Tuple( + CTX_VAR(info, STD_NET_INFO_REMOTE_IP), + cast(REBYTE*, &sock->remote_ip), + 4 + ); + Init_Integer( + CTX_VAR(info, STD_NET_INFO_REMOTE_PORT), + sock->remote_port + ); + + Init_Object(out, info); +} + + +// +// Accept_New_Port: C +// +// Clone a listening port as a new accept port. +// +static void Accept_New_Port(REBVAL *out, REBCTX *port, struct devreq_net *sock) +{ + struct devreq_net *nsock; + REBREQ *req = AS_REBREQ(sock); + + // Get temp sock struct created by the device: + nsock = cast(struct devreq_net*, req->common.sock); + if (!nsock) return; // false alarm + req->common.sock = AS_REBREQ(nsock)->next; + REBREQ *nreq = AS_REBREQ(nsock); + nreq->common.data = 0; + nreq->next = 0; + + // Create a new port using ACCEPT request passed by sock->common.sock: + port = Copy_Context_Shallow(port); + Init_Port(out, port); // Also for GC protect + + Init_Blank(CTX_VAR(port, STD_PORT_DATA)); // just to be sure. + Init_Blank(CTX_VAR(port, STD_PORT_STATE)); // just to be sure. + + // Copy over the new sock data: + sock = cast(struct devreq_net*, Ensure_Port_State(port, RDI_NET)); + *sock = *nsock; + AS_REBREQ(sock)->port = port; + OS_FREE(nsock); // allocated by dev_net.c (MT issues?) +} + +// +// Transport_Actor: C +// +static REB_R Transport_Actor( + REBFRM *frame_, + REBCTX *port, + REBSYM action, + enum Transport_Types proto +) { + // Initialize the IO request + // + REBREQ *sock = Ensure_Port_State(port, RDI_NET); + if (proto == TRANSPORT_UDP) + SET_FLAG(sock->modes, RST_UDP); + + REBVAL *spec = CTX_VAR(port, STD_PORT_SPEC); + if (!IS_OBJECT(spec)) + fail (Error_Invalid_Port_Raw()); + + // sock->timeout = 4000; // where does this go? !!! + + // !!! Comment said "HOW TO PREVENT OVERWRITE DURING BUSY OPERATION!!! + // Should it just ignore it or cause an error?" + + // Actions for an unopened socket: + + if (!IS_OPEN(sock)) { + + switch (action) { // Ordered by frequency + + case SYM_OPEN: { + REBVAL *arg = Obj_Value(spec, STD_PORT_SPEC_NET_HOST); + REBVAL *val = Obj_Value(spec, STD_PORT_SPEC_NET_PORT_ID); + + if (OS_DO_DEVICE(sock, RDC_OPEN)) + fail (Error_On_Port(RE_CANNOT_OPEN, port, -12)); + SET_OPEN(sock); + + // Lookup host name (an extra TCP device step): + if (IS_STRING(arg)) { + sock->common.data = VAL_BIN(arg); + DEVREQ_NET(sock)->remote_port = + IS_INTEGER(val) ? VAL_INT32(val) : 80; + + // Note: sets remote_ip field + // + REBINT result = OS_DO_DEVICE(sock, RDC_LOOKUP); + if (result < 0) + fail (Error_On_Port(RE_NO_CONNECT, port, sock->error)); + + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; + } + else if (IS_TUPLE(arg)) { // Host IP specified: + DEVREQ_NET(sock)->remote_port = + IS_INTEGER(val) ? VAL_INT32(val) : 80; + memcpy(&(DEVREQ_NET(sock)->remote_ip), VAL_TUPLE(arg), 4); + break; + } + else if (IS_BLANK(arg)) { // No host, must be a LISTEN socket: + SET_FLAG(sock->modes, RST_LISTEN); + sock->common.sock = 0; // where ACCEPT requests are queued + DEVREQ_NET(sock)->local_port = + IS_INTEGER(val) ? VAL_INT32(val) : 8000; + break; + } + else + fail (Error_On_Port(RE_INVALID_SPEC, port, -10)); + break; } + + case SYM_CLOSE: + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; + + case SYM_OPEN_Q: + return R_FALSE; + + case SYM_UPDATE: // allowed after a close + break; + + default: + fail (Error_On_Port(RE_NOT_OPEN, port, -12)); + } + } + + // Actions for an open socket: + + switch (action) { // Ordered by frequency + + case SYM_UPDATE: { + // + // Update the port object after a READ or WRITE operation. + // This is normally called by the WAKE-UP function. + // + REBVAL *port_data = CTX_VAR(port, STD_PORT_DATA); + if (sock->command == RDC_READ) { + if (ANY_BINSTR(port_data)) { + SET_SERIES_LEN( + VAL_SERIES(port_data), + VAL_LEN_HEAD(port_data) + sock->actual + ); + } + } + else if (sock->command == RDC_WRITE) { + Init_Blank(port_data); // Write is done. + } + return R_BLANK; } + + case SYM_READ: { + INCLUDE_PARAMS_OF_READ; + + UNUSED(PAR(source)); + + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(seek)) { + UNUSED(ARG(index)); + fail (Error_Bad_Refines_Raw()); + } + UNUSED(PAR(string)); // handled in dispatcher + UNUSED(PAR(lines)); // handled in dispatcher + + // Read data into a buffer, expanding the buffer if needed. + // If no length is given, program must stop it at some point. + if ( + !GET_FLAG(sock->modes, RST_UDP) + && !GET_FLAG(sock->state, RSM_CONNECT) + ) { + fail (Error_On_Port(RE_NOT_CONNECTED, port, -15)); + } + + // Setup the read buffer (allocate a buffer if needed): + // + REBVAL *port_data = CTX_VAR(port, STD_PORT_DATA); + REBSER *buffer; + if (!IS_STRING(port_data) && !IS_BINARY(port_data)) { + buffer = Make_Binary(NET_BUF_SIZE); + Init_Binary(port_data, buffer); + } + else { + buffer = VAL_SERIES(port_data); + assert(BYTE_SIZE(buffer)); + + if (SER_AVAIL(buffer) < NET_BUF_SIZE/2) + Extend_Series(buffer, NET_BUF_SIZE); + } + + sock->length = SER_AVAIL(buffer); + sock->common.data = BIN_TAIL(buffer); // write at tail + sock->actual = 0; // actual for THIS read (not for total) + + // Note: recv can happen immediately + // + REBINT result = OS_DO_DEVICE(sock, RDC_READ); + if (result < 0) + fail (Error_On_Port(RE_READ_ERROR, port, sock->error)); + + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; } + + case SYM_WRITE: { + INCLUDE_PARAMS_OF_WRITE; + + UNUSED(PAR(destination)); + + if (REF(seek)) { + UNUSED(ARG(index)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(append)) + fail (Error_Bad_Refines_Raw()); + if (REF(allow)) { + UNUSED(ARG(access)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(lines)) + fail (Error_Bad_Refines_Raw()); + + // Write the entire argument string to the network. + // The lower level write code continues until done. + + if ( + !GET_FLAG(sock->modes, RST_UDP) + && !GET_FLAG(sock->state, RSM_CONNECT) + ){ + fail (Error_On_Port(RE_NOT_CONNECTED, port, -15)); + } + + // Determine length. Clip /PART to size of string if needed. + REBVAL *data = ARG(data); + + REBCNT len = VAL_LEN_AT(data); + if (REF(part)) { + REBCNT n = Int32s(ARG(limit), 0); + if (n <= len) + len = n; + } + + // Setup the write: + + Move_Value(CTX_VAR(port, STD_PORT_DATA), data); // keep it GC safe + sock->length = len; + sock->common.data = VAL_BIN_AT(data); + sock->actual = 0; + + // Note: send can happen immediately + // + REBINT result = OS_DO_DEVICE(sock, RDC_WRITE); + if (result < 0) + fail (Error_On_Port(RE_WRITE_ERROR, port, sock->error)); + + if (result == DR_DONE) + Init_Blank(CTX_VAR(port, STD_PORT_DATA)); + + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; } + + case SYM_PICK_P: { + INCLUDE_PARAMS_OF_PICK_P; + UNUSED(PAR(location)); + + // FIRST server-port returns new port connection. + // + REBCNT len = Get_Num_From_Arg(ARG(picker)); + if (len == 1 && GET_FLAG(sock->modes, RST_LISTEN) && sock->common.data) + Accept_New_Port(SINK(D_OUT), port, DEVREQ_NET(sock)); + else + fail (Error_Out_Of_Range(ARG(picker))); + return R_OUT; } + + case SYM_QUERY: { + // + // Get specific information - the scheme's info object. + // Special notation allows just getting part of the info. + // + Ret_Query_Net(port, DEVREQ_NET(sock), D_OUT); + return R_OUT; } + + case SYM_OPEN_Q: + // + // Connect for clients, bind for servers: + // + return R_FROM_BOOL ( + LOGICAL(sock->state & ((1 << RSM_CONNECT) | (1 << RSM_BIND))) + ); + + case SYM_CLOSE: { + if (IS_OPEN(sock)) { + OS_DO_DEVICE(sock, RDC_CLOSE); + SET_CLOSED(sock); + } + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; } + + case SYM_LENGTH_OF: { + REBVAL *port_data = CTX_VAR(port, STD_PORT_DATA); + Init_Integer( + D_OUT, + ANY_SERIES(port_data) ? VAL_LEN_HEAD(port_data) : 0 + ); + return R_OUT; } + + case SYM_OPEN: { + REBINT result = OS_DO_DEVICE(sock, RDC_CONNECT); + if (result < 0) + fail (Error_On_Port(RE_NO_CONNECT, port, sock->error)); + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; } + + case SYM_DELETE: { + // + // !!! Comment said "Temporary to TEST error handler!" + // + REBVAL *event = Append_Event(); // sets signal + VAL_RESET_HEADER(event, REB_EVENT); // has more space, if needed + VAL_EVENT_TYPE(event) = EVT_ERROR; + VAL_EVENT_DATA(event) = 101; + VAL_EVENT_REQ(event) = sock; + Move_Value(D_OUT, CTX_VALUE(port)); + return R_OUT; } + + default: + break; + } + + fail (Error_Illegal_Action(REB_PORT, action)); +} + + +// +// TCP_Actor: C +// +static REB_R TCP_Actor(REBFRM *frame_, REBCTX *port, REBSYM action) { - REBVAL *info = In_Object(port, STD_PORT_SCHEME, STD_SCHEME_INFO, 0); - REBSER *obj; + return Transport_Actor(frame_, port, action, TRANSPORT_TCP); +} + - if (!info || !IS_OBJECT(info)) Trap_Port(RE_INVALID_SPEC, port, -10); +// +// UDP_Actor: C +// +static REB_R UDP_Actor(REBFRM *frame_, REBCTX *port, REBSYM action) +{ + return Transport_Actor(frame_, port, action, TRANSPORT_UDP); +} - obj = CLONE_OBJECT(VAL_OBJ_FRAME(info)); - SET_OBJECT(ret, obj); - Set_Tuple(OFV(obj, STD_NET_INFO_LOCAL_IP), (REBYTE*)&sock->net.local_ip, 4); - Set_Tuple(OFV(obj, STD_NET_INFO_REMOTE_IP), (REBYTE*)&sock->net.remote_ip, 4); - SET_INTEGER(OFV(obj, STD_NET_INFO_LOCAL_PORT), sock->net.local_port); - SET_INTEGER(OFV(obj, STD_NET_INFO_REMOTE_PORT), sock->net.remote_port); +// +// get-tcp-actor-handle: native [ +// +// {Retrieve handle to the native actor for TCP} +// +// return: [handle!] +// ] +// +REBNATIVE(get_tcp_actor_handle) +{ + Make_Port_Actor_Handle(D_OUT, &TCP_Actor); + return R_OUT; } -/*********************************************************************** -** -*/ static void Accept_New_Port(REBVAL *ds, REBSER *port, REBREQ *sock) -/* -** Clone a listening port as a new accept port. -** -***********************************************************************/ +// +// get-udp-actor-handle: native [ +// +// {Retrieve handle to the native actor for UDP} +// +// return: [handle!] +// ] +// +REBNATIVE(get_udp_actor_handle) { - REBREQ *nsock; - - // Get temp sock struct created by the device: - nsock = sock->sock; - if (!nsock) return; // false alarm - sock->sock = nsock->next; - nsock->data = 0; - nsock->next = 0; - - // Create a new port using ACCEPT request passed by sock->sock: - port = Copy_Block(port, 0); - SET_PORT(DS_RETURN, port); // Also for GC protect - SET_NONE(OFV(port, STD_PORT_DATA)); // just to be sure. - SET_NONE(OFV(port, STD_PORT_STATE)); // just to be sure. - - // Copy over the new sock data: - sock = Use_Port_State(port, RDI_NET, sizeof(*sock)); - *sock = *nsock; - sock->clen = sizeof(*sock); - sock->port = port; - OS_FREE(nsock); // allocated by dev_net.c (MT issues?) + Make_Port_Actor_Handle(D_OUT, &UDP_Actor); + return R_OUT; } -/*********************************************************************** -** -*/ static int TCP_Actor(REBVAL *ds, REBSER *port, REBCNT action) -/* -***********************************************************************/ +// +// set-udp-multicast: native [ +// +// {Join (or leave) an IPv4 multicast group} +// +// return: [] +// port [port!] +// {An open UDP port} +// group [tuple!] +// {Multicast group to join (224.0.0.0 to 239.255.255.255)} +// member [tuple!] +// {Member to add to multicast group (use 0.0.0.0 for INADDR_ANY)} +// /drop +// {Leave the group (default is to add)} +// ] +// +REBNATIVE(set_udp_multicast) +// +// !!! SET-MODES was never standardized or implemented for R3-Alpha, so there +// was no RDC_MODIFY written. While it is tempting to just go ahead and +// start writing `setsockopt` calls right here in this file, that would mean +// adding platform-sensitive network includes into the core. +// +// Ultimately, the desire is that ports would be modules--consisting of some +// Rebol code, and some C code (possibly with platform-conditional libs). +// This is the direction for the extension model, where the artificial limit +// of having "native port actors" that can't just do the OS calls they want +// will disappear. +// +// Until that happens, we want to pass this through to the Reb_Device layer +// somehow. It's not easy to see how to modify this "REBREQ" which is +// actually *the port's state* to pass it the necessary information for this +// request. Hence the cheat is just to pass it the frame, and then let +// Reb_Device implementations go ahead and use the extension API to pick +// that frame apart. { - REBREQ *sock; // IO request - REBVAL *spec; // port spec - REBVAL *arg; // action argument value - REBVAL *val; // e.g. port number value - REBINT result; // IO result - REBCNT refs; // refinement argument flags - REBCNT len; // generic length - REBSER *ser; // simplifier - - Validate_Port(port, action); - - *D_RET = *D_ARG(1); - arg = D_ARG(2); - refs = 0; - - sock = Use_Port_State(port, RDI_NET, sizeof(*sock)); - //Debug_Fmt("Sock: %x", sock); - spec = OFV(port, STD_PORT_SPEC); - if (!IS_OBJECT(spec)) Trap0(RE_INVALID_PORT); - - // sock->timeout = 4000; // where does this go? !!! - - // HOW TO PREVENT OVERWRITE DURING BUSY OPERATION!!! - // Should it just ignore it or cause an error? - - // Actions for an unopened socket: - if (!IS_OPEN(sock)) { - - switch (action) { // Ordered by frequency - - case A_OPEN: - - arg = Obj_Value(spec, STD_PORT_SPEC_NET_HOST); - val = Obj_Value(spec, STD_PORT_SPEC_NET_PORT_ID); - - if (OS_DO_DEVICE(sock, RDC_OPEN)) Trap_Port(RE_CANNOT_OPEN, port, -12); - SET_OPEN(sock); - - // Lookup host name (an extra TCP device step): - if (IS_STRING(arg)) { - sock->data = VAL_BIN(arg); - sock->net.remote_port = IS_INTEGER(val) ? VAL_INT32(val) : 80; - result = OS_DO_DEVICE(sock, RDC_LOOKUP); // sets remote_ip field - if (result < 0) Trap_Port(RE_NO_CONNECT, port, sock->error); - return R_RET; - } - - // Host IP specified: - else if (IS_TUPLE(arg)) { - sock->net.remote_port = IS_INTEGER(val) ? VAL_INT32(val) : 80; - memcpy(&sock->net.remote_ip, VAL_TUPLE(arg), 4); - break; - } - - // No host, must be a LISTEN socket: - else if (IS_NONE(arg)) { - SET_FLAG(sock->modes, RST_LISTEN); - sock->data = 0; // where ACCEPT requests are queued - sock->net.local_port = IS_INTEGER(val) ? VAL_INT32(val) : 8000; - break; - } - else Trap_Port(RE_INVALID_SPEC, port, -10); - - case A_CLOSE: - return R_RET; - - case A_OPENQ: - return R_FALSE; - - case A_UPDATE: // allowed after a close - break; - - default: - Trap_Port(RE_NOT_OPEN, port, -12); - } - } - - // Actions for an open socket: - switch (action) { // Ordered by frequency - - case A_UPDATE: - // Update the port object after a READ or WRITE operation. - // This is normally called by the WAKE-UP function. - arg = OFV(port, STD_PORT_DATA); - if (sock->command == RDC_READ) { - if (ANY_BINSTR(arg)) VAL_TAIL(arg) += sock->actual; - } - else if (sock->command == RDC_WRITE) { - SET_NONE(arg); // Write is done. - } - return R_NONE; - - case A_READ: - // Read data into a buffer, expanding the buffer if needed. - // If no length is given, program must stop it at some point. - refs = Find_Refines(ds, ALL_READ_REFS); - if (!GET_FLAG(sock->state, RSM_CONNECT)) Trap_Port(RE_NOT_CONNECTED, port, -15); - - // Setup the read buffer (allocate a buffer if needed): - arg = OFV(port, STD_PORT_DATA); - if (!IS_STRING(arg) && !IS_BINARY(arg)) { - Set_Binary(arg, Make_Binary(NET_BUF_SIZE)); - } - ser = VAL_SERIES(arg); - sock->length = SERIES_AVAIL(ser); // space available - if (sock->length < NET_BUF_SIZE/2) Extend_Series(ser, NET_BUF_SIZE); - sock->length = SERIES_AVAIL(ser); - sock->data = STR_TAIL(ser); // write at tail - //if (SERIES_TAIL(ser) == 0) - sock->actual = 0; // Actual for THIS read, not for total. - - //Print("(max read length %d)", sock->length); - result = OS_DO_DEVICE(sock, RDC_READ); // recv can happen immediately - if (result < 0) Trap_Port(RE_READ_ERROR, port, sock->error); - break; - - case A_WRITE: - // Write the entire argument string to the network. - // The lower level write code continues until done. - - refs = Find_Refines(ds, ALL_WRITE_REFS); - if (!GET_FLAG(sock->state, RSM_CONNECT)) Trap_Port(RE_NOT_CONNECTED, port, -15); - - // Determine length. Clip /PART to size of string if needed. - spec = D_ARG(2); - len = VAL_LEN(spec); - if (refs & AM_WRITE_PART) { - REBCNT n = Int32s(D_ARG(ARG_WRITE_LENGTH), 0); - if (n <= len) len = n; - } - - // Setup the write: - *OFV(port, STD_PORT_DATA) = *spec; // keep it GC safe - sock->length = len; - sock->data = VAL_BIN_DATA(spec); - sock->actual = 0; - - //Print("(write length %d)", len); - result = OS_DO_DEVICE(sock, RDC_WRITE); // send can happen immediately - if (result < 0) Trap_Port(RE_WRITE_ERROR, port, sock->error); - if (result == DR_DONE) SET_NONE(OFV(port, STD_PORT_DATA)); - break; - - case A_PICK: - // FIRST server-port returns new port connection. - len = Get_Num_Arg(arg); // Position - if (len == 1 && GET_FLAG(sock->modes, RST_LISTEN) && sock->data) - Accept_New_Port(ds, port, sock); // sets D_RET - else - Trap_Range(arg); - break; - - case A_QUERY: - // Get specific information - the scheme's info object. - // Special notation allows just getting part of the info. - Ret_Query_Net(port, sock, D_RET); - break; - - case A_OPENQ: - // Connect for clients, bind for servers: - if (sock->state & ((1<error); - break; - //Trap_Port(RE_ALREADY_OPEN, port); - - case A_DELETE: // Temporary to TEST error handler! - { - REBVAL *event = Append_Event(); // sets signal - VAL_SET(event, REB_EVENT); // (has more space, if we need it) - VAL_EVENT_TYPE(event) = EVT_ERROR; - VAL_EVENT_DATA(event) = 101; - VAL_EVENT_REQ(event) = sock; - } - break; - - default: - Trap_Action(REB_PORT, action); - } - - return R_RET; + INCLUDE_PARAMS_OF_SET_UDP_MULTICAST; + + REBCTX *port = VAL_CONTEXT(ARG(port)); + REBREQ *sock = Ensure_Port_State(port, RDI_NET); + + sock->common.data = cast(REBYTE*, frame_); + + // sock->command is going to just be RDC_MODIFY, so all there is to go + // by is the data and flags. Since RFC3171 specifies IPv4 multicast + // address space...how about that? + // + sock->flags = 3171; + + UNUSED(ARG(group)); + UNUSED(ARG(member)); + UNUSED(REF(drop)); + + REBINT result = OS_DO_DEVICE(sock, RDC_MODIFY); + if (result < 0) + fail ("SET-UDP-MULTICAST failure"); // can device layer just fail()? + + return R_VOID; } -/*********************************************************************** -** -*/ void Init_TCP_Scheme(void) -/* -***********************************************************************/ +// +// set-udp-ttl: native [ +// +// {Set the TTL of a UDP port} +// +// return: [] +// port [port!] +// {An open UDP port} +// ttl [integer!] +// {0 = local machine only, 1 = subnet (default), or up to 255} +// ] +// +REBNATIVE(set_udp_ttl) { - Register_Scheme(SYM_TCP, 0, TCP_Actor); + INCLUDE_PARAMS_OF_SET_UDP_TTL; + + REBCTX *port = VAL_CONTEXT(ARG(port)); + REBREQ *sock = Ensure_Port_State(port, RDI_NET); + + sock->common.data = cast(REBYTE*, frame_); + + // sock->command is going to just be RDC_MODIFY, so all there is to go + // by is the data and flags. Since RFC2365 specifies IPv4 multicast + // administrative boundaries...how about that? + // + sock->flags = 2365; + + UNUSED(ARG(ttl)); + + REBINT result = OS_DO_DEVICE(sock, RDC_MODIFY); + if (result < 0) + fail ("SET-UDP-TTL failure"); // can device layer just fail()? + + return R_VOID; } diff --git a/src/core/p-serial.c b/src/core/p-serial.c new file mode 100644 index 0000000000..c38e579269 --- /dev/null +++ b/src/core/p-serial.c @@ -0,0 +1,294 @@ +// +// File: %p-serial.c +// Summary: "serial port interface" +// Section: ports +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2013 REBOL Technologies +// Copyright 2013-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + +#include "sys-core.h" +#include "reb-evtypes.h" + +#define MAX_SERIAL_DEV_PATH 128 + +// +// Serial_Actor: C +// +static REB_R Serial_Actor(REBFRM *frame_, REBCTX *port, REBSYM action) +{ + REBVAL *spec; // port spec + REBVAL *arg; // action argument value + REBINT result; // IO result + REBCNT len; // generic length + REBSER *ser; // simplifier + REBVAL *path; + + Move_Value(D_OUT, D_ARG(1)); + + // Validate PORT fields: + spec = CTX_VAR(port, STD_PORT_SPEC); + if (!IS_OBJECT(spec)) fail (Error_Invalid_Port_Raw()); + path = Obj_Value(spec, STD_PORT_SPEC_HEAD_REF); + if (!path) fail (Error_Invalid_Spec_Raw(spec)); + + //if (!IS_FILE(path)) fail (Error_Invalid_Spec_Raw(path)); + + REBREQ *req = Ensure_Port_State(port, RDI_SERIAL); + struct devreq_serial *serial = DEVREQ_SERIAL(req); + + // Actions for an unopened serial port: + if (!IS_OPEN(req)) { + + switch (action) { + + case SYM_OPEN: + arg = Obj_Value(spec, STD_PORT_SPEC_SERIAL_PATH); + if (! (IS_FILE(arg) || IS_STRING(arg) || IS_BINARY(arg))) + fail (Error_Invalid_Port_Arg_Raw(arg)); + + serial->path = ALLOC_N(REBCHR, MAX_SERIAL_DEV_PATH); + OS_STRNCPY( + serial->path, + // + // !!! This is assuming VAL_DATA contains native chars. + // Should it? (2 bytes on windows, 1 byte on linux/mac) + // + SER_AT(REBCHR, VAL_SERIES(arg), VAL_INDEX(arg)), + MAX_SERIAL_DEV_PATH + ); + arg = Obj_Value(spec, STD_PORT_SPEC_SERIAL_SPEED); + if (! IS_INTEGER(arg)) + fail (Error_Invalid_Port_Arg_Raw(arg)); + + serial->baud = VAL_INT32(arg); + //Secure_Port(SYM_SERIAL, ???, path, ser); + arg = Obj_Value(spec, STD_PORT_SPEC_SERIAL_DATA_SIZE); + if (!IS_INTEGER(arg) + || VAL_INT64(arg) < 5 + || VAL_INT64(arg) > 8 + ) { + fail (Error_Invalid_Port_Arg_Raw(arg)); + } + serial->data_bits = VAL_INT32(arg); + + arg = Obj_Value(spec, STD_PORT_SPEC_SERIAL_STOP_BITS); + if (!IS_INTEGER(arg) + || VAL_INT64(arg) < 1 + || VAL_INT64(arg) > 2 + ) { + fail (Error_Invalid_Port_Arg_Raw(arg)); + } + serial->stop_bits = VAL_INT32(arg); + + arg = Obj_Value(spec, STD_PORT_SPEC_SERIAL_PARITY); + if (IS_BLANK(arg)) { + serial->parity = SERIAL_PARITY_NONE; + } else { + if (!IS_WORD(arg)) + fail (Error_Invalid_Port_Arg_Raw(arg)); + + switch (VAL_WORD_SYM(arg)) { + case SYM_ODD: + serial->parity = SERIAL_PARITY_ODD; + break; + case SYM_EVEN: + serial->parity = SERIAL_PARITY_EVEN; + break; + default: + fail (Error_Invalid_Port_Arg_Raw(arg)); + } + } + + arg = Obj_Value(spec, STD_PORT_SPEC_SERIAL_FLOW_CONTROL); + if (IS_BLANK(arg)) { + serial->flow_control = SERIAL_FLOW_CONTROL_NONE; + } else { + if (!IS_WORD(arg)) + fail (Error_Invalid_Port_Arg_Raw(arg)); + + switch (VAL_WORD_SYM(arg)) { + case SYM_HARDWARE: + serial->flow_control = SERIAL_FLOW_CONTROL_HARDWARE; + break; + case SYM_SOFTWARE: + serial->flow_control = SERIAL_FLOW_CONTROL_SOFTWARE; + break; + default: + fail (Error_Invalid_Port_Arg_Raw(arg)); + } + } + + if (OS_DO_DEVICE(req, RDC_OPEN)) + fail (Error_On_Port(RE_CANNOT_OPEN, port, -12)); + SET_OPEN(req); + return R_OUT; + + case SYM_CLOSE: + return R_OUT; + + case SYM_OPEN_Q: + return R_FALSE; + + default: + fail (Error_On_Port(RE_NOT_OPEN, port, -12)); + } + } + + // Actions for an open socket: + switch (action) { + + case SYM_READ: { + INCLUDE_PARAMS_OF_READ; + + UNUSED(PAR(source)); + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(seek)) { + UNUSED(ARG(index)); + fail (Error_Bad_Refines_Raw()); + } + UNUSED(PAR(string)); // handled in dispatcher + UNUSED(PAR(lines)); // handled in dispatcher + + // Setup the read buffer (allocate a buffer if needed): + arg = CTX_VAR(port, STD_PORT_DATA); + if (!IS_STRING(arg) && !IS_BINARY(arg)) { + Init_Binary(arg, Make_Binary(32000)); + } + ser = VAL_SERIES(arg); + req->length = SER_AVAIL(ser); // space available + if (req->length < 32000/2) Extend_Series(ser, 32000); + req->length = SER_AVAIL(ser); + + // This used STR_TAIL (obsolete, equivalent to BIN_TAIL) but was it + // sure the series was byte sized? Added in a check. + assert(BYTE_SIZE(ser)); + req->common.data = BIN_TAIL(ser); // write at tail + + //if (SER_LEN(ser) == 0) + req->actual = 0; // Actual for THIS read, not for total. +#ifdef DEBUG_SERIAL + printf("(max read length %d)", req->length); +#endif + result = OS_DO_DEVICE(req, RDC_READ); // recv can happen immediately + if (result < 0) fail (Error_On_Port(RE_READ_ERROR, port, req->error)); +#ifdef DEBUG_SERIAL + for (len = 0; len < req->actual; len++) { + if (len % 16 == 0) printf("\n"); + printf("%02x ", req->common.data[len]); + } + printf("\n"); +#endif + Move_Value(D_OUT, arg); + return R_OUT; } + + case SYM_WRITE: { + INCLUDE_PARAMS_OF_WRITE; + + UNUSED(PAR(destination)); + + if (REF(seek)) { + UNUSED(ARG(index)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(append)) + fail (Error_Bad_Refines_Raw()); + if (REF(allow)) { + UNUSED(ARG(access)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(lines)) + fail (Error_Bad_Refines_Raw()); + + // Determine length. Clip /PART to size of string if needed. + REBVAL *data = ARG(data); + len = VAL_LEN_AT(data); + if (REF(part)) { + REBCNT n = Int32s(ARG(limit), 0); + if (n <= len) len = n; + } + + // Setup the write: + Move_Value(CTX_VAR(port, STD_PORT_DATA), data); // keep it GC safe + req->length = len; + req->common.data = VAL_BIN_AT(data); + req->actual = 0; + + //Print("(write length %d)", len); + result = OS_DO_DEVICE(req, RDC_WRITE); // send can happen immediately + if (result < 0) + fail (Error_On_Port(RE_WRITE_ERROR, port, req->error)); + break; } + + case SYM_UPDATE: + // Update the port object after a READ or WRITE operation. + // This is normally called by the WAKE-UP function. + arg = CTX_VAR(port, STD_PORT_DATA); + if (req->command == RDC_READ) { + if (ANY_BINSTR(arg)) { + SET_SERIES_LEN( + VAL_SERIES(arg), + VAL_LEN_HEAD(arg) + req->actual + ); + } + } + else if (req->command == RDC_WRITE) { + Init_Blank(arg); // Write is done. + } + return R_BLANK; + + case SYM_OPEN_Q: + return R_TRUE; + + case SYM_CLOSE: + if (IS_OPEN(req)) { + OS_DO_DEVICE(req, RDC_CLOSE); + SET_CLOSED(req); + } + break; + + default: + fail (Error_Illegal_Action(REB_PORT, action)); + } + + return R_OUT; +} + + +// +// get-serial-actor-handle: native [ +// +// {Retrieve handle to the native actor for the serial port} +// +// return: [handle!] +// ] +// +REBNATIVE(get_serial_actor_handle) +{ + Make_Port_Actor_Handle(D_OUT, &Serial_Actor); + return R_OUT; +} diff --git a/src/core/p-signal.c b/src/core/p-signal.c new file mode 100644 index 0000000000..09e8bbbff9 --- /dev/null +++ b/src/core/p-signal.c @@ -0,0 +1,304 @@ +// +// File: %p-signal.c +// Summary: "signal port interface" +// Section: ports +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2014 Atronix Engineering, Inc. +// Copyright 2014-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + +#include "sys-core.h" + +#ifdef HAS_POSIX_SIGNAL +#include + +static void update(struct devreq_posix_signal *signal, REBINT len, REBVAL *arg) +{ + REBREQ *req = AS_REBREQ(signal); + const siginfo_t *sig = cast(siginfo_t *, req->common.data); + int i = 0; + const REBYTE signal_no[] = "signal-no"; + const REBYTE code[] = "code"; + const REBYTE source_pid[] = "source-pid"; + const REBYTE source_uid[] = "source-uid"; + + Extend_Series(VAL_SERIES(arg), len); + + for (i = 0; i < len; i ++) { + REBCTX *obj = Alloc_Context(REB_OBJECT, 8); + REBVAL *val = Append_Context( + obj, NULL, Intern_UTF8_Managed(signal_no, LEN_BYTES(signal_no)) + ); + Init_Integer(val, sig[i].si_signo); + + val = Append_Context( + obj, NULL, Intern_UTF8_Managed(code, LEN_BYTES(code)) + ); + Init_Integer(val, sig[i].si_code); + val = Append_Context( + obj, NULL, Intern_UTF8_Managed(source_pid, LEN_BYTES(source_pid)) + ); + Init_Integer(val, sig[i].si_pid); + val = Append_Context( + obj, NULL, Intern_UTF8_Managed(source_uid, LEN_BYTES(source_uid)) + ); + Init_Integer(val, sig[i].si_uid); + + Init_Object(Alloc_Tail_Array(VAL_ARRAY(arg)), obj); + } + + req->actual = 0; /* avoid duplicate updates */ +} + +static int sig_word_num(REBSTR *canon) +{ + switch (STR_SYMBOL(canon)) { + case SYM_SIGALRM: + return SIGALRM; + case SYM_SIGABRT: + return SIGABRT; + case SYM_SIGBUS: + return SIGBUS; + case SYM_SIGCHLD: + return SIGCHLD; + case SYM_SIGCONT: + return SIGCONT; + case SYM_SIGFPE: + return SIGFPE; + case SYM_SIGHUP: + return SIGHUP; + case SYM_SIGILL: + return SIGILL; + case SYM_SIGINT: + return SIGINT; +/* can't be caught + case SYM_SIGKILL: + return SIGKILL; +*/ + case SYM_SIGPIPE: + return SIGPIPE; + case SYM_SIGQUIT: + return SIGQUIT; + case SYM_SIGSEGV: + return SIGSEGV; +/* can't be caught + case SYM_SIGSTOP: + return SIGSTOP; +*/ + case SYM_SIGTERM: + return SIGTERM; + case SYM_SIGTTIN: + return SIGTTIN; + case SYM_SIGTTOU: + return SIGTTOU; + case SYM_SIGUSR1: + return SIGUSR1; + case SYM_SIGUSR2: + return SIGUSR2; + case SYM_SIGTSTP: + return SIGTSTP; + case SYM_SIGPOLL: + return SIGPOLL; + case SYM_SIGPROF: + return SIGPROF; + case SYM_SIGSYS: + return SIGSYS; + case SYM_SIGTRAP: + return SIGTRAP; + case SYM_SIGURG: + return SIGURG; + case SYM_SIGVTALRM: + return SIGVTALRM; + case SYM_SIGXCPU: + return SIGXCPU; + case SYM_SIGXFSZ: + return SIGXFSZ; + default: { + DECLARE_LOCAL (word); + Init_Word(word, canon); + + fail (Error_Invalid_Spec_Raw(word)); + } + } +} + +// +// Signal_Actor: C +// +static REB_R Signal_Actor(REBFRM *frame_, REBCTX *port, REBSYM action) +{ + REBINT result; + REBVAL *arg; + REBINT len; + REBSER *ser; + REBVAL *spec; + REBVAL *val; + RELVAL *sig; + + REBREQ *req = Ensure_Port_State(port, RDI_SIGNAL); + struct devreq_posix_signal *signal = DEVREQ_POSIX_SIGNAL(req); + spec = CTX_VAR(port, STD_PORT_SPEC); + + if (!IS_OPEN(req)) { + switch (action) { + case SYM_READ: + case SYM_OPEN: + val = Obj_Value(spec, STD_PORT_SPEC_SIGNAL_MASK); + if (!IS_BLOCK(val)) + fail (Error_Invalid_Spec_Raw(val)); + + sigemptyset(&signal->mask); + for(sig = VAL_ARRAY_AT_HEAD(val, 0); NOT_END(sig); sig ++) { + if (IS_WORD(sig)) { + /* handle the special word "ALL" */ + if (VAL_WORD_SYM(sig) == SYM_ALL) { + if (sigfillset(&signal->mask) < 0) { + // !!! Needs better error + fail (Error_Invalid_Spec_Raw(sig)); + } + break; + } + + if ( + sigaddset( + &signal->mask, + sig_word_num(VAL_WORD_CANON(sig)) + ) < 0 + ) { + fail (Error_Invalid_Spec_Raw(sig)); + } + } + else + fail (Error_Invalid_Spec_Raw(sig)); + } + + if (OS_DO_DEVICE(req, RDC_OPEN)) + fail (Error_On_Port(RE_CANNOT_OPEN, port, req->error)); + if (action == SYM_OPEN) { + Move_Value(D_OUT, D_ARG(1)); // port + return R_OUT; + } + break; + + case SYM_CLOSE: + return R_OUT; + + case SYM_OPEN_Q: + return R_FALSE; + + case SYM_UPDATE: // allowed after a close + break; + + default: + fail (Error_On_Port(RE_NOT_OPEN, port, -12)); + } + } + + switch (action) { + case SYM_UPDATE: + // Update the port object after a READ or WRITE operation. + // This is normally called by the WAKE-UP function. + arg = CTX_VAR(port, STD_PORT_DATA); + if (req->command == RDC_READ) { + len = req->actual; + if (len > 0) { + update(signal, len, arg); + } + } + return R_BLANK; + + case SYM_READ: + // This device is opened on the READ: + // Issue the read request: + arg = CTX_VAR(port, STD_PORT_DATA); + + len = req->length = 8; + ser = Make_Binary(len * sizeof(siginfo_t)); + req->common.data = BIN_HEAD(ser); + result = OS_DO_DEVICE(req, RDC_READ); + if (result < 0) { + Free_Series(ser); + fail (Error_On_Port(RE_READ_ERROR, port, req->error)); + } + + arg = CTX_VAR(port, STD_PORT_DATA); + if (!IS_BLOCK(arg)) + Init_Block(arg, Make_Array(len)); + + len = req->actual; + + if (len > 0) { + update(signal, len, arg); + Free_Series(ser); + Move_Value(D_OUT, arg); + return R_OUT; + } else { + Free_Series(ser); + return R_BLANK; + } + + case SYM_CLOSE: + OS_DO_DEVICE(req, RDC_CLOSE); + Move_Value(D_OUT, D_ARG(1)); + return R_OUT; + + case SYM_OPEN_Q: + return R_TRUE; + + case SYM_OPEN: { + fail (Error_Already_Open_Raw(D_ARG(1))); + } + + default: + fail (Error_Illegal_Action(REB_PORT, action)); + } + + return R_OUT; +} + +#endif //HAS_POSIX_SIGNAL + + +// +// get-signal-actor-handle: native [ +// +// {Retrieve handle to the native actor for POSIX signals} +// +// return: [handle!] +// ] +// +REBNATIVE(get_signal_actor_handle) +// +// !!! The native scanner isn't smart enough to notice REBNATIVE() inside a +// disabled #ifdef, so a definition for this has to be provided... even if +// it's not a build where it should be available. +{ +#ifdef HAS_POSIX_SIGNAL + Make_Port_Actor_Handle(D_OUT, &Signal_Actor); + return R_OUT; +#else + UNUSED(frame_); + fail ("GET-SIGNAL-ACTOR-HANDLE only works in builds with POSIX signals"); +#endif +} diff --git a/src/core/p-timer.c b/src/core/p-timer.c index ba5b614852..b4a84d569b 100644 --- a/src/core/p-timer.c +++ b/src/core/p-timer.c @@ -1,128 +1,142 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: p-timer.c -** Summary: timer port interface -** Section: ports -** Author: Carl Sassenrath -** Notes: NOT IMPLEMENTED -** -***********************************************************************/ +// +// File: %p-timer.c +// Summary: "timer port interface" +// Section: ports +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// NOT IMPLEMENTED +// /* - General idea of usage: - - t: open timer://name - write t 10 ; set timer - also allow: 1.23 1:23 - wait t - clear t ; reset or delete? - read t ; get timer value - t/awake: func [event] [print "timer!"] - one-shot vs restart timer + General idea of usage: + + t: open timer://name + write t 10 ; set timer - also allow: 1.23 1:23 + wait t + clear t ; reset or delete? + read t ; get timer value + t/awake: func [event] [print "timer!"] + one-shot vs restart timer */ #include "sys-core.h" -/*********************************************************************** -** -*/ static int Event_Actor(REBVAL *ds, REBSER *port, REBCNT action) -/* -***********************************************************************/ +// +// Timer_Actor: C +// +static REB_R Timer_Actor(REBFRM *frame_, REBCTX *port, REBCNT action) { - REBVAL *spec; - REBVAL *state; - REBCNT result; - REBVAL *arg; - REBVAL save_port; - - Validate_Port(port, action); - - arg = D_ARG(2); - *D_RET = *D_ARG(1); - - // Validate and fetch relevant PORT fields: - state = BLK_SKIP(port, STD_PORT_STATE); - spec = BLK_SKIP(port, STD_PORT_SPEC); - if (!IS_OBJECT(spec)) Trap1(RE_INVALID_SPEC, spec); - - // Get or setup internal state data: - if (!IS_BLOCK(state)) Set_Block(state, Make_Block(127)); - - switch (action) { - - case A_UPDATE: - return R_NONE; - - // Normal block actions done on events: - case A_POKE: - if (!IS_EVENT(D_ARG(3))) Trap_Arg(D_ARG(3)); - goto act_blk; - case A_INSERT: - case A_APPEND: - //case A_PATH: // not allowed: port/foo is port object field access - //case A_PATH_SET: // not allowed: above - if (!IS_EVENT(arg)) Trap_Arg(arg); - case A_PICK: + REBVAL *spec; + REBVAL *state; + REBCNT result; + REBVAL *arg; + + DECLARE_LOCAL (save_port); + + arg = D_ARGC > 1 ? D_ARG(2) : NULL; + Move_Value(D_OUT, D_ARG(1)); + + // Validate and fetch relevant PORT fields: + state = CTX_VAR(port, STD_PORT_STATE); + spec = CTX_VAR(port, STD_PORT_SPEC); + if (!IS_OBJECT(spec)) fail (Error_Invalid_Spec_Raw(spec)); + + // Get or setup internal state data: + if (!IS_BLOCK(state)) + Init_Block(state, Make_Array(127)); + + switch (action) { + + case SYM_UPDATE: + return R_BLANK; + + // Normal block actions done on events: + case SYM_POKE: + if (NOT(IS_EVENT(D_ARG(3)))) + fail (D_ARG(3)); + goto act_blk; + case SYM_INSERT: + case SYM_APPEND: + //case SYM_PATH: // not allowed: port/foo is port object field access + //case SYM_PATH_SET: // not allowed: above + if (NOT(IS_EVENT(arg))) + fail (arg); + case SYM_PICK_P: act_blk: - save_port = *D_ARG(1); // save for return - *D_ARG(1) = *state; - result = T_Block(ds, action); - SET_FLAG(Eval_Signals, SIG_EVENT_PORT); - if (action == A_INSERT || action == A_APPEND || action == A_REMOVE) { - *D_RET = save_port; - break; - } - return result; // return condition - - case A_CLEAR: - VAL_TAIL(state) = 0; - VAL_BLK_TERM(state); - CLR_FLAG(Eval_Signals, SIG_EVENT_PORT); - break; - - case A_LENGTHQ: - SET_INTEGER(D_RET, VAL_TAIL(state)); - break; - - case A_OPEN: - if (!req) { //!!! - req = OS_MAKE_DEVREQ(RDI_EVENT); - SET_OPEN(req); - OS_DO_DEVICE(req, RDC_CONNECT); // stays queued - } - break; - - default: - Trap_Action(REB_PORT, action); - } - - return R_RET; + Move_Value(&save_port, D_ARG(1)); // save for return + Move_Value(D_ARG(1), state); + result = T_Block(ds, action); + SET_SIGNAL(SIG_EVENT_PORT); + if ( + action == SYM_INSERT + || action == SYM_APPEND + || action == SYM_REMOVE + ){ + Move_Value(D_OUT, save_port); + break; + } + return result; // return condition + + case SYM_CLEAR: + RESET_ARRAY(state); + CLR_FLAG(Eval_Signals, SIG_EVENT_PORT); + break; + + case SYM_LENGTH_OF: + Init_Integer(D_OUT, VAL_LEN_HEAD(state)); + break; + + case SYM_OPEN: { + INCLUDE_PARAMS_OF_OPEN; + if (!req) { //!!! + req = OS_MAKE_DEVREQ(RDI_EVENT); + SET_OPEN(req); + OS_DO_DEVICE(req, RDC_CONNECT); // stays queued + } + break; } + + default: + fail (Error_Illegal_Action(REB_PORT, action)); + } + + return R_OUT; } -/*********************************************************************** -** -*/ void Init_Timer_Scheme(void) -/* -***********************************************************************/ +// !!! Timer code is currently not used +//x +//x get-timer-actor-handle: native [ +//x +//x {Retrieve handle to the native actor for timer features} +//x +//x return: [handle!] +//x ] +//x +REBNATIVE(get_timer_actor_handle) { - Register_Scheme(SYM_TIMER, 0, Event_Actor); + Make_Port_Actor_Handle(D_OUT, &Timer_Actor); + return R_OUT; } diff --git a/src/core/s-cases.c b/src/core/s-cases.c index 131fdb538a..10352e3989 100644 --- a/src/core/s-cases.c +++ b/src/core/s-cases.c @@ -1,940 +1,951 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: s-cases.c -** Summary: unicode string case handling -** Section: strings -** Notes: -** -***********************************************************************/ +// +// File: %s-cases.c +// Summary: "unicode string case handling" +// Section: strings +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" // Unicode 5.0 case folding table: -static short const Char_Cases[] = { - 0x0041, 0x0061, // LATIN CAPITAL LETTER A - 0x0042, 0x0062, // LATIN CAPITAL LETTER B - 0x0043, 0x0063, // LATIN CAPITAL LETTER C - 0x0044, 0x0064, // LATIN CAPITAL LETTER D - 0x0045, 0x0065, // LATIN CAPITAL LETTER E - 0x0046, 0x0066, // LATIN CAPITAL LETTER F - 0x0047, 0x0067, // LATIN CAPITAL LETTER G - 0x0048, 0x0068, // LATIN CAPITAL LETTER H - 0x0049, 0x0069, // LATIN CAPITAL LETTER I - 0x004A, 0x006A, // LATIN CAPITAL LETTER J - 0x004B, 0x006B, // LATIN CAPITAL LETTER K - 0x004C, 0x006C, // LATIN CAPITAL LETTER L - 0x004D, 0x006D, // LATIN CAPITAL LETTER M - 0x004E, 0x006E, // LATIN CAPITAL LETTER N - 0x004F, 0x006F, // LATIN CAPITAL LETTER O - 0x0050, 0x0070, // LATIN CAPITAL LETTER P - 0x0051, 0x0071, // LATIN CAPITAL LETTER Q - 0x0052, 0x0072, // LATIN CAPITAL LETTER R - 0x0053, 0x0073, // LATIN CAPITAL LETTER S - 0x0054, 0x0074, // LATIN CAPITAL LETTER T - 0x0055, 0x0075, // LATIN CAPITAL LETTER U - 0x0056, 0x0076, // LATIN CAPITAL LETTER V - 0x0057, 0x0077, // LATIN CAPITAL LETTER W - 0x0058, 0x0078, // LATIN CAPITAL LETTER X - 0x0059, 0x0079, // LATIN CAPITAL LETTER Y - 0x005A, 0x007A, // LATIN CAPITAL LETTER Z - 0x00B5, 0x03BC, // MICRO SIGN - 0x00C0, 0x00E0, // LATIN CAPITAL LETTER A WITH GRAVE - 0x00C1, 0x00E1, // LATIN CAPITAL LETTER A WITH ACUTE - 0x00C2, 0x00E2, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX - 0x00C3, 0x00E3, // LATIN CAPITAL LETTER A WITH TILDE - 0x00C4, 0x00E4, // LATIN CAPITAL LETTER A WITH DIAERESIS - 0x00C5, 0x00E5, // LATIN CAPITAL LETTER A WITH RING ABOVE - 0x00C6, 0x00E6, // LATIN CAPITAL LETTER AE - 0x00C7, 0x00E7, // LATIN CAPITAL LETTER C WITH CEDILLA - 0x00C8, 0x00E8, // LATIN CAPITAL LETTER E WITH GRAVE - 0x00C9, 0x00E9, // LATIN CAPITAL LETTER E WITH ACUTE - 0x00CA, 0x00EA, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX - 0x00CB, 0x00EB, // LATIN CAPITAL LETTER E WITH DIAERESIS - 0x00CC, 0x00EC, // LATIN CAPITAL LETTER I WITH GRAVE - 0x00CD, 0x00ED, // LATIN CAPITAL LETTER I WITH ACUTE - 0x00CE, 0x00EE, // LATIN CAPITAL LETTER I WITH CIRCUMFLEX - 0x00CF, 0x00EF, // LATIN CAPITAL LETTER I WITH DIAERESIS - 0x00D0, 0x00F0, // LATIN CAPITAL LETTER ETH - 0x00D1, 0x00F1, // LATIN CAPITAL LETTER N WITH TILDE - 0x00D2, 0x00F2, // LATIN CAPITAL LETTER O WITH GRAVE - 0x00D3, 0x00F3, // LATIN CAPITAL LETTER O WITH ACUTE - 0x00D4, 0x00F4, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX - 0x00D5, 0x00F5, // LATIN CAPITAL LETTER O WITH TILDE - 0x00D6, 0x00F6, // LATIN CAPITAL LETTER O WITH DIAERESIS - 0x00D8, 0x00F8, // LATIN CAPITAL LETTER O WITH STROKE - 0x00D9, 0x00F9, // LATIN CAPITAL LETTER U WITH GRAVE - 0x00DA, 0x00FA, // LATIN CAPITAL LETTER U WITH ACUTE - 0x00DB, 0x00FB, // LATIN CAPITAL LETTER U WITH CIRCUMFLEX - 0x00DC, 0x00FC, // LATIN CAPITAL LETTER U WITH DIAERESIS - 0x00DD, 0x00FD, // LATIN CAPITAL LETTER Y WITH ACUTE - 0x00DE, 0x00FE, // LATIN CAPITAL LETTER THORN - 0x0100, 0x0101, // LATIN CAPITAL LETTER A WITH MACRON - 0x0102, 0x0103, // LATIN CAPITAL LETTER A WITH BREVE - 0x0104, 0x0105, // LATIN CAPITAL LETTER A WITH OGONEK - 0x0106, 0x0107, // LATIN CAPITAL LETTER C WITH ACUTE - 0x0108, 0x0109, // LATIN CAPITAL LETTER C WITH CIRCUMFLEX - 0x010A, 0x010B, // LATIN CAPITAL LETTER C WITH DOT ABOVE - 0x010C, 0x010D, // LATIN CAPITAL LETTER C WITH CARON - 0x010E, 0x010F, // LATIN CAPITAL LETTER D WITH CARON - 0x0110, 0x0111, // LATIN CAPITAL LETTER D WITH STROKE - 0x0112, 0x0113, // LATIN CAPITAL LETTER E WITH MACRON - 0x0114, 0x0115, // LATIN CAPITAL LETTER E WITH BREVE - 0x0116, 0x0117, // LATIN CAPITAL LETTER E WITH DOT ABOVE - 0x0118, 0x0119, // LATIN CAPITAL LETTER E WITH OGONEK - 0x011A, 0x011B, // LATIN CAPITAL LETTER E WITH CARON - 0x011C, 0x011D, // LATIN CAPITAL LETTER G WITH CIRCUMFLEX - 0x011E, 0x011F, // LATIN CAPITAL LETTER G WITH BREVE - 0x0120, 0x0121, // LATIN CAPITAL LETTER G WITH DOT ABOVE - 0x0122, 0x0123, // LATIN CAPITAL LETTER G WITH CEDILLA - 0x0124, 0x0125, // LATIN CAPITAL LETTER H WITH CIRCUMFLEX - 0x0126, 0x0127, // LATIN CAPITAL LETTER H WITH STROKE - 0x0128, 0x0129, // LATIN CAPITAL LETTER I WITH TILDE - 0x012A, 0x012B, // LATIN CAPITAL LETTER I WITH MACRON - 0x012C, 0x012D, // LATIN CAPITAL LETTER I WITH BREVE - 0x012E, 0x012F, // LATIN CAPITAL LETTER I WITH OGONEK - 0x0132, 0x0133, // LATIN CAPITAL LIGATURE IJ - 0x0134, 0x0135, // LATIN CAPITAL LETTER J WITH CIRCUMFLEX - 0x0136, 0x0137, // LATIN CAPITAL LETTER K WITH CEDILLA - 0x0139, 0x013A, // LATIN CAPITAL LETTER L WITH ACUTE - 0x013B, 0x013C, // LATIN CAPITAL LETTER L WITH CEDILLA - 0x013D, 0x013E, // LATIN CAPITAL LETTER L WITH CARON - 0x013F, 0x0140, // LATIN CAPITAL LETTER L WITH MIDDLE DOT - 0x0141, 0x0142, // LATIN CAPITAL LETTER L WITH STROKE - 0x0143, 0x0144, // LATIN CAPITAL LETTER N WITH ACUTE - 0x0145, 0x0146, // LATIN CAPITAL LETTER N WITH CEDILLA - 0x0147, 0x0148, // LATIN CAPITAL LETTER N WITH CARON - 0x014A, 0x014B, // LATIN CAPITAL LETTER ENG - 0x014C, 0x014D, // LATIN CAPITAL LETTER O WITH MACRON - 0x014E, 0x014F, // LATIN CAPITAL LETTER O WITH BREVE - 0x0150, 0x0151, // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE - 0x0152, 0x0153, // LATIN CAPITAL LIGATURE OE - 0x0154, 0x0155, // LATIN CAPITAL LETTER R WITH ACUTE - 0x0156, 0x0157, // LATIN CAPITAL LETTER R WITH CEDILLA - 0x0158, 0x0159, // LATIN CAPITAL LETTER R WITH CARON - 0x015A, 0x015B, // LATIN CAPITAL LETTER S WITH ACUTE - 0x015C, 0x015D, // LATIN CAPITAL LETTER S WITH CIRCUMFLEX - 0x015E, 0x015F, // LATIN CAPITAL LETTER S WITH CEDILLA - 0x0160, 0x0161, // LATIN CAPITAL LETTER S WITH CARON - 0x0162, 0x0163, // LATIN CAPITAL LETTER T WITH CEDILLA - 0x0164, 0x0165, // LATIN CAPITAL LETTER T WITH CARON - 0x0166, 0x0167, // LATIN CAPITAL LETTER T WITH STROKE - 0x0168, 0x0169, // LATIN CAPITAL LETTER U WITH TILDE - 0x016A, 0x016B, // LATIN CAPITAL LETTER U WITH MACRON - 0x016C, 0x016D, // LATIN CAPITAL LETTER U WITH BREVE - 0x016E, 0x016F, // LATIN CAPITAL LETTER U WITH RING ABOVE - 0x0170, 0x0171, // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE - 0x0172, 0x0173, // LATIN CAPITAL LETTER U WITH OGONEK - 0x0174, 0x0175, // LATIN CAPITAL LETTER W WITH CIRCUMFLEX - 0x0176, 0x0177, // LATIN CAPITAL LETTER Y WITH CIRCUMFLEX - 0x0178, 0x00FF, // LATIN CAPITAL LETTER Y WITH DIAERESIS - 0x0179, 0x017A, // LATIN CAPITAL LETTER Z WITH ACUTE - 0x017B, 0x017C, // LATIN CAPITAL LETTER Z WITH DOT ABOVE - 0x017D, 0x017E, // LATIN CAPITAL LETTER Z WITH CARON - 0x017F, 0x0073, // LATIN SMALL LETTER LONG S - 0x0181, 0x0253, // LATIN CAPITAL LETTER B WITH HOOK - 0x0182, 0x0183, // LATIN CAPITAL LETTER B WITH TOPBAR - 0x0184, 0x0185, // LATIN CAPITAL LETTER TONE SIX - 0x0186, 0x0254, // LATIN CAPITAL LETTER OPEN O - 0x0187, 0x0188, // LATIN CAPITAL LETTER C WITH HOOK - 0x0189, 0x0256, // LATIN CAPITAL LETTER AFRICAN D - 0x018A, 0x0257, // LATIN CAPITAL LETTER D WITH HOOK - 0x018B, 0x018C, // LATIN CAPITAL LETTER D WITH TOPBAR - 0x018E, 0x01DD, // LATIN CAPITAL LETTER REVERSED E - 0x018F, 0x0259, // LATIN CAPITAL LETTER SCHWA - 0x0190, 0x025B, // LATIN CAPITAL LETTER OPEN E - 0x0191, 0x0192, // LATIN CAPITAL LETTER F WITH HOOK - 0x0193, 0x0260, // LATIN CAPITAL LETTER G WITH HOOK - 0x0194, 0x0263, // LATIN CAPITAL LETTER GAMMA - 0x0196, 0x0269, // LATIN CAPITAL LETTER IOTA - 0x0197, 0x0268, // LATIN CAPITAL LETTER I WITH STROKE - 0x0198, 0x0199, // LATIN CAPITAL LETTER K WITH HOOK - 0x019C, 0x026F, // LATIN CAPITAL LETTER TURNED M - 0x019D, 0x0272, // LATIN CAPITAL LETTER N WITH LEFT HOOK - 0x019F, 0x0275, // LATIN CAPITAL LETTER O WITH MIDDLE TILDE - 0x01A0, 0x01A1, // LATIN CAPITAL LETTER O WITH HORN - 0x01A2, 0x01A3, // LATIN CAPITAL LETTER OI - 0x01A4, 0x01A5, // LATIN CAPITAL LETTER P WITH HOOK - 0x01A6, 0x0280, // LATIN LETTER YR - 0x01A7, 0x01A8, // LATIN CAPITAL LETTER TONE TWO - 0x01A9, 0x0283, // LATIN CAPITAL LETTER ESH - 0x01AC, 0x01AD, // LATIN CAPITAL LETTER T WITH HOOK - 0x01AE, 0x0288, // LATIN CAPITAL LETTER T WITH RETROFLEX HOOK - 0x01AF, 0x01B0, // LATIN CAPITAL LETTER U WITH HORN - 0x01B1, 0x028A, // LATIN CAPITAL LETTER UPSILON - 0x01B2, 0x028B, // LATIN CAPITAL LETTER V WITH HOOK - 0x01B3, 0x01B4, // LATIN CAPITAL LETTER Y WITH HOOK - 0x01B5, 0x01B6, // LATIN CAPITAL LETTER Z WITH STROKE - 0x01B7, 0x0292, // LATIN CAPITAL LETTER EZH - 0x01B8, 0x01B9, // LATIN CAPITAL LETTER EZH REVERSED - 0x01BC, 0x01BD, // LATIN CAPITAL LETTER TONE FIVE - 0x01C4, 0x01C6, // LATIN CAPITAL LETTER DZ WITH CARON - 0x01C5, 0x01C6, // LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON - 0x01C7, 0x01C9, // LATIN CAPITAL LETTER LJ - 0x01C8, 0x01C9, // LATIN CAPITAL LETTER L WITH SMALL LETTER J - 0x01CA, 0x01CC, // LATIN CAPITAL LETTER NJ - 0x01CB, 0x01CC, // LATIN CAPITAL LETTER N WITH SMALL LETTER J - 0x01CD, 0x01CE, // LATIN CAPITAL LETTER A WITH CARON - 0x01CF, 0x01D0, // LATIN CAPITAL LETTER I WITH CARON - 0x01D1, 0x01D2, // LATIN CAPITAL LETTER O WITH CARON - 0x01D3, 0x01D4, // LATIN CAPITAL LETTER U WITH CARON - 0x01D5, 0x01D6, // LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON - 0x01D7, 0x01D8, // LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE - 0x01D9, 0x01DA, // LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON - 0x01DB, 0x01DC, // LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE - 0x01DE, 0x01DF, // LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON - 0x01E0, 0x01E1, // LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON - 0x01E2, 0x01E3, // LATIN CAPITAL LETTER AE WITH MACRON - 0x01E4, 0x01E5, // LATIN CAPITAL LETTER G WITH STROKE - 0x01E6, 0x01E7, // LATIN CAPITAL LETTER G WITH CARON - 0x01E8, 0x01E9, // LATIN CAPITAL LETTER K WITH CARON - 0x01EA, 0x01EB, // LATIN CAPITAL LETTER O WITH OGONEK - 0x01EC, 0x01ED, // LATIN CAPITAL LETTER O WITH OGONEK AND MACRON - 0x01EE, 0x01EF, // LATIN CAPITAL LETTER EZH WITH CARON - 0x01F1, 0x01F3, // LATIN CAPITAL LETTER DZ - 0x01F2, 0x01F3, // LATIN CAPITAL LETTER D WITH SMALL LETTER Z - 0x01F4, 0x01F5, // LATIN CAPITAL LETTER G WITH ACUTE - 0x01F6, 0x0195, // LATIN CAPITAL LETTER HWAIR - 0x01F7, 0x01BF, // LATIN CAPITAL LETTER WYNN - 0x01F8, 0x01F9, // LATIN CAPITAL LETTER N WITH GRAVE - 0x01FA, 0x01FB, // LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE - 0x01FC, 0x01FD, // LATIN CAPITAL LETTER AE WITH ACUTE - 0x01FE, 0x01FF, // LATIN CAPITAL LETTER O WITH STROKE AND ACUTE - 0x0200, 0x0201, // LATIN CAPITAL LETTER A WITH DOUBLE GRAVE - 0x0202, 0x0203, // LATIN CAPITAL LETTER A WITH INVERTED BREVE - 0x0204, 0x0205, // LATIN CAPITAL LETTER E WITH DOUBLE GRAVE - 0x0206, 0x0207, // LATIN CAPITAL LETTER E WITH INVERTED BREVE - 0x0208, 0x0209, // LATIN CAPITAL LETTER I WITH DOUBLE GRAVE - 0x020A, 0x020B, // LATIN CAPITAL LETTER I WITH INVERTED BREVE - 0x020C, 0x020D, // LATIN CAPITAL LETTER O WITH DOUBLE GRAVE - 0x020E, 0x020F, // LATIN CAPITAL LETTER O WITH INVERTED BREVE - 0x0210, 0x0211, // LATIN CAPITAL LETTER R WITH DOUBLE GRAVE - 0x0212, 0x0213, // LATIN CAPITAL LETTER R WITH INVERTED BREVE - 0x0214, 0x0215, // LATIN CAPITAL LETTER U WITH DOUBLE GRAVE - 0x0216, 0x0217, // LATIN CAPITAL LETTER U WITH INVERTED BREVE - 0x0218, 0x0219, // LATIN CAPITAL LETTER S WITH COMMA BELOW - 0x021A, 0x021B, // LATIN CAPITAL LETTER T WITH COMMA BELOW - 0x021C, 0x021D, // LATIN CAPITAL LETTER YOGH - 0x021E, 0x021F, // LATIN CAPITAL LETTER H WITH CARON - 0x0220, 0x019E, // LATIN CAPITAL LETTER N WITH LONG RIGHT LEG - 0x0222, 0x0223, // LATIN CAPITAL LETTER OU - 0x0224, 0x0225, // LATIN CAPITAL LETTER Z WITH HOOK - 0x0226, 0x0227, // LATIN CAPITAL LETTER A WITH DOT ABOVE - 0x0228, 0x0229, // LATIN CAPITAL LETTER E WITH CEDILLA - 0x022A, 0x022B, // LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON - 0x022C, 0x022D, // LATIN CAPITAL LETTER O WITH TILDE AND MACRON - 0x022E, 0x022F, // LATIN CAPITAL LETTER O WITH DOT ABOVE - 0x0230, 0x0231, // LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON - 0x0232, 0x0233, // LATIN CAPITAL LETTER Y WITH MACRON - 0x023A, 0x2C65, // LATIN CAPITAL LETTER A WITH STROKE - 0x023B, 0x023C, // LATIN CAPITAL LETTER C WITH STROKE - 0x023D, 0x019A, // LATIN CAPITAL LETTER L WITH BAR - 0x023E, 0x2C66, // LATIN CAPITAL LETTER T WITH DIAGONAL STROKE - 0x0241, 0x0242, // LATIN CAPITAL LETTER GLOTTAL STOP - 0x0243, 0x0180, // LATIN CAPITAL LETTER B WITH STROKE - 0x0244, 0x0289, // LATIN CAPITAL LETTER U BAR - 0x0245, 0x028C, // LATIN CAPITAL LETTER TURNED V - 0x0246, 0x0247, // LATIN CAPITAL LETTER E WITH STROKE - 0x0248, 0x0249, // LATIN CAPITAL LETTER J WITH STROKE - 0x024A, 0x024B, // LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL - 0x024C, 0x024D, // LATIN CAPITAL LETTER R WITH STROKE - 0x024E, 0x024F, // LATIN CAPITAL LETTER Y WITH STROKE - 0x0345, 0x03B9, // COMBINING GREEK YPOGEGRAMMENI - 0x0386, 0x03AC, // GREEK CAPITAL LETTER ALPHA WITH TONOS - 0x0388, 0x03AD, // GREEK CAPITAL LETTER EPSILON WITH TONOS - 0x0389, 0x03AE, // GREEK CAPITAL LETTER ETA WITH TONOS - 0x038A, 0x03AF, // GREEK CAPITAL LETTER IOTA WITH TONOS - 0x038C, 0x03CC, // GREEK CAPITAL LETTER OMICRON WITH TONOS - 0x038E, 0x03CD, // GREEK CAPITAL LETTER UPSILON WITH TONOS - 0x038F, 0x03CE, // GREEK CAPITAL LETTER OMEGA WITH TONOS - 0x0391, 0x03B1, // GREEK CAPITAL LETTER ALPHA - 0x0392, 0x03B2, // GREEK CAPITAL LETTER BETA - 0x0393, 0x03B3, // GREEK CAPITAL LETTER GAMMA - 0x0394, 0x03B4, // GREEK CAPITAL LETTER DELTA - 0x0395, 0x03B5, // GREEK CAPITAL LETTER EPSILON - 0x0396, 0x03B6, // GREEK CAPITAL LETTER ZETA - 0x0397, 0x03B7, // GREEK CAPITAL LETTER ETA - 0x0398, 0x03B8, // GREEK CAPITAL LETTER THETA - 0x0399, 0x03B9, // GREEK CAPITAL LETTER IOTA - 0x039A, 0x03BA, // GREEK CAPITAL LETTER KAPPA - 0x039B, 0x03BB, // GREEK CAPITAL LETTER LAMDA - 0x039C, 0x03BC, // GREEK CAPITAL LETTER MU - 0x039D, 0x03BD, // GREEK CAPITAL LETTER NU - 0x039E, 0x03BE, // GREEK CAPITAL LETTER XI - 0x039F, 0x03BF, // GREEK CAPITAL LETTER OMICRON - 0x03A0, 0x03C0, // GREEK CAPITAL LETTER PI - 0x03A1, 0x03C1, // GREEK CAPITAL LETTER RHO - 0x03A3, 0x03C3, // GREEK CAPITAL LETTER SIGMA - 0x03A4, 0x03C4, // GREEK CAPITAL LETTER TAU - 0x03A5, 0x03C5, // GREEK CAPITAL LETTER UPSILON - 0x03A6, 0x03C6, // GREEK CAPITAL LETTER PHI - 0x03A7, 0x03C7, // GREEK CAPITAL LETTER CHI - 0x03A8, 0x03C8, // GREEK CAPITAL LETTER PSI - 0x03A9, 0x03C9, // GREEK CAPITAL LETTER OMEGA - 0x03AA, 0x03CA, // GREEK CAPITAL LETTER IOTA WITH DIALYTIKA - 0x03AB, 0x03CB, // GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA - 0x03C2, 0x03C3, // GREEK SMALL LETTER FINAL SIGMA - 0x03D0, 0x03B2, // GREEK BETA SYMBOL - 0x03D1, 0x03B8, // GREEK THETA SYMBOL - 0x03D5, 0x03C6, // GREEK PHI SYMBOL - 0x03D6, 0x03C0, // GREEK PI SYMBOL - 0x03D8, 0x03D9, // GREEK LETTER ARCHAIC KOPPA - 0x03DA, 0x03DB, // GREEK LETTER STIGMA - 0x03DC, 0x03DD, // GREEK LETTER DIGAMMA - 0x03DE, 0x03DF, // GREEK LETTER KOPPA - 0x03E0, 0x03E1, // GREEK LETTER SAMPI - 0x03E2, 0x03E3, // COPTIC CAPITAL LETTER SHEI - 0x03E4, 0x03E5, // COPTIC CAPITAL LETTER FEI - 0x03E6, 0x03E7, // COPTIC CAPITAL LETTER KHEI - 0x03E8, 0x03E9, // COPTIC CAPITAL LETTER HORI - 0x03EA, 0x03EB, // COPTIC CAPITAL LETTER GANGIA - 0x03EC, 0x03ED, // COPTIC CAPITAL LETTER SHIMA - 0x03EE, 0x03EF, // COPTIC CAPITAL LETTER DEI - 0x03F0, 0x03BA, // GREEK KAPPA SYMBOL - 0x03F1, 0x03C1, // GREEK RHO SYMBOL - 0x03F4, 0x03B8, // GREEK CAPITAL THETA SYMBOL - 0x03F5, 0x03B5, // GREEK LUNATE EPSILON SYMBOL - 0x03F7, 0x03F8, // GREEK CAPITAL LETTER SHO - 0x03F9, 0x03F2, // GREEK CAPITAL LUNATE SIGMA SYMBOL - 0x03FA, 0x03FB, // GREEK CAPITAL LETTER SAN - 0x03FD, 0x037B, // GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL - 0x03FE, 0x037C, // GREEK CAPITAL DOTTED LUNATE SIGMA SYMBOL - 0x03FF, 0x037D, // GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL - 0x0400, 0x0450, // CYRILLIC CAPITAL LETTER IE WITH GRAVE - 0x0401, 0x0451, // CYRILLIC CAPITAL LETTER IO - 0x0402, 0x0452, // CYRILLIC CAPITAL LETTER DJE - 0x0403, 0x0453, // CYRILLIC CAPITAL LETTER GJE - 0x0404, 0x0454, // CYRILLIC CAPITAL LETTER UKRAINIAN IE - 0x0405, 0x0455, // CYRILLIC CAPITAL LETTER DZE - 0x0406, 0x0456, // CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I - 0x0407, 0x0457, // CYRILLIC CAPITAL LETTER YI - 0x0408, 0x0458, // CYRILLIC CAPITAL LETTER JE - 0x0409, 0x0459, // CYRILLIC CAPITAL LETTER LJE - 0x040A, 0x045A, // CYRILLIC CAPITAL LETTER NJE - 0x040B, 0x045B, // CYRILLIC CAPITAL LETTER TSHE - 0x040C, 0x045C, // CYRILLIC CAPITAL LETTER KJE - 0x040D, 0x045D, // CYRILLIC CAPITAL LETTER I WITH GRAVE - 0x040E, 0x045E, // CYRILLIC CAPITAL LETTER SHORT U - 0x040F, 0x045F, // CYRILLIC CAPITAL LETTER DZHE - 0x0410, 0x0430, // CYRILLIC CAPITAL LETTER A - 0x0411, 0x0431, // CYRILLIC CAPITAL LETTER BE - 0x0412, 0x0432, // CYRILLIC CAPITAL LETTER VE - 0x0413, 0x0433, // CYRILLIC CAPITAL LETTER GHE - 0x0414, 0x0434, // CYRILLIC CAPITAL LETTER DE - 0x0415, 0x0435, // CYRILLIC CAPITAL LETTER IE - 0x0416, 0x0436, // CYRILLIC CAPITAL LETTER ZHE - 0x0417, 0x0437, // CYRILLIC CAPITAL LETTER ZE - 0x0418, 0x0438, // CYRILLIC CAPITAL LETTER I - 0x0419, 0x0439, // CYRILLIC CAPITAL LETTER SHORT I - 0x041A, 0x043A, // CYRILLIC CAPITAL LETTER KA - 0x041B, 0x043B, // CYRILLIC CAPITAL LETTER EL - 0x041C, 0x043C, // CYRILLIC CAPITAL LETTER EM - 0x041D, 0x043D, // CYRILLIC CAPITAL LETTER EN - 0x041E, 0x043E, // CYRILLIC CAPITAL LETTER O - 0x041F, 0x043F, // CYRILLIC CAPITAL LETTER PE - 0x0420, 0x0440, // CYRILLIC CAPITAL LETTER ER - 0x0421, 0x0441, // CYRILLIC CAPITAL LETTER ES - 0x0422, 0x0442, // CYRILLIC CAPITAL LETTER TE - 0x0423, 0x0443, // CYRILLIC CAPITAL LETTER U - 0x0424, 0x0444, // CYRILLIC CAPITAL LETTER EF - 0x0425, 0x0445, // CYRILLIC CAPITAL LETTER HA - 0x0426, 0x0446, // CYRILLIC CAPITAL LETTER TSE - 0x0427, 0x0447, // CYRILLIC CAPITAL LETTER CHE - 0x0428, 0x0448, // CYRILLIC CAPITAL LETTER SHA - 0x0429, 0x0449, // CYRILLIC CAPITAL LETTER SHCHA - 0x042A, 0x044A, // CYRILLIC CAPITAL LETTER HARD SIGN - 0x042B, 0x044B, // CYRILLIC CAPITAL LETTER YERU - 0x042C, 0x044C, // CYRILLIC CAPITAL LETTER SOFT SIGN - 0x042D, 0x044D, // CYRILLIC CAPITAL LETTER E - 0x042E, 0x044E, // CYRILLIC CAPITAL LETTER YU - 0x042F, 0x044F, // CYRILLIC CAPITAL LETTER YA - 0x0460, 0x0461, // CYRILLIC CAPITAL LETTER OMEGA - 0x0462, 0x0463, // CYRILLIC CAPITAL LETTER YAT - 0x0464, 0x0465, // CYRILLIC CAPITAL LETTER IOTIFIED E - 0x0466, 0x0467, // CYRILLIC CAPITAL LETTER LITTLE YUS - 0x0468, 0x0469, // CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS - 0x046A, 0x046B, // CYRILLIC CAPITAL LETTER BIG YUS - 0x046C, 0x046D, // CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS - 0x046E, 0x046F, // CYRILLIC CAPITAL LETTER KSI - 0x0470, 0x0471, // CYRILLIC CAPITAL LETTER PSI - 0x0472, 0x0473, // CYRILLIC CAPITAL LETTER FITA - 0x0474, 0x0475, // CYRILLIC CAPITAL LETTER IZHITSA - 0x0476, 0x0477, // CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT - 0x0478, 0x0479, // CYRILLIC CAPITAL LETTER UK - 0x047A, 0x047B, // CYRILLIC CAPITAL LETTER ROUND OMEGA - 0x047C, 0x047D, // CYRILLIC CAPITAL LETTER OMEGA WITH TITLO - 0x047E, 0x047F, // CYRILLIC CAPITAL LETTER OT - 0x0480, 0x0481, // CYRILLIC CAPITAL LETTER KOPPA - 0x048A, 0x048B, // CYRILLIC CAPITAL LETTER SHORT I WITH TAIL - 0x048C, 0x048D, // CYRILLIC CAPITAL LETTER SEMISOFT SIGN - 0x048E, 0x048F, // CYRILLIC CAPITAL LETTER ER WITH TICK - 0x0490, 0x0491, // CYRILLIC CAPITAL LETTER GHE WITH UPTURN - 0x0492, 0x0493, // CYRILLIC CAPITAL LETTER GHE WITH STROKE - 0x0494, 0x0495, // CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK - 0x0496, 0x0497, // CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER - 0x0498, 0x0499, // CYRILLIC CAPITAL LETTER ZE WITH DESCENDER - 0x049A, 0x049B, // CYRILLIC CAPITAL LETTER KA WITH DESCENDER - 0x049C, 0x049D, // CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE - 0x049E, 0x049F, // CYRILLIC CAPITAL LETTER KA WITH STROKE - 0x04A0, 0x04A1, // CYRILLIC CAPITAL LETTER BASHKIR KA - 0x04A2, 0x04A3, // CYRILLIC CAPITAL LETTER EN WITH DESCENDER - 0x04A4, 0x04A5, // CYRILLIC CAPITAL LIGATURE EN GHE - 0x04A6, 0x04A7, // CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK - 0x04A8, 0x04A9, // CYRILLIC CAPITAL LETTER ABKHASIAN HA - 0x04AA, 0x04AB, // CYRILLIC CAPITAL LETTER ES WITH DESCENDER - 0x04AC, 0x04AD, // CYRILLIC CAPITAL LETTER TE WITH DESCENDER - 0x04AE, 0x04AF, // CYRILLIC CAPITAL LETTER STRAIGHT U - 0x04B0, 0x04B1, // CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE - 0x04B2, 0x04B3, // CYRILLIC CAPITAL LETTER HA WITH DESCENDER - 0x04B4, 0x04B5, // CYRILLIC CAPITAL LIGATURE TE TSE - 0x04B6, 0x04B7, // CYRILLIC CAPITAL LETTER CHE WITH DESCENDER - 0x04B8, 0x04B9, // CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE - 0x04BA, 0x04BB, // CYRILLIC CAPITAL LETTER SHHA - 0x04BC, 0x04BD, // CYRILLIC CAPITAL LETTER ABKHASIAN CHE - 0x04BE, 0x04BF, // CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER - 0x04C0, 0x04CF, // CYRILLIC LETTER PALOCHKA - 0x04C1, 0x04C2, // CYRILLIC CAPITAL LETTER ZHE WITH BREVE - 0x04C3, 0x04C4, // CYRILLIC CAPITAL LETTER KA WITH HOOK - 0x04C5, 0x04C6, // CYRILLIC CAPITAL LETTER EL WITH TAIL - 0x04C7, 0x04C8, // CYRILLIC CAPITAL LETTER EN WITH HOOK - 0x04C9, 0x04CA, // CYRILLIC CAPITAL LETTER EN WITH TAIL - 0x04CB, 0x04CC, // CYRILLIC CAPITAL LETTER KHAKASSIAN CHE - 0x04CD, 0x04CE, // CYRILLIC CAPITAL LETTER EM WITH TAIL - 0x04D0, 0x04D1, // CYRILLIC CAPITAL LETTER A WITH BREVE - 0x04D2, 0x04D3, // CYRILLIC CAPITAL LETTER A WITH DIAERESIS - 0x04D4, 0x04D5, // CYRILLIC CAPITAL LIGATURE A IE - 0x04D6, 0x04D7, // CYRILLIC CAPITAL LETTER IE WITH BREVE - 0x04D8, 0x04D9, // CYRILLIC CAPITAL LETTER SCHWA - 0x04DA, 0x04DB, // CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS - 0x04DC, 0x04DD, // CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS - 0x04DE, 0x04DF, // CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS - 0x04E0, 0x04E1, // CYRILLIC CAPITAL LETTER ABKHASIAN DZE - 0x04E2, 0x04E3, // CYRILLIC CAPITAL LETTER I WITH MACRON - 0x04E4, 0x04E5, // CYRILLIC CAPITAL LETTER I WITH DIAERESIS - 0x04E6, 0x04E7, // CYRILLIC CAPITAL LETTER O WITH DIAERESIS - 0x04E8, 0x04E9, // CYRILLIC CAPITAL LETTER BARRED O - 0x04EA, 0x04EB, // CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS - 0x04EC, 0x04ED, // CYRILLIC CAPITAL LETTER E WITH DIAERESIS - 0x04EE, 0x04EF, // CYRILLIC CAPITAL LETTER U WITH MACRON - 0x04F0, 0x04F1, // CYRILLIC CAPITAL LETTER U WITH DIAERESIS - 0x04F2, 0x04F3, // CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE - 0x04F4, 0x04F5, // CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS - 0x04F6, 0x04F7, // CYRILLIC CAPITAL LETTER GHE WITH DESCENDER - 0x04F8, 0x04F9, // CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS - 0x04FA, 0x04FB, // CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK - 0x04FC, 0x04FD, // CYRILLIC CAPITAL LETTER HA WITH HOOK - 0x04FE, 0x04FF, // CYRILLIC CAPITAL LETTER HA WITH STROKE - 0x0500, 0x0501, // CYRILLIC CAPITAL LETTER KOMI DE - 0x0502, 0x0503, // CYRILLIC CAPITAL LETTER KOMI DJE - 0x0504, 0x0505, // CYRILLIC CAPITAL LETTER KOMI ZJE - 0x0506, 0x0507, // CYRILLIC CAPITAL LETTER KOMI DZJE - 0x0508, 0x0509, // CYRILLIC CAPITAL LETTER KOMI LJE - 0x050A, 0x050B, // CYRILLIC CAPITAL LETTER KOMI NJE - 0x050C, 0x050D, // CYRILLIC CAPITAL LETTER KOMI SJE - 0x050E, 0x050F, // CYRILLIC CAPITAL LETTER KOMI TJE - 0x0510, 0x0511, // CYRILLIC CAPITAL LETTER REVERSED ZE - 0x0512, 0x0513, // CYRILLIC CAPITAL LETTER EL WITH HOOK - 0x0531, 0x0561, // ARMENIAN CAPITAL LETTER AYB - 0x0532, 0x0562, // ARMENIAN CAPITAL LETTER BEN - 0x0533, 0x0563, // ARMENIAN CAPITAL LETTER GIM - 0x0534, 0x0564, // ARMENIAN CAPITAL LETTER DA - 0x0535, 0x0565, // ARMENIAN CAPITAL LETTER ECH - 0x0536, 0x0566, // ARMENIAN CAPITAL LETTER ZA - 0x0537, 0x0567, // ARMENIAN CAPITAL LETTER EH - 0x0538, 0x0568, // ARMENIAN CAPITAL LETTER ET - 0x0539, 0x0569, // ARMENIAN CAPITAL LETTER TO - 0x053A, 0x056A, // ARMENIAN CAPITAL LETTER ZHE - 0x053B, 0x056B, // ARMENIAN CAPITAL LETTER INI - 0x053C, 0x056C, // ARMENIAN CAPITAL LETTER LIWN - 0x053D, 0x056D, // ARMENIAN CAPITAL LETTER XEH - 0x053E, 0x056E, // ARMENIAN CAPITAL LETTER CA - 0x053F, 0x056F, // ARMENIAN CAPITAL LETTER KEN - 0x0540, 0x0570, // ARMENIAN CAPITAL LETTER HO - 0x0541, 0x0571, // ARMENIAN CAPITAL LETTER JA - 0x0542, 0x0572, // ARMENIAN CAPITAL LETTER GHAD - 0x0543, 0x0573, // ARMENIAN CAPITAL LETTER CHEH - 0x0544, 0x0574, // ARMENIAN CAPITAL LETTER MEN - 0x0545, 0x0575, // ARMENIAN CAPITAL LETTER YI - 0x0546, 0x0576, // ARMENIAN CAPITAL LETTER NOW - 0x0547, 0x0577, // ARMENIAN CAPITAL LETTER SHA - 0x0548, 0x0578, // ARMENIAN CAPITAL LETTER VO - 0x0549, 0x0579, // ARMENIAN CAPITAL LETTER CHA - 0x054A, 0x057A, // ARMENIAN CAPITAL LETTER PEH - 0x054B, 0x057B, // ARMENIAN CAPITAL LETTER JHEH - 0x054C, 0x057C, // ARMENIAN CAPITAL LETTER RA - 0x054D, 0x057D, // ARMENIAN CAPITAL LETTER SEH - 0x054E, 0x057E, // ARMENIAN CAPITAL LETTER VEW - 0x054F, 0x057F, // ARMENIAN CAPITAL LETTER TIWN - 0x0550, 0x0580, // ARMENIAN CAPITAL LETTER REH - 0x0551, 0x0581, // ARMENIAN CAPITAL LETTER CO - 0x0552, 0x0582, // ARMENIAN CAPITAL LETTER YIWN - 0x0553, 0x0583, // ARMENIAN CAPITAL LETTER PIWR - 0x0554, 0x0584, // ARMENIAN CAPITAL LETTER KEH - 0x0555, 0x0585, // ARMENIAN CAPITAL LETTER OH - 0x0556, 0x0586, // ARMENIAN CAPITAL LETTER FEH - 0x10A0, 0x2D00, // GEORGIAN CAPITAL LETTER AN - 0x10A1, 0x2D01, // GEORGIAN CAPITAL LETTER BAN - 0x10A2, 0x2D02, // GEORGIAN CAPITAL LETTER GAN - 0x10A3, 0x2D03, // GEORGIAN CAPITAL LETTER DON - 0x10A4, 0x2D04, // GEORGIAN CAPITAL LETTER EN - 0x10A5, 0x2D05, // GEORGIAN CAPITAL LETTER VIN - 0x10A6, 0x2D06, // GEORGIAN CAPITAL LETTER ZEN - 0x10A7, 0x2D07, // GEORGIAN CAPITAL LETTER TAN - 0x10A8, 0x2D08, // GEORGIAN CAPITAL LETTER IN - 0x10A9, 0x2D09, // GEORGIAN CAPITAL LETTER KAN - 0x10AA, 0x2D0A, // GEORGIAN CAPITAL LETTER LAS - 0x10AB, 0x2D0B, // GEORGIAN CAPITAL LETTER MAN - 0x10AC, 0x2D0C, // GEORGIAN CAPITAL LETTER NAR - 0x10AD, 0x2D0D, // GEORGIAN CAPITAL LETTER ON - 0x10AE, 0x2D0E, // GEORGIAN CAPITAL LETTER PAR - 0x10AF, 0x2D0F, // GEORGIAN CAPITAL LETTER ZHAR - 0x10B0, 0x2D10, // GEORGIAN CAPITAL LETTER RAE - 0x10B1, 0x2D11, // GEORGIAN CAPITAL LETTER SAN - 0x10B2, 0x2D12, // GEORGIAN CAPITAL LETTER TAR - 0x10B3, 0x2D13, // GEORGIAN CAPITAL LETTER UN - 0x10B4, 0x2D14, // GEORGIAN CAPITAL LETTER PHAR - 0x10B5, 0x2D15, // GEORGIAN CAPITAL LETTER KHAR - 0x10B6, 0x2D16, // GEORGIAN CAPITAL LETTER GHAN - 0x10B7, 0x2D17, // GEORGIAN CAPITAL LETTER QAR - 0x10B8, 0x2D18, // GEORGIAN CAPITAL LETTER SHIN - 0x10B9, 0x2D19, // GEORGIAN CAPITAL LETTER CHIN - 0x10BA, 0x2D1A, // GEORGIAN CAPITAL LETTER CAN - 0x10BB, 0x2D1B, // GEORGIAN CAPITAL LETTER JIL - 0x10BC, 0x2D1C, // GEORGIAN CAPITAL LETTER CIL - 0x10BD, 0x2D1D, // GEORGIAN CAPITAL LETTER CHAR - 0x10BE, 0x2D1E, // GEORGIAN CAPITAL LETTER XAN - 0x10BF, 0x2D1F, // GEORGIAN CAPITAL LETTER JHAN - 0x10C0, 0x2D20, // GEORGIAN CAPITAL LETTER HAE - 0x10C1, 0x2D21, // GEORGIAN CAPITAL LETTER HE - 0x10C2, 0x2D22, // GEORGIAN CAPITAL LETTER HIE - 0x10C3, 0x2D23, // GEORGIAN CAPITAL LETTER WE - 0x10C4, 0x2D24, // GEORGIAN CAPITAL LETTER HAR - 0x10C5, 0x2D25, // GEORGIAN CAPITAL LETTER HOE - 0x1E00, 0x1E01, // LATIN CAPITAL LETTER A WITH RING BELOW - 0x1E02, 0x1E03, // LATIN CAPITAL LETTER B WITH DOT ABOVE - 0x1E04, 0x1E05, // LATIN CAPITAL LETTER B WITH DOT BELOW - 0x1E06, 0x1E07, // LATIN CAPITAL LETTER B WITH LINE BELOW - 0x1E08, 0x1E09, // LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE - 0x1E0A, 0x1E0B, // LATIN CAPITAL LETTER D WITH DOT ABOVE - 0x1E0C, 0x1E0D, // LATIN CAPITAL LETTER D WITH DOT BELOW - 0x1E0E, 0x1E0F, // LATIN CAPITAL LETTER D WITH LINE BELOW - 0x1E10, 0x1E11, // LATIN CAPITAL LETTER D WITH CEDILLA - 0x1E12, 0x1E13, // LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW - 0x1E14, 0x1E15, // LATIN CAPITAL LETTER E WITH MACRON AND GRAVE - 0x1E16, 0x1E17, // LATIN CAPITAL LETTER E WITH MACRON AND ACUTE - 0x1E18, 0x1E19, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW - 0x1E1A, 0x1E1B, // LATIN CAPITAL LETTER E WITH TILDE BELOW - 0x1E1C, 0x1E1D, // LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE - 0x1E1E, 0x1E1F, // LATIN CAPITAL LETTER F WITH DOT ABOVE - 0x1E20, 0x1E21, // LATIN CAPITAL LETTER G WITH MACRON - 0x1E22, 0x1E23, // LATIN CAPITAL LETTER H WITH DOT ABOVE - 0x1E24, 0x1E25, // LATIN CAPITAL LETTER H WITH DOT BELOW - 0x1E26, 0x1E27, // LATIN CAPITAL LETTER H WITH DIAERESIS - 0x1E28, 0x1E29, // LATIN CAPITAL LETTER H WITH CEDILLA - 0x1E2A, 0x1E2B, // LATIN CAPITAL LETTER H WITH BREVE BELOW - 0x1E2C, 0x1E2D, // LATIN CAPITAL LETTER I WITH TILDE BELOW - 0x1E2E, 0x1E2F, // LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE - 0x1E30, 0x1E31, // LATIN CAPITAL LETTER K WITH ACUTE - 0x1E32, 0x1E33, // LATIN CAPITAL LETTER K WITH DOT BELOW - 0x1E34, 0x1E35, // LATIN CAPITAL LETTER K WITH LINE BELOW - 0x1E36, 0x1E37, // LATIN CAPITAL LETTER L WITH DOT BELOW - 0x1E38, 0x1E39, // LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON - 0x1E3A, 0x1E3B, // LATIN CAPITAL LETTER L WITH LINE BELOW - 0x1E3C, 0x1E3D, // LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW - 0x1E3E, 0x1E3F, // LATIN CAPITAL LETTER M WITH ACUTE - 0x1E40, 0x1E41, // LATIN CAPITAL LETTER M WITH DOT ABOVE - 0x1E42, 0x1E43, // LATIN CAPITAL LETTER M WITH DOT BELOW - 0x1E44, 0x1E45, // LATIN CAPITAL LETTER N WITH DOT ABOVE - 0x1E46, 0x1E47, // LATIN CAPITAL LETTER N WITH DOT BELOW - 0x1E48, 0x1E49, // LATIN CAPITAL LETTER N WITH LINE BELOW - 0x1E4A, 0x1E4B, // LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW - 0x1E4C, 0x1E4D, // LATIN CAPITAL LETTER O WITH TILDE AND ACUTE - 0x1E4E, 0x1E4F, // LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS - 0x1E50, 0x1E51, // LATIN CAPITAL LETTER O WITH MACRON AND GRAVE - 0x1E52, 0x1E53, // LATIN CAPITAL LETTER O WITH MACRON AND ACUTE - 0x1E54, 0x1E55, // LATIN CAPITAL LETTER P WITH ACUTE - 0x1E56, 0x1E57, // LATIN CAPITAL LETTER P WITH DOT ABOVE - 0x1E58, 0x1E59, // LATIN CAPITAL LETTER R WITH DOT ABOVE - 0x1E5A, 0x1E5B, // LATIN CAPITAL LETTER R WITH DOT BELOW - 0x1E5C, 0x1E5D, // LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON - 0x1E5E, 0x1E5F, // LATIN CAPITAL LETTER R WITH LINE BELOW - 0x1E60, 0x1E61, // LATIN CAPITAL LETTER S WITH DOT ABOVE - 0x1E62, 0x1E63, // LATIN CAPITAL LETTER S WITH DOT BELOW - 0x1E64, 0x1E65, // LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE - 0x1E66, 0x1E67, // LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE - 0x1E68, 0x1E69, // LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE - 0x1E6A, 0x1E6B, // LATIN CAPITAL LETTER T WITH DOT ABOVE - 0x1E6C, 0x1E6D, // LATIN CAPITAL LETTER T WITH DOT BELOW - 0x1E6E, 0x1E6F, // LATIN CAPITAL LETTER T WITH LINE BELOW - 0x1E70, 0x1E71, // LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW - 0x1E72, 0x1E73, // LATIN CAPITAL LETTER U WITH DIAERESIS BELOW - 0x1E74, 0x1E75, // LATIN CAPITAL LETTER U WITH TILDE BELOW - 0x1E76, 0x1E77, // LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW - 0x1E78, 0x1E79, // LATIN CAPITAL LETTER U WITH TILDE AND ACUTE - 0x1E7A, 0x1E7B, // LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS - 0x1E7C, 0x1E7D, // LATIN CAPITAL LETTER V WITH TILDE - 0x1E7E, 0x1E7F, // LATIN CAPITAL LETTER V WITH DOT BELOW - 0x1E80, 0x1E81, // LATIN CAPITAL LETTER W WITH GRAVE - 0x1E82, 0x1E83, // LATIN CAPITAL LETTER W WITH ACUTE - 0x1E84, 0x1E85, // LATIN CAPITAL LETTER W WITH DIAERESIS - 0x1E86, 0x1E87, // LATIN CAPITAL LETTER W WITH DOT ABOVE - 0x1E88, 0x1E89, // LATIN CAPITAL LETTER W WITH DOT BELOW - 0x1E8A, 0x1E8B, // LATIN CAPITAL LETTER X WITH DOT ABOVE - 0x1E8C, 0x1E8D, // LATIN CAPITAL LETTER X WITH DIAERESIS - 0x1E8E, 0x1E8F, // LATIN CAPITAL LETTER Y WITH DOT ABOVE - 0x1E90, 0x1E91, // LATIN CAPITAL LETTER Z WITH CIRCUMFLEX - 0x1E92, 0x1E93, // LATIN CAPITAL LETTER Z WITH DOT BELOW - 0x1E94, 0x1E95, // LATIN CAPITAL LETTER Z WITH LINE BELOW - 0x1E9B, 0x1E61, // LATIN SMALL LETTER LONG S WITH DOT ABOVE - 0x1EA0, 0x1EA1, // LATIN CAPITAL LETTER A WITH DOT BELOW - 0x1EA2, 0x1EA3, // LATIN CAPITAL LETTER A WITH HOOK ABOVE - 0x1EA4, 0x1EA5, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE - 0x1EA6, 0x1EA7, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE - 0x1EA8, 0x1EA9, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE - 0x1EAA, 0x1EAB, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE - 0x1EAC, 0x1EAD, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW - 0x1EAE, 0x1EAF, // LATIN CAPITAL LETTER A WITH BREVE AND ACUTE - 0x1EB0, 0x1EB1, // LATIN CAPITAL LETTER A WITH BREVE AND GRAVE - 0x1EB2, 0x1EB3, // LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE - 0x1EB4, 0x1EB5, // LATIN CAPITAL LETTER A WITH BREVE AND TILDE - 0x1EB6, 0x1EB7, // LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW - 0x1EB8, 0x1EB9, // LATIN CAPITAL LETTER E WITH DOT BELOW - 0x1EBA, 0x1EBB, // LATIN CAPITAL LETTER E WITH HOOK ABOVE - 0x1EBC, 0x1EBD, // LATIN CAPITAL LETTER E WITH TILDE - 0x1EBE, 0x1EBF, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE - 0x1EC0, 0x1EC1, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE - 0x1EC2, 0x1EC3, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE - 0x1EC4, 0x1EC5, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE - 0x1EC6, 0x1EC7, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW - 0x1EC8, 0x1EC9, // LATIN CAPITAL LETTER I WITH HOOK ABOVE - 0x1ECA, 0x1ECB, // LATIN CAPITAL LETTER I WITH DOT BELOW - 0x1ECC, 0x1ECD, // LATIN CAPITAL LETTER O WITH DOT BELOW - 0x1ECE, 0x1ECF, // LATIN CAPITAL LETTER O WITH HOOK ABOVE - 0x1ED0, 0x1ED1, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE - 0x1ED2, 0x1ED3, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE - 0x1ED4, 0x1ED5, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE - 0x1ED6, 0x1ED7, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE - 0x1ED8, 0x1ED9, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW - 0x1EDA, 0x1EDB, // LATIN CAPITAL LETTER O WITH HORN AND ACUTE - 0x1EDC, 0x1EDD, // LATIN CAPITAL LETTER O WITH HORN AND GRAVE - 0x1EDE, 0x1EDF, // LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE - 0x1EE0, 0x1EE1, // LATIN CAPITAL LETTER O WITH HORN AND TILDE - 0x1EE2, 0x1EE3, // LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW - 0x1EE4, 0x1EE5, // LATIN CAPITAL LETTER U WITH DOT BELOW - 0x1EE6, 0x1EE7, // LATIN CAPITAL LETTER U WITH HOOK ABOVE - 0x1EE8, 0x1EE9, // LATIN CAPITAL LETTER U WITH HORN AND ACUTE - 0x1EEA, 0x1EEB, // LATIN CAPITAL LETTER U WITH HORN AND GRAVE - 0x1EEC, 0x1EED, // LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE - 0x1EEE, 0x1EEF, // LATIN CAPITAL LETTER U WITH HORN AND TILDE - 0x1EF0, 0x1EF1, // LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW - 0x1EF2, 0x1EF3, // LATIN CAPITAL LETTER Y WITH GRAVE - 0x1EF4, 0x1EF5, // LATIN CAPITAL LETTER Y WITH DOT BELOW - 0x1EF6, 0x1EF7, // LATIN CAPITAL LETTER Y WITH HOOK ABOVE - 0x1EF8, 0x1EF9, // LATIN CAPITAL LETTER Y WITH TILDE - 0x1F08, 0x1F00, // GREEK CAPITAL LETTER ALPHA WITH PSILI - 0x1F09, 0x1F01, // GREEK CAPITAL LETTER ALPHA WITH DASIA - 0x1F0A, 0x1F02, // GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA - 0x1F0B, 0x1F03, // GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA - 0x1F0C, 0x1F04, // GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA - 0x1F0D, 0x1F05, // GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA - 0x1F0E, 0x1F06, // GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI - 0x1F0F, 0x1F07, // GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI - 0x1F18, 0x1F10, // GREEK CAPITAL LETTER EPSILON WITH PSILI - 0x1F19, 0x1F11, // GREEK CAPITAL LETTER EPSILON WITH DASIA - 0x1F1A, 0x1F12, // GREEK CAPITAL LETTER EPSILON WITH PSILI AND VARIA - 0x1F1B, 0x1F13, // GREEK CAPITAL LETTER EPSILON WITH DASIA AND VARIA - 0x1F1C, 0x1F14, // GREEK CAPITAL LETTER EPSILON WITH PSILI AND OXIA - 0x1F1D, 0x1F15, // GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA - 0x1F28, 0x1F20, // GREEK CAPITAL LETTER ETA WITH PSILI - 0x1F29, 0x1F21, // GREEK CAPITAL LETTER ETA WITH DASIA - 0x1F2A, 0x1F22, // GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA - 0x1F2B, 0x1F23, // GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA - 0x1F2C, 0x1F24, // GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA - 0x1F2D, 0x1F25, // GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA - 0x1F2E, 0x1F26, // GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI - 0x1F2F, 0x1F27, // GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI - 0x1F38, 0x1F30, // GREEK CAPITAL LETTER IOTA WITH PSILI - 0x1F39, 0x1F31, // GREEK CAPITAL LETTER IOTA WITH DASIA - 0x1F3A, 0x1F32, // GREEK CAPITAL LETTER IOTA WITH PSILI AND VARIA - 0x1F3B, 0x1F33, // GREEK CAPITAL LETTER IOTA WITH DASIA AND VARIA - 0x1F3C, 0x1F34, // GREEK CAPITAL LETTER IOTA WITH PSILI AND OXIA - 0x1F3D, 0x1F35, // GREEK CAPITAL LETTER IOTA WITH DASIA AND OXIA - 0x1F3E, 0x1F36, // GREEK CAPITAL LETTER IOTA WITH PSILI AND PERISPOMENI - 0x1F3F, 0x1F37, // GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI - 0x1F48, 0x1F40, // GREEK CAPITAL LETTER OMICRON WITH PSILI - 0x1F49, 0x1F41, // GREEK CAPITAL LETTER OMICRON WITH DASIA - 0x1F4A, 0x1F42, // GREEK CAPITAL LETTER OMICRON WITH PSILI AND VARIA - 0x1F4B, 0x1F43, // GREEK CAPITAL LETTER OMICRON WITH DASIA AND VARIA - 0x1F4C, 0x1F44, // GREEK CAPITAL LETTER OMICRON WITH PSILI AND OXIA - 0x1F4D, 0x1F45, // GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA - 0x1F59, 0x1F51, // GREEK CAPITAL LETTER UPSILON WITH DASIA - 0x1F5B, 0x1F53, // GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA - 0x1F5D, 0x1F55, // GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA - 0x1F5F, 0x1F57, // GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI - 0x1F68, 0x1F60, // GREEK CAPITAL LETTER OMEGA WITH PSILI - 0x1F69, 0x1F61, // GREEK CAPITAL LETTER OMEGA WITH DASIA - 0x1F6A, 0x1F62, // GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA - 0x1F6B, 0x1F63, // GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA - 0x1F6C, 0x1F64, // GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA - 0x1F6D, 0x1F65, // GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA - 0x1F6E, 0x1F66, // GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI - 0x1F6F, 0x1F67, // GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI - 0x1F88, 0x1F80, // GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI - 0x1F89, 0x1F81, // GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI - 0x1F8A, 0x1F82, // GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI - 0x1F8B, 0x1F83, // GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI - 0x1F8C, 0x1F84, // GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI - 0x1F8D, 0x1F85, // GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI - 0x1F8E, 0x1F86, // GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI - 0x1F8F, 0x1F87, // GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI - 0x1F98, 0x1F90, // GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI - 0x1F99, 0x1F91, // GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI - 0x1F9A, 0x1F92, // GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI - 0x1F9B, 0x1F93, // GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI - 0x1F9C, 0x1F94, // GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI - 0x1F9D, 0x1F95, // GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI - 0x1F9E, 0x1F96, // GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI - 0x1F9F, 0x1F97, // GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI - 0x1FA8, 0x1FA0, // GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI - 0x1FA9, 0x1FA1, // GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI - 0x1FAA, 0x1FA2, // GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI - 0x1FAB, 0x1FA3, // GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI - 0x1FAC, 0x1FA4, // GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI - 0x1FAD, 0x1FA5, // GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI - 0x1FAE, 0x1FA6, // GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI - 0x1FAF, 0x1FA7, // GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI - 0x1FB8, 0x1FB0, // GREEK CAPITAL LETTER ALPHA WITH VRACHY - 0x1FB9, 0x1FB1, // GREEK CAPITAL LETTER ALPHA WITH MACRON - 0x1FBA, 0x1F70, // GREEK CAPITAL LETTER ALPHA WITH VARIA - 0x1FBB, 0x1F71, // GREEK CAPITAL LETTER ALPHA WITH OXIA - 0x1FBC, 0x1FB3, // GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI - 0x1FBE, 0x03B9, // GREEK PROSGEGRAMMENI - 0x1FC8, 0x1F72, // GREEK CAPITAL LETTER EPSILON WITH VARIA - 0x1FC9, 0x1F73, // GREEK CAPITAL LETTER EPSILON WITH OXIA - 0x1FCA, 0x1F74, // GREEK CAPITAL LETTER ETA WITH VARIA - 0x1FCB, 0x1F75, // GREEK CAPITAL LETTER ETA WITH OXIA - 0x1FCC, 0x1FC3, // GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI - 0x1FD8, 0x1FD0, // GREEK CAPITAL LETTER IOTA WITH VRACHY - 0x1FD9, 0x1FD1, // GREEK CAPITAL LETTER IOTA WITH MACRON - 0x1FDA, 0x1F76, // GREEK CAPITAL LETTER IOTA WITH VARIA - 0x1FDB, 0x1F77, // GREEK CAPITAL LETTER IOTA WITH OXIA - 0x1FE8, 0x1FE0, // GREEK CAPITAL LETTER UPSILON WITH VRACHY - 0x1FE9, 0x1FE1, // GREEK CAPITAL LETTER UPSILON WITH MACRON - 0x1FEA, 0x1F7A, // GREEK CAPITAL LETTER UPSILON WITH VARIA - 0x1FEB, 0x1F7B, // GREEK CAPITAL LETTER UPSILON WITH OXIA - 0x1FEC, 0x1FE5, // GREEK CAPITAL LETTER RHO WITH DASIA - 0x1FF8, 0x1F78, // GREEK CAPITAL LETTER OMICRON WITH VARIA - 0x1FF9, 0x1F79, // GREEK CAPITAL LETTER OMICRON WITH OXIA - 0x1FFA, 0x1F7C, // GREEK CAPITAL LETTER OMEGA WITH VARIA - 0x1FFB, 0x1F7D, // GREEK CAPITAL LETTER OMEGA WITH OXIA - 0x1FFC, 0x1FF3, // GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI - 0x2126, 0x03C9, // OHM SIGN - 0x212A, 0x006B, // KELVIN SIGN - 0x212B, 0x00E5, // ANGSTROM SIGN - 0x2132, 0x214E, // TURNED CAPITAL F - 0x2160, 0x2170, // ROMAN NUMERAL ONE - 0x2161, 0x2171, // ROMAN NUMERAL TWO - 0x2162, 0x2172, // ROMAN NUMERAL THREE - 0x2163, 0x2173, // ROMAN NUMERAL FOUR - 0x2164, 0x2174, // ROMAN NUMERAL FIVE - 0x2165, 0x2175, // ROMAN NUMERAL SIX - 0x2166, 0x2176, // ROMAN NUMERAL SEVEN - 0x2167, 0x2177, // ROMAN NUMERAL EIGHT - 0x2168, 0x2178, // ROMAN NUMERAL NINE - 0x2169, 0x2179, // ROMAN NUMERAL TEN - 0x216A, 0x217A, // ROMAN NUMERAL ELEVEN - 0x216B, 0x217B, // ROMAN NUMERAL TWELVE - 0x216C, 0x217C, // ROMAN NUMERAL FIFTY - 0x216D, 0x217D, // ROMAN NUMERAL ONE HUNDRED - 0x216E, 0x217E, // ROMAN NUMERAL FIVE HUNDRED - 0x216F, 0x217F, // ROMAN NUMERAL ONE THOUSAND - 0x2183, 0x2184, // ROMAN NUMERAL REVERSED ONE HUNDRED - 0x24B6, 0x24D0, // CIRCLED LATIN CAPITAL LETTER A - 0x24B7, 0x24D1, // CIRCLED LATIN CAPITAL LETTER B - 0x24B8, 0x24D2, // CIRCLED LATIN CAPITAL LETTER C - 0x24B9, 0x24D3, // CIRCLED LATIN CAPITAL LETTER D - 0x24BA, 0x24D4, // CIRCLED LATIN CAPITAL LETTER E - 0x24BB, 0x24D5, // CIRCLED LATIN CAPITAL LETTER F - 0x24BC, 0x24D6, // CIRCLED LATIN CAPITAL LETTER G - 0x24BD, 0x24D7, // CIRCLED LATIN CAPITAL LETTER H - 0x24BE, 0x24D8, // CIRCLED LATIN CAPITAL LETTER I - 0x24BF, 0x24D9, // CIRCLED LATIN CAPITAL LETTER J - 0x24C0, 0x24DA, // CIRCLED LATIN CAPITAL LETTER K - 0x24C1, 0x24DB, // CIRCLED LATIN CAPITAL LETTER L - 0x24C2, 0x24DC, // CIRCLED LATIN CAPITAL LETTER M - 0x24C3, 0x24DD, // CIRCLED LATIN CAPITAL LETTER N - 0x24C4, 0x24DE, // CIRCLED LATIN CAPITAL LETTER O - 0x24C5, 0x24DF, // CIRCLED LATIN CAPITAL LETTER P - 0x24C6, 0x24E0, // CIRCLED LATIN CAPITAL LETTER Q - 0x24C7, 0x24E1, // CIRCLED LATIN CAPITAL LETTER R - 0x24C8, 0x24E2, // CIRCLED LATIN CAPITAL LETTER S - 0x24C9, 0x24E3, // CIRCLED LATIN CAPITAL LETTER T - 0x24CA, 0x24E4, // CIRCLED LATIN CAPITAL LETTER U - 0x24CB, 0x24E5, // CIRCLED LATIN CAPITAL LETTER V - 0x24CC, 0x24E6, // CIRCLED LATIN CAPITAL LETTER W - 0x24CD, 0x24E7, // CIRCLED LATIN CAPITAL LETTER X - 0x24CE, 0x24E8, // CIRCLED LATIN CAPITAL LETTER Y - 0x24CF, 0x24E9, // CIRCLED LATIN CAPITAL LETTER Z - 0x2C00, 0x2C30, // GLAGOLITIC CAPITAL LETTER AZU - 0x2C01, 0x2C31, // GLAGOLITIC CAPITAL LETTER BUKY - 0x2C02, 0x2C32, // GLAGOLITIC CAPITAL LETTER VEDE - 0x2C03, 0x2C33, // GLAGOLITIC CAPITAL LETTER GLAGOLI - 0x2C04, 0x2C34, // GLAGOLITIC CAPITAL LETTER DOBRO - 0x2C05, 0x2C35, // GLAGOLITIC CAPITAL LETTER YESTU - 0x2C06, 0x2C36, // GLAGOLITIC CAPITAL LETTER ZHIVETE - 0x2C07, 0x2C37, // GLAGOLITIC CAPITAL LETTER DZELO - 0x2C08, 0x2C38, // GLAGOLITIC CAPITAL LETTER ZEMLJA - 0x2C09, 0x2C39, // GLAGOLITIC CAPITAL LETTER IZHE - 0x2C0A, 0x2C3A, // GLAGOLITIC CAPITAL LETTER INITIAL IZHE - 0x2C0B, 0x2C3B, // GLAGOLITIC CAPITAL LETTER I - 0x2C0C, 0x2C3C, // GLAGOLITIC CAPITAL LETTER DJERVI - 0x2C0D, 0x2C3D, // GLAGOLITIC CAPITAL LETTER KAKO - 0x2C0E, 0x2C3E, // GLAGOLITIC CAPITAL LETTER LJUDIJE - 0x2C0F, 0x2C3F, // GLAGOLITIC CAPITAL LETTER MYSLITE - 0x2C10, 0x2C40, // GLAGOLITIC CAPITAL LETTER NASHI - 0x2C11, 0x2C41, // GLAGOLITIC CAPITAL LETTER ONU - 0x2C12, 0x2C42, // GLAGOLITIC CAPITAL LETTER POKOJI - 0x2C13, 0x2C43, // GLAGOLITIC CAPITAL LETTER RITSI - 0x2C14, 0x2C44, // GLAGOLITIC CAPITAL LETTER SLOVO - 0x2C15, 0x2C45, // GLAGOLITIC CAPITAL LETTER TVRIDO - 0x2C16, 0x2C46, // GLAGOLITIC CAPITAL LETTER UKU - 0x2C17, 0x2C47, // GLAGOLITIC CAPITAL LETTER FRITU - 0x2C18, 0x2C48, // GLAGOLITIC CAPITAL LETTER HERU - 0x2C19, 0x2C49, // GLAGOLITIC CAPITAL LETTER OTU - 0x2C1A, 0x2C4A, // GLAGOLITIC CAPITAL LETTER PE - 0x2C1B, 0x2C4B, // GLAGOLITIC CAPITAL LETTER SHTA - 0x2C1C, 0x2C4C, // GLAGOLITIC CAPITAL LETTER TSI - 0x2C1D, 0x2C4D, // GLAGOLITIC CAPITAL LETTER CHRIVI - 0x2C1E, 0x2C4E, // GLAGOLITIC CAPITAL LETTER SHA - 0x2C1F, 0x2C4F, // GLAGOLITIC CAPITAL LETTER YERU - 0x2C20, 0x2C50, // GLAGOLITIC CAPITAL LETTER YERI - 0x2C21, 0x2C51, // GLAGOLITIC CAPITAL LETTER YATI - 0x2C22, 0x2C52, // GLAGOLITIC CAPITAL LETTER SPIDERY HA - 0x2C23, 0x2C53, // GLAGOLITIC CAPITAL LETTER YU - 0x2C24, 0x2C54, // GLAGOLITIC CAPITAL LETTER SMALL YUS - 0x2C25, 0x2C55, // GLAGOLITIC CAPITAL LETTER SMALL YUS WITH TAIL - 0x2C26, 0x2C56, // GLAGOLITIC CAPITAL LETTER YO - 0x2C27, 0x2C57, // GLAGOLITIC CAPITAL LETTER IOTATED SMALL YUS - 0x2C28, 0x2C58, // GLAGOLITIC CAPITAL LETTER BIG YUS - 0x2C29, 0x2C59, // GLAGOLITIC CAPITAL LETTER IOTATED BIG YUS - 0x2C2A, 0x2C5A, // GLAGOLITIC CAPITAL LETTER FITA - 0x2C2B, 0x2C5B, // GLAGOLITIC CAPITAL LETTER IZHITSA - 0x2C2C, 0x2C5C, // GLAGOLITIC CAPITAL LETTER SHTAPIC - 0x2C2D, 0x2C5D, // GLAGOLITIC CAPITAL LETTER TROKUTASTI A - 0x2C2E, 0x2C5E, // GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE - 0x2C60, 0x2C61, // LATIN CAPITAL LETTER L WITH DOUBLE BAR - 0x2C62, 0x026B, // LATIN CAPITAL LETTER L WITH MIDDLE TILDE - 0x2C63, 0x1D7D, // LATIN CAPITAL LETTER P WITH STROKE - 0x2C64, 0x027D, // LATIN CAPITAL LETTER R WITH TAIL - 0x2C67, 0x2C68, // LATIN CAPITAL LETTER H WITH DESCENDER - 0x2C69, 0x2C6A, // LATIN CAPITAL LETTER K WITH DESCENDER - 0x2C6B, 0x2C6C, // LATIN CAPITAL LETTER Z WITH DESCENDER - 0x2C75, 0x2C76, // LATIN CAPITAL LETTER HALF H - 0x2C80, 0x2C81, // COPTIC CAPITAL LETTER ALFA - 0x2C82, 0x2C83, // COPTIC CAPITAL LETTER VIDA - 0x2C84, 0x2C85, // COPTIC CAPITAL LETTER GAMMA - 0x2C86, 0x2C87, // COPTIC CAPITAL LETTER DALDA - 0x2C88, 0x2C89, // COPTIC CAPITAL LETTER EIE - 0x2C8A, 0x2C8B, // COPTIC CAPITAL LETTER SOU - 0x2C8C, 0x2C8D, // COPTIC CAPITAL LETTER ZATA - 0x2C8E, 0x2C8F, // COPTIC CAPITAL LETTER HATE - 0x2C90, 0x2C91, // COPTIC CAPITAL LETTER THETHE - 0x2C92, 0x2C93, // COPTIC CAPITAL LETTER IAUDA - 0x2C94, 0x2C95, // COPTIC CAPITAL LETTER KAPA - 0x2C96, 0x2C97, // COPTIC CAPITAL LETTER LAULA - 0x2C98, 0x2C99, // COPTIC CAPITAL LETTER MI - 0x2C9A, 0x2C9B, // COPTIC CAPITAL LETTER NI - 0x2C9C, 0x2C9D, // COPTIC CAPITAL LETTER KSI - 0x2C9E, 0x2C9F, // COPTIC CAPITAL LETTER O - 0x2CA0, 0x2CA1, // COPTIC CAPITAL LETTER PI - 0x2CA2, 0x2CA3, // COPTIC CAPITAL LETTER RO - 0x2CA4, 0x2CA5, // COPTIC CAPITAL LETTER SIMA - 0x2CA6, 0x2CA7, // COPTIC CAPITAL LETTER TAU - 0x2CA8, 0x2CA9, // COPTIC CAPITAL LETTER UA - 0x2CAA, 0x2CAB, // COPTIC CAPITAL LETTER FI - 0x2CAC, 0x2CAD, // COPTIC CAPITAL LETTER KHI - 0x2CAE, 0x2CAF, // COPTIC CAPITAL LETTER PSI - 0x2CB0, 0x2CB1, // COPTIC CAPITAL LETTER OOU - 0x2CB2, 0x2CB3, // COPTIC CAPITAL LETTER DIALECT-P ALEF - 0x2CB4, 0x2CB5, // COPTIC CAPITAL LETTER OLD COPTIC AIN - 0x2CB6, 0x2CB7, // COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE - 0x2CB8, 0x2CB9, // COPTIC CAPITAL LETTER DIALECT-P KAPA - 0x2CBA, 0x2CBB, // COPTIC CAPITAL LETTER DIALECT-P NI - 0x2CBC, 0x2CBD, // COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI - 0x2CBE, 0x2CBF, // COPTIC CAPITAL LETTER OLD COPTIC OOU - 0x2CC0, 0x2CC1, // COPTIC CAPITAL LETTER SAMPI - 0x2CC2, 0x2CC3, // COPTIC CAPITAL LETTER CROSSED SHEI - 0x2CC4, 0x2CC5, // COPTIC CAPITAL LETTER OLD COPTIC SHEI - 0x2CC6, 0x2CC7, // COPTIC CAPITAL LETTER OLD COPTIC ESH - 0x2CC8, 0x2CC9, // COPTIC CAPITAL LETTER AKHMIMIC KHEI - 0x2CCA, 0x2CCB, // COPTIC CAPITAL LETTER DIALECT-P HORI - 0x2CCC, 0x2CCD, // COPTIC CAPITAL LETTER OLD COPTIC HORI - 0x2CCE, 0x2CCF, // COPTIC CAPITAL LETTER OLD COPTIC HA - 0x2CD0, 0x2CD1, // COPTIC CAPITAL LETTER L-SHAPED HA - 0x2CD2, 0x2CD3, // COPTIC CAPITAL LETTER OLD COPTIC HEI - 0x2CD4, 0x2CD5, // COPTIC CAPITAL LETTER OLD COPTIC HAT - 0x2CD6, 0x2CD7, // COPTIC CAPITAL LETTER OLD COPTIC GANGIA - 0x2CD8, 0x2CD9, // COPTIC CAPITAL LETTER OLD COPTIC DJA - 0x2CDA, 0x2CDB, // COPTIC CAPITAL LETTER OLD COPTIC SHIMA - 0x2CDC, 0x2CDD, // COPTIC CAPITAL LETTER OLD NUBIAN SHIMA - 0x2CDE, 0x2CDF, // COPTIC CAPITAL LETTER OLD NUBIAN NGI - 0x2CE0, 0x2CE1, // COPTIC CAPITAL LETTER OLD NUBIAN NYI - 0x2CE2, 0x2CE3, // COPTIC CAPITAL LETTER OLD NUBIAN WAU - 0, 0 +static const REBUNI Char_Cases[] = { + 0x0041, 0x0061, // LATIN CAPITAL LETTER A + 0x0042, 0x0062, // LATIN CAPITAL LETTER B + 0x0043, 0x0063, // LATIN CAPITAL LETTER C + 0x0044, 0x0064, // LATIN CAPITAL LETTER D + 0x0045, 0x0065, // LATIN CAPITAL LETTER E + 0x0046, 0x0066, // LATIN CAPITAL LETTER F + 0x0047, 0x0067, // LATIN CAPITAL LETTER G + 0x0048, 0x0068, // LATIN CAPITAL LETTER H + 0x0049, 0x0069, // LATIN CAPITAL LETTER I + 0x004A, 0x006A, // LATIN CAPITAL LETTER J + 0x004B, 0x006B, // LATIN CAPITAL LETTER K + 0x004C, 0x006C, // LATIN CAPITAL LETTER L + 0x004D, 0x006D, // LATIN CAPITAL LETTER M + 0x004E, 0x006E, // LATIN CAPITAL LETTER N + 0x004F, 0x006F, // LATIN CAPITAL LETTER O + 0x0050, 0x0070, // LATIN CAPITAL LETTER P + 0x0051, 0x0071, // LATIN CAPITAL LETTER Q + 0x0052, 0x0072, // LATIN CAPITAL LETTER R + 0x0053, 0x0073, // LATIN CAPITAL LETTER S + 0x0054, 0x0074, // LATIN CAPITAL LETTER T + 0x0055, 0x0075, // LATIN CAPITAL LETTER U + 0x0056, 0x0076, // LATIN CAPITAL LETTER V + 0x0057, 0x0077, // LATIN CAPITAL LETTER W + 0x0058, 0x0078, // LATIN CAPITAL LETTER X + 0x0059, 0x0079, // LATIN CAPITAL LETTER Y + 0x005A, 0x007A, // LATIN CAPITAL LETTER Z + 0x00B5, 0x03BC, // MICRO SIGN + 0x00C0, 0x00E0, // LATIN CAPITAL LETTER A WITH GRAVE + 0x00C1, 0x00E1, // LATIN CAPITAL LETTER A WITH ACUTE + 0x00C2, 0x00E2, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX + 0x00C3, 0x00E3, // LATIN CAPITAL LETTER A WITH TILDE + 0x00C4, 0x00E4, // LATIN CAPITAL LETTER A WITH DIAERESIS + 0x00C5, 0x00E5, // LATIN CAPITAL LETTER A WITH RING ABOVE + 0x00C6, 0x00E6, // LATIN CAPITAL LETTER AE + 0x00C7, 0x00E7, // LATIN CAPITAL LETTER C WITH CEDILLA + 0x00C8, 0x00E8, // LATIN CAPITAL LETTER E WITH GRAVE + 0x00C9, 0x00E9, // LATIN CAPITAL LETTER E WITH ACUTE + 0x00CA, 0x00EA, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX + 0x00CB, 0x00EB, // LATIN CAPITAL LETTER E WITH DIAERESIS + 0x00CC, 0x00EC, // LATIN CAPITAL LETTER I WITH GRAVE + 0x00CD, 0x00ED, // LATIN CAPITAL LETTER I WITH ACUTE + 0x00CE, 0x00EE, // LATIN CAPITAL LETTER I WITH CIRCUMFLEX + 0x00CF, 0x00EF, // LATIN CAPITAL LETTER I WITH DIAERESIS + 0x00D0, 0x00F0, // LATIN CAPITAL LETTER ETH + 0x00D1, 0x00F1, // LATIN CAPITAL LETTER N WITH TILDE + 0x00D2, 0x00F2, // LATIN CAPITAL LETTER O WITH GRAVE + 0x00D3, 0x00F3, // LATIN CAPITAL LETTER O WITH ACUTE + 0x00D4, 0x00F4, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX + 0x00D5, 0x00F5, // LATIN CAPITAL LETTER O WITH TILDE + 0x00D6, 0x00F6, // LATIN CAPITAL LETTER O WITH DIAERESIS + 0x00D8, 0x00F8, // LATIN CAPITAL LETTER O WITH STROKE + 0x00D9, 0x00F9, // LATIN CAPITAL LETTER U WITH GRAVE + 0x00DA, 0x00FA, // LATIN CAPITAL LETTER U WITH ACUTE + 0x00DB, 0x00FB, // LATIN CAPITAL LETTER U WITH CIRCUMFLEX + 0x00DC, 0x00FC, // LATIN CAPITAL LETTER U WITH DIAERESIS + 0x00DD, 0x00FD, // LATIN CAPITAL LETTER Y WITH ACUTE + 0x00DE, 0x00FE, // LATIN CAPITAL LETTER THORN + 0x0100, 0x0101, // LATIN CAPITAL LETTER A WITH MACRON + 0x0102, 0x0103, // LATIN CAPITAL LETTER A WITH BREVE + 0x0104, 0x0105, // LATIN CAPITAL LETTER A WITH OGONEK + 0x0106, 0x0107, // LATIN CAPITAL LETTER C WITH ACUTE + 0x0108, 0x0109, // LATIN CAPITAL LETTER C WITH CIRCUMFLEX + 0x010A, 0x010B, // LATIN CAPITAL LETTER C WITH DOT ABOVE + 0x010C, 0x010D, // LATIN CAPITAL LETTER C WITH CARON + 0x010E, 0x010F, // LATIN CAPITAL LETTER D WITH CARON + 0x0110, 0x0111, // LATIN CAPITAL LETTER D WITH STROKE + 0x0112, 0x0113, // LATIN CAPITAL LETTER E WITH MACRON + 0x0114, 0x0115, // LATIN CAPITAL LETTER E WITH BREVE + 0x0116, 0x0117, // LATIN CAPITAL LETTER E WITH DOT ABOVE + 0x0118, 0x0119, // LATIN CAPITAL LETTER E WITH OGONEK + 0x011A, 0x011B, // LATIN CAPITAL LETTER E WITH CARON + 0x011C, 0x011D, // LATIN CAPITAL LETTER G WITH CIRCUMFLEX + 0x011E, 0x011F, // LATIN CAPITAL LETTER G WITH BREVE + 0x0120, 0x0121, // LATIN CAPITAL LETTER G WITH DOT ABOVE + 0x0122, 0x0123, // LATIN CAPITAL LETTER G WITH CEDILLA + 0x0124, 0x0125, // LATIN CAPITAL LETTER H WITH CIRCUMFLEX + 0x0126, 0x0127, // LATIN CAPITAL LETTER H WITH STROKE + 0x0128, 0x0129, // LATIN CAPITAL LETTER I WITH TILDE + 0x012A, 0x012B, // LATIN CAPITAL LETTER I WITH MACRON + 0x012C, 0x012D, // LATIN CAPITAL LETTER I WITH BREVE + 0x012E, 0x012F, // LATIN CAPITAL LETTER I WITH OGONEK + 0x0132, 0x0133, // LATIN CAPITAL LIGATURE IJ + 0x0134, 0x0135, // LATIN CAPITAL LETTER J WITH CIRCUMFLEX + 0x0136, 0x0137, // LATIN CAPITAL LETTER K WITH CEDILLA + 0x0139, 0x013A, // LATIN CAPITAL LETTER L WITH ACUTE + 0x013B, 0x013C, // LATIN CAPITAL LETTER L WITH CEDILLA + 0x013D, 0x013E, // LATIN CAPITAL LETTER L WITH CARON + 0x013F, 0x0140, // LATIN CAPITAL LETTER L WITH MIDDLE DOT + 0x0141, 0x0142, // LATIN CAPITAL LETTER L WITH STROKE + 0x0143, 0x0144, // LATIN CAPITAL LETTER N WITH ACUTE + 0x0145, 0x0146, // LATIN CAPITAL LETTER N WITH CEDILLA + 0x0147, 0x0148, // LATIN CAPITAL LETTER N WITH CARON + 0x014A, 0x014B, // LATIN CAPITAL LETTER ENG + 0x014C, 0x014D, // LATIN CAPITAL LETTER O WITH MACRON + 0x014E, 0x014F, // LATIN CAPITAL LETTER O WITH BREVE + 0x0150, 0x0151, // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE + 0x0152, 0x0153, // LATIN CAPITAL LIGATURE OE + 0x0154, 0x0155, // LATIN CAPITAL LETTER R WITH ACUTE + 0x0156, 0x0157, // LATIN CAPITAL LETTER R WITH CEDILLA + 0x0158, 0x0159, // LATIN CAPITAL LETTER R WITH CARON + 0x015A, 0x015B, // LATIN CAPITAL LETTER S WITH ACUTE + 0x015C, 0x015D, // LATIN CAPITAL LETTER S WITH CIRCUMFLEX + 0x015E, 0x015F, // LATIN CAPITAL LETTER S WITH CEDILLA + 0x0160, 0x0161, // LATIN CAPITAL LETTER S WITH CARON + 0x0162, 0x0163, // LATIN CAPITAL LETTER T WITH CEDILLA + 0x0164, 0x0165, // LATIN CAPITAL LETTER T WITH CARON + 0x0166, 0x0167, // LATIN CAPITAL LETTER T WITH STROKE + 0x0168, 0x0169, // LATIN CAPITAL LETTER U WITH TILDE + 0x016A, 0x016B, // LATIN CAPITAL LETTER U WITH MACRON + 0x016C, 0x016D, // LATIN CAPITAL LETTER U WITH BREVE + 0x016E, 0x016F, // LATIN CAPITAL LETTER U WITH RING ABOVE + 0x0170, 0x0171, // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE + 0x0172, 0x0173, // LATIN CAPITAL LETTER U WITH OGONEK + 0x0174, 0x0175, // LATIN CAPITAL LETTER W WITH CIRCUMFLEX + 0x0176, 0x0177, // LATIN CAPITAL LETTER Y WITH CIRCUMFLEX + 0x0178, 0x00FF, // LATIN CAPITAL LETTER Y WITH DIAERESIS + 0x0179, 0x017A, // LATIN CAPITAL LETTER Z WITH ACUTE + 0x017B, 0x017C, // LATIN CAPITAL LETTER Z WITH DOT ABOVE + 0x017D, 0x017E, // LATIN CAPITAL LETTER Z WITH CARON + 0x017F, 0x0073, // LATIN SMALL LETTER LONG S + 0x0181, 0x0253, // LATIN CAPITAL LETTER B WITH HOOK + 0x0182, 0x0183, // LATIN CAPITAL LETTER B WITH TOPBAR + 0x0184, 0x0185, // LATIN CAPITAL LETTER TONE SIX + 0x0186, 0x0254, // LATIN CAPITAL LETTER OPEN O + 0x0187, 0x0188, // LATIN CAPITAL LETTER C WITH HOOK + 0x0189, 0x0256, // LATIN CAPITAL LETTER AFRICAN D + 0x018A, 0x0257, // LATIN CAPITAL LETTER D WITH HOOK + 0x018B, 0x018C, // LATIN CAPITAL LETTER D WITH TOPBAR + 0x018E, 0x01DD, // LATIN CAPITAL LETTER REVERSED E + 0x018F, 0x0259, // LATIN CAPITAL LETTER SCHWA + 0x0190, 0x025B, // LATIN CAPITAL LETTER OPEN E + 0x0191, 0x0192, // LATIN CAPITAL LETTER F WITH HOOK + 0x0193, 0x0260, // LATIN CAPITAL LETTER G WITH HOOK + 0x0194, 0x0263, // LATIN CAPITAL LETTER GAMMA + 0x0196, 0x0269, // LATIN CAPITAL LETTER IOTA + 0x0197, 0x0268, // LATIN CAPITAL LETTER I WITH STROKE + 0x0198, 0x0199, // LATIN CAPITAL LETTER K WITH HOOK + 0x019C, 0x026F, // LATIN CAPITAL LETTER TURNED M + 0x019D, 0x0272, // LATIN CAPITAL LETTER N WITH LEFT HOOK + 0x019F, 0x0275, // LATIN CAPITAL LETTER O WITH MIDDLE TILDE + 0x01A0, 0x01A1, // LATIN CAPITAL LETTER O WITH HORN + 0x01A2, 0x01A3, // LATIN CAPITAL LETTER OI + 0x01A4, 0x01A5, // LATIN CAPITAL LETTER P WITH HOOK + 0x01A6, 0x0280, // LATIN LETTER YR + 0x01A7, 0x01A8, // LATIN CAPITAL LETTER TONE TWO + 0x01A9, 0x0283, // LATIN CAPITAL LETTER ESH + 0x01AC, 0x01AD, // LATIN CAPITAL LETTER T WITH HOOK + 0x01AE, 0x0288, // LATIN CAPITAL LETTER T WITH RETROFLEX HOOK + 0x01AF, 0x01B0, // LATIN CAPITAL LETTER U WITH HORN + 0x01B1, 0x028A, // LATIN CAPITAL LETTER UPSILON + 0x01B2, 0x028B, // LATIN CAPITAL LETTER V WITH HOOK + 0x01B3, 0x01B4, // LATIN CAPITAL LETTER Y WITH HOOK + 0x01B5, 0x01B6, // LATIN CAPITAL LETTER Z WITH STROKE + 0x01B7, 0x0292, // LATIN CAPITAL LETTER EZH + 0x01B8, 0x01B9, // LATIN CAPITAL LETTER EZH REVERSED + 0x01BC, 0x01BD, // LATIN CAPITAL LETTER TONE FIVE + 0x01C4, 0x01C6, // LATIN CAPITAL LETTER DZ WITH CARON + 0x01C5, 0x01C6, // LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON + 0x01C7, 0x01C9, // LATIN CAPITAL LETTER LJ + 0x01C8, 0x01C9, // LATIN CAPITAL LETTER L WITH SMALL LETTER J + 0x01CA, 0x01CC, // LATIN CAPITAL LETTER NJ + 0x01CB, 0x01CC, // LATIN CAPITAL LETTER N WITH SMALL LETTER J + 0x01CD, 0x01CE, // LATIN CAPITAL LETTER A WITH CARON + 0x01CF, 0x01D0, // LATIN CAPITAL LETTER I WITH CARON + 0x01D1, 0x01D2, // LATIN CAPITAL LETTER O WITH CARON + 0x01D3, 0x01D4, // LATIN CAPITAL LETTER U WITH CARON + 0x01D5, 0x01D6, // LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON + 0x01D7, 0x01D8, // LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE + 0x01D9, 0x01DA, // LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON + 0x01DB, 0x01DC, // LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE + 0x01DE, 0x01DF, // LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON + 0x01E0, 0x01E1, // LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON + 0x01E2, 0x01E3, // LATIN CAPITAL LETTER AE WITH MACRON + 0x01E4, 0x01E5, // LATIN CAPITAL LETTER G WITH STROKE + 0x01E6, 0x01E7, // LATIN CAPITAL LETTER G WITH CARON + 0x01E8, 0x01E9, // LATIN CAPITAL LETTER K WITH CARON + 0x01EA, 0x01EB, // LATIN CAPITAL LETTER O WITH OGONEK + 0x01EC, 0x01ED, // LATIN CAPITAL LETTER O WITH OGONEK AND MACRON + 0x01EE, 0x01EF, // LATIN CAPITAL LETTER EZH WITH CARON + 0x01F1, 0x01F3, // LATIN CAPITAL LETTER DZ + 0x01F2, 0x01F3, // LATIN CAPITAL LETTER D WITH SMALL LETTER Z + 0x01F4, 0x01F5, // LATIN CAPITAL LETTER G WITH ACUTE + 0x01F6, 0x0195, // LATIN CAPITAL LETTER HWAIR + 0x01F7, 0x01BF, // LATIN CAPITAL LETTER WYNN + 0x01F8, 0x01F9, // LATIN CAPITAL LETTER N WITH GRAVE + 0x01FA, 0x01FB, // LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE + 0x01FC, 0x01FD, // LATIN CAPITAL LETTER AE WITH ACUTE + 0x01FE, 0x01FF, // LATIN CAPITAL LETTER O WITH STROKE AND ACUTE + 0x0200, 0x0201, // LATIN CAPITAL LETTER A WITH DOUBLE GRAVE + 0x0202, 0x0203, // LATIN CAPITAL LETTER A WITH INVERTED BREVE + 0x0204, 0x0205, // LATIN CAPITAL LETTER E WITH DOUBLE GRAVE + 0x0206, 0x0207, // LATIN CAPITAL LETTER E WITH INVERTED BREVE + 0x0208, 0x0209, // LATIN CAPITAL LETTER I WITH DOUBLE GRAVE + 0x020A, 0x020B, // LATIN CAPITAL LETTER I WITH INVERTED BREVE + 0x020C, 0x020D, // LATIN CAPITAL LETTER O WITH DOUBLE GRAVE + 0x020E, 0x020F, // LATIN CAPITAL LETTER O WITH INVERTED BREVE + 0x0210, 0x0211, // LATIN CAPITAL LETTER R WITH DOUBLE GRAVE + 0x0212, 0x0213, // LATIN CAPITAL LETTER R WITH INVERTED BREVE + 0x0214, 0x0215, // LATIN CAPITAL LETTER U WITH DOUBLE GRAVE + 0x0216, 0x0217, // LATIN CAPITAL LETTER U WITH INVERTED BREVE + 0x0218, 0x0219, // LATIN CAPITAL LETTER S WITH COMMA BELOW + 0x021A, 0x021B, // LATIN CAPITAL LETTER T WITH COMMA BELOW + 0x021C, 0x021D, // LATIN CAPITAL LETTER YOGH + 0x021E, 0x021F, // LATIN CAPITAL LETTER H WITH CARON + 0x0220, 0x019E, // LATIN CAPITAL LETTER N WITH LONG RIGHT LEG + 0x0222, 0x0223, // LATIN CAPITAL LETTER OU + 0x0224, 0x0225, // LATIN CAPITAL LETTER Z WITH HOOK + 0x0226, 0x0227, // LATIN CAPITAL LETTER A WITH DOT ABOVE + 0x0228, 0x0229, // LATIN CAPITAL LETTER E WITH CEDILLA + 0x022A, 0x022B, // LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON + 0x022C, 0x022D, // LATIN CAPITAL LETTER O WITH TILDE AND MACRON + 0x022E, 0x022F, // LATIN CAPITAL LETTER O WITH DOT ABOVE + 0x0230, 0x0231, // LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON + 0x0232, 0x0233, // LATIN CAPITAL LETTER Y WITH MACRON + 0x023A, 0x2C65, // LATIN CAPITAL LETTER A WITH STROKE + 0x023B, 0x023C, // LATIN CAPITAL LETTER C WITH STROKE + 0x023D, 0x019A, // LATIN CAPITAL LETTER L WITH BAR + 0x023E, 0x2C66, // LATIN CAPITAL LETTER T WITH DIAGONAL STROKE + 0x0241, 0x0242, // LATIN CAPITAL LETTER GLOTTAL STOP + 0x0243, 0x0180, // LATIN CAPITAL LETTER B WITH STROKE + 0x0244, 0x0289, // LATIN CAPITAL LETTER U BAR + 0x0245, 0x028C, // LATIN CAPITAL LETTER TURNED V + 0x0246, 0x0247, // LATIN CAPITAL LETTER E WITH STROKE + 0x0248, 0x0249, // LATIN CAPITAL LETTER J WITH STROKE + 0x024A, 0x024B, // LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL + 0x024C, 0x024D, // LATIN CAPITAL LETTER R WITH STROKE + 0x024E, 0x024F, // LATIN CAPITAL LETTER Y WITH STROKE + 0x0345, 0x03B9, // COMBINING GREEK YPOGEGRAMMENI + 0x0386, 0x03AC, // GREEK CAPITAL LETTER ALPHA WITH TONOS + 0x0388, 0x03AD, // GREEK CAPITAL LETTER EPSILON WITH TONOS + 0x0389, 0x03AE, // GREEK CAPITAL LETTER ETA WITH TONOS + 0x038A, 0x03AF, // GREEK CAPITAL LETTER IOTA WITH TONOS + 0x038C, 0x03CC, // GREEK CAPITAL LETTER OMICRON WITH TONOS + 0x038E, 0x03CD, // GREEK CAPITAL LETTER UPSILON WITH TONOS + 0x038F, 0x03CE, // GREEK CAPITAL LETTER OMEGA WITH TONOS + 0x0391, 0x03B1, // GREEK CAPITAL LETTER ALPHA + 0x0392, 0x03B2, // GREEK CAPITAL LETTER BETA + 0x0393, 0x03B3, // GREEK CAPITAL LETTER GAMMA + 0x0394, 0x03B4, // GREEK CAPITAL LETTER DELTA + 0x0395, 0x03B5, // GREEK CAPITAL LETTER EPSILON + 0x0396, 0x03B6, // GREEK CAPITAL LETTER ZETA + 0x0397, 0x03B7, // GREEK CAPITAL LETTER ETA + 0x0398, 0x03B8, // GREEK CAPITAL LETTER THETA + 0x0399, 0x03B9, // GREEK CAPITAL LETTER IOTA + 0x039A, 0x03BA, // GREEK CAPITAL LETTER KAPPA + 0x039B, 0x03BB, // GREEK CAPITAL LETTER LAMDA + 0x039C, 0x03BC, // GREEK CAPITAL LETTER MU + 0x039D, 0x03BD, // GREEK CAPITAL LETTER NU + 0x039E, 0x03BE, // GREEK CAPITAL LETTER XI + 0x039F, 0x03BF, // GREEK CAPITAL LETTER OMICRON + 0x03A0, 0x03C0, // GREEK CAPITAL LETTER PI + 0x03A1, 0x03C1, // GREEK CAPITAL LETTER RHO + 0x03A3, 0x03C3, // GREEK CAPITAL LETTER SIGMA + 0x03A4, 0x03C4, // GREEK CAPITAL LETTER TAU + 0x03A5, 0x03C5, // GREEK CAPITAL LETTER UPSILON + 0x03A6, 0x03C6, // GREEK CAPITAL LETTER PHI + 0x03A7, 0x03C7, // GREEK CAPITAL LETTER CHI + 0x03A8, 0x03C8, // GREEK CAPITAL LETTER PSI + 0x03A9, 0x03C9, // GREEK CAPITAL LETTER OMEGA + 0x03AA, 0x03CA, // GREEK CAPITAL LETTER IOTA WITH DIALYTIKA + 0x03AB, 0x03CB, // GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA + 0x03C2, 0x03C3, // GREEK SMALL LETTER FINAL SIGMA + 0x03D0, 0x03B2, // GREEK BETA SYMBOL + 0x03D1, 0x03B8, // GREEK THETA SYMBOL + 0x03D5, 0x03C6, // GREEK PHI SYMBOL + 0x03D6, 0x03C0, // GREEK PI SYMBOL + 0x03D8, 0x03D9, // GREEK LETTER ARCHAIC KOPPA + 0x03DA, 0x03DB, // GREEK LETTER STIGMA + 0x03DC, 0x03DD, // GREEK LETTER DIGAMMA + 0x03DE, 0x03DF, // GREEK LETTER KOPPA + 0x03E0, 0x03E1, // GREEK LETTER SAMPI + 0x03E2, 0x03E3, // COPTIC CAPITAL LETTER SHEI + 0x03E4, 0x03E5, // COPTIC CAPITAL LETTER FEI + 0x03E6, 0x03E7, // COPTIC CAPITAL LETTER KHEI + 0x03E8, 0x03E9, // COPTIC CAPITAL LETTER HORI + 0x03EA, 0x03EB, // COPTIC CAPITAL LETTER GANGIA + 0x03EC, 0x03ED, // COPTIC CAPITAL LETTER SHIMA + 0x03EE, 0x03EF, // COPTIC CAPITAL LETTER DEI + 0x03F0, 0x03BA, // GREEK KAPPA SYMBOL + 0x03F1, 0x03C1, // GREEK RHO SYMBOL + 0x03F4, 0x03B8, // GREEK CAPITAL THETA SYMBOL + 0x03F5, 0x03B5, // GREEK LUNATE EPSILON SYMBOL + 0x03F7, 0x03F8, // GREEK CAPITAL LETTER SHO + 0x03F9, 0x03F2, // GREEK CAPITAL LUNATE SIGMA SYMBOL + 0x03FA, 0x03FB, // GREEK CAPITAL LETTER SAN + 0x03FD, 0x037B, // GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL + 0x03FE, 0x037C, // GREEK CAPITAL DOTTED LUNATE SIGMA SYMBOL + 0x03FF, 0x037D, // GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL + 0x0400, 0x0450, // CYRILLIC CAPITAL LETTER IE WITH GRAVE + 0x0401, 0x0451, // CYRILLIC CAPITAL LETTER IO + 0x0402, 0x0452, // CYRILLIC CAPITAL LETTER DJE + 0x0403, 0x0453, // CYRILLIC CAPITAL LETTER GJE + 0x0404, 0x0454, // CYRILLIC CAPITAL LETTER UKRAINIAN IE + 0x0405, 0x0455, // CYRILLIC CAPITAL LETTER DZE + 0x0406, 0x0456, // CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I + 0x0407, 0x0457, // CYRILLIC CAPITAL LETTER YI + 0x0408, 0x0458, // CYRILLIC CAPITAL LETTER JE + 0x0409, 0x0459, // CYRILLIC CAPITAL LETTER LJE + 0x040A, 0x045A, // CYRILLIC CAPITAL LETTER NJE + 0x040B, 0x045B, // CYRILLIC CAPITAL LETTER TSHE + 0x040C, 0x045C, // CYRILLIC CAPITAL LETTER KJE + 0x040D, 0x045D, // CYRILLIC CAPITAL LETTER I WITH GRAVE + 0x040E, 0x045E, // CYRILLIC CAPITAL LETTER SHORT U + 0x040F, 0x045F, // CYRILLIC CAPITAL LETTER DZHE + 0x0410, 0x0430, // CYRILLIC CAPITAL LETTER A + 0x0411, 0x0431, // CYRILLIC CAPITAL LETTER BE + 0x0412, 0x0432, // CYRILLIC CAPITAL LETTER VE + 0x0413, 0x0433, // CYRILLIC CAPITAL LETTER GHE + 0x0414, 0x0434, // CYRILLIC CAPITAL LETTER DE + 0x0415, 0x0435, // CYRILLIC CAPITAL LETTER IE + 0x0416, 0x0436, // CYRILLIC CAPITAL LETTER ZHE + 0x0417, 0x0437, // CYRILLIC CAPITAL LETTER ZE + 0x0418, 0x0438, // CYRILLIC CAPITAL LETTER I + 0x0419, 0x0439, // CYRILLIC CAPITAL LETTER SHORT I + 0x041A, 0x043A, // CYRILLIC CAPITAL LETTER KA + 0x041B, 0x043B, // CYRILLIC CAPITAL LETTER EL + 0x041C, 0x043C, // CYRILLIC CAPITAL LETTER EM + 0x041D, 0x043D, // CYRILLIC CAPITAL LETTER EN + 0x041E, 0x043E, // CYRILLIC CAPITAL LETTER O + 0x041F, 0x043F, // CYRILLIC CAPITAL LETTER PE + 0x0420, 0x0440, // CYRILLIC CAPITAL LETTER ER + 0x0421, 0x0441, // CYRILLIC CAPITAL LETTER ES + 0x0422, 0x0442, // CYRILLIC CAPITAL LETTER TE + 0x0423, 0x0443, // CYRILLIC CAPITAL LETTER U + 0x0424, 0x0444, // CYRILLIC CAPITAL LETTER EF + 0x0425, 0x0445, // CYRILLIC CAPITAL LETTER HA + 0x0426, 0x0446, // CYRILLIC CAPITAL LETTER TSE + 0x0427, 0x0447, // CYRILLIC CAPITAL LETTER CHE + 0x0428, 0x0448, // CYRILLIC CAPITAL LETTER SHA + 0x0429, 0x0449, // CYRILLIC CAPITAL LETTER SHCHA + 0x042A, 0x044A, // CYRILLIC CAPITAL LETTER HARD SIGN + 0x042B, 0x044B, // CYRILLIC CAPITAL LETTER YERU + 0x042C, 0x044C, // CYRILLIC CAPITAL LETTER SOFT SIGN + 0x042D, 0x044D, // CYRILLIC CAPITAL LETTER E + 0x042E, 0x044E, // CYRILLIC CAPITAL LETTER YU + 0x042F, 0x044F, // CYRILLIC CAPITAL LETTER YA + 0x0460, 0x0461, // CYRILLIC CAPITAL LETTER OMEGA + 0x0462, 0x0463, // CYRILLIC CAPITAL LETTER YAT + 0x0464, 0x0465, // CYRILLIC CAPITAL LETTER IOTIFIED E + 0x0466, 0x0467, // CYRILLIC CAPITAL LETTER LITTLE YUS + 0x0468, 0x0469, // CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS + 0x046A, 0x046B, // CYRILLIC CAPITAL LETTER BIG YUS + 0x046C, 0x046D, // CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS + 0x046E, 0x046F, // CYRILLIC CAPITAL LETTER KSI + 0x0470, 0x0471, // CYRILLIC CAPITAL LETTER PSI + 0x0472, 0x0473, // CYRILLIC CAPITAL LETTER FITA + 0x0474, 0x0475, // CYRILLIC CAPITAL LETTER IZHITSA + 0x0476, 0x0477, // CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT + 0x0478, 0x0479, // CYRILLIC CAPITAL LETTER UK + 0x047A, 0x047B, // CYRILLIC CAPITAL LETTER ROUND OMEGA + 0x047C, 0x047D, // CYRILLIC CAPITAL LETTER OMEGA WITH TITLO + 0x047E, 0x047F, // CYRILLIC CAPITAL LETTER OT + 0x0480, 0x0481, // CYRILLIC CAPITAL LETTER KOPPA + 0x048A, 0x048B, // CYRILLIC CAPITAL LETTER SHORT I WITH TAIL + 0x048C, 0x048D, // CYRILLIC CAPITAL LETTER SEMISOFT SIGN + 0x048E, 0x048F, // CYRILLIC CAPITAL LETTER ER WITH TICK + 0x0490, 0x0491, // CYRILLIC CAPITAL LETTER GHE WITH UPTURN + 0x0492, 0x0493, // CYRILLIC CAPITAL LETTER GHE WITH STROKE + 0x0494, 0x0495, // CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK + 0x0496, 0x0497, // CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER + 0x0498, 0x0499, // CYRILLIC CAPITAL LETTER ZE WITH DESCENDER + 0x049A, 0x049B, // CYRILLIC CAPITAL LETTER KA WITH DESCENDER + 0x049C, 0x049D, // CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE + 0x049E, 0x049F, // CYRILLIC CAPITAL LETTER KA WITH STROKE + 0x04A0, 0x04A1, // CYRILLIC CAPITAL LETTER BASHKIR KA + 0x04A2, 0x04A3, // CYRILLIC CAPITAL LETTER EN WITH DESCENDER + 0x04A4, 0x04A5, // CYRILLIC CAPITAL LIGATURE EN GHE + 0x04A6, 0x04A7, // CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK + 0x04A8, 0x04A9, // CYRILLIC CAPITAL LETTER ABKHASIAN HA + 0x04AA, 0x04AB, // CYRILLIC CAPITAL LETTER ES WITH DESCENDER + 0x04AC, 0x04AD, // CYRILLIC CAPITAL LETTER TE WITH DESCENDER + 0x04AE, 0x04AF, // CYRILLIC CAPITAL LETTER STRAIGHT U + 0x04B0, 0x04B1, // CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE + 0x04B2, 0x04B3, // CYRILLIC CAPITAL LETTER HA WITH DESCENDER + 0x04B4, 0x04B5, // CYRILLIC CAPITAL LIGATURE TE TSE + 0x04B6, 0x04B7, // CYRILLIC CAPITAL LETTER CHE WITH DESCENDER + 0x04B8, 0x04B9, // CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE + 0x04BA, 0x04BB, // CYRILLIC CAPITAL LETTER SHHA + 0x04BC, 0x04BD, // CYRILLIC CAPITAL LETTER ABKHASIAN CHE + 0x04BE, 0x04BF, // CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER + 0x04C0, 0x04CF, // CYRILLIC LETTER PALOCHKA + 0x04C1, 0x04C2, // CYRILLIC CAPITAL LETTER ZHE WITH BREVE + 0x04C3, 0x04C4, // CYRILLIC CAPITAL LETTER KA WITH HOOK + 0x04C5, 0x04C6, // CYRILLIC CAPITAL LETTER EL WITH TAIL + 0x04C7, 0x04C8, // CYRILLIC CAPITAL LETTER EN WITH HOOK + 0x04C9, 0x04CA, // CYRILLIC CAPITAL LETTER EN WITH TAIL + 0x04CB, 0x04CC, // CYRILLIC CAPITAL LETTER KHAKASSIAN CHE + 0x04CD, 0x04CE, // CYRILLIC CAPITAL LETTER EM WITH TAIL + 0x04D0, 0x04D1, // CYRILLIC CAPITAL LETTER A WITH BREVE + 0x04D2, 0x04D3, // CYRILLIC CAPITAL LETTER A WITH DIAERESIS + 0x04D4, 0x04D5, // CYRILLIC CAPITAL LIGATURE A IE + 0x04D6, 0x04D7, // CYRILLIC CAPITAL LETTER IE WITH BREVE + 0x04D8, 0x04D9, // CYRILLIC CAPITAL LETTER SCHWA + 0x04DA, 0x04DB, // CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS + 0x04DC, 0x04DD, // CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS + 0x04DE, 0x04DF, // CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS + 0x04E0, 0x04E1, // CYRILLIC CAPITAL LETTER ABKHASIAN DZE + 0x04E2, 0x04E3, // CYRILLIC CAPITAL LETTER I WITH MACRON + 0x04E4, 0x04E5, // CYRILLIC CAPITAL LETTER I WITH DIAERESIS + 0x04E6, 0x04E7, // CYRILLIC CAPITAL LETTER O WITH DIAERESIS + 0x04E8, 0x04E9, // CYRILLIC CAPITAL LETTER BARRED O + 0x04EA, 0x04EB, // CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS + 0x04EC, 0x04ED, // CYRILLIC CAPITAL LETTER E WITH DIAERESIS + 0x04EE, 0x04EF, // CYRILLIC CAPITAL LETTER U WITH MACRON + 0x04F0, 0x04F1, // CYRILLIC CAPITAL LETTER U WITH DIAERESIS + 0x04F2, 0x04F3, // CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE + 0x04F4, 0x04F5, // CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS + 0x04F6, 0x04F7, // CYRILLIC CAPITAL LETTER GHE WITH DESCENDER + 0x04F8, 0x04F9, // CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS + 0x04FA, 0x04FB, // CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK + 0x04FC, 0x04FD, // CYRILLIC CAPITAL LETTER HA WITH HOOK + 0x04FE, 0x04FF, // CYRILLIC CAPITAL LETTER HA WITH STROKE + 0x0500, 0x0501, // CYRILLIC CAPITAL LETTER KOMI DE + 0x0502, 0x0503, // CYRILLIC CAPITAL LETTER KOMI DJE + 0x0504, 0x0505, // CYRILLIC CAPITAL LETTER KOMI ZJE + 0x0506, 0x0507, // CYRILLIC CAPITAL LETTER KOMI DZJE + 0x0508, 0x0509, // CYRILLIC CAPITAL LETTER KOMI LJE + 0x050A, 0x050B, // CYRILLIC CAPITAL LETTER KOMI NJE + 0x050C, 0x050D, // CYRILLIC CAPITAL LETTER KOMI SJE + 0x050E, 0x050F, // CYRILLIC CAPITAL LETTER KOMI TJE + 0x0510, 0x0511, // CYRILLIC CAPITAL LETTER REVERSED ZE + 0x0512, 0x0513, // CYRILLIC CAPITAL LETTER EL WITH HOOK + 0x0531, 0x0561, // ARMENIAN CAPITAL LETTER AYB + 0x0532, 0x0562, // ARMENIAN CAPITAL LETTER BEN + 0x0533, 0x0563, // ARMENIAN CAPITAL LETTER GIM + 0x0534, 0x0564, // ARMENIAN CAPITAL LETTER DA + 0x0535, 0x0565, // ARMENIAN CAPITAL LETTER ECH + 0x0536, 0x0566, // ARMENIAN CAPITAL LETTER ZA + 0x0537, 0x0567, // ARMENIAN CAPITAL LETTER EH + 0x0538, 0x0568, // ARMENIAN CAPITAL LETTER ET + 0x0539, 0x0569, // ARMENIAN CAPITAL LETTER TO + 0x053A, 0x056A, // ARMENIAN CAPITAL LETTER ZHE + 0x053B, 0x056B, // ARMENIAN CAPITAL LETTER INI + 0x053C, 0x056C, // ARMENIAN CAPITAL LETTER LIWN + 0x053D, 0x056D, // ARMENIAN CAPITAL LETTER XEH + 0x053E, 0x056E, // ARMENIAN CAPITAL LETTER CA + 0x053F, 0x056F, // ARMENIAN CAPITAL LETTER KEN + 0x0540, 0x0570, // ARMENIAN CAPITAL LETTER HO + 0x0541, 0x0571, // ARMENIAN CAPITAL LETTER JA + 0x0542, 0x0572, // ARMENIAN CAPITAL LETTER GHAD + 0x0543, 0x0573, // ARMENIAN CAPITAL LETTER CHEH + 0x0544, 0x0574, // ARMENIAN CAPITAL LETTER MEN + 0x0545, 0x0575, // ARMENIAN CAPITAL LETTER YI + 0x0546, 0x0576, // ARMENIAN CAPITAL LETTER NOW + 0x0547, 0x0577, // ARMENIAN CAPITAL LETTER SHA + 0x0548, 0x0578, // ARMENIAN CAPITAL LETTER VO + 0x0549, 0x0579, // ARMENIAN CAPITAL LETTER CHA + 0x054A, 0x057A, // ARMENIAN CAPITAL LETTER PEH + 0x054B, 0x057B, // ARMENIAN CAPITAL LETTER JHEH + 0x054C, 0x057C, // ARMENIAN CAPITAL LETTER RA + 0x054D, 0x057D, // ARMENIAN CAPITAL LETTER SEH + 0x054E, 0x057E, // ARMENIAN CAPITAL LETTER VEW + 0x054F, 0x057F, // ARMENIAN CAPITAL LETTER TIWN + 0x0550, 0x0580, // ARMENIAN CAPITAL LETTER REH + 0x0551, 0x0581, // ARMENIAN CAPITAL LETTER CO + 0x0552, 0x0582, // ARMENIAN CAPITAL LETTER YIWN + 0x0553, 0x0583, // ARMENIAN CAPITAL LETTER PIWR + 0x0554, 0x0584, // ARMENIAN CAPITAL LETTER KEH + 0x0555, 0x0585, // ARMENIAN CAPITAL LETTER OH + 0x0556, 0x0586, // ARMENIAN CAPITAL LETTER FEH + 0x10A0, 0x2D00, // GEORGIAN CAPITAL LETTER AN + 0x10A1, 0x2D01, // GEORGIAN CAPITAL LETTER BAN + 0x10A2, 0x2D02, // GEORGIAN CAPITAL LETTER GAN + 0x10A3, 0x2D03, // GEORGIAN CAPITAL LETTER DON + 0x10A4, 0x2D04, // GEORGIAN CAPITAL LETTER EN + 0x10A5, 0x2D05, // GEORGIAN CAPITAL LETTER VIN + 0x10A6, 0x2D06, // GEORGIAN CAPITAL LETTER ZEN + 0x10A7, 0x2D07, // GEORGIAN CAPITAL LETTER TAN + 0x10A8, 0x2D08, // GEORGIAN CAPITAL LETTER IN + 0x10A9, 0x2D09, // GEORGIAN CAPITAL LETTER KAN + 0x10AA, 0x2D0A, // GEORGIAN CAPITAL LETTER LAS + 0x10AB, 0x2D0B, // GEORGIAN CAPITAL LETTER MAN + 0x10AC, 0x2D0C, // GEORGIAN CAPITAL LETTER NAR + 0x10AD, 0x2D0D, // GEORGIAN CAPITAL LETTER ON + 0x10AE, 0x2D0E, // GEORGIAN CAPITAL LETTER PAR + 0x10AF, 0x2D0F, // GEORGIAN CAPITAL LETTER ZHAR + 0x10B0, 0x2D10, // GEORGIAN CAPITAL LETTER RAE + 0x10B1, 0x2D11, // GEORGIAN CAPITAL LETTER SAN + 0x10B2, 0x2D12, // GEORGIAN CAPITAL LETTER TAR + 0x10B3, 0x2D13, // GEORGIAN CAPITAL LETTER UN + 0x10B4, 0x2D14, // GEORGIAN CAPITAL LETTER PHAR + 0x10B5, 0x2D15, // GEORGIAN CAPITAL LETTER KHAR + 0x10B6, 0x2D16, // GEORGIAN CAPITAL LETTER GHAN + 0x10B7, 0x2D17, // GEORGIAN CAPITAL LETTER QAR + 0x10B8, 0x2D18, // GEORGIAN CAPITAL LETTER SHIN + 0x10B9, 0x2D19, // GEORGIAN CAPITAL LETTER CHIN + 0x10BA, 0x2D1A, // GEORGIAN CAPITAL LETTER CAN + 0x10BB, 0x2D1B, // GEORGIAN CAPITAL LETTER JIL + 0x10BC, 0x2D1C, // GEORGIAN CAPITAL LETTER CIL + 0x10BD, 0x2D1D, // GEORGIAN CAPITAL LETTER CHAR + 0x10BE, 0x2D1E, // GEORGIAN CAPITAL LETTER XAN + 0x10BF, 0x2D1F, // GEORGIAN CAPITAL LETTER JHAN + 0x10C0, 0x2D20, // GEORGIAN CAPITAL LETTER HAE + 0x10C1, 0x2D21, // GEORGIAN CAPITAL LETTER HE + 0x10C2, 0x2D22, // GEORGIAN CAPITAL LETTER HIE + 0x10C3, 0x2D23, // GEORGIAN CAPITAL LETTER WE + 0x10C4, 0x2D24, // GEORGIAN CAPITAL LETTER HAR + 0x10C5, 0x2D25, // GEORGIAN CAPITAL LETTER HOE + 0x1E00, 0x1E01, // LATIN CAPITAL LETTER A WITH RING BELOW + 0x1E02, 0x1E03, // LATIN CAPITAL LETTER B WITH DOT ABOVE + 0x1E04, 0x1E05, // LATIN CAPITAL LETTER B WITH DOT BELOW + 0x1E06, 0x1E07, // LATIN CAPITAL LETTER B WITH LINE BELOW + 0x1E08, 0x1E09, // LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE + 0x1E0A, 0x1E0B, // LATIN CAPITAL LETTER D WITH DOT ABOVE + 0x1E0C, 0x1E0D, // LATIN CAPITAL LETTER D WITH DOT BELOW + 0x1E0E, 0x1E0F, // LATIN CAPITAL LETTER D WITH LINE BELOW + 0x1E10, 0x1E11, // LATIN CAPITAL LETTER D WITH CEDILLA + 0x1E12, 0x1E13, // LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW + 0x1E14, 0x1E15, // LATIN CAPITAL LETTER E WITH MACRON AND GRAVE + 0x1E16, 0x1E17, // LATIN CAPITAL LETTER E WITH MACRON AND ACUTE + 0x1E18, 0x1E19, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW + 0x1E1A, 0x1E1B, // LATIN CAPITAL LETTER E WITH TILDE BELOW + 0x1E1C, 0x1E1D, // LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE + 0x1E1E, 0x1E1F, // LATIN CAPITAL LETTER F WITH DOT ABOVE + 0x1E20, 0x1E21, // LATIN CAPITAL LETTER G WITH MACRON + 0x1E22, 0x1E23, // LATIN CAPITAL LETTER H WITH DOT ABOVE + 0x1E24, 0x1E25, // LATIN CAPITAL LETTER H WITH DOT BELOW + 0x1E26, 0x1E27, // LATIN CAPITAL LETTER H WITH DIAERESIS + 0x1E28, 0x1E29, // LATIN CAPITAL LETTER H WITH CEDILLA + 0x1E2A, 0x1E2B, // LATIN CAPITAL LETTER H WITH BREVE BELOW + 0x1E2C, 0x1E2D, // LATIN CAPITAL LETTER I WITH TILDE BELOW + 0x1E2E, 0x1E2F, // LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE + 0x1E30, 0x1E31, // LATIN CAPITAL LETTER K WITH ACUTE + 0x1E32, 0x1E33, // LATIN CAPITAL LETTER K WITH DOT BELOW + 0x1E34, 0x1E35, // LATIN CAPITAL LETTER K WITH LINE BELOW + 0x1E36, 0x1E37, // LATIN CAPITAL LETTER L WITH DOT BELOW + 0x1E38, 0x1E39, // LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON + 0x1E3A, 0x1E3B, // LATIN CAPITAL LETTER L WITH LINE BELOW + 0x1E3C, 0x1E3D, // LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW + 0x1E3E, 0x1E3F, // LATIN CAPITAL LETTER M WITH ACUTE + 0x1E40, 0x1E41, // LATIN CAPITAL LETTER M WITH DOT ABOVE + 0x1E42, 0x1E43, // LATIN CAPITAL LETTER M WITH DOT BELOW + 0x1E44, 0x1E45, // LATIN CAPITAL LETTER N WITH DOT ABOVE + 0x1E46, 0x1E47, // LATIN CAPITAL LETTER N WITH DOT BELOW + 0x1E48, 0x1E49, // LATIN CAPITAL LETTER N WITH LINE BELOW + 0x1E4A, 0x1E4B, // LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW + 0x1E4C, 0x1E4D, // LATIN CAPITAL LETTER O WITH TILDE AND ACUTE + 0x1E4E, 0x1E4F, // LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS + 0x1E50, 0x1E51, // LATIN CAPITAL LETTER O WITH MACRON AND GRAVE + 0x1E52, 0x1E53, // LATIN CAPITAL LETTER O WITH MACRON AND ACUTE + 0x1E54, 0x1E55, // LATIN CAPITAL LETTER P WITH ACUTE + 0x1E56, 0x1E57, // LATIN CAPITAL LETTER P WITH DOT ABOVE + 0x1E58, 0x1E59, // LATIN CAPITAL LETTER R WITH DOT ABOVE + 0x1E5A, 0x1E5B, // LATIN CAPITAL LETTER R WITH DOT BELOW + 0x1E5C, 0x1E5D, // LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON + 0x1E5E, 0x1E5F, // LATIN CAPITAL LETTER R WITH LINE BELOW + 0x1E60, 0x1E61, // LATIN CAPITAL LETTER S WITH DOT ABOVE + 0x1E62, 0x1E63, // LATIN CAPITAL LETTER S WITH DOT BELOW + 0x1E64, 0x1E65, // LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE + 0x1E66, 0x1E67, // LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE + 0x1E68, 0x1E69, // LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE + 0x1E6A, 0x1E6B, // LATIN CAPITAL LETTER T WITH DOT ABOVE + 0x1E6C, 0x1E6D, // LATIN CAPITAL LETTER T WITH DOT BELOW + 0x1E6E, 0x1E6F, // LATIN CAPITAL LETTER T WITH LINE BELOW + 0x1E70, 0x1E71, // LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW + 0x1E72, 0x1E73, // LATIN CAPITAL LETTER U WITH DIAERESIS BELOW + 0x1E74, 0x1E75, // LATIN CAPITAL LETTER U WITH TILDE BELOW + 0x1E76, 0x1E77, // LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW + 0x1E78, 0x1E79, // LATIN CAPITAL LETTER U WITH TILDE AND ACUTE + 0x1E7A, 0x1E7B, // LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS + 0x1E7C, 0x1E7D, // LATIN CAPITAL LETTER V WITH TILDE + 0x1E7E, 0x1E7F, // LATIN CAPITAL LETTER V WITH DOT BELOW + 0x1E80, 0x1E81, // LATIN CAPITAL LETTER W WITH GRAVE + 0x1E82, 0x1E83, // LATIN CAPITAL LETTER W WITH ACUTE + 0x1E84, 0x1E85, // LATIN CAPITAL LETTER W WITH DIAERESIS + 0x1E86, 0x1E87, // LATIN CAPITAL LETTER W WITH DOT ABOVE + 0x1E88, 0x1E89, // LATIN CAPITAL LETTER W WITH DOT BELOW + 0x1E8A, 0x1E8B, // LATIN CAPITAL LETTER X WITH DOT ABOVE + 0x1E8C, 0x1E8D, // LATIN CAPITAL LETTER X WITH DIAERESIS + 0x1E8E, 0x1E8F, // LATIN CAPITAL LETTER Y WITH DOT ABOVE + 0x1E90, 0x1E91, // LATIN CAPITAL LETTER Z WITH CIRCUMFLEX + 0x1E92, 0x1E93, // LATIN CAPITAL LETTER Z WITH DOT BELOW + 0x1E94, 0x1E95, // LATIN CAPITAL LETTER Z WITH LINE BELOW + 0x1E9B, 0x1E61, // LATIN SMALL LETTER LONG S WITH DOT ABOVE + 0x1EA0, 0x1EA1, // LATIN CAPITAL LETTER A WITH DOT BELOW + 0x1EA2, 0x1EA3, // LATIN CAPITAL LETTER A WITH HOOK ABOVE + 0x1EA4, 0x1EA5, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE + 0x1EA6, 0x1EA7, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE + 0x1EA8, 0x1EA9, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE + 0x1EAA, 0x1EAB, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE + 0x1EAC, 0x1EAD, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW + 0x1EAE, 0x1EAF, // LATIN CAPITAL LETTER A WITH BREVE AND ACUTE + 0x1EB0, 0x1EB1, // LATIN CAPITAL LETTER A WITH BREVE AND GRAVE + 0x1EB2, 0x1EB3, // LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE + 0x1EB4, 0x1EB5, // LATIN CAPITAL LETTER A WITH BREVE AND TILDE + 0x1EB6, 0x1EB7, // LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW + 0x1EB8, 0x1EB9, // LATIN CAPITAL LETTER E WITH DOT BELOW + 0x1EBA, 0x1EBB, // LATIN CAPITAL LETTER E WITH HOOK ABOVE + 0x1EBC, 0x1EBD, // LATIN CAPITAL LETTER E WITH TILDE + 0x1EBE, 0x1EBF, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE + 0x1EC0, 0x1EC1, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE + 0x1EC2, 0x1EC3, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE + 0x1EC4, 0x1EC5, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE + 0x1EC6, 0x1EC7, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW + 0x1EC8, 0x1EC9, // LATIN CAPITAL LETTER I WITH HOOK ABOVE + 0x1ECA, 0x1ECB, // LATIN CAPITAL LETTER I WITH DOT BELOW + 0x1ECC, 0x1ECD, // LATIN CAPITAL LETTER O WITH DOT BELOW + 0x1ECE, 0x1ECF, // LATIN CAPITAL LETTER O WITH HOOK ABOVE + 0x1ED0, 0x1ED1, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE + 0x1ED2, 0x1ED3, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE + 0x1ED4, 0x1ED5, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE + 0x1ED6, 0x1ED7, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE + 0x1ED8, 0x1ED9, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW + 0x1EDA, 0x1EDB, // LATIN CAPITAL LETTER O WITH HORN AND ACUTE + 0x1EDC, 0x1EDD, // LATIN CAPITAL LETTER O WITH HORN AND GRAVE + 0x1EDE, 0x1EDF, // LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE + 0x1EE0, 0x1EE1, // LATIN CAPITAL LETTER O WITH HORN AND TILDE + 0x1EE2, 0x1EE3, // LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW + 0x1EE4, 0x1EE5, // LATIN CAPITAL LETTER U WITH DOT BELOW + 0x1EE6, 0x1EE7, // LATIN CAPITAL LETTER U WITH HOOK ABOVE + 0x1EE8, 0x1EE9, // LATIN CAPITAL LETTER U WITH HORN AND ACUTE + 0x1EEA, 0x1EEB, // LATIN CAPITAL LETTER U WITH HORN AND GRAVE + 0x1EEC, 0x1EED, // LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE + 0x1EEE, 0x1EEF, // LATIN CAPITAL LETTER U WITH HORN AND TILDE + 0x1EF0, 0x1EF1, // LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW + 0x1EF2, 0x1EF3, // LATIN CAPITAL LETTER Y WITH GRAVE + 0x1EF4, 0x1EF5, // LATIN CAPITAL LETTER Y WITH DOT BELOW + 0x1EF6, 0x1EF7, // LATIN CAPITAL LETTER Y WITH HOOK ABOVE + 0x1EF8, 0x1EF9, // LATIN CAPITAL LETTER Y WITH TILDE + 0x1F08, 0x1F00, // GREEK CAPITAL LETTER ALPHA WITH PSILI + 0x1F09, 0x1F01, // GREEK CAPITAL LETTER ALPHA WITH DASIA + 0x1F0A, 0x1F02, // GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA + 0x1F0B, 0x1F03, // GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA + 0x1F0C, 0x1F04, // GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA + 0x1F0D, 0x1F05, // GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA + 0x1F0E, 0x1F06, // GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI + 0x1F0F, 0x1F07, // GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI + 0x1F18, 0x1F10, // GREEK CAPITAL LETTER EPSILON WITH PSILI + 0x1F19, 0x1F11, // GREEK CAPITAL LETTER EPSILON WITH DASIA + 0x1F1A, 0x1F12, // GREEK CAPITAL LETTER EPSILON WITH PSILI AND VARIA + 0x1F1B, 0x1F13, // GREEK CAPITAL LETTER EPSILON WITH DASIA AND VARIA + 0x1F1C, 0x1F14, // GREEK CAPITAL LETTER EPSILON WITH PSILI AND OXIA + 0x1F1D, 0x1F15, // GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA + 0x1F28, 0x1F20, // GREEK CAPITAL LETTER ETA WITH PSILI + 0x1F29, 0x1F21, // GREEK CAPITAL LETTER ETA WITH DASIA + 0x1F2A, 0x1F22, // GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA + 0x1F2B, 0x1F23, // GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA + 0x1F2C, 0x1F24, // GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA + 0x1F2D, 0x1F25, // GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA + 0x1F2E, 0x1F26, // GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI + 0x1F2F, 0x1F27, // GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI + 0x1F38, 0x1F30, // GREEK CAPITAL LETTER IOTA WITH PSILI + 0x1F39, 0x1F31, // GREEK CAPITAL LETTER IOTA WITH DASIA + 0x1F3A, 0x1F32, // GREEK CAPITAL LETTER IOTA WITH PSILI AND VARIA + 0x1F3B, 0x1F33, // GREEK CAPITAL LETTER IOTA WITH DASIA AND VARIA + 0x1F3C, 0x1F34, // GREEK CAPITAL LETTER IOTA WITH PSILI AND OXIA + 0x1F3D, 0x1F35, // GREEK CAPITAL LETTER IOTA WITH DASIA AND OXIA + 0x1F3E, 0x1F36, // GREEK CAPITAL LETTER IOTA WITH PSILI AND PERISPOMENI + 0x1F3F, 0x1F37, // GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI + 0x1F48, 0x1F40, // GREEK CAPITAL LETTER OMICRON WITH PSILI + 0x1F49, 0x1F41, // GREEK CAPITAL LETTER OMICRON WITH DASIA + 0x1F4A, 0x1F42, // GREEK CAPITAL LETTER OMICRON WITH PSILI AND VARIA + 0x1F4B, 0x1F43, // GREEK CAPITAL LETTER OMICRON WITH DASIA AND VARIA + 0x1F4C, 0x1F44, // GREEK CAPITAL LETTER OMICRON WITH PSILI AND OXIA + 0x1F4D, 0x1F45, // GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA + 0x1F59, 0x1F51, // GREEK CAPITAL LETTER UPSILON WITH DASIA + 0x1F5B, 0x1F53, // GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA + 0x1F5D, 0x1F55, // GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA + 0x1F5F, 0x1F57, // GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI + 0x1F68, 0x1F60, // GREEK CAPITAL LETTER OMEGA WITH PSILI + 0x1F69, 0x1F61, // GREEK CAPITAL LETTER OMEGA WITH DASIA + 0x1F6A, 0x1F62, // GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA + 0x1F6B, 0x1F63, // GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA + 0x1F6C, 0x1F64, // GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA + 0x1F6D, 0x1F65, // GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA + 0x1F6E, 0x1F66, // GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI + 0x1F6F, 0x1F67, // GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI + 0x1F88, 0x1F80, // GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI + 0x1F89, 0x1F81, // GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI + 0x1F8A, 0x1F82, // GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI + 0x1F8B, 0x1F83, // GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI + 0x1F8C, 0x1F84, // GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI + 0x1F8D, 0x1F85, // GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI + 0x1F8E, 0x1F86, // GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI + 0x1F8F, 0x1F87, // GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI + 0x1F98, 0x1F90, // GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI + 0x1F99, 0x1F91, // GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI + 0x1F9A, 0x1F92, // GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI + 0x1F9B, 0x1F93, // GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI + 0x1F9C, 0x1F94, // GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI + 0x1F9D, 0x1F95, // GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI + 0x1F9E, 0x1F96, // GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI + 0x1F9F, 0x1F97, // GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI + 0x1FA8, 0x1FA0, // GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI + 0x1FA9, 0x1FA1, // GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI + 0x1FAA, 0x1FA2, // GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI + 0x1FAB, 0x1FA3, // GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI + 0x1FAC, 0x1FA4, // GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI + 0x1FAD, 0x1FA5, // GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI + 0x1FAE, 0x1FA6, // GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI + 0x1FAF, 0x1FA7, // GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI + 0x1FB8, 0x1FB0, // GREEK CAPITAL LETTER ALPHA WITH VRACHY + 0x1FB9, 0x1FB1, // GREEK CAPITAL LETTER ALPHA WITH MACRON + 0x1FBA, 0x1F70, // GREEK CAPITAL LETTER ALPHA WITH VARIA + 0x1FBB, 0x1F71, // GREEK CAPITAL LETTER ALPHA WITH OXIA + 0x1FBC, 0x1FB3, // GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI + 0x1FBE, 0x03B9, // GREEK PROSGEGRAMMENI + 0x1FC8, 0x1F72, // GREEK CAPITAL LETTER EPSILON WITH VARIA + 0x1FC9, 0x1F73, // GREEK CAPITAL LETTER EPSILON WITH OXIA + 0x1FCA, 0x1F74, // GREEK CAPITAL LETTER ETA WITH VARIA + 0x1FCB, 0x1F75, // GREEK CAPITAL LETTER ETA WITH OXIA + 0x1FCC, 0x1FC3, // GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI + 0x1FD8, 0x1FD0, // GREEK CAPITAL LETTER IOTA WITH VRACHY + 0x1FD9, 0x1FD1, // GREEK CAPITAL LETTER IOTA WITH MACRON + 0x1FDA, 0x1F76, // GREEK CAPITAL LETTER IOTA WITH VARIA + 0x1FDB, 0x1F77, // GREEK CAPITAL LETTER IOTA WITH OXIA + 0x1FE8, 0x1FE0, // GREEK CAPITAL LETTER UPSILON WITH VRACHY + 0x1FE9, 0x1FE1, // GREEK CAPITAL LETTER UPSILON WITH MACRON + 0x1FEA, 0x1F7A, // GREEK CAPITAL LETTER UPSILON WITH VARIA + 0x1FEB, 0x1F7B, // GREEK CAPITAL LETTER UPSILON WITH OXIA + 0x1FEC, 0x1FE5, // GREEK CAPITAL LETTER RHO WITH DASIA + 0x1FF8, 0x1F78, // GREEK CAPITAL LETTER OMICRON WITH VARIA + 0x1FF9, 0x1F79, // GREEK CAPITAL LETTER OMICRON WITH OXIA + 0x1FFA, 0x1F7C, // GREEK CAPITAL LETTER OMEGA WITH VARIA + 0x1FFB, 0x1F7D, // GREEK CAPITAL LETTER OMEGA WITH OXIA + 0x1FFC, 0x1FF3, // GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI + 0x2126, 0x03C9, // OHM SIGN + 0x212A, 0x006B, // KELVIN SIGN + 0x212B, 0x00E5, // ANGSTROM SIGN + 0x2132, 0x214E, // TURNED CAPITAL F + 0x2160, 0x2170, // ROMAN NUMERAL ONE + 0x2161, 0x2171, // ROMAN NUMERAL TWO + 0x2162, 0x2172, // ROMAN NUMERAL THREE + 0x2163, 0x2173, // ROMAN NUMERAL FOUR + 0x2164, 0x2174, // ROMAN NUMERAL FIVE + 0x2165, 0x2175, // ROMAN NUMERAL SIX + 0x2166, 0x2176, // ROMAN NUMERAL SEVEN + 0x2167, 0x2177, // ROMAN NUMERAL EIGHT + 0x2168, 0x2178, // ROMAN NUMERAL NINE + 0x2169, 0x2179, // ROMAN NUMERAL TEN + 0x216A, 0x217A, // ROMAN NUMERAL ELEVEN + 0x216B, 0x217B, // ROMAN NUMERAL TWELVE + 0x216C, 0x217C, // ROMAN NUMERAL FIFTY + 0x216D, 0x217D, // ROMAN NUMERAL ONE HUNDRED + 0x216E, 0x217E, // ROMAN NUMERAL FIVE HUNDRED + 0x216F, 0x217F, // ROMAN NUMERAL ONE THOUSAND + 0x2183, 0x2184, // ROMAN NUMERAL REVERSED ONE HUNDRED + 0x24B6, 0x24D0, // CIRCLED LATIN CAPITAL LETTER A + 0x24B7, 0x24D1, // CIRCLED LATIN CAPITAL LETTER B + 0x24B8, 0x24D2, // CIRCLED LATIN CAPITAL LETTER C + 0x24B9, 0x24D3, // CIRCLED LATIN CAPITAL LETTER D + 0x24BA, 0x24D4, // CIRCLED LATIN CAPITAL LETTER E + 0x24BB, 0x24D5, // CIRCLED LATIN CAPITAL LETTER F + 0x24BC, 0x24D6, // CIRCLED LATIN CAPITAL LETTER G + 0x24BD, 0x24D7, // CIRCLED LATIN CAPITAL LETTER H + 0x24BE, 0x24D8, // CIRCLED LATIN CAPITAL LETTER I + 0x24BF, 0x24D9, // CIRCLED LATIN CAPITAL LETTER J + 0x24C0, 0x24DA, // CIRCLED LATIN CAPITAL LETTER K + 0x24C1, 0x24DB, // CIRCLED LATIN CAPITAL LETTER L + 0x24C2, 0x24DC, // CIRCLED LATIN CAPITAL LETTER M + 0x24C3, 0x24DD, // CIRCLED LATIN CAPITAL LETTER N + 0x24C4, 0x24DE, // CIRCLED LATIN CAPITAL LETTER O + 0x24C5, 0x24DF, // CIRCLED LATIN CAPITAL LETTER P + 0x24C6, 0x24E0, // CIRCLED LATIN CAPITAL LETTER Q + 0x24C7, 0x24E1, // CIRCLED LATIN CAPITAL LETTER R + 0x24C8, 0x24E2, // CIRCLED LATIN CAPITAL LETTER S + 0x24C9, 0x24E3, // CIRCLED LATIN CAPITAL LETTER T + 0x24CA, 0x24E4, // CIRCLED LATIN CAPITAL LETTER U + 0x24CB, 0x24E5, // CIRCLED LATIN CAPITAL LETTER V + 0x24CC, 0x24E6, // CIRCLED LATIN CAPITAL LETTER W + 0x24CD, 0x24E7, // CIRCLED LATIN CAPITAL LETTER X + 0x24CE, 0x24E8, // CIRCLED LATIN CAPITAL LETTER Y + 0x24CF, 0x24E9, // CIRCLED LATIN CAPITAL LETTER Z + 0x2C00, 0x2C30, // GLAGOLITIC CAPITAL LETTER AZU + 0x2C01, 0x2C31, // GLAGOLITIC CAPITAL LETTER BUKY + 0x2C02, 0x2C32, // GLAGOLITIC CAPITAL LETTER VEDE + 0x2C03, 0x2C33, // GLAGOLITIC CAPITAL LETTER GLAGOLI + 0x2C04, 0x2C34, // GLAGOLITIC CAPITAL LETTER DOBRO + 0x2C05, 0x2C35, // GLAGOLITIC CAPITAL LETTER YESTU + 0x2C06, 0x2C36, // GLAGOLITIC CAPITAL LETTER ZHIVETE + 0x2C07, 0x2C37, // GLAGOLITIC CAPITAL LETTER DZELO + 0x2C08, 0x2C38, // GLAGOLITIC CAPITAL LETTER ZEMLJA + 0x2C09, 0x2C39, // GLAGOLITIC CAPITAL LETTER IZHE + 0x2C0A, 0x2C3A, // GLAGOLITIC CAPITAL LETTER INITIAL IZHE + 0x2C0B, 0x2C3B, // GLAGOLITIC CAPITAL LETTER I + 0x2C0C, 0x2C3C, // GLAGOLITIC CAPITAL LETTER DJERVI + 0x2C0D, 0x2C3D, // GLAGOLITIC CAPITAL LETTER KAKO + 0x2C0E, 0x2C3E, // GLAGOLITIC CAPITAL LETTER LJUDIJE + 0x2C0F, 0x2C3F, // GLAGOLITIC CAPITAL LETTER MYSLITE + 0x2C10, 0x2C40, // GLAGOLITIC CAPITAL LETTER NASHI + 0x2C11, 0x2C41, // GLAGOLITIC CAPITAL LETTER ONU + 0x2C12, 0x2C42, // GLAGOLITIC CAPITAL LETTER POKOJI + 0x2C13, 0x2C43, // GLAGOLITIC CAPITAL LETTER RITSI + 0x2C14, 0x2C44, // GLAGOLITIC CAPITAL LETTER SLOVO + 0x2C15, 0x2C45, // GLAGOLITIC CAPITAL LETTER TVRIDO + 0x2C16, 0x2C46, // GLAGOLITIC CAPITAL LETTER UKU + 0x2C17, 0x2C47, // GLAGOLITIC CAPITAL LETTER FRITU + 0x2C18, 0x2C48, // GLAGOLITIC CAPITAL LETTER HERU + 0x2C19, 0x2C49, // GLAGOLITIC CAPITAL LETTER OTU + 0x2C1A, 0x2C4A, // GLAGOLITIC CAPITAL LETTER PE + 0x2C1B, 0x2C4B, // GLAGOLITIC CAPITAL LETTER SHTA + 0x2C1C, 0x2C4C, // GLAGOLITIC CAPITAL LETTER TSI + 0x2C1D, 0x2C4D, // GLAGOLITIC CAPITAL LETTER CHRIVI + 0x2C1E, 0x2C4E, // GLAGOLITIC CAPITAL LETTER SHA + 0x2C1F, 0x2C4F, // GLAGOLITIC CAPITAL LETTER YERU + 0x2C20, 0x2C50, // GLAGOLITIC CAPITAL LETTER YERI + 0x2C21, 0x2C51, // GLAGOLITIC CAPITAL LETTER YATI + 0x2C22, 0x2C52, // GLAGOLITIC CAPITAL LETTER SPIDERY HA + 0x2C23, 0x2C53, // GLAGOLITIC CAPITAL LETTER YU + 0x2C24, 0x2C54, // GLAGOLITIC CAPITAL LETTER SMALL YUS + 0x2C25, 0x2C55, // GLAGOLITIC CAPITAL LETTER SMALL YUS WITH TAIL + 0x2C26, 0x2C56, // GLAGOLITIC CAPITAL LETTER YO + 0x2C27, 0x2C57, // GLAGOLITIC CAPITAL LETTER IOTATED SMALL YUS + 0x2C28, 0x2C58, // GLAGOLITIC CAPITAL LETTER BIG YUS + 0x2C29, 0x2C59, // GLAGOLITIC CAPITAL LETTER IOTATED BIG YUS + 0x2C2A, 0x2C5A, // GLAGOLITIC CAPITAL LETTER FITA + 0x2C2B, 0x2C5B, // GLAGOLITIC CAPITAL LETTER IZHITSA + 0x2C2C, 0x2C5C, // GLAGOLITIC CAPITAL LETTER SHTAPIC + 0x2C2D, 0x2C5D, // GLAGOLITIC CAPITAL LETTER TROKUTASTI A + 0x2C2E, 0x2C5E, // GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE + 0x2C60, 0x2C61, // LATIN CAPITAL LETTER L WITH DOUBLE BAR + 0x2C62, 0x026B, // LATIN CAPITAL LETTER L WITH MIDDLE TILDE + 0x2C63, 0x1D7D, // LATIN CAPITAL LETTER P WITH STROKE + 0x2C64, 0x027D, // LATIN CAPITAL LETTER R WITH TAIL + 0x2C67, 0x2C68, // LATIN CAPITAL LETTER H WITH DESCENDER + 0x2C69, 0x2C6A, // LATIN CAPITAL LETTER K WITH DESCENDER + 0x2C6B, 0x2C6C, // LATIN CAPITAL LETTER Z WITH DESCENDER + 0x2C75, 0x2C76, // LATIN CAPITAL LETTER HALF H + 0x2C80, 0x2C81, // COPTIC CAPITAL LETTER ALFA + 0x2C82, 0x2C83, // COPTIC CAPITAL LETTER VIDA + 0x2C84, 0x2C85, // COPTIC CAPITAL LETTER GAMMA + 0x2C86, 0x2C87, // COPTIC CAPITAL LETTER DALDA + 0x2C88, 0x2C89, // COPTIC CAPITAL LETTER EIE + 0x2C8A, 0x2C8B, // COPTIC CAPITAL LETTER SOU + 0x2C8C, 0x2C8D, // COPTIC CAPITAL LETTER ZATA + 0x2C8E, 0x2C8F, // COPTIC CAPITAL LETTER HATE + 0x2C90, 0x2C91, // COPTIC CAPITAL LETTER THETHE + 0x2C92, 0x2C93, // COPTIC CAPITAL LETTER IAUDA + 0x2C94, 0x2C95, // COPTIC CAPITAL LETTER KAPA + 0x2C96, 0x2C97, // COPTIC CAPITAL LETTER LAULA + 0x2C98, 0x2C99, // COPTIC CAPITAL LETTER MI + 0x2C9A, 0x2C9B, // COPTIC CAPITAL LETTER NI + 0x2C9C, 0x2C9D, // COPTIC CAPITAL LETTER KSI + 0x2C9E, 0x2C9F, // COPTIC CAPITAL LETTER O + 0x2CA0, 0x2CA1, // COPTIC CAPITAL LETTER PI + 0x2CA2, 0x2CA3, // COPTIC CAPITAL LETTER RO + 0x2CA4, 0x2CA5, // COPTIC CAPITAL LETTER SIMA + 0x2CA6, 0x2CA7, // COPTIC CAPITAL LETTER TAU + 0x2CA8, 0x2CA9, // COPTIC CAPITAL LETTER UA + 0x2CAA, 0x2CAB, // COPTIC CAPITAL LETTER FI + 0x2CAC, 0x2CAD, // COPTIC CAPITAL LETTER KHI + 0x2CAE, 0x2CAF, // COPTIC CAPITAL LETTER PSI + 0x2CB0, 0x2CB1, // COPTIC CAPITAL LETTER OOU + 0x2CB2, 0x2CB3, // COPTIC CAPITAL LETTER DIALECT-P ALEF + 0x2CB4, 0x2CB5, // COPTIC CAPITAL LETTER OLD COPTIC AIN + 0x2CB6, 0x2CB7, // COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE + 0x2CB8, 0x2CB9, // COPTIC CAPITAL LETTER DIALECT-P KAPA + 0x2CBA, 0x2CBB, // COPTIC CAPITAL LETTER DIALECT-P NI + 0x2CBC, 0x2CBD, // COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI + 0x2CBE, 0x2CBF, // COPTIC CAPITAL LETTER OLD COPTIC OOU + 0x2CC0, 0x2CC1, // COPTIC CAPITAL LETTER SAMPI + 0x2CC2, 0x2CC3, // COPTIC CAPITAL LETTER CROSSED SHEI + 0x2CC4, 0x2CC5, // COPTIC CAPITAL LETTER OLD COPTIC SHEI + 0x2CC6, 0x2CC7, // COPTIC CAPITAL LETTER OLD COPTIC ESH + 0x2CC8, 0x2CC9, // COPTIC CAPITAL LETTER AKHMIMIC KHEI + 0x2CCA, 0x2CCB, // COPTIC CAPITAL LETTER DIALECT-P HORI + 0x2CCC, 0x2CCD, // COPTIC CAPITAL LETTER OLD COPTIC HORI + 0x2CCE, 0x2CCF, // COPTIC CAPITAL LETTER OLD COPTIC HA + 0x2CD0, 0x2CD1, // COPTIC CAPITAL LETTER L-SHAPED HA + 0x2CD2, 0x2CD3, // COPTIC CAPITAL LETTER OLD COPTIC HEI + 0x2CD4, 0x2CD5, // COPTIC CAPITAL LETTER OLD COPTIC HAT + 0x2CD6, 0x2CD7, // COPTIC CAPITAL LETTER OLD COPTIC GANGIA + 0x2CD8, 0x2CD9, // COPTIC CAPITAL LETTER OLD COPTIC DJA + 0x2CDA, 0x2CDB, // COPTIC CAPITAL LETTER OLD COPTIC SHIMA + 0x2CDC, 0x2CDD, // COPTIC CAPITAL LETTER OLD NUBIAN SHIMA + 0x2CDE, 0x2CDF, // COPTIC CAPITAL LETTER OLD NUBIAN NGI + 0x2CE0, 0x2CE1, // COPTIC CAPITAL LETTER OLD NUBIAN NYI + 0x2CE2, 0x2CE3, // COPTIC CAPITAL LETTER OLD NUBIAN WAU + 0, 0 }; -/*********************************************************************** -** -*/ void Init_Char_Cases(void) -/* -** Initialize the Unicode character casing tables. -** These tables support simple 1-to-1 casing methods. -** -***********************************************************************/ +// +// Init_Char_Cases: C +// +// Initialize the Unicode character casing tables. +// These tables support simple 1-to-1 casing methods. +// +void Init_Char_Cases(void) { - const REBUNI *up; - int n; + const REBUNI *up; + int n; - // Init whitespace table: - White_Chars = Make_Mem(34); - memset(White_Chars, 1, 33); // All white chars: NL, CR, BS, etc... - White_Chars[' '] = 3; // space - White_Chars['\t'] = 3; // space - White_Chars[0] = 0; // special + // Init whitespace table: + White_Chars = ALLOC_N(REBYTE, 34); + memset(White_Chars, 1, 33); // All white chars: NL, CR, BS, etc... + White_Chars[cast(REBYTE, ' ')] = 3; // space + White_Chars[cast(REBYTE, '\t')] = 3; // tab + White_Chars[0] = 0; // special - // Casing tables: - Upper_Cases = Make_Mem(UNICODE_CASES * sizeof(REBUNI)); - Lower_Cases = Make_Mem(UNICODE_CASES * sizeof(REBUNI)); + // Casing tables: + Upper_Cases = ALLOC_N(REBUNI, UNICODE_CASES); + Lower_Cases = ALLOC_N(REBUNI, UNICODE_CASES); - for (n = 0; n < UNICODE_CASES; n++) { - UP_CASE(n) = n; - LO_CASE(n) = n; - } + for (n = 0; n < UNICODE_CASES; n++) { + UP_CASE(n) = n; + LO_CASE(n) = n; + } - for (up = &Char_Cases[0]; *up; up += 2) { - //ASSERT2(UP_CASE(up[1]) == up[1], 910); - // Only map if not already set (multiple mappings exist): - if (UP_CASE(up[1]) == up[1]) UP_CASE(up[1]) = up[0]; - if (LO_CASE(up[1]) == up[1]) LO_CASE(up[0]) = up[1]; - } + for (up = &Char_Cases[0]; *up; up += 2) { + //assert(UP_CASE(up[1]) == up[1], 910); + // Only map if not already set (multiple mappings exist): + if (UP_CASE(up[1]) == up[1]) UP_CASE(up[1]) = up[0]; + if (LO_CASE(up[1]) == up[1]) LO_CASE(up[0]) = up[1]; + } } + +// +// Shutdown_Char_Cases: C +// +void Shutdown_Char_Cases(void) +{ + FREE_N(REBUNI, UNICODE_CASES, Upper_Cases); + FREE_N(REBUNI, UNICODE_CASES, Lower_Cases); + FREE_N(REBYTE, 34, White_Chars); +} diff --git a/src/core/s-crc.c b/src/core/s-crc.c index 640c604b05..4b409964f1 100644 --- a/src/core/s-crc.c +++ b/src/core/s-crc.c @@ -1,906 +1,615 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: s-crc.c -** Summary: CRC computation -** Section: strings -** Author: Carl Sassenrath (REBOL interface sections) -** Notes: -** -***********************************************************************/ +// +// File: %s-crc.c +// Summary: "CRC computation" +// Section: strings +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" #define CRC_DEFINED -#define CRCBITS 24 /* may be 16, 24, or 32 */ -#define MASK_CRC(crc) ((crc) & 0x00ffffffL) /* if CRCBITS is 24 */ -#define CRCHIBIT ((REBCNT) (1L<<(CRCBITS-1))) /* 0x8000 if CRCBITS is 16 */ +#define CRCBITS 24 /* may be 16, 24, or 32 */ +#define MASK_CRC(crc) ((crc) & I32_C(0x00ffffff)) /* if CRCBITS is 24 */ +#define CRCHIBIT ((REBCNT) (I32_C(1)<<(CRCBITS-1))) /* 0x8000 if CRCBITS is 16 */ #define CRCSHIFTS (CRCBITS-8) -#define CCITTCRC 0x1021 /* CCITT's 16-bit CRC generator polynomial */ -#define PRZCRC 0x864cfb /* PRZ's 24-bit CRC generator polynomial */ -#define CRCINIT 0xB704CE /* Init value for CRC accumulator */ +#define CCITTCRC 0x1021 /* CCITT's 16-bit CRC generator polynomial */ +#define PRZCRC 0x864cfb /* PRZ's 24-bit CRC generator polynomial */ +#define CRCINIT 0xB704CE /* Init value for CRC accumulator */ static REBCNT *CRC_Table; -/*********************************************************************** -** -*/ static REBCNT Generate_CRC(REBYTE ch, REBCNT poly, REBCNT accum) -/* -** Simulates CRC hardware circuit. Generates true CRC -** directly, without requiring extra NULL bytes to be appended -** to the message. Returns new updated CRC accumulator. -** -** These CRC functions are derived from code in chapter 19 of the book -** "C Programmer's Guide to Serial Communications", by Joe Campbell. -** Generalized to any CRC width by Philip Zimmermann. -** -** CRC-16 X^16 + X^15 + X^2 + 1 -** CRC-CCITT X^16 + X^12 + X^2 + 1 -** -** Notes on making a good 24-bit CRC: -** The primitive irreducible polynomial of degree 23 over GF(2), -** 040435651 (octal), comes from Appendix C of "Error Correcting Codes, -** 2nd edition" by Peterson and Weldon, page 490. This polynomial was -** chosen for its uniform density of ones and zeros, which has better -** error detection properties than polynomials with a minimal number of -** nonzero terms. Multiplying this primitive degree-23 polynomial by -** the polynomial x+1 yields the additional property of detecting any -** odd number of bits in error, which means it adds parity. This -** approach was recommended by Neal Glover. -** -** To multiply the polynomial 040435651 by x+1, shift it left 1 bit and -** bitwise add (xor) the unshifted version back in. Dropping the unused -** upper bit (bit 24) produces a CRC-24 generator bitmask of 041446373 -** octal, or 0x864cfb hex. -** -** You can detect spurious leading zeros or framing errors in the -** message by initializing the CRC accumulator to some agreed-upon -** nonzero "random-like" value, but this is a bit nonstandard. -** -***********************************************************************/ +// +// Generate_CRC: C +// +// Simulates CRC hardware circuit. Generates true CRC +// directly, without requiring extra NULL bytes to be appended +// to the message. Returns new updated CRC accumulator. +// +// These CRC functions are derived from code in chapter 19 of the book +// "C Programmer's Guide to Serial Communications", by Joe Campbell. +// Generalized to any CRC width by Philip Zimmermann. +// +// CRC-16 X^16 + X^15 + X^2 + 1 +// CRC-CCITT X^16 + X^12 + X^2 + 1 +// +// Notes on making a good 24-bit CRC: +// The primitive irreducible polynomial of degree 23 over GF(2), +// 040435651 (octal), comes from Appendix C of "Error Correcting Codes, +// 2nd edition" by Peterson and Weldon, page 490. This polynomial was +// chosen for its uniform density of ones and zeros, which has better +// error detection properties than polynomials with a minimal number of +// nonzero terms. Multiplying this primitive degree-23 polynomial by +// the polynomial x+1 yields the additional property of detecting any +// odd number of bits in error, which means it adds parity. This +// approach was recommended by Neal Glover. +// +// To multiply the polynomial 040435651 by x+1, shift it left 1 bit and +// bitwise add (xor) the unshifted version back in. Dropping the unused +// upper bit (bit 24) produces a CRC-24 generator bitmask of 041446373 +// octal, or 0x864cfb hex. +// +// You can detect spurious leading zeros or framing errors in the +// message by initializing the CRC accumulator to some agreed-upon +// nonzero "random-like" value, but this is a bit nonstandard. +// +static REBCNT Generate_CRC(REBYTE ch, REBCNT poly, REBCNT accum) { - REBINT i; - REBCNT data; - - data = ch; - data <<= CRCSHIFTS; /* shift data to line up with MSB of accum */ - i = 8; /* counts 8 bits of data */ - do { /* if MSB of (data XOR accum) is TRUE, shift and subtract poly */ - if ((data ^ accum) & CRCHIBIT) accum = (accum<<1) ^ poly; - else accum <<= 1; - data <<= 1; - } while (--i); /* counts 8 bits of data */ - return (MASK_CRC(accum)); + REBINT i; + REBCNT data; + + data = ch; + data <<= CRCSHIFTS; /* shift data to line up with MSB of accum */ + i = 8; /* counts 8 bits of data */ + do { /* if MSB of (data XOR accum) is TRUE, shift and subtract poly */ + if ((data ^ accum) & CRCHIBIT) accum = (accum<<1) ^ poly; + else accum <<= 1; + data <<= 1; + } while (--i); /* counts 8 bits of data */ + return (MASK_CRC(accum)); } -/*********************************************************************** -** -*/ static void Make_CRC_Table(REBCNT poly) -/* -** Derives a CRC lookup table from the CRC polynomial. -** The table is used later by crcupdate function given below. -** Only needs to be called once at the dawn of time. -** -***********************************************************************/ +// +// Make_CRC_Table: C +// +// Derives a CRC lookup table from the CRC polynomial. +// The table is used later by crcupdate function given below. +// Only needs to be called once at the dawn of time. +// +static void Make_CRC_Table(REBCNT poly) { - REBINT i; + REBINT i; - FOREACH (i, 256) CRC_Table[i] = Generate_CRC((REBYTE) i, poly, 0); + for (i = 0; i < 256; i++) + CRC_Table[i] = Generate_CRC(cast(REBYTE, i), poly, 0); } -/*********************************************************************** -** -*/ REBINT Compute_CRC(REBYTE *str, REBCNT len) -/* -***********************************************************************/ +// +// Compute_CRC: C +// +// Rebol had canonized signed numbers for CRCs, and the signed logic +// actually does turn high bytes into negative numbers so they +// subtract instead of add *during* the calculation. Hence the casts +// are necessary so long as compatibility with the historical results +// of the CHECKSUM native is needed. +// +REBINT Compute_CRC(REBYTE *str, REBCNT len) { - REBYTE n; - REBINT crc = (REBINT)len + (REBINT)((REBYTE)(*str)); + REBINT crc = cast(REBINT, len) + cast(REBINT, cast(REBYTE, *str)); - for (; len > 0; len--) { - n = (REBYTE)((crc >> CRCSHIFTS) ^ (REBYTE)(*str++)); - crc = MASK_CRC(crc << 8) ^ (REBINT)CRC_Table[n]; - } + for (; len > 0; len--) { + REBYTE n = cast(REBYTE, (crc >> CRCSHIFTS) ^ cast(REBYTE, *str++)); - return crc; + // Left shift math must use unsigned to avoid undefined behavior + // http://stackoverflow.com/q/3784996/211160 + crc = cast(REBINT, MASK_CRC(cast(REBCNT, crc) << 8) ^ CRC_Table[n]); + } + + return crc; } -/*********************************************************************** -** -*/ REBINT Hash_String(REBYTE *str, REBCNT len) -/* -** Return a case insensitive hash value for the string. The -** string does not have to be zero terminated and UTF8 is ok. -** -***********************************************************************/ +// +// Hash_Word: C +// +// Return a case insensitive hash value for the string. +// +REBINT Hash_Word(const REBYTE *str, REBCNT len) { - REBYTE n; - REBINT hash = (REBINT)len + (REBINT)((REBYTE)LO_CASE(*str)); - - for (; len > 0; len--) { - n = (REBYTE)((hash >> CRCSHIFTS) ^ (REBYTE)LO_CASE(*str++)); - hash = MASK_CRC(hash << 8) ^ (REBINT)CRC_Table[n]; - } + REBINT hash = + cast(REBINT, len) + cast(REBINT, cast(REBYTE, LO_CASE(*str))); - return hash; -} + for (; len > 0; str++, len--) { + REBUNI n = *str; + if (n >= 0x80) { + str = Back_Scan_UTF8_Char(&n, str, &len); + assert(str); // UTF8 should have already been verified good + } -/*********************************************************************** -** -*/ REBINT Hash_Word(REBYTE *str, REBINT len) -/* -** Return a case insensitive hash value for the string. -** -***********************************************************************/ -{ - REBINT m, n; - REBINT hash; - REBCNT ulen; + // Optimize `n = cast(REBYTE, LO_CASE(n))` (drop upper 8 bits) + // !!! Is this actually faster? + if (n < UNICODE_CASES) + n = cast(REBYTE, LO_CASE(n)); + else + n = cast(REBYTE, n); - if (len < 0) len = LEN_BYTES(str); + n = cast(REBYTE, (hash >> CRCSHIFTS) ^ n); - hash = (REBINT)len + (REBINT)((REBYTE)LO_CASE(*str)); + // Left shift math must use unsigned to avoid undefined behavior + // http://stackoverflow.com/q/3784996/211160 + hash = cast(REBINT, MASK_CRC(cast(REBCNT, hash) << 8) ^ CRC_Table[n]); + } - ulen = (REBCNT)len; // so the & operation later isn't for the wrong type + return hash; +} - for (; ulen > 0; str++, ulen--) { - n = *str; - if (n > 127 && NZ(m = Decode_UTF8_Char(&str, &ulen))) n = m; // mods str, ulen - if (n < UNICODE_CASES) n = LO_CASE(n); - n = (REBYTE)((hash >> CRCSHIFTS) ^ (REBYTE)n); // drop upper 8 bits - hash = MASK_CRC(hash << 8) ^ (REBINT)CRC_Table[n]; - } +static u32 *crc32_table = 0; - return hash; -} +static void Make_CRC32_Table(void); -/*********************************************************************** -** -*/ REBINT Hash_Value(REBVAL *val, REBCNT hash_size) -/* -** Return a case insensitive hash value for any value. -** -** Result will be > 0 and < hash_size, except if -** datatype cannot be hashed, a 0 is returned. -** -***********************************************************************/ +// +// Hash_Value: C +// +// Return a case insensitive hash value for any value. +// +// Fails if datatype cannot be hashed. Note that the specifier is not used +// in hashing, because it is not used in comparisons either. +// +REBCNT Hash_Value(const RELVAL *v) { - REBCNT ret; - - switch(VAL_TYPE(val)) { - - case REB_WORD: - case REB_SET_WORD: - case REB_GET_WORD: - case REB_LIT_WORD: - case REB_REFINEMENT: - case REB_ISSUE: - ret = VAL_WORD_CANON(val); - break; - - case REB_BINARY: - case REB_STRING: - case REB_FILE: - case REB_EMAIL: - case REB_URL: - case REB_TAG: - ret = Hash_String(VAL_BIN_DATA(val), Val_Byte_Len(val)); - break; - - case REB_LOGIC: - ret = VAL_LOGIC(val) ? (hash_size/5) : (2*hash_size/5); - break; - - case REB_INTEGER: - case REB_DECIMAL: // depends on INT64 sharing the DEC64 bits - ret = (REBCNT)(VAL_INT64(val) >> 32) ^ ((REBCNT)VAL_INT64(val)); - break; - - case REB_CHAR: - ret = VAL_CHAR(val) << 15; // avoid running into WORD hashes - break; - - case REB_MONEY: - ret = VAL_ALL_BITS(val)[0] ^ VAL_ALL_BITS(val)[1] ^ VAL_ALL_BITS(val)[2]; - break; - - case REB_TIME: - case REB_DATE: - ret = (REBCNT)(VAL_TIME(val) ^ (VAL_TIME(val) / SEC_SEC)); - if (IS_DATE(val)) ret ^= VAL_DATE(val).bits; - break; - - case REB_TUPLE: - ret = Hash_String(VAL_TUPLE(val), VAL_TUPLE_LEN(val)); - break; - - case REB_PAIR: - ret = VAL_ALL_BITS(val)[0] ^ VAL_ALL_BITS(val)[1]; - break; - - case REB_OBJECT: - ret = ((REBCNT)VAL_OBJ_FRAME(val)) >> 4; - break; - - case REB_DATATYPE: - ret = Hash_Word(Get_Sym_Name(VAL_DATATYPE(val)+1), -1); - break; - - case REB_NONE: - ret = 1; - break; - - case REB_UNSET: - ret = 0; - break; - - default: - return 0; //ret = 3 * (hash_size/5); - } - - return 1 + ((hash_size-1) & ret); + REBCNT ret; + + switch(VAL_TYPE(v)) { + case REB_MAX_VOID: + // + // While a void might technically be hashed, it can't be a value *or* + // a key in a map. + // + panic (NULL); + + case REB_BAR: + case REB_LIT_BAR: + case REB_BLANK: + ret = 0; + break; + + case REB_LOGIC: + ret = VAL_LOGIC(v) ? 1 : 0; + break; + + case REB_INTEGER: + // + // R3-Alpha XOR'd with (VAL_INT64(val) >> 32). But: "XOR with high + // bits collapses -1 with 0 etc. (If your key k is |k| < 2^32 high + // bits are 0-informative." -Giulio + // + ret = cast(REBCNT, VAL_INT64(v)); + break; + + case REB_DECIMAL: + case REB_PERCENT: + // depends on INT64 sharing the DEC64 bits + ret = (VAL_INT64(v) >> 32) ^ (VAL_INT64(v)); + break; + + case REB_MONEY: + ret = VAL_ALL_BITS(v)[0] ^ VAL_ALL_BITS(v)[1] ^ v->extra.m0; + break; + + case REB_CHAR: + ret = LO_CASE(VAL_CHAR(v)); + break; + + case REB_PAIR: + ret = (VAL_ALL_BITS(v)[0] << 16) + ^ (VAL_ALL_BITS(v)[0] >> 16) + ^ (VAL_ALL_BITS(v)[1]); + break; + + case REB_TUPLE: + ret = Hash_String(VAL_TUPLE(v), VAL_TUPLE_LEN(v), 1); + break; + + case REB_TIME: + case REB_DATE: + ret = cast(REBCNT, VAL_NANO(v) ^ (VAL_NANO(v) / SEC_SEC)); + if (IS_DATE(v)) + ret ^= VAL_DATE(v).bits; + break; + + case REB_BINARY: + case REB_STRING: + case REB_FILE: + case REB_EMAIL: + case REB_URL: + case REB_TAG: + ret = Hash_String( + VAL_RAW_DATA_AT(v), + VAL_LEN_HEAD(v), + SER_WIDE(VAL_SERIES(v)) + ); + break; + + case REB_BLOCK: + case REB_GROUP: + case REB_PATH: + case REB_SET_PATH: + case REB_GET_PATH: + case REB_LIT_PATH: + // + // !!! Lame hash just to get it working. There will be lots of + // collisions. Intentionally bad to avoid writing something that + // is less obviously not thought out. + // + // Whatever hash is used must be able to match lax equality. So it + // could hash all the values case-insensitively, or the first N values, + // or something. + // + // Note that if there is a way to mutate this array, there will be + // problems. Do not hash mutable arrays unless you are sure hashings + // won't cross a mutation. + // + ret = ARR_LEN(VAL_ARRAY(v)); + break; + + case REB_DATATYPE: { + REBSTR *canon = Canon(VAL_TYPE_SYM(v)); + ret = Hash_Word(STR_HEAD(canon), STR_NUM_BYTES(canon)); + break; } + + case REB_BITSET: + case REB_IMAGE: + case REB_VECTOR: + case REB_TYPESET: + // + // These types are currently not supported. + // + // !!! Why not? + // + fail (Error_Invalid_Type(VAL_TYPE(v))); + + case REB_WORD: + case REB_SET_WORD: + case REB_GET_WORD: + case REB_LIT_WORD: + case REB_REFINEMENT: + case REB_ISSUE: { + // + // Note that the canon symbol may change for a group of word synonyms + // if that canon is GC'd--it picks another synonym. Thus the pointer + // of the canon cannot be used as a long term hash. A case insensitive + // hashing of the word spelling itself is needed. + // + // !!! Should this hash be cached on the words somehow, e.g. in the + // data payload before the actual string? + // + REBSTR *spelling = VAL_WORD_SPELLING(v); + ret = Hash_Word(STR_HEAD(spelling), STR_NUM_BYTES(spelling)); + break; } + + case REB_FUNCTION: + // + // Because function equality is by identity only and they are + // immutable once created, it is legal to put them in hashes. The + // VAL_FUNC is the paramlist series, guaranteed unique per function + // + ret = cast(REBCNT, cast(REBUPT, VAL_FUNC(v)) >> 4); + break; + + case REB_FRAME: + case REB_MODULE: + case REB_ERROR: + case REB_PORT: + case REB_OBJECT: + // + // !!! ANY-CONTEXT has a uniquely identifying context pointer for that + // context. However, this does not help with "natural =" comparison + // as the hashing will be for SAME? contexts only: + // + // http://stackoverflow.com/a/33577210/211160 + // + // Allowing object keys to be OBJECT! and then comparing by field + // values creates problems for hashing if that object is mutable. + // However, since it was historically allowed it is allowed for + // all ANY-CONTEXT! types at the moment. + // + ret = cast(REBCNT, cast(REBUPT, VAL_CONTEXT(v)) >> 4); + break; + + case REB_MAP: + // + // Looking up a map in a map is fairly analogous to looking up an + // object in a map. If one is permitted, so should the other be. + // (Again this will just find the map by identity, not by comparing + // the values of one against the values of the other...) + // + ret = cast(REBCNT, cast(REBUPT, VAL_MAP(v)) >> 4); + break; + + case REB_GOB: + case REB_EVENT: + case REB_HANDLE: + case REB_STRUCT: + case REB_LIBRARY: + // + // !!! Review hashing behavior or needs of these types if necessary. + // + fail (Error_Invalid_Type(VAL_TYPE(v))); + + default: + // The list above should be comprehensive. panic in order to keep + // there from being an uninitialized ret warning. + // + panic (NULL); + } + + return ret ^ crc32_table[VAL_TYPE(v)]; } -/*********************************************************************** -** -*/ REBSER *Make_Hash_Array(REBCNT len) -/* -***********************************************************************/ +// +// Make_Hash_Sequence: C +// +REBSER *Make_Hash_Sequence(REBCNT len) { - REBCNT n; - REBSER *ser; + REBCNT n = Get_Hash_Prime(len * 2); // best when 2X # of keys + if (n == 0) { + DECLARE_LOCAL (temp); + Init_Integer(temp, len); - n = Get_Hash_Prime(len * 2); // best when 2X # of keys - if (!n) Trap_Num(RE_SIZE_LIMIT, len); + fail (Error_Size_Limit_Raw(temp)); + } - ser = Make_Series(n + 1, sizeof(REBCNT), FALSE); - LABEL_SERIES(ser, "make hash array"); - Clear_Series(ser); - ser->tail = n; + REBSER *ser = Make_Series(n + 1, sizeof(REBCNT)); + Clear_Series(ser); + SET_SERIES_LEN(ser, n); - return ser; + return ser; } -/*********************************************************************** -** -*/ REBSER *Hash_Block(REBVAL *block, REBCNT cased) -/* -** Hash ALL values of a block. Return hash array series. -** Used for SET logic (unique, union, etc.) -** -** Note: hash array contents (indexes) are 1-based! -** -***********************************************************************/ +// +// Init_Map: C +// +// A map has an additional hash element hidden in the ->extra +// field of the REBSER which needs to be given to memory +// management as well. +// +void Init_Map(REBVAL *out, REBMAP *map) { - REBCNT n; - REBCNT key; - REBSER *hser; - REBCNT *hashes; - REBSER *series = VAL_SERIES(block); - - // Create the hash array (integer indexes): - hser = Make_Hash_Array(VAL_LEN(block)); - hashes = (REBCNT*)hser->data; - - for (n = VAL_INDEX(block); n < series->tail; n++) { - key = Find_Key(series, hser, BLK_SKIP(series, n), 1, cased, 0); - hashes[key] = n + 1; - } - - return hser; -} + if (MAP_HASHLIST(map)) + ENSURE_SERIES_MANAGED(MAP_HASHLIST(map)); + ENSURE_ARRAY_MANAGED(MAP_PAIRLIST(map)); -/*********************************************************************** -** -*/ void Init_CRC(void) -/* -***********************************************************************/ -{ - CRC_Table = Make_Mem(sizeof(REBCNT) * 256); - Make_CRC_Table(PRZCRC); + VAL_RESET_HEADER(out, REB_MAP); + out->extra.binding = (REBARR*)SPECIFIED; // !!! cast() gripes, investigate + out->payload.any_series.series = SER(MAP_PAIRLIST(map)); + out->payload.any_series.index = 0; } -/*********************************************************************** -** -*/ REBINT Compute_IPC(REBYTE *data, REBCNT length) -/* -** Compute an IP checksum given some data and a length. -** Used only on BINARY values. -** -***********************************************************************/ +// +// Hash_Block: C +// +// Hash ALL values of a block. Return hash array series. +// Used for SET logic (unique, union, etc.) +// +// Note: hash array contents (indexes) are 1-based! +// +REBSER *Hash_Block(const REBVAL *block, REBCNT skip, REBOOL cased) { - REBCNT lSum = 0; // stores the summation - REBYTE *up = data; - - while (length > 1) { - lSum += (up[0] << 8) | up[1]; - up += 2; - length -= 2; - } - - // Handle the odd byte if necessary - if (length) lSum += *up; - - // Add back the carry outs from the 16 bits to the low 16 bits - lSum = (lSum >> 16) + (lSum & 0xffff); // Add high-16 to low-16 - lSum += (lSum >> 16); // Add carry - return (REBINT)( (~lSum) & 0xffff); // 1's complement, then truncate + REBCNT n; + REBSER *hashlist; + REBCNT *hashes; + REBARR *array = VAL_ARRAY(block); + RELVAL *value; + + // Create the hash array (integer indexes): + hashlist = Make_Hash_Sequence(VAL_LEN_AT(block)); + hashes = SER_HEAD(REBCNT, hashlist); + + value = VAL_ARRAY_AT(block); + if (IS_END(value)) + return hashlist; + + n = VAL_INDEX(block); + while (TRUE) { + REBCNT skip_index = skip; + + REBCNT hash = Find_Key_Hashed( + array, hashlist, value, VAL_SPECIFIER(block), 1, cased, 0 + ); + hashes[hash] = (n / skip) + 1; + + while (skip_index != 0) { + value++; + n++; + skip_index--; + + if (IS_END(value)) { + if (skip_index != 0) { + // + // !!! It's not clear what to do when hashing something + // for a skip index when the number isn't evenly divisible + // by that amount. It means a hash lookup will find + // something, but it won't be a "full record". Just as + // we have to check for ENDs inside the hashed-to material + // here, later code would have to check also. + // + // The conservative thing to do here is to error. If a + // compelling coherent behavior and rationale in the + // rest of the code can be established. But more likely + // than not, this will catch bugs in callers vs. be + // a roadblock to them. + // + fail (Error_Block_Skip_Wrong_Raw()); + } + + return hashlist; + } + } + } + + DEAD_END; } - - -static u32 *crc32_table = 0; - -static void Make_CRC32_Table(void) { - unsigned long c; - int n,k; - - crc32_table = Make_Mem(256 * sizeof(u32)); - - for(n=0;n<256;n++) { - c=(unsigned long)n; - for(k=0;k<8;k++) { - if(c&1) - c=0xedb88320L^(c>>1); - else - c=c>>1; - } - crc32_table[n]=c; - } -} - -REBCNT Update_CRC32(u32 crc, REBYTE *buf, int len) { - u32 c = ~crc; - int n; - - if(!crc32_table) Make_CRC32_Table(); - - for(n = 0; n < len; n++) - c = crc32_table[(c^buf[n])&0xff]^(c>>8); - - return ~c; -} - -/*********************************************************************** -** -*/ REBCNT CRC32(REBYTE *buf, REBCNT len) -/* -***********************************************************************/ +// +// Compute_IPC: C +// +// Compute an IP checksum given some data and a length. +// Used only on BINARY values. +// +REBINT Compute_IPC(REBYTE *data, REBCNT length) { - return Update_CRC32(0x00000000L, buf, len); + REBCNT lSum = 0; // stores the summation + REBYTE *up = data; + + while (length > 1) { + lSum += (up[0] << 8) | up[1]; + up += 2; + length -= 2; + } + + // Handle the odd byte if necessary + if (length) lSum += *up; + + // Add back the carry outs from the 16 bits to the low 16 bits + lSum = (lSum >> 16) + (lSum & 0xffff); // Add high-16 to low-16 + lSum += (lSum >> 16); // Add carry + return (REBINT)( (~lSum) & 0xffff); // 1's complement, then truncate } - -#ifdef ndef -Header File -// CRCdemo.h - -protected: - ULONG crc32_table[256]; // Lookup table array - void Init_CRC32_Table(); // Builds lookup table array - ULONG Reflect(ULONG ref, char ch); // Reflects CRC bits in the lookup table - int Get_CRC(CString& text); // Creates a CRC from a text string - - -Source File -// CRCdemo.cpp - -void CRCdemo::Init_CRC32_Table() -{// Call this function only once to initialize the CRC table. - - // This is the official polynomial used by CRC-32 - // in PKZip, WinZip and Ethernet. - ULONG ulPolynomial = 0x04c11db7; - - // 256 values representing ASCII character codes. - for(int i = 0; i <= 0xFF; i++) - { - crc32_table[i]=Reflect(i, 8) << 24; - for (int j = 0; j < 8; j++) - crc32_table[i] = (crc32_table[i] << 1) ^ (crc32_table[i] & (1 << 31) ? ulPolynomial : 0); - crc32_table[i] = Reflect(crc32_table[i], 32); - } +static void Make_CRC32_Table(void) { + u32 c; + int n,k; + + crc32_table = ALLOC_N(u32, 256); + + for(n=0;n<256;n++) { + c=(u32)n; + for(k=0;k<8;k++) { + if(c&1) + c=U32_C(0xedb88320)^(c>>1); + else + c=c>>1; + } + crc32_table[n]=c; + } } -ULONG CRCdemo::Reflect(ULONG ref, char ch) -{// Used only by Init_CRC32_Table(). - ULONG value(0); +REBCNT Update_CRC32(u32 crc, REBYTE *buf, int len) { + u32 c = ~crc; + int n; - // Swap bit 0 for bit 7 - // bit 1 for bit 6, etc. - for(int i = 1; i < (ch + 1); i++) - { - if(ref & 1) - value |= 1 << (ch - i); - ref >>= 1; - } - return value; -} + for(n = 0; n < len; n++) + c = crc32_table[(c^buf[n])&0xff]^(c>>8); -int CRCdemo::Get_CRC(CString& text) -{ // Pass a text string to this function and it will return the CRC. - - // Once the lookup table has been filled in by the two functions above, - // this function creates all CRCs using only the lookup table. - // Note that CString is an MFC class. - // If you don't have MFC, use the function below instead. - - // Be sure to use unsigned variables, - // because negative values introduce high bits - // where zero bits are required. - - // Start out with all bits set high. - ULONG ulCRC(0xffffffff); - int len; - unsigned char* buffer; - - // Get the length. - len = text.GetLength(); - // Save the text in the buffer. - buffer = (unsigned char*)(LPCTSTR)text; - // Perform the algorithm on each character - // in the string, using the lookup table values. - while(len--) - ulCRC = (ulCRC >> 8) ^ crc32_table[(ulCRC & 0xFF) ^ *buffer++]; - // Exclusive OR the result with the beginning value. - return ulCRC ^ 0xffffffff; + return ~c; } -If you don't have an MFC compiler, you can substitute this function, which doesn't use a CString. Just change the declaration in the header file to: int Get_CRC(char* text); // Creates a CRC from a text string - -int CRCdemo::Get_CRC(char* text) -{// Pass a text string to this function and it will return the CRC. - - // Once the lookup table has been filled in by the two functions above, - // this function creates all CRCs using only the lookup table. - - // Be sure to use unsigned variables, - // because negative values introduce high bits - // where zero bits are required. - - // Start out with all bits set high. - ULONG ulCRC(0xffffffff); - int len; - unsigned char* buffer; - - // Get the length. - len = LEN_BYTES(text); - // Save the text in the buffer. - buffer = (unsigned char*)text; - // Perform the algorithm on each character - // in the string, using the lookup table values. - while(len--) - ulCRC = (ulCRC >> 8) ^ crc32_table[(ulCRC & 0xFF) ^ *buffer++]; - // Exclusive OR the result with the beginning value. - return ulCRC ^ 0xffffffff; -} - -//---------------- - -/* - * crc32.c - * This code is in the public domain; copyright abandoned. - * Liability for non-performance of this code is limited to the amount - * you paid for it. Since it is distributed for free, your refund will - * be very very small. If it breaks, you get to keep both pieces. - */ - -#include "crc32.h" - -#if __GNUC__ >= 3 /* 2.x has "attribute", but only 3.0 has "pure */ -#define attribute(x) __attribute__(x) -#else -#define attribute(x) -#endif - -/* - * There are multiple 16-bit CRC polynomials in common use, but this is - * *the* standard CRC-32 polynomial, first popularized by Ethernet. - * x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+x^0 - */ -#define CRCPOLY_LE 0xedb88320 -#define CRCPOLY_BE 0x04c11db7 - -/* How many bits at a time to use. Requires a table of 4< 8 || CRC_LE_BITS < 1 || CRC_LE_BITS & CRC_LE_BITS-1 -# error CRC_LE_BITS must be a power of 2 between 1 and 8 -#endif - -#if CRC_LE_BITS == 1 -/* - * In fact, the table-based code will work in this case, but it can be - * simplified by inlining the table in ?: form. - */ -#define crc32init_le() -#define crc32cleanup_le() -/** - * crc32_le() - Calculate bitwise little-endian Ethernet AUTODIN II CRC32 - * @crc - seed value for computation. ~0 for Ethernet, sometimes 0 for - * other uses, or the previous crc32 value if computing incrementally. - * @p - pointer to buffer over which CRC is run - * @len - length of buffer @p - * - */ -uint32_t attribute((pure)) crc32_le(uint32_t crc, unsigned char const *p, size_t len) +// +// CRC32: C +// +REBCNT CRC32(REBYTE *buf, REBCNT len) { - int i; - while (len--) { - crc ^= *p++; - for (i = 0; i < 8; i++) - crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0); - } - return crc; -} -#else /* Table-based approach */ - -static uint32_t *crc32table_le; -/** - * crc32init_le() - allocate and initialize LE table data - * - * crc is the crc of the byte i; other entries are filled in based on the - * fact that crctable[i^j] = crctable[i] ^ crctable[j]. - * - */ -static int -crc32init_le(void) -{ - unsigned i, j; - uint32_t crc = 1; - - crc32table_le = - malloc((1 << CRC_LE_BITS) * sizeof(uint32_t)); - if (!crc32table_le) - return 1; - crc32table_le[0] = 0; - - for (i = 1 << (CRC_LE_BITS - 1); i; i >>= 1) { - crc = (crc >> 1) ^ ((crc & 1) ? CRCPOLY_LE : 0); - for (j = 0; j < 1 << CRC_LE_BITS; j += 2 * i) - crc32table_le[i + j] = crc ^ crc32table_le[j]; - } - return 0; + return Update_CRC32(U32_C(0x00000000), buf, len); } -/** - * crc32cleanup_le(): free LE table data - */ -static void -crc32cleanup_le(void) -{ - if (crc32table_le) free(crc32table_le); - crc32table_le = NULL; -} -/** - * crc32_le() - Calculate bitwise little-endian Ethernet AUTODIN II CRC32 - * @crc - seed value for computation. ~0 for Ethernet, sometimes 0 for - * other uses, or the previous crc32 value if computing incrementally. - * @p - pointer to buffer over which CRC is run - * @len - length of buffer @p - * - */ -uint32_t attribute((pure)) crc32_le(uint32_t crc, unsigned char const *p, size_t len) -{ - while (len--) { -# if CRC_LE_BITS == 8 - crc = (crc >> 8) ^ crc32table_le[(crc ^ *p++) & 255]; -# elif CRC_LE_BITS == 4 - crc ^= *p++; - crc = (crc >> 4) ^ crc32table_le[crc & 15]; - crc = (crc >> 4) ^ crc32table_le[crc & 15]; -# elif CRC_LE_BITS == 2 - crc ^= *p++; - crc = (crc >> 2) ^ crc32table_le[crc & 3]; - crc = (crc >> 2) ^ crc32table_le[crc & 3]; - crc = (crc >> 2) ^ crc32table_le[crc & 3]; - crc = (crc >> 2) ^ crc32table_le[crc & 3]; -# endif - } - return crc; -} -#endif - -/* - * Big-endian CRC computation. Used with serial bit streams sent - * msbit-first. Be sure to use cpu_to_be32() to append the computed CRC. - */ -#if CRC_BE_BITS > 8 || CRC_BE_BITS < 1 || CRC_BE_BITS & CRC_BE_BITS-1 -# error CRC_BE_BITS must be a power of 2 between 1 and 8 -#endif - -#if CRC_BE_BITS == 1 -/* - * In fact, the table-based code will work in this case, but it can be - * simplified by inlining the table in ?: form. - */ -#define crc32init_be() -#define crc32cleanup_be() - -/** - * crc32_be() - Calculate bitwise big-endian Ethernet AUTODIN II CRC32 - * @crc - seed value for computation. ~0 for Ethernet, sometimes 0 for - * other uses, or the previous crc32 value if computing incrementally. - * @p - pointer to buffer over which CRC is run - * @len - length of buffer @p - * - */ -uint32_t attribute((pure)) crc32_be(uint32_t crc, unsigned char const *p, size_t len) -{ - int i; - while (len--) { - crc ^= *p++ << 24; - for (i = 0; i < 8; i++) - crc = - (crc << 1) ^ ((crc & 0x80000000) ? CRCPOLY_BE : - 0); - } - return crc; +// +// Hash_String: C +// +// Return a 32-bit case insensitive hash value for the string. The +// string does not have to be zero terminated and UTF8 is ok. +// +REBINT Hash_String( + const void *data, // REBYTE* or REBUNI* + REBCNT len, // chars, not bytes + REBCNT wide // 1 = byte-sized, 2 = Unicode +) { + u32 c = 0x00000000; + u32 c2 = 0x00000000; // don't change, see [1] below + REBCNT n; + const REBYTE *b = cast(const REBYTE*, data); + const REBUNI *u = cast(const REBUNI*, data); + + if(!crc32_table) Make_CRC32_Table(); + + if (wide == 1) { + for(n = 0; n < len; n++) { + c = (c >> 8) ^ crc32_table[(c ^ LO_CASE(b[n])) & 0xff]; + } + } else if (wide == 2) { + for(n = 0; n < len; n++) { + c = (c >> 8) ^ crc32_table[(c ^ LO_CASE(u[n])) & 0xff]; + + c2 = (c2 >> 8) ^ crc32_table[ + (c2 ^ (LO_CASE(u[n]) >> 8)) & 0xff + ]; + } + } + else + assert(wide == 1 || wide == 2); + + // [1] If wide = 2 but all chars <= 0xFF then c2 = 0, and c is the same + // as wide = 1 + // + c ^= c2; + + return cast(REBINT,~c); } -#else /* Table-based approach */ -static uint32_t *crc32table_be; -/** - * crc32init_be() - allocate and initialize BE table data - */ -static int -crc32init_be(void) +// +// Startup_CRC: C +// +void Startup_CRC(void) { - unsigned i, j; - uint32_t crc = 0x80000000; - - crc32table_be = - malloc((1 << CRC_BE_BITS) * sizeof(uint32_t)); - if (!crc32table_be) - return 1; - crc32table_be[0] = 0; - - for (i = 1; i < 1 << CRC_BE_BITS; i <<= 1) { - crc = (crc << 1) ^ ((crc & 0x80000000) ? CRCPOLY_BE : 0); - for (j = 0; j < i; j++) - crc32table_be[i + j] = crc ^ crc32table_be[j]; - } - return 0; -} + CRC_Table = ALLOC_N(REBCNT, 256); + Make_CRC_Table(PRZCRC); -/** - * crc32cleanup_be(): free BE table data - */ -static void -crc32cleanup_be(void) -{ - if (crc32table_be) free(crc32table_be); - crc32table_be = NULL; + Make_CRC32_Table(); } -/** - * crc32_be() - Calculate bitwise big-endian Ethernet AUTODIN II CRC32 - * @crc - seed value for computation. ~0 for Ethernet, sometimes 0 for - * other uses, or the previous crc32 value if computing incrementally. - * @p - pointer to buffer over which CRC is run - * @len - length of buffer @p - * - */ -uint32_t attribute((pure)) crc32_be(uint32_t crc, unsigned char const *p, size_t len) +// +// Shutdown_CRC: C +// +void Shutdown_CRC(void) { - while (len--) { -# if CRC_BE_BITS == 8 - crc = (crc << 8) ^ crc32table_be[(crc >> 24) ^ *p++]; -# elif CRC_BE_BITS == 4 - crc ^= *p++ << 24; - crc = (crc << 4) ^ crc32table_be[crc >> 28]; - crc = (crc << 4) ^ crc32table_be[crc >> 28]; -# elif CRC_BE_BITS == 2 - crc ^= *p++ << 24; - crc = (crc << 2) ^ crc32table_be[crc >> 30]; - crc = (crc << 2) ^ crc32table_be[crc >> 30]; - crc = (crc << 2) ^ crc32table_be[crc >> 30]; - crc = (crc << 2) ^ crc32table_be[crc >> 30]; -# endif - } - return crc; -} -#endif - -/* - * A brief CRC tutorial. - * - * A CRC is a long-division remainder. You add the CRC to the message, - * and the whole thing (message+CRC) is a multiple of the given - * CRC polynomial. To check the CRC, you can either check that the - * CRC matches the recomputed value, *or* you can check that the - * remainder computed on the message+CRC is 0. This latter approach - * is used by a lot of hardware implementations, and is why so many - * protocols put the end-of-frame flag after the CRC. - * - * It's actually the same long division you learned in school, except that - * - We're working in binary, so the digits are only 0 and 1, and - * - When dividing polynomials, there are no carries. Rather than add and - * subtract, we just xor. Thus, we tend to get a bit sloppy about - * the difference between adding and subtracting. - * - * A 32-bit CRC polynomial is actually 33 bits long. But since it's - * 33 bits long, bit 32 is always going to be set, so usually the CRC - * is written in hex with the most significant bit omitted. (If you're - * familiar with the IEEE 754 floating-point format, it's the same idea.) - * - * Note that a CRC is computed over a string of *bits*, so you have - * to decide on the endianness of the bits within each byte. To get - * the best error-detecting properties, this should correspond to the - * order they're actually sent. For example, standard RS-232 serial is - * little-endian; the most significant bit (sometimes used for parity) - * is sent last. And when appending a CRC word to a message, you should - * do it in the right order, matching the endianness. - * - * Just like with ordinary division, the remainder is always smaller than - * the divisor (the CRC polynomial) you're dividing by. Each step of the - * division, you take one more digit (bit) of the dividend and append it - * to the current remainder. Then you figure out the appropriate multiple - * of the divisor to subtract to being the remainder back into range. - * In binary, it's easy - it has to be either 0 or 1, and to make the - * XOR cancel, it's just a copy of bit 32 of the remainder. - * - * When computing a CRC, we don't care about the quotient, so we can - * throw the quotient bit away, but subtract the appropriate multiple of - * the polynomial from the remainder and we're back to where we started, - * ready to process the next bit. - * - * A big-endian CRC written this way would be coded like: - * for (i = 0; i < input_bits; i++) { - * multiple = remainder & 0x80000000 ? CRCPOLY : 0; - * remainder = (remainder << 1 | next_input_bit()) ^ multiple; - * } - * Notice how, to get at bit 32 of the shifted remainder, we look - * at bit 31 of the remainder *before* shifting it. - * - * But also notice how the next_input_bit() bits we're shifting into - * the remainder don't actually affect any decision-making until - * 32 bits later. Thus, the first 32 cycles of this are pretty boring. - * Also, to add the CRC to a message, we need a 32-bit-long hole for it at - * the end, so we have to add 32 extra cycles shifting in zeros at the - * end of every message, - * - * So the standard trick is to rearrage merging in the next_input_bit() - * until the moment it's needed. Then the first 32 cycles can be precomputed, - * and merging in the final 32 zero bits to make room for the CRC can be - * skipped entirely. - * This changes the code to: - * for (i = 0; i < input_bits; i++) { - * remainder ^= next_input_bit() << 31; - * multiple = (remainder & 0x80000000) ? CRCPOLY : 0; - * remainder = (remainder << 1) ^ multiple; - * } - * With this optimization, the little-endian code is simpler: - * for (i = 0; i < input_bits; i++) { - * remainder ^= next_input_bit(); - * multiple = (remainder & 1) ? CRCPOLY : 0; - * remainder = (remainder >> 1) ^ multiple; - * } - * - * Note that the other details of endianness have been hidden in CRCPOLY - * (which must be bit-reversed) and next_input_bit(). - * - * However, as long as next_input_bit is returning the bits in a sensible - * order, we can actually do the merging 8 or more bits at a time rather - * than one bit at a time: - * for (i = 0; i < input_bytes; i++) { - * remainder ^= next_input_byte() << 24; - * for (j = 0; j < 8; j++) { - * multiple = (remainder & 0x80000000) ? CRCPOLY : 0; - * remainder = (remainder << 1) ^ multiple; - * } - * } - * Or in little-endian: - * for (i = 0; i < input_bytes; i++) { - * remainder ^= next_input_byte(); - * for (j = 0; j < 8; j++) { - * multiple = (remainder & 1) ? CRCPOLY : 0; - * remainder = (remainder << 1) ^ multiple; - * } - * } - * If the input is a multiple of 32 bits, you can even XOR in a 32-bit - * word at a time and increase the inner loop count to 32. - * - * You can also mix and match the two loop styles, for example doing the - * bulk of a message byte-at-a-time and adding bit-at-a-time processing - * for any fractional bytes at the end. - * - * The only remaining optimization is to the byte-at-a-time table method. - * Here, rather than just shifting one bit of the remainder to decide - * in the correct multiple to subtract, we can shift a byte at a time. - * This produces a 40-bit (rather than a 33-bit) intermediate remainder, - * but again the multiple of the polynomial to subtract depends only on - * the high bits, the high 8 bits in this case. - * - * The multile we need in that case is the low 32 bits of a 40-bit - * value whose high 8 bits are given, and which is a multiple of the - * generator polynomial. This is simply the CRC-32 of the given - * one-byte message. - * - * Two more details: normally, appending zero bits to a message which - * is already a multiple of a polynomial produces a larger multiple of that - * polynomial. To enable a CRC to detect this condition, it's common to - * invert the CRC before appending it. This makes the remainder of the - * message+crc come out not as zero, but some fixed non-zero value. - * - * The same problem applies to zero bits prepended to the message, and - * a similar solution is used. Instead of starting with a remainder of - * 0, an initial remainder of all ones is used. As long as you start - * the same way on decoding, it doesn't make a difference. - */ - - -/** - * init_crc32(): generates CRC32 tables - * - * On successful initialization, use count is increased. - * This guarantees that the library functions will stay resident - * in memory, and prevents someone from 'rmmod crc32' while - * a driver that needs it is still loaded. - * This also greatly simplifies drivers, as there's no need - * to call an initialization/cleanup function from each driver. - * Since crc32.o is a library module, there's no requirement - * that the user can unload it. - */ -int -init_crc32(void) -{ - int rc1, rc2, rc; - rc1 = crc32init_le(); - rc2 = crc32init_be(); - rc = rc1 || rc2; - return rc; -} + FREE_N(u32, 256, crc32_table); -/** - * cleanup_crc32(): frees crc32 data when no longer needed - */ -void -cleanup_crc32(void) -{ - crc32cleanup_le(); - crc32cleanup_be(); + FREE_N(REBCNT, 256, CRC_Table); } - -#endif diff --git a/src/core/s-file.c b/src/core/s-file.c index 3c73299558..404052b7ff 100644 --- a/src/core/s-file.c +++ b/src/core/s-file.c @@ -1,275 +1,314 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: s-file.c -** Summary: file and path string handling -** Section: strings -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %s-file.c +// Summary: "file and path string handling" +// Section: strings +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -#define FN_PAD 2 // pad file name len for adding /, /*, and /? +#define FN_PAD 2 // pad file name len for adding /, /*, and /? -/*********************************************************************** -** -*/ REBSER *To_REBOL_Path(void *bp, REBCNT len, REBINT uni, REBFLG dir) -/* -** Convert local filename to a REBOL filename. -** -** Allocate and return a new series with the converted path. -** Return 0 on error. -** -** Reduces width when possible. -** Adds extra space at end for appending a dir /* -** -** REBDIFF: No longer appends current dir to volume when no -** root slash is provided (that odd MSDOS c:file case). -** -***********************************************************************/ +// +// To_REBOL_Path: C +// +// Convert local filename to a REBOL filename. +// +// Allocate and return a new series with the converted path. +// Return NULL on error. +// +// Reduces width when possible to byte-size from unicode, unless the flag +// PATH_OPT_FORCE_UNI_DEST is used. +// +// Adds extra space at end for appending a dir /(star) +// (Note: don't put actual star, as "/" "*" ends this comment) +// +// REBDIFF: No longer appends current dir to volume when no +// root slash is provided (that odd MSDOS c:file case). +// +REBSER *To_REBOL_Path(const void *p, REBCNT len, REBFLGS flags) { - REBOOL colon = 0; // have we hit a ':' yet? - REBOOL slash = 0; // have we hit a '/' yet? - REBUNI c; - REBSER *dst; - REBCNT n; - REBCNT i; +#ifdef TO_WINDOWS + REBOOL saw_colon = FALSE; // have we hit a ':' yet? + REBOOL saw_slash = FALSE; // have we hit a '/' yet? +#endif + + REBUNI c; + REBSER *dst; + REBCNT n; + REBCNT i; + REBOOL unicode = LOGICAL(flags & PATH_OPT_UNI_SRC); + + const REBYTE *bp = unicode ? NULL : cast(const REBYTE *, p); + + const REBUNI *up = unicode ? cast(const REBUNI *, p) : NULL; - if (len == 0) - len = uni ? wcslen((REBUNI*)bp) : LEN_BYTES((REBYTE*)bp); - - n = 0; - dst = ((uni == -1) || (uni && Is_Wide((REBUNI*)bp, len))) - ? Make_Unicode(len+FN_PAD) : Make_Binary(len+FN_PAD); + if (len == 0) + len = unicode ? Strlen_Uni(up) : LEN_BYTES(bp); - for (i = 0; i < len;) { - c = uni ? ((REBUNI*)bp)[i] : ((REBYTE*)bp)[i]; - i++; - if (c == ':') { - // Handle the vol:dir/file format: - if (colon || slash) return 0; // no prior : or / allowed - colon = 1; - if (i < len) { - c = uni ? ((REBUNI*)bp)[i] : ((REBYTE*)bp)[i]; - if (c == '\\' || c == '/') i++; // skip / in foo:/file - } - c = '/'; // replace : with a / - } - else if (c == '\\' || c== '/') { - if (slash > 0) continue; - c = '/'; - slash = 1; - } - else slash = 0; - SET_ANY_CHAR(dst, n++, c); - } - if (dir && c != '/') { // watch for %/c/ case - SET_ANY_CHAR(dst, n++, '/'); - } - SERIES_TAIL(dst) = n; - TERM_SERIES(dst); + n = 0; - // Change C:/ to /C/ (and C:X to /C/X): - if (colon) Insert_Char(dst, 0, (REBCNT)'/'); + // The default is to scan unicode input to see if it contains any + // codepoints over 0xFF, and if not make a byte-sized result string. + // But this can be overridden with PATH_OPT_FORCE_UNI_DEST if (for + // instance) the target is going to be used as a Win32 native string. + // + assert( + (flags & PATH_OPT_FORCE_UNI_DEST) + ? LOGICAL(flags & PATH_OPT_UNI_SRC) + : TRUE + ); + dst = ((flags & PATH_OPT_FORCE_UNI_DEST) || (unicode && Is_Wide(up, len))) + ? Make_Unicode(len + FN_PAD) + : Make_Binary(len + FN_PAD); + + c = '\0'; // for test after loop (in case loop does not run) + for (i = 0; i < len;) { + c = unicode ? up[i] : bp[i]; + i++; +#ifdef TO_WINDOWS + if (c == ':') { + // Handle the vol:dir/file format: + if (saw_colon || saw_slash) return NULL; // no prior : or / allowed + saw_colon = TRUE; + if (i < len) { + c = unicode ? up[i] : bp[i]; + if (c == '\\' || c == '/') i++; // skip / in foo:/file + } + c = '/'; // replace : with a / + } + else if (c == '\\' || c== '/') { + if (saw_slash) continue; + c = '/'; + saw_slash = TRUE; + } + else saw_slash = FALSE; +#endif + SET_ANY_CHAR(dst, n++, c); + } + if ((flags & PATH_OPT_SRC_IS_DIR) && c != '/') { // watch for %/c/ case + SET_ANY_CHAR(dst, n++, '/'); + } + TERM_SEQUENCE_LEN(dst, n); + +#ifdef TO_WINDOWS + // Change C:/ to /C/ (and C:X to /C/X): + if (saw_colon) Insert_Char(dst, 0, '/'); +#endif - return dst; + return dst; } -/*********************************************************************** -** -*/ REBSER *Value_To_REBOL_Path(REBVAL *val, REBOOL dir) -/* -** Helper to above function. -** -***********************************************************************/ +// +// Value_To_REBOL_Path: C +// +// Helper to above function. +// +REBSER *Value_To_REBOL_Path(REBVAL *val, REBOOL is_dir) { - ASSERT1(ANY_BINSTR(val), RP_MISC); - return To_REBOL_Path(VAL_DATA(val), VAL_LEN(val), (REBOOL)!VAL_BYTE_SIZE(val), dir); + assert(ANY_BINSTR(val)); + return To_REBOL_Path( + VAL_RAW_DATA_AT(val), + VAL_LEN_AT(val), + ( + (VAL_BYTE_SIZE(val) ? 0 : PATH_OPT_UNI_SRC) + | (is_dir ? PATH_OPT_SRC_IS_DIR : 0) + ) + ); } -/*********************************************************************** -** -*/ REBSER *To_Local_Path(void *bp, REBCNT len, REBOOL uni, REBFLG full) -/* -** Convert REBOL filename to a local filename. -** -** Allocate and return a new series with the converted path. -** Return 0 on error. -** -** Adds extra space at end for appending a dir /* -** Expands width for OS's that require it. -** -***********************************************************************/ +// +// To_Local_Path: C +// +// Convert REBOL filename to a local filename. +// +// Allocate and return a new series with the converted path. +// Return 0 on error. +// +// Adds extra space at end for appending a dir /(star) +// (Note: don't put actual star, as "/" "*" ends this comment) +// +// Expands width for OS's that require it. +// +REBSER *To_Local_Path(const void *p, REBCNT len, REBOOL unicode, REBOOL full) { - REBUNI c, d; - REBSER *dst; - REBCNT i = 0; - REBCNT n = 0; - REBUNI *out; - REBCHR *lpath; - REBCNT l = 0; + REBUNI c; + REBSER *dst; + REBCNT i = 0; + REBCNT n = 0; + REBUNI *out; + REBCHR *lpath; + REBCNT l = 0; + const REBYTE *bp = unicode ? NULL : cast(const REBYTE *, p); + const REBUNI *up = unicode ? cast(const REBUNI *, p) : NULL; - if (len == 0) - len = uni ? wcslen((REBUNI*)bp) : LEN_BYTES((REBYTE*)bp); + if (len == 0) + len = unicode ? Strlen_Uni(up) : LEN_BYTES(bp); - // Prescan for: /c/dir = c:/dir, /vol/dir = //vol/dir, //dir = ?? - c = GET_CHAR_UNI(uni, bp, i); - if (c == '/') { // %/ - dst = Make_Unicode(len+FN_PAD); - out = UNI_HEAD(dst); -#ifdef TO_WIN32 - i++; - if (i < len) { - c = GET_CHAR_UNI(uni, bp, i); - i++; - } - if (c != '/') { // %/c or %/c/ but not %/ %// %//c - // peek ahead for a '/': - d = '/'; - if (i < len) d = GET_CHAR_UNI(uni, bp, i); - if (d == '/') { // %/c/ => "c:/" - i++; - out[n++] = c; - out[n++] = ':'; - } - else { - out[n++] = OS_DIR_SEP; // %/cc %//cc => "//cc" - i--; - } - } + // Prescan for: /c/dir = c:/dir, /vol/dir = //vol/dir, //dir = ?? + c = unicode ? up[i] : bp[i]; + if (c == '/') { // %/ + dst = Make_Unicode(len+FN_PAD); + out = UNI_HEAD(dst); +#ifdef TO_WINDOWS + i++; + if (i < len) { + c = unicode ? up[i] : bp[i]; + i++; + } + if (c != '/') { // %/c or %/c/ but not %/ %// %//c + // peek ahead for a '/': + REBUNI d = '/'; + if (i < len) + d = unicode ? up[i] : bp[i]; + if (d == '/') { // %/c/ => "c:/" + i++; + out[n++] = c; + out[n++] = ':'; + } + else { + out[n++] = OS_DIR_SEP; // %/cc %//cc => "//cc" + i--; + } + } #endif - out[n++] = OS_DIR_SEP; - } - else { - if (full) l = OS_GET_CURRENT_DIR(&lpath); - dst = Make_Unicode(l + len + FN_PAD); // may be longer (if lpath is encoded) - if (full) { -#ifdef TO_WIN32 - Append_Uni_Uni(dst, lpath, l); + out[n++] = OS_DIR_SEP; + } + else { + if (full) l = OS_GET_CURRENT_DIR(&lpath); + dst = Make_Unicode(l + len + FN_PAD); // may be longer (if lpath is encoded) + if (full) { +#ifdef TO_WINDOWS + assert(sizeof(REBCHR) == sizeof(REBUNI)); + Append_Uni_Uni(dst, cast(const REBUNI*, lpath), l); #else - REBINT clen = Decode_UTF8(UNI_HEAD(dst), lpath, l, FALSE); - dst->tail = abs(clen); - //Append_Bytes(dst, lpath); + REBINT clen = Decode_UTF8_Negative_If_Latin1( + UNI_HEAD(dst), cast(const REBYTE*, lpath), l, FALSE + ); + SET_SERIES_LEN(dst, abs(clen)); + //Append_Unencoded(dst, lpath); #endif - Append_Byte(dst, OS_DIR_SEP); - OS_FREE(lpath); - } - out = UNI_HEAD(dst); - n = SERIES_TAIL(dst); - } + Append_Codepoint_Raw(dst, OS_DIR_SEP); + OS_FREE(lpath); + } + out = UNI_HEAD(dst); + n = SER_LEN(dst); + } - // Prescan each file segment for: . .. directory names: - // (Note the top of this loop always follows / or start) - while (i < len) { - if (full) { - // Peek for: . .. - c = GET_CHAR_UNI(uni, bp, i); - if (c == '.') { // . - i++; - c = GET_CHAR_UNI(uni, bp, i); - if (c == '.') { // .. - c = GET_CHAR_UNI(uni, bp, i+1); - if (c == 0 || c == '/') { // ../ or .. - i++; - // backup a dir - n -= (n > 2) ? 2 : n; - for (; n > 0 && out[n] != OS_DIR_SEP; n--); - c = c ? 0 : OS_DIR_SEP; // add / if necessary - } - // fall through on invalid ..x combination: - } - else { // .a or . or ./ - if (c == '/') { - i++; - c = 0; // ignore it - } - else if (c) c = '.'; // for store below - } - if (c) out[n++] = c; - } - } - for (; i < len; i++) { - c = GET_CHAR_UNI(uni, bp, i); - if (c == '/') { - if (n == 0 || out[n-1] != OS_DIR_SEP) out[n++] = OS_DIR_SEP; - i++; - break; - } - out[n++] = c; - } - } - out[n] = 0; - SERIES_TAIL(dst) = n; -// TERM_SERIES(dst); -// Debug_Uni(dst); + // Prescan each file segment for: . .. directory names: + // (Note the top of this loop always follows / or start) + while (i < len) { + // each iteration takes care of one segment of the path, i.e. stops after OS_DIR_SEP + if (full) { + // Peek for: . .. + c = unicode ? up[i] : bp[i]; + if (c == '.') { // . + i++; + c = unicode ? up[i] : bp[i]; + if (c == '.') { // .. + c = unicode ? up[i + 1] : bp[i + 1]; + if (c == 0 || c == '/') { // ../ or .. + i++; + // backup a dir + n -= (n > 2) ? 2 : n; + for (; n > 0 && out[n] != OS_DIR_SEP; n--); + c = c ? 0 : OS_DIR_SEP; // add / if necessary + } + // fall through on invalid ..x combination: + } + else { // .a or . or ./ + if (c == '/') { + c = 0; // ignore it + } + else if (c) c = '.'; // for store below + } + if (c) out[n++] = c; + } + } + for (; i < len; i++) { + c = unicode ? up[i] : bp[i]; + if (c == '/') { + if (n == 0 || out[n-1] != OS_DIR_SEP) out[n++] = OS_DIR_SEP; + i++; + break; + } + out[n++] = c; + } + } + out[n] = 0; + SET_SERIES_LEN(dst, n); + ASSERT_SERIES_TERM(dst); - return dst; + return dst; } -/*********************************************************************** -** -*/ REBSER *Value_To_Local_Path(REBVAL *val, REBFLG full) -/* -** Helper to above function. -** -***********************************************************************/ +// +// Value_To_Local_Path: C +// +// Helper to above function. +// +REBSER *Value_To_Local_Path(REBVAL *val, REBOOL full) { - ASSERT1(ANY_BINSTR(val), RP_MISC); - return To_Local_Path(VAL_DATA(val), VAL_LEN(val), (REBOOL)!VAL_BYTE_SIZE(val), full); + assert(ANY_BINSTR(val)); + return To_Local_Path( + VAL_RAW_DATA_AT(val), VAL_LEN_AT(val), NOT(VAL_BYTE_SIZE(val)), full + ); } -/*********************************************************************** -** -*/ REBSER *Value_To_OS_Path(REBVAL *val) -/* -** Helper to above function. -** -***********************************************************************/ +// +// Value_To_OS_Path: C +// +// Helper to above function. +// +REBSER *Value_To_OS_Path(const REBVAL *val, REBOOL full) { - REBSER *ser; // will be unicode size -#ifndef TO_WIN32 - REBSER *bin; - REBCNT n; + REBSER *ser; // will be unicode size +#ifndef TO_WINDOWS + REBSER *bin; #endif - ASSERT1(ANY_BINSTR(val), RP_MISC); + assert(ANY_BINSTR(val)); - ser = To_Local_Path(VAL_DATA(val), VAL_LEN(val), (REBOOL)!VAL_BYTE_SIZE(val), TRUE); + ser = To_Local_Path( + VAL_RAW_DATA_AT(val), VAL_LEN_AT(val), NOT(VAL_BYTE_SIZE(val)), full + ); -#ifndef TO_WIN32 - // Posix needs UTF8 conversion: - n = Length_As_UTF8(UNI_HEAD(ser), SERIES_TAIL(ser), TRUE, OS_CRLF); - bin = Make_Binary(n + FN_PAD); - Encode_UTF8(BIN_HEAD(bin), n+FN_PAD, UNI_HEAD(ser), &n, TRUE, OS_CRLF); - SERIES_TAIL(bin) = n; - TERM_SERIES(bin); - ser = bin; +#ifndef TO_WINDOWS + // Posix needs UTF8 conversion: + bin = Make_UTF8_Binary( + UNI_HEAD(ser), SER_LEN(ser), FN_PAD, OPT_ENC_UNISRC + ); + Free_Series(ser); + ser = bin; #endif - return ser; + return ser; } diff --git a/src/core/s-find.c b/src/core/s-find.c index 9d5bf6f184..d1fa9e0843 100644 --- a/src/core/s-find.c +++ b/src/core/s-find.c @@ -1,573 +1,761 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: s-find.c -** Summary: string search and comparison -** Section: strings -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %s-find.c +// Summary: "string search and comparison" +// Section: strings +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/*********************************************************************** -** -*/ REBINT Compare_Binary_Vals(REBVAL *v1, REBVAL *v2) -/* -** Compare two binary values. -** -** Compares bytes, not chars. Return the difference. -** -** Used for: Binary comparision function -** -***********************************************************************/ +// +// Compare_Binary_Vals: C +// +// Compare two binary values. +// +// Compares bytes, not chars. Return the difference. +// +// Used for: Binary comparision function +// +REBINT Compare_Binary_Vals(const RELVAL *v1, const RELVAL *v2) { - REBCNT l1 = VAL_LEN(v1); - REBCNT l2 = VAL_LEN(v2); - REBCNT len = MIN(l1, l2); - REBINT n; - - if (IS_IMAGE(v1)) len *= 4; - - n = memcmp(VAL_BIN_DATA(v1), VAL_BIN_DATA(v2), len); - - if (n != 0) return n; - - return l1 - l2; + REBCNT l1 = VAL_LEN_AT(v1); + REBCNT l2 = VAL_LEN_AT(v2); + REBCNT len = MIN(l1, l2); + REBINT n; + + if (IS_IMAGE(v1)) len *= 4; + + // Image is not "byte size" (note multiplied by 4 above) but still calls + // binary compare...can't use VAL_BIN_AT as long as it does, because + // that asserts BYTE_SIZE(). + // + n = memcmp( + SER_AT_RAW(SER_WIDE(VAL_SERIES(v1)), VAL_SERIES(v1), VAL_INDEX(v1)), + SER_AT_RAW(SER_WIDE(VAL_SERIES(v2)), VAL_SERIES(v2), VAL_INDEX(v2)), + len + ); + + if (n != 0) return n; + + return l1 - l2; } -/*********************************************************************** -** -*/ REBINT Compare_Bytes(REBYTE *b1, REBYTE *b2, REBCNT len, REBOOL uncase) -/* -** Compare two byte-wide strings. Return lexical difference. -** -** Uncase: compare is case-insensitive. -** -***********************************************************************/ +// +// Compare_Bytes: C +// +// Compare two byte-wide strings. Return lexical difference. +// +// Uncase: compare is case-insensitive. +// +REBINT Compare_Bytes(const REBYTE *b1, const REBYTE *b2, REBCNT len, REBOOL uncase) { - REBINT d; + REBINT d; - for (; len > 0; len--, b1++, b2++) { + for (; len > 0; len--, b1++, b2++) { - if (uncase) - d = LO_CASE(*b1) - LO_CASE(*b2); - else - d = *b1 - *b2; + if (uncase) + d = LO_CASE(*b1) - LO_CASE(*b2); + else + d = *b1 - *b2; - if (d != 0) return d; - } + if (d != 0) return d; + } - return 0; + return 0; } -/*********************************************************************** -** -*/ REBYTE *Match_Bytes(REBYTE *src, REBYTE *pat) -/* -** Compare two binary strings. Return where the first differed. -** Case insensitive. -** -***********************************************************************/ +// +// Match_Bytes: C +// +// Compare two binary strings. Return where the first differed. +// Case insensitive. +// +const REBYTE *Match_Bytes(const REBYTE *src, const REBYTE *pat) { - while (*src && *pat) { - if (LO_CASE(*src++) != LO_CASE(*pat++)) return 0; - } + while (*src && *pat) { + if (LO_CASE(*src++) != LO_CASE(*pat++)) return 0; + } - if (*pat) return 0; // if not at end of pat, then error + if (*pat) return 0; // if not at end of pat, then error - return src; + return src; } -/*********************************************************************** -** -*/ REBFLG Match_Sub_Path(REBSER *s1, REBSER *s2) -/* -** Compare two file path series, regardless of char size. -** Return TRUE if s1 is a subpath of s2. -** Case insensitive. -** -***********************************************************************/ +// +// Match_Sub_Path: C +// +// Compare two file path series, regardless of char size. +// Return TRUE if s1 is a subpath of s2. +// Case insensitive. +// +REBOOL Match_Sub_Path(REBSER *s1, REBSER *s2) { - REBCNT len = s1->tail; - REBCNT n; - REBUNI c1 = 0; - REBUNI c2; - -// Debug_Series(s1); -// Debug_Series(s2); + REBCNT len = SER_LEN(s1); + REBCNT n; + REBUNI c1 = 0; + REBUNI c2; - // s1 len must be <= s2 len - if (len > s2->tail) return FALSE; + // s1 len must be <= s2 len + if (len > SER_LEN(s2)) return FALSE; - for (n = 0; n < len; n++) { // includes terminator + for (n = 0; n < len; n++) { // includes terminator - c1 = GET_ANY_CHAR(s1, n); - c2 = GET_ANY_CHAR(s2, n); + c1 = GET_ANY_CHAR(s1, n); + c2 = GET_ANY_CHAR(s2, n); - if (c1 < UNICODE_CASES) c1 = LO_CASE(c1); - if (c2 < UNICODE_CASES) c2 = LO_CASE(c2); + if (c1 < UNICODE_CASES) c1 = LO_CASE(c1); + if (c2 < UNICODE_CASES) c2 = LO_CASE(c2); - if (c1 != c2) break; - } + if (c1 != c2) break; + } - // a/b matches: a/b, a/b/, a/b/c - c2 = GET_ANY_CHAR(s2, n); - return ( - n >= len // all chars matched - && // Must be at end or at dir sep: - (c1 == '/' || c1 == '\\' - || c2 == 0 || c2 == '/' || c2 == '\\') - ); + // a/b matches: a/b, a/b/, a/b/c + c2 = GET_ANY_CHAR(s2, n); + return LOGICAL( + n >= len // all chars matched + && // Must be at end or at dir sep: + (c1 == '/' || c1 == '\\' + || c2 == 0 || c2 == '/' || c2 == '\\') + ); } -/*********************************************************************** -** -*/ REBINT Compare_Uni_Byte(REBUNI *u1, REBYTE *b2, REBCNT len, REBOOL uncase) -/* -** Compare unicode and byte-wide strings. Return lexical difference. -** -** Uncase: compare is case-insensitive. -** -***********************************************************************/ +// +// Compare_Uni_Byte: C +// +// Compare unicode and byte-wide strings. Return lexical difference. +// +// Uncase: compare is case-insensitive. +// +REBINT Compare_Uni_Byte(REBUNI *u1, REBYTE *b2, REBCNT len, REBOOL uncase) { - REBINT d; - REBUNI c1; - REBUNI c2; + REBINT d; + REBUNI c1; + REBUNI c2; - for (; len > 0; len--) { + for (; len > 0; len--) { - c1 = *u1++; - c2 = *b2++; + c1 = *u1++; + c2 = *b2++; - if (uncase && c1 < UNICODE_CASES) - d = LO_CASE(c1) - LO_CASE(c2); - else - d = c1 - c2; + if (uncase && c1 < UNICODE_CASES) + d = LO_CASE(c1) - LO_CASE(c2); + else + d = c1 - c2; - if (d != 0) return d; - } + if (d != 0) return d; + } - return 0; + return 0; } -/*********************************************************************** -** -*/ REBINT Compare_Uni_Str(REBUNI *u1, REBUNI *u2, REBCNT len, REBOOL uncase) -/* -** Compare two unicode-wide strings. Return lexical difference. -** -** Uncase: compare is case-insensitive. -** -***********************************************************************/ +// +// Compare_Uni_Str: C +// +// Compare two unicode-wide strings. Return lexical difference. +// +// Uncase: compare is case-insensitive. +// +REBINT Compare_Uni_Str(REBUNI *u1, REBUNI *u2, REBCNT len, REBOOL uncase) { - REBINT d; - REBUNI c1; - REBUNI c2; + REBINT d; + REBUNI c1; + REBUNI c2; - for (; len > 0; len--) { + for (; len > 0; len--) { - c1 = *u1++; - c2 = *u2++; + c1 = *u1++; + c2 = *u2++; - if (uncase && c1 < UNICODE_CASES && c2 < UNICODE_CASES) - d = LO_CASE(c1) - LO_CASE(c2); - else - d = c1 - c2; + if (uncase && c1 < UNICODE_CASES && c2 < UNICODE_CASES) + d = LO_CASE(c1) - LO_CASE(c2); + else + d = c1 - c2; - if (d != 0) return d; - } + if (d != 0) return d; + } - return 0; + return 0; } -/*********************************************************************** -** -*/ REBINT Compare_String_Vals(REBVAL *v1, REBVAL *v2, REBOOL uncase) -/* -** Compare two string values. Either can be byte or unicode wide. -** -** Uncase: compare is case-insensitive. -** -** Used for: general string comparions (various places) -** -***********************************************************************/ +// +// Compare_String_Vals: C +// +// Compare two string values. Either can be byte or unicode wide. +// +// Uncase: compare is case-insensitive. +// +// Used for: general string comparions (various places) +// +REBINT Compare_String_Vals(const RELVAL *v1, const RELVAL *v2, REBOOL uncase) { - REBCNT l1 = VAL_LEN(v1); - REBCNT l2 = VAL_LEN(v2); - REBCNT len = MIN(l1, l2); - REBINT n; - - if (IS_BINARY(v1) || IS_BINARY(v2)) uncase = FALSE; - - if (VAL_BYTE_SIZE(v1)) { // v1 is 8 - if (VAL_BYTE_SIZE(v2)) - n = Compare_Bytes(VAL_BIN_DATA(v1), VAL_BIN_DATA(v2), len, uncase); - else - n = -Compare_Uni_Byte(VAL_UNI_DATA(v2), VAL_BIN_DATA(v1), len, uncase); - } - else { // v1 is 16 - if (VAL_BYTE_SIZE(v2)) - n = Compare_Uni_Byte(VAL_UNI_DATA(v1), VAL_BIN_DATA(v2), len, uncase); - else - n = Compare_Uni_Str(VAL_UNI_DATA(v1), VAL_UNI_DATA(v2), len, uncase); - } - - if (n != 0) return n; - return l1 - l2; + REBCNT l1 = VAL_LEN_AT(v1); + REBCNT l2 = VAL_LEN_AT(v2); + REBCNT len = MIN(l1, l2); + REBINT n; + + if (IS_BINARY(v1) || IS_BINARY(v2)) uncase = FALSE; + + if (VAL_BYTE_SIZE(v1)) { // v1 is 8 + if (VAL_BYTE_SIZE(v2)) + n = Compare_Bytes(VAL_BIN_AT(v1), VAL_BIN_AT(v2), len, uncase); + else + n = -Compare_Uni_Byte(VAL_UNI_AT(v2), VAL_BIN_AT(v1), len, uncase); + } + else { // v1 is 16 + if (VAL_BYTE_SIZE(v2)) + n = Compare_Uni_Byte(VAL_UNI_AT(v1), VAL_BIN_AT(v2), len, uncase); + else + n = Compare_Uni_Str(VAL_UNI_AT(v1), VAL_UNI_AT(v2), len, uncase); + } + + if (n != 0) return n; + return l1 - l2; } -/*********************************************************************** -** -*/ REBINT Compare_UTF8(REBYTE *s1, REBYTE *s2, REBCNT l2) -/* -** Compare two UTF8 strings. -** -** It is necessary to decode the strings to check if the match -** case-insensitively. -** -** Returns: -** -3: no match, s2 > s1 -** -1: no match, s1 > s2 -** 0: exact match -** 1: non-case match, s2 > s1 -** 3: non-case match, s1 > s2 -** -** So, result + 2 for no-match gives proper sort order. -** And, result - 2 for non-case match gives sort order. -** -** Used for: WORD comparison. -** -***********************************************************************/ +// +// Compare_UTF8: C +// +// Compare two UTF8 strings. +// +// It is necessary to decode the strings to check if the match +// case-insensitively. +// +// Returns: +// -3: no match, s2 > s1 +// -1: no match, s1 > s2 +// 0: exact match +// 1: non-case match, s2 > s1 +// 3: non-case match, s1 > s2 +// +// So, result + 2 for no-match gives proper sort order. +// And, result - 2 for non-case match gives sort order. +// +// Used for: WORD comparison. +// +REBINT Compare_UTF8(const REBYTE *s1, const REBYTE *s2, REBCNT l2) { - REBINT c1, c2; - REBCNT l1 = LEN_BYTES(s1); - REBINT result = 0; - - for (; l1 > 0 && l2 > 0; s1++, s2++, l1--, l2--) { - c1 = (REBYTE)*s1; - c2 = (REBYTE)*s2; - if (c1 > 127) c1 = Decode_UTF8_Char(&s1, &l1); //!!! can return 0 on error! - if (c2 > 127) c2 = Decode_UTF8_Char(&s2, &l2); - if (c1 != c2) { - if (c1 >= UNICODE_CASES || c2 >= UNICODE_CASES || - LO_CASE(c1) != LO_CASE(c2)) { - return (c1 > c2) ? -1 : -3; - } - if (!result) result = (c1 > c2) ? 3 : 1; - } - } - if (l1 != l2) result = (l1 > l2) ? -1 : -3; - - return result; + REBUNI c1, c2; + REBCNT l1 = LEN_BYTES(s1); + REBINT result = 0; + + for (; l1 > 0 && l2 > 0; s1++, s2++, l1--, l2--) { + c1 = *s1; + c2 = *s2; + if (c1 > 127) { + s1 = Back_Scan_UTF8_Char(&c1, s1, &l1); + assert(s1); // UTF8 should have already been verified good + } + if (c2 > 127) { + s2 = Back_Scan_UTF8_Char(&c2, s2, &l2); + assert(s2); // UTF8 should have already been verified good + } + if (c1 != c2) { + if (c1 >= UNICODE_CASES || c2 >= UNICODE_CASES || + LO_CASE(c1) != LO_CASE(c2)) { + return (c1 > c2) ? -1 : -3; + } + if (!result) result = (c1 > c2) ? 3 : 1; + } + } + if (l1 != l2) result = (l1 > l2) ? -1 : -3; + + return result; } -/*********************************************************************** -** -*/ REBCNT Find_Byte_Str(REBSER *series, REBCNT index, REBYTE *b2, REBCNT l2, REBFLG uncase, REBFLG match) -/* -** Find a byte string within a byte string. Optimized for speed. -** -** Returns starting position or NOT_FOUND. -** -** Uncase: compare is case-insensitive. -** Match: compare to first position only. -** -** NOTE: Series tail must be > index. -** -***********************************************************************/ +// +// Find_Byte_Str: C +// +// Find a byte string within a byte string. Optimized for speed. +// +// Returns starting position or NOT_FOUND. +// +// Uncase: compare is case-insensitive. +// Match: compare to first position only. +// +// NOTE: Series tail must be > index. +// +REBCNT Find_Byte_Str(REBSER *series, REBCNT index, REBYTE *b2, REBCNT l2, REBOOL uncase, REBOOL match) { - REBYTE *b1; - REBYTE *e1; - REBCNT l1; - REBYTE c; - REBCNT n; + REBYTE *b1; + REBYTE *e1; + REBCNT l1; + REBYTE c; + REBCNT n; - // The pattern empty or is longer than the target: - if (l2 == 0 || (l2 + index) > SERIES_TAIL(series)) return NOT_FOUND; + // The pattern empty or is longer than the target: + if (l2 == 0 || (l2 + index) > SER_LEN(series)) return NOT_FOUND; - b1 = BIN_SKIP(series, index); - l1 = SERIES_TAIL(series) - index; + b1 = BIN_AT(series, index); + l1 = SER_LEN(series) - index; - e1 = b1 + (match ? 1 : l1 - (l2 - 1)); + e1 = b1 + (match ? 1 : l1 - (l2 - 1)); - c = *b2; // first char + c = *b2; // first char - if (!uncase) { + if (!uncase) { - while (b1 != e1) { - if (*b1 == c) { // matched first char - for (n = 1; n < l2; n++) { - if (b1[n] != b2[n]) break; - } - if (n == l2) return (b1 - BIN_HEAD(series)); - } - b1++; - } + while (b1 != e1) { + if (*b1 == c) { // matched first char + for (n = 1; n < l2; n++) { + if (b1[n] != b2[n]) break; + } + if (n == l2) return (b1 - BIN_HEAD(series)); + } + b1++; + } - } else { + } else { - c = (REBYTE)LO_CASE(c); // OK! (never > 255) + c = (REBYTE)LO_CASE(c); // OK! (never > 255) - while (b1 != e1) { - if (LO_CASE(*b1) == c) { // matched first char - for (n = 1; n < l2; n++) { - if (LO_CASE(b1[n]) != LO_CASE(b2[n])) break; - } - if (n == l2) return (b1 - BIN_HEAD(series)); - } - b1++; - } + while (b1 != e1) { + if (LO_CASE(*b1) == c) { // matched first char + for (n = 1; n < l2; n++) { + if (LO_CASE(b1[n]) != LO_CASE(b2[n])) break; + } + if (n == l2) return (b1 - BIN_HEAD(series)); + } + b1++; + } - } + } - return NOT_FOUND; + return NOT_FOUND; } -/*********************************************************************** -** -*/ REBCNT Find_Str_Str(REBSER *ser1, REBCNT head, REBCNT index, REBCNT tail, REBINT skip, REBSER *ser2, REBCNT index2, REBCNT len, REBCNT flags) -/* -** General purpose find a substring. -** -** Supports: forward/reverse with skip, cased/uncase, Unicode/byte. -** -** Skip can be set positive or negative (for reverse). -** -** Flags are set according to ALL_FIND_REFS -** -***********************************************************************/ +// +// Find_Str_Str: C +// +// General purpose find a substring. +// +// Supports: forward/reverse with skip, cased/uncase, Unicode/byte. +// +// Skip can be set positive or negative (for reverse). +// +// Flags are set according to ALL_FIND_REFS +// +REBCNT Find_Str_Str(REBSER *ser1, REBCNT head, REBCNT index, REBCNT tail, REBINT skip, REBSER *ser2, REBCNT index2, REBCNT len, REBCNT flags) { - REBUNI c1; - REBUNI c2; - REBUNI c3; - REBCNT n = 0; - REBOOL uncase = !(flags & AM_FIND_CASE); // uncase = case insenstive - - c2 = GET_ANY_CHAR(ser2, index2); // starting char - if (uncase && c2 < UNICODE_CASES) c2 = LO_CASE(c2); - - for (; index >= head && index < tail; index += skip) { - - c1 = GET_ANY_CHAR(ser1, index); - if (uncase && c1 < UNICODE_CASES) c1 = LO_CASE(c1); - - if (c1 == c2) { - for (n = 1; n < len; n++) { - c1 = GET_ANY_CHAR(ser1, index+n); - c3 = GET_ANY_CHAR(ser2, index2+n); - if (uncase && c1 < UNICODE_CASES && c3 < UNICODE_CASES) { - if (LO_CASE(c1) != LO_CASE(c3)) break; - } else { - if (c1 != c3) break; - } - } - if (n == len) { - if (flags & AM_FIND_TAIL) return index + len; - return index; - } - } - if (flags & AM_FIND_MATCH) break; - } - - return NOT_FOUND; + REBUNI c1; + REBUNI c2; + REBUNI c3; + REBCNT n = 0; + REBOOL uncase = NOT(flags & AM_FIND_CASE); // case insenstive + + c2 = GET_ANY_CHAR(ser2, index2); // starting char + if (uncase && c2 < UNICODE_CASES) c2 = LO_CASE(c2); + + for (; index >= head && index < tail; index += skip) { + + c1 = GET_ANY_CHAR(ser1, index); + if (uncase && c1 < UNICODE_CASES) c1 = LO_CASE(c1); + + if (c1 == c2) { + for (n = 1; n < len; n++) { + c1 = GET_ANY_CHAR(ser1, index+n); + c3 = GET_ANY_CHAR(ser2, index2+n); + if (uncase && c1 < UNICODE_CASES && c3 < UNICODE_CASES) { + if (LO_CASE(c1) != LO_CASE(c3)) break; + } else { + if (c1 != c3) break; + } + } + if (n == len) { + if (flags & AM_FIND_TAIL) return index + len; + return index; + } + } + if (flags & AM_FIND_MATCH) break; + } + + return NOT_FOUND; } -/*********************************************************************** -** -*/ REBCNT Find_Str_Char(REBSER *ser, REBCNT head, REBCNT index, REBCNT tail, REBINT skip, REBUNI c2, REBCNT flags) -/* -** General purpose find a char in a string. -** -** Supports: forward/reverse with skip, cased/uncase, Unicode/byte. -** -** Skip can be set positive or negative (for reverse). -** -** Flags are set according to ALL_FIND_REFS -** -***********************************************************************/ -{ - REBUNI c1; - REBOOL uncase = !GET_FLAG(flags, ARG_FIND_CASE-1); // uncase = case insenstive - - if (uncase && c2 < UNICODE_CASES) c2 = LO_CASE(c2); - - for (; index >= head && index < tail; index += skip) { - - c1 = GET_ANY_CHAR(ser, index); - if (uncase && c1 < UNICODE_CASES) c1 = LO_CASE(c1); - - if (c1 == c2) return index; - - if GET_FLAG(flags, ARG_FIND_MATCH-1) break; - } - - return NOT_FOUND; +#if !defined(NDEBUG) + +// +// Find_Str_Char_Old: C +// +// The Find_Str_Char routine turned out to be kind of a bottleneck in code +// that was heavily reliant on PARSE, so it became slightly interesting to +// try and optimize it a bit. The old routine is kept around for the +// moment (and maybe indefinitely) as a debug check to make sure the +// optimized routine gives back the same answer. +// +// Note: the old routine did not handle negative skips correctly, because +// index is unsigned and it tries to use a comparison crossing zero. This +// is handled by the new version, and will be vetted separately. +// +static REBCNT Find_Str_Char_Old( + REBSER *ser, + REBCNT head, + REBCNT index, + REBCNT tail, + REBINT skip, + REBUNI c2, + REBCNT flags +) { + REBOOL uncase = NOT(flags & AM_FIND_CASE); // case insensitive + + if (uncase && c2 < UNICODE_CASES) c2 = LO_CASE(c2); + + for (; index >= head && index < tail; index += skip) { + REBUNI c1 = GET_ANY_CHAR(ser, index); + if (uncase && c1 < UNICODE_CASES) + c1 = LO_CASE(c1); + + if (c1 == c2) + return index; + + if (flags & AM_FIND_MATCH) + break; + } + + return NOT_FOUND; } +#endif -/*********************************************************************** -** -*/ REBCNT Find_Str_Bitset(REBSER *ser, REBCNT head, REBCNT index, REBCNT tail, REBINT skip, REBSER *bset, REBCNT flags) -/* -** General purpose find a bitset char in a string. -** -** Supports: forward/reverse with skip, cased/uncase, Unicode/byte. -** -** Skip can be set positive or negative (for reverse). -** -** Flags are set according to ALL_FIND_REFS -** -***********************************************************************/ -{ - REBUNI c1; - REBOOL uncase = !GET_FLAG(flags, ARG_FIND_CASE-1); // uncase = case insenstive - - for (; index >= head && index < tail; index += skip) { - - c1 = GET_ANY_CHAR(ser, index); - //if (uncase && c1 < UNICODE_CASES) { - // if (Check_Bit(bset, LO_CASE(c1)) || Check_Bit(bset, UP_CASE(c1))) - // return index; - //} - //else - if (Check_Bit(bset, c1, uncase)) return index; +// +// Find_Str_Char: C +// +// General purpose find a char in a string, which works with both unicode and +// byte-sized strings. Supports AM_FIND_CASE for case-sensitivity (as +// opposed to the case-insensitive default) and AM_FIND_MATCH to check only +// the character at the current position and then stop. +// +// Skip can be set positive or negative (for reverse), and will be bounded +// by the `start` and `end`. +// +// Note that features like "/LAST" are handled at a higher level and +// translated into SKIP=(-1) and starting at (highest - 1). +// +// *This routine is called a lot*, especially in PARSE. So the seeming +// micro-optimization of it was motivated by that. It's not all that +// complicated, in truth. For the near-term, the old implementation of the +// routine is run in parallel as a debug check to ensure the same result +// is coming from the optimized code. +// +REBCNT Find_Str_Char( + REBUNI uni, // character to look for + REBSER *series, // series with width sizeof(REBYTE) or sizeof(REBUNI) + REBCNT lowest, // lowest return index + REBCNT index_orig, // first index to examine (if out of range, NOT_FOUND) + REBCNT highest, // *one past* highest return result (e.g. SER_LEN) + REBINT skip, // step amount while searching, can be negative! + REBFLGS flags // AM_FIND_CASE, AM_FIND_MATCH +) { + // Because the skip may be negative, and we don't check before we step + // and may "cross zero", it's necessary to use a signed index to be + // able to notice that crossing. + // + REBINT index; + + // We establish an array of two potential cases we are looking for. + // If there aren't actually two, this array sets both to be the same (vs. + // using something like a '\0' in one cell if they are) because FIND is + // able to seek NUL in strings. + // + REBUNI casings[2]; + + if (LOGICAL(flags & AM_FIND_CASE)) { // case-*sensitive* + casings[0] = uni; + casings[1] = uni; + } + else { + casings[0] = uni < UNICODE_CASES ? LO_CASE(uni) : uni; + casings[1] = uni < UNICODE_CASES ? UP_CASE(uni) : uni; + } + + assert(lowest <= SER_LEN(series)); + assert(index_orig <= SER_LEN(series)); + assert(highest <= SER_LEN(series)); + + // !!! Would skip = 0 be a clearer expression of /MATCH, as in "there + // is no skip count"? Perhaps in the interface as /SKIP NONE and then + // translated to 0 for this internal call? + // + assert(skip != 0); + + // Rest of routine assumes we are inside of the range to begin with. + // + if (index_orig < lowest || index_orig >= highest || lowest == highest) + goto return_not_found; + + // Past this point we'll be using the signed index. + // + index = cast(REBINT, index_orig); + + // /MATCH only does one check at the current position for the character + // and then returns. It basically subverts any optimization we might + // try that uses memory range functions/etc, and if "/skip 0" were the + // replacement for match it would have to be handled separately anyway. + // + if (LOGICAL(flags & AM_FIND_MATCH)) { + REBUNI single = GET_ANY_CHAR(series, index_orig); + if (single == casings[0] || single == casings[1]) + goto return_index; + goto return_not_found; + } + + // If searching a potentially much longer string, take opportunities to + // use optimized C library functions if possible. + // + if (BYTE_SIZE(series)) { + REBYTE *bp = BIN_HEAD(series); + REBYTE breakset[3]; + + // We need to cover when the lowercase or uppercase variant of a + // unicode character is <= 0xFF even though the character itself + // is not. Build our breakset while we're doing the test. Note + // that this handles the case-sensitive version fine because it + // will be noticed if breakset[0] and breakset[1] are the same. + // + if (casings[0] > 0xFF) { + if (casings[1] > 0xFF) goto return_not_found; + + breakset[0] = cast(REBYTE, casings[1]); + breakset[1] = '\0'; + } + else { + breakset[0] = cast(REBYTE, casings[0]); + + if (casings[1] > 0xFF || casings[1] == casings[0]) { + breakset[1] = '\0'; + } + else { + breakset[1] = cast(REBYTE, casings[1]); + breakset[2] = '\0'; + } + } + + // breakset[0] will be '\0' if we're literally searching for a '\0'. + // But it will also be '\0' if no candidate we were searching for + // would be byte-sized, and hence won't be found...so return NOT_FOUND + // if the latter is true. + // + if (breakset[0] == '\0' && uni != '\0') + goto return_not_found; + + if (skip == 1 && breakset[1] == '\0') { + // + // For case-sensitive comparisons, or if the character has no + // distinction in upper and lower cases, or if only one of the + // two unicode casings is byte-sized...we can use use the + // optimized `memchr()` operation to find the single byte. + // This can only work if SKIP is 1. + // + void *v = memchr(bp + index, breakset[0], highest - index); + if (v) { + index = cast(REBYTE*, v) - bp; + goto return_index; + } + } + else { + // If the comparison is case-insensitive and the character has + // a distinct upper and lower case, there are two candidate + // characters we are looking for. + // + // We use a threshold to decide if it's worth it to use a library + // routine that can only search forward to null terminators vs. + // a for loop we can limit, run reverse, or skip by more than 1. + // ( routines also can't be used to hunt for a 0 byte.) + // + if ( + skip == 1 + && (SER_LEN(series) - highest) < ((highest - lowest) / 2) + && uni != '\0' + ) { + // The `strcspn()` optimized routine can be used to check for + // a set of characters, and returns the number of characters + // read before a match was found. It will be the length of + // the string if no match. + // + while (TRUE) { + index += strcspn( + cast(char*, bp + index), cast(char*, breakset) + ); + if (index >= cast(REBINT, highest)) + goto return_not_found; + + goto return_index; + } + } + else { + // We're skipping by more than one, going in reverse, or + // looking for a NULL byte. Can't use any fancy tricks + // (besides the trick of precalculating the casings) + // + while (TRUE) { + if (bp[index] == breakset[0] || bp[index] == breakset[1]) + goto return_index; + + index += skip; + if (index < cast(REBINT, lowest)) break; + if (index >= cast(REBINT, highest)) break; + } + } + } + } + else { + REBUNI *up = UNI_HEAD(series); + + // Can't actually use wchar_t routines in the general case, because + // REBUNI and wchar_t may not be the same size...though on Win32 + // compilers must guarantee `sizeof(wchar_t) == 2`. But consider + // adapting `casings` for a similar optimization to what's being + // done for byte-sized strings at some later date, perhaps based + // on a check of `sizeof(wchar_t) == sizeof(REBUNI)`. + // + while (TRUE) { + if (up[index] == casings[0] || up[index] == casings[1]) + goto return_index; + + index += skip; + if (index < cast(REBINT, lowest)) break; + if (index >= cast(REBINT, highest)) break; + } + } + +return_not_found: + +#if !defined(NDEBUG) + assert(NOT_FOUND == Find_Str_Char_Old( + series, lowest, index_orig, highest, skip, uni, flags + )); +#endif + return NOT_FOUND; - if (flags & AM_FIND_MATCH) break; - } - - return NOT_FOUND; -} +return_index: +#if !defined(NDEBUG) + assert(cast(REBCNT, index) == Find_Str_Char_Old( + series, lowest, index_orig, highest, skip, uni, flags + )); +#endif -#ifdef old -/*********************************************************************** -** -x*/ REBCNT Match_2_String(REBSER *series, REBCNT index, REBYTE *str, REBCNT len, REBINT uncase) -/* -** (Evaluate if there is another function to use. ???!!!) -** -** Used for: PARSE function -** -***********************************************************************/ -{ - REBYTE *ser = STR_SKIP(series, index); - REBCNT tail = series->tail; - - if (uncase) { - for (;len > 0 && index < tail; index++, len--) { - if (*ser++ != *str++) return 0; - } - } else { - for (;len > 0 && index < tail; index++, len--) { - if (LO_CASE(*ser++) != LO_CASE(*str++)) return 0; - } - } - if (len == 0) return index; - return 0; + assert(index >= 0); + return cast(REBCNT, index); } -/*********************************************************************** -** -x*/ REBYTE *Match_Str_Part(REBYTE *str, REBYTE *pat, REBCNT len) -/* -** If the string matches the pattern for the given length -** return the char string just past the match (in str). -** Else, return 0. A case insensitive compare is made. -** -***********************************************************************/ -{ - REBYTE *pp = pat; - REBYTE *cp = str; - for (;len > 0 && *pp && *cp; pp++, cp++, len--) { - if (UP_CASE(*pp) != UP_CASE(*cp)) return 0; - } - if (len == 0) return cp; - return 0; +// +// Find_Str_Bitset: C +// +// General purpose find a bitset char in a string. +// +// Supports: forward/reverse with skip, cased/uncase, Unicode/byte. +// +// Skip can be set positive or negative (for reverse). +// +// Flags are set according to ALL_FIND_REFS +// +REBCNT Find_Str_Bitset( + REBSER *ser, + REBCNT head, + REBCNT index, + REBCNT tail, + REBINT skip, + REBSER *bset, + REBCNT flags +) { + REBOOL uncase = NOT(flags & AM_FIND_CASE); // case insensitive + + for (; index >= head && index < tail; index += skip) { + REBUNI c1 = GET_ANY_CHAR(ser, index); + + if (Check_Bit(bset, c1, uncase)) + return index; + + if (flags & AM_FIND_MATCH) + break; + } + + return NOT_FOUND; } -#endif -/*********************************************************************** -** -*/ REBCNT Count_Lines(REBYTE *bp, REBCNT len) -/* -** Count lines in a UTF-8 file. -** -***********************************************************************/ +// +// Count_Lines: C +// +// Count lines in a UTF-8 file. +// +REBCNT Count_Lines(REBYTE *bp, REBCNT len) { - REBCNT count = 0; - - for (; len > 0; bp++, len--) { - if (*bp == CR) { - count++; - if (len == 1) break; - if (bp[1] == LF) bp++, len--; - } - else if (*bp == LF) count++; - } - - return count; + REBCNT count = 0; + + for (; len > 0; bp++, len--) { + if (*bp == CR) { + count++; + if (len == 1) break; + if (bp[1] == LF) bp++, len--; + } + else if (*bp == LF) count++; + } + + return count; } -/*********************************************************************** -** -*/ REBCNT Next_Line(REBYTE **bin) -/* -** Find next line termination. Advance the bp; return bin length. -** -***********************************************************************/ +// +// Next_Line: C +// +// Find next line termination. Advance the bp; return bin length. +// +REBCNT Next_Line(REBYTE **bin) { - REBCNT count = 0; - REBYTE *bp = *bin; - - for (; *bp; bp++) { - if (*bp == CR) { - bp++; - if (*bp == LF) bp++; - break; - } - else if (*bp == LF) { - bp++; - break; - } - else count++; - } - - *bin = bp; - return count; + REBCNT count = 0; + REBYTE *bp = *bin; + + for (; *bp; bp++) { + if (*bp == CR) { + bp++; + if (*bp == LF) bp++; + break; + } + else if (*bp == LF) { + bp++; + break; + } + else count++; + } + + *bin = bp; + return count; } diff --git a/src/core/s-make.c b/src/core/s-make.c index 5368bf3269..1962c69fcf 100644 --- a/src/core/s-make.c +++ b/src/core/s-make.c @@ -1,669 +1,657 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: s-make.c -** Summary: binary and unicode string support -** Section: strings -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %s-make.c +// Summary: "binary and unicode string support" +// Section: strings +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -#include "sys-scan.h" -/*********************************************************************** -** -*/ REBSER *Make_Binary(REBCNT length) -/* -** Make a binary string series. For byte, C, and UTF8 strings. -** Add 1 extra for terminator. -** -***********************************************************************/ +// +// Make_Binary: C +// +// Make a binary string series. For byte, C, and UTF8 strings. +// Add 1 extra for terminator. +// +REBSER *Make_Binary(REBCNT length) { - REBSER *series = Make_Series(length + 1, sizeof(REBYTE), FALSE); - LABEL_SERIES(series, "make binary"); - BIN_DATA(series)[length] = 0; - return series; + REBSER *series = Make_Series(length + 1, sizeof(REBYTE)); + + // !!! Clients seem to have different expectations of if `length` is + // total capacity (and the binary should be empty) or actually is + // specifically being preallocated at a fixed length. Until this + // is straightened out, terminate for both possibilities. + + BIN_HEAD(series)[length] = 0; + TERM_SEQUENCE(series); + return series; } -/*********************************************************************** -** -*/ REBSER *Make_Unicode(REBCNT length) -/* -** Make a unicode string series. Used for internal strings. -** Add 1 extra for terminator. -** -***********************************************************************/ +// +// Make_Unicode: C +// +// Make a unicode string series. Used for internal strings. +// Add 1 extra for terminator. +// +REBSER *Make_Unicode(REBCNT length) { - REBSER *series = Make_Series(length + 1, sizeof(REBUNI), FALSE); - LABEL_SERIES(series, "make unicode"); - UNI_HEAD(series)[length] = 0; - return series; + REBSER *series = Make_Series(length + 1, sizeof(REBUNI)); + + // !!! Clients seem to have different expectations of if `length` is + // total capacity (and the binary should be empty) or actually is + // specifically being preallocated at a fixed length. Until this + // is straightened out, terminate for both possibilities. + + UNI_HEAD(series)[length] = 0; + TERM_SEQUENCE(series); + return series; } -/*********************************************************************** -** -*/ REBSER *Copy_Bytes(REBYTE *src, REBINT len) -/* -** Create a string series from the given bytes. -** Source is always latin-1 valid. Result is always 8bit. -** -***********************************************************************/ +// +// Copy_Bytes: C +// +// Create a string series from the given bytes. +// Source is always latin-1 valid. Result is always 8bit. +// +REBSER *Copy_Bytes(const REBYTE *src, REBINT len) { - REBSER *dst; + if (len < 0) + len = LEN_BYTES(src); - if (len < 0) len = LEN_BYTES(src); + REBSER *dst = Make_Binary(len); + memcpy(BIN_HEAD(dst), src, len); + TERM_SEQUENCE_LEN(dst, len); - dst = Make_Binary(len); - memcpy(STR_DATA(dst), src, len); - SERIES_TAIL(dst) = len; - STR_TERM(dst); - - return dst; + return dst; } -/*********************************************************************** -** -*/ REBSER *Copy_Bytes_To_Unicode(REBYTE *src, REBINT len) -/* -** Convert a byte string to a unicode string. This can -** be used for ASCII or LATIN-8 strings. -** -***********************************************************************/ +// +// Copy_Bytes_To_Unicode: C +// +// Convert a byte string to a unicode string. This can +// be used for ASCII or LATIN-8 strings. +// +REBSER *Copy_Bytes_To_Unicode(REBYTE *src, REBINT len) { - REBSER *series; - REBUNI *dst; + REBSER *series = Make_Unicode(len); + REBUNI *dst = UNI_HEAD(series); - series = Make_Unicode(len); - dst = UNI_HEAD(series); - SERIES_TAIL(series) = len; + for (; len > 0; len--) + *dst++ = cast(REBUNI, *src++); - for (; len > 0; len--) { - *dst++ = (REBUNI)(*src++); - } + TERM_UNI_LEN(series, len); + return series; +} - UNI_TERM(series); - return series; +// +// Copy_Wide_Str: C +// +// Create a REBOL string series from a wide char string. +// Minimize to bytes if possible +// +REBSER *Copy_Wide_Str(void *src, REBINT len) +{ + REBSER *dst; + REBUNI *str = (REBUNI*)src; + if (Is_Wide(str, len)) { + REBUNI *up; + dst = Make_Unicode(len); + SET_SERIES_LEN(dst, len); + up = UNI_HEAD(dst); + while (len-- > 0) *up++ = *str++; + *up = 0; + } + else { + REBYTE *bp; + dst = Make_Binary(len); + SET_SERIES_LEN(dst, len); + bp = BIN_HEAD(dst); + while (len-- > 0) *bp++ = (REBYTE)*str++; + *bp = 0; + } + ASSERT_SERIES_TERM(dst); + return dst; } - -/*********************************************************************** -** -*/ REBSER *Copy_OS_Str(void *src, REBINT len) -/* -** Create a REBOL string series from an OS native string. -** -** For example, in Win32 with the wide char interface, we must -** convert wide char strings, minimizing to bytes if possible. -** -** For Linux the char string could be UTF-8, so that must be -** converted to REBOL Unicode or Latin byte strings. -** -***********************************************************************/ +// +// Copy_OS_Str: C +// +// Create a REBOL string series from an OS native string. +// +// For example, in Win32 with the wide char interface, we must +// convert wide char strings, minimizing to bytes if possible. +// +// For Linux the char string could be UTF-8, so that must be +// converted to REBOL Unicode or Latin byte strings. +// +REBSER *Copy_OS_Str(void *src, REBINT len) { #ifdef OS_WIDE_CHAR - REBSER *dst; - REBUNI *str = (REBUNI*)src; - if (Is_Wide(str, len)) { - REBUNI *up; - dst = Make_Unicode(len); - SERIES_TAIL(dst) = len; - up = UNI_HEAD(dst); - while (len-- > 0) *up++ = *str++; - *up = 0; - } - else { - REBYTE *bp; - dst = Make_Binary(len); - SERIES_TAIL(dst) = len; - bp = BIN_HEAD(dst); - while (len-- > 0) *bp++ = (REBYTE)*str++; - *bp = 0; - } - return dst; + return Copy_Wide_Str(src, len); #else - return Decode_UTF_String((REBYTE*)src, len, 8); + return Decode_UTF_String((REBYTE*)src, len, 8); #endif } -/*********************************************************************** -** -*/ void Widen_String(REBSER *series) -/* -** Widen string from 1 byte to 2 bytes. -** -** NOTE: allocates new memory. Cached pointers are invalid. -** -***********************************************************************/ +// +// Insert_Char: C +// +// Insert a Char (byte or unicode) into a string. +// +void Insert_Char(REBSER *dst, REBCNT index, REBCNT chr) { - REBSER *uni = Make_Unicode(STR_LEN(series)); - REBUNI *up; - REBYTE *bp; - REBCNT n; - REBSER tmp; - - // !!! optimize the empty case by just modifying series header?? - - bp = BIN_HEAD(series); - up = UNI_HEAD(uni); - for (n = 0; n < STR_LEN(series); n++) up[n] = bp[n]; - SERIES_TAIL(uni) = SERIES_TAIL(series); - - // Swap series headers: // !!?? is it valid for all? - tmp = *series; - *series = *uni; - *uni = tmp; + if (index > SER_LEN(dst)) index = SER_LEN(dst); + if (chr > 0xFF && BYTE_SIZE(dst)) Widen_String(dst, TRUE); + Expand_Series(dst, index, 1); + SET_ANY_CHAR(dst, index, chr); } -/*********************************************************************** -** -*/ void Insert_Char(REBSER *dst, REBCNT index, REBCNT chr) -/* -** Insert a Char (byte or unicode) into a string. -** -***********************************************************************/ -{ - if (index > dst->tail) index = dst->tail; - if (chr > 0xFF && BYTE_SIZE(dst)) Widen_String(dst); - Expand_Series(dst, index, 1); - SET_ANY_CHAR(dst, index, chr); +// +// Insert_String: C +// +// Insert a non-encoded string into a series at given index. +// Source and/or destination can be 1 or 2 bytes wide. +// If destination is not wide enough, it will be widened. +// +void Insert_String( + REBSER *dst, + REBCNT idx, + REBSER *src, + REBCNT pos, + REBCNT len, + REBOOL no_expand +) { + REBUNI *up; + REBYTE *bp; + REBCNT n; + + assert(idx <= SER_LEN(dst)); + + if (!no_expand) Expand_Series(dst, idx, len); // tail changed too + + // Src and dst have same width (8 or 16): + if (SER_WIDE(dst) == SER_WIDE(src)) { +cp_same: + if (BYTE_SIZE(dst)) + memcpy(BIN_AT(dst, idx), BIN_AT(src, pos), len); + else + memcpy(UNI_AT(dst, idx), UNI_AT(src, pos), sizeof(REBUNI) * len); + return; + } + + // Src is 8 and dst is 16: + if (!BYTE_SIZE(dst)) { + bp = BIN_AT(src, pos); + up = UNI_AT(dst, idx); + for (n = 0; n < len; n++) up[n] = (REBUNI)bp[n]; + return; + } + + // Src is 16 and dst is 8: + bp = BIN_AT(dst, idx); + up = UNI_AT(src, pos); + for (n = 0; n < len; n++) { + if (up[n] > 0xFF) { + // Expand dst and restart: + idx += n; + pos += n; + len -= n; + Widen_String(dst, TRUE); + goto cp_same; + } + bp[n] = (REBYTE)up[n]; + } } -/*********************************************************************** -** -*/ void Insert_String(REBSER *dst, REBCNT idx, REBSER *src, REBCNT pos, REBCNT len, REBFLG no_expand) -/* -** Insert a non-encoded string into a series at given index. -** Source and/or destination can be 1 or 2 bytes wide. -** If destination is not wide enough, it will be widened. -** -***********************************************************************/ +// +// Copy_String_Slimming: C +// +// Copies a portion of any string (byte or unicode). If the input is a +// wide REBUNI string, the range of copied characters will be examined to +// see if they could fit in a byte-size series. The string will be +// "slimmed" if possible. +// +REBSER *Copy_String_Slimming(REBSER *src, REBCNT index, REBINT length) { - REBUNI *up; - REBYTE *bp; - REBCNT n; + REBYTE wide = 1; - if (idx > dst->tail) idx = dst->tail; - if (!no_expand) Expand_Series(dst, idx, len); // tail changed too + if (length < 0) + length = SER_LEN(src) - index; - // Src and dst have same width (8 or 16): - if (SERIES_WIDE(dst) == SERIES_WIDE(src)) { -cp_same: - if (BYTE_SIZE(dst)) - memcpy(BIN_SKIP(dst, idx), BIN_SKIP(src, pos), len); - else - memcpy(UNI_SKIP(dst, idx), UNI_SKIP(src, pos), sizeof(REBUNI) * len); - return; - } - - // Src is 8 and dst is 16: - if (!BYTE_SIZE(dst)) { - bp = BIN_SKIP(src, pos); - up = UNI_SKIP(dst, idx); - for (n = 0; n < len; n++) up[n] = (REBUNI)bp[n]; - return; - } - - // Src is 16 and dst is 8: - bp = BIN_SKIP(dst, idx); - up = UNI_SKIP(src, pos); - for (n = 0; n < len; n++) { - if (up[n] > 0xFF) { - //Debug_Num("##Widen-series because char value is:", up[n]); - // Expand dst and restart: - idx += n; - pos += n; - len -= n; - Widen_String(dst); - goto cp_same; - } - bp[n] = (REBYTE)up[n]; - } -} + // Can it be slimmed down? + if (!BYTE_SIZE(src)) { + REBUNI *up = UNI_AT(src, index); -#ifdef not_used -/*********************************************************************** -** -x*/ REBCNT Insert_Value(REBSER *series, REBCNT index, REBVAL *item, REBCNT type, REBFLG only) -/* -** A general method to insert a value into a block, string, -** or binary. -** -** Returns: index past the insert. -** -***********************************************************************/ -{ - REBCNT len = 1; - - if (type >= REB_BLOCK) { - if (only || !ANY_BLOCK(item)) - Insert_Series(series, index, (void*)item, len); - else { - len = VAL_LEN(item); - Insert_Series(series, index, (void*)VAL_BLK_DATA(item), len); - } - } - else if (type == REB_BINARY) { - if (IS_BINARY(item)) { - len = VAL_LEN(item); - Insert_String(series, index, VAL_SERIES(item), VAL_INDEX(item), len, 0); - } - else if (IS_INTEGER(item)) { - Insert_Char(series, index, (0xff & VAL_INT32(item))); - } - else if (IS_CHAR(item)) { - Insert_Char(series, index, (0xff & VAL_CHAR(item))); - } - } - else { // other strings - if (ANY_STR(item)) { - len = VAL_LEN(item); - Insert_String(series, index, VAL_SERIES(item), VAL_INDEX(item), len, 0); - } - else if (IS_CHAR(item)) { - Insert_Char(series, index, VAL_CHAR(item)); - } - } - - return index + len; -} -#endif + REBINT n; + for (n = 0; n < length; n++) + if (up[n] > 0xff) + break; + if (n < length) + wide = sizeof(REBUNI); + } -/*********************************************************************** -** -*/ REBSER *Copy_String(REBSER *src, REBCNT index, REBINT length) -/* -** Copies a portion of any string (byte or unicode). -** Will slim the string, if needed. -** -** The index + length must be in range unsigned int 32. -** -***********************************************************************/ -{ - REBUNI *up; - REBINT wide = 1; - REBSER *dst; - REBINT n; - - if (length < 0) length = src->tail; - - // Can it be slimmed down? - if (!BYTE_SIZE(src)) { - up = UNI_SKIP(src, index); - for (n = 0; n < length; n++) - if (up[n] > 0xff) break; - if (n < length) wide = sizeof(REBUNI); - } - - dst = Make_Series(length + 1, wide, FALSE); - Insert_String(dst, 0, src, index, length, TRUE); - SERIES_TAIL(dst) = length; - TERM_SERIES(dst); - - return dst; + REBSER *dst = Make_Series(length + 1, wide); + Insert_String(dst, 0, src, index, length, TRUE); + TERM_SEQUENCE_LEN(dst, length); + + return dst; } -/*********************************************************************** -** -*/ REBCHR *Val_Str_To_OS(REBVAL *val) -/* -** This is used to pass a REBOL value string to an OS API. -** -** The REBOL (input) string can be byte or wide sized. -** The OS (output) string is in the native OS format. -** On Windows, its a wide-char, but on Linux, its UTF-8. -** -** If we know that the string can be used directly as-is, -** (because it's in the OS size format), we can used it -** like that. -** -***********************************************************************/ +// +// Val_Str_To_OS_Managed: C +// +// This is used to pass a REBOL value string to an OS API. +// +// The REBOL (input) string can be byte or wide sized. +// The OS (output) string is in the native OS format. +// On Windows, its a wide-char, but on Linux, its UTF-8. +// +// If we know that the string can be used directly as-is, +// (because it's in the OS size format), we can used it +// like that. +// +// !!! The series is created but just let up to the garbage +// collector to free. This is a "leaky" approach. You may +// optionally request to have the series returned if it is +// important for you to protect it from GC, but you cannot +// currently get a "freeable" series out of this. +// +REBCHR *Val_Str_To_OS_Managed(REBSER **out, REBVAL *val) { #ifdef OS_WIDE_CHAR - if (VAL_BYTE_SIZE(val)) { - // On windows, we need to convert byte to wide: - REBINT n = VAL_LEN(val); - REBSER *up = Make_Unicode(n); // will be GC'd ok - n = Decode_UTF8(UNI_HEAD(up), VAL_BIN_DATA(val), n, FALSE); - SERIES_TAIL(up) = abs(n); - UNI_TERM(up); - return UNI_HEAD(up); - } - else { - // Already wide, we can use it as-is: - // !Assumes the OS uses same wide format! - return VAL_UNI_DATA(val); - } + if (VAL_BYTE_SIZE(val)) { + // On windows, we need to convert byte to wide: + REBINT n = VAL_LEN_AT(val); + REBSER *up = Make_Unicode(n); + + // !!!"Leaks" in the sense that the GC has to take care of this + MANAGE_SERIES(up); + + n = Decode_UTF8_Negative_If_Latin1( + UNI_HEAD(up), + VAL_BIN_AT(val), + n, + FALSE + ); + TERM_UNI_LEN(up, abs(n)); + + if (out) *out = up; + + return cast(REBCHR*, UNI_HEAD(up)); + } + else { + // Already wide, we can use it as-is: + // !Assumes the OS uses same wide format! + + if (out) *out = VAL_SERIES(val); + + return cast(REBCHR*, VAL_UNI_AT(val)); + } #else - if (VAL_STR_IS_ASCII(val)) { - // On Linux/Unix we can use ASCII directly (it is valid UTF-8): - return VAL_BIN_DATA(val); - } - else { - REBINT n = VAL_LEN(val); - REBSER *ser = Prep_Bin_Str(val, 0, &n); - // NOTE: may return a shared buffer! - return BIN_HEAD(ser); // (actually, it's a byte pointer) - } + if ( + VAL_BYTE_SIZE(val) + && All_Bytes_ASCII(VAL_BIN_AT(val), VAL_LEN_AT(val)) + ) { + if (out) *out = VAL_SERIES(val); + + // On Linux/Unix we can use ASCII directly (it is valid UTF-8): + return cast(REBCHR*, VAL_BIN_AT(val)); + } + else { + // !!! "Leaks" in the sense that the GC has to take care of this + REBSER *ser = Temp_Bin_Str_Managed(val, 0, NULL); + + if (out) *out = ser; + + // NOTE: may return a shared buffer! + return cast(REBCHR*, BIN_HEAD(ser)); + } #endif } -/*********************************************************************** -** -*/ REBSER *Append_Bytes_Len(REBSER *dst, REBYTE *src, REBCNT len) -/* -** Optimized function to append a non-encoded byte string. -** -** If dst is null, it will be created and returned. -** Such src strings normally come from C code or tables. -** Destination can be 1 or 2 bytes wide. -** -***********************************************************************/ +// +// Append_Unencoded_Len: C +// +// Optimized function to append a non-encoded byte string. +// +// If dst is null, it will be created and returned. +// Such src strings normally come from C code or tables. +// Destination can be 1 or 2 bytes wide. +// +REBSER *Append_Unencoded_Len(REBSER *dst, const char *src, REBCNT len) { - REBUNI *up; - REBCNT tail; - - if (!dst) { - dst = Make_Binary(len); - tail = 0; - } else { - tail = SERIES_TAIL(dst); - EXPAND_SERIES_TAIL(dst, len); - } - - if (BYTE_SIZE(dst)) { - memcpy(STR_SKIP(dst, tail), src, len); - STR_TERM(dst); - } - else { - up = UNI_SKIP(dst, tail); - for (; len > 0; len--) *up++ = (REBUNI)*src++; - *up = 0; - } - - return dst; + REBUNI *up; + REBCNT tail; + + if (!dst) { + dst = Make_Binary(len); + tail = 0; + } else { + tail = SER_LEN(dst); + EXPAND_SERIES_TAIL(dst, len); + } + + if (BYTE_SIZE(dst)) { + memcpy(BIN_AT(dst, tail), src, len); + TERM_SEQUENCE(dst); + } + else { + up = UNI_AT(dst, tail); + for (; len > 0; len--) *up++ = (REBUNI)*src++; + *up = 0; + } + + return dst; } -/*********************************************************************** -** -*/ REBSER *Append_Bytes(REBSER *dst, REBYTE *src) -/* -** Optimized function to append a non-encoded byte string. -** If dst is null, it will be created and returned. -** Such src strings normally come from C code or tables. -** Destination can be 1 or 2 bytes wide. -** -***********************************************************************/ +// +// Append_Unencoded: C +// +// Optimized function to append a non-encoded byte string. +// If dst is null, it will be created and returned. +// Such src strings normally come from C code or tables. +// Destination can be 1 or 2 bytes wide. +// +REBSER *Append_Unencoded(REBSER *dst, const char *src) { - return Append_Bytes_Len(dst, src, LEN_BYTES(src)); + return Append_Unencoded_Len(dst, src, strlen(src)); } -/*********************************************************************** -** -*/ REBSER *Append_Byte(REBSER *dst, REBCNT chr) -/* -** Optimized function to append a non-encoded character. -** If dst is null, it will be created and returned and the -** chr will be used to determine the width. -** -** Destination can be 1 or 2 bytes wide, but DOES NOT WIDEN. -** -***********************************************************************/ +// +// Append_Codepoint_Raw: C +// +// Optimized function to append a non-encoded character. +// Destination can be 1 or 2 bytes wide, but DOES NOT WIDEN. +// +REBSER *Append_Codepoint_Raw(REBSER *dst, REBCNT codepoint) { - REBCNT tail; - - if (!dst) { - dst = (chr > 255) ? Make_Unicode(3) : Make_Binary(3); - tail = 0; - SERIES_TAIL(dst) = 1; - } else { - tail = SERIES_TAIL(dst); - EXPAND_SERIES_TAIL(dst, 1); - } - - if (BYTE_SIZE(dst)) { - *STR_SKIP(dst, tail) = (REBYTE)chr; - STR_TERM(dst); - } - else { - *UNI_SKIP(dst, tail) = (REBUNI)chr; - UNI_TERM(dst); - } - - return dst; + REBCNT tail = SER_LEN(dst); + + EXPAND_SERIES_TAIL(dst, 1); + + if (BYTE_SIZE(dst)) { + assert(codepoint < (1 << 8)); + *BIN_AT(dst, tail) = cast(REBYTE, codepoint); + TERM_BIN(dst); + } + else { + assert(codepoint < (1 << 16)); + *UNI_AT(dst, tail) = cast(REBUNI, codepoint); + TERM_UNI(dst); + } + + return dst; } -/*********************************************************************** -** -*/ void Append_Uni_Bytes(REBSER *dst, REBUNI *src, REBCNT len) -/* -** Append a unicode string to a byte string. OPTIMZED. -** -***********************************************************************/ +// +// Make_Series_Codepoint: C +// +// Create a series that holds a single codepoint. If the +// codepoint will fit into a byte, then it will be a byte +// series. If two bytes, it will be a REBUNI series. +// +// (Codepoints greater than the size of REBUNI are not +// currently supported in Rebol3.) +// +REBSER *Make_Series_Codepoint(REBCNT codepoint) { - REBYTE *bp; - REBCNT tail = SERIES_TAIL(dst); + REBSER *out; - EXPAND_SERIES_TAIL(dst, len); + assert(codepoint < (1 << 16)); - bp = BIN_SKIP(dst, tail); + out = (codepoint > 255) ? Make_Unicode(1) : Make_Binary(1); + TERM_SEQUENCE(out); - for (; len > 0; len--) - *bp++ = (REBYTE)*src++; + Append_Codepoint_Raw(out, codepoint); - *bp = 0; + return out; } -/*********************************************************************** -** -*/ void Append_Uni_Uni(REBSER *dst, REBUNI *src, REBCNT len) -/* -** Append a unicode string to a unicode string. OPTIMZED. -** -***********************************************************************/ +// +// Append_Uni_Bytes: C +// +// Append a unicode string to a byte string. OPTIMZED. +// +void Append_Uni_Bytes(REBSER *dst, const REBUNI *src, REBCNT len) { - REBUNI *up; - REBCNT tail = SERIES_TAIL(dst); + REBCNT old_len = SER_LEN(dst); - EXPAND_SERIES_TAIL(dst, len); + EXPAND_SERIES_TAIL(dst, len); + SET_SERIES_LEN(dst, old_len + len); - up = UNI_SKIP(dst, tail); + REBYTE *bp = BIN_AT(dst, old_len); - for (; len > 0; len--) - *up++ = *src++; + for (; len > 0; len--) + *bp++ = cast(REBYTE, *src++); - *up = 0; + *bp = 0; } -/*********************************************************************** -** -*/ void Append_String(REBSER *dst, REBSER *src, REBCNT i, REBCNT len) -/* -** Append a byte or unicode string to a unicode string. -** -***********************************************************************/ +// +// Append_Uni_Uni: C +// +// Append a unicode string to a unicode string. OPTIMZED. +// +void Append_Uni_Uni(REBSER *dst, const REBUNI *src, REBCNT len) { - Insert_String(dst, SERIES_TAIL(dst), src, i, len, 0); -} + REBCNT old_len = SER_LEN(dst); + EXPAND_SERIES_TAIL(dst, len); + SET_SERIES_LEN(dst, old_len + len); -/*********************************************************************** -** -*/ void Append_Boot_Str(REBSER *dst, REBINT num) -/* -***********************************************************************/ -{ - Append_Bytes(dst, PG_Boot_Strs[num]); + REBUNI *up = UNI_AT(dst, old_len); + + for (; len > 0; len--) + *up++ = *src++; + + *up = 0; } -/*********************************************************************** -** -*/ void Append_Int(REBSER *dst, REBINT num) -/* -** Append an integer string. -** -***********************************************************************/ +// +// Append_String: C +// +// Append a byte or unicode string to a unicode string. +// +void Append_String(REBSER *dst, REBSER *src, REBCNT i, REBCNT len) { - REBYTE buf[32]; - - Form_Int(buf, num); - Append_Bytes(dst, buf); + Insert_String(dst, SER_LEN(dst), src, i, len, FALSE); } -/*********************************************************************** -** -*/ void Append_Int_Pad(REBSER *dst, REBINT num, REBINT digs) -/* -** Append an integer string. -** -***********************************************************************/ +// +// Append_Int: C +// +// Append an integer string. +// +void Append_Int(REBSER *dst, REBINT num) { - REBYTE buf[32]; - if (digs > 0) - Form_Int_Pad(buf, num, digs, -digs, '0'); - else - Form_Int_Pad(buf, num, -digs, digs, '0'); + REBYTE buf[32]; - Append_Bytes(dst, buf); + Form_Int(buf, num); + Append_Unencoded(dst, s_cast(buf)); } - -/*********************************************************************** -** -*/ REBSER *Append_UTF8(REBSER *dst, REBYTE *src, REBINT len) -/* -** Append (or create) decoded UTF8 to a string. OPTIMIZED. -** -** Result can be 8 bits (latin-1 optimized) or 16 bits wide. -** -** dst = null means make a new string. -** -***********************************************************************/ +// +// Append_Int_Pad: C +// +// Append an integer string. +// +void Append_Int_Pad(REBSER *dst, REBINT num, REBINT digs) { - REBSER *ser = BUF_UTF8; // buffer is Unicode width - - if (len < 0) len = LEN_BYTES(src); + REBYTE buf[32]; + if (digs > 0) + Form_Int_Pad(buf, num, digs, -digs, '0'); + else + Form_Int_Pad(buf, num, -digs, digs, '0'); - Resize_Series(ser, len+1); // needs at most this much - - len = Decode_UTF8(UNI_HEAD(ser), src, len, FALSE); + Append_Unencoded(dst, s_cast(buf)); +} - if (len < 0) { - len = -len; - if (!dst) dst = Make_Binary(len); - if (BYTE_SIZE(dst)) { - Append_Uni_Bytes(dst, UNI_HEAD(ser), len); - return dst; - } - } else { - if (!dst) dst = Make_Unicode(len); - } - Append_Uni_Uni(dst, UNI_HEAD(ser), len); - return dst; +// +// Append_UTF8_May_Fail: C +// +// Append (or create) decoded UTF8 to a string. OPTIMIZED. +// +// Result can be 8 bits (latin-1 optimized) or 16 bits wide. +// +// dst = null means make a new string. +// +REBSER *Append_UTF8_May_Fail(REBSER *dst, const REBYTE *src, REBCNT num_bytes) +{ + REBSER *ser = BUF_UTF8; // buffer is Unicode width + + Resize_Series(ser, num_bytes + 1); // needs at most this many unicode chars + + REBINT len = Decode_UTF8_Negative_If_Latin1( + UNI_HEAD(ser), + src, + num_bytes, + FALSE + ); + + if (len < 0) { // All characters being added are Latin1 + len = -len; + if (dst == NULL) + dst = Make_Binary(len); + if (BYTE_SIZE(dst)) { + Append_Uni_Bytes(dst, UNI_HEAD(ser), len); + return dst; + } + } + else { + if (dst == NULL) + dst = Make_Unicode(len); + } + + Append_Uni_Uni(dst, UNI_HEAD(ser), len); + + return dst; } -/*********************************************************************** -** -*/ REBSER *Join_Binary(REBVAL *blk) -/* -** Join a binary from component values for use in standard -** actions like make, insert, or append. -** -** WARNING: returns BUF_FORM, not a copy! -** -***********************************************************************/ +// +// Join_Binary: C +// +// Join a binary from component values for use in standard +// actions like make, insert, or append. +// limit: maximum number of values to process +// limit < 0 means no limit +// +// WARNING: returns BYTE_BUF, not a copy! +// +REBSER *Join_Binary(const REBVAL *blk, REBINT limit) { - REBSER *series = BUF_FORM; - REBVAL *val; - REBCNT tail = 0; - REBCNT len; - void *bp; - - RESET_TAIL(series); - - for (val = VAL_BLK_DATA(blk); NOT_END(val); val++) { - switch (VAL_TYPE(val)) { - - case REB_INTEGER: - if (VAL_INT64(val) > (i64)255 || VAL_INT64(val) < 0) Trap_Range(val); - EXPAND_SERIES_TAIL(series, 1); - *BIN_SKIP(series, tail) = (REBYTE)VAL_INT32(val); - break; - - case REB_BINARY: - len = VAL_LEN(val); - EXPAND_SERIES_TAIL(series, len); - memcpy(BIN_SKIP(series, tail), VAL_BIN_DATA(val), len); - break; - - case REB_STRING: - case REB_FILE: - case REB_EMAIL: - case REB_URL: - case REB_TAG: - len = VAL_LEN(val); - bp = VAL_BYTE_SIZE(val) ? VAL_BIN_DATA(val) : (REBYTE*)VAL_UNI_DATA(val); - len = Length_As_UTF8(bp, len, (REBOOL)!VAL_BYTE_SIZE(val), 0); - EXPAND_SERIES_TAIL(series, len); - Encode_UTF8(BIN_SKIP(series, tail), len, bp, &len, !VAL_BYTE_SIZE(val), 0); - series->tail = tail + len; - break; - - case REB_CHAR: - EXPAND_SERIES_TAIL(series, 6); - len = Encode_UTF8_Char(BIN_SKIP(series, tail), VAL_CHAR(val)); - series->tail = tail + len; - break; - - default: - Trap_Arg(val); - } - - tail = series->tail; - } - - SET_STR_END(series, tail); - - return series; // SHARED FORM SERIES! + REBSER *series = BYTE_BUF; + + REBCNT tail = 0; + + if (limit < 0) + limit = VAL_LEN_AT(blk); + + SET_SERIES_LEN(series, 0); + + RELVAL *val; + for (val = VAL_ARRAY_AT(blk); limit > 0; val++, limit--) { + switch (VAL_TYPE(val)) { + case REB_INTEGER: + if (VAL_INT64(val) > cast(i64, 255) || VAL_INT64(val) < 0) + fail (Error_Out_Of_Range(KNOWN(val))); + EXPAND_SERIES_TAIL(series, 1); + *BIN_AT(series, tail) = (REBYTE)VAL_INT32(val); + break; + + case REB_BINARY: { + REBCNT len = VAL_LEN_AT(val); + EXPAND_SERIES_TAIL(series, len); + memcpy(BIN_AT(series, tail), VAL_BIN_AT(val), len); + break; } + + case REB_STRING: + case REB_FILE: + case REB_EMAIL: + case REB_URL: + case REB_TAG: { + REBCNT len = VAL_LEN_AT(val); + + void *bp = VAL_BYTE_SIZE(val) + ? VAL_BIN_AT(val) + : (REBYTE*)VAL_UNI_AT(val); + + REBCNT bl = Length_As_UTF8( + bp, len, VAL_BYTE_SIZE(val) ? 0 : OPT_ENC_UNISRC + ); + + EXPAND_SERIES_TAIL(series, bl); + SET_SERIES_LEN( + series, + tail + Encode_UTF8( + BIN_AT(series, tail), + bl, + bp, + &len, + VAL_BYTE_SIZE(val) ? 0 : OPT_ENC_UNISRC + ) + ); + break; } + + case REB_CHAR: { + EXPAND_SERIES_TAIL(series, 6); + REBCNT len = + Encode_UTF8_Char(BIN_AT(series, tail), VAL_CHAR(val)); + SET_SERIES_LEN(series, tail + len); + break; } + + default: + fail (Error_Invalid_Arg_Core(val, VAL_SPECIFIER(blk))); + } + + tail = SER_LEN(series); + } + + *BIN_AT(series, tail) = 0; + + return series; // SHARED FORM SERIES! } diff --git a/src/core/s-mold.c b/src/core/s-mold.c old mode 100644 new mode 100755 index 4be4c60155..17a8685021 --- a/src/core/s-mold.c +++ b/src/core/s-mold.c @@ -1,40 +1,38 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: s-mold.c -** Summary: value to string conversion -** Section: strings -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ - -//#define INCLUDE_TYPE_NAMES // include the value names table +// +// File: %s-mold.c +// Summary: "value to string conversion" +// Section: strings +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + +//#define INCLUDE_TYPE_NAMES // include the value names table #include "sys-core.h" -#include "sys-scan.h" #include -#define STOID static void - -#define MAX_QUOTED_STR 50 // max length of "string" before going to { } +#define MAX_QUOTED_STR 50 // max length of "string" before going to { } //typedef REBSER *(*MOLD_FUNC)(REBVAL *, REBSER *, REBCNT); typedef void (*MOLD_FUNC)(REBVAL *, REB_MOLD *); @@ -43,11 +41,11 @@ typedef void (*MOLD_FUNC)(REBVAL *, REB_MOLD *); const REBYTE Punctuation[] = ".,-/"; enum REB_Punct { - PUNCT_DOT = 0, // Must be 0 - PUNCT_COMMA, // Must be 1 - PUNCT_DASH, - PUNCT_SLASH, - PUNCT_MAX + PUNCT_DOT = 0, // Must be 0 + PUNCT_COMMA, // Must be 1 + PUNCT_DASH, + PUNCT_SLASH, + PUNCT_MAX }; REBYTE *Char_Escapes; @@ -60,1381 +58,1876 @@ REBYTE *URL_Escapes; #define IS_FILE_ESC(c) ((c) <= MAX_URL_CHAR && (URL_Escapes[c] & ESC_FILE)) enum { - ESC_URL = 1, - ESC_FILE = 2, - ESC_EMAIL = 4, + ESC_URL = 1, + ESC_FILE = 2, + ESC_EMAIL = 4 }; /*********************************************************************** ************************************************************************ ** -** SECTION: Global Mold Utilities +** SECTION: Global Mold Utilities ** ************************************************************************ ***********************************************************************/ -/*********************************************************************** -** -*/ REBSER *Emit(REB_MOLD *mold, REBYTE *fmt, ...) -/* -***********************************************************************/ +// +// Emit: C +// +REBSER *Emit(REB_MOLD *mold, const char *fmt, ...) { - va_list args; - REBYTE ender = 0; - REBSER *series = mold->series; - - ASSERT2(SERIES_WIDE(series) ==2, 9997); - - va_start(args, fmt); - - for (; *fmt; fmt++) { - switch (*fmt) { - case 'W': // Word symbol - Append_UTF8(series, Get_Word_Name(va_arg(args, REBVAL*)), -1); - break; - case 'V': // Value - Mold_Value(mold, va_arg(args, REBVAL*), TRUE); - break; - case 'S': // String of bytes - Append_Bytes(series, va_arg(args, REBYTE*)); - break; - case 'C': // Char - Append_Byte(series, va_arg(args, REBCNT)); - break; - case 'E': // Series (byte or uni) - { - REBSER *src = va_arg(args, REBSER*); - Insert_String(series, SERIES_TAIL(series), src, 0, SERIES_TAIL(src), 0); - } - break; - case 'I': // Integer - Append_Int(series, va_arg(args, REBINT)); - break; - case 'i': - Append_Int_Pad(series, va_arg(args, REBINT), -9); - Trim_Tail(mold->series, '0'); - break; - case '2': // 2 digit int (for time) - Append_Int_Pad(series, va_arg(args, REBINT), 2); - break; - case 'T': // Type name - Append_UTF8(series, Get_Type_Name(va_arg(args, REBVAL*)), -1); - break; - case 'N': // Symbol name - Append_UTF8(series, Get_Sym_Name(va_arg(args, REBCNT)), -1); - break; - case '+': // Add #[ if mold/all - if (GET_MOPT(mold, MOPT_MOLD_ALL)) { - Append_Bytes(series, "#["); - ender = ']'; - } - break; - case 'D': // Datatype symbol: #[type - if (ender) { - Append_UTF8(series, Get_Sym_Name(va_arg(args, REBCNT)), -1); - Append_Byte(series, ' '); - } else va_arg(args, REBCNT); // ignore it - break; - case 'B': // Boot string - Append_Boot_Str(series, va_arg(args, REBINT)); - break; - default: - Append_Byte(series, *fmt); - } - } - va_end(args); - - if (ender) Append_Byte(series, ender); - - return series; + va_list va; + REBYTE ender = 0; + REBSER *series = mold->series; + + assert(SER_WIDE(series) == 2); + + va_start(va, fmt); + + for (; *fmt; fmt++) { + switch (*fmt) { + case 'W': { // Word symbol + const REBVAL *any_word = va_arg(va, const REBVAL*); + REBSTR *spelling = VAL_WORD_SPELLING(any_word); + Append_UTF8_May_Fail( + series, STR_HEAD(spelling), STR_NUM_BYTES(spelling) + ); + break; + } + + case 'V': // Value + Mold_Value(mold, va_arg(va, const REBVAL*), TRUE); + break; + case 'S': // String of bytes + Append_Unencoded(series, va_arg(va, const char *)); + break; + case 'C': // Char + Append_Codepoint_Raw(series, va_arg(va, REBCNT)); + break; + case 'E': { // Series (byte or uni) + REBSER *src = va_arg(va, REBSER*); + Insert_String( + series, SER_LEN(series), src, 0, SER_LEN(src), FALSE + ); + break; + } + case 'I': // Integer + Append_Int(series, va_arg(va, REBINT)); + break; + case 'i': + Append_Int_Pad(series, va_arg(va, REBINT), -9); + Trim_Tail(mold->series, '0'); + break; + case '2': // 2 digit int (for time) + Append_Int_Pad(series, va_arg(va, REBINT), 2); + break; + case 'T': { // Type name + const REBYTE *bytes = Get_Type_Name(va_arg(va, REBVAL*)); + Append_UTF8_May_Fail(series, bytes, LEN_BYTES(bytes)); + break; } + case 'N': { // Symbol name + REBSTR *spelling = va_arg(va, REBSTR*); + Append_UTF8_May_Fail( + series, STR_HEAD(spelling), STR_NUM_BYTES(spelling) + ); + break; } + case '+': // Add #[ if mold/all + if (GET_MOPT(mold, MOPT_MOLD_ALL)) { + Append_Unencoded(series, "#["); + ender = ']'; + } + break; + case 'D': // Datatype symbol: #[type + if (ender) { + REBSTR *canon = Canon(cast(REBSYM, va_arg(va, int))); + Append_UTF8_May_Fail( + series, STR_HEAD(canon), STR_NUM_BYTES(canon) + ); + Append_Codepoint_Raw(series, ' '); + } + else + va_arg(va, REBCNT); // ignore it + break; + default: + Append_Codepoint_Raw(series, *fmt); + } + } + va_end(va); + + if (ender) Append_Codepoint_Raw(series, ender); + + return series; } -/*********************************************************************** -** -*/ REBSER *Prep_String(REBSER *series, REBYTE **str, REBCNT len) -/* -** Helper function for the string related Mold functions below. -** Creates or expands the series and provides the location to -** copy text into. -** -***********************************************************************/ +// +// Prep_String: C +// +// Helper function for the string related Mold functions below. +// Creates or expands the series and provides the location to +// copy text into. +// +REBSER *Prep_String(REBSER *series, REBYTE **str, REBCNT len) { - REBCNT tail; - - if (!series) { - series = Make_Binary(len); - series->tail = len; - *str = STR_HEAD(series); - } - else { - tail = SERIES_TAIL(series); - EXPAND_SERIES_TAIL(series, len); - *str = STR_SKIP(series, tail); - } - return series; + REBCNT tail; + + if (!series) { + series = Make_Binary(len); + SET_SERIES_LEN(series, len); + *str = BIN_HEAD(series); + } + else { + // This used "STR_AT" (obsolete) but didn't have an explicit case + // here that it was byte sized. Check it, because if you have + // unicode characters this would give the wrong pointer. + // + assert(BYTE_SIZE(series)); + + tail = SER_LEN(series); + EXPAND_SERIES_TAIL(series, len); + *str = BIN_AT(series, tail); + } + return series; } -/*********************************************************************** -** -*/ REBUNI *Prep_Uni_Series(REB_MOLD *mold, REBCNT len) -/* -***********************************************************************/ +// +// Prep_Uni_Series: C +// +REBUNI *Prep_Uni_Series(REB_MOLD *mold, REBCNT len) { - REBCNT tail = SERIES_TAIL(mold->series); + REBCNT tail = SER_LEN(mold->series); - EXPAND_SERIES_TAIL(mold->series, len); + EXPAND_SERIES_TAIL(mold->series, len); - return UNI_SKIP(mold->series, tail); + return UNI_AT(mold->series, tail); } /*********************************************************************** ************************************************************************ ** -** SECTION: Local MOLD Utilities +** SECTION: Local MOLD Utilities ** ************************************************************************ ***********************************************************************/ -/*********************************************************************** -** -*/ void Pre_Mold(REBVAL *value, REB_MOLD *mold) -/* -** Emit the initial datatype function, depending on /ALL option -** -***********************************************************************/ +// +// Pre_Mold: C +// +// Emit the initial datatype function, depending on /ALL option +// +void Pre_Mold(const RELVAL *value, REB_MOLD *mold) { - Emit(mold, GET_MOPT(mold, MOPT_MOLD_ALL) ? "#[T " : "make T ", value); + Emit(mold, GET_MOPT(mold, MOPT_MOLD_ALL) ? "#[T " : "make T ", value); } -/*********************************************************************** -** -*/ void End_Mold(REB_MOLD *mold) -/* -** Finish the mold, depending on /ALL with close block. -** -***********************************************************************/ +// +// End_Mold: C +// +// Finish the mold, depending on /ALL with close block. +// +void End_Mold(REB_MOLD *mold) { - if (GET_MOPT(mold, MOPT_MOLD_ALL)) Append_Byte(mold->series, ']'); + if (GET_MOPT(mold, MOPT_MOLD_ALL)) Append_Codepoint_Raw(mold->series, ']'); } -/*********************************************************************** -** -*/ void Post_Mold(REBVAL *value, REB_MOLD *mold) -/* -** For series that has an index, add the index for mold/all. -** Add closing block. -** -***********************************************************************/ +// +// Post_Mold: C +// +// For series that has an index, add the index for mold/all. +// Add closing block. +// +void Post_Mold(const RELVAL *value, REB_MOLD *mold) { - if (VAL_INDEX(value)) { - Append_Byte(mold->series, ' '); - Append_Int(mold->series, VAL_INDEX(value)+1); - } - if (GET_MOPT(mold, MOPT_MOLD_ALL)) Append_Byte(mold->series, ']'); + if (VAL_INDEX(value)) { + Append_Codepoint_Raw(mold->series, ' '); + Append_Int(mold->series, VAL_INDEX(value)+1); + } + if (GET_MOPT(mold, MOPT_MOLD_ALL)) Append_Codepoint_Raw(mold->series, ']'); } -/*********************************************************************** -** -*/ void New_Indented_Line(REB_MOLD *mold) -/* -** Create a newline with auto-indent on next line if needed. -** -***********************************************************************/ +// +// New_Indented_Line: C +// +// Create a newline with auto-indent on next line if needed. +// +void New_Indented_Line(REB_MOLD *mold) { - REBINT n; - REBUNI *cp = 0; - - // Check output string has content already but no terminator: - if (mold->series->tail) { - cp = UNI_LAST(mold->series); - if (*cp == ' ' || *cp == '\t') *cp = '\n'; - else cp = 0; - } - - // Add terminator: - if (!cp) Append_Byte(mold->series, '\n'); - - // Add proper indentation: - if (!GET_MOPT(mold, MOPT_INDENT)) { - for (n = 0; n < mold->indent; n++) - Append_Bytes(mold->series, " "); - } + REBINT n; + REBUNI *cp = 0; + + // Check output string has content already but no terminator: + if (SER_LEN(mold->series)) { + cp = UNI_LAST(mold->series); + if (*cp == ' ' || *cp == '\t') *cp = '\n'; + else cp = 0; + } + + // Add terminator: + if (!cp) Append_Codepoint_Raw(mold->series, '\n'); + + // Add proper indentation: + if (!GET_MOPT(mold, MOPT_INDENT)) { + for (n = 0; n < mold->indent; n++) + Append_Unencoded(mold->series, " "); + } } /*********************************************************************** ************************************************************************ ** -** SECTION: Char/String Datatypes +** SECTION: Char/String Datatypes ** ************************************************************************ ***********************************************************************/ typedef struct REB_Str_Flags { - REBCNT escape; // escaped chars - REBCNT brace_in; // { - REBCNT brace_out; // } - REBCNT newline; // lf - REBCNT quote; // " - REBCNT paren; // (1234) - REBCNT chr1e; - REBCNT malign; + REBCNT escape; // escaped chars + REBCNT brace_in; // { + REBCNT brace_out; // } + REBCNT newline; // lf + REBCNT quote; // " + REBCNT paren; // (1234) + REBCNT chr1e; + REBCNT malign; } REB_STRF; -STOID Sniff_String(REBSER *ser, REBCNT idx, REB_STRF *sf) +static void Sniff_String(REBSER *ser, REBCNT idx, REB_STRF *sf) { - // Scan to find out what special chars the string contains? - REBYTE *bp = STR_HEAD(ser); - REBUNI *up = (REBUNI*)bp; - REBUNI c; - REBCNT n; - - for (n = idx; n < SERIES_TAIL(ser); n++) { - c = (BYTE_SIZE(ser)) ? (REBUNI)(bp[n]) : up[n]; - switch (c) { - case '{': - sf->brace_in++; - break; - case '}': - sf->brace_out++; - if (sf->brace_out > sf->brace_in) sf->malign++; - break; - case '"': - sf->quote++; - break; - case '\n': - sf->newline++; - break; - default: - if (c == 0x1e) sf->chr1e += 4; // special case of ^(1e) - else if (IS_CHR_ESC(c)) sf->escape++; - else if (c >= 0x1000) sf->paren += 6; // ^(1234) - else if (c >= 0x100) sf->paren += 5; // ^(123) - else if (c >= 0x80) sf->paren += 4; // ^(12) - } - } - if (sf->brace_in != sf->brace_out) sf->malign++; + // Scan to find out what special chars the string contains? + REBYTE *bp = SER_DATA_RAW(ser); + REBUNI *up = cast(REBUNI*, bp); + REBUNI c; + REBCNT n; + + for (n = idx; n < SER_LEN(ser); n++) { + c = BYTE_SIZE(ser) ? cast(REBUNI, bp[n]) : up[n]; + switch (c) { + case '{': + sf->brace_in++; + break; + case '}': + sf->brace_out++; + if (sf->brace_out > sf->brace_in) sf->malign++; + break; + case '"': + sf->quote++; + break; + case '\n': + sf->newline++; + break; + default: + if (c == 0x1e) sf->chr1e += 4; // special case of ^(1e) + else if (IS_CHR_ESC(c)) sf->escape++; + else if (c >= 0x1000) sf->paren += 6; // ^(1234) + else if (c >= 0x100) sf->paren += 5; // ^(123) + else if (c >= 0x80) sf->paren += 4; // ^(12) + } + } + if (sf->brace_in != sf->brace_out) sf->malign++; } static REBUNI *Emit_Uni_Char(REBUNI *up, REBUNI chr, REBOOL parened) { - if (chr >= 0x7f || chr == 0x1e) { // non ASCII or ^ must be (00) escaped - if (parened || chr == 0x1e) { // do not AND with above - *up++ = '^'; - *up++ = '('; - up = Form_Uni_Hex(up, chr); - *up++ = ')'; - return up; - } - } - else if (IS_CHR_ESC(chr)) { - *up++ = '^'; - *up++ = Char_Escapes[chr]; - return up; - } - - *up++ = chr; - return up; + if (chr >= 0x7f || chr == 0x1e) { // non ASCII or ^ must be (00) escaped + if (parened || chr == 0x1e) { // do not AND with above + *up++ = '^'; + *up++ = '('; + up = Form_Uni_Hex(up, chr); + *up++ = ')'; + return up; + } + } + else if (IS_CHR_ESC(chr)) { + *up++ = '^'; + *up++ = Char_Escapes[chr]; + return up; + } + + *up++ = chr; + return up; } -STOID Mold_Uni_Char(REBSER *dst, REBUNI chr, REBOOL molded, REBOOL parened) +static void Mold_Uni_Char(REBSER *dst, REBUNI chr, REBOOL molded, REBOOL parened) { - REBCNT tail = SERIES_TAIL(dst); - REBUNI *up; - - if (!molded) { - EXPAND_SERIES_TAIL(dst, 1); - *UNI_SKIP(dst, tail) = chr; - } - else { - EXPAND_SERIES_TAIL(dst, 10); // worst case: #"^(1234)" - up = UNI_SKIP(dst, tail); - *up++ = '#'; - *up++ = '"'; - up = Emit_Uni_Char(up, chr, parened); - *up++ = '"'; - dst->tail = up - UNI_HEAD(dst); - } - UNI_TERM(dst); + REBCNT tail = SER_LEN(dst); + + if (!molded) { + EXPAND_SERIES_TAIL(dst, 1); + *UNI_AT(dst, tail) = chr; + } + else { + EXPAND_SERIES_TAIL(dst, 10); // worst case: #"^(1234)" + + REBUNI *up = UNI_AT(dst, tail); + *up++ = '#'; + *up++ = '"'; + up = Emit_Uni_Char(up, chr, parened); + *up++ = '"'; + + SET_SERIES_LEN(dst, up - UNI_HEAD(dst)); + } + TERM_UNI(dst); } -STOID Mold_String_Series(REBVAL *value, REB_MOLD *mold) +static void Mold_String_Series(const REBVAL *value, REB_MOLD *mold) { - REBCNT len = VAL_LEN(value); - REBSER *ser = VAL_SERIES(value); - REBCNT idx = VAL_INDEX(value); - REB_STRF sf = {0}; - REBYTE *bp; - REBUNI *up; - REBUNI *dp; - REBOOL uni = !BYTE_SIZE(ser); - REBCNT n; - REBUNI c; - - // Empty string: - if (idx >= VAL_TAIL(value)) { - Append_Bytes(mold->series, "\"\""); //Trap0(RE_PAST_END); - return; - } - - Sniff_String(ser, idx, &sf); - if (!GET_MOPT(mold, MOPT_ANSI_ONLY)) sf.paren = 0; - - // Source can be 8 or 16 bits: - if (uni) up = UNI_HEAD(ser); - else bp = STR_HEAD(ser); - - // If it is a short quoted string, emit it as "string": - if (len <= MAX_QUOTED_STR && sf.quote == 0 && sf.newline < 3) { - - dp = Prep_Uni_Series(mold, len + sf.newline + sf.escape + sf.paren + sf.chr1e + 2); - - *dp++ = '"'; - - for (n = idx; n < VAL_TAIL(value); n++) { - c = uni ? up[n] : (REBUNI)(bp[n]); - dp = Emit_Uni_Char(dp, c, (REBOOL)GET_MOPT(mold, MOPT_ANSI_ONLY)); // parened - } - - *dp++ = '"'; - *dp = 0; - return; - } - - // It is a braced string, emit it as {string}: - if (!sf.malign) sf.brace_in = sf.brace_out = 0; - - dp = Prep_Uni_Series(mold, len + sf.brace_in + sf.brace_out + sf.escape + sf.paren + sf.chr1e + 2); - - *dp++ = '{'; - - for (n = idx; n < VAL_TAIL(value); n++) { - - c = uni ? up[n] : (REBUNI)(bp[n]); - switch (c) { - case '{': - case '}': - if (sf.malign) { - *dp++ = '^'; - *dp++ = c; - break; - } - case '\n': - case '"': - *dp++ = c; - break; - default: - dp = Emit_Uni_Char(dp, c, (REBOOL)GET_MOPT(mold, MOPT_ANSI_ONLY)); // parened - } - } - - *dp++ = '}'; - *dp = 0; + REBCNT len = VAL_LEN_AT(value); + REBSER *ser = VAL_SERIES(value); + REBCNT idx = VAL_INDEX(value); + REBYTE *bp; + REBUNI *up; + REBUNI *dp; + REBOOL unicode = NOT(BYTE_SIZE(ser)); + REBCNT n; + REBUNI c; + + REB_STRF sf; + CLEARS(&sf); + + // Empty string: + if (idx >= VAL_LEN_HEAD(value)) { + // !!! Comment said `fail (Error_Past_End_Raw());` + Append_Unencoded(mold->series, "\"\""); + return; + } + + Sniff_String(ser, idx, &sf); + if (!GET_MOPT(mold, MOPT_NON_ANSI_PARENED)) sf.paren = 0; + + // Source can be 8 or 16 bits: + if (unicode) { + up = UNI_HEAD(ser); + bp = NULL; // wasteful, but avoids may be used uninitialized warning + } + else { + up = NULL; // wasteful, but avoids may be used uninitialized warning + bp = BIN_HEAD(ser); + } + + // If it is a short quoted string, emit it as "string": + if (len <= MAX_QUOTED_STR && sf.quote == 0 && sf.newline < 3) { + + dp = Prep_Uni_Series(mold, len + sf.newline + sf.escape + sf.paren + sf.chr1e + 2); + + *dp++ = '"'; + + for (n = idx; n < VAL_LEN_HEAD(value); n++) { + c = unicode ? up[n] : cast(REBUNI, bp[n]); + dp = Emit_Uni_Char(dp, c, GET_MOPT(mold, MOPT_NON_ANSI_PARENED)); + } + + *dp++ = '"'; + *dp = 0; + return; + } + + // It is a braced string, emit it as {string}: + if (!sf.malign) sf.brace_in = sf.brace_out = 0; + + dp = Prep_Uni_Series(mold, len + sf.brace_in + sf.brace_out + sf.escape + sf.paren + sf.chr1e + 2); + + *dp++ = '{'; + + for (n = idx; n < VAL_LEN_HEAD(value); n++) { + + c = unicode ? up[n] : cast(REBUNI, bp[n]); + + switch (c) { + case '{': + case '}': + if (sf.malign) { + *dp++ = '^'; + *dp++ = c; + break; + } + // falls through + case '\n': + case '"': + *dp++ = c; + break; + default: + dp = Emit_Uni_Char(dp, c, GET_MOPT(mold, MOPT_NON_ANSI_PARENED)); + } + } + + *dp++ = '}'; + *dp = 0; } -#ifdef not_used -STOID Mold_Issue(REBVAL *value, REB_MOLD *mold) -{ - REBUNI *dp; - REBCNT n; - REBUNI c; - REBSER *ser = VAL_SERIES(value); - - dp = Prep_Uni_Series(mold, VAL_LEN(value)+1); // '#' extra - - *dp++ = '#'; - - for (n = VAL_INDEX(value); n < VAL_TAIL(value); n++) { - c = GET_ANY_CHAR(ser, n); - if (IS_LEX_DELIMIT(c)) c = '?'; - *dp++ = c; - } - - *dp = 0; -} -#endif /* - http://www.blooberry.com/indexdot/html/topics/urlencoding.htm + http://www.blooberry.com/indexdot/html/topics/urlencoding.htm - Only alphanumerics [0-9a-zA-Z], the special characters $-_.+!*'(), - and reserved characters used for their reserved purposes may be used - unencoded within a URL. + Only alphanumerics [0-9a-zA-Z], the special characters $-_.+!*'(), + and reserved characters used for their reserved purposes may be used + unencoded within a URL. */ -STOID Mold_Url(REBVAL *value, REB_MOLD *mold) +static void Mold_Url(const REBVAL *value, REB_MOLD *mold) { - REBUNI *dp; - REBCNT n; - REBUNI c; - REBCNT len = VAL_LEN(value); - REBSER *ser = VAL_SERIES(value); - - // Compute extra space needed for hex encoded characters: - for (n = VAL_INDEX(value); n < VAL_TAIL(value); n++) { - c = GET_ANY_CHAR(ser, n); - if (IS_URL_ESC(c)) len += 2; - } - - dp = Prep_Uni_Series(mold, len); - - for (n = VAL_INDEX(value); n < VAL_TAIL(value); n++) { - c = GET_ANY_CHAR(ser, n); - if (IS_URL_ESC(c)) dp = Form_Hex_Esc_Uni(dp, c); // c => %xx - else *dp++ = c; - } - - *dp = 0; + REBUNI *dp; + REBCNT n; + REBUNI c; + REBCNT len = VAL_LEN_AT(value); + REBSER *ser = VAL_SERIES(value); + + // Compute extra space needed for hex encoded characters: + for (n = VAL_INDEX(value); n < VAL_LEN_HEAD(value); n++) { + c = GET_ANY_CHAR(ser, n); + if (IS_URL_ESC(c)) len += 2; + } + + dp = Prep_Uni_Series(mold, len); + + for (n = VAL_INDEX(value); n < VAL_LEN_HEAD(value); n++) { + c = GET_ANY_CHAR(ser, n); + if (IS_URL_ESC(c)) dp = Form_Hex_Esc_Uni(dp, c); // c => %xx + else *dp++ = c; + } + + *dp = 0; } -STOID Mold_File(REBVAL *value, REB_MOLD *mold) +static void Mold_File(const REBVAL *value, REB_MOLD *mold) { - REBUNI *dp; - REBCNT n; - REBUNI c; - REBCNT len = VAL_LEN(value); - REBSER *ser = VAL_SERIES(value); + REBUNI *dp; + REBCNT n; + REBUNI c; + REBCNT len = VAL_LEN_AT(value); + REBSER *ser = VAL_SERIES(value); - // Compute extra space needed for hex encoded characters: - for (n = VAL_INDEX(value); n < VAL_TAIL(value); n++) { - c = GET_ANY_CHAR(ser, n); - if (IS_FILE_ESC(c)) len += 2; - } + // Compute extra space needed for hex encoded characters: + for (n = VAL_INDEX(value); n < VAL_LEN_HEAD(value); n++) { + c = GET_ANY_CHAR(ser, n); + if (IS_FILE_ESC(c)) len += 2; + } - len++; // room for % at start + len++; // room for % at start - dp = Prep_Uni_Series(mold, len); + dp = Prep_Uni_Series(mold, len); - *dp++ = '%'; + *dp++ = '%'; - for (n = VAL_INDEX(value); n < VAL_TAIL(value); n++) { - c = GET_ANY_CHAR(ser, n); - if (IS_FILE_ESC(c)) dp = Form_Hex_Esc_Uni(dp, c); // c => %xx - else *dp++ = c; - } + for (n = VAL_INDEX(value); n < VAL_LEN_HEAD(value); n++) { + c = GET_ANY_CHAR(ser, n); + if (IS_FILE_ESC(c)) dp = Form_Hex_Esc_Uni(dp, c); // c => %xx + else *dp++ = c; + } - *dp = 0; + *dp = 0; } -STOID Mold_Tag(REBVAL *value, REB_MOLD *mold) +static void Mold_Tag(const REBVAL *value, REB_MOLD *mold) { - Append_Byte(mold->series, '<'); - Insert_String(mold->series, AT_TAIL, VAL_SERIES(value), VAL_INDEX(value), VAL_LEN(value), 0); - Append_Byte(mold->series, '>'); + Append_Codepoint_Raw(mold->series, '<'); + Insert_String( + mold->series, + SER_LEN(mold->series), // "insert" at tail (append) + VAL_SERIES(value), + VAL_INDEX(value), + VAL_LEN_AT(value), + FALSE + ); + Append_Codepoint_Raw(mold->series, '>'); } -/*********************************************************************** -** -*/ void Mold_Binary(REBVAL *value, REB_MOLD *mold) -/* -***********************************************************************/ +// +// Mold_Binary: C +// +void Mold_Binary(const REBVAL *value, REB_MOLD *mold) { - REBCNT len = VAL_LEN(value); - REBSER *out; - - switch (Get_System_Int(SYS_OPTIONS, OPTIONS_BINARY_BASE, 16)) { - default: - case 16: - out = Encode_Base16(value, 0, len > 32); - break; - case 64: - Append_Bytes(mold->series, "64"); - out = Encode_Base64(value, 0, len > 64); - break; - case 2: - Append_Byte(mold->series, '2'); - out = Encode_Base2(value, 0, len > 8); - break; - } - - Emit(mold, "#{E}", out); + REBCNT len = VAL_LEN_AT(value); + REBSER *out; + + switch (Get_System_Int(SYS_OPTIONS, OPTIONS_BINARY_BASE, 16)) { + default: + case 16: + out = Encode_Base16(value, 0, LOGICAL(len > 32)); + break; + case 64: + Append_Unencoded(mold->series, "64"); + out = Encode_Base64(value, 0, LOGICAL(len > 64)); + break; + case 2: + Append_Codepoint_Raw(mold->series, '2'); + out = Encode_Base2(value, 0, LOGICAL(len > 8)); + break; + } + + Emit(mold, "#{E}", out); + Free_Series(out); } -STOID Mold_All_String(REBVAL *value, REB_MOLD *mold) +static void Mold_All_String(const REBVAL *value, REB_MOLD *mold) { - // The string that is molded for /all option: - REBVAL val; - - //// ???? move to above Mold_String_Series function???? - - Pre_Mold(value, mold); // #[file! part - val = *value; - VAL_INDEX(&val) = 0; - if (IS_BINARY(value)) Mold_Binary(&val, mold); - else { - VAL_SET(&val, REB_STRING); - Mold_String_Series(&val, mold); - } - Post_Mold(value, mold); + //// ???? move to above Mold_String_Series function???? + + Pre_Mold(value, mold); // e.g. #[file! part + + DECLARE_LOCAL (head); + Move_Value(head, value); + VAL_INDEX(head) = 0; + + if (IS_BINARY(value)) + Mold_Binary(head, mold); + else { + VAL_RESET_HEADER(head, REB_STRING); + Mold_String_Series(head, mold); + } + + Post_Mold(value, mold); } /*********************************************************************** ************************************************************************ ** -** SECTION: Block Series Datatypes +** SECTION: Block Series Datatypes ** ************************************************************************ ***********************************************************************/ -STOID Mold_Block_Series(REB_MOLD *mold, REBSER *series, REBCNT index, REBYTE *sep) -{ - REBSER *out = mold->series; - REBOOL line_flag = FALSE; // newline was part of block - REBOOL had_lines = FALSE; - REBVAL *value = BLK_SKIP(series, index); - - if (!sep) sep = "[]"; - - if (IS_END(value)) { - Append_Bytes(out, sep); - return; - } - - // Recursion check: (variation of: Find_Same_Block(MOLD_LOOP, value)) - for (value = BLK_HEAD(MOLD_LOOP); NOT_END(value); value++) { - if (VAL_SERIES(value) == series) { - Emit(mold, "C...C", sep[0], sep[1]); - return; - } - } - value = Append_Value(MOLD_LOOP); - Set_Block(value, series); - - if (sep[1]) { - Append_Byte(out, sep[0]); - mold->indent++; - } -// else out->tail--; // why????? - - value = BLK_SKIP(series, index); - while (NOT_END(value)) { - if (VAL_GET_LINE(value)) { - if (sep[1] || line_flag) New_Indented_Line(mold); - had_lines = TRUE; - } - line_flag = TRUE; - Mold_Value(mold, value, TRUE); - value++; - if (NOT_END(value)) - Append_Byte(out, (sep[0] == '/') ? '/' : ' '); - } - - if (sep[1]) { - mold->indent--; - if (VAL_GET_LINE(value) || had_lines) New_Indented_Line(mold); - Append_Byte(out, sep[1]); - } - - Remove_Last(MOLD_LOOP); +// +// Mold_Array_At: C +// +void Mold_Array_At( + REB_MOLD *mold, + REBARR *array, + REBCNT index, + const char *sep +) { + REBSER *out = mold->series; + REBOOL line_flag = FALSE; // newline was part of block + REBOOL had_lines = FALSE; + RELVAL *value = ARR_AT(array, index); + + if (!sep) sep = "[]"; + + if (IS_END(value)) { + Append_Unencoded(out, sep); + return; + } + + // Recursion check: + if (Find_Same_Array(MOLD_STACK, value) != NOT_FOUND) { + Emit(mold, "C...C", sep[0], sep[1]); + return; + } + + // We don't want to use Init_Block because it will create an implicit + // managed value, and the incoming series may be from an unmanaged source + // !!! Review how to avoid needing to put the series into a value + { + REBVAL *temp = Alloc_Tail_Array(MOLD_STACK); + VAL_RESET_HEADER(temp, REB_BLOCK); + INIT_VAL_ARRAY(temp, array); // copies args + VAL_INDEX(temp) = 0; + } + + if (sep[1]) { + Append_Codepoint_Raw(out, sep[0]); + mold->indent++; + } +// else out->tail--; // why????? + + value = ARR_AT(array, index); + while (NOT_END(value)) { + if (GET_VAL_FLAG(value, VALUE_FLAG_LINE)) { + if (sep[1] || line_flag) New_Indented_Line(mold); + had_lines = TRUE; + } + line_flag = TRUE; + Mold_Value(mold, value, TRUE); + value++; + if (NOT_END(value)) + Append_Codepoint_Raw(out, (sep[0] == '/') ? '/' : ' '); + } + + if (sep[1]) { + mold->indent--; + if (had_lines) + New_Indented_Line(mold); + Append_Codepoint_Raw(out, sep[1]); + } + + TERM_ARRAY_LEN(MOLD_STACK, ARR_LEN(MOLD_STACK) - 1); } -STOID Mold_Block(REBVAL *value, REB_MOLD *mold) + +static void Mold_Block(const RELVAL *value, REB_MOLD *mold) { - REBYTE *sep; - REBOOL all = GET_MOPT(mold, MOPT_MOLD_ALL); - REBSER *series = mold->series; - REBFLG over = FALSE; - - if (SERIES_WIDE(VAL_SERIES(value)) == 0) - Crash(RP_BAD_WIDTH, sizeof(REBVAL), 0, VAL_TYPE(value)); - - // Optimize when no index needed: - if (VAL_INDEX(value) == 0 && !IS_MAP(value)) // && (VAL_TYPE(value) <= REB_LIT_PATH)) - all = FALSE; - - // If out of range, do not cause error to avoid error looping. - if (VAL_INDEX(value) >= VAL_TAIL(value)) over = TRUE; // Force it into [] - - if (all || (over && !IS_BLOCK(value) && !IS_PAREN(value))) { - SET_FLAG(mold->opts, MOPT_MOLD_ALL); - Pre_Mold(value, mold); // #[block! part - //if (over) Append_Bytes(mold->series, "[]"); - //else - Mold_Block_Series(mold, VAL_SERIES(value), 0, 0); - Post_Mold(value, mold); - } - else - { - switch(VAL_TYPE(value)) { - - case REB_MAP: - Pre_Mold(value, mold); - sep = 0; - - case REB_BLOCK: - if (GET_MOPT(mold, MOPT_ONLY)) { - CLR_FLAG(mold->opts, MOPT_ONLY); // only top level - sep = "\000\000"; - } - else sep = 0; - break; - - case REB_PAREN: - sep = "()"; - break; - - case REB_GET_PATH: - series = Append_Byte(series, ':'); - sep = "/"; - break; - - case REB_LIT_PATH: - series = Append_Byte(series, '\''); - /* fall through */ - case REB_PATH: - case REB_SET_PATH: - sep = "/"; - break; - } - - if (over) Append_Bytes(mold->series, sep ? sep : (REBYTE*)("[]")); - else Mold_Block_Series(mold, VAL_SERIES(value), VAL_INDEX(value), sep); - - if (VAL_TYPE(value) == REB_SET_PATH) - Append_Byte(series, ':'); - } + const char *sep; + REBOOL all = GET_MOPT(mold, MOPT_MOLD_ALL); + REBSER *series = mold->series; + REBOOL over = FALSE; + + // Optimize when no index needed: + if (VAL_INDEX(value) == 0 && !IS_MAP(value)) // && (VAL_TYPE(value) <= REB_LIT_PATH)) + all = FALSE; + + // If out of range, do not cause error to avoid error looping. + if (VAL_INDEX(value) >= VAL_LEN_HEAD(value)) over = TRUE; // Force it into [] + + if (all || (over && !IS_BLOCK(value) && !IS_GROUP(value))) { + SET_FLAG(mold->opts, MOPT_MOLD_ALL); + Pre_Mold(value, mold); // #[block! part + //if (over) Append_Unencoded(mold->series, "[]"); + //else + Append_Codepoint_Raw(mold->series, '['); + Mold_Array_At(mold, VAL_ARRAY(value), 0, 0); + Post_Mold(value, mold); + Append_Codepoint_Raw(mold->series, ']'); + } + else + { + switch(VAL_TYPE(value)) { + + case REB_MAP: + Pre_Mold(value, mold); + sep = 0; + + // falls through + case REB_BLOCK: + if (GET_MOPT(mold, MOPT_ONLY)) { + CLR_FLAG(mold->opts, MOPT_ONLY); // only top level + sep = "\000\000"; + } + else sep = 0; + break; + + case REB_GROUP: + sep = "()"; + break; + + case REB_GET_PATH: + series = Append_Codepoint_Raw(series, ':'); + sep = "/"; + break; + + case REB_LIT_PATH: + series = Append_Codepoint_Raw(series, '\''); + /* fall through */ + case REB_PATH: + case REB_SET_PATH: + sep = "/"; + break; + default: + sep = NULL; + } + + if (over) Append_Unencoded(mold->series, sep ? sep : "[]"); + else Mold_Array_At(mold, VAL_ARRAY(value), VAL_INDEX(value), sep); + + if (VAL_TYPE(value) == REB_SET_PATH) + Append_Codepoint_Raw(series, ':'); + } } -STOID Mold_Simple_Block(REB_MOLD *mold, REBVAL *block, REBCNT len) +static void Mold_Simple_Block(REB_MOLD *mold, RELVAL *block, REBCNT len) { - // Simple molder for error locations. Series must be valid. - // Max length in chars must be provided. - REBCNT start = SERIES_TAIL(mold->series); - - while (NOT_END(block)) { - if ((SERIES_TAIL(mold->series) - start) > len) break; - Mold_Value(mold, block, TRUE); - block++; - if (NOT_END(block)) Append_Byte(mold->series, ' '); - } - - // If it's too large, truncate it: - if ((SERIES_TAIL(mold->series) - start) > len) { - SERIES_TAIL(mold->series) = start + len; - Append_Bytes(mold->series, "..."); - } + // Simple molder for error locations. Series must be valid. + // Max length in chars must be provided. + REBCNT start = SER_LEN(mold->series); + + while (NOT_END(block)) { + if ((SER_LEN(mold->series) - start) > len) break; + Mold_Value(mold, block, TRUE); + block++; + if (NOT_END(block)) Append_Codepoint_Raw(mold->series, ' '); + } + + // If it's too large, truncate it: + if ((SER_LEN(mold->series) - start) > len) { + SET_SERIES_LEN(mold->series, start + len); + Append_Unencoded(mold->series, "..."); + } } -STOID Form_Block_Series(REBSER *blk, REBCNT index, REB_MOLD *mold, REBSER *frame) -{ - // Form a series (part_mold means mold non-string values): - REBINT n; - REBINT len = SERIES_TAIL(blk) - index; - REBVAL *val; - REBVAL *wval; - - if (len < 0) len = 0; - - for (n = 0; n < len;) { - val = BLK_SKIP(blk, index+n); - wval = 0; - if (frame && (IS_WORD(val) || IS_GET_WORD(val))) { - wval = Find_Word_Value(frame, VAL_WORD_SYM(val)); - if (wval) val = wval; - } - Mold_Value(mold, val, wval != 0); - n++; - if (GET_MOPT(mold, MOPT_LINES)) { - Append_Byte(mold->series, LF); - } - else { - // Add a space if needed: - if (n < len && mold->series->tail - && *UNI_LAST(mold->series) != LF - && !GET_MOPT(mold, MOPT_TIGHT) - ) - Append_Byte(mold->series, ' '); - } - } + +static void Form_Array_At( + REBARR *array, + REBCNT index, + REB_MOLD *mold, + REBCTX *context +) { + // Form a series (part_mold means mold non-string values): + REBINT n; + REBINT len = ARR_LEN(array) - index; + + if (len < 0) len = 0; + + for (n = 0; n < len;) { + RELVAL *val = ARR_AT(array, index + n); + REBVAL *wval = NULL; + if (context && (IS_WORD(val) || IS_GET_WORD(val))) { + wval = Select_Canon_In_Context(context, VAL_WORD_CANON(val)); + if (wval) val = wval; + } + Mold_Value(mold, val, LOGICAL(wval != NULL)); + n++; + if (GET_MOPT(mold, MOPT_LINES)) { + Append_Codepoint_Raw(mold->series, LF); + } + else { + // Add a space if needed: + if (n < len && SER_LEN(mold->series) + && *UNI_LAST(mold->series) != LF + && !GET_MOPT(mold, MOPT_TIGHT) + ){ + Append_Codepoint_Raw(mold->series, ' '); + } + } + } } /*********************************************************************** ************************************************************************ ** -** SECTION: Special Datatypes +** SECTION: Special Datatypes ** ************************************************************************ ***********************************************************************/ -#ifdef removed -STOID Mold_Logic(REB_MOLD *mold, REBVAL *value) -{ - REBYTE buf[20]; - Pre_Mold(value, mold); - - INT_TO_STR(VAL_LOGIC(value), buf); - Append_Bytes(mold->series, buf); - Append_Byte(mold->series, ' '); - old_Block_Series(mold, BLK_HEAD(VAL_LOGIC_WORDS(value)), 0); +static void Mold_Typeset(const REBVAL *value, REB_MOLD *mold, REBOOL molded) +{ + REBINT n; + + if (molded) { + Pre_Mold(value, mold); // #[typeset! or make typeset! + Append_Codepoint_Raw(mold->series, '['); + } + +#if !defined(NDEBUG) + if (VAL_KEY_SPELLING(value) != NULL) { + // + // In debug builds we're probably more interested in the symbol than + // the typesets, if we are looking at a PARAMLIST or KEYLIST. + // + Append_Unencoded(mold->series, "("); + + REBSTR *spelling = VAL_KEY_SPELLING(value); + Append_UTF8_May_Fail( + mold->series, STR_HEAD(spelling), STR_NUM_BYTES(spelling) + ); + Append_Unencoded(mold->series, ") "); + + // REVIEW: should detect when a lot of types are active and condense + // only if the number of types is unreasonable (often for keys/params) + // + if (TRUE) { + Append_Unencoded(mold->series, "..."); + goto skip_types; + } + } +#endif - End_Mold(mold); -} + assert(!TYPE_CHECK(value, REB_0)); // REB_0 is used for internal purposes + + // Note that although REB_MAX_VOID is used as an implementation detail for + // special typesets in function paramlists or context keys to indicate + // -style optionality, the "absence of a type" is not generally legal + // in user typesets. Only legal "key" typesets (that have symbols). + // + assert( + !TYPE_CHECK(value, REB_MAX_VOID) || VAL_KEY_SPELLING(value) != NULL + ); + + // Convert bits to types. + // + for (n = REB_0 + 1; n < REB_MAX; n++) { + if (TYPE_CHECK(value, cast(enum Reb_Kind, n))) { + Emit(mold, "+DN ", SYM_DATATYPE_X, Canon(cast(REBSYM, n))); + } + } + Trim_Tail(mold->series, ' '); + +#if !defined(NDEBUG) +skip_types: #endif -STOID Mold_Typeset(REBVAL *value, REB_MOLD *mold, REBFLG molded) -{ - REBINT n; - - if (molded) { - Pre_Mold(value, mold); // #[typeset! or make typeset! - Append_Byte(mold->series, '['); - } - - // Convert bits to types (we can make this more efficient !!) - for (n = 0; n < REB_MAX; n++) { - if (TYPE_CHECK(value, n)) { - Emit(mold, "+DN ", SYM_DATATYPE_TYPE, n + 1); - } - } - Trim_Tail(mold->series, ' '); - - if (molded) { - //Form_Typeset(value, mold & ~(1<series, ']'); - End_Mold(mold); - } + if (molded) { + //Form_Typeset(value, mold & ~(1<series, ']'); + End_Mold(mold); + } } -STOID Mold_Function(REBVAL *value, REB_MOLD *mold) +static void Mold_Function(const REBVAL *value, REB_MOLD *mold) { - Pre_Mold(value, mold); - - Append_Byte(mold->series, '['); - - Mold_Block_Series(mold, VAL_FUNC_SPEC(value), 0, 0); //// & ~(1<series, ']'); - End_Mold(mold); + Pre_Mold(value, mold); + + Append_Codepoint_Raw(mold->series, '['); + + // !!! The system is no longer keeping the spec of functions, in order + // to focus on a generalized "meta info object" service. MOLD of + // functions temporarily uses the word list as a substitute (which + // drops types) + // + REBARR *words_list = List_Func_Words(value, TRUE); // show pure locals + Mold_Array_At(mold, words_list, 0, 0); + Free_Array(words_list); + + if (IS_FUNCTION_INTERPRETED(value)) { + // + // MOLD is an example of user-facing code that needs to be complicit + // in the "lie" about the effective bodies of the functions made + // by the optimized generators FUNC and CLOS... + + REBOOL is_fake; + REBARR *body = Get_Maybe_Fake_Func_Body(&is_fake, value); + + Mold_Array_At(mold, body, 0, 0); + + if (is_fake) Free_Array(body); // was shallow copy + } + else if (IS_FUNCTION_SPECIALIZER(value)) { + // + // !!! Interim form of looking at specialized functions... show + // the frame + // + // >> source first + // first: make function! [[aggregate index] [ + // aggregate: $void + // index: 1 + // ]] + // + REBVAL *exemplar = KNOWN(VAL_FUNC_BODY(value)); + Mold_Value(mold, exemplar, TRUE); + } + + Append_Codepoint_Raw(mold->series, ']'); + End_Mold(mold); } -STOID Mold_Map(REBVAL *value, REB_MOLD *mold, REBFLG molded) + +static void Mold_Map(const REBVAL *value, REB_MOLD *mold, REBOOL molded) { - REBSER *mapser = VAL_SERIES(value); - REBVAL *val; - - // Prevent endless mold loop: - if (Find_Same_Block(MOLD_LOOP, value) > 0) { - Append_Bytes(mold->series, "...]"); - return; - } - Append_Val(MOLD_LOOP, value); - - if (molded) { - Pre_Mold(value, mold); - Append_Byte(mold->series, '['); - } - - // Mold all non-none entries - mold->indent++; - for (val = BLK_HEAD(mapser); NOT_END(val) && NOT_END(val+1); val += 2) { - if (!IS_NONE(val+1)) { - if (molded) New_Indented_Line(mold); - Emit(mold, "V V", val, val+1); - if (!molded) Append_Byte(mold->series, '\n'); - } - } - mold->indent--; - - if (molded) { - New_Indented_Line(mold); - Append_Byte(mold->series, ']'); - } - - End_Mold(mold); - Remove_Last(MOLD_LOOP); + REBARR *mapser = MAP_PAIRLIST(VAL_MAP(value)); + RELVAL *val; + + // Prevent endless mold loop: + if (Find_Same_Array(MOLD_STACK, value) != NOT_FOUND) { + Append_Unencoded(mold->series, "...]"); + return; + } + Append_Value(MOLD_STACK, value); + + if (molded) { + Pre_Mold(value, mold); + Append_Codepoint_Raw(mold->series, '['); + } + + // Mold all entries that are set. As with contexts, void values are not + // valid entries but indicate the absence of a value. + // + mold->indent++; + for (val = ARR_HEAD(mapser); NOT_END(val) && NOT_END(val+1); val += 2) { + if (!IS_VOID(val + 1)) { + if (molded) New_Indented_Line(mold); + Emit(mold, "V V", val, val+1); + if (!molded) Append_Codepoint_Raw(mold->series, '\n'); + } + } + mold->indent--; + + if (molded) { + New_Indented_Line(mold); + Append_Codepoint_Raw(mold->series, ']'); + } + + End_Mold(mold); + TERM_ARRAY_LEN(MOLD_STACK, ARR_LEN(MOLD_STACK) - 1); } -STOID Form_Object(REBVAL *value, REB_MOLD *mold) + +static void Form_Object(const REBVAL *value, REB_MOLD *mold) { - REBSER *wser = VAL_OBJ_WORDS(value); - REBVAL *words = BLK_HEAD(wser); - REBVAL *vals = VAL_OBJ_VALUES(value); // first value is context - REBCNT n; - - // Prevent endless mold loop: - if (Find_Same_Block(MOLD_LOOP, value) > 0) { - Append_Bytes(mold->series, "...]"); - return; - } - Append_Val(MOLD_LOOP, value); - - // Mold all words and their values: - for (n = 1; n < SERIES_TAIL(wser); n++) { - if (!VAL_GET_OPT(words+n, OPTS_HIDE)) - Emit(mold, "N: V\n", VAL_WORD_SYM(words+n), vals+n); - } - Remove_Last(mold->series); - Remove_Last(MOLD_LOOP); + REBVAL *key = CTX_KEYS_HEAD(VAL_CONTEXT(value)); + REBVAL *var = CTX_VARS_HEAD(VAL_CONTEXT(value)); + REBOOL had_output = FALSE; + + // Prevent endless mold loop: + if (Find_Same_Array(MOLD_STACK, value) != NOT_FOUND) { + Append_Unencoded(mold->series, "...]"); + return; + } + + Append_Value(MOLD_STACK, value); + + // Mold all words and their values: + for (; NOT_END(key); key++, var++) { + if (NOT_VAL_FLAG(key, TYPESET_FLAG_HIDDEN)) { + had_output = TRUE; + Emit(mold, "N: V\n", VAL_KEY_SPELLING(key), var); + } + } + + // Remove the final newline...but only if WE added something to the buffer + if (had_output) { + SET_SERIES_LEN(mold->series, SER_LEN(mold->series) - 1); + TERM_SEQUENCE(mold->series); + } + + TERM_ARRAY_LEN(MOLD_STACK, ARR_LEN(MOLD_STACK) - 1); } -STOID Mold_Object(REBVAL *value, REB_MOLD *mold) + +static void Mold_Object(const REBVAL *value, REB_MOLD *mold) { - REBSER *wser; - REBVAL *words; - REBVAL *vals; // first value is context - REBCNT n; - - ASSERT(VAL_OBJ_FRAME(value), RP_NO_OBJECT_FRAME); - - wser = VAL_OBJ_WORDS(value); -// if (wser < 1000) -// Dump_Block_Raw(VAL_OBJ_FRAME(value), 0, 1); - words = BLK_HEAD(wser); - - vals = VAL_OBJ_VALUES(value); // first value is context - - Pre_Mold(value, mold); - - Append_Byte(mold->series, '['); - - // Prevent infinite looping: - if (Find_Same_Block(MOLD_LOOP, value) > 0) { - Append_Bytes(mold->series, "...]"); - return; - } - Append_Val(MOLD_LOOP, value); - - mold->indent++; - for (n = 1; n < SERIES_TAIL(wser); n++) { - if ( - !VAL_GET_OPT(words+n, OPTS_HIDE) && - ((VAL_TYPE(vals+n) > REB_NONE) || !GET_MOPT(mold, MOPT_NO_NONE)) - ){ - New_Indented_Line(mold); - Append_UTF8(mold->series, Get_Sym_Name(VAL_WORD_SYM(words+n)), -1); - //Print("Slot: %s", Get_Sym_Name(VAL_WORD_SYM(words+n))); - Append_Bytes(mold->series, ": "); - if (IS_WORD(vals+n) && !GET_MOPT(mold, MOPT_MOLD_ALL)) Append_Byte(mold->series, '\''); - Mold_Value(mold, vals+n, TRUE); - } - } - mold->indent--; - New_Indented_Line(mold); - Append_Byte(mold->series, ']'); - - End_Mold(mold); - Remove_Last(MOLD_LOOP); + REBVAL *keys_head = CTX_KEYS_HEAD(VAL_CONTEXT(value)); + + REBVAL *vars_head; + if (CTX_VARS_UNAVAILABLE(VAL_CONTEXT(value))) { + // + // If something like a function call has gone of the stack, the data + // for the vars will no longer be available. The keys should still + // be good, however. + // + vars_head = NULL; + } + else + vars_head = CTX_VARS_HEAD(VAL_CONTEXT(value)); + + Pre_Mold(value, mold); + + Append_Codepoint_Raw(mold->series, '['); + + // Prevent infinite looping: + if (Find_Same_Array(MOLD_STACK, value) != NOT_FOUND) { + Append_Unencoded(mold->series, "...]"); + return; + } + + Append_Value(MOLD_STACK, value); + mold->indent++; + + // !!! New experimental Ren-C code for the [[spec][body]] format of the + // non-evaluative MAKE OBJECT!. + + // First loop: spec block. This is difficult because unlike functions, + // objects are dynamically modified with new members added. If the spec + // were captured with strings and other data in it as separate from the + // "keylist" information, it would have to be updated to reflect newly + // added fields in order to be able to run a corresponding MAKE OBJECT!. + // + // To get things started, we aren't saving the original spec that made + // the object...but regenerate one from the keylist. If this were done + // with functions, they would "forget" their help strings in MOLDing. + + New_Indented_Line(mold); + Append_Codepoint_Raw(mold->series, '['); + + REBVAL *key = keys_head; + + for (; NOT_END(key); ++key) { + if (key != keys_head) + Append_Codepoint_Raw(mold->series, ' '); + + // !!! Feature of hidden words in object specs not yet implemented, + // but if it paralleled how function specs work it would be SET-WORD! + // + DECLARE_LOCAL (any_word); + Init_Any_Word( + any_word, + GET_VAL_FLAG(key, TYPESET_FLAG_HIDDEN) ? REB_SET_WORD : REB_WORD, + VAL_KEY_SPELLING(key) + ); + Mold_Value(mold, any_word, TRUE); + } + + Append_Codepoint_Raw(mold->series, ']'); + New_Indented_Line(mold); + Append_Codepoint_Raw(mold->series, '['); + + mold->indent++; + + key = keys_head; + + REBVAL *var = vars_head; + + for (; NOT_END(key); var ? (++key, ++var) : ++key) { + if (GET_VAL_FLAG(key, TYPESET_FLAG_HIDDEN)) + continue; // !!! Should hidden fields be in molded view? + + // Having the key mentioned in the spec and then not being assigned + // a value in the body is how voids are denoted. + // + if (var && IS_VOID(var)) + continue; + + New_Indented_Line(mold); + + REBSTR *spelling = VAL_KEY_SPELLING(key); + Append_UTF8_May_Fail( + mold->series, STR_HEAD(spelling), STR_NUM_BYTES(spelling) + ); + + Append_Unencoded(mold->series, ": "); + + if (var) + Mold_Value(mold, var, TRUE); + else + Append_Unencoded(mold->series, ": --optimized out--"); + } + + mold->indent--; + New_Indented_Line(mold); + Append_Codepoint_Raw(mold->series, ']'); + mold->indent--; + New_Indented_Line(mold); + Append_Codepoint_Raw(mold->series, ']'); + + End_Mold(mold); + TERM_ARRAY_LEN(MOLD_STACK, ARR_LEN(MOLD_STACK) - 1); } -STOID Mold_Error(REBVAL *value, REB_MOLD *mold, REBFLG molded) + +static void Mold_Error(const REBVAL *value, REB_MOLD *mold, REBOOL molded) { - ERROR_OBJ *err; - REBVAL *msg; // Error message block - - // Protect against recursion. !!!! - - if (molded) { - if (VAL_OBJ_FRAME(value) && VAL_ERR_NUM(value) >= RE_NOTE && VAL_ERR_OBJECT(value)) - Mold_Object(value, mold); - else { - // Happens if throw or return is molded. - // make error! 0-3 - Pre_Mold(value, mold); - Append_Int(mold->series, VAL_ERR_NUM(value)); - End_Mold(mold); - } - return; - } - - // If it is an unprocessed BREAK, THROW, CONTINUE, RETURN: - if (VAL_ERR_NUM(value) < RE_NOTE || !VAL_ERR_OBJECT(value)) { - VAL_ERR_OBJECT(value) = Make_Error(VAL_ERR_NUM(value), value, 0, 0); // spoofs field - } - err = VAL_ERR_VALUES(value); - - // Form: ** Error: - Emit(mold, "** WB", &err->type, RS_ERRS+0); - - // Append: error message ARG1, ARG2, etc. - msg = Find_Error_Info(err, 0); - if (msg) { - if (!IS_BLOCK(msg)) Mold_Value(mold, msg, 0); - else { - //start = DSP + 1; - //Reduce_In_Frame(VAL_ERR_OBJECT(value), VAL_BLK_DATA(msg)); - //SERIES_TAIL(DS_Series) = DSP + 1; - //Form_Block_Series(DS_Series, start, mold, 0); - Form_Block_Series(VAL_SERIES(msg), 0, mold, VAL_ERR_OBJECT(value)); - } - } else - Append_Boot_Str(mold->series, RS_ERRS+1); - - Append_Byte(mold->series, '\n'); - - // Form: ** Where: function - value = &err->where; - if (VAL_TYPE(value) > REB_NONE) { - Append_Boot_Str(mold->series, RS_ERRS+2); - Mold_Value(mold, value, 0); - Append_Byte(mold->series, '\n'); - } - - // Form: ** Near: location - value = &err->nearest; - if (VAL_TYPE(value) > REB_NONE) { - Append_Boot_Str(mold->series, RS_ERRS+3); - if (IS_STRING(value)) // special case: source file line number - Append_String(mold->series, VAL_SERIES(value), 0, VAL_TAIL(value)); - else if (IS_BLOCK(value)) - Mold_Simple_Block(mold, VAL_BLK_DATA(value), 60); - Append_Byte(mold->series, '\n'); - } + ERROR_VARS *vars; + REBCTX *context; + + // Protect against recursion. !!!! + + if (molded) { + Mold_Object(value, mold); + return; + } + + context = VAL_CONTEXT(value); + vars = VAL_ERR_VARS(value); + + // Form: ** Error: + if (IS_BLANK(&vars->type)) + Emit(mold, "** S", RM_ERROR_LABEL); + else { + assert(IS_WORD(&vars->type)); + Emit(mold, "** W S", &vars->type, RM_ERROR_LABEL); + } + + // Append: error message ARG1, ARG2, etc. + if (IS_BLOCK(&vars->message)) + Form_Array_At(VAL_ARRAY(&vars->message), 0, mold, context); + else if (IS_STRING(&vars->message)) + Mold_Value(mold, &vars->message, FALSE); + else + Append_Unencoded(mold->series, RM_BAD_ERROR_FORMAT); + + // Form: ** Where: function + REBVAL *where = &vars->where; + if (NOT(IS_BLANK(where))) { + Append_Codepoint_Raw(mold->series, '\n'); + Append_Unencoded(mold->series, RM_ERROR_WHERE); + Mold_Value(mold, where, FALSE); + } + + // Form: ** Near: location + REBVAL *nearest = &vars->nearest; + if (NOT(IS_BLANK(nearest))) { + Append_Codepoint_Raw(mold->series, '\n'); + Append_Unencoded(mold->series, RM_ERROR_NEAR); + + if (IS_STRING(nearest)) { + // + // !!! The scanner puts strings into the near information in order + // to say where the file and line of the scan problem was. This + // seems better expressed as an explicit argument to the scanner + // error, because otherwise it obscures the LOAD call where the + // scanner was invoked. Review. + // + Append_String( + mold->series, VAL_SERIES(nearest), 0, VAL_LEN_HEAD(nearest) + ); + } + else if (IS_BLOCK(nearest)) + Mold_Simple_Block(mold, VAL_ARRAY_AT(nearest), 60); + else + Append_Unencoded(mold->series, RM_BAD_ERROR_FORMAT); + } + + // Form: ** File: filename + // + // !!! In order to conserve space in the system, filenames are interned. + // Although interned strings are GC'd when no longer referenced, they can + // only be used in ANY-WORD! values at the moment, so the filename is + // not a FILE!. + // + REBVAL *file = &vars->file; + if (NOT(IS_BLANK(file))) { + Append_Codepoint_Raw(mold->series, '\n'); + Append_Unencoded(mold->series, RM_ERROR_FILE); + if (IS_WORD(file)) + Mold_Value(mold, file, FALSE); + else + Append_Unencoded(mold->series, RM_BAD_ERROR_FORMAT); + } + + // Form: ** Line: line-number + REBVAL *line = &vars->line; + if (NOT(IS_BLANK(line))) { + Append_Codepoint_Raw(mold->series, '\n'); + Append_Unencoded(mold->series, RM_ERROR_LINE); + if (IS_INTEGER(line)) + Mold_Value(mold, line, FALSE); + else + Append_Unencoded(mold->series, RM_BAD_ERROR_FORMAT); + } } /*********************************************************************** ************************************************************************ ** -** SECTION: Global Mold Functions +** SECTION: Global Mold Functions ** ************************************************************************ ***********************************************************************/ -/*********************************************************************** -** -*/ void Mold_Value(REB_MOLD *mold, REBVAL *value, REBFLG molded) -/* -** Mold or form any value to string series tail. -** -***********************************************************************/ +// +// Mold_Value: C +// +// Mold or form any value to string series tail. +// +void Mold_Value(REB_MOLD *mold, const RELVAL *value, REBOOL molded) { - REBYTE buf[60]; - REBINT len; - REBSER *ser = mold->series; - - CHECK_STACK(&len); - - ASSERT2(SERIES_WIDE(mold->series) == sizeof(REBUNI), RP_BAD_SIZE); - ASSERT2(ser, RP_NO_BUFFER); - - // Special handling of string series: { - if (ANY_STR(value) && !IS_TAG(value)) { - - // Forming a string: - if (!molded) { - Insert_String(ser, -1, VAL_SERIES(value), VAL_INDEX(value), VAL_LEN(value), 0); - return; - } - - // Special format for ALL string series when not at head: - if (GET_MOPT(mold, MOPT_MOLD_ALL) && VAL_INDEX(value) != 0) { - Mold_All_String(value, mold); - return; - } - } - - switch (VAL_TYPE(value)) { - case REB_NONE: - Emit(mold, "+N", SYM_NONE); - break; - - case REB_LOGIC: -// if (!molded || !VAL_LOGIC_WORDS(value) || !GET_MOPT(mold, MOPT_MOLD_ALL)) - Emit(mold, "+N", VAL_LOGIC(value) ? SYM_TRUE : SYM_FALSE); -// else -// Mold_Logic(mold, value); - break; - - case REB_INTEGER: - len = Emit_Integer(buf, VAL_INT64(value)); - goto append; - - case REB_DECIMAL: - case REB_PERCENT: - len = Emit_Decimal(buf, VAL_DECIMAL(value), IS_PERCENT(value)?DEC_MOLD_PERCENT:0, - Punctuation[GET_MOPT(mold, MOPT_COMMA_PT) ? PUNCT_COMMA : PUNCT_DOT], mold->digits); - goto append; - - case REB_MONEY: - len = Emit_Money(value, buf, mold->opts); - goto append; - - case REB_CHAR: - Mold_Uni_Char(ser, VAL_CHAR(value), (REBOOL)molded, (REBOOL)GET_MOPT(mold, MOPT_MOLD_ALL)); - break; - - case REB_PAIR: - len = Emit_Decimal(buf, VAL_PAIR_X(value), DEC_MOLD_MINIMAL, Punctuation[PUNCT_DOT], mold->digits/2); - Append_Bytes_Len(ser, buf, len); - Append_Byte(ser, 'x'); - len = Emit_Decimal(buf, VAL_PAIR_Y(value), DEC_MOLD_MINIMAL, Punctuation[PUNCT_DOT], mold->digits/2); - Append_Bytes_Len(ser, buf, len); - //Emit(mold, "IxI", VAL_PAIR_X(value), VAL_PAIR_Y(value)); - break; - - case REB_TUPLE: - len = Emit_Tuple(value, buf); - goto append; - - case REB_TIME: - //len = Emit_Time(value, buf, Punctuation[GET_MOPT(mold, MOPT_COMMA_PT) ? PUNCT_COMMA : PUNCT_DOT]); - Emit_Time(mold, value); - break; - - case REB_DATE: - Emit_Date(mold, value); - break; - - case REB_STRING: - // FORM happens in top section. - Mold_String_Series(value, mold); - break; - - case REB_BINARY: - if (GET_MOPT(mold, MOPT_MOLD_ALL) && VAL_INDEX(value) != 0) { - Mold_All_String(value, mold); - return; - } - Mold_Binary(value, mold); - break; - - case REB_FILE: - if (VAL_LEN(value) == 0) { - Append_Bytes(ser, "%\"\""); - break; - } - Mold_File(value, mold); - break; - - case REB_EMAIL: - case REB_URL: - Mold_Url(value, mold); - break; - - case REB_TAG: - if (GET_MOPT(mold, MOPT_MOLD_ALL) && VAL_INDEX(value) != 0) { - Mold_All_String(value, mold); - return; - } - Mold_Tag(value, mold); - break; - -// Mold_Issue(value, mold); -// break; - - case REB_BITSET: - Pre_Mold(value, mold); // #[bitset! or make bitset! - Mold_Bitset(value, mold); - End_Mold(mold); - break; - - case REB_IMAGE: - Pre_Mold(value, mold); - if (!GET_MOPT(mold, MOPT_MOLD_ALL)) { - Append_Byte(ser, '['); - Mold_Image_Data(value, mold); - Append_Byte(ser, ']'); - End_Mold(mold); - } - else { - REBVAL val = *value; - VAL_INDEX(&val) = 0; // mold all of it - Mold_Image_Data(&val, mold); - Post_Mold(value, mold); - } - break; - - case REB_BLOCK: - case REB_PAREN: - if (!molded) - Form_Block_Series(VAL_SERIES(value), VAL_INDEX(value), mold, 0); - else - Mold_Block(value, mold); - break; - - case REB_PATH: - case REB_SET_PATH: - case REB_GET_PATH: - case REB_LIT_PATH: - Mold_Block(value, mold); - break; - - case REB_VECTOR: - Mold_Vector(value, mold, molded); - break; - - case REB_DATATYPE: - if (!molded) - Emit(mold, "N", VAL_DATATYPE(value) + 1); - else - Emit(mold, "+DN", SYM_DATATYPE_TYPE, VAL_DATATYPE(value) + 1); - break; - - case REB_TYPESET: - Mold_Typeset(value, mold, molded); - break; - - case REB_WORD: - // This is a high frequency function, so it is optimized. - Append_UTF8(ser, Get_Sym_Name(VAL_WORD_SYM(value)), -1); - break; - - case REB_SET_WORD: - Emit(mold, "W:", value); - break; - - case REB_GET_WORD: - Emit(mold, ":W", value); - break; - - case REB_LIT_WORD: - Emit(mold, "\'W", value); - break; - - case REB_REFINEMENT: - Emit(mold, "/W", value); - break; - - case REB_ISSUE: - Emit(mold, "#W", value); - break; - - case REB_CLOSURE: - case REB_FUNCTION: - case REB_NATIVE: - case REB_ACTION: - case REB_COMMAND: - Mold_Function(value, mold); - break; - - case REB_OBJECT: - case REB_MODULE: - case REB_PORT: - if (!molded) Form_Object(value, mold); - else Mold_Object(value, mold); - break; - - case REB_TASK: - Mold_Object(value, mold); //// | (1<series; + + if (C_STACK_OVERFLOWING(&len)) Trap_Stack_Overflow(); + + assert(SER_WIDE(ser) == sizeof(REBUNI)); + ASSERT_SERIES_TERM(ser); + + if (GET_MOPT(mold, MOPT_LIMIT)) { + // + // It's hard to detect the exact moment of tripping over the length + // limit unless all code paths that add to the mold buffer (e.g. + // tacking on delimiters etc.) check the limit. The easier thing + // to do is check at the end and truncate. This adds a lot of data + // wastefully, so short circuit here in the release build. (Have + // the debug build keep going to exercise mold on the data.) + // + #ifdef NDEBUG + if (SER_LEN(mold->series) >= mold->limit) + return; + #endif + } + + if (THROWN(value)) { + // !!! You do not want to see THROWN values leak into user awareness, + // as they are an implementation detail. So unless this is debug + // output, it should be an assert. Thus REB_MOLD probably needs a + // "for debug output purposes" switch. + Emit(mold, "S", "!!! THROWN() -> "); + } + + // Special handling of string series: { + if (ANY_STRING(value) && !IS_TAG(value)) { + + // Forming a string: + if (!molded) { + Insert_String( + ser, + SER_LEN(ser), // "insert" at tail (append) + VAL_SERIES(value), + VAL_INDEX(value), + VAL_LEN_AT(value), + FALSE + ); + return; + } + + // Special format for ALL string series when not at head: + if (GET_MOPT(mold, MOPT_MOLD_ALL) && VAL_INDEX(value) != 0) { + Mold_All_String(const_KNOWN(value), mold); + return; + } + } + + switch (VAL_TYPE(value)) { + case REB_MAX_VOID: + // Voids should only be molded in debug scenarios + Append_Unencoded(ser, "&void"); + break; + + case REB_BAR: + Append_Unencoded(ser, "|"); + break; + + case REB_LIT_BAR: + Append_Unencoded(ser, "'|"); + break; + + case REB_BLANK: + Append_Unencoded(ser, "_"); + break; + + case REB_LOGIC: + Emit(mold, "+N", VAL_LOGIC(value) ? Canon(SYM_TRUE) : Canon(SYM_FALSE)); + break; + + case REB_INTEGER: + len = Emit_Integer(buf, VAL_INT64(value)); + goto append; + + case REB_DECIMAL: + case REB_PERCENT: + len = Emit_Decimal(buf, VAL_DECIMAL(value), IS_PERCENT(value)?DEC_MOLD_PERCENT:0, + Punctuation[GET_MOPT(mold, MOPT_COMMA_PT) ? PUNCT_COMMA : PUNCT_DOT], mold->digits); + goto append; + + case REB_MONEY: + len = Emit_Money(const_KNOWN(value), buf, mold->opts); + goto append; + + case REB_CHAR: + Mold_Uni_Char( + ser, VAL_CHAR(value), molded, GET_MOPT(mold, MOPT_MOLD_ALL) + ); + break; + + case REB_PAIR: + len = Emit_Decimal(buf, VAL_PAIR_X(value), DEC_MOLD_MINIMAL, Punctuation[PUNCT_DOT], mold->digits/2); + Append_Unencoded_Len(ser, s_cast(buf), len); + Append_Codepoint_Raw(ser, 'x'); + len = Emit_Decimal(buf, VAL_PAIR_Y(value), DEC_MOLD_MINIMAL, Punctuation[PUNCT_DOT], mold->digits/2); + Append_Unencoded_Len(ser, s_cast(buf), len); + //Emit(mold, "IxI", VAL_PAIR_X(value), VAL_PAIR_Y(value)); + break; + + case REB_TUPLE: + len = Emit_Tuple(const_KNOWN(value), buf); + goto append; + + case REB_TIME: + //len = Emit_Time(value, buf, Punctuation[GET_MOPT(mold, MOPT_COMMA_PT) ? PUNCT_COMMA : PUNCT_DOT]); + Emit_Time(mold, const_KNOWN(value)); + break; + + case REB_DATE: + Emit_Date(mold, const_KNOWN(value)); + break; + + case REB_STRING: + // FORM happens in top section. + Mold_String_Series(const_KNOWN(value), mold); + break; + + case REB_BINARY: + if (GET_MOPT(mold, MOPT_MOLD_ALL) && VAL_INDEX(value) != 0) { + Mold_All_String(const_KNOWN(value), mold); + return; + } + Mold_Binary(const_KNOWN(value), mold); + break; + + case REB_FILE: + if (VAL_LEN_AT(value) == 0) { + Append_Unencoded(ser, "%\"\""); + break; + } + Mold_File(const_KNOWN(value), mold); + break; + + case REB_EMAIL: + case REB_URL: + Mold_Url(const_KNOWN(value), mold); + break; + + case REB_TAG: + if (GET_MOPT(mold, MOPT_MOLD_ALL) && VAL_INDEX(value) != 0) { + Mold_All_String(const_KNOWN(value), mold); + return; + } + Mold_Tag(const_KNOWN(value), mold); + break; + +// Mold_Issue(value, mold); +// break; + + case REB_BITSET: + Pre_Mold(value, mold); // #[bitset! or make bitset! + Mold_Bitset(const_KNOWN(value), mold); + End_Mold(mold); + break; + + case REB_IMAGE: + Pre_Mold(value, mold); + if (!GET_MOPT(mold, MOPT_MOLD_ALL)) { + Append_Codepoint_Raw(ser, '['); + Mold_Image_Data(const_KNOWN(value), mold); + Append_Codepoint_Raw(ser, ']'); + End_Mold(mold); + } + else { + DECLARE_LOCAL (head); + Move_Value(head, const_KNOWN(value)); + VAL_INDEX(head) = 0; // mold all of it + Mold_Image_Data(head, mold); + Post_Mold(value, mold); + } + break; + + case REB_BLOCK: + case REB_GROUP: + if (!molded) + Form_Array_At(VAL_ARRAY(value), VAL_INDEX(value), mold, 0); + else + Mold_Block(value, mold); + break; + + case REB_PATH: + case REB_SET_PATH: + case REB_GET_PATH: + case REB_LIT_PATH: + Mold_Block(value, mold); + break; + + case REB_VECTOR: + Mold_Vector(const_KNOWN(value), mold, molded); + break; + + case REB_DATATYPE: { + REBSTR *name = Canon(VAL_TYPE_SYM(value)); + #if !defined(NDEBUG) + if (LEGACY(OPTIONS_PAREN_INSTEAD_OF_GROUP)) { + if (VAL_TYPE_KIND(value) == REB_GROUP) + name = Canon(SYM_PAREN_X); // e_Xclamation point (GROUP!) + } + #endif + if (!molded) + Emit(mold, "N", name); + else + Emit(mold, "+DN", SYM_DATATYPE_X, name); + break; } + + case REB_TYPESET: + Mold_Typeset(const_KNOWN(value), mold, molded); + break; + + case REB_WORD: { // Note: called often + REBSTR *spelling = VAL_WORD_SPELLING(value); + Append_UTF8_May_Fail(ser, STR_HEAD(spelling), STR_NUM_BYTES(spelling)); + break; + } + + case REB_SET_WORD: + Emit(mold, "W:", value); + break; + + case REB_GET_WORD: + Emit(mold, ":W", value); + break; + + case REB_LIT_WORD: + Emit(mold, "\'W", value); + break; + + case REB_REFINEMENT: + Emit(mold, "/W", value); + break; + + case REB_ISSUE: + Emit(mold, "#W", value); + break; + + case REB_FUNCTION: + Mold_Function(const_KNOWN(value), mold); + break; + + case REB_VARARGS: + Mold_Varargs(const_KNOWN(value), mold); + break; + + case REB_OBJECT: + case REB_MODULE: + case REB_PORT: + case REB_FRAME: + if (!molded) Form_Object(const_KNOWN(value), mold); + else Mold_Object(const_KNOWN(value), mold); + break; + + case REB_ERROR: + Mold_Error(const_KNOWN(value), mold, molded); + break; + + case REB_MAP: + Mold_Map(const_KNOWN(value), mold, molded); + break; + + case REB_GOB: + { + REBARR *array; + Pre_Mold(value, mold); + array = Gob_To_Array(VAL_GOB(value)); + Mold_Array_At(mold, array, 0, 0); + End_Mold(mold); + Free_Array(array); + } + break; + + case REB_EVENT: + Mold_Event(const_KNOWN(value), mold); + break; + + case REB_STRUCT: { + Pre_Mold(value, mold); + + REBARR *array = Struct_To_Array(VAL_STRUCT(value)); + Mold_Array_At(mold, array, 0, 0); + Free_Array(array); + + End_Mold(mold); + break; } + + case REB_LIBRARY: { + Pre_Mold(value, mold); + + REBCTX *meta = VAL_LIBRARY_META(value); + if (meta) + Mold_Object(CTX_VALUE(meta), mold); + + End_Mold(mold); + break; } + + case REB_HANDLE: + // Value has no printable form, so just print its name. + if (!molded) Emit(mold, "?T?", value); + else Emit(mold, "+T", value); + break; + + default: + panic (value); + } + goto check_and_return; append: - Append_Bytes_Len(ser, buf, len); + Append_Unencoded_Len(ser, s_cast(buf), len); +check_and_return: + ASSERT_SERIES_TERM(ser); } -/*********************************************************************** -** -*/ REBSER *Copy_Form_Value(REBVAL *value, REBCNT opts) -/* -** Form a value based on the mold opts provided. -** -***********************************************************************/ +// +// Copy_Form_Value: C +// +// Form a value based on the mold opts provided. +// +REBSER *Copy_Form_Value(const RELVAL *value, REBFLGS opts) { - REB_MOLD mo = {0}; + REB_MOLD mo; + CLEARS(&mo); + mo.opts = opts; - mo.opts = opts; - Reset_Mold(&mo); - Mold_Value(&mo, value, 0); - return Copy_String(mo.series, 0, -1); + Push_Mold(&mo); + Mold_Value(&mo, value, FALSE); + return Pop_Molded_String(&mo); } -/*********************************************************************** -** -*/ REBSER *Copy_Mold_Value(REBVAL *value, REBCNT opts) -/* -** Form a value based on the mold opts provided. -** -***********************************************************************/ +// +// Copy_Mold_Value: C +// +// Form a value based on the mold opts provided. +// +REBSER *Copy_Mold_Value(const REBVAL *value, REBFLGS opts) { - REB_MOLD mo = {0}; + REB_MOLD mo; + CLEARS(&mo); + mo.opts = opts; - mo.opts = opts; - Reset_Mold(&mo); - Mold_Value(&mo, value, TRUE); - return Copy_String(mo.series, 0, -1); + Push_Mold(&mo); + Mold_Value(&mo, value, TRUE); + return Pop_Molded_String(&mo); } -/*********************************************************************** -** -*/ REBSER *Form_Reduce(REBSER *block, REBCNT index) -/* -** Reduce a block and then form each value into a string. Return the -** string or NULL if an unwind triggered while reducing. -** -***********************************************************************/ -{ - REBINT start = DSP + 1; - REBINT n; - REB_MOLD mo = {0}; +// +// Form_Reduce_Throws: C +// +// Evaluates each item in a block and forms it, with an optional delimiter. +// +// The special treatment of BLANK! in the source block is to act as an +// opt-out, and the special treatment of BAR! is to act as a line break. +// There's no such thing as a void literal in the incoming block, but if +// an element evaluated to void it is also considered an opt-out, equivalent +// to a BLANK!. +// +// BAR!, BLANK!/void, and CHAR! suppress the delimiter logic. Hence if you +// are to form `["a" space "b" | () (blank) "c" newline "d" "e"]` with a +// delimiter of ":", you will get back `"a b^/c^/d:e"... where only the +// last interstitial is considered a valid candidate for delimiting. +// +REBOOL Form_Reduce_Throws( + REBVAL *out, + REBARR *array, + REBCNT index, + REBSPC *specifier, + const REBVAL *delimiter +) { + assert(!IS_VOID(delimiter)); // use BLANK! to indicate no delimiting + if (IS_BAR(delimiter)) + delimiter = ROOT_NEWLINE_CHAR; // BAR! is synonymous to newline here + + REB_MOLD mo; + CLEARS(&mo); + + Push_Mold(&mo); + + DECLARE_FRAME (f); + Push_Frame_At(f, array, index, specifier, DO_FLAG_NORMAL); + + REBOOL pending = FALSE; + + while (NOT_END(f->value)) { + if (IS_BLANK(f->value)) { // opt-out + Fetch_Next_In_Frame(f); + continue; + } + + if (IS_BAR(f->value)) { // newline + Append_Codepoint_Raw(mo.series, '\n'); + pending = FALSE; + Fetch_Next_In_Frame(f); + continue; + } + + if (Do_Next_In_Frame_Throws(out, f)) { + Drop_Frame(f); + return TRUE; + } + + if (IS_VOID(out) || IS_BLANK(out)) // opt-out + continue; + + if (IS_BAR(out)) { // newline + Append_Codepoint_Raw(mo.series, '\n'); + pending = FALSE; + continue; + } + + if (IS_CHAR(out)) { + Append_Codepoint_Raw(mo.series, VAL_CHAR(out)); + pending = FALSE; + } + else if (IS_BLANK(delimiter)) // no delimiter + Mold_Value(&mo, out, FALSE); + else { + if (pending) + Mold_Value(&mo, delimiter, FALSE); + + Mold_Value(&mo, out, FALSE); + pending = TRUE; + } + } + + Drop_Frame(f); + + Init_String(out, Pop_Molded_String(&mo)); + + return FALSE; +} - while (index < BLK_LEN(block)) { - index = Do_Next(block, index, 0); - if (THROWN(DS_TOP)) { - *DS_VALUE(start) = *DS_TOP; - DSP = start; - return NULL; - } - } - Reset_Mold(&mo); +// +// Form_Tight_Block: C +// +REBSER *Form_Tight_Block(const REBVAL *blk) +{ + RELVAL *val; - for (n = start; n <= DSP; n++) - Mold_Value(&mo, &DS_Base[n], 0); + REB_MOLD mo; + CLEARS(&mo); - DSP = start; + Push_Mold(&mo); + for (val = VAL_ARRAY_AT(blk); NOT_END(val); val++) + Mold_Value(&mo, val, FALSE); - return Copy_String(mo.series, 0, -1); + return Pop_Molded_String(&mo); } -/*********************************************************************** -** -*/ REBSER *Form_Tight_Block(REBVAL *blk) -/* -***********************************************************************/ +// +// Push_Mold: C +// +void Push_Mold(REB_MOLD *mold) { - REB_MOLD mo = {0}; - REBVAL *val; +#if !defined(NDEBUG) + // + // If some kind of Debug_Fmt() happens while this Push_Mold is happening, + // it will lead to a recursion. It's necessary to look at the stack in + // the debugger and figure it out manually. (e.g. any failures in this + // function will break the very mechanism by which failure messages + // are reported.) + // + // !!! This isn't ideal. So if all the routines below guaranteed to + // use some kind of assert reporting mechanism "lower than mold" + // (hence "lower than Debug_Fmt") that would be an improvement. + // + assert(!TG_Pushing_Mold); + TG_Pushing_Mold = TRUE; +#endif + + // Series is nulled out on Pop in debug builds to make sure you don't + // Push the same mold tracker twice (without a Pop) + // + assert(!mold->series); + +#if !defined(NDEBUG) + // Sanity check that if they set a limit it wasn't 0. (Perhaps over the + // long term it would be okay, but for now we'll consider it a mistake.) + // + if (GET_MOPT(mold, MOPT_LIMIT)) + assert(mold->limit != 0); +#endif - Reset_Mold(&mo); - for (val = VAL_BLK_DATA(blk); NOT_END(val); val++) - Mold_Value(&mo, val, 0); - return Copy_String(mo.series, 0, -1); + mold->series = UNI_BUF; + mold->start = SER_LEN(mold->series); + + ASSERT_SERIES_TERM(mold->series); + + if ( + GET_MOPT(mold, MOPT_RESERVE) + && SER_REST(mold->series) < mold->reserve + ) { + // Expand will add to the series length, so we set it back. + // + // !!! Should reserve actually leave the length expanded? Some cases + // definitely don't want this, others do. The protocol most + // compatible with the appending mold is to come back with an + // empty buffer after a push. + // + Expand_Series(mold->series, mold->start, mold->reserve); + SET_SERIES_LEN(mold->series, mold->start); + } + else if (SER_REST(mold->series) - SER_LEN(mold->series) > MAX_COMMON) { + // + // If the "extra" space in the series has gotten to be excessive (due + // to some particularly large mold), back off the space. But preserve + // the contents, as there may be important mold data behind the + // ->start index in the stack! + // + Remake_Series( + mold->series, + SER_LEN(mold->series) + MIN_COMMON, + SER_WIDE(mold->series), + NODE_FLAG_NODE // NODE_FLAG_NODE means preserve the data + ); + } + + if (GET_MOPT(mold, MOPT_MOLD_ALL)) + mold->digits = MAX_DIGITS; + else { + // If there is no notification when the option is changed, this + // must be retrieved each time. + // + // !!! It may be necessary to mold out values before the options + // block is loaded, and this 'Get_System_Int' is a bottleneck which + // crashes that in early debugging. BOOT_ERRORS is sufficient. + // + if (PG_Boot_Phase >= BOOT_ERRORS) { + REBINT idigits = Get_System_Int( + SYS_OPTIONS, OPTIONS_DECIMAL_DIGITS, MAX_DIGITS + ); + if (idigits < 0) + mold->digits = 0; + else if (idigits > MAX_DIGITS) + mold->digits = cast(REBCNT, idigits); + else + mold->digits = MAX_DIGITS; + } + else + mold->digits = MAX_DIGITS; + } + +#if !defined(NDEBUG) + TG_Pushing_Mold = FALSE; +#endif } -/*********************************************************************** -** -*/ void Reset_Mold(REB_MOLD *mold) -/* -***********************************************************************/ +// +// Throttle_Mold: C +// +// Contain a mold's series to its limit (if it has one). +// +void Throttle_Mold(REB_MOLD *mold) { + if (GET_MOPT(mold, MOPT_LIMIT) && SER_LEN(mold->series) > mold->limit) { + SET_SERIES_LEN(mold->series, mold->limit - 3); // account for ellipsis + Append_Unencoded(mold->series, "..."); // adds a null at the tail + } +} + + +// +// Pop_Molded_String_Core: C +// +// When a Push_Mold is started, then string data for the mold is accumulated +// at the tail of the task-global unicode buffer. Once the molding is done, +// this allows extraction of the string, and resets the buffer to its length +// at the time when the last push began. +// +// Can limit string output to a specified size to prevent long console +// garbage output if MOPT_LIMIT was set in Push_Mold(). +// +// If len is END_FLAG then all the string content will be copied, otherwise +// it will be copied up to `len`. If there are not enough characters then +// the debug build will assert. +// +REBSER *Pop_Molded_String_Core(REB_MOLD *mold, REBCNT len) { - REBSER *buf = BUF_MOLD; - REBINT len; - - if (!buf) Crash(RP_NO_BUFFER); - - if (SERIES_REST(buf) > MAX_COMMON) - Shrink_Series(buf, MIN_COMMON); - - BLK_RESET(MOLD_LOOP); - RESET_SERIES(buf); - mold->series = buf; - - // This is not needed every time, but w/o a functional way to set the option, - // it must be done like this and each time. - if (GET_MOPT(mold, MOPT_MOLD_ALL)) len = MAX_DIGITS; - else { - len = Get_System_Int(SYS_OPTIONS, OPTIONS_DECIMAL_DIGITS, MAX_DIGITS); - if (len > MAX_DIGITS) len = MAX_DIGITS; - else if (len < 0) len = 0; - } - mold->digits = len; + REBSER *string; + + assert(mold->series); // if NULL there was no Push_Mold() + + ASSERT_SERIES_TERM(mold->series); + Throttle_Mold(mold); + + assert( + (len == UNKNOWN) || (len <= SER_LEN(mold->series) - mold->start) + ); + + // The copy process looks at the characters in range and will make a + // BYTE_SIZE() target string out of the REBUNIs if possible... + // + string = Copy_String_Slimming( + mold->series, + mold->start, + (len == UNKNOWN) + ? SER_LEN(mold->series) - mold->start + : len + ); + + // Though the protocol of Mold_Value does terminate, it only does so if + // it adds content to the buffer. If we did not terminate when we + // reset the size, then these no-op molds (e.g. mold of "") would leave + // whatever value in the terminator spot was there. This could be + // addressed by making no-op molds terminate. + // + TERM_UNI_LEN(mold->series, mold->start); + + mold->series = NULL; + + return string; } -/*********************************************************************** -** -*/ REBSER *Mold_Print_Value(REBVAL *value, REBCNT limit, REBFLG mold) -/* -** Basis function for print. Can do a form or a mold based -** on the mold flag setting. Can limit string output to a -** specified size to prevent long console garbage output. -** -***********************************************************************/ +// +// Pop_Molded_UTF8: C +// +// Same as Pop_Molded_String() except gives back the data in UTF8 byte-size +// series form. +// +REBSER *Pop_Molded_UTF8(REB_MOLD *mold) { - REB_MOLD mo = {0}; + assert(SER_LEN(mold->series) >= mold->start); - Reset_Mold(&mo); + ASSERT_SERIES_TERM(mold->series); + Throttle_Mold(mold); - Mold_Value(&mo, value, mold); + REBSER *bytes = Make_UTF8_Binary( + UNI_AT(mold->series, mold->start), + SER_LEN(mold->series) - mold->start, + 0, + OPT_ENC_UNISRC + ); + assert(BYTE_SIZE(bytes)); - if (limit != 0 && STR_LEN(mo.series) > limit) { - SERIES_TAIL(mo.series) = limit; - Append_Bytes(mo.series, "..."); // adds a null at the tail - } + TERM_UNI_LEN(mold->series, mold->start); - return mo.series; + mold->series = NULL; + return bytes; } -/*********************************************************************** -** -*/ void Init_Mold(REBCNT size) -/* -***********************************************************************/ +// +// Drop_Mold_Core: C +// +// When generating a molded string, sometimes it's enough to have access to +// the molded data without actually creating a new series out of it. If the +// information in the mold has done its job and Pop_Molded_String() is not +// required, just call this to drop back to the state of the last push. +// +void Drop_Mold_Core(REB_MOLD *mold, REBOOL not_pushed_ok) +{ + // The tokenizer can often identify tokens to load by their start and end + // pointers in the UTF8 data it is loading alone. However, scanning + // string escapes is a process that requires converting the actual + // characters to unicode. To avoid redoing this work later in the scan, + // it uses the unicode buffer as a storage space from the tokenization + // that did UTF-8 decoding of string contents to reuse. + // + // Despite this usage, it's desirable to be able to do things like output + // debug strings or do basic molding in that code. So to reuse the + // allocated unicode buffer, it has to properly participate in the mold + // stack protocol. + // + // However, only a few token types use the buffer. Rather than burden + // the tokenizer with an additional flag, having a modality to be willing + // to "drop" a mold that hasn't ever been pushed is the easiest way to + // avoid intervening. Drop_Mold_If_Pushed(&mo) macro makes this clearer. + // + if (not_pushed_ok && mold->series == NULL) + return; + + assert(mold->series != NULL); // if NULL there was no Push_Mold + + // When pushed data are to be discarded, mold->series may be unterminated. + // (Indeed that happens when Scan_Item_Push_Mold returns NULL/0.) + // + NOTE_SERIES_MAYBE_TERM(mold->series); + + TERM_UNI_LEN(mold->series, mold->start); // see Pop_Molded_String() notes + + mold->series = NULL; +} + + +// +// Startup_Mold: C +// +void Startup_Mold(REBCNT size) +{ + REBYTE *cp; + REBYTE c; + const REBYTE *dc; + + Init_Block(TASK_MOLD_STACK, Make_Array(size/10)); + Init_String(TASK_UNI_BUF, Make_Unicode(size)); + + // Create quoted char escape table: + Char_Escapes = cp = ALLOC_N_ZEROFILL(REBYTE, MAX_ESC_CHAR + 1); + for (c = '@'; c <= '_'; c++) *cp++ = c; + Char_Escapes[cast(REBYTE, '\t')] = '-'; // tab + Char_Escapes[cast(REBYTE, '\n')] = '/'; // line feed + Char_Escapes[cast(REBYTE, '"')] = '"'; + Char_Escapes[cast(REBYTE, '^')] = '^'; + + URL_Escapes = cp = ALLOC_N_ZEROFILL(REBYTE, MAX_URL_CHAR + 1); + //for (c = 0; c <= MAX_URL_CHAR; c++) if (IS_LEX_DELIMIT(c)) cp[c] = ESC_URL; + for (c = 0; c <= ' '; c++) cp[c] = ESC_URL | ESC_FILE; + dc = cb_cast(";%\"()[]{}<>"); + for (c = LEN_BYTES(dc); c > 0; c--) URL_Escapes[*dc++] = ESC_URL | ESC_FILE; +} + + +// +// Shutdown_Mold: C +// +void Shutdown_Mold(void) { - REBYTE *cp; - REBYTE c; - REBYTE *dc; - - Set_Root_Series(TASK_MOLD_LOOP, Make_Block(size/10), "mold loop"); - Set_Root_Series(TASK_BUF_MOLD, Make_Unicode(size), "mold buffer"); - - // Create quoted char escape table: - Char_Escapes = cp = Make_Mem(MAX_ESC_CHAR+1); // cleared - for (c = '@'; c <= '_'; c++) *cp++ = c; - Char_Escapes[TAB] = '-'; - Char_Escapes[LF] = '/'; - Char_Escapes['"'] = '"'; - Char_Escapes['^'] = '^'; - - URL_Escapes = cp = Make_Mem(MAX_URL_CHAR+1); // cleared - //for (c = 0; c <= MAX_URL_CHAR; c++) if (IS_LEX_DELIMIT(c)) cp[c] = ESC_URL; - for (c = 0; c <= ' '; c++) cp[c] = ESC_URL | ESC_FILE; - dc = ";%\"()[]{}<>"; - for (c = LEN_BYTES(dc); c > 0; c--) URL_Escapes[*dc++] = ESC_URL | ESC_FILE; + FREE_N(REBYTE, MAX_ESC_CHAR + 1, Char_Escapes); + FREE_N(REBYTE, MAX_URL_CHAR + 1, URL_Escapes); } diff --git a/src/core/s-ops.c b/src/core/s-ops.c old mode 100644 new mode 100755 index 7c755caa09..767e439d59 --- a/src/core/s-ops.c +++ b/src/core/s-ops.c @@ -1,776 +1,798 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: s-ops.c -** Summary: string handling utilities -** Section: strings -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %s-ops.c +// Summary: "string handling utilities" +// Section: strings +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -#include "sys-scan.h" -/********************************************************************* -** -*/ REBOOL Is_Not_ASCII(REBYTE *bp, REBCNT len) -/* -** Returns TRUE if byte string uses upper code page. -** -***********************************************************************/ +// +// All_Bytes_ASCII: C +// +// Returns TRUE if byte string does not use upper code page +// (e.g. no 128-255 characters) +// +REBOOL All_Bytes_ASCII(REBYTE *bp, REBCNT len) { - for (; len > 0; len--, bp++) - if (*bp >= 0x80) return TRUE; + for (; len > 0; len--, bp++) + if (*bp >= 0x80) return FALSE; - return FALSE; + return TRUE; } -/********************************************************************* -** -*/ REBOOL Is_Wide(REBUNI *up, REBCNT len) -/* -** Returns TRUE if uni string needs 16 bits. -** -***********************************************************************/ +// +// Is_Wide: C +// +// Returns TRUE if uni string needs 16 bits. +// +REBOOL Is_Wide(const REBUNI *up, REBCNT len) { - for (; len > 0; len--, up++) - if (*up >= 0x100) return TRUE; + for (; len > 0; len--, up++) + if (*up >= 0x100) return TRUE; - return FALSE; + return FALSE; } -/********************************************************************* -** -*/ REBYTE *Qualify_String(REBVAL *val, REBINT max_len, REBCNT *length, REBINT opts) -/* -** Prequalifies a string before using it with a function that -** expects it to be 8-bits. -** -** Returns a temporary string and sets the length field. -** -** Opts can be: -** 0 - no special options -** 1 - allow UTF8 (val is converted to UTF8 during qualification) -** 2 - allow binary -** -** Checks or converts it: -** -** 1. it is byte string (not unicode) -** 2. if unicode, copy and return as temp byte string -** 3. it's actual content (less space, newlines) <= max len -** 4. it does not contain other values ("123 456") -** 5. it's not empty or only whitespace -** -** Notes: -* -** 1. This function will TRAP on errors. -** 2. Do not recursively use it (internal buffer) -** -***********************************************************************/ -{ - REBCNT tail = VAL_TAIL(val); - REBCNT index = VAL_INDEX(val); - REBCNT len; - REBUNI c; - REBYTE *bp; - REBSER *src = VAL_SERIES(val); - - if (index > tail) Trap0(RE_PAST_END); - - Resize_Series(BUF_FORM, max_len+1); - bp = BIN_HEAD(BUF_FORM); - - // Skip leading whitespace: - for (; index < tail; index++) { - c = GET_ANY_CHAR(src, index); - if (!IS_SPACE(c)) break; - } - - // Copy chars that are valid: - for (; index < tail; index++) { - c = GET_ANY_CHAR(src, index); - if (opts < 2 && c >= 0x80) { - if (opts == 0) Trap0(RE_INVALID_CHARS); - len = Encode_UTF8_Char(bp, c); - max_len -= len; - bp += len; - } - else if (!IS_SPACE(c)) { - *bp++ = (REBYTE)c; - max_len--; - } - else break; - if (max_len < 0) - Trap0(RE_TOO_LONG); - } - - // Rest better be just spaces: - for (; index < tail; index++) { - c = GET_ANY_CHAR(src, index); - if (!IS_SPACE(c)) Trap0(RE_INVALID_CHARS); - } - - *bp= 0; - - len = bp - BIN_HEAD(BUF_FORM); - if (len == 0) Trap0(RE_TOO_SHORT); - - if (length) *length = len; - - return BIN_HEAD(BUF_FORM); +// +// Temp_Byte_Chars_May_Fail: C +// +// NOTE: This function returns a temporary result, and uses an internal +// buffer. Do not use it recursively. Also, it will Trap on errors. +// +// Prequalifies a string before using it with a function that +// expects it to be 8-bits. It would be used for instance to convert +// a string that is potentially REBUNI-wide into a form that can be used +// with a Scan_XXX routine, that is expecting ASCII or UTF-8 source. +// (Many TO-XXX conversions from STRING re-use that scanner logic.) +// +// Returns a temporary string and sets the length field. +// +// If `allow_utf8`, the constructed result is converted to UTF8. +// +// Checks or converts it: +// +// 1. it is byte string (not unicode) +// 2. if unicode, copy and return as temp byte string +// 3. it's actual content (less space, newlines) <= max len +// 4. it does not contain other values ("123 456") +// 5. it's not empty or only whitespace +// +REBYTE *Temp_Byte_Chars_May_Fail( + const REBVAL *val, + REBINT max_len, + REBCNT *length, + REBOOL allow_utf8 +) { + REBCNT tail = VAL_LEN_HEAD(val); + REBCNT index = VAL_INDEX(val); + REBCNT len; + REBUNI c; + REBYTE *bp; + REBSER *src = VAL_SERIES(val); + + if (index > tail) fail (Error_Past_End_Raw()); + + Resize_Series(BYTE_BUF, max_len+1); + bp = BIN_HEAD(BYTE_BUF); + + // Skip leading whitespace: + for (; index < tail; index++) { + c = GET_ANY_CHAR(src, index); + if (!IS_SPACE(c)) break; + } + + // Copy chars that are valid: + for (; index < tail; index++) { + c = GET_ANY_CHAR(src, index); + if (c >= 0x80) { + if (!allow_utf8) fail (Error_Invalid_Chars_Raw()); + + len = Encode_UTF8_Char(bp, c); + max_len -= len; + bp += len; + } + else if (!IS_SPACE(c)) { + *bp++ = (REBYTE)c; + max_len--; + } + else break; + if (max_len < 0) + fail (Error_Too_Long_Raw()); + } + + // Rest better be just spaces: + for (; index < tail; index++) { + c = GET_ANY_CHAR(src, index); + if (!IS_SPACE(c)) fail (Error_Invalid_Chars_Raw()); + } + + *bp = '\0'; + + len = bp - BIN_HEAD(BYTE_BUF); + if (len == 0) fail (Error_Too_Short_Raw()); + + if (length) *length = len; + + return BIN_HEAD(BYTE_BUF); } -/********************************************************************* -** -*/ REBSER *Prep_Bin_Str(REBVAL *val, REBCNT *index, REBCNT *length) -/* -** Determines if UTF8 conversion is needed for a series before it -** is used with a byte-oriented function. -** -** If conversion is needed, a temp series is returned with the UTF8. -** Otherwise, the source series is returned as-is. -** -** The UTF8 flags that val is converted to UTF8 during qualification. -** -** Do not recursively use it (because of internal buffer). -** -***********************************************************************/ +// +// Temp_Bin_Str_Managed: C +// +// Determines if UTF8 conversion is needed for a series before it +// is used with a byte-oriented function. +// +// If conversion is needed, a UTF8 series will be created. Otherwise, +// the source series is returned as-is. +// +// Note: This routine should only be used to generate a value used +// for temporary purposes, because it has a "surprising variance" +// regarding its input. If the value's series can be reused, it is-- +// and this depends on an implementation detail of internal encoding +// that the user should not be aware of (they need not know if the +// internal representation of an ASCII string uses 1, 2, or however +// many bytes). But copying vs. non-copying means the resulting +// data might or might not have previous values available to step +// back into from the originating series! +// +// !!! Should performance dictate it, the callsites could be +// adapted to know whether this produced a new series or not, and +// instead of managing a created result they could be responsible +// for freeing it if so. +// +REBSER *Temp_Bin_Str_Managed(const RELVAL *val, REBCNT *index, REBCNT *length) { - REBCNT idx = VAL_INDEX(val); - REBCNT len; - REBSER *ser = 0; - - len = (length && *length) ? *length : VAL_LEN(val); - - // Is it binary? If so, then no conversion needed. - if (IS_BINARY(val) || len == 0) - ser = VAL_SERIES(val); - else // Convert it if 16-bit or has latin-1 upper chars. - if (NZ(ser = Encode_UTF8_Value(val, len, ENCF_NO_COPY))) { - idx = 0; - len = SERIES_TAIL(ser); - } - else ser = VAL_SERIES(val); - - if (index) *index = idx; - if (length) *length = len; - return ser; + REBCNT len = (length && *length) ? *length : VAL_LEN_AT(val); + REBSER *series; + + assert(IS_BINARY(val) || ANY_STRING(val)); + + // !!! This used to check `len == 0` and reuse a zero length string. + // However, the zero length string could have the wrong width. We are + // expected to be returning a BYTE_SIZE() string, and that confused + // things. It's not a good idea to mutate the source string (e.g. + // reallocate under a new width) so consider having an EMPTY_BYTE_STRING + // like EMPTY_ARRAY which is protected to hand back. + // + if ( + IS_BINARY(val) + || ( + VAL_BYTE_SIZE(val) + && All_Bytes_ASCII(VAL_BIN_AT(val), VAL_LEN_AT(val)) + ) + ){ + // + // It's BINARY!, or an ANY-STRING! whose codepoints are all values in + // ASCII (0x00 => 0x7F), hence not needing any UTF-8 encoding. + // + series = VAL_SERIES(val); + ASSERT_SERIES_MANAGED(series); + + if (index) + *index = VAL_INDEX(val); + if (length) + *length = len; + } + else { + // UTF-8 conversion is required, and we manage the result. + + series = Make_UTF8_From_Any_String(val, len, OPT_ENC_CRLF_MAYBE); + MANAGE_SERIES(series); + + #if !defined(NDEBUG) + // + // Also, PROTECT the result in the debug build...because since the + // caller doesn't know if a new series was created or if the initial + // data is being used, they should not be modifying it! (We don't + // want to protect the original data, because we wouldn't know when + // we were allowed to unlock it...there's no later call in this + // model to clean up the series.) + { + DECLARE_LOCAL (protect); + Init_String(protect, series); + + Protect_Value(protect, FLAGIT(PROT_SET)); + + // just a string...not /DEEP...shouldn't need to Uncolor() + } + #endif + + if (index) + *index = 0; + if (length) + *length = SER_LEN(series); + } + + assert(BYTE_SIZE(series)); + return series; } -/*********************************************************************** -** -*/ REBSER *Xandor_Binary(REBCNT action, REBVAL *value, REBVAL *arg) -/* -** Only valid for BINARY data. -** -***********************************************************************/ +// +// Xandor_Binary: C +// +// Only valid for BINARY data. +// +REBSER *Xandor_Binary(REBCNT action, REBVAL *value, REBVAL *arg) { - REBSER *series; - REBYTE *p0 = VAL_BIN_DATA(value); - REBYTE *p1 = VAL_BIN_DATA(arg); - REBYTE *p2; - REBCNT i; - REBCNT mt, t1, t0, t2; - - t0 = VAL_LEN(value); - t1 = VAL_LEN(arg); - - mt = MIN(t0, t1); // smaller array size - // For AND - result is size of shortest input: -// if (action == A_AND || (action == 0 && t1 >= t0)) -// t2 = mt; -// else - t2 = MAX(t0, t1); - - series = Make_Binary(t2); - SERIES_TAIL(series) = t2; - p2 = BIN_HEAD(series); - - switch (action) { - case A_AND: - for (i = 0; i < mt; i++) *p2++ = *p0++ & *p1++; - CLEAR(p2, t2 - mt); - return series; - case A_OR: - for (i = 0; i < mt; i++) *p2++ = *p0++ | *p1++; - break; - case A_XOR: - for (i = 0; i < mt; i++) *p2++ = *p0++ ^ *p1++; - break; - default: - // special bit set case EXCLUDE: - for (i = 0; i < mt; i++) *p2++ = *p0++ & ~*p1++; - if (t0 > t1) memcpy(p2, p0, t0 - t1); // residual from first only - return series; - } - - // Copy the residual: - memcpy(p2, ((t0 > t1) ? p0 : p1), t2 - mt); - return series; + REBSER *series; + REBYTE *p0 = VAL_BIN_AT(value); + REBYTE *p1 = VAL_BIN_AT(arg); + REBYTE *p2; + REBCNT i; + REBCNT mt, t1, t0, t2; + + t0 = VAL_LEN_AT(value); + t1 = VAL_LEN_AT(arg); + + mt = MIN(t0, t1); // smaller array size + // For AND - result is size of shortest input: +// if (action == A_AND || (action == 0 && t1 >= t0)) +// t2 = mt; +// else + t2 = MAX(t0, t1); + + if (IS_BITSET(value)) { + // + // Although bitsets and binaries share some implementation here, + // they have distinct allocation functions...and bitsets need + // to set the REBSER.misc.negated union field (BITS_NOT) as + // it would be illegal to read it if it were cleared via another + // element of the union. + // + assert(IS_BITSET(arg)); + series = Make_Bitset(t2 * 8); + } + else { + // Ordinary binary + // + series = Make_Binary(t2); + SET_SERIES_LEN(series, t2); + } + + p2 = BIN_HEAD(series); + + switch (action) { + case SYM_AND_T: // and~ + for (i = 0; i < mt; i++) *p2++ = *p0++ & *p1++; + CLEAR(p2, t2 - mt); + return series; + + case SYM_OR_T: // or~ + for (i = 0; i < mt; i++) *p2++ = *p0++ | *p1++; + break; + + case SYM_XOR_T: // xor~ + for (i = 0; i < mt; i++) *p2++ = *p0++ ^ *p1++; + break; + + default: + // special bit set case EXCLUDE: + for (i = 0; i < mt; i++) *p2++ = *p0++ & ~*p1++; + if (t0 > t1) memcpy(p2, p0, t0 - t1); // residual from first only + return series; + } + + // Copy the residual: + memcpy(p2, ((t0 > t1) ? p0 : p1), t2 - mt); + return series; } -/*********************************************************************** -** -*/ REBSER *Complement_Binary(REBVAL *value) -/* -** Only valid for BINARY data. -** -***********************************************************************/ +// +// Complement_Binary: C +// +// Only valid for BINARY data. +// +REBSER *Complement_Binary(REBVAL *value) { - REBSER *series; - REBYTE *str = VAL_BIN_DATA(value); - REBINT len = VAL_LEN(value); - REBYTE *out; - - series = Make_Binary(len); - SERIES_TAIL(series) = len; - out = BIN_HEAD(series); - for (; len > 0; len--) - *out++ = ~ *str++; - - return series; + REBSER *series; + REBYTE *str = VAL_BIN_AT(value); + REBINT len = VAL_LEN_AT(value); + REBYTE *out; + + series = Make_Binary(len); + SET_SERIES_LEN(series, len); + out = BIN_HEAD(series); + for (; len > 0; len--) { + *out++ = ~(*str); + ++str; + } + + return series; } -/*********************************************************************** -** -*/ void Shuffle_String(REBVAL *value, REBFLG secure) -/* -** Randomize a string. Return a new string series. -** Handles both BYTE and UNICODE strings. -** -***********************************************************************/ +// +// Shuffle_String: C +// +// Randomize a string. Return a new string series. +// Handles both BYTE and UNICODE strings. +// +void Shuffle_String(REBVAL *value, REBOOL secure) { - REBCNT n; - REBCNT k; - REBSER *series = VAL_SERIES(value); - REBCNT idx = VAL_INDEX(value); - REBUNI swap; - - for (n = VAL_LEN(value); n > 1;) { - k = idx + (REBCNT)Random_Int(secure) % n; - n--; - swap = GET_ANY_CHAR(series, k); - SET_ANY_CHAR(series, k, GET_ANY_CHAR(series, n + idx)); - SET_ANY_CHAR(series, n + idx, swap); - } + REBCNT n; + REBCNT k; + REBSER *series = VAL_SERIES(value); + REBCNT idx = VAL_INDEX(value); + REBUNI swap; + + for (n = VAL_LEN_AT(value); n > 1;) { + k = idx + (REBCNT)Random_Int(secure) % n; + n--; + swap = GET_ANY_CHAR(series, k); + SET_ANY_CHAR(series, k, GET_ANY_CHAR(series, n + idx)); + SET_ANY_CHAR(series, n + idx, swap); + } } -/* -#define SEED_LEN 10 -static REBYTE seed_str[SEED_LEN] = { - 249, 52, 217, 38, 207, 59, 216, 52, 222, 61 // xor "Sassenrath" #{AA55..} -}; -// kp = seed_str; // Any seed constant. -// klen = SEED_LEN; -*/ - -/*********************************************************************** -** -*/ REBOOL Cloak(REBOOL decode, REBYTE *cp, REBCNT dlen, REBYTE *kp, REBCNT klen, REBFLG as_is) -/* -** Simple data scrambler. Quality depends on the key length. -** Result is made in place (data string). -** -** The key (kp) is passed as a REBVAL or REBYTE (when klen is !0). -** -***********************************************************************/ +// +// Trim_Tail: C +// +// Used to trim off hanging spaces during FORM and MOLD. +// +void Trim_Tail(REBSER *src, REBYTE chr) { - REBCNT i, n; - REBYTE src[20]; - REBYTE dst[20]; - - if (dlen == 0) return TRUE; - - // Decode KEY as VALUE field (binary, string, or integer) - if (klen == 0) { - REBVAL *val = (REBVAL*)kp; - REBSER *ser; - - switch (VAL_TYPE(val)) { - case REB_BINARY: - kp = (void*)VAL_BIN_DATA(val); - klen = VAL_LEN(val); - break; - case REB_STRING: - ser = Prep_Bin_Str(val, &i, &klen); // result may be a SHARED BUFFER! - kp = BIN_SKIP(ser, i); - break; - case REB_INTEGER: - INT_TO_STR(VAL_INT64(val), dst); - klen = LEN_BYTES(dst); - as_is = FALSE; - break; - } - - if (klen == 0) return FALSE; - } - - if (!as_is) { - for (i = 0; i < 20; i++) src[i] = kp[i % klen]; - SHA1(src, 20, dst); - klen = 20; - kp = dst; - } - - if (decode) - for (i = dlen-1; i > 0; i--) cp[i] ^= cp[i-1] ^ kp[i % klen]; - - // Change starting byte based all other bytes. - n = 0xa5; - for (i = 1; i < dlen; i++) n += cp[i]; - cp[0] ^= (REBYTE)n; - - if (!decode) - for (i = 1; i < dlen; i++) cp[i] ^= cp[i-1] ^ kp[i % klen]; - - return TRUE; + assert(NOT_SER_FLAG(src, SERIES_FLAG_ARRAY)); + + REBOOL unicode = NOT(BYTE_SIZE(src)); + REBCNT tail; + REBUNI c; + + for (tail = SER_LEN(src); tail > 0; tail--) { + c = unicode ? *UNI_AT(src, tail - 1) : *BIN_AT(src, tail - 1); + if (c != chr) break; + } + SET_SERIES_LEN(src, tail); + TERM_SEQUENCE(src); } -/*********************************************************************** -** -*/ void Trim_Tail(REBSER *src, REBYTE chr) -/* -** Used to trim off hanging spaces during FORM and MOLD. -** -***********************************************************************/ +// +// Deline_Bytes: C +// +// This function converts any combination of CR and +// LF line endings to the internal REBOL line ending. +// The new length of the buffer is returned. +// +REBCNT Deline_Bytes(REBYTE *buf, REBCNT len) { - REBOOL wide = !BYTE_SIZE(src); - REBCNT tail; - REBUNI c; - - for (tail = SERIES_TAIL(src); tail > 0; tail--) { - c = wide ? *UNI_SKIP(src, tail-1) : (REBUNI)*BIN_SKIP(src, tail-1); - if (c != (REBUNI)chr) break; - } - SERIES_TAIL(src) = tail; - TERM_SERIES(src); + REBYTE c, *cp, *tp; + + cp = tp = buf; + while (cp < buf + len) { + if ((c = *cp++) == LF) { + if (*cp == CR) cp++; + } + else if (c == CR) { + c = LF; + if (*cp == LF) cp++; + } + *tp++ = c; + } + *tp = 0; + + return (REBCNT)(tp - buf); } -/*********************************************************************** -** -*/ REBCNT Deline_Bytes(REBYTE *buf, REBCNT len) -/* -** This function converts any combination of CR and -** LF line endings to the internal REBOL line ending. -** The new length of the buffer is returned. -** -***********************************************************************/ +// +// Deline_Uni: C +// +REBCNT Deline_Uni(REBUNI *buf, REBCNT len) { - REBYTE c, *cp, *tp; - - cp = tp = buf; - while (cp < buf + len) { - if ((c = *cp++) == LF) { - if (*cp == CR) cp++; - } - else if (c == CR) { - c = LF; - if (*cp == LF) cp++; - } - *tp++ = c; - } - *tp = 0; - - return (REBCNT)(tp - buf); + REBUNI c, *cp, *tp; + + cp = tp = buf; + while (cp < buf + len) { + if ((c = *cp++) == LF) { + if (*cp == CR) cp++; + } + else if (c == CR) { + c = LF; + if (*cp == LF) cp++; + } + *tp++ = c; + } + *tp = 0; + + return (REBCNT)(tp - buf); } -/*********************************************************************** -** -*/ REBCNT Deline_Uni(REBUNI *buf, REBCNT len) -/* -***********************************************************************/ +// +// Enline_Bytes: C +// +void Enline_Bytes(REBSER *ser, REBCNT idx, REBCNT len) { - REBUNI c, *cp, *tp; - - cp = tp = buf; - while (cp < buf + len) { - if ((c = *cp++) == LF) { - if (*cp == CR) cp++; - } - else if (c == CR) { - c = LF; - if (*cp == LF) cp++; - } - *tp++ = c; - } - *tp = 0; - - return (REBCNT)(tp - buf); + REBCNT cnt = 0; + REBYTE *bp; + REBYTE c = 0; + REBCNT tail; + + // Calculate the size difference by counting the number of LF's + // that have no CR's in front of them. + bp = BIN_AT(ser, idx); + for (; len > 0; len--) { + if (*bp == LF && c != CR) cnt++; + c = *bp++; + } + if (cnt == 0) return; + + // Extend series: + len = SER_LEN(ser); // before expansion + EXPAND_SERIES_TAIL(ser, cnt); + tail = SER_LEN(ser); // after expansion + bp = BIN_HEAD(ser); // expand may change it + + // Add missing CRs: + while (cnt > 0) { + bp[tail--] = bp[len]; // Copy src to dst. + if (bp[len] == LF && (len == 0 || bp[len - 1] != CR)) { + bp[tail--] = CR; + cnt--; + } + len--; + } } -/*********************************************************************** -** -*/ void Enline_Bytes(REBSER *ser, REBCNT idx, REBCNT len) -/* -***********************************************************************/ +// +// Enline_Uni: C +// +void Enline_Uni(REBSER *ser, REBCNT idx, REBCNT len) { - REBCNT cnt = 0; - REBYTE *bp; - REBYTE c = 0; - REBCNT tail; - - // Calculate the size difference by counting the number of LF's - // that have no CR's in front of them. - bp = BIN_SKIP(ser, idx); - for (; len > 0; len--) { - if (*bp == LF && c != CR) cnt++; - c = *bp++; - } - if (cnt == 0) return; - - // Extend series: - len = SERIES_TAIL(ser); // before expansion - EXPAND_SERIES_TAIL(ser, cnt); - tail = SERIES_TAIL(ser); // after expansion - bp = BIN_HEAD(ser); // expand may change it - - // Add missing CRs: - while (cnt > 0) { - bp[tail--] = bp[len]; // Copy src to dst. - if (bp[len--] == LF && bp[len] != CR) { - bp[tail--] = CR; - cnt--; - } - } + REBCNT cnt = 0; + REBUNI *bp; + REBUNI c = 0; + REBCNT tail; + + // Calculate the size difference by counting the number of LF's + // that have no CR's in front of them. + bp = UNI_AT(ser, idx); + for (; len > 0; len--) { + if (*bp == LF && c != CR) cnt++; + c = *bp++; + } + if (cnt == 0) return; + + // Extend series: + len = SER_LEN(ser); // before expansion + EXPAND_SERIES_TAIL(ser, cnt); + tail = SER_LEN(ser); // after expansion + bp = UNI_HEAD(ser); // expand may change it + + // Add missing CRs: + while (cnt > 0) { + bp[tail--] = bp[len]; // Copy src to dst. + if (bp[len] == LF && (len == 0 || bp[len - 1] != CR)) { + bp[tail--] = CR; + cnt--; + } + len--; + } } -/*********************************************************************** -** -*/ void Enline_Uni(REBSER *ser, REBCNT idx, REBCNT len) -/* -***********************************************************************/ +// +// Entab_Bytes: C +// +// Entab a string and return a new series. +// +REBSER *Entab_Bytes(REBYTE *bp, REBCNT index, REBCNT len, REBINT tabsize) { - REBCNT cnt = 0; - REBUNI *bp; - REBUNI c = 0; - REBCNT tail; - - // Calculate the size difference by counting the number of LF's - // that have no CR's in front of them. - bp = UNI_SKIP(ser, idx); - for (; len > 0; len--) { - if (*bp == LF && c != CR) cnt++; - c = *bp++; - } - if (cnt == 0) return; - - // Extend series: - len = SERIES_TAIL(ser); // before expansion - EXPAND_SERIES_TAIL(ser, cnt); - tail = SERIES_TAIL(ser); // after expansion - bp = UNI_HEAD(ser); // expand may change it - - // Add missing CRs: - while (cnt > 0) { - bp[tail--] = bp[len]; // Copy src to dst. - if (bp[len--] == LF && bp[len] != CR) { - bp[tail--] = CR; - cnt--; - } - } + REBINT n = 0; + REBYTE *dp; + REBYTE c; + + dp = Reset_Buffer(BYTE_BUF, len); + + for (; index < len; index++) { + + c = bp[index]; + + // Count leading spaces, insert TAB for each tabsize: + if (c == ' ') { + if (++n >= tabsize) { + *dp++ = '\t'; + n = 0; + } + continue; + } + + // Hitting a leading TAB resets space counter: + if (c == '\t') { + *dp++ = (REBYTE)c; + n = 0; + } + else { + // Incomplete tab space, pad with spaces: + for (; n > 0; n--) *dp++ = ' '; + + // Copy chars thru end-of-line (or end of buffer): + while (index < len) { + if ((*dp++ = bp[index++]) == '\n') break; + } + } + } + + return Copy_Buffer(BYTE_BUF, 0, dp); } -/*********************************************************************** -** -*/ REBSER *Entab_Bytes(REBYTE *bp, REBCNT index, REBCNT len, REBINT tabsize) -/* -** Entab a string and return a new series. -** -***********************************************************************/ +// +// Entab_Unicode: C +// +// Entab a string and return a new series. +// +REBSER *Entab_Unicode(REBUNI *bp, REBCNT index, REBCNT len, REBINT tabsize) { - REBINT n = 0; - REBYTE *dp; - REBYTE c; - - dp = Reset_Buffer(BUF_FORM, len); - - for (; index < len; index++) { - - c = bp[index]; - - // Count leading spaces, insert TAB for each tabsize: - if (c == ' ') { - if (++n >= tabsize) { - *dp++ = '\t'; - n = 0; - } - continue; - } - - // Hitting a leading TAB resets space counter: - if (c == '\t') { - *dp++ = (REBYTE)c; - n = 0; - } - else { - // Incomplete tab space, pad with spaces: - for (; n > 0; n--) *dp++ = ' '; - - // Copy chars thru end-of-line (or end of buffer): - while (index < len) { - if ((*dp++ = bp[index++]) == '\n') break; - } - } - } - - return Copy_Buffer(BUF_FORM, dp); + REBINT n = 0; + REBUNI *dp; + REBUNI c; + + REB_MOLD mo; + CLEARS(&mo); + mo.opts = MOPT_RESERVE; + mo.reserve = len; + + Push_Mold(&mo); + dp = UNI_AT(mo.series, mo.start); + + for (; index < len; index++) { + + c = bp[index]; + + // Count leading spaces, insert TAB for each tabsize: + if (c == ' ') { + if (++n >= tabsize) { + *dp++ = '\t'; + n = 0; + } + continue; + } + + // Hitting a leading TAB resets space counter: + if (c == '\t') { + *dp++ = (REBYTE)c; + n = 0; + } + else { + // Incomplete tab space, pad with spaces: + for (; n > 0; n--) *dp++ = ' '; + + // Copy chars thru end-of-line (or end of buffer): + while (index < len) { + if ((*dp++ = bp[index++]) == '\n') break; + } + } + } + + TERM_UNI_LEN( + mo.series, + mo.start + cast(REBCNT, dp - UNI_AT(mo.series, mo.start)) + ); + + return Pop_Molded_String(&mo); } -/*********************************************************************** -** -*/ REBSER *Entab_Unicode(REBUNI *bp, REBCNT index, REBCNT len, REBINT tabsize) -/* -** Entab a string and return a new series. -** -***********************************************************************/ +// +// Detab_Bytes: C +// +// Detab a string and return a new series. +// +REBSER *Detab_Bytes(REBYTE *bp, REBCNT index, REBCNT len, REBINT tabsize) { - REBINT n = 0; - REBUNI *dp; - REBUNI c; - - dp = (REBUNI *)Reset_Buffer(BUF_MOLD, len); - - for (; index < len; index++) { - - c = bp[index]; - - // Count leading spaces, insert TAB for each tabsize: - if (c == ' ') { - if (++n >= tabsize) { - *dp++ = '\t'; - n = 0; - } - continue; - } - - // Hitting a leading TAB resets space counter: - if (c == '\t') { - *dp++ = (REBYTE)c; - n = 0; - } - else { - // Incomplete tab space, pad with spaces: - for (; n > 0; n--) *dp++ = ' '; - - // Copy chars thru end-of-line (or end of buffer): - while (index < len) { - if ((*dp++ = bp[index++]) == '\n') break; - } - } - } - - return Copy_Buffer(BUF_MOLD, dp); -} + REBCNT cnt = 0; + REBCNT n; + REBYTE *dp; + REBYTE c; + // Estimate new length based on tab expansion: + for (n = index; n < len; n++) + if (bp[n] == '\t') // tab character + ++cnt; -/*********************************************************************** -** -*/ REBSER *Detab_Bytes(REBYTE *bp, REBCNT index, REBCNT len, REBINT tabsize) -/* -** Detab a string and return a new series. -** -***********************************************************************/ -{ - REBCNT cnt = 0; - REBCNT n; - REBYTE *dp; - REBYTE c; - - // Estimate new length based on tab expansion: - for (n = index; n < len; n++) - if (bp[n] == TAB) cnt++; + dp = Reset_Buffer(BYTE_BUF, len + (cnt * (tabsize-1))); - dp = Reset_Buffer(BUF_FORM, len + (cnt * (tabsize-1))); + n = 0; + while (index < len) { - n = 0; - while (index < len) { + c = bp[index++]; - c = bp[index++]; + if (c == '\t') { + *dp++ = ' '; + n++; + for (; n % tabsize != 0; n++) *dp++ = ' '; + continue; + } - if (c == '\t') { - *dp++ = ' '; - n++; - for (; n % tabsize != 0; n++) *dp++ = ' '; - continue; - } + if (c == '\n') n = 0; + else n++; - if (c == '\n') n = 0; - else n++; + *dp++ = c; + } - *dp++ = c; - } - - return Copy_Buffer(BUF_FORM, dp); + return Copy_Buffer(BYTE_BUF, 0, dp); } -/*********************************************************************** -** -*/ REBSER *Detab_Unicode(REBUNI *bp, REBCNT index, REBCNT len, REBINT tabsize) -/* -** Detab a unicode string and return a new series. -** -***********************************************************************/ +// +// Detab_Unicode: C +// +// Detab a unicode string and return a new series. +// +REBSER *Detab_Unicode(REBUNI *bp, REBCNT index, REBCNT len, REBINT tabsize) { - REBCNT cnt = 0; - REBCNT n; - REBUNI *dp; - REBUNI c; + REBCNT cnt = 0; + REBCNT n; + REBUNI *dp; + REBUNI c; + + REB_MOLD mo; + CLEARS(&mo); + + // Estimate new length based on tab expansion: + for (n = index; n < len; n++) + if (bp[n] == '\t') // tab character + ++cnt; - // Estimate new length based on tab expansion: - for (n = index; n < len; n++) - if (bp[n] == TAB) cnt++; + mo.opts = MOPT_RESERVE; + mo.reserve = len + (cnt * (tabsize - 1)); - dp = (REBUNI *)Reset_Buffer(BUF_MOLD, len + (cnt * (tabsize-1))); + Push_Mold(&mo); + dp = UNI_AT(mo.series, mo.start); + n = 0; + while (index < len) { - n = 0; - while (index < len) { + c = bp[index++]; - c = bp[index++]; + if (c == '\t') { + *dp++ = ' '; + n++; + for (; n % tabsize != 0; n++) *dp++ = ' '; + continue; + } - if (c == '\t') { - *dp++ = ' '; - n++; - for (; n % tabsize != 0; n++) *dp++ = ' '; - continue; - } + if (c == '\n') n = 0; + else n++; - if (c == '\n') n = 0; - else n++; + *dp++ = c; + } - *dp++ = c; - } + TERM_UNI_LEN( + mo.series, + mo.start + cast(REBCNT, dp - UNI_AT(mo.series, mo.start)) + ); - return Copy_Buffer(BUF_MOLD, dp); + return Pop_Molded_String(&mo); } -/*********************************************************************** -** -*/ void Change_Case(REBVAL *ds, REBVAL *val, REBVAL *part, REBOOL upper) -/* -** Common code for string case handling. -** -***********************************************************************/ +// +// Change_Case: C +// +// Common code for string case handling. +// +void Change_Case(REBVAL *out, REBVAL *val, REBVAL *part, REBOOL upper) { - REBCNT len; - REBCNT n; - - *D_RET = *val; - - if (IS_CHAR(val)) { - REBUNI c = VAL_CHAR(val); - if (c < UNICODE_CASES) { - c = upper ? UP_CASE(c) : LO_CASE(c); - } - VAL_CHAR(D_RET) = c; - return; - } - - // String series: - - if (IS_PROTECT_SERIES(VAL_SERIES(val))) Trap0(RE_PROTECTED); - - len = Partial(val, 0, part, 0); - n = VAL_INDEX(val); - len += n; - - if (VAL_BYTE_SIZE(val)) { - REBYTE *bp = VAL_BIN(val); - if (upper) - for (; n < len; n++) bp[n] = (REBYTE)UP_CASE(bp[n]); - else { - for (; n < len; n++) bp[n] = (REBYTE)LO_CASE(bp[n]); - } - } else { - REBUNI *up = VAL_UNI(val); - if (upper) { - for (; n < len; n++) { - if (up[n] < UNICODE_CASES) up[n] = UP_CASE(up[n]); - } - } - else { - for (; n < len; n++) { - if (up[n] < UNICODE_CASES) up[n] = LO_CASE(up[n]); - } - } - } + REBCNT len; + REBCNT n; + + Move_Value(out, val); + + if (IS_CHAR(val)) { + REBUNI c = VAL_CHAR(val); + if (c < UNICODE_CASES) { + c = upper ? UP_CASE(c) : LO_CASE(c); + } + VAL_CHAR(out) = c; + return; + } + + // String series: + + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(val)); + + len = Partial(val, 0, part); + n = VAL_INDEX(val); + len += n; + + if (VAL_BYTE_SIZE(val)) { + REBYTE *bp = VAL_BIN(val); + if (upper) + for (; n < len; n++) bp[n] = (REBYTE)UP_CASE(bp[n]); + else { + for (; n < len; n++) bp[n] = (REBYTE)LO_CASE(bp[n]); + } + } else { + REBUNI *up = VAL_UNI(val); + if (upper) { + for (; n < len; n++) { + if (up[n] < UNICODE_CASES) up[n] = UP_CASE(up[n]); + } + } + else { + for (; n < len; n++) { + if (up[n] < UNICODE_CASES) up[n] = LO_CASE(up[n]); + } + } + } } -/*********************************************************************** -** -*/ REBSER *Split_Lines(REBVAL *val) -/* -** Given a string series, split lines on CR-LF. -** Series can be bytes or Unicode. -** -***********************************************************************/ +// +// Split_Lines: C +// +// Given a string series, split lines on CR-LF. +// Series can be bytes or Unicode. +// +REBARR *Split_Lines(REBVAL *str) { - REBSER *ser = BUF_EMIT; // GC protected (because it is emit buffer) - REBSER *str = VAL_SERIES(val); - REBCNT len = VAL_LEN(val); - REBCNT idx = VAL_INDEX(val); - REBCNT start = idx; - REBSER *out; - REBCHR c; - - BLK_RESET(ser); - - while (idx < len) { - c = GET_ANY_CHAR(str, idx); - if (c == LF || c == CR) { - out = Copy_String(str, start, idx - start); - val = Append_Value(ser); - SET_STRING(val, out); - VAL_SET_LINE(val); - idx++; - if (c == CR && GET_ANY_CHAR(str, idx) == LF) idx++; - start = idx; - } - else idx++; - } - // Possible remainder (no terminator) - if (idx > start) { - out = Copy_String(str, start, idx - start); - val = Append_Value(ser); - SET_STRING(val, out); - VAL_SET_LINE(val); - } - - return Copy_Block(ser, 0); + REBDSP dsp_orig = DSP; + + REBSER *s = VAL_SERIES(str); + REBCNT len = VAL_LEN_AT(str); + REBCNT i = VAL_INDEX(str); + + REBCNT start = i; + + while (i < len) { + REBUNI c = GET_ANY_CHAR(s, i); + if (c == LF || c == CR) { + DS_PUSH_TRASH; + Init_String( + DS_TOP, + Copy_String_Slimming(s, start, i - start) + ); + SET_VAL_FLAG(DS_TOP, VALUE_FLAG_LINE); + ++i; + if (c == CR && GET_ANY_CHAR(s, i) == LF) + ++i; + start = i; + } + else + ++i; + } + // Possible remainder (no terminator) + if (i > start) { + DS_PUSH_TRASH; + Init_String( + DS_TOP, + Copy_String_Slimming(s, start, i - start) + ); + SET_VAL_FLAG(DS_TOP, VALUE_FLAG_LINE); + } + + return Pop_Stack_Values(dsp_orig); } diff --git a/src/core/s-trim.c b/src/core/s-trim.c index 4ee6e0ca66..96aaac4636 100644 --- a/src/core/s-trim.c +++ b/src/core/s-trim.c @@ -1,295 +1,279 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: s-trim.c -** Summary: string trimming -** Section: strings -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %s-trim.c +// Summary: "string trimming" +// Section: strings +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -static REBFLG find_in_uni(REBUNI *up, REBINT len, REBUNI c) +static REBOOL find_in_uni(REBUNI *up, REBINT len, REBUNI c) { - while (len-- > 0) if (*up++ == c) return TRUE; - return FALSE; + while (len-- > 0) if (*up++ == c) return TRUE; + return FALSE; } -/*********************************************************************** -** -*/ static void replace_with(REBSER *ser, REBCNT index, REBCNT tail, REBVAL *with) -/* -** Replace whitespace chars that match WITH string. -** -** Resulting string is always smaller than it was to start. -** -***********************************************************************/ -{ - #define MAX_WITH 32 - REBCNT wlen; - REBUNI with_chars[MAX_WITH]; // chars to be trimmed - REBUNI *up = with_chars; - REBYTE *bp; - REBCNT n; - REBUNI uc; - - // Setup WITH array from arg or the default: - n = 0; - if (IS_NONE(with)) { - bp = "\n \r\t"; - wlen = n = 4; - } - else if (IS_CHAR(with)) { - wlen = 1; - *up++ = VAL_CHAR(with); - } - else if (IS_INTEGER(with)) { - wlen = 1; - *up++ = Int32s(with, 0); - } - else if (ANY_BINSTR(with)) { - n = VAL_LEN(with); - if (n >= MAX_WITH) n = MAX_WITH-1; - wlen = n; - if (VAL_BYTE_SIZE(with)) { - bp = VAL_BIN_DATA(with); - } else { - memcpy(up, VAL_UNI_DATA(with), n * sizeof(REBUNI)); - n = 0; - } - } - for (; n > 0; n--) *up++ = (REBUNI)*bp++; - - // Remove all occurances of chars found in WITH string: - for (n = index; index < tail; index++) { - uc = GET_ANY_CHAR(ser, index); - if (!find_in_uni(with_chars, wlen, uc)) { - SET_ANY_CHAR(ser, n, uc); - n++; - } - } - - SET_ANY_CHAR(ser, n, 0); - SERIES_TAIL(ser) = n; -} - - -/*********************************************************************** -** -*/ static void trim_auto(REBSER *ser, REBCNT index, REBCNT tail) -/* -** Skip any blank lines and then determine indent of -** first line and make the rest align with it. -** -** BUG!!! If the indentation uses TABS, then it could -** fill past the source pointer! -** -***********************************************************************/ -{ - REBCNT out = index; - REBCNT line; - REBCNT len; - REBCNT indent; - REBUNI uc = 0; - - // Skip whitespace, remember start of last line: - for (line = index; index < tail; index++) { - uc = GET_ANY_CHAR(ser, index); - if (!IS_WHITE(uc)) break; - if (uc == LF) line = index+1; - } - - // Count the indentation used: - for (indent = 0; line < index; line++) { - if (GET_ANY_CHAR(ser, line) == ' ') indent++; - else indent = (indent + TAB_SIZE) & ~3; - } - - // For each line, pad with necessary indentation: - while (index < tail) { - // Skip to next content, track indentation: - for (len = 0; index < tail; index++) { - uc = GET_ANY_CHAR(ser, index); - if (!IS_SPACE(uc) || len >= indent) break; - if (uc == ' ') len++; - else len = (len + TAB_SIZE) & ~3; - } - - // Indent the line: - for (; len > indent; len--) { - SET_ANY_CHAR(ser, out, ' '); - out++; - } - - // Copy line contents: - while (index < tail) { - uc = GET_ANY_CHAR(ser, index); - SET_ANY_CHAR(ser, out, uc); - out++; - index++; - if (uc == LF) break; - } - } - - SET_ANY_CHAR(ser, out, 0); - SERIES_TAIL(ser) = out; +// +// Whitespace_Replace_With: C +// +// Replace whitespace chars that match WITH string. +// +// Resulting string is always smaller than it was to start. +// +void Whitespace_Replace_With( + REBSER *ser, + REBCNT index, + REBCNT tail, + const REBVAL *with +) { + #define MAX_WITH 32 + REBCNT wlen; + REBUNI with_chars[MAX_WITH]; // chars to be trimmed + REBUNI *up = with_chars; + const REBYTE *bp; + REBCNT n; + REBUNI uc; + + // Setup WITH array from arg or the default: + n = 0; + if (IS_VOID(with)) { + bp = cb_cast("\n \r\t"); + wlen = n = 4; + } + else if (IS_CHAR(with)) { + wlen = 1; + *up++ = VAL_CHAR(with); + } + else if (IS_INTEGER(with)) { + wlen = 1; + *up++ = Int32s(with, 0); + } + else { + assert(ANY_BINSTR(with)); + n = VAL_LEN_AT(with); + if (n >= MAX_WITH) n = MAX_WITH-1; + wlen = n; + if (VAL_BYTE_SIZE(with)) { + bp = VAL_BIN_AT(with); + } else { + memcpy(up, VAL_UNI_AT(with), n * sizeof(REBUNI)); + n = 0; + } + } + + for (; n > 0; n--) *up++ = (REBUNI)*bp++; + + // Remove all occurances of chars found in WITH string: + for (n = index; index < tail; index++) { + uc = GET_ANY_CHAR(ser, index); + if (!find_in_uni(with_chars, wlen, uc)) { + SET_ANY_CHAR(ser, n, uc); + n++; + } + } + + SET_ANY_CHAR(ser, n, 0); + SET_SERIES_LEN(ser, n); } -/*********************************************************************** -** -*/ static void trim_lines(REBSER *ser, REBCNT index, REBCNT tail) -/* -** Remove all newlines and extra space. -** -***********************************************************************/ +// +// Trim_String_Auto: C +// +// Skip any blank lines and then determine indent of +// first line and make the rest align with it. +// +// BUG!!! If the indentation uses TABS, then it could +// fill past the source pointer! +// +void Trim_String_Auto(REBSER *ser, REBCNT index, REBCNT tail) { - REBINT pad = 1; // used to allow a single space - REBUNI uc; - REBCNT out = index; - - for (; index < tail; index++) { - uc = GET_ANY_CHAR(ser, index); - if (IS_WHITE(uc)) { - uc = ' '; - if (!pad) { - SET_ANY_CHAR(ser, out, uc); - out++; - pad = 2; - } - } - else { - SET_ANY_CHAR(ser, out, uc); - out++; - pad = 0; - } - } - - // Remove extra end pad if found: - if (pad == 2) out--; - - SET_ANY_CHAR(ser, out, 0); - SERIES_TAIL(ser) = out; + REBCNT out = index; + REBCNT line; + REBCNT len; + REBCNT indent; + REBUNI uc = 0; + + // Skip whitespace, remember start of last line: + for (line = index; index < tail; index++) { + uc = GET_ANY_CHAR(ser, index); + if (!IS_WHITE(uc)) break; + if (uc == LF) line = index+1; + } + + // Count the indentation used: + for (indent = 0; line < index; line++) { + if (GET_ANY_CHAR(ser, line) == ' ') indent++; + else indent = (indent + TAB_SIZE) & ~3; + } + + // For each line, pad with necessary indentation: + while (index < tail) { + // Skip to next content, track indentation: + for (len = 0; index < tail; index++) { + uc = GET_ANY_CHAR(ser, index); + if (!IS_SPACE(uc) || len >= indent) break; + if (uc == ' ') len++; + else len = (len + TAB_SIZE) & ~3; + } + + // Indent the line: + for (; len > indent; len--) { + SET_ANY_CHAR(ser, out, ' '); + out++; + } + + // Copy line contents: + while (index < tail) { + uc = GET_ANY_CHAR(ser, index); + SET_ANY_CHAR(ser, out, uc); + out++; + index++; + if (uc == LF) break; + } + } + + SET_ANY_CHAR(ser, out, 0); + SET_SERIES_LEN(ser, out); } -/*********************************************************************** -** -*/ static void trim_head_tail(REBSER *ser, REBCNT index, REBCNT tail, REBFLG h, REBFLG t) -/* -** Trim from head and tail of each line, trim any leading or -** trailing lines as well, leaving one at the end if present -** -***********************************************************************/ +// +// Trim_String_Lines: C +// +// Remove all newlines and extra space. +// +void Trim_String_Lines(REBSER *ser, REBCNT index, REBCNT tail) { - REBCNT out = index; - REBOOL append_line_feed = FALSE; - REBUNI uc; - - // Skip head lines if required: - if (h || !t) { - for (; index < tail; index++) { - uc = GET_ANY_CHAR(ser, index); - if (!IS_WHITE(uc)) break; - } - } - - // Skip tail lines if required: - if (t || !h) { - for (; index < tail; tail--) { - uc = GET_ANY_CHAR(ser, tail -1); - if (uc == LF) append_line_feed = TRUE; - if (!IS_WHITE(uc)) break; - } - } - - // Trim head and tail of innner lines if required: - if (!h && !t) { - REBOOL outside = FALSE; // inside an inner line - REBCNT left = 0; // index of leftmost space (in output) - - for (; index < tail; index++) { - - uc = GET_ANY_CHAR(ser, index); - - if (IS_SPACE(uc)) { - if (outside) continue; - if (!left) left = out; - } - else if (uc == LF) { - outside = TRUE; - if (left) out = left, left = 0; - } - else { - outside = FALSE; - left = 0; - } - - SET_ANY_CHAR(ser, out, uc); - out++; - } - } - else { - for (; index < tail; index++) { - uc = GET_ANY_CHAR(ser, index); - SET_ANY_CHAR(ser, out, uc); - out++; - } - } - - // Append line feed if necessary - if (append_line_feed && !t) { - SET_ANY_CHAR(ser, out, LF); - out++; - } - - SET_ANY_CHAR(ser, out, 0); - SERIES_TAIL(ser) = out; + REBINT pad = 1; // used to allow a single space + REBUNI uc; + REBCNT out = index; + + for (; index < tail; index++) { + uc = GET_ANY_CHAR(ser, index); + if (IS_WHITE(uc)) { + uc = ' '; + if (!pad) { + SET_ANY_CHAR(ser, out, uc); + out++; + pad = 2; + } + } + else { + SET_ANY_CHAR(ser, out, uc); + out++; + pad = 0; + } + } + + // Remove extra end pad if found: + if (pad == 2) out--; + + SET_ANY_CHAR(ser, out, 0); + SET_SERIES_LEN(ser, out); } -/*********************************************************************** -** -*/ void Trim_String(REBSER *ser, REBCNT index, REBCNT len, REBCNT flags, REBVAL *with) -/* -***********************************************************************/ -{ - REBCNT tail = index + len; - - // /all or /with - if (flags & (AM_TRIM_ALL | AM_TRIM_WITH)) { - replace_with(ser, index, tail, with); - } - // /auto option - else if (flags & AM_TRIM_AUTO) { - trim_auto(ser, index, tail); - } - // /lines option - else if (flags & AM_TRIM_LINES) { - trim_lines(ser, index, tail); - } - else { - trim_head_tail(ser, index, tail, flags & AM_TRIM_HEAD, flags & AM_TRIM_TAIL); - } +// +// Trim_String_Head_Tail: C +// +// Trim from head and tail of each line, trim any leading or +// trailing lines as well, leaving one at the end if present +// +void Trim_String_Head_Tail( + REBSER *ser, + REBCNT index, + REBCNT tail, + REBOOL h, + REBOOL t +) { + REBCNT out = index; + REBOOL append_line_feed = FALSE; + REBUNI uc; + if (tail == index){ + return; + } + // Skip head lines if required: + if (h || !t) { + for (; index < tail; index++) { + uc = GET_ANY_CHAR(ser, index); + if (!IS_WHITE(uc)) break; + } + } + + // Skip tail lines if required: + if (t || !h) { + for (; index < tail; tail--) { + uc = GET_ANY_CHAR(ser, tail -1); + if (uc == LF) append_line_feed = TRUE; + if (!IS_WHITE(uc)) break; + } + } + + // Trim head and tail of innner lines if required: + if (!h && !t) { + REBOOL outside = FALSE; // inside an inner line + REBCNT left = 0; // index of leftmost space (in output) + + for (; index < tail; index++) { + + uc = GET_ANY_CHAR(ser, index); + + if (IS_SPACE(uc)) { + if (outside) continue; + if (!left) left = out; + } + else if (uc == LF) { + outside = TRUE; + if (left) out = left, left = 0; + } + else { + outside = FALSE; + left = 0; + } + + SET_ANY_CHAR(ser, out, uc); + out++; + } + } + else { + for (; index < tail; index++) { + uc = GET_ANY_CHAR(ser, index); + SET_ANY_CHAR(ser, out, uc); + out++; + } + } + + // Append line feed if necessary + if (append_line_feed && !t) { + SET_ANY_CHAR(ser, out, LF); + out++; + } + + SET_ANY_CHAR(ser, out, 0); + SET_SERIES_LEN(ser, out); } diff --git a/src/core/s-unicode.c b/src/core/s-unicode.c index c6383ae9db..0e97249520 100644 --- a/src/core/s-unicode.c +++ b/src/core/s-unicode.c @@ -1,39 +1,40 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: s-unicode.c -** Summary: unicode support functions -** Section: strings -** Author: Carl Sassenrath -** Notes: -** The top part of this code is from Unicode Inc. The second -** part was added by REBOL Technologies. -** -***********************************************************************/ +// +// Rebol 3 Language Interpreter and Run-time Environment +// "Ren-C" branch @ https://github.com/metaeducation/ren-c +// REBOL is a trademark of REBOL Technologies +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Project: Rebol 3 Interpreter and Run-time (Ren-C branch) +// Homepage: https://github.com/metaeducation/ren-c/ +// File: %s-unicode.c +// Summary: unicode support functions +// Section: strings +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The top part of this code is from Unicode Inc. The second +// part was added by REBOL Technologies. +// /* * Copyright 2001-2004 Unicode, Inc. - * + * * Disclaimer - * + * * This source code is provided as is by Unicode, Inc. No claims are * made as to fitness for any particular purpose. No warranties of any * kind are expressed or implied. The recipient agrees to determine @@ -41,9 +42,9 @@ * purchased on magnetic or optical media from Unicode, Inc., the * sole remedy for any claim will be exchange of defective media * within 90 days of receipt. - * + * * Limitations on Rights to Redistribute This Code - * + * * Unicode, Inc. hereby grants the right to freely use the information * supplied in this file in the creation of products supporting the * Unicode Standard, and to make copies of this file in any form @@ -53,60 +54,60 @@ /* --------------------------------------------------------------------- - Conversions between UTF32, UTF-16, and UTF-8. Header file. - - Several funtions are included here, forming a complete set of - conversions between the three formats. UTF-7 is not included - here, but is handled in a separate source file. - - Each of these routines takes pointers to input buffers and output - buffers. The input buffers are const. - - Each routine converts the text between *sourceStart and sourceEnd, - putting the result into the buffer between *targetStart and - targetEnd. Note: the end pointers are *after* the last item: e.g. - *(sourceEnd - 1) is the last item. - - The return result indicates whether the conversion was successful, - and if not, whether the problem was in the source or target buffers. - (Only the first encountered problem is indicated.) - - After the conversion, *sourceStart and *targetStart are both - updated to point to the end of last text successfully converted in - the respective buffers. - - Input parameters: - sourceStart - pointer to a pointer to the source buffer. - The contents of this are modified on return so that - it points at the next thing to be converted. - targetStart - similarly, pointer to pointer to the target buffer. - sourceEnd, targetEnd - respectively pointers to the ends of the - two buffers, for overflow checking only. - - These conversion functions take a ConversionFlags argument. When this - flag is set to strict, both irregular sequences and isolated surrogates - will cause an error. When the flag is set to lenient, both irregular - sequences and isolated surrogates are converted. - - Whether the flag is strict or lenient, all illegal sequences will cause - an error return. This includes sequences such as: , , - or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code - must check for illegal sequences. - - When the flag is set to lenient, characters over 0x10FFFF are converted - to the replacement character; otherwise (when the flag is set to strict) - they constitute an error. - - Output parameters: - The value "sourceIllegal" is returned from some routines if the input - sequence is malformed. When "sourceIllegal" is returned, the source - value will point to the illegal value that caused the problem. E.g., - in UTF-8 when a sequence is malformed, it points to the start of the - malformed sequence. - - Author: Mark E. Davis, 1994. - Rev History: Rick McGowan, fixes & updates May 2001. - Fixes & updates, Sept 2001. + Conversions between UTF32, UTF-16, and UTF-8. Header file. + + Several funtions are included here, forming a complete set of + conversions between the three formats. UTF-7 is not included + here, but is handled in a separate source file. + + Each of these routines takes pointers to input buffers and output + buffers. The input buffers are const. + + Each routine converts the text between *sourceStart and sourceEnd, + putting the result into the buffer between *targetStart and + targetEnd. Note: the end pointers are *after* the last item: e.g. + *(sourceEnd - 1) is the last item. + + The return result indicates whether the conversion was successful, + and if not, whether the problem was in the source or target buffers. + (Only the first encountered problem is indicated.) + + After the conversion, *sourceStart and *targetStart are both + updated to point to the end of last text successfully converted in + the respective buffers. + + Input parameters: + sourceStart - pointer to a pointer to the source buffer. + The contents of this are modified on return so that + it points at the next thing to be converted. + targetStart - similarly, pointer to pointer to the target buffer. + sourceEnd, targetEnd - respectively pointers to the ends of the + two buffers, for overflow checking only. + + These conversion functions take a ConversionFlags argument. When this + flag is set to strict, both irregular sequences and isolated surrogates + will cause an error. When the flag is set to lenient, both irregular + sequences and isolated surrogates are converted. + + Whether the flag is strict or lenient, all illegal sequences will cause + an error return. This includes sequences such as: , , + or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code + must check for illegal sequences. + + When the flag is set to lenient, characters over 0x10FFFF are converted + to the replacement character; otherwise (when the flag is set to strict) + they constitute an error. + + Output parameters: + The value "sourceIllegal" is returned from some routines if the input + sequence is malformed. When "sourceIllegal" is returned, the source + value will point to the illegal value that caused the problem. E.g., + in UTF-8 when a sequence is malformed, it points to the start of the + malformed sequence. + + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Fixes & updates, Sept 2001. ------------------------------------------------------------------------ */ @@ -114,17 +115,17 @@ /* --------------------------------------------------------------------- - The following 4 definitions are compiler-specific. - The C standard does not guarantee that wchar_t has at least - 16 bits, so wchar_t is no less portable than unsigned short! - All should be unsigned values to avoid sign extension during - bit mask & shift operations. + The following 4 definitions are compiler-specific. + The C standard does not guarantee that wchar_t has at least + 16 bits, so wchar_t is no less portable than unsigned short! + All should be unsigned values to avoid sign extension during + bit mask & shift operations. ------------------------------------------------------------------------ */ -typedef unsigned long UTF32; /* at least 32 bits */ -typedef unsigned short UTF16; /* at least 16 bits */ -typedef unsigned char UTF8; /* typically 8 bits */ -typedef unsigned char Boolean; /* 0 or 1 */ +typedef unsigned long UTF32; /* at least 32 bits */ +typedef unsigned short UTF16; /* at least 16 bits */ +typedef unsigned char UTF8; /* typically 8 bits */ +typedef unsigned char Boolean; /* 0 or 1 */ /* Some fundamental constants */ #define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD @@ -134,77 +135,73 @@ typedef unsigned char Boolean; /* 0 or 1 */ #define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF typedef enum { - conversionOK, /* conversion successful */ - sourceExhausted, /* partial character in source, but hit end */ - targetExhausted, /* insuff. room in target for conversion */ - sourceIllegal /* source sequence is illegal/malformed */ + conversionOK, /* conversion successful */ + sourceExhausted, /* partial character in source, but hit end */ + targetExhausted, /* insuff. room in target for conversion */ + sourceIllegal /* source sequence is illegal/malformed */ } ConversionResult; typedef enum { - strictConversion = 0, - lenientConversion + strictConversion = 0, + lenientConversion } ConversionFlags; ConversionResult ConvertUTF8toUTF16 ( - const UTF8** sourceStart, const UTF8* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); ConversionResult ConvertUTF16toUTF8 ( - const UTF16** sourceStart, const UTF16* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); - + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); + ConversionResult ConvertUTF8toUTF32 ( - const UTF8** sourceStart, const UTF8* sourceEnd, - UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); ConversionResult ConvertUTF32toUTF8 ( - const UTF32** sourceStart, const UTF32* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); - + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); + ConversionResult ConvertUTF16toUTF32 ( - const UTF16** sourceStart, const UTF16* sourceEnd, - UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); ConversionResult ConvertUTF32toUTF16 ( - const UTF32** sourceStart, const UTF32* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags); Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd); /* --------------------------------------------------------------------- - Conversions between UTF32, UTF-16, and UTF-8. Source code file. - Author: Mark E. Davis, 1994. - Rev History: Rick McGowan, fixes & updates May 2001. - Sept 2001: fixed const & error conditions per - mods suggested by S. Parent & A. Lillich. - June 2002: Tim Dodd added detection and handling of incomplete - source sequences, enhanced error detection, added casts - to eliminate compiler warnings. - July 2003: slight mods to back out aggressive FFFE detection. - Jan 2004: updated switches in from-UTF8 conversions. - Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. + Conversions between UTF32, UTF-16, and UTF-8. Source code file. + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Sept 2001: fixed const & error conditions per + mods suggested by S. Parent & A. Lillich. + June 2002: Tim Dodd added detection and handling of incomplete + source sequences, enhanced error detection, added casts + to eliminate compiler warnings. + July 2003: slight mods to back out aggressive FFFE detection. + Jan 2004: updated switches in from-UTF8 conversions. + Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. - See the header file "ConvertUTF.h" for complete documentation. + See the header file "ConvertUTF.h" for complete documentation. ------------------------------------------------------------------------ */ #ifdef CVTUTF_DEBUG -#include +// #include // !!! No in Ren-C release builds #endif -static const int halfShift = 10; /* used for shifting by 10 bits */ - -static const UTF32 halfBase = 0x0010000UL; -static const UTF32 halfMask = 0x3FFUL; #define UNI_SUR_HIGH_START (UTF32)0xD800 #define UNI_SUR_HIGH_END (UTF32)0xDBFF #define UNI_SUR_LOW_START (UTF32)0xDC00 #define UNI_SUR_LOW_END (UTF32)0xDFFF -#define false 0 -#define true 1 +#define false 0 +#define true 1 /* --------------------------------------------------------------------- */ @@ -216,14 +213,14 @@ static const UTF32 halfMask = 0x3FFUL; * allowed in earlier algorithms. */ static const char trailingBytesForUTF8[256] = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 }; /* @@ -231,8 +228,8 @@ static const char trailingBytesForUTF8[256] = { * This table contains as many values as there might be trailing bytes * in a UTF-8 sequence. */ -static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, - 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; +static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; /* * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed @@ -245,110 +242,115 @@ static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC /* --------------------------------------------------------------------- */ -#ifdef unused +#ifdef USE_ARCHIVED_UTF8_SOURCE + +static const int halfShift = 10; /* used for shifting by 10 bits */ + +static const UTF32 halfBase = 0x0010000UL; +static const UTF32 halfMask = 0x3FFUL; ConversionResult ConvertUTF32toUTF16 ( - const UTF32** sourceStart, const UTF32* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF32* source = *sourceStart; - UTF16* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - if (target >= targetEnd) { - result = targetExhausted; break; - } - ch = *source++; - if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ - /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - *target++ = (UTF16)ch; /* normal case */ - } - } else if (ch > UNI_MAX_LEGAL_UTF32) { - if (flags == strictConversion) { - result = sourceIllegal; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - /* target is a character in range 0xFFFF - 0x10FFFF. */ - if (target + 1 >= targetEnd) { - --source; /* Back up source pointer! */ - result = targetExhausted; break; - } - ch -= halfBase; - *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); - *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); - } - } - *sourceStart = source; - *targetStart = target; - return result; + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + if (target >= targetEnd) { + result = targetExhausted; break; + } + ch = *source++; + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_LEGAL_UTF32) { + if (flags == strictConversion) { + result = sourceIllegal; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + --source; /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; } /* --------------------------------------------------------------------- */ ConversionResult ConvertUTF16toUTF32 ( - const UTF16** sourceStart, const UTF16* sourceEnd, - UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF16* source = *sourceStart; - UTF32* target = *targetStart; - UTF32 ch, ch2; - while (source < sourceEnd) { - const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ - ch = *source++; - /* If we have a surrogate pair, convert to UTF32 first. */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { - /* If the 16 bits following the high surrogate are in the source buffer... */ - if (source < sourceEnd) { - ch2 = *source; - /* If it's a low surrogate, convert to UTF32. */ - if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { - ch = ((ch - UNI_SUR_HIGH_START) << halfShift) - + (ch2 - UNI_SUR_LOW_START) + halfBase; - ++source; - } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } else { /* We don't have the 16 bits following the high surrogate. */ - --source; /* return to the high surrogate */ - result = sourceExhausted; - break; - } - } else if (flags == strictConversion) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - if (target >= targetEnd) { - source = oldSource; /* Back up source pointer! */ - result = targetExhausted; break; - } - *target++ = ch; - } - *sourceStart = source; - *targetStart = target; + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF32* target = *targetStart; + UTF32 ch, ch2; + while (source < sourceEnd) { + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + if (target >= targetEnd) { + source = oldSource; /* Back up source pointer! */ + result = targetExhausted; break; + } + *target++ = ch; + } + *sourceStart = source; + *targetStart = target; #ifdef CVTUTF_DEBUG if (result == sourceIllegal) { - fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); - fflush(stderr); + fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2); + fflush(stderr); } #endif - return result; + return result; } /* --------------------------------------------------------------------- */ @@ -364,73 +366,73 @@ if (result == sourceIllegal) { /* --------------------------------------------------------------------- */ ConversionResult ConvertUTF16toUTF8 ( - const UTF16** sourceStart, const UTF16* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF16* source = *sourceStart; - UTF8* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - unsigned short bytesToWrite = 0; - const UTF32 byteMask = 0xBF; - const UTF32 byteMark = 0x80; - const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ - ch = *source++; - /* If we have a surrogate pair, convert to UTF32 first. */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { - /* If the 16 bits following the high surrogate are in the source buffer... */ - if (source < sourceEnd) { - UTF32 ch2 = *source; - /* If it's a low surrogate, convert to UTF32. */ - if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { - ch = ((ch - UNI_SUR_HIGH_START) << halfShift) - + (ch2 - UNI_SUR_LOW_START) + halfBase; - ++source; - } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } else { /* We don't have the 16 bits following the high surrogate. */ - --source; /* return to the high surrogate */ - result = sourceExhausted; - break; - } - } else if (flags == strictConversion) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - /* Figure out how many bytes the result will require */ - if (ch < (UTF32)0x80) { bytesToWrite = 1; - } else if (ch < (UTF32)0x800) { bytesToWrite = 2; - } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; - } else if (ch < (UTF32)0x110000) { bytesToWrite = 4; - } else { bytesToWrite = 3; - ch = UNI_REPLACEMENT_CHAR; - } - - target += bytesToWrite; - if (target > targetEnd) { - source = oldSource; /* Back up source pointer! */ - target -= bytesToWrite; result = targetExhausted; break; - } - switch (bytesToWrite) { /* note: everything falls through. */ - case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); - } - target += bytesToWrite; - } - *sourceStart = source; - *targetStart = target; - return result; + const UTF16** sourceStart, const UTF16* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF16* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */ + ch = *source++; + /* If we have a surrogate pair, convert to UTF32 first. */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + /* If the 16 bits following the high surrogate are in the source buffer... */ + if (source < sourceEnd) { + UTF32 ch2 = *source; + /* If it's a low surrogate, convert to UTF32. */ + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + ++source; + } else if (flags == strictConversion) { /* it's an unpaired high surrogate */ + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --source; /* return to the high surrogate */ + result = sourceExhausted; + break; + } + } else if (flags == strictConversion) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* Figure out how many bytes the result will require */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch < (UTF32)0x110000) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + } + + target += bytesToWrite; + if (target > targetEnd) { + source = oldSource; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; } -#endif //unused +#endif // USE_ARCHIVED_UTF8_SOURCE /* --------------------------------------------------------------------- */ @@ -446,31 +448,32 @@ ConversionResult ConvertUTF16toUTF8 ( */ static Boolean isLegalUTF8(const UTF8 *source, int length) { - UTF8 a; - const UTF8 *srcptr = source+length; - - switch (length) { - default: return false; - /* Everything else falls through when "true"... */ - case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; - case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; - case 2: if ((a = (*--srcptr)) > 0xBF) return false; - - switch (*source) { - /* no fall-through in this inner switch */ - case 0xE0: if (a < 0xA0) return false; break; - case 0xED: if (a > 0x9F) return false; break; - case 0xF0: if (a < 0x90) return false; break; - case 0xF4: if (a > 0x8F) return false; break; - default: if (a < 0x80) return false; - } - - case 1: if (*source >= 0x80 && *source < 0xC2) return false; - } - - if (*source > 0xF4) return false; - - return true; + UTF8 a; + const UTF8 *srcptr = source+length; + + switch (length) { + default: return false; + /* Everything else falls through when "true"... */ + case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; // falls through + case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; // falls through + case 2: if ((a = (*--srcptr)) > 0xBF) return false; // falls through + + switch (*source) { + /* no fall-through in this inner switch */ + case 0xE0: if (a < 0xA0) return false; break; + case 0xED: if (a > 0x9F) return false; break; + case 0xF0: if (a < 0x90) return false; break; + case 0xF4: if (a > 0x8F) return false; break; + default: if (a < 0x80) return false; break; + } + + // falls through + case 1: if (*source >= 0x80 && *source < 0xC2) return false; + } + + if (*source > 0xF4) return false; + + return true; } /* --------------------------------------------------------------------- */ @@ -480,215 +483,215 @@ static Boolean isLegalUTF8(const UTF8 *source, int length) { * This is not used here; it's just exported. */ Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) { - int length = trailingBytesForUTF8[*source]+1; - if (source+length > sourceEnd) return false; - return isLegalUTF8(source, length); + int length = trailingBytesForUTF8[*source]+1; + if (source+length > sourceEnd) return false; + return isLegalUTF8(source, length); } /* --------------------------------------------------------------------- */ -#ifdef unused +#ifdef USE_ARCHIVED_UTF16_CODE ConversionResult ConvertUTF8toUTF16 ( - const UTF8** sourceStart, const UTF8* sourceEnd, - UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF8* source = *sourceStart; - UTF16* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch = 0; - unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; - if (source + extraBytesToRead >= sourceEnd) { - result = sourceExhausted; break; - } - /* Do this check whether lenient or strict */ - if (! isLegalUTF8(source, extraBytesToRead+1)) { - result = sourceIllegal; - break; - } - /* - * The cases all fall through. See "Note A" below. - */ - switch (extraBytesToRead) { - case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ - case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ - case 3: ch += *source++; ch <<= 6; - case 2: ch += *source++; ch <<= 6; - case 1: ch += *source++; ch <<= 6; - case 0: ch += *source++; - } - ch -= offsetsFromUTF8[extraBytesToRead]; - - if (target >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up source pointer! */ - result = targetExhausted; break; - } - if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - source -= (extraBytesToRead+1); /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - *target++ = (UTF16)ch; /* normal case */ - } - } else if (ch > UNI_MAX_UTF16) { - if (flags == strictConversion) { - result = sourceIllegal; - source -= (extraBytesToRead+1); /* return to the start */ - break; /* Bail out; shouldn't continue */ - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - /* target is a character in range 0xFFFF - 0x10FFFF. */ - if (target + 1 >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up source pointer! */ - result = targetExhausted; break; - } - ch -= halfBase; - *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); - *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); - } - } - *sourceStart = source; - *targetStart = target; - return result; + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF16* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */ + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = (UTF16)ch; /* normal case */ + } + } else if (ch > UNI_MAX_UTF16) { + if (flags == strictConversion) { + result = sourceIllegal; + source -= (extraBytesToRead+1); /* return to the start */ + break; /* Bail out; shouldn't continue */ + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + if (target + 1 >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up source pointer! */ + result = targetExhausted; break; + } + ch -= halfBase; + *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START); + *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START); + } + } + *sourceStart = source; + *targetStart = target; + return result; } /* --------------------------------------------------------------------- */ ConversionResult ConvertUTF32toUTF8 ( - const UTF32** sourceStart, const UTF32* sourceEnd, - UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF32* source = *sourceStart; - UTF8* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch; - unsigned short bytesToWrite = 0; - const UTF32 byteMask = 0xBF; - const UTF32 byteMark = 0x80; - ch = *source++; - if (flags == strictConversion ) { - /* UTF-16 surrogate values are illegal in UTF-32 */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - --source; /* return to the illegal value itself */ - result = sourceIllegal; - break; - } - } - /* - * Figure out how many bytes the result will require. Turn any - * illegally large UTF32 things (> Plane 17) into replacement chars. - */ - if (ch < (UTF32)0x80) { bytesToWrite = 1; - } else if (ch < (UTF32)0x800) { bytesToWrite = 2; - } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; - } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; - } else { bytesToWrite = 3; - ch = UNI_REPLACEMENT_CHAR; - result = sourceIllegal; - } - - target += bytesToWrite; - if (target > targetEnd) { - --source; /* Back up source pointer! */ - target -= bytesToWrite; result = targetExhausted; break; - } - switch (bytesToWrite) { /* note: everything falls through. */ - case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; - case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); - } - target += bytesToWrite; - } - *sourceStart = source; - *targetStart = target; - return result; + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + ch = *source++; + if (flags == strictConversion ) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* + * Figure out how many bytes the result will require. Turn any + * illegally large UTF32 things (> Plane 17) into replacement chars. + */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + result = sourceIllegal; + } + + target += bytesToWrite; + if (target > targetEnd) { + --source; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; + case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; } /* --------------------------------------------------------------------- */ ConversionResult ConvertUTF8toUTF32 ( - const UTF8** sourceStart, const UTF8* sourceEnd, - UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { - ConversionResult result = conversionOK; - const UTF8* source = *sourceStart; - UTF32* target = *targetStart; - while (source < sourceEnd) { - UTF32 ch = 0; - unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; - if (source + extraBytesToRead >= sourceEnd) { - result = sourceExhausted; break; - } - /* Do this check whether lenient or strict */ - if (! isLegalUTF8(source, extraBytesToRead+1)) { - result = sourceIllegal; - break; - } - /* - * The cases all fall through. See "Note A" below. - */ - switch (extraBytesToRead) { - case 5: ch += *source++; ch <<= 6; - case 4: ch += *source++; ch <<= 6; - case 3: ch += *source++; ch <<= 6; - case 2: ch += *source++; ch <<= 6; - case 1: ch += *source++; ch <<= 6; - case 0: ch += *source++; - } - ch -= offsetsFromUTF8[extraBytesToRead]; - - if (target >= targetEnd) { - source -= (extraBytesToRead+1); /* Back up the source pointer! */ - result = targetExhausted; break; - } - if (ch <= UNI_MAX_LEGAL_UTF32) { - /* - * UTF-16 surrogate values are illegal in UTF-32, and anything - * over Plane 17 (> 0x10FFFF) is illegal. - */ - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { - if (flags == strictConversion) { - source -= (extraBytesToRead+1); /* return to the illegal value itself */ - result = sourceIllegal; - break; - } else { - *target++ = UNI_REPLACEMENT_CHAR; - } - } else { - *target++ = ch; - } - } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ - result = sourceIllegal; - *target++ = UNI_REPLACEMENT_CHAR; - } - } - *sourceStart = source; - *targetStart = target; - return result; + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF32* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: ch += *source++; ch <<= 6; + case 4: ch += *source++; ch <<= 6; + case 3: ch += *source++; ch <<= 6; + case 2: ch += *source++; ch <<= 6; + case 1: ch += *source++; ch <<= 6; + case 0: ch += *source++; + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up the source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_LEGAL_UTF32) { + /* + * UTF-16 surrogate values are illegal in UTF-32, and anything + * over Plane 17 (> 0x10FFFF) is illegal. + */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = ch; + } + } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ + result = sourceIllegal; + *target++ = UNI_REPLACEMENT_CHAR; + } + } + *sourceStart = source; + *targetStart = target; + return result; } /* --------------------------------------------------------------------- - Note A. - The fall-through switches in UTF-8 reading code save a - temp variable, some decrements & conditionals. The switches - are equivalent to the following loop: - { - int tmpBytesToRead = extraBytesToRead+1; - do { - ch += *source++; - --tmpBytesToRead; - if (tmpBytesToRead) ch <<= 6; - } while (tmpBytesToRead > 0); - } - In UTF-8 writing code, the switches on "bytesToWrite" are - similarly unrolled loops. + Note A. + The fall-through switches in UTF-8 reading code save a + temp variable, some decrements & conditionals. The switches + are equivalent to the following loop: + { + int tmpBytesToRead = extraBytesToRead+1; + do { + ch += *source++; + --tmpBytesToRead; + if (tmpBytesToRead) ch <<= 6; + } while (tmpBytesToRead > 0); + } + In UTF-8 writing code, the switches on "bytesToWrite" are + similarly unrolled loops. --------------------------------------------------------------------- */ @@ -698,497 +701,680 @@ ConversionResult ConvertUTF8toUTF32 ( /*********************************************************************** ************************************************************************ ** -** Code below added by REBOL Technologies 2008 +** Code below added by REBOL Technologies 2008 ** ************************************************************************ ***********************************************************************/ -/*********************************************************************** -** -*/ REBINT What_UTF(REBYTE *bp, REBCNT len) -/* -** Tell us what UTF encoding the string has. Negative for LE. -** -***********************************************************************/ +// +// What_UTF: C +// +// Tell us what UTF encoding the byte stream has, as integer # of bits. +// 0 is unknown, negative for Little Endian. +// +// !!! Currently only uses the Byte-Order-Mark for detection (which is not +// necessarily present) +// +// !!! Note that UTF8 is not prescribed to have a byte order mark by the +// standard. Writing routines will not add it by default, hence if it is +// present it is to be considered part of the in-band data stream...so that +// reading and writing back out will preserve the input. +// +REBINT What_UTF(REBYTE *bp, REBCNT len) { - // UTF8: - if (len >= 3 && bp[0] == 0xef && bp[1] == 0xbb && bp[2] == 0xbf) return 8; - - if (len >= 2) { - - // UTF16: - if (bp[0] == 0xfe && bp[1] == 0xff) return 16; - - // Either UTF16 or 32: - if (bp[0] == 0xff && bp[1] == 0xfe) { - if (len >= 4 && bp[2] == 0 && bp[3] == 0) return -32; - return -16; - } - - // UTF32 - if (len >= 4 && bp[0] == 0 && bp[1] == 0 && bp[2] == 0xfe && bp[3] == 0xff) - return 32; - } - - // Unknown: - return 0; + if (len >= 3 && bp[0] == 0xef && bp[1] == 0xbb && bp[2] == 0xbf) + return 8; // UTF8 (endian agnostic) + + if (len >= 2) { + if (bp[0] == 0xfe && bp[1] == 0xff) + return 16; // UTF16 big endian + + if (bp[0] == 0xff && bp[1] == 0xfe) { + if (len >= 4 && bp[2] == 0 && bp[3] == 0) + return -32; // UTF32 little endian + return -16; // UTF16 little endian + } + + if ( + len >= 4 + && bp[0] == 0 && bp[1] == 0 && bp[2] == 0xfe && bp[3] == 0xff + ){ + return 32; // UTF32 big endian + } + } + + return 0; // unknown } -/*********************************************************************** -** -*/ REBFLG Legal_UTF8_Char(REBYTE *str, REBCNT len) -/* -** Returns TRUE if char is legal. -** -***********************************************************************/ + +// +// Legal_UTF8_Char: C +// +// Returns TRUE if char is legal. +// +REBOOL Legal_UTF8_Char(const REBYTE *str, REBCNT len) { - return isLegalUTF8Sequence(str, str + len); + return LOGICAL(isLegalUTF8Sequence(str, str + len)); } -/*********************************************************************** -** -*/ REBYTE *Check_UTF8(REBYTE *str, REBCNT len) -/* -** Returns 0 for success, else str where error occurred. -** -***********************************************************************/ +// +// Check_UTF8: C +// +// Returns 0 for success, else str where error occurred. +// +REBYTE *Check_UTF8(REBYTE *str, REBCNT len) { - REBINT n; - REBYTE *end = str + len; + REBINT n; + REBYTE *end = str + len; - for (;str < end; str += n) { - n = trailingBytesForUTF8[*str] + 1; - if (str + n > end || !isLegalUTF8(str, n)) return str; - } + for (;str < end; str += n) { + n = trailingBytesForUTF8[*str] + 1; + if (str + n > end || !isLegalUTF8(str, n)) return str; + } - return 0; + return 0; } -/*********************************************************************** -** -*/ REBCNT Decode_UTF8_Char(REBYTE **str, REBCNT *len) -/* -** Converts a single UTF8 code-point (to 32 bit). -** Errors are returned as zero. (So prescan source for null.) -** Increments str by extra chars needed. -** Decrements len by extra chars needed. -** -***********************************************************************/ -{ - UTF8 *source = *str; - UTF32 ch = 0; - int slen = trailingBytesForUTF8[*source]; - - // Check that we have enough valid source bytes: - if (len) { - if (slen+1 > *len) return 0; - } - else { - for (; slen >= 0; slen--) - if (source[slen] < 0x80) return 0; - slen = trailingBytesForUTF8[*source]; - } - - // Do this check whether lenient or strict: - // if (!isLegalUTF8(source, slen+1)) return 0; - - switch (slen) { - case 5: ch += *source++; ch <<= 6; - case 4: ch += *source++; ch <<= 6; - case 3: ch += *source++; ch <<= 6; - case 2: ch += *source++; ch <<= 6; - case 1: ch += *source++; ch <<= 6; - case 0: ch += *source++; - } - ch -= offsetsFromUTF8[slen]; - - // UTF-16 surrogate values are illegal in UTF-32, and anything - // over Plane 17 (> 0x10FFFF) is illegal. - if (ch > UNI_MAX_LEGAL_UTF32) return 0; - if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) return 0; - - if (len) *len -= slen; - *str += slen; - return ch; +// +// Back_Scan_UTF8_Char_Core: C +// +// Converts a single UTF8 code-point and returns the position *at the +// the last byte of the character's data*. (This differs from the usual +// `Scan_XXX` interface of returning the position after the scanned +// element, ready to read the next one.) +// +// The peculiar interface is useful in loops that are processing +// ordinary ASCII chars directly -as well- as UTF8 ones. The loop can +// do a single byte pointer increment after both kinds of +// elements, avoiding the need to call any kind of `Scan_Ascii()`: +// +// for (; len > 0; bp++, len--) { +// if (*bp < 0x80) { +// // do ASCII stuff... +// } +// else { +// REBUNI uni; +// bp = Back_Scan_UTF8_Char(&uni, bp, &len); +// // do UNICODE stuff... +// } +// } +// +// The third parameter is an optional length that will be decremented by +// the number of "extra" bytes the UTF8 has beyond a single byte character. +// This allows for decrement-style loops such as the above. +// +// Prescans source for null, and will not return code point 0. +// +// If failure due to insufficient data or malformed bytes, then NULL is +// returned (len is not advanced). +// +const REBYTE *Back_Scan_UTF8_Char_Core( + unsigned long *out, // "UTF32" is defined as unsigned long above + const REBYTE *bp, + REBCNT *len +) { + *out = 0; + + const UTF8 *source = bp; + REBCNT trail = trailingBytesForUTF8[*source]; + + // Check that we have enough valid source bytes: + if (len) { + if (trail + 1 > *len) + return NULL; + } + else if (trail != 0) { + do { + if (source[trail] < 0x80) + return NULL; + } while (--trail != 0); + + trail = trailingBytesForUTF8[*source]; + } + + // Do this check whether lenient or strict: + // if (!isLegalUTF8(source, slen+1)) return 0; + + switch (trail) { + case 5: *out += *source++; *out <<= 6; // falls through + case 4: *out += *source++; *out <<= 6; // falls through + case 3: *out += *source++; *out <<= 6; // falls through + case 2: *out += *source++; *out <<= 6; // falls through + case 1: *out += *source++; *out <<= 6; // falls through + case 0: *out += *source++; + } + *out -= offsetsFromUTF8[trail]; + + // UTF-16 surrogate values are illegal in UTF-32, and anything + // over Plane 17 (> 0x10FFFF) is illegal. + // + // !!! Is this still relevant, in a system that is fully UTF8 based? + // + if (*out > UNI_MAX_LEGAL_UTF32) + return NULL; + if (*out >= UNI_SUR_HIGH_START && *out <= UNI_SUR_LOW_END) + return NULL; + + if (len) + *len -= trail; + + // !!! Original implementation used 0 as a return value to indicate a + // decoding failure. However, 0 is a legal UTF8 codepoint, and also + // Rebol strings are able to store NUL characters (they track a length + // and are not zero-terminated.) Should this be legal? + // + assert(*out != 0); + if (*out == 0) + return NULL; + + return bp + trail; } -/*********************************************************************** -** -*/ int Decode_UTF8(REBUNI *dst, REBYTE *src, REBCNT len, REBFLG ccr) -/* -** Decode UTF8 byte string into a 16 bit preallocated array. -** -** dst: the desination array, must always be large enough! -** src: source binary data -** len: byte-length of source (not number of chars) -** ccr: convert CRLF/CR to LF -** -** Returns length in chars (negative if all chars are latin-1). -** No terminator is added. -** -***********************************************************************/ -{ - int flag = -1; - UTF32 ch; - REBUNI *start = dst; - - for (; len > 0; len--, src++) { - if ((ch = *src) >= 0x80) { - ch = Decode_UTF8_Char(&src, &len); - if (ch == 0) ch = UNI_REPLACEMENT_CHAR; // temporary! - if (ch > 0xff) flag = 1; - } if (ch == CR && ccr) { - if (src[1] == LF) continue; - ch = LF; - } - *dst++ = (REBUNI)ch; - } - - return (dst - start) * flag; +// +// Decode_UTF8_Negative_If_Latin1: C +// +// Decode UTF8 byte string into a 16 bit preallocated array. +// +// dst: the desination array, must always be large enough! +// src: source binary data +// len: byte-length of source (not number of chars) +// crlf_to_lf: convert CRLF/CR to LF +// +// Returns length in chars (negative if all chars are latin-1). +// No terminator is added. +// +int Decode_UTF8_Negative_If_Latin1( + REBUNI *dst, + const REBYTE *src, + REBCNT len, + REBOOL crlf_to_lf +) { + int flag = -1; + REBUNI ch; + REBUNI *start = dst; + + for (; len > 0; len--, src++) { + if ((ch = *src) >= 0x80) { + if (!(src = Back_Scan_UTF8_Char(&ch, src, &len))) + fail (Error_Bad_Utf8_Raw()); + + if (ch > 0xff) flag = 1; + } + else if (ch == CR && crlf_to_lf) { + if (src[1] == LF) continue; + ch = LF; + } + *dst++ = ch; + } + + return (dst - start) * flag; } -/*********************************************************************** -** -*/ int Decode_UTF16(REBUNI *dst, REBYTE *src, REBCNT len, REBFLG lee, REBFLG ccr) -/* -** dst: the desination array, must always be large enough! -** src: source binary data -** len: byte-length of source (not number of chars) -** lee: little endian encoded -** ccr: convert CRLF/CR to LF -** -** Returns length in chars (negative if all chars are latin-1). -** No terminator is added. -** -***********************************************************************/ -{ - int flag = -1; - UTF32 ch; - REBUNI *start = dst; - - if (ccr) ccr = 1; - - for (; len > 0; len--, src++) { - - // Combine bytes in big or little endian format: - ch = *src; - if (!lee) ch <<= 8; - if (--len <= 0) break; - src++; - ch |= lee ? (UTF32)(*src) << 8 : *src; - - // Skip CR, but add LF (even if missing) - if (ccr) { - if (ccr < 0 && ch != LF) { - ccr = 1; - *dst++ = LF; - } - if (ch == CR) { - ccr = -1; - continue; - } - } - - // check for surrogate pair ?? - - if (ch > 0xff) flag = 1; - - *dst++ = (REBUNI)ch; - } - - return (dst - start) * flag; +// +// Decode_UTF8_Maybe_Astral_Throws: C +// +// Prior to formal support for unicode codepoints higher than 0xFFFF, this +// routine allows a handler to be called for high codepoints that can return +// something to substitute into the string instead. Whereas typical UTF8 +// decoding knows an upper bound on the total string length, this does not... +// so the interface must allow for resizing the buffer. +// +REBOOL Decode_UTF8_Maybe_Astral_Throws( + REBVAL *out_if_thrown, + REBSER *dst, + const REBYTE *src, + REBCNT len, + REBOOL crlf_to_lf, + const REBVAL *handler +) { + TRASH_CELL_IF_DEBUG(out_if_thrown); + + assert(SER_WIDE(dst) == sizeof(REBUNI)); // Append_Codepoint_Raw is used + + UTF32 ch; + + DECLARE_LOCAL (item); + DECLARE_LOCAL (astral); + + for (; len > 0; len--, src++) { + if ((ch = *src) >= 0x80) { + if (!(src = Back_Scan_UTF8_Char_Core(&ch, src, &len))) + fail (Error_Bad_Utf8_Raw()); + + if (ch > 0xFFFF) { // too big to fit in today's REBUNI + if (IS_FUNCTION(handler)) { + Init_Integer(astral, ch); // CHAR! only 16-bit for now + + // Try passing the handler the codepoint value. Passing + // FALSE for `fully` means it will not raise an error if + // the handler happens to be arity 0. + + const REBOOL fully = FALSE; + if (Apply_Only_Throws(item, fully, handler, astral, END)) { + Move_Value(out_if_thrown, item); + return TRUE; + } + } + else + Move_Value(item, handler); + + switch (VAL_TYPE(item)) { + case REB_MAX_VOID: + case REB_BLANK: + break; // tolerate void or blank as meaning nothing + + case REB_CHAR: + Append_Codepoint_Raw(dst, VAL_CHAR(item)); + break; + + case REB_STRING: + Append_String( + dst, + VAL_SERIES(item), + VAL_INDEX(item), + VAL_LEN_AT(item) + ); + break; + + default: + fail (item); + } + + continue; + } + } + else if (ch == CR && crlf_to_lf) { + if (src[1] == LF) continue; + ch = LF; + } + Append_Codepoint_Raw(dst, ch); + } + + return FALSE; // no throw } -/*********************************************************************** -** -*/ int Decode_UTF32(REBUNI *dst, REBYTE *src, REBINT len, REBFLG lee, REBFLG ccr) -/* -***********************************************************************/ -{ - return 0; +// +// Decode_UTF16: C +// +// dst: the desination array, must always be large enough! +// src: source binary data +// len: byte-length of source (not number of chars) +// little_endian: little endian encoded +// crlf_to_lf: convert CRLF/CR to LF +// +// Returns length in chars (negative if all chars are latin-1). +// No terminator is added. +// +int Decode_UTF16( + REBUNI *dst, + const REBYTE *src, + REBCNT len, + REBOOL little_endian, + REBOOL crlf_to_lf +) { + REBOOL expect_lf = FALSE; + REBOOL latin1 = TRUE; + UTF32 ch; + REBUNI *start = dst; + + for (; len > 0; len--, src++) { + // + // Combine bytes in big or little endian format + // + ch = *src; + if (!little_endian) ch <<= 8; + if (--len <= 0) break; + src++; + ch |= little_endian ? (cast(UTF32, *src) << 8) : *src; + + if (crlf_to_lf) { + // + // Skip CR, but add LF (even if missing) + // + if (expect_lf && ch != LF) { + expect_lf = FALSE; + *dst++ = LF; + } + if (ch == CR) { + expect_lf = TRUE; + continue; + } + } + + // !!! "check for surrogate pair" ?? + + if (ch > 0xff) latin1 = FALSE; + + *dst++ = cast(REBUNI, ch); + } + + return latin1 ? -(dst - start) : (dst - start); } -/*********************************************************************** -** -*/ REBSER *Decode_UTF_String(REBYTE *bp, REBCNT len, REBINT utf) -/* -** Do all the details to decode a string. -** Input is a byte series. Len is len of input. -** The utf is 0, 8, +/-16, +/-32. -** A special -1 means use the BOM. -** -***********************************************************************/ +// +// Decode_UTF_String: C +// +// Do all the details to decode a string. +// Input is a byte series. Len is len of input. +// The utf is 0, 8, +/-16 +// A special -1 means use the BOM, if present, or UTF-8 otherwise. +// +// Returns the decoded string or NULL for unsupported encodings. +// +REBSER *Decode_UTF_String(REBYTE *bp, REBCNT len, REBINT utf) { - REBSER *ser = BUF_UTF8; // buffer is Unicode width - REBSER *dst; - REBINT size; - - if (utf == -1) { - utf = What_UTF(bp, len); - if (utf) { - if (utf == 8) bp += 3, len -= 3; - else if (utf == -16 || utf == 16) bp += 2, len -= 2; - else if (utf == -32 || utf == 32) bp += 4, len -= 4; - } - } - - if (utf == 0 || utf == 8) { - size = Decode_UTF8((REBUNI*)Reset_Buffer(ser, len), bp, len, TRUE); - } - else if (utf == -16 || utf == 16) { - size = Decode_UTF16((REBUNI*)Reset_Buffer(ser, len/2 + 1), bp, len, utf < 0, TRUE); - } -// else if (utf == -32 || utf == 32) { -// size = Decode_UTF32((REBUNI*)Reset_Buffer(ser, len/4 + 1), bp, len, utf < 0, TRUE); -// } - - if (size < 0) { - size = -size; - dst = Make_Binary(size); - Append_Uni_Bytes(dst, UNI_HEAD(ser), size); - } - else { - dst = Make_Unicode(size); - Append_Uni_Uni(dst, UNI_HEAD(ser), size); - } - - return dst; + REBSER *ser = BUF_UTF8; // buffer is Unicode width + REBSER *dst; + REBINT size; + + if (utf == -1) { + // Try to detect UTF encoding from a BOM. Returns 0 if no BOM present. + utf = What_UTF(bp, len); + if (utf != 0) { + if (utf == 8) bp += 3, len -= 3; + else if (utf == -16 || utf == 16) bp += 2, len -= 2; + else return NULL; + } + } + + if (utf == 0 || utf == 8) { + size = Decode_UTF8_Negative_If_Latin1( + cast(REBUNI*, Reset_Buffer(ser, len)), bp, len, TRUE + ); + } + else if (utf == -16 || utf == 16) { + size = Decode_UTF16( + cast(REBUNI*, Reset_Buffer(ser, (len / 2) + 1)), + bp, + len, + LOGICAL(utf < 0), + TRUE + ); + } + else { + // Encoding is unsupported or not yet implemented. + return NULL; + } + + if (size < 0) { + size = -size; + dst = Make_Binary(size); + Append_Uni_Bytes(dst, UNI_HEAD(ser), size); + } + else { + dst = Make_Unicode(size); + Append_Uni_Uni(dst, UNI_HEAD(ser), size); + } + + return dst; } -/*********************************************************************** -** -*/ REBCNT Length_As_UTF8(REBUNI *src, REBCNT len, REBOOL uni, REBOOL ccr) -/* -** Returns how long the UTF8 encoded string would be. -** -***********************************************************************/ +// +// Length_As_UTF8: C +// +// Returns how long the UTF8 encoded string would be. +// +REBCNT Length_As_UTF8(const void *p, REBCNT len, REBFLGS opts) { - REBCNT size = 0; - REBCNT c; - REBYTE *bp = (REBYTE*)src; - - for (; len > 0; len--) { - c = uni ? *src++ : *bp++; - if (c < (UTF32)0x80) { -#ifdef TO_WIN32 - if (ccr && c == LF) size++; // because we will add a CR to it + REBCNT size = 0; + REBCNT c; + REBOOL unicode = LOGICAL(opts & OPT_ENC_UNISRC); + + const REBYTE *bp = unicode ? NULL : cast(const REBYTE *, p); + const REBUNI *up = unicode ? cast(const REBUNI *, p) : NULL; + + for (; len > 0; len--) { + c = unicode ? *up++ : *bp++; + if (c < (UTF32)0x80) { +#ifdef TO_WINDOWS + if (LOGICAL(opts & OPT_ENC_CRLF) && c == LF) + size++; // since we will add a CR to it #endif - size++; - } - else if (c < (UTF32)0x800) size += 2; - else if (c < (UTF32)0x10000) size += 3; - else if (c <= UNI_MAX_LEGAL_UTF32) size += 4; - else size += 3; - } - - return size; + size++; + } + else if (c < (UTF32)0x800) size += 2; + else if (c < (UTF32)0x10000) size += 3; + else if (c <= UNI_MAX_LEGAL_UTF32) size += 4; + else size += 3; + } + + return size; } -/*********************************************************************** -** -*/ REBCNT Encode_UTF8_Char(REBYTE *dst, REBCNT src) -/* -** Converts a single char to UTF8 code-point. -** Returns length of char stored in dst. -** Be sure dst has at least 4 bytes available. -** -***********************************************************************/ +// +// Encode_UTF8_Char: C +// +// Converts a single char to UTF8 code-point. +// Returns length of char stored in dst. +// Be sure dst has at least 4 bytes available. +// +REBCNT Encode_UTF8_Char(REBYTE *dst, REBCNT src) { - int len = 0; - const UTF32 mask = 0xBF; - const UTF32 mark = 0x80; - - if (src < (UTF32)0x80) len = 1; - else if (src < (UTF32)0x800) len = 2; - else if (src < (UTF32)0x10000) len = 3; - else if (src <= UNI_MAX_LEGAL_UTF32) len = 4; - else { - len = 3; - src = UNI_REPLACEMENT_CHAR; - } - - dst += len; - - switch (len) { - case 4: *--dst = (UTF8)((src | mark) & mask); src >>= 6; - case 3: *--dst = (UTF8)((src | mark) & mask); src >>= 6; - case 2: *--dst = (UTF8)((src | mark) & mask); src >>= 6; - case 1: *--dst = (UTF8) (src | firstByteMark[len]); - } - - return len; + int len = 0; + const UTF32 mask = 0xBF; + const UTF32 mark = 0x80; + + if (src < (UTF32)0x80) len = 1; + else if (src < (UTF32)0x800) len = 2; + else if (src < (UTF32)0x10000) len = 3; + else if (src <= UNI_MAX_LEGAL_UTF32) len = 4; + else { + len = 3; + src = UNI_REPLACEMENT_CHAR; + } + + dst += len; + + switch (len) { + case 4: *--dst = (UTF8)((src | mark) & mask); src >>= 6; // falls through + case 3: *--dst = (UTF8)((src | mark) & mask); src >>= 6; // falls through + case 2: *--dst = (UTF8)((src | mark) & mask); src >>= 6; // falls through + case 1: *--dst = (UTF8) (src | firstByteMark[len]); + } + + return len; } -/*********************************************************************** -** -*/ REBCNT Encode_UTF8(REBYTE *dst, REBINT max, void *src, REBCNT *len, REBFLG uni, REBFLG ccr) -/* -** Encode the unicode into UTF8 byte string. -** -** Source string can be byte or unichar sized (uni = TRUE); -** Max is the maximum size of the result (UTF8). -** Returns number of source chars used. -** Updates len for dst bytes used. -** Does not add a terminator. -** -***********************************************************************/ -{ - REBUNI c; - REBINT n; - REBYTE buf[8]; - REBYTE *bs = dst; // save start - REBYTE *bp = (REBYTE*)src; - REBUNI *up = (REBUNI*)src; - REBCNT cnt; - - if (len) cnt = *len; - else { - cnt = uni ? wcslen((REBUNI*)bp) : LEN_BYTES((REBYTE*)bp); - } - - for (; max > 0 && cnt > 0; cnt--) { - c = uni ? *up++ : *bp++; - if (c < 0x80) { -#if defined(TO_WIN32) - if (ccr && c == LF) { - // If there's not room, don't try to output CRLF - if (2 > max) {up--; break;} - *dst++ = CR; - max--; - c = LF; - } +// +// Encode_UTF8: C +// +// Encode the unicode into UTF8 byte string. +// +// Source string can be byte or unichar sized (OPT_ENC_UNISRC); +// Max is the maximum size of the result (UTF8). +// Returns number of dst bytes used. +// Updates len for source chars used. +// Does not add a terminator. +// +REBCNT Encode_UTF8( + REBYTE *dst, + REBCNT max, + const void *src, + REBCNT *len, + REBFLGS opts +) { + REBUNI c; + REBINT n; + REBYTE buf[8]; + REBYTE *bs = dst; // save start + const REBYTE *bp = cast(const REBYTE*, src); + const REBUNI *up = cast(const REBUNI*, src); + REBCNT cnt; + REBOOL unicode = LOGICAL(opts & OPT_ENC_UNISRC); + + if (len) cnt = *len; + else cnt = unicode ? Strlen_Uni(up) : LEN_BYTES(bp); + + for (; max > 0 && cnt > 0; cnt--) { + c = unicode ? *up++ : *bp++; + if (c < 0x80) { +#if defined(TO_WINDOWS) + if (LOGICAL(opts & OPT_ENC_CRLF) && c == LF) { + // If there's not room, don't try to output CRLF + if (2 > max) {bp--; up--; break;} + *dst++ = CR; + max--; + c = LF; + } #endif - *dst++ = (REBYTE)c; - max--; - } - else { - n = Encode_UTF8_Char(buf, c); - if (n > max) {up--; break;} - memcpy(dst, buf, n); - dst += n; - max -= n; - } - } - - if (len) *len = dst - bs; - - return uni ? up - (REBUNI*)src : bp - (REBYTE*)src; + *dst++ = cast(REBYTE, c); + max--; + } + else { + n = Encode_UTF8_Char(buf, c); + if (n > cast(REBINT, max)) {bp--; up--; break;} + memcpy(dst, buf, n); + dst += n; + max -= n; + } + } + + if (len) + *len = unicode + ? up - cast(const REBUNI*, src) + : bp - cast(const REBYTE*, src); + + return dst - bs; } -/*********************************************************************** -** -*/ int Encode_UTF8_Line(REBSER *dst, REBSER *src, REBCNT idx) -/* -** Encode a unicode source buffer into a binary line of UTF8. -** Include the LF terminator in the result. -** Return the length of the line buffer. -** -***********************************************************************/ +// +// Encode_UTF8_Line: C +// +// Encode a unicode source buffer into a binary line of UTF8. +// Include the LF terminator in the result. +// Return the length of the line buffer. +// +int Encode_UTF8_Line(REBSER *dst, REBSER *src, REBCNT idx) { - REBUNI *up = UNI_HEAD(src); - REBCNT len = SERIES_TAIL(src); - REBCNT tail; - REBUNI c; - REBINT n; - REBYTE buf[8]; - - tail = RESET_TAIL(dst); - - while (idx < len) { - if ((c = up[idx]) < 0x80) { - EXPAND_SERIES_TAIL(dst, 1); - BIN_HEAD(dst)[tail++] = (REBYTE)c; - } - else { - n = Encode_UTF8_Char(buf, c); - EXPAND_SERIES_TAIL(dst, n); - memcpy(BIN_SKIP(dst, tail), buf, n); - tail += n; - } - idx++; - if (c == LF) break; - } - - BIN_HEAD(dst)[tail] = 0; - SERIES_TAIL(dst) = tail; - return idx; + REBUNI *up = UNI_HEAD(src); + REBCNT len = SER_LEN(src); + REBCNT tail; + REBUNI c; + REBINT n; + REBYTE buf[8]; + + SET_SERIES_LEN(dst, 0); + tail = 0; + + while (idx < len) { + if ((c = up[idx]) < 0x80) { + EXPAND_SERIES_TAIL(dst, 1); + BIN_HEAD(dst)[tail++] = (REBYTE)c; + } + else { + n = Encode_UTF8_Char(buf, c); + EXPAND_SERIES_TAIL(dst, n); + memcpy(BIN_AT(dst, tail), buf, n); + tail += n; + } + idx++; + if (c == LF) break; + } + + BIN_HEAD(dst)[tail] = 0; + SET_SERIES_LEN(dst, tail); + return idx; } -/*********************************************************************** -** -*/ REBSER *Encode_UTF8_Value(REBVAL *arg, REBCNT len, REBFLG opts) -/* -** Do all the details to encode a string as UTF8. -** No_copy means do not make a copy. -** Result can be a shared buffer! -** -***********************************************************************/ -{ - REBSER *ser = BUF_FORM; // a shared buffer - REBCNT size; - REBYTE *cp; - REBFLG ccr = GET_FLAG(opts, ENC_OPT_CRLF); - - if (VAL_BYTE_SIZE(arg)) { - REBYTE *bp = VAL_BIN_DATA(arg); - - if (Is_Not_ASCII(bp, len)) { - size = Length_As_UTF8((REBUNI*)bp, len, FALSE, (REBOOL)ccr); - cp = Reset_Buffer(ser, size + (GET_FLAG(opts, ENC_OPT_BOM) ? 3 : 0)); - Encode_UTF8(cp, size, bp, &len, FALSE, ccr); - } - else if (GET_FLAG(opts, ENC_OPT_NO_COPY)) return 0; - else return Copy_Bytes(bp, len); - - } else { - REBUNI *up = VAL_UNI_DATA(arg); - - size = Length_As_UTF8(up, len, TRUE, (REBOOL)ccr); - cp = Reset_Buffer(ser, size + (GET_FLAG(opts, ENC_OPT_BOM) ? 3 : 0)); - Encode_UTF8(Reset_Buffer(ser, size), size, up, &len, TRUE, ccr); - } - - SERIES_TAIL(ser) = len; - STR_TERM(ser); - - return Copy_Bytes(BIN_HEAD(ser), len); +// +// Make_UTF8_Binary: C +// +// Convert byte- or REBUNI-sized data to UTF8-encoded +// null-terminated series. Can reserve extra bytes of space. +// Resulting series must be either freed or handed to the GC. +// +REBSER *Make_UTF8_Binary( + const void *data, + REBCNT len, + REBCNT extra, + REBFLGS opts +) { + REBCNT size = Length_As_UTF8(data, len, opts); + REBSER *series = Make_Binary(size + extra); + SET_SERIES_LEN(series, Encode_UTF8( + BIN_HEAD(series), size, data, &len, opts + )); + assert(SER_LEN(series) == size); + TERM_SEQUENCE(series); + return series; } -/*********************************************************************** -** -*/ REBSER *Encode_String(void *str, REBCNT len, REBCNT opts) -/* -** str: byte or unicode string -** len: length in chars -** opt: special options (UTF, LE/BE, CR/LF, BOM) -** -***********************************************************************/ -{ - REBSER *ser = 0; +// +// Make_UTF8_From_Any_String: C +// +// Do all the details to encode either a byte-sized or REBUNI +// size ANY-STRING! value to a UTF8-encoded series. Resulting +// series must be either freed or handed to the GC. +// +REBSER *Make_UTF8_From_Any_String( + const RELVAL *value, + REBCNT len, + REBFLGS opts +) { + assert(ANY_STRING(value)); + + if ( + NOT(opts & OPT_ENC_CRLF) + && ( + VAL_BYTE_SIZE(value) + && All_Bytes_ASCII(VAL_BIN_AT(value), VAL_LEN_AT(value)) + ) + ){ + // We can copy a one-byte-per-character series if it doesn't contain + // codepoints like 128 - 255 (pure ASCII is valid UTF-8) + // + return Copy_Bytes(VAL_BIN_AT(value), len); + } + else { + const void *data; + if (VAL_BYTE_SIZE(value)) { + opts &= ~OPT_ENC_UNISRC; // remove flag + data = VAL_BIN_AT(value); + } + else { + opts |= OPT_ENC_UNISRC; // add flag + data = VAL_UNI_AT(value); + } + return Make_UTF8_Binary(data, len, 0, opts); + } +} - if (GET_FLAG(opts, ENC_OPT_UTF8)) { - //ser = Encode_UTF8_Value(arg, len, opts); - } - if (GET_FLAG(opts, ENC_OPT_UTF16)) { - // ser = Encode_UTF16_Value(arg, len, FALSE, ccr); - } +// +// Strlen_Uni: C +// +// Rebol's current choice is to use UCS-2 internally, such that +// a REBUNI is an unsigned 16-bit number. This means that you +// cannot use wcslen() to determine a REBUNI* string size, as +// wchar_t is not guaranteed to be 2 bytes on every platform. +// +// Note: ideally this would use a routine like memmem() to look +// for two sequential zero bytes and then match only those aligned +// on an even byte boundary (to prevent spanning characters). But +// memmem() is not POSIX and only on GNU. So this uses a simple +// byte-by-byte search. +// +REBCNT Strlen_Uni(const REBUNI *up) +{ + const char *cp = cast(const char *, up) + 1; // "C"har vs. "U"nicode + assert(sizeof(REBUNI) == 2); + assert(cast(REBUPT, up) % 2 == 0); -// if (utf == 0 || ser == 0) { - // Enline_Bytes(); -// } + while (*cp || *(cp - 1)) cp += 2; - return ser; + assert(cast(REBUPT, cp - 1) % 2 == 0); + return cast(const REBUNI*, cp - 1) - up; } + diff --git a/src/core/t-bitset.c b/src/core/t-bitset.c index 8d8e769ea0..0d6111ddf3 100644 --- a/src/core/t-bitset.c +++ b/src/core/t-bitset.c @@ -1,620 +1,693 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-bitset.c -** Summary: bitset datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-bitset.c +// Summary: "bitset datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" #define MAX_BITSET 0x7fffffff -#define BITS_NOT(s) ((s)->size) +static inline REBOOL BITS_NOT(REBSER *s) { + assert(s->misc.negated == TRUE || s->misc.negated == FALSE); + return s->misc.negated; +} + +static inline void INIT_BITS_NOT(REBSER *s, REBOOL negated) { + s->misc.negated = negated; +} + -/*********************************************************************** -** -*/ REBINT CT_Bitset(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Bitset: C +// +REBINT CT_Bitset(const RELVAL *a, const RELVAL *b, REBINT mode) { - if (mode == 3) return VAL_SERIES(a) == VAL_SERIES(b); - if (mode >= 0) return ( - BITS_NOT(VAL_SERIES(a)) == BITS_NOT(VAL_SERIES(b)) - && - Compare_Binary_Vals(a, b) == 0 - ); - return -1; + if (mode >= 0) return ( + BITS_NOT(VAL_SERIES(a)) == BITS_NOT(VAL_SERIES(b)) + && + Compare_Binary_Vals(a, b) == 0 + ); + return -1; } -/*********************************************************************** -** -*/ REBSER *Make_Bitset(REBCNT len) -/* -** Return a bitset series (binary. -** -** len: the # of bits in the bitset. -** -***********************************************************************/ +// +// Make_Bitset: C +// +// Return a bitset series (binary. +// +// len: the # of bits in the bitset. +// +REBSER *Make_Bitset(REBCNT len) { - REBSER *ser; + REBSER *ser; - len = (len + 7) / 8; - ser = Make_Binary(len); - Clear_Series(ser); - SERIES_TAIL(ser) = len; - BITS_NOT(ser) = 0; + len = (len + 7) / 8; + ser = Make_Binary(len); + Clear_Series(ser); + SET_SERIES_LEN(ser, len); + INIT_BITS_NOT(ser, FALSE); - return ser; + return ser; } -/*********************************************************************** -** -*/ void Mold_Bitset(REBVAL *value, REB_MOLD *mold) -/* -***********************************************************************/ +// +// Mold_Bitset: C +// +void Mold_Bitset(const REBVAL *value, REB_MOLD *mold) { - REBSER *ser = VAL_SERIES(value); + REBSER *ser = VAL_SERIES(value); - if (BITS_NOT(ser)) Append_Bytes(mold->series, "[not bits "); - Mold_Binary(value, mold); - if (BITS_NOT(ser)) Append_Byte(mold->series, ']'); + if (BITS_NOT(ser)) Append_Unencoded(mold->series, "[not bits "); + Mold_Binary(value, mold); + if (BITS_NOT(ser)) Append_Codepoint_Raw(mold->series, ']'); } -/*********************************************************************** -** -*/ REBFLG MT_Bitset(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ -{ - REBFLG is_not = 0; - - if (IS_BLOCK(data)) { - REBINT len = Find_Max_Bit(data); - REBSER *ser; - if (len < 0 || len > 0xFFFFFF) Trap_Arg(data); - ser = Make_Bitset(len); - Set_Bits(ser, data, TRUE); - Set_Series(REB_BITSET, out, ser); - return TRUE; - } - - if (!IS_BINARY(data)) return FALSE; - Set_Series(REB_BITSET, out, Copy_Series_Value(data)); - BITS_NOT(VAL_SERIES(out)) = 0; - return TRUE; +// +// MAKE_Bitset: C +// +void MAKE_Bitset(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { +#ifdef NDEBUG + UNUSED(kind); +#else + assert(kind == REB_BITSET); +#endif + + REBINT len = Find_Max_Bit(arg); + + // Determine size of bitset. Returns -1 for errors. + // + // !!! R3-alpha construction syntax said 0xFFFFFF while the A_MAKE + // path used 0x0FFFFFFF. Assume A_MAKE was more likely right. + // + if (len < 0 || len > 0x0FFFFFFF) + fail (arg); + + REBSER *ser = Make_Bitset(len); + Init_Bitset(out, ser); + + if (IS_INTEGER(arg)) return; // allocated at a size, no contents. + + if (IS_BINARY(arg)) { + memcpy(BIN_HEAD(ser), VAL_BIN_AT(arg), len/8 + 1); + return; + } + + Set_Bits(ser, arg, TRUE); + INIT_BITS_NOT(VAL_SERIES(out), FALSE); +} + + +// +// TO_Bitset: C +// +void TO_Bitset(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { + MAKE_Bitset(out, kind, arg); } -/*********************************************************************** -** -*/ REBINT Find_Max_Bit(REBVAL *val) -/* -** Return integer number for the maximum bit number defined by -** the value. Used to determine how much space to allocate. -** -***********************************************************************/ +// +// Find_Max_Bit: C +// +// Return integer number for the maximum bit number defined by +// the value. Used to determine how much space to allocate. +// +REBINT Find_Max_Bit(const RELVAL *val) { - REBINT maxi = 0; - REBINT n; - - switch (VAL_TYPE(val)) { - - case REB_CHAR: - maxi = VAL_CHAR(val)+1; - break; - - case REB_INTEGER: - maxi = Int32s(val, 0); - break; - - case REB_STRING: - case REB_FILE: - case REB_EMAIL: - case REB_URL: - case REB_TAG: -// case REB_ISSUE: - n = VAL_INDEX(val); - if (VAL_BYTE_SIZE(val)) { - REBYTE *bp = VAL_BIN(val); - for (; n < (REBINT)VAL_TAIL(val); n++) - if (bp[n] > maxi) maxi = bp[n]; - } - else { - REBUNI *up = VAL_UNI(val); - for (; n < (REBINT)VAL_TAIL(val); n++) - if (up[n] > maxi) maxi = up[n]; - } - maxi++; - break; - - case REB_BINARY: - maxi = VAL_LEN(val) * 8 - 1; - if (maxi < 0) maxi = 0; - break; - - case REB_BLOCK: - for (val = VAL_BLK_DATA(val); NOT_END(val); val++) { - n = Find_Max_Bit(val); - if (n > maxi) maxi = n; - } - //maxi++; - break; - - case REB_NONE: - maxi = 0; - break; - - default: - return -1; - } - - return maxi; + REBINT maxi = 0; + REBINT n; + + switch (VAL_TYPE(val)) { + + case REB_CHAR: + maxi = VAL_CHAR(val) + 1; + break; + + case REB_INTEGER: + maxi = Int32s(val, 0); + break; + + case REB_STRING: + case REB_FILE: + case REB_EMAIL: + case REB_URL: + case REB_TAG: +// case REB_ISSUE: + n = VAL_INDEX(val); + if (VAL_BYTE_SIZE(val)) { + REBYTE *bp = VAL_BIN(val); + for (; n < cast(REBINT, VAL_LEN_HEAD(val)); n++) + if (bp[n] > maxi) maxi = bp[n]; + } + else { + REBUNI *up = VAL_UNI(val); + for (; n < cast(REBINT, VAL_LEN_HEAD(val)); n++) + if (up[n] > maxi) maxi = up[n]; + } + maxi++; + break; + + case REB_BINARY: + maxi = VAL_LEN_AT(val) * 8 - 1; + if (maxi < 0) maxi = 0; + break; + + case REB_BLOCK: + for (val = VAL_ARRAY_AT(val); NOT_END(val); val++) { + n = Find_Max_Bit(val); + if (n > maxi) maxi = n; + } + //maxi++; + break; + + case REB_BLANK: + maxi = 0; + break; + + default: + return -1; + } + + return maxi; } -/*********************************************************************** -** -*/ REBFLG Check_Bit(REBSER *bset, REBCNT c, REBFLG uncased) -/* -** Check bit indicated. Returns TRUE if set. -** If uncased is TRUE, try to match either upper or lower case. -** -***********************************************************************/ +// +// Check_Bit: C +// +// Check bit indicated. Returns TRUE if set. +// If uncased is TRUE, try to match either upper or lower case. +// +REBOOL Check_Bit(REBSER *bset, REBCNT c, REBOOL uncased) { - REBCNT i, n = c; - REBCNT tail = SERIES_TAIL(bset); - REBFLG flag = 0; + REBCNT i, n = c; + REBCNT tail = SER_LEN(bset); + REBOOL flag = FALSE; - if (uncased) { - if (n >= UNICODE_CASES) uncased = FALSE; // no need to check - else n = LO_CASE(c); - } + if (uncased) { + if (n >= UNICODE_CASES) uncased = FALSE; // no need to check + else n = LO_CASE(c); + } - // Check lowercase char: + // Check lowercase char: retry: - i = n >> 3; - if (i < tail) - flag = (0 != (BIN_HEAD(bset)[i] & (1 << (7 - ((n) & 7))))); - - // Check uppercase if needed: - if (uncased && !flag) { - n = UP_CASE(c); - uncased = FALSE; - goto retry; - } - - return (BITS_NOT(bset)) ? !flag : flag; + i = n >> 3; + if (i < tail) + flag = LOGICAL(BIN_HEAD(bset)[i] & (1 << (7 - ((n) & 7)))); + + // Check uppercase if needed: + if (uncased && !flag) { + n = UP_CASE(c); + uncased = FALSE; + goto retry; + } + + return BITS_NOT(bset) ? NOT(flag) : flag; } -/*********************************************************************** -** -*/ REBFLG Check_Bit_Str(REBSER *bset, REBVAL *val, REBFLG uncased) -/* -** If uncased is TRUE, try to match either upper or lower case. -** -***********************************************************************/ +// +// Check_Bit_Str: C +// +// If uncased is TRUE, try to match either upper or lower case. +// +REBOOL Check_Bit_Str(REBSER *bset, const REBVAL *val, REBOOL uncased) { - REBCNT n = VAL_INDEX(val); - - if (VAL_BYTE_SIZE(val)) { - REBYTE *bp = VAL_BIN(val); - for (; n < VAL_TAIL(val); n++) - if (Check_Bit(bset, bp[n], uncased)) return TRUE; - } - else { - REBUNI *up = VAL_UNI(val); - for (; n < VAL_TAIL(val); n++) - if (Check_Bit(bset, up[n], uncased)) return TRUE; - } - return FALSE; + REBCNT n = VAL_INDEX(val); + + if (VAL_BYTE_SIZE(val)) { + REBYTE *bp = VAL_BIN(val); + for (; n < VAL_LEN_HEAD(val); n++) + if (Check_Bit(bset, bp[n], uncased)) return TRUE; + } + else { + REBUNI *up = VAL_UNI(val); + for (; n < VAL_LEN_HEAD(val); n++) + if (Check_Bit(bset, up[n], uncased)) return TRUE; + } + return FALSE; } -/*********************************************************************** -** -*/ void Set_Bit(REBSER *bset, REBCNT n, REBOOL set) -/* -** Set/clear a single bit. Expand if needed. -** -***********************************************************************/ +// +// Set_Bit: C +// +// Set/clear a single bit. Expand if needed. +// +void Set_Bit(REBSER *bset, REBCNT n, REBOOL set) { - REBCNT i = n >> 3; - REBCNT tail = SERIES_TAIL(bset); - REBYTE bit; - - // Expand if not enough room: - if (i >= tail) { - if (!set) return; // no need to expand - Expand_Series(bset, tail, (i - tail) + 1); - CLEAR(BIN_SKIP(bset, tail), (i - tail) + 1); - } - - bit = 1 << (7 - ((n) & 7)); - if (set) - BIN_HEAD(bset)[i] |= bit; - else - BIN_HEAD(bset)[i] &= ~bit; + REBCNT i = n >> 3; + REBCNT tail = SER_LEN(bset); + REBYTE bit; + + // Expand if not enough room: + if (i >= tail) { + if (!set) return; // no need to expand + Expand_Series(bset, tail, (i - tail) + 1); + CLEAR(BIN_AT(bset, tail), (i - tail) + 1); + } + + bit = 1 << (7 - ((n) & 7)); + if (set) + BIN_HEAD(bset)[i] |= bit; + else + BIN_HEAD(bset)[i] &= ~bit; } -/*********************************************************************** -** -*/ void Set_Bit_Str(REBSER *bset, REBVAL *val, REBOOL set) -/* -***********************************************************************/ +// +// Set_Bit_Str: C +// +void Set_Bit_Str(REBSER *bset, const REBVAL *val, REBOOL set) { - REBCNT n = VAL_INDEX(val); - - if (VAL_BYTE_SIZE(val)) { - REBYTE *bp = VAL_BIN(val); - for (; n < VAL_TAIL(val); n++) - Set_Bit(bset, bp[n], set); - } - else { - REBUNI *up = VAL_UNI(val); - for (; n < VAL_TAIL(val); n++) - Set_Bit(bset, up[n], set); - } + REBCNT n = VAL_INDEX(val); + + if (VAL_BYTE_SIZE(val)) { + REBYTE *bp = VAL_BIN(val); + for (; n < VAL_LEN_HEAD(val); n++) + Set_Bit(bset, bp[n], set); + } + else { + REBUNI *up = VAL_UNI(val); + for (; n < VAL_LEN_HEAD(val); n++) + Set_Bit(bset, up[n], set); + } } -/*********************************************************************** -** -*/ REBFLG Set_Bits(REBSER *bset, REBVAL *val, REBOOL set) -/* -** Set/clear bits indicated by strings and chars and ranges. -** -***********************************************************************/ +// +// Set_Bits: C +// +// Set/clear bits indicated by strings and chars and ranges. +// +REBOOL Set_Bits(REBSER *bset, const REBVAL *val, REBOOL set) { - REBCNT n; - REBCNT c; - - if (IS_CHAR(val)) { - Set_Bit(bset, VAL_CHAR(val), set); - return TRUE; - } - - if (IS_INTEGER(val)) { - n = Int32s(val, 0); - if (n > MAX_BITSET) return 0; - Set_Bit(bset, n, set); - return TRUE; - } - - if (ANY_BINSTR(val)) { - Set_Bit_Str(bset, val, set); - return TRUE; - } - - if (!ANY_BLOCK(val)) Trap_Type(val); - - val = VAL_BLK_DATA(val); - if (IS_SAME_WORD(val, SYM_NOT)) { - BITS_NOT(bset) = TRUE; - val++; - } - - // Loop through block of bit specs: - for (; NOT_END(val); val++) { - - switch (VAL_TYPE(val)) { - - case REB_CHAR: - c = VAL_CHAR(val); - if (IS_SAME_WORD(val + 1, SYM__)) { - val += 2; - if (IS_CHAR(val)) { - n = VAL_CHAR(val); + FAIL_IF_READ_ONLY_SERIES(bset); + + REBCNT n; + REBCNT c; + + if (IS_CHAR(val)) { + Set_Bit(bset, VAL_CHAR(val), set); + return TRUE; + } + + if (IS_INTEGER(val)) { + n = Int32s(val, 0); + if (n > MAX_BITSET) return FALSE; + Set_Bit(bset, n, set); + return TRUE; + } + + if (ANY_BINSTR(val)) { + Set_Bit_Str(bset, val, set); + return TRUE; + } + + if (!ANY_ARRAY(val)) + fail (Error_Invalid_Type(VAL_TYPE(val))); + + RELVAL *item = VAL_ARRAY_AT(val); + + if ( + NOT_END(item) + && IS_WORD(item) + && VAL_WORD_SYM(item) == SYM_NOT + ){ + INIT_BITS_NOT(bset, TRUE); + item++; + } + + // Loop through block of bit specs: + for (; NOT_END(item); item++) { + + switch (VAL_TYPE(item)) { + case REB_CHAR: + c = VAL_CHAR(item); + if ( + NOT_END(item + 1) + && IS_WORD(item + 1) + && VAL_WORD_SYM(item + 1) == SYM_HYPHEN + ){ + item += 2; + if (IS_CHAR(item)) { + n = VAL_CHAR(item); span_bits: - if (n < c) Trap1(RE_PAST_END, val); - for (; c <= n; c++) Set_Bit(bset, c, set); - } else Trap_Arg(val); - } - else Set_Bit(bset, c, set); - break; - - case REB_INTEGER: - n = Int32s(val, 0); - if (n > MAX_BITSET) return 0; - if (IS_SAME_WORD(val + 1, SYM__)) { - c = n; - val += 2; - if (IS_INTEGER(val)) { - n = Int32s(val, 0); - goto span_bits; - } else Trap_Arg(val); - } - else Set_Bit(bset, n, set); - break; - - case REB_BINARY: - case REB_STRING: - case REB_FILE: - case REB_EMAIL: - case REB_URL: - case REB_TAG: -// case REB_ISSUE: - Set_Bit_Str(bset, val, set); - break; - - case REB_WORD: - // Special: BITS #{000...} - if (!IS_SAME_WORD(val, SYM_BITS)) return 0; - val++; - if (!IS_BINARY(val)) return 0; - n = VAL_LEN(val); - c = bset->tail; - if (n >= c) { - Expand_Series(bset, c, (n - c)); - CLEAR(BIN_SKIP(bset, c), (n - c)); - } - memcpy(BIN_HEAD(bset), VAL_BIN_DATA(val), n); - break; - - default: - return 0; - } - } - - return TRUE; + if (n < c) fail (Error_Past_End_Raw()); + for (; c <= n; c++) Set_Bit(bset, c, set); + } + else + fail (Error_Invalid_Arg_Core(item, VAL_SPECIFIER(val))); + } + else Set_Bit(bset, c, set); + break; + + case REB_INTEGER: + n = Int32s(KNOWN(item), 0); + if (n > MAX_BITSET) return FALSE; + if (IS_WORD(item + 1) && VAL_WORD_SYM(item + 1) == SYM_HYPHEN) { + c = n; + item += 2; + if (IS_INTEGER(item)) { + n = Int32s(KNOWN(item), 0); + goto span_bits; + } + else + fail (Error_Invalid_Arg_Core(item, VAL_SPECIFIER(val))); + } + else Set_Bit(bset, n, set); + break; + + case REB_BINARY: + case REB_STRING: + case REB_FILE: + case REB_EMAIL: + case REB_URL: + case REB_TAG: +// case REB_ISSUE: + Set_Bit_Str(bset, KNOWN(item), set); + break; + + case REB_WORD: + // Special: BITS #{000...} + if (!IS_WORD(item) || VAL_WORD_SYM(item) != SYM_BITS) + return FALSE; + item++; + if (!IS_BINARY(item)) return FALSE; + n = VAL_LEN_AT(item); + c = SER_LEN(bset); + if (n >= c) { + Expand_Series(bset, c, (n - c)); + CLEAR(BIN_AT(bset, c), (n - c)); + } + memcpy(BIN_HEAD(bset), VAL_BIN_AT(item), n); + break; + + default: + return FALSE; + } + } + + return TRUE; } - -/*********************************************************************** -** -*/ REBFLG Check_Bits(REBSER *bset, REBVAL *val, REBFLG uncased) -/* -** Check bits indicated by strings and chars and ranges. -** If uncased is TRUE, try to match either upper or lower case. -** -***********************************************************************/ + +// +// Check_Bits: C +// +// Check bits indicated by strings and chars and ranges. +// If uncased is TRUE, try to match either upper or lower case. +// +REBOOL Check_Bits(REBSER *bset, const REBVAL *val, REBOOL uncased) { - REBCNT n; - REBUNI c; + REBCNT n; + REBUNI c; + RELVAL *item; - if (IS_CHAR(val)) - return Check_Bit(bset, VAL_CHAR(val), uncased); + if (IS_CHAR(val)) + return Check_Bit(bset, VAL_CHAR(val), uncased); - if (IS_INTEGER(val)) - return Check_Bit(bset, Int32s(val, 0), uncased); + if (IS_INTEGER(val)) + return Check_Bit(bset, Int32s(val, 0), uncased); - if (ANY_BINSTR(val)) - return Check_Bit_Str(bset, val, uncased); + if (ANY_BINSTR(val)) + return Check_Bit_Str(bset, val, uncased); - if (!ANY_BLOCK(val)) Trap_Type(val); + if (!ANY_ARRAY(val)) + fail (Error_Invalid_Type(VAL_TYPE(val))); - // Loop through block of bit specs: - for (val = VAL_BLK_DATA(val); NOT_END(val); val++) { + // Loop through block of bit specs: + for (item = VAL_ARRAY_AT(val); NOT_END(item); item++) { - switch (VAL_TYPE(val)) { + switch (VAL_TYPE(item)) { - case REB_CHAR: - c = VAL_CHAR(val); - if (IS_SAME_WORD(val + 1, SYM__)) { - val += 2; - if (IS_CHAR(val)) { - n = VAL_CHAR(val); + case REB_CHAR: + c = VAL_CHAR(item); + if (IS_WORD(item + 1) && VAL_WORD_SYM(item + 1) == SYM_HYPHEN) { + item += 2; + if (IS_CHAR(item)) { + n = VAL_CHAR(item); scan_bits: - if (n < c) Trap1(RE_PAST_END, val); - for (; c <= n; c++) - if (Check_Bit(bset, c, uncased)) goto found; - } else Trap_Arg(val); - } - else - if (Check_Bit(bset, c, uncased)) goto found; - break; - - case REB_INTEGER: - n = Int32s(val, 0); - if (n > 0xffff) return 0; - if (IS_SAME_WORD(val + 1, SYM__)) { - c = n; - val += 2; - if (IS_INTEGER(val)) { - n = Int32s(val, 0); - goto scan_bits; - } else Trap_Arg(val); - } - else - if (Check_Bit(bset, n, uncased)) goto found; - break; - - case REB_BINARY: - case REB_STRING: - case REB_FILE: - case REB_EMAIL: - case REB_URL: - case REB_TAG: -// case REB_ISSUE: - if (Check_Bit_Str(bset, val, uncased)) goto found; - break; - - default: - Trap_Type(val); - } - } - return FALSE; + if (n < c) fail (Error_Past_End_Raw()); + for (; c <= n; c++) + if (Check_Bit(bset, c, uncased)) goto found; + } + else + fail (Error_Invalid_Arg_Core(item, VAL_SPECIFIER(val))); + } + else + if (Check_Bit(bset, c, uncased)) goto found; + break; + + case REB_INTEGER: + n = Int32s(KNOWN(item), 0); + if (n > 0xffff) return FALSE; + if (IS_WORD(item + 1) && VAL_WORD_SYM(item + 1) == SYM_HYPHEN) { + c = n; + item += 2; + if (IS_INTEGER(item)) { + n = Int32s(KNOWN(item), 0); + goto scan_bits; + } + else + fail (Error_Invalid_Arg_Core(item, VAL_SPECIFIER(val))); + } + else + if (Check_Bit(bset, n, uncased)) goto found; + break; + + case REB_BINARY: + case REB_STRING: + case REB_FILE: + case REB_EMAIL: + case REB_URL: + case REB_TAG: +// case REB_ISSUE: + if (Check_Bit_Str(bset, KNOWN(item), uncased)) goto found; + break; + + default: + fail (Error_Invalid_Type(VAL_TYPE(item))); + } + } + return FALSE; found: - return TRUE; + return TRUE; } -/*********************************************************************** -** -*/ REBINT PD_Bitset(REBPVS *pvs) -/* -***********************************************************************/ +// +// PD_Bitset: C +// +REBINT PD_Bitset(REBPVS *pvs) { - REBVAL *data = pvs->value; - REBVAL *val = pvs->setval; - REBSER *ser = VAL_SERIES(data); - REBFLG t; - - if (val == 0) { - if (Check_Bits(ser, pvs->select, 0)) { - SET_TRUE(pvs->store); - return PE_USE; - } - return PE_NONE; - } - - t = IS_TRUE(val); - if (BITS_NOT(ser)) t = !t; - if (Set_Bits(ser, pvs->select, (REBOOL)t)) - return PE_OK; - - return PE_BAD_SET; + REBSER *ser = VAL_SERIES(pvs->value); + + if (!pvs->opt_setval) { + if (Check_Bits(ser, pvs->picker, FALSE)) { + Init_Logic(pvs->store, TRUE); + return PE_USE_STORE; + } + return PE_NONE; + } + + if (Set_Bits( + ser, + pvs->picker, + BITS_NOT(ser) + ? IS_CONDITIONAL_FALSE(pvs->opt_setval) + : IS_CONDITIONAL_TRUE(pvs->opt_setval) + )) { + return PE_OK; + } + + fail (Error_Bad_Path_Set(pvs)); } -/*********************************************************************** -** -*/ void Trim_Tail_Zeros(REBSER *ser) -/* -** Remove extra zero bytes from end of byte string. -** -***********************************************************************/ +// +// Trim_Tail_Zeros: C +// +// Remove extra zero bytes from end of byte string. +// +void Trim_Tail_Zeros(REBSER *ser) { - REBCNT tail = SERIES_TAIL(ser); - REBYTE *bp = BIN_HEAD(ser); + REBCNT len = SER_LEN(ser); + REBYTE *bp = BIN_HEAD(ser); + + while (len > 0 && bp[len] == 0) + len--; - for (; tail > 0 && !bp[tail]; tail--); + if (bp[len] != 0) + len++; - if (bp[tail]) tail++; - SERIES_TAIL(ser) = tail; + SET_SERIES_LEN(ser, len); } -/*********************************************************************** -** -*/ REBTYPE(Bitset) -/* -***********************************************************************/ +// +// REBTYPE: C +// +REBTYPE(Bitset) { - REBYTE *data = 0; - REBVAL *value = D_ARG(1); - REBVAL *arg = D_ARG(2); - REBSER *ser; - REBINT len; - REBINT diff; - - if (action != A_MAKE && action != A_TO) - data = VAL_BIT_DATA(value); - - // Check must be in this order (to avoid checking a non-series value); - if (action >= A_TAKE && action <= A_SORT && IS_PROTECT_SERIES(VAL_SERIES(value))) - Trap0(RE_PROTECTED); - - switch (action) { - - // Define PICK for BITSETS? PICK's set bits and returns #? - // Add AND, OR, XOR - - case A_PICK: - case A_FIND: - if (!Check_Bits(VAL_SERIES(value), arg, D_REF(ARG_FIND_CASE))) return R_NONE; - return R_TRUE; - - case A_COMPLEMENT: - case A_NEGATE: - ser = Copy_Series(VAL_SERIES(value)); - BITS_NOT(ser) = !BITS_NOT(VAL_SERIES(value)); - Set_Series(REB_BITSET, value, ser); - break; - - case A_MAKE: - case A_TO: - // Determine size of bitset. Returns -1 for errors. - len = Find_Max_Bit(arg); - if (len < 0 || len > 0x0FFFFFFF) Trap_Arg(arg); - - ser = Make_Bitset(len); - Set_Series(REB_BITSET, value, ser); - - // Nothing more to do. - if (IS_INTEGER(arg)) break; - - if (IS_BINARY(arg)) { - memcpy(BIN_HEAD(ser), VAL_BIN_DATA(arg), len/8 + 1); - break; - } - // FALL THRU... - - case A_APPEND: // Accepts: #"a" "abc" [1 - 10] [#"a" - #"z"] etc. - case A_INSERT: - diff = TRUE; - goto set_bits; - - case A_POKE: - diff = Get_Logic_Arg(D_ARG(3)); -set_bits: - if (BITS_NOT(VAL_SERIES(value))) diff = !diff; - if (Set_Bits(VAL_SERIES(value), arg, (REBOOL)diff)) break; - Trap_Arg(arg); - - case A_REMOVE: // #"a" "abc" remove/part bs "abcd" yuk: /part ? - if (!D_REF(2)) Trap0(RE_MISSING_ARG); // /part required - if (Set_Bits(VAL_SERIES(value), D_ARG(3), FALSE)) break; - Trap_Arg(D_ARG(3)); - - case A_COPY: - VAL_SERIES(value) = Copy_Series_Value(value); - break; - - case A_LENGTHQ: - len = VAL_TAIL(value) * 8; - SET_INTEGER(value, len); - break; - - case A_TAILQ: - // Necessary to make EMPTY? work: - return (VAL_TAIL(value) == 0) ? R_TRUE : R_FALSE; - - case A_CLEAR: - Clear_Series(VAL_SERIES(value)); - break; - - case A_AND: - case A_OR: - case A_XOR: - if (!IS_BITSET(arg) && !IS_BINARY(arg)) - Trap_Math_Args(VAL_TYPE(arg), action); - VAL_SERIES(value) = ser = Xandor_Binary(action, value, arg); - Trim_Tail_Zeros(ser); - break; - - default: - Trap_Action(REB_BITSET, action); - } - - DS_RET_VALUE(value); - return R_RET; + REBVAL *value = D_ARG(1); + REBVAL *arg = D_ARGC > 1 ? D_ARG(2) : NULL; + REBSER *ser; + REBINT len; + REBOOL diff; + + // !!! Set_Bits does locked series check--what should the more general + // responsibility be for checking? + + switch (action) { + + // Add AND, OR, XOR + + case SYM_FIND: { + INCLUDE_PARAMS_OF_FIND; + + UNUSED(PAR(series)); + UNUSED(PAR(value)); + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(only)) + fail (Error_Bad_Refines_Raw()); + if (REF(skip)) { + UNUSED(ARG(size)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(last)) + fail (Error_Bad_Refines_Raw()); + if (REF(reverse)) + fail (Error_Bad_Refines_Raw()); + if (REF(tail)) + fail (Error_Bad_Refines_Raw()); + if (REF(match)) + fail (Error_Bad_Refines_Raw()); + + if (!Check_Bits(VAL_SERIES(value), arg, REF(case))) + return R_BLANK; + return R_TRUE; + } + + case SYM_COMPLEMENT: + case SYM_NEGATE: + ser = Copy_Sequence(VAL_SERIES(value)); + INIT_BITS_NOT(ser, NOT(BITS_NOT(VAL_SERIES(value)))); + Init_Bitset(value, ser); + break; + + case SYM_APPEND: // Accepts: #"a" "abc" [1 - 10] [#"a" - #"z"] etc. + case SYM_INSERT: + if (BITS_NOT(VAL_SERIES(value))) + diff = FALSE; + else + diff = TRUE; + + if (NOT(Set_Bits(VAL_SERIES(value), arg, diff))) + fail (arg); + break; + + case SYM_REMOVE: { + INCLUDE_PARAMS_OF_REMOVE; + + UNUSED(PAR(series)); + if (REF(map)) { + UNUSED(ARG(key)); + fail (Error_Bad_Refines_Raw()); + } + + if (NOT(REF(part))) + fail (Error_Missing_Arg_Raw()); + + if (Set_Bits(VAL_SERIES(value), ARG(limit), FALSE)) + break; + + fail (ARG(limit)); } + + case SYM_COPY: { + INCLUDE_PARAMS_OF_COPY; + + UNUSED(PAR(value)); + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(deep)) + fail (Error_Bad_Refines_Raw()); + if (REF(types)) { + UNUSED(ARG(kinds)); + fail (Error_Bad_Refines_Raw()); + } + + Init_Any_Series_At( + D_OUT, + REB_BITSET, + Copy_Sequence_At_Position(value), + VAL_INDEX(value) // !!! can bitset ever not be at 0? + ); + INIT_BITS_NOT(VAL_SERIES(D_OUT), BITS_NOT(VAL_SERIES(value))); + return R_OUT; } + + case SYM_LENGTH_OF: + len = VAL_LEN_HEAD(value) * 8; + Init_Integer(value, len); + break; + + case SYM_TAIL_Q: + // Necessary to make EMPTY? work: + return (VAL_LEN_HEAD(value) == 0) ? R_TRUE : R_FALSE; + + case SYM_CLEAR: + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(value)); + Clear_Series(VAL_SERIES(value)); + break; + + case SYM_AND_T: + case SYM_OR_T: + case SYM_XOR_T: + if (!IS_BITSET(arg) && !IS_BINARY(arg)) + fail (Error_Math_Args(VAL_TYPE(arg), action)); + ser = Xandor_Binary(action, value, arg); + Trim_Tail_Zeros(ser); + Init_Any_Series(D_OUT, VAL_TYPE(value), ser); + return R_OUT; + + default: + fail (Error_Illegal_Action(REB_BITSET, action)); + } + + Move_Value(D_OUT, value); + return R_OUT; } diff --git a/src/core/t-blank.c b/src/core/t-blank.c new file mode 100644 index 0000000000..13144d0874 --- /dev/null +++ b/src/core/t-blank.c @@ -0,0 +1,119 @@ +// +// File: %t-blank.c +// Summary: "Blank datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + +#include "sys-core.h" + +// +// CT_Unit: C +// +REBINT CT_Unit(const RELVAL *a, const RELVAL *b, REBINT mode) +{ + if (mode >= 0) return (VAL_TYPE(a) == VAL_TYPE(b)); + return -1; +} + + +// +// MAKE_Unit: C +// +void MAKE_Unit(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { + UNUSED(arg); + VAL_RESET_HEADER(out, kind); +} + + +// +// TO_Unit: C +// +void TO_Unit(REBVAL *out, enum Reb_Kind kind, const REBVAL *data) { + UNUSED(data); + VAL_RESET_HEADER(out, kind); +} + + +// +// REBTYPE: C +// +REBTYPE(Unit) +{ + REBVAL *val = D_ARG(1); + assert(!IS_VOID(val)); + + switch (action) { + case SYM_TAIL_Q: + return R_TRUE; + + case SYM_INDEX_OF: + case SYM_LENGTH_OF: + case SYM_SELECT_P: + case SYM_FIND: + case SYM_REMOVE: + case SYM_CLEAR: + case SYM_TAKE_P: + return R_BLANK; + + case SYM_COPY: { + if (IS_BLANK(val)) + return R_BLANK; // perhaps allow COPY on any type, as well. + break; } + + default: + break; + } + + fail (Error_Illegal_Action(VAL_TYPE(val), action)); +} + + +// +// CT_Handle: C +// +REBINT CT_Handle(const RELVAL *a, const RELVAL *b, REBINT mode) +{ + // Would it be meaningful to allow user code to compare HANDLE!? + // + UNUSED(a); + UNUSED(b); + UNUSED(mode); + + fail ("Currently comparing HANDLE! types is not allowed."); +} + + + +// +// REBTYPE: C +// +REBTYPE(Handle) +{ + UNUSED(frame_); + + fail (Error_Illegal_Action(REB_HANDLE, action)); +} diff --git a/src/core/t-block.c b/src/core/t-block.c old mode 100644 new mode 100755 index 8e5664417d..49a4f6031f --- a/src/core/t-block.c +++ b/src/core/t-block.c @@ -1,855 +1,1069 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-block.c -** Summary: block related datatypes -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-block.c +// Summary: "block related datatypes" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/*********************************************************************** -** -*/ REBINT CT_Block(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Array: C +// +// "Compare Type" dispatcher for the following types: (list here to help +// text searches) +// +// CT_Block() +// CT_Group() +// CT_Path() +// CT_Set_Path() +// CT_Get_Path() +// CT_Lit_Path() +// +REBINT CT_Array(const RELVAL *a, const RELVAL *b, REBINT mode) { - REBINT num; + REBINT num; - if (mode == 3) - return VAL_SERIES(a) == VAL_SERIES(b) && VAL_INDEX(a) == VAL_INDEX(b); - - num = Cmp_Block(a, b, mode > 1); - if (mode >= 0) return (num == 0); - if (mode == -1) return (num >= 0); - return (num > 0); + num = Cmp_Array(a, b, LOGICAL(mode == 1)); + if (mode >= 0) return (num == 0); + if (mode == -1) return (num >= 0); + return (num > 0); } -static void No_Nones(REBVAL *arg) { - arg = VAL_BLK_DATA(arg); - for (; NOT_END(arg); arg++) { - if (IS_NONE(arg)) Trap_Arg(arg); - } -} -/*********************************************************************** -** -*/ REBFLG MT_Block(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ -{ - REBCNT i; - - if (!ANY_BLOCK(data)) return FALSE; - if (type >= REB_PATH && type <= REB_LIT_PATH) - if (!ANY_WORD(VAL_BLK(data))) return FALSE; - - *out = *data++; - VAL_SET(out, type); - i = IS_INTEGER(data) ? Int32(data) - 1 : 0; - if (i > VAL_TAIL(out)) i = VAL_TAIL(out); // clip it - VAL_INDEX(out) = i; - return TRUE; +// +// MAKE_Array: C +// +// "Make Type" dispatcher for the following subtypes: +// +// MAKE_Block +// MAKE_Group +// MAKE_Path +// MAKE_Set_Path +// MAKE_Get_Path +// MAKE_Lit_Path +// +void MAKE_Array(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { + // + // `make block! 10` => creates array with certain initial capacity + // + if (IS_INTEGER(arg) || IS_DECIMAL(arg)) { + Init_Any_Array(out, kind, Make_Array(Int32s(arg, 0))); + return; + } + + // !!! See #2263 -- Ren-C has unified MAKE and construction syntax. A + // block parameter to MAKE should be arity 2...the existing array for + // the data source, and an offset from that array value's index: + // + // >> p1: #[path! [[a b c] 2]] + // == b/c + // + // >> head p1 + // == a/b/c + // + // >> block: [a b c] + // >> p2: make path! compose [(block) 2] + // == b/c + // + // >> append block 'd + // == [a b c d] + // + // >> p2 + // == b/c/d + // + // !!! This could be eased to not require the index, but without it then + // it can be somewhat confusing as to why [[a b c]] is needed instead of + // just [a b c] as the construction spec. + // + if (ANY_ARRAY(arg)) { + if ( + VAL_ARRAY_LEN_AT(arg) != 2 + || !ANY_ARRAY(VAL_ARRAY_AT(arg)) + || !IS_INTEGER(VAL_ARRAY_AT(arg) + 1) + ) { + goto bad_make; + } + + RELVAL *any_array = VAL_ARRAY_AT(arg); + REBINT index = VAL_INDEX(any_array) + Int32(VAL_ARRAY_AT(arg) + 1) - 1; + + if (index < 0 || index > cast(REBINT, VAL_LEN_HEAD(any_array))) + goto bad_make; + + REBSPC *derived = Derive_Specifier(VAL_SPECIFIER(arg), any_array); + Init_Any_Series_At_Core( + out, + kind, + SER(VAL_ARRAY(any_array)), + index, + derived + ); + + // !!! Previously this code would clear line break options on path + // elements, using `CLEAR_VAL_FLAG(..., VALUE_FLAG_LINE)`. But if + // arrays are allowed to alias each others contents, the aliasing + // via MAKE shouldn't modify the store. Line marker filtering out of + // paths should be part of the MOLDing logic -or- a path with embedded + // line markers should use construction syntax to preserve them. + + return; + } + + // !!! In R3-Alpha, MAKE and TO handled all cases except INTEGER! + // and TYPESET! in the same way. Ren-C switches MAKE of ANY-ARRAY! + // to be special (in order to compatible with construction syntax), + // continues the special treatment of INTEGER! by MAKE to mean + // a size, and disallows MAKE TYPESET!. This is a practical matter + // of addressing changes in #2263 and keeping legacy working, as + // opposed to endorsing any rationale in R3-Alpha's choices. + // + if (IS_TYPESET(arg)) + goto bad_make; + + TO_Array(out, kind, arg); + return; + +bad_make: + fail (Error_Bad_Make(kind, arg)); } -/*********************************************************************** -** -*/ REBCNT Find_Block(REBSER *series, REBCNT index, REBCNT end, REBVAL *target, REBCNT len, REBCNT flags, REBINT skip) -/* -** Flags are set according to: ALL_FIND_REFS -** -** Main Parameters: -** start - index to start search -** end - ending position -** len - length of target -** skip - skip factor -** dir - direction -** -** Comparison Parameters: -** case - case sensitivity -** wild - wild cards/keys -** -** Final Parmameters: -** tail - tail position -** match - sequence -** SELECT - (value that follows) -** -***********************************************************************/ -{ - REBVAL *value; - REBVAL *val; - REBCNT cnt; - REBCNT start = index; - - if (flags & (AM_FIND_REVERSE | AM_FIND_LAST)) { - skip = -1; - start = 0; - if (flags & AM_FIND_LAST) index = end - len; - else index--; - } - - // Optimized find word in block: - if (ANY_WORD(target)) { - for (; index >= start && index < end; index += skip) { - value = BLK_SKIP(series, index); - if (ANY_WORD(value)) { - cnt = (VAL_WORD_SYM(value) == VAL_WORD_SYM(target)); - if (flags & AM_FIND_CASE) { - // Must be same type and spelling: - if (cnt && VAL_TYPE(value) == VAL_TYPE(target)) return index; - } - else { - // Can be different type or alias: - if (cnt || VAL_WORD_CANON(value) == VAL_WORD_CANON(target)) return index; - } - } - if (flags & AM_FIND_MATCH) break; - } - return NOT_FOUND; - } - // Match a block against a block: - else if (ANY_BLOCK(target) && !(flags & AM_FIND_ONLY)) { - for (; index >= start && index < end; index += skip) { - cnt = 0; - value = BLK_SKIP(series, index); - for (val = VAL_BLK_DATA(target); NOT_END(val); val++, value++) { - if (0 != Cmp_Value(value, val, (REBOOL)(flags & AM_FIND_CASE))) break; - if (++cnt >= len) { - return index; - } - } - if (flags & AM_FIND_MATCH) break; - } - return NOT_FOUND; - } - // Find a datatype in block: - else if (IS_DATATYPE(target) || IS_TYPESET(target)) { - for (; index >= start && index < end; index += skip) { - value = BLK_SKIP(series, index); - // Used if's so we can trace it... - if (IS_DATATYPE(target)) { - if ((REBINT)VAL_TYPE(value) == VAL_DATATYPE(target)) return index; - if (IS_DATATYPE(value) && VAL_DATATYPE(value) == VAL_DATATYPE(target)) return index; - } - if (IS_TYPESET(target)) { - if (TYPE_CHECK(target, VAL_TYPE(value))) return index; - if (IS_DATATYPE(value) && TYPE_CHECK(target, VAL_DATATYPE(value))) return index; - if (IS_TYPESET(value) && EQUAL_TYPESET(value, target)) return index; - } - if (flags & AM_FIND_MATCH) break; - } - return NOT_FOUND; - } - // All other cases: - else { - for (; index >= start && index < end; index += skip) { - value = BLK_SKIP(series, index); - if (0 == Cmp_Value(value, target, (REBOOL)(flags & AM_FIND_CASE))) return index; - if (flags & AM_FIND_MATCH) break; - } - return NOT_FOUND; - } +// +// TO_Array: C +// +void TO_Array(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { + if (IS_TYPESET(arg)) { + // + // This makes a block of types out of a typeset. Previously it was + // restricted to only BLOCK!, now it lets you turn a typeset into + // a GROUP! or a PATH!, etc. + // + Init_Any_Array(out, kind, Typeset_To_Array(arg)); + } + else if (ANY_ARRAY(arg)) { + // + // `to group! [1 2 3]` etc. -- copy the array data at the index + // position and change the type. (Note: MAKE does not copy the + // data, but aliases it under a new kind.) + // + Init_Any_Array( + out, + kind, + Copy_Values_Len_Shallow( + VAL_ARRAY_AT(arg), VAL_SPECIFIER(arg), VAL_ARRAY_LEN_AT(arg) + ) + ); + } + else if (IS_STRING(arg)) { + // + // `to block! "some string"` historically scans the source, so you + // get an unbound code array. Because the string may contain REBUNI + // characters, it may have to be converted to UTF8 before being + // used with the scanner. + // + REBCNT index; + REBSER *utf8 = Temp_Bin_Str_Managed(arg, &index, NULL); + PUSH_GUARD_SERIES(utf8); + REBSTR * const filename = Canon(SYM___ANONYMOUS__); + Init_Any_Array( + out, + kind, + Scan_UTF8_Managed(BIN_HEAD(utf8), BIN_LEN(utf8), filename) + ); + DROP_GUARD_SERIES(utf8); + } + else if (IS_BINARY(arg)) { + // + // `to block! #{00BDAE....}` assumes the binary data is UTF8, and + // goes directly to the scanner to make an unbound code array. + // + REBSTR * const filename = Canon(SYM___ANONYMOUS__); + Init_Any_Array( + out, + kind, + Scan_UTF8_Managed(VAL_BIN_AT(arg), VAL_LEN_AT(arg), filename) + ); + } + else if (IS_MAP(arg)) { + Init_Any_Array(out, kind, Map_To_Array(VAL_MAP(arg), 0)); + } + else if (ANY_CONTEXT(arg)) { + Init_Any_Array(out, kind, Context_To_Array(VAL_CONTEXT(arg), 3)); + } + else if (IS_VECTOR(arg)) { + Init_Any_Array(out, kind, Vector_To_Array(arg)); + } + else { + // !!! The general case of not having any special conversion behavior + // in R3-Alpha is just to fall through to making a 1-element block + // containing the value. This may seem somewhat random, and an + // error may be preferable. + // + Init_Any_Array(out, kind, Copy_Values_Len_Shallow(arg, SPECIFIED, 1)); + } } -/*********************************************************************** -** -*/ void Modify_Blockx(REBCNT action, REBVAL *block, REBVAL *arg) -/* -** Actions: INSERT, APPEND, CHANGE -** -** block [block!] {Series at point to insert} -** value [any-type!] {The value to insert} -** /part {Limits to a given length or position.} -** length [number! series! pair!] -** /only {Inserts a series as a series.} -** /dup {Duplicates the insert a specified number of times.} -** count [number! pair!] -** -** Add: -** Handle insert [] () case -** What does insert () [] do? -** /deep option for cloning subcontents? -** -***********************************************************************/ -{ - REBSER *series = VAL_SERIES(block); - REBCNT index = VAL_INDEX(block); - REBCNT tail = VAL_TAIL(block); - REBFLG only = DS_REF(AN_ONLY); - REBINT rlen; // length to be removed - REBINT ilen = 1; // length to be inserted - REBINT cnt = 1; // DUP count - REBINT size; - REBFLG is_blk = FALSE; // arg is a block not a value - - // Length of target (may modify index): (arg can be anything) - rlen = Partial1((action == A_CHANGE) ? block : arg, DS_ARG(AN_LENGTH)); - - index = VAL_INDEX(block); - if (action == A_APPEND || index > tail) index = tail; - - // Check /PART, compute LEN: - if (!only && ANY_BLOCK(arg)) { - is_blk = TRUE; // arg is a block - // Are we modifying ourselves? If so, copy arg block first: - if (series == VAL_SERIES(arg)) { - VAL_SERIES(arg) = Copy_Block(VAL_SERIES(arg), VAL_INDEX(arg)); - VAL_INDEX(arg) = 0; - } - // Length of insertion: - ilen = (action != A_CHANGE && DS_REF(AN_PART)) ? rlen : VAL_LEN(arg); - } - - // Get /DUP count: - if (DS_REF(AN_DUP)) { - cnt = Int32(DS_ARG(AN_COUNT)); - if (cnt <= 0) return; // no changes - } - - // Total to insert: - size = cnt * ilen; - - if (action != A_CHANGE) { - // Always expand series for INSERT and APPEND actions: - Expand_Series(series, index, size); - } else { - if (size > rlen) - Expand_Series(series, index, size-rlen); - else if (size < rlen && DS_REF(AN_PART)) - Remove_Series(series, index, rlen-size); - else if (size + index > tail) { - EXPAND_SERIES_TAIL(series, size - (tail - index)); - } - } - - if (is_blk) arg = VAL_BLK_DATA(arg); - - // For dup count: - VAL_INDEX(block) = (action == A_APPEND) ? 0 : size + index; - - index *= SERIES_WIDE(series); // loop invariant - ilen *= SERIES_WIDE(series); // loop invariant - for (; cnt > 0; cnt--) { - memcpy(series->data + index, (REBYTE *)arg, ilen); - index += ilen; - } - BLK_TERM(series); +// +// Find_In_Array: C +// +// Flags are set according to: ALL_FIND_REFS +// +// Main Parameters: +// start - index to start search +// end - ending position +// len - length of target +// skip - skip factor +// dir - direction +// +// Comparison Parameters: +// case - case sensitivity +// wild - wild cards/keys +// +// Final Parmameters: +// tail - tail position +// match - sequence +// SELECT - (value that follows) +// +REBCNT Find_In_Array( + REBARR *array, + REBCNT index, + REBCNT end, + const RELVAL *target, + REBCNT len, + REBFLGS flags, + REBINT skip +) { + RELVAL *value; + RELVAL *val; + REBCNT cnt; + REBCNT start = index; + + if (flags & (AM_FIND_REVERSE | AM_FIND_LAST)) { + skip = -1; + start = 0; + if (flags & AM_FIND_LAST) index = end - len; + else index--; + } + + // Optimized find word in block: + if (ANY_WORD(target)) { + for (; index >= start && index < end; index += skip) { + value = ARR_AT(array, index); + if (ANY_WORD(value)) { + cnt = (VAL_WORD_SPELLING(value) == VAL_WORD_SPELLING(target)); + if (flags & AM_FIND_CASE) { + // Must be same type and spelling: + if (cnt && VAL_TYPE(value) == VAL_TYPE(target)) return index; + } + else { + // Can be different type or alias: + if (cnt || VAL_WORD_CANON(value) == VAL_WORD_CANON(target)) return index; + } + } + if (flags & AM_FIND_MATCH) break; + } + return NOT_FOUND; + } + // Match a block against a block: + else if (ANY_ARRAY(target) && !(flags & AM_FIND_ONLY)) { + for (; index >= start && index < end; index += skip) { + cnt = 0; + value = ARR_AT(array, index); + for (val = VAL_ARRAY_AT(target); NOT_END(val); val++, value++) { + if (0 != Cmp_Value(value, val, LOGICAL(flags & AM_FIND_CASE))) + break; + if (++cnt >= len) { + return index; + } + } + if (flags & AM_FIND_MATCH) break; + } + return NOT_FOUND; + } + // Find a datatype in block: + else if (IS_DATATYPE(target) || IS_TYPESET(target)) { + for (; index >= start && index < end; index += skip) { + value = ARR_AT(array, index); + // Used if's so we can trace it... + if (IS_DATATYPE(target)) { + if (VAL_TYPE(value) == VAL_TYPE_KIND(target)) return index; + if (IS_DATATYPE(value) && VAL_TYPE_KIND(value) == VAL_TYPE_KIND(target)) return index; + } + if (IS_TYPESET(target)) { + if (TYPE_CHECK(target, VAL_TYPE(value))) return index; + if (IS_DATATYPE(value) && TYPE_CHECK(target, VAL_TYPE_KIND(value))) return index; + if (IS_TYPESET(value) && EQUAL_TYPESET(value, target)) return index; + } + if (flags & AM_FIND_MATCH) break; + } + return NOT_FOUND; + } + // All other cases: + else { + for (; index >= start && index < end; index += skip) { + value = ARR_AT(array, index); + if ( + 0 == Cmp_Value( + value, target, LOGICAL(flags & AM_FIND_CASE) + ) + ) { + return index; + } + + if (flags & AM_FIND_MATCH) break; + } + return NOT_FOUND; + } } -/*********************************************************************** -** -*/ void Make_Block_Type(REBFLG make, REBVAL *value, REBVAL *arg) -/* -** Value can be: -** 1. a datatype (e.g. BLOCK!) -** 2. a value (e.g. [...]) -** -** Arg can be: -** 1. integer (length of block) -** 2. block (copy it) -** 3. value (convert to a block) -** -***********************************************************************/ -{ - REBCNT type; - REBCNT len; - REBSER *ser; - - // make block! ... - if (IS_DATATYPE(value)) - type = VAL_DATATYPE(value); - else // make [...] .... - type = VAL_TYPE(value); - - // make block! [1 2 3] - if (ANY_BLOCK(arg)) { - len = VAL_BLK_LEN(arg); - if (len > 0 && type >= REB_PATH && type <= REB_LIT_PATH) - No_Nones(arg); - ser = Copy_Values(VAL_BLK_DATA(arg), len); - goto done; - } - - if (IS_STRING(arg)) { - REBCNT index, len = 0; - VAL_SERIES(arg) = Prep_Bin_Str(arg, &index, &len); // (keeps safe) - ser = Scan_Source(VAL_BIN(arg), VAL_LEN(arg)); - goto done; - } - - if (IS_BINARY(arg)) { - ser = Scan_Source(VAL_BIN_DATA(arg), VAL_LEN(arg)); - goto done; - } - - if (IS_MAP(arg)) { - ser = Map_To_Block(VAL_SERIES(arg), 0); - goto done; - } - - if (ANY_OBJECT(arg)) { - ser = Make_Object_Block(VAL_OBJ_FRAME(arg), 3); - goto done; - } - - if (IS_VECTOR(arg)) { - ser = Make_Vector_Block(arg); - goto done; - } - -// if (make && IS_NONE(arg)) { -// ser = Make_Block(0); -// goto done; -// } - - // to block! typset - if (!make && IS_TYPESET(arg) && type == REB_BLOCK) { - Set_Block(value, Typeset_To_Block(arg)); - return; - } - - if (make) { - // make block! 10 - if (IS_INTEGER(arg) || IS_DECIMAL(arg)) { - len = Int32s(arg, 0); - Set_Series(type, value, Make_Block(len)); - return; - } - Trap_Arg(arg); - } - - ser = Copy_Values(arg, 1); - -done: - Set_Series(type, value, ser); - return; -} +struct sort_flags { + REBOOL cased; + REBOOL reverse; + REBCNT offset; + REBVAL *comparator; + REBOOL all; // !!! not used? +}; + -// WARNING! Not re-entrant. !!! Must find a way to push it on stack? -static struct { - REBFLG cased; - REBFLG reverse; - REBCNT offset; - REBVAL *compare; -} sort_flags = {0}; - -/*********************************************************************** -** -*/ static int Compare_Val(const void *v1, const void *v2) -/* -***********************************************************************/ +// +// Compare_Val: C +// +static int Compare_Val(void *arg, const void *v1, const void *v2) { - // !!!! BE SURE that 64 bit large difference comparisons work - - if (sort_flags.reverse) - return Cmp_Value((REBVAL*)v2+sort_flags.offset, (REBVAL*)v1+sort_flags.offset, sort_flags.cased); - else - return Cmp_Value((REBVAL*)v1+sort_flags.offset, (REBVAL*)v2+sort_flags.offset, sort_flags.cased); - -/* - REBI64 n = VAL_INT64((REBVAL*)v1) - VAL_INT64((REBVAL*)v2); - if (n > 0) return 1; - if (n < 0) return -1; - return 0; -*/ + struct sort_flags *flags = cast(struct sort_flags*, arg); + + // !!!! BE SURE that 64 bit large difference comparisons work + + if (flags->reverse) + return Cmp_Value( + cast(const RELVAL*, v2) + flags->offset, + cast(const RELVAL*, v1) + flags->offset, + flags->cased + ); + else + return Cmp_Value( + cast(const RELVAL*, v1) + flags->offset, + cast(const RELVAL*, v2) + flags->offset, + flags->cased + ); } -/*********************************************************************** -** -*/ static int Compare_Call(const void *v1, const void *v2) -/* -***********************************************************************/ +// +// Compare_Val_Custom: C +// +static int Compare_Val_Custom(void *arg, const void *v1, const void *v2) { - REBVAL *val; - - if (sort_flags.reverse) - val = Apply_Func(0, sort_flags.compare, v1, v2, 0); - else - val = Apply_Func(0, sort_flags.compare, v2, v1, 0); - - if (IS_LOGIC(val)) { - if (IS_TRUE(val)) return 1; - return -1; - } - if (IS_INTEGER(val)) { - if (VAL_INT64(val) > 0) return 1; - if (VAL_INT64(val) == 0) return 0; - return -1; - } - if (IS_DECIMAL(val)) { - if (VAL_DECIMAL(val) > 0) return 1; - if (VAL_DECIMAL(val) == 0) return 0; - return -1; - } - if (IS_TRUE(val)) return 1; - return -1; + struct sort_flags *flags = cast(struct sort_flags*, arg); + + const REBOOL fully = TRUE; // error if not all arguments consumed + + DECLARE_LOCAL (result); + if (Apply_Only_Throws( + result, + fully, + flags->comparator, + flags->reverse ? v1 : v2, + flags->reverse ? v2 : v1, + END + )) { + fail (Error_No_Catch_For_Throw(result)); + } + + REBINT tristate = -1; + + if (IS_LOGIC(result)) { + if (VAL_LOGIC(result)) + tristate = 1; + } + else if (IS_INTEGER(result)) { + if (VAL_INT64(result) > 0) + tristate = 1; + else if (VAL_INT64(result) == 0) + tristate = 0; + } + else if (IS_DECIMAL(result)) { + if (VAL_DECIMAL(result) > 0) + tristate = 1; + else if (VAL_DECIMAL(result) == 0) + tristate = 0; + } + else if (IS_CONDITIONAL_TRUE(result)) + tristate = 1; + + return tristate; } -/*********************************************************************** -** -*/ static void Sort_Block(REBVAL *block, REBFLG ccase, REBVAL *skipv, REBVAL *compv, REBVAL *part, REBFLG all, REBFLG rev) -/* -** series [series!] -** /case {Case sensitive sort} -** /skip {Treat the series as records of fixed size} -** size [integer!] {Size of each record} -** /compare {Comparator offset, block or function} -** comparator [integer! block! function!] -** /part {Sort only part of a series} -** length [number! series!] {Length of series to sort} -** /all {Compare all fields} -** /reverse {Reverse sort order} -** -***********************************************************************/ -{ - REBCNT len; - REBCNT skip = 1; - REBCNT size = sizeof(REBVAL); -// int (*sfunc)(const void *v1, const void *v2); - - sort_flags.cased = ccase; - sort_flags.reverse = rev; - sort_flags.compare = 0; - sort_flags.offset = 0; - - if (IS_INTEGER(compv)) sort_flags.offset = Int32(compv)-1; - if (ANY_FUNC(compv)) sort_flags.compare = compv; - - // Determine length of sort: - len = Partial1(block, part); - if (len <= 1) return; - - // Skip factor: - if (!IS_NONE(skipv)) { - skip = Get_Num_Arg(skipv); - if (skip <= 0 || len % skip != 0 || skip > len) - Trap_Range(skipv); - } - - // Use fast quicksort library function: - if (skip > 1) len /= skip, size *= skip; - - if (sort_flags.compare) - qsort((void *)VAL_BLK_DATA(block), len, size, Compare_Call); - else - qsort((void *)VAL_BLK_DATA(block), len, size, Compare_Val); - +// +// Sort_Block: C +// +// series [any-series!] +// /case {Case sensitive sort} +// /skip {Treat the series as records of fixed size} +// size [integer!] {Size of each record} +// /compare {Comparator offset, block or function} +// comparator [integer! block! function!] +// /part {Sort only part of a series} +// limit [any-number! any-series!] {Length of series to sort} +// /all {Compare all fields} +// /reverse {Reverse sort order} +// +static void Sort_Block( + REBVAL *block, + REBOOL ccase, + REBVAL *skipv, + REBVAL *compv, + REBVAL *part, + REBOOL all, + REBOOL rev +) { + struct sort_flags flags; + flags.cased = ccase; + flags.reverse = rev; + flags.all = all; // !!! not used? + + if (IS_FUNCTION(compv)) { + flags.comparator = compv; + flags.offset = 0; + } + else if (IS_INTEGER(compv)) { + flags.comparator = NULL; + flags.offset = Int32(compv) - 1; + } + else { + assert(IS_VOID(compv)); + flags.comparator = NULL; + flags.offset = 0; + } + + // Determine length of sort: + REBCNT len; + Partial1(block, part, &len); + if (len <= 1) + return; + + // Skip factor: + REBCNT skip; + if (!IS_VOID(skipv)) { + skip = Get_Num_From_Arg(skipv); + if (skip <= 0 || len % skip != 0 || skip > len) + fail (Error_Out_Of_Range(skipv)); + } + else + skip = 1; + + reb_qsort_r( + VAL_ARRAY_AT(block), + len / skip, + sizeof(REBVAL) * skip, + &flags, + flags.comparator != NULL ? &Compare_Val_Custom : &Compare_Val + ); } -/*********************************************************************** -** -*/ static void Trim_Block(REBSER *ser, REBCNT index, REBCNT flags) -/* -** See Trim_String(). -** -***********************************************************************/ +// +// Shuffle_Block: C +// +void Shuffle_Block(REBVAL *value, REBOOL secure) { - REBVAL *blk = BLK_HEAD(ser); - REBCNT out = index; - REBCNT end = ser->tail; - - if (flags & AM_TRIM_TAIL) { - for (; end >= (index+1); end--) { - if (VAL_TYPE(blk+end-1) > REB_NONE) break; - } - Remove_Series(ser, end, ser->tail - end); - if (!(flags & AM_TRIM_HEAD) || index >= end) return; - } - - if (flags & AM_TRIM_HEAD) { - for (; index < end; index++) { - if (VAL_TYPE(blk+index) > REB_NONE) break; - } - Remove_Series(ser, out, index - out); - } - - if (flags == 0) { - for (; index < end; index++) { - if (VAL_TYPE(blk+index) > REB_NONE) { - *BLK_SKIP(ser, out) = blk[index]; - out++; - } - } - Remove_Series(ser, out, end - out); - } + REBCNT n; + REBCNT k; + REBCNT idx = VAL_INDEX(value); + RELVAL *data = VAL_ARRAY_HEAD(value); + + // Rare case where RELVAL bit copying is okay...between spots in the + // same array. + // + RELVAL swap; + + for (n = VAL_LEN_AT(value); n > 1;) { + k = idx + (REBCNT)Random_Int(secure) % n; + n--; + swap = data[k]; + data[k] = data[n + idx]; + data[n + idx] = swap; + } } -/*********************************************************************** -** -*/ void Shuffle_Block(REBVAL *value, REBFLG secure) -/* -***********************************************************************/ +// +// PD_Array: C +// +// Path dispatch for the following types: +// +// PD_Block +// PD_Group +// PD_Path +// PD_Get_Path +// PD_Set_Path +// PD_Lit_Path +// +REBINT PD_Array(REBPVS *pvs) { - REBCNT n; - REBCNT k; - REBCNT idx = VAL_INDEX(value); - REBVAL *data = VAL_BLK(value); - REBVAL swap; - - for (n = VAL_LEN(value); n > 1;) { - k = idx + (REBCNT)Random_Int(secure) % n; - n--; - swap = data[k]; - data[k] = data[n + idx]; - data[n + idx] = swap; - } + REBINT n = 0; + + /* Issues!!! + a/1.3 + a/not-found: 10 error or append? + a/not-followed: 10 error or append? + */ + + if (IS_INTEGER(pvs->picker)) { + n = Int32(pvs->picker) + VAL_INDEX(pvs->value) - 1; + } + else if (IS_WORD(pvs->picker)) { + n = Find_Word_In_Array( + VAL_ARRAY(pvs->value), + VAL_INDEX(pvs->value), + VAL_WORD_CANON(pvs->picker) + ); + if (cast(REBCNT, n) != NOT_FOUND) n++; + } + else if (IS_LOGIC(pvs->picker)) { + // + // !!! PICK in R3-Alpha historically would use a logic TRUE to get + // the first element in an array, and a logic FALSE to get the second. + // It did this regardless of how many elements were in the array. + // (For safety, it has been suggested non-binary arrays should fail). + // But path picking would act like you had written SELECT and looked + // for the item to come after a TRUE. With the merging of path + // picking and PICK, this changes the behavior. + // + if (VAL_LOGIC(pvs->picker)) + n = VAL_INDEX(pvs->value); + else + n = VAL_INDEX(pvs->value) + 1; + } + else { + // other values: + n = 1 + Find_In_Array_Simple( + VAL_ARRAY(pvs->value), + VAL_INDEX(pvs->value), + pvs->picker + ); + } + + if (n < 0 || cast(REBCNT, n) >= VAL_LEN_HEAD(pvs->value)) { + if (pvs->opt_setval) + fail (Error_Bad_Path_Select(pvs)); + + Init_Void(pvs->store); + return PE_USE_STORE; + } + + if (pvs->opt_setval) + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(pvs->value)); + + pvs->value_specifier = Derive_Specifier(pvs->value_specifier, pvs->value); + pvs->value = VAL_ARRAY_AT_HEAD(pvs->value, n); + +#if !defined(NDEBUG) + if (pvs->value_specifier == SPECIFIED && IS_RELATIVE(pvs->value)) { + printf("Relative value found in PD_Array with no specifier\n"); + panic (pvs->value); + } +#endif + + return PE_SET_IF_END; } -/*********************************************************************** -** -*/ REBINT PD_Block(REBPVS *pvs) -/* -***********************************************************************/ +// +// Pick_Block: C +// +// Fills out with void if no pick. +// +RELVAL *Pick_Block(REBVAL *out, const REBVAL *block, const REBVAL *picker) { - REBINT n = 0; - - /* Issues!!! - a/1.3 - a/not-found: 10 error or append? - a/not-followed: 10 error or append? - */ - - if (IS_INTEGER(pvs->select)) { - n = Int32(pvs->select) + VAL_INDEX(pvs->value) - 1; - } - else if (IS_WORD(pvs->select)) { - n = Find_Word(VAL_SERIES(pvs->value), VAL_INDEX(pvs->value), VAL_WORD_CANON(pvs->select)); - if (n != NOT_FOUND) n++; - } - else { - // other values: - n = Find_Block_Simple(VAL_SERIES(pvs->value), VAL_INDEX(pvs->value), pvs->select) + 1; - } - - if (n < 0 || (REBCNT)n >= VAL_TAIL(pvs->value)) { - if (pvs->setval) return PE_BAD_SELECT; - return PE_NONE; - } - - if (pvs->setval) TRAP_PROTECT(VAL_SERIES(pvs->value)); - pvs->value = VAL_BLK_SKIP(pvs->value, n); - // if valset - check PROTECT on block - //if (NOT_END(pvs->path+1)) Next_Path(pvs); return PE_OK; - return PE_SET; + REBINT n = Get_Num_From_Arg(picker); + n += VAL_INDEX(block) - 1; + if (n < 0 || cast(REBCNT, n) >= VAL_LEN_HEAD(block)) { + Init_Void(out); + return NULL; + } + + RELVAL *slot = VAL_ARRAY_AT_HEAD(block, n); + Derelativize(out, slot, VAL_SPECIFIER(block)); + return slot; } -/*********************************************************************** -** -*/ REBVAL *Pick_Block(REBVAL *block, REBVAL *selector) -/* -***********************************************************************/ +// +// REBTYPE: C +// +// Implementation of type dispatch of the following: +// +// REBTYPE(Block) +// REBTYPE(Group) +// REBTYPE(Path) +// REBTYPE(Get_Path) +// REBTYPE(Set_Path) +// REBTYPE(Lit_Path) +// +REBTYPE(Array) { - REBINT n = 0; + REBVAL *value = D_ARG(1); + REBVAL *arg = D_ARGC > 1 ? D_ARG(2) : NULL; - n = Get_Num_Arg(selector); - n += VAL_INDEX(block) - 1; - if (n < 0 || (REBCNT)n >= VAL_TAIL(block)) return 0; - return VAL_BLK_SKIP(block, n); + // Common operations for any series type (length, head, etc.) + { + REB_R r = Series_Common_Action_Maybe_Unhandled(frame_, action); + if (r != R_UNHANDLED) + return r; + } + + // NOTE: Partial1() used below can mutate VAL_INDEX(value), be aware :-/ + // + REBARR *array = VAL_ARRAY(value); + REBCNT index = VAL_INDEX(value); + REBSPC *specifier = VAL_SPECIFIER(value); + + switch (action) { + + case SYM_TAKE_P: { + INCLUDE_PARAMS_OF_TAKE_P; + + UNUSED(PAR(series)); + if (REF(deep)) + fail (Error_Bad_Refines_Raw()); + + REBCNT len; + + FAIL_IF_READ_ONLY_ARRAY(array); + + if (REF(part)) { + Partial1(value, ARG(limit), &len); + if (len == 0) + goto return_empty_block; + + assert(VAL_LEN_HEAD(value) >= len); + } + else + len = 1; + + index = VAL_INDEX(value); // /part can change index + + if (REF(last)) + index = VAL_LEN_HEAD(value) - len; + + if (index >= VAL_LEN_HEAD(value)) { + if (NOT(REF(part))) + return R_VOID; + + goto return_empty_block; + } + + if (REF(part)) + Init_Block( + D_OUT, Copy_Array_At_Max_Shallow(array, index, specifier, len) + ); + else + Derelativize(D_OUT, &ARR_HEAD(array)[index], specifier); + + Remove_Series(SER(array), index, len); + return R_OUT; + } + + //-- Search: + + case SYM_FIND: + case SYM_SELECT_P: { + INCLUDE_PARAMS_OF_FIND; + + UNUSED(PAR(series)); + UNUSED(PAR(value)); // aliased as arg + + REBINT len = ANY_ARRAY(arg) ? VAL_ARRAY_LEN_AT(arg) : 1; + + REBCNT limit; + if (REF(part)) + Partial1(value, ARG(limit), &limit); + else + limit = VAL_LEN_HEAD(value); + + REBFLGS flags = ( + (REF(only) ? AM_FIND_ONLY : 0) + | (REF(match) ? AM_FIND_MATCH : 0) + | (REF(reverse) ? AM_FIND_REVERSE : 0) + | (REF(case) ? AM_FIND_CASE : 0) + | (REF(last) ? AM_FIND_LAST : 0) + ); + + REBCNT skip = REF(skip) ? Int32s(ARG(size), 1) : 1; + + REBCNT ret = Find_In_Array( + array, index, limit, arg, len, flags, skip + ); + + if (ret >= limit) { + if (action == SYM_FIND) + return R_BLANK; + return R_VOID; + } + + if (REF(only)) + len = 1; + + if (action == SYM_FIND) { + if (REF(tail) || REF(match)) + ret += len; + VAL_INDEX(value) = ret; + Move_Value(D_OUT, value); + } + else { + ret += len; + if (ret >= limit) { + if (action == SYM_FIND) + return R_BLANK; + return R_VOID; + } + Derelativize(D_OUT, ARR_AT(array, ret), specifier); + } + return R_OUT; + } + + //-- Modification: + case SYM_APPEND: + case SYM_INSERT: + case SYM_CHANGE: { + INCLUDE_PARAMS_OF_INSERT; + + UNUSED(PAR(series)); + UNUSED(PAR(value)); + + // Length of target (may modify index): (arg can be anything) + // + REBCNT len; + Partial1( + (action == SYM_CHANGE) + ? value + : arg, + ARG(limit), + &len + ); + + FAIL_IF_READ_ONLY_ARRAY(array); + index = VAL_INDEX(value); + + REBFLGS flags = 0; + if (REF(only)) + flags |= AM_ONLY; + if (REF(part)) + flags |= AM_PART; + + index = Modify_Array( + action, + array, + index, + arg, + flags, + len, + REF(dup) ? Int32(ARG(count)) : 1 + ); + VAL_INDEX(value) = index; + Move_Value(D_OUT, value); + return R_OUT; + } + + case SYM_CLEAR: { + FAIL_IF_READ_ONLY_ARRAY(array); + if (index < VAL_LEN_HEAD(value)) { + if (index == 0) Reset_Array(array); + else { + SET_END(ARR_AT(array, index)); + SET_SERIES_LEN(VAL_SERIES(value), cast(REBCNT, index)); + } + } + Move_Value(D_OUT, value); + return R_OUT; + } + + //-- Creation: + + case SYM_COPY: { + INCLUDE_PARAMS_OF_COPY; + + UNUSED(PAR(value)); + + REBU64 types = 0; + REBCNT tail = 0; + + UNUSED(REF(part)); + Partial1(value, ARG(limit), &tail); // may change VAL_INDEX + tail += VAL_INDEX(value); + + if (REF(deep)) + types |= REF(types) ? 0 : TS_STD_SERIES; + + if (REF(types)) { + if (IS_DATATYPE(ARG(kinds))) + types |= FLAGIT_KIND(VAL_TYPE(ARG(kinds))); + else + types |= VAL_TYPESET_BITS(ARG(kinds)); + } + + REBARR *copy = Copy_Array_Core_Managed( + array, + VAL_INDEX(value), // at + specifier, + tail, // tail + 0, // extra + REF(deep), // deep + types // types + ); + Init_Any_Array(D_OUT, VAL_TYPE(value), copy); + return R_OUT; + } + + //-- Special actions: + + case SYM_TRIM: { + INCLUDE_PARAMS_OF_TRIM; + + UNUSED(PAR(series)); + + FAIL_IF_READ_ONLY_ARRAY(array); + + if (REF(auto) || REF(all) || REF(lines)) + fail (Error_Bad_Refines_Raw()); + + if (REF(with)) { + UNUSED(ARG(str)); + fail (Error_Bad_Refines_Raw()); + } + + RELVAL *head = ARR_HEAD(array); + REBCNT out = index; + REBINT end = ARR_LEN(array); + + if (REF(tail)) { + for (; end >= cast(REBINT, index + 1); end--) { + if (VAL_TYPE(head + end - 1) != REB_BLANK) + break; + } + Remove_Series(SER(array), end, ARR_LEN(array) - end); + + // if (!(flags & AM_TRIM_HEAD) || index >= end) return; + } + + if (REF(head)) { + for (; cast(REBINT, index) < end; index++) { + if (VAL_TYPE(head + index) != REB_BLANK) break; + } + Remove_Series(SER(array), out, index - out); + } + + if (NOT(REF(head) || REF(tail))) { + for (; cast(REBINT, index) < end; index++) { + if (VAL_TYPE(head + index) != REB_BLANK) { + // + // Rare case of legal RELVAL bit copying... from one slot + // in an array to another in that same array. + // + *ARR_AT(array, out) = head[index]; + out++; + } + } + Remove_Series(SER(array), out, end - out); + } + + Move_Value(D_OUT, value); + return R_OUT; + } + + case SYM_SWAP: { + if (NOT(ANY_ARRAY(arg))) + fail (arg); + + FAIL_IF_READ_ONLY_ARRAY(array); + FAIL_IF_READ_ONLY_ARRAY(VAL_ARRAY(arg)); + + if ( + index < VAL_LEN_HEAD(value) + && VAL_INDEX(arg) < VAL_LEN_HEAD(arg) + ) { + // RELVAL bits can be copied within the same array + // + RELVAL temp = *VAL_ARRAY_AT(value); + *VAL_ARRAY_AT(value) = *VAL_ARRAY_AT(arg); + *VAL_ARRAY_AT(arg) = temp; + } + Move_Value(D_OUT, D_ARG(1)); + return R_OUT; + } + + case SYM_REVERSE: { + REBCNT len; + Partial1(value, D_ARG(3), &len); + + FAIL_IF_READ_ONLY_ARRAY(array); + + if (len != 0) { + // + // RELVAL bits may be copied from slots within the same array + // + RELVAL *front = VAL_ARRAY_AT(value); + RELVAL *back = front + len - 1; + for (len /= 2; len > 0; len--) { + RELVAL temp = *front; + *front++ = *back; + *back-- = temp; + } + } + Move_Value(D_OUT, D_ARG(1)); + return R_OUT; + } + + case SYM_SORT: { + INCLUDE_PARAMS_OF_SORT; + + UNUSED(PAR(series)); + UNUSED(REF(part)); // checks limit as void + UNUSED(REF(skip)); // checks size as void + UNUSED(REF(compare)); // checks comparator as void + + FAIL_IF_READ_ONLY_ARRAY(array); + + Sort_Block( + value, + REF(case), + ARG(size), // skip size (may be void if no /SKIP) + ARG(comparator), // (may be void if no /COMPARE) + ARG(limit), // (may be void if no /PART) + REF(all), + REF(reverse) + ); + Move_Value(D_OUT, value); + return R_OUT; + } + + case SYM_RANDOM: { + INCLUDE_PARAMS_OF_RANDOM; + + UNUSED(PAR(value)); + + if (REF(seed)) + fail (Error_Bad_Refines_Raw()); + + if (REF(only)) { // pick an element out of the array + if (index >= VAL_LEN_HEAD(value)) + return R_BLANK; + + Init_Integer( + ARG(seed), + 1 + (Random_Int(REF(secure)) % (VAL_LEN_HEAD(value) - index)) + ); + + RELVAL *slot = Pick_Block(D_OUT, value, ARG(seed)); + if (IS_VOID(D_OUT)) { + assert(slot == NULL); + UNUSED(slot); + return R_VOID; + } + return R_OUT; + + } + + Shuffle_Block(value, REF(secure)); + Move_Value(D_OUT, value); + return R_OUT; + } + + default: + break; // fallthrough to error + } + + // If it wasn't one of the block actions, fall through and let the port + // system try. OPEN [scheme: ...], READ [ ], etc. + // + // !!! This used to be done by sensing explicitly what a "port action" + // was, but that involved checking if the action was in a numeric range. + // The symbol-based action dispatch is more open-ended. Trying this + // to see how it works. + + return T_Port(frame_, action); + +return_empty_block: + Init_Block(D_OUT, Make_Array(0)); + return R_OUT; } -/*********************************************************************** -** -*/ REBTYPE(Block) -/* -***********************************************************************/ +#if !defined(NDEBUG) + +// +// Assert_Array_Core: C +// +void Assert_Array_Core(REBARR *a) { - REBVAL *value = D_ARG(1); - REBVAL *arg = D_ARG(2); - REBSER *ser; - REBINT index; - REBINT tail; - REBINT len; - REBVAL val; - REBCNT args; - REBCNT ret; - - // Support for port: OPEN [scheme: ...], READ [ ], etc. - if (action >= PORT_ACTIONS && IS_BLOCK(value)) - return T_Port(ds, action); - - // Most common series actions: !!! speed this up! - len = Do_Series_Action(action, value, arg); - if (len >= 0) return len; // return code - - // Special case (to avoid fetch of index and tail below): - if (action == A_MAKE || action == A_TO) { - Make_Block_Type(action == A_MAKE, value, arg); // returned in value - if (ANY_PATH(value)) Clear_Value_Opts(VAL_SERIES(value)); - *D_RET = *value; - return R_RET; - } - - index = (REBINT)VAL_INDEX(value); - tail = (REBINT)VAL_TAIL(value); - ser = VAL_SERIES(value); - - // Check must be in this order (to avoid checking a non-series value); - if (action >= A_TAKE && action <= A_SORT && IS_PROTECT_SERIES(ser)) - Trap0(RE_PROTECTED); - - switch (action) { - - //-- Picking: - -#ifdef REMOVE_THIS - -//CHANGE SELECT TO USE PD_BLOCK? - - case A_PATH: - if (IS_INTEGER(arg)) { - action = A_PICK; - goto repick; - } - // block/select case: - ret = Find_Block_Simple(ser, index, arg); - goto select_val; - - case A_PATH_SET: - action = A_POKE; - // no SELECT case allowed !!!! -#endif + // Basic integrity checks (series is not marked free, etc.) Note that + // we don't use ASSERT_SERIES the macro here, because that checks to + // see if the series is an array...and if so, would call this routine + // + Assert_Series_Core(SER(a)); + + if (NOT(GET_SER_FLAG(a, SERIES_FLAG_ARRAY))) + panic (a); + + RELVAL *item = ARR_HEAD(a); + REBCNT i; + for (i = 0; i < ARR_LEN(a); ++i, ++item) { + if (IS_END(item)) { + printf("Premature array end at index %d\n", cast(int, i)); + panic (a); + } + } + + if (NOT_END(item)) + panic (item); + + if (GET_SER_INFO(a, SERIES_INFO_HAS_DYNAMIC)) { + REBCNT rest = SER_REST(SER(a)); + + assert(rest > 0 && rest > i); + for (; i < rest - 1; ++i, ++item) { + if (NOT(item->header.bits & NODE_FLAG_CELL)) { + printf("Unwritable cell found in array rest capacity\n"); + panic (a); + } + } + assert(item == ARR_AT(a, rest - 1)); + + RELVAL *ultimate = ARR_AT(a, rest - 1); + if (NOT_END(ultimate) || (ultimate->header.bits & NODE_FLAG_CELL)) { + printf("Implicit termination/unwritable END missing from array\n"); + panic (a); + } + } - case A_POKE: - case A_PICK: -repick: - value = Pick_Block(value, arg); - if (action == A_PICK) { - if (!value) goto is_none; - *D_RET = *value; - } else { - if (!value) Trap_Range(arg); - arg = D_ARG(3); - *value = *arg; - *D_RET = *arg; - } - return R_RET; - -/* - len = Get_Num_Arg(arg); // Position - index += len; - if (len > 0) index--; - if (len == 0 || index < 0 || index >= tail) { - if (action == A_PICK) goto is_none; - Trap_Range(arg); - } - if (action == A_PICK) { -pick_it: - *D_RET = BLK_HEAD(ser)[index]; - return R_RET; - } - arg = D_ARG(3); - *D_RET = *arg; - BLK_HEAD(ser)[index] = *arg; - return R_RET; -*/ - - case A_TAKE: - // take/part: - if (D_REF(2)) { - len = Partial1(value, D_ARG(3)); - if (len == 0) { -zero_blk: - Set_Block(D_RET, Make_Block(0)); - return R_RET; - } - } else - len = 1; - - index = VAL_INDEX(value); // /part can change index - // take/last: - if (D_REF(5)) index = tail - len; - if (index < 0 || index >= tail) { - if (!D_REF(2)) goto is_none; - goto zero_blk; - } - - // if no /part, just return value, else return block: - if (!D_REF(2)) *D_RET = BLK_HEAD(ser)[index]; - else Set_Block(D_RET, Copy_Block_Len(ser, index, len)); // no more /DEEP -// else Set_Block(D_RET, Copy_Block_Deep(ser, index, len, D_REF(4) ? COPY_DEEP: 0)); - Remove_Series(ser, index, len); - return R_RET; - - //-- Search: - - case A_FIND: - case A_SELECT: - args = Find_Refines(ds, ALL_FIND_REFS); -// if (ANY_BLOCK(arg) || args) { - len = ANY_BLOCK(arg) ? VAL_BLK_LEN(arg) : 1; - if (args & AM_FIND_PART) tail = Partial1(value, D_ARG(ARG_FIND_LENGTH)); - ret = 1; - if (args & AM_FIND_SKIP) ret = Int32s(D_ARG(ARG_FIND_SIZE), 1); - ret = Find_Block(ser, index, tail, arg, len, args, ret); -// } -/* else { - len = 1; - ret = Find_Block_Simple(ser, index, arg); - } -*/ - if (ret >= (REBCNT)tail) goto is_none; - if (args & AM_FIND_ONLY) len = 1; - if (action == A_FIND) { - if (args & (AM_FIND_TAIL | AM_FIND_MATCH)) ret += len; - VAL_INDEX(value) = ret; - } - else { - ret += len; - if (ret >= (REBCNT)tail) goto is_none; - value = BLK_SKIP(ser, ret); - } - break; - - //-- Modification: - case A_APPEND: - case A_INSERT: - case A_CHANGE: - // Length of target (may modify index): (arg can be anything) - len = Partial1((action == A_CHANGE) ? value : arg, DS_ARG(AN_LENGTH)); - index = VAL_INDEX(value); - args = 0; - if (DS_REF(AN_ONLY)) SET_FLAG(args, AN_ONLY); - if (DS_REF(AN_PART)) SET_FLAG(args, AN_PART); - index = Modify_Block(action, ser, index, arg, args, len, DS_REF(AN_DUP) ? Int32(DS_ARG(AN_COUNT)) : 1); - VAL_INDEX(value) = index; - break; - - case A_CLEAR: - if (index < tail) { - if (index == 0) Reset_Series(ser); - else { - SET_END(BLK_SKIP(ser, index)); - VAL_TAIL(value) = (REBCNT)index; - } - } - break; - - //-- Creation: - - case A_COPY: // /PART len /DEEP /TYPES kinds -#if 0 - args = D_REF(ARG_COPY_DEEP) ? COPY_ALL : 0; - len = Partial1(value, D_ARG(ARG_COPY_LENGTH)); - index = (REBINT)VAL_INDEX(value); -// VAL_SERIES(value) = (len > 0) ? Copy_Block_Deep(ser, index, len, args) : Make_Block(0); - VAL_INDEX(value) = 0; -#else - { - REBU64 types = 0; - if (D_REF(ARG_COPY_DEEP)) { - types |= CP_DEEP | (D_REF(ARG_COPY_TYPES) ? 0 : TS_STD_SERIES); - } - if D_REF(ARG_COPY_TYPES) { - arg = D_ARG(ARG_COPY_KINDS); - if (IS_DATATYPE(arg)) types |= TYPESET(VAL_DATATYPE(arg)); - else types |= VAL_TYPESET(arg); - } - len = Partial1(value, D_ARG(ARG_COPY_LENGTH)); - VAL_SERIES(value) = Copy_Block_Values(ser, VAL_INDEX(value), VAL_INDEX(value)+len, types); - VAL_INDEX(value) = 0; - } -#endif - break; - - //-- Special actions: - - case A_TRIM: - args = Find_Refines(ds, ALL_TRIM_REFS); - if (args & ~(AM_TRIM_HEAD|AM_TRIM_TAIL)) Trap0(RE_BAD_REFINES); - Trim_Block(ser, index, args); - break; - - case A_SWAP: - if (SERIES_WIDE(ser) != SERIES_WIDE(VAL_SERIES(arg))) - Trap_Arg(arg); - if (IS_PROTECT_SERIES(VAL_SERIES(arg))) Trap0(RE_PROTECTED); - if (index < tail && VAL_INDEX(arg) < VAL_TAIL(arg)) { - val = *VAL_BLK_DATA(value); - *VAL_BLK_DATA(value) = *VAL_BLK_DATA(arg); - *VAL_BLK_DATA(arg) = val; - } - value = 0; - break; - - case A_REVERSE: - len = Partial1(value, D_ARG(3)); - if (len == 0) break; - value = VAL_BLK_DATA(value); - arg = value + len - 1; - for (len /= 2; len > 0; len--) { - val = *value; - *value++ = *arg; - *arg-- = val; - } - value = 0; - break; - - case A_SORT: - Sort_Block( - value, - D_REF(2), // case sensitive - D_ARG(4), // skip size - D_ARG(6), // comparator - D_ARG(8), // part-length - D_REF(9), // all fields - D_REF(10) // reverse - ); - break; - - case A_RANDOM: - if (!IS_BLOCK(value)) Trap_Action(VAL_TYPE(value), action); - if (D_REF(2)) Trap0(RE_BAD_REFINES); // seed - if (D_REF(4)) { // /only - if (index >= tail) goto is_none; - len = (REBCNT)Random_Int(D_REF(3)) % (tail - index); // /secure - arg = D_ARG(2); // pass to pick - SET_INTEGER(arg, len+1); - action = A_PICK; - goto repick; - } - Shuffle_Block(value, D_REF(3)); - break; - - default: - Trap_Action(VAL_TYPE(value), action); - } - - if (!value) value = D_ARG(1); - DS_RET_VALUE(value); - return R_RET; - -is_none: - return R_NONE; } +#endif diff --git a/src/core/t-char.c b/src/core/t-char.c index f2f2f34abb..44151278f9 100644 --- a/src/core/t-char.c +++ b/src/core/t-char.c @@ -1,180 +1,242 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-char.c -** Summary: character datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-char.c +// Summary: "character datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/*********************************************************************** -** -*/ REBINT CT_Char(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Char: C +// +REBINT CT_Char(const RELVAL *a, const RELVAL *b, REBINT mode) { - REBINT num; - - if (mode >= 0) { - if (mode < 2) - num = LO_CASE(VAL_CHAR(a)) - LO_CASE(VAL_CHAR(b)); - else - num = VAL_CHAR(a) - VAL_CHAR(b); - return (num == 0); - } - - num = VAL_CHAR(a) - VAL_CHAR(b); - if (mode == -1) return (num >= 0); - return (num > 0); + REBINT num; + + if (mode >= 0) { + if (mode == 0) + num = LO_CASE(VAL_CHAR(a)) - LO_CASE(VAL_CHAR(b)); + else + num = VAL_CHAR(a) - VAL_CHAR(b); + return (num == 0); + } + + num = VAL_CHAR(a) - VAL_CHAR(b); + if (mode == -1) return (num >= 0); + return (num > 0); } -/*********************************************************************** -** -*/ REBTYPE(Char) -/* -***********************************************************************/ +// +// MAKE_Char: C +// +void MAKE_Char(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBINT chr = VAL_CHAR(D_ARG(1)); - REBINT arg; - REBVAL *val; - - if (IS_BINARY_ACT(action)) { - val = D_ARG(2); - if (IS_CHAR(val)) - arg = VAL_CHAR(val); - else if (IS_INTEGER(val)) - arg = VAL_INT32(val); - else if (IS_DECIMAL(val)) - arg = (REBINT)VAL_DECIMAL(val); - else - Trap_Math_Args(REB_CHAR, action); - } - - switch (action) { - - case A_ADD: chr += (REBUNI)arg; break; - case A_SUBTRACT: - chr -= (REBUNI)arg; - if (IS_CHAR(D_ARG(2))) { - DS_RET_INT(chr); - return R_RET; - } - break; - case A_MULTIPLY: chr *= arg; break; - case A_DIVIDE: - if (arg == 0) Trap0(RE_ZERO_DIVIDE); - chr /= arg; - break; - case A_REMAINDER: - if (arg == 0) Trap0(RE_ZERO_DIVIDE); - chr %= arg; - break; - - case A_AND: chr &= (REBUNI)arg; break; - case A_OR: chr |= (REBUNI)arg; break; - case A_XOR: chr ^= (REBUNI)arg; break; - - case A_NEGATE: chr = (REBUNI)-chr; break; - case A_COMPLEMENT: chr = (REBUNI)~chr; break; - case A_EVENQ: chr = (REBUNI)~chr; - case A_ODDQ: DECIDE(chr & 1); - - case A_RANDOM: //!!! needs further definition ? random/zero - if (D_REF(2)) { // /seed - Set_Random(chr); - return R_UNSET; - } - if (chr == 0) break; - chr = (REBUNI)(1 + ((REBCNT)Random_Int(D_REF(3)) % chr)); // /secure - break; - - case A_MAKE: - case A_TO: - val = D_ARG(2); - - switch(VAL_TYPE(val)) { - case REB_CHAR: - chr = VAL_CHAR(val); - break; - - case REB_INTEGER: - case REB_DECIMAL: - arg = Int32(val); - if (arg > MAX_UNI || arg < 0) goto bad_make; - chr = arg; - break; - - case REB_BINARY: - { - REBYTE *bp = VAL_BIN(val); - arg = VAL_LEN(val); - if (arg == 0) goto bad_make; - if (*bp > 0x80) { - if (!Legal_UTF8_Char(bp, arg)) goto bad_make; - chr = Decode_UTF8_Char(&bp, 0); // zero on error - if (!chr) goto bad_make; - } - else - chr = *bp; - } - break; + assert(kind == REB_CHAR); + UNUSED(kind); + + REBUNI uni; + + switch(VAL_TYPE(arg)) { + case REB_CHAR: + uni = VAL_CHAR(arg); + break; + + case REB_INTEGER: + case REB_DECIMAL: + { + REBINT n = Int32(arg); + if (n > MAX_UNI || n < 0) goto bad_make; + uni = n; + } + break; + + case REB_BINARY: + { + const REBYTE *bp = VAL_BIN(arg); + REBCNT len = VAL_LEN_AT(arg); + if (len == 0) goto bad_make; + if (*bp <= 0x80) { + if (len != 1) + goto bad_make; + + uni = *bp; + } + else { + --len; + bp = Back_Scan_UTF8_Char(&uni, bp, &len); + if (!bp || len != 0) // must be valid UTF8 and consume all data + goto bad_make; + } + } // case REB_BINARY + break; #ifdef removed -// case REB_ISSUE: - // Scan 8 or 16 bit hex str, will throw on error... - arg = Scan_Hex_Value(VAL_DATA(val), VAL_LEN(val), (REBOOL)!VAL_BYTE_SIZE(val)); - if (arg > MAX_UNI || arg < 0) goto bad_make; - chr = arg; - break; +// case REB_ISSUE: + // Scan 8 or 16 bit hex str, will throw on error... + REBINT n = Scan_Hex_Value( + VAL_RAW_DATA_AT(arg), VAL_LEN_AT(arg), !VAL_BYTE_SIZE(arg) + ); + if (n > MAX_UNI || n < 0) goto bad_make; + chr = n; + break; #endif - case REB_STRING: - if (VAL_INDEX(val) >= VAL_TAIL(val)) Trap_Make(REB_CHAR, val); - chr = GET_ANY_CHAR(VAL_SERIES(val), VAL_INDEX(val)); - break; - - default: -bad_make: - Trap_Make(REB_CHAR, val); - } - break; - - default: - Trap_Action(REB_CHAR, action); - } - - if ((chr >> 16) != 0 && (chr >> 16) != 0xffff) Trap1(RE_TYPE_LIMIT, Get_Type(REB_CHAR)); - SET_CHAR(DS_RETURN, chr); - return R_RET; - -is_false: - return R_FALSE; - -is_true: - return R_TRUE; + case REB_STRING: + if (VAL_INDEX(arg) >= VAL_LEN_HEAD(arg)) + goto bad_make; + uni = GET_ANY_CHAR(VAL_SERIES(arg), VAL_INDEX(arg)); + break; + + default: + bad_make: + fail (Error_Bad_Make(REB_CHAR, arg)); + } + + Init_Char(out, uni); +} + + +// +// TO_Char: C +// +void TO_Char(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) +{ + MAKE_Char(out, kind, arg); +} + + +static REBINT Math_Arg_For_Char(REBVAL *arg, REBSYM action) +{ + switch (VAL_TYPE(arg)) { + case REB_CHAR: + return VAL_CHAR(arg); + + case REB_INTEGER: + return VAL_INT32(arg); + + case REB_DECIMAL: + return cast(REBINT, VAL_DECIMAL(arg)); + + default: + fail (Error_Math_Args(REB_CHAR, action)); + } +} + + +// +// REBTYPE: C +// +REBTYPE(Char) +{ + REBCNT chr = VAL_CHAR(D_ARG(1)); // !!! Larger than REBCHR for math ops? + REBINT arg; + + switch (action) { + + case SYM_ADD: + arg = Math_Arg_For_Char(D_ARG(2), action); + chr += cast(REBUNI, arg); + break; + + case SYM_SUBTRACT: + arg = Math_Arg_For_Char(D_ARG(2), action); + chr -= cast(REBUNI, arg); + if (IS_CHAR(D_ARG(2))) { + Init_Integer(D_OUT, chr); + return R_OUT; + } + break; + + case SYM_MULTIPLY: + arg = Math_Arg_For_Char(D_ARG(2), action); + chr *= arg; + break; + + case SYM_DIVIDE: + arg = Math_Arg_For_Char(D_ARG(2), action); + if (arg == 0) fail (Error_Zero_Divide_Raw()); + chr /= arg; + break; + + case SYM_REMAINDER: + arg = Math_Arg_For_Char(D_ARG(2), action); + if (arg == 0) fail (Error_Zero_Divide_Raw()); + chr %= arg; + break; + + case SYM_AND_T: + arg = Math_Arg_For_Char(D_ARG(2), action); + chr &= cast(REBUNI, arg); + break; + + case SYM_OR_T: + arg = Math_Arg_For_Char(D_ARG(2), action); + chr |= cast(REBUNI, arg); + break; + + case SYM_XOR_T: + arg = Math_Arg_For_Char(D_ARG(2), action); + chr ^= cast(REBUNI, arg); + break; + + case SYM_COMPLEMENT: + chr = cast(REBUNI, ~chr); + break; + + case SYM_EVEN_Q: + return (cast(REBUNI, ~chr) & 1) ? R_TRUE : R_FALSE; + + case SYM_ODD_Q: + return (chr & 1) ? R_TRUE : R_FALSE; + + case SYM_RANDOM: { + INCLUDE_PARAMS_OF_RANDOM; + + UNUSED(PAR(value)); + if (REF(only)) + fail (Error_Bad_Refines_Raw()); + + if (REF(seed)) { + Set_Random(chr); + return R_VOID; + } + if (chr == 0) break; + chr = cast(REBUNI, 1 + cast(REBCNT, Random_Int(REF(secure)) % chr)); + break; } + + default: + fail (Error_Illegal_Action(REB_CHAR, action)); + } + + if ((chr >> 16) != 0 && (chr >> 16) != 0xffff) + fail (Error_Type_Limit_Raw(Get_Type(REB_CHAR))); + Init_Char(D_OUT, chr); + return R_OUT; } diff --git a/src/core/t-datatype.c b/src/core/t-datatype.c index b0d7457361..8b4403c953 100644 --- a/src/core/t-datatype.c +++ b/src/core/t-datatype.c @@ -1,107 +1,130 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-datatype.c -** Summary: datatype datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-datatype.c +// Summary: "datatype datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/*********************************************************************** -** -*/ REBINT CT_Datatype(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Datatype: C +// +REBINT CT_Datatype(const RELVAL *a, const RELVAL *b, REBINT mode) { - if (mode >= 0) return (VAL_DATATYPE(a) == VAL_DATATYPE(b)); - return -1; + if (mode >= 0) return (VAL_TYPE_KIND(a) == VAL_TYPE_KIND(b)); + return -1; } -/*********************************************************************** -** -*/ REBFLG MT_Datatype(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ -{ - if (!IS_WORD(data)) return FALSE; - type = VAL_WORD_CANON(data); - if (type > REB_MAX) return FALSE; - VAL_SET(out, REB_DATATYPE); - VAL_DATATYPE(out) = type-1; - VAL_TYPE_SPEC(out) = 0; - return TRUE; +// +// MAKE_Datatype: C +// +void MAKE_Datatype(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { + if (!IS_WORD(arg)) + fail (Error_Bad_Make(kind, arg)); + + REBSYM sym = VAL_WORD_SYM(arg); + if (sym == SYM_0 || sym > SYM_FROM_KIND(REB_MAX)) + fail (Error_Bad_Make(kind, arg)); + + VAL_RESET_HEADER(out, REB_DATATYPE); + VAL_TYPE_KIND(out) = KIND_FROM_SYM(sym); + VAL_TYPE_SPEC(out) = 0; +} + + +// +// TO_Datatype: C +// +void TO_Datatype(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { + MAKE_Datatype(out, kind, arg); } -/*********************************************************************** -** -*/ REBTYPE(Datatype) -/* -***********************************************************************/ +// +// REBTYPE: C +// +REBTYPE(Datatype) { - REBVAL *value = D_ARG(1); - REBVAL *arg = D_ARG(2); - REBACT act; - REBINT type = VAL_DATATYPE(value); - REBSER *obj; - REBINT n; - - switch (action) { - - case A_REFLECT: - n = What_Reflector(arg); // zero on error - if (n == OF_SPEC) { - obj = Make_Std_Object(STD_TYPE_SPEC); - Set_Object_Values(obj, BLK_HEAD(VAL_TYPE_SPEC(BLK_SKIP(Lib_Context, type+1)))); - SET_OBJECT(D_RET, obj); - } - else if (n == OF_TITLE) { - Set_String(D_RET, Copy_Series(VAL_SERIES(BLK_HEAD(VAL_TYPE_SPEC(BLK_SKIP(Lib_Context, type+1)))))); - } - else Trap_Reflect(VAL_TYPE(value), arg); - break; - - case A_MAKE: - case A_TO: - if (type != REB_DATATYPE) { - act = Value_Dispatch[type]; - if (act) return act(ds, action); - //return R_NONE; - Trap_Make(type, arg); - } - // if (IS_NONE(arg)) return R_NONE; - if (MT_Datatype(D_RET, arg, REB_DATATYPE)) - break; - - Trap_Make(REB_DATATYPE, arg); - - default: - Trap_Action(REB_DATATYPE, action); - } - - return R_RET; + REBVAL *value = D_ARG(1); + REBVAL *arg = D_ARG(2); + enum Reb_Kind kind = VAL_TYPE_KIND(value); + + switch (action) { + + case SYM_REFLECT: { + REBSYM sym = VAL_WORD_SYM(arg); + if (sym == SYM_SPEC) { + // + // The "type specs" were loaded as an array, but this reflector + // wants to give back an object. Combine the array with the + // standard object that mirrors its field order. + // + REBCTX *context = Copy_Context_Shallow( + VAL_CONTEXT(Get_System(SYS_STANDARD, STD_TYPE_SPEC)) + ); + MANAGE_ARRAY(CTX_VARLIST(context)); + + assert(CTX_TYPE(context) == REB_OBJECT); + + REBVAL *var = CTX_VARS_HEAD(context); + REBVAL *key = CTX_KEYS_HEAD(context); + + // !!! Account for the "invisible" self key in the current + // stop-gap implementation of self, still default on MAKE OBJECT!s + // + assert(VAL_KEY_SYM(key) == SYM_SELF); + ++key; ++var; + + RELVAL *value = ARR_HEAD( + VAL_TYPE_SPEC(CTX_VAR(Lib_Context, SYM_FROM_KIND(kind))) + ); + + for (; NOT_END(var); ++var, ++key) { + if (IS_END(value)) + Init_Blank(var); + else { + // typespec array does not contain relative values + // + Derelativize(var, value, SPECIFIED); + ++value; + } + } + + Init_Object(D_OUT, context); + } + else + fail (Error_Cannot_Reflect(VAL_TYPE(value), arg)); + break;} + + default: + fail (Error_Illegal_Action(REB_DATATYPE, action)); + } + + return R_OUT; } diff --git a/src/core/t-date.c b/src/core/t-date.c index a514609e3b..1183a69722 100644 --- a/src/core/t-date.c +++ b/src/core/t-date.c @@ -1,837 +1,883 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-date.c -** Summary: date datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** Date and time are stored in UTC format with an optional timezone. -** The zone must be added when a date is exported or imported, but not -** when date computations are performed. -** -***********************************************************************/ - +// +// File: %t-date.c +// Summary: "date datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Date and time are stored in UTC format with an optional timezone. +// The zone must be added when a date is exported or imported, but not +// when date computations are performed. +// #include "sys-core.h" -/*********************************************************************** -** -*/ void Set_Date_UTC(REBVAL *val, REBINT y, REBINT m, REBINT d, REBI64 t, REBINT z) -/* -** Convert date/time/zone to UTC with zone. -** -***********************************************************************/ -{ - // Adjust for zone.... - VAL_YEAR(val) = y; - VAL_MONTH(val) = m; - VAL_DAY(val) = d; - VAL_TIME(val) = t; - VAL_ZONE(val) = z; - VAL_SET(val, REB_DATE); - if (z) Adjust_Date_Zone(val, TRUE); -} - - -/*********************************************************************** -** -*/ void Set_Date(REBVAL *val, REBOL_DAT *dat) -/* -** Convert OS date struct to REBOL value struct. -** NOTE: Input zone is in minutes. -** -***********************************************************************/ +// +// Set_Date_UTC: C +// +// Convert date/time/zone to UTC with zone. +// +void Set_Date_UTC(REBVAL *val, REBINT y, REBINT m, REBINT d, REBI64 t, REBINT z) { - VAL_YEAR(val) = dat->year; - VAL_MONTH(val) = dat->month; - VAL_DAY(val) = dat->day; - VAL_ZONE(val) = dat->zone / ZONE_MINS; - VAL_TIME(val) = TIME_SEC(dat->time) + dat->nano; - VAL_SET(val, REB_DATE); + // Adjust for zone.... + VAL_YEAR(val) = y; + VAL_MONTH(val) = m; + VAL_DAY(val) = d; + VAL_NANO(val) = t; + VAL_ZONE(val) = z; + VAL_RESET_HEADER(val, REB_DATE); + if (z) Adjust_Date_Zone(val, TRUE); } -/*********************************************************************** -** -*/ REBINT CT_Date(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Date: C +// +REBINT CT_Date(const RELVAL *a, const RELVAL *b, REBINT mode) { - REBINT num = Cmp_Date(a, b); - if (mode >= 2) - return VAL_DATE(a).bits == VAL_DATE(b).bits && VAL_TIME(a) == VAL_TIME(b); - if (mode >= 0) return (num == 0); - if (mode == -1) return (num >= 0); - return (num > 0); + REBINT num = Cmp_Date(a, b); + if (mode == 1) + return ( + VAL_DATE(a).bits == VAL_DATE(b).bits + && VAL_NANO(a) == VAL_NANO(b) + ); + if (mode >= 0) return (num == 0); + if (mode == -1) return (num >= 0); + return (num > 0); } -/*********************************************************************** -** -*/ void Emit_Date(REB_MOLD *mold, REBVAL *value) -/* -***********************************************************************/ +// +// Emit_Date: C +// +void Emit_Date(REB_MOLD *mold, const REBVAL *value_orig) { - REBYTE buf[64]; - REBYTE *bp = &buf[0]; - REBINT tz; - REBYTE dash = GET_MOPT(mold, MOPT_SLASH_DATE) ? '/' : '-'; - REBVAL val = *value; - value = &val; - - if ( - VAL_MONTH(value) == 0 - || VAL_MONTH(value) > 12 - || VAL_DAY(value) == 0 - || VAL_DAY(value) > 31 - ) { - Append_Bytes(mold->series, "?date?"); - return; - } - - if (VAL_TIME(value) != NO_TIME) Adjust_Date_Zone(value, FALSE); - -// Punctuation[GET_MOPT(mold, MOPT_COMMA_PT) ? PUNCT_COMMA : PUNCT_DOT] - - bp = Form_Int(bp, (REBINT)VAL_DAY(value)); - *bp++ = dash; - memcpy(bp, Month_Names[VAL_MONTH(value)-1], 3); - bp += 3; - *bp++ = dash; - bp = Form_Int_Pad(bp, (REBINT)VAL_YEAR(value), 6, -4, '0'); - *bp = 0; - - Append_Bytes(mold->series, buf); - - if (VAL_TIME(value) != NO_TIME) { - - Append_Byte(mold->series, '/'); - Emit_Time(mold, value); - - if (VAL_ZONE(value) != 0) { - - bp = &buf[0]; - tz = VAL_ZONE(value); - if (tz < 0) { - *bp++ = '-'; - tz = -tz; - } - else - *bp++ = '+'; - - bp = Form_Int(bp, tz/4); - *bp++ = ':'; - bp = Form_Int_Pad(bp, (tz&3) * 15, 2, 2, '0'); - *bp = 0; - - Append_Bytes(mold->series, buf); - } - } + REBYTE buf[64]; + REBYTE *bp = &buf[0]; + REBINT tz; + REBYTE dash = GET_MOPT(mold, MOPT_SLASH_DATE) ? '/' : '-'; + + // We don't want to modify the incoming date value we are molding, + // so we make a copy that we can tweak during the emit process + + DECLARE_LOCAL (value); + Move_Value(value, value_orig); + + if ( + VAL_MONTH(value) == 0 + || VAL_MONTH(value) > 12 + || VAL_DAY(value) == 0 + || VAL_DAY(value) > 31 + ) { + Append_Unencoded(mold->series, "?date?"); + return; + } + + if (VAL_NANO(value) != NO_TIME) + Adjust_Date_Zone(value, FALSE); + +// Punctuation[GET_MOPT(mold, MOPT_COMMA_PT) ? PUNCT_COMMA : PUNCT_DOT] + + bp = Form_Int(bp, (REBINT)VAL_DAY(value)); + *bp++ = dash; + memcpy(bp, Month_Names[VAL_MONTH(value)-1], 3); + bp += 3; + *bp++ = dash; + bp = Form_Int_Pad(bp, (REBINT)VAL_YEAR(value), 6, -4, '0'); + *bp = 0; + + Append_Unencoded(mold->series, s_cast(buf)); + + if (VAL_NANO(value) != NO_TIME) { + + Append_Codepoint_Raw(mold->series, '/'); + Emit_Time(mold, value); + + if (VAL_ZONE(value) != 0) { + + bp = &buf[0]; + tz = VAL_ZONE(value); + if (tz < 0) { + *bp++ = '-'; + tz = -tz; + } + else + *bp++ = '+'; + + bp = Form_Int(bp, tz/4); + *bp++ = ':'; + bp = Form_Int_Pad(bp, (tz&3) * 15, 2, 2, '0'); + *bp = 0; + + Append_Unencoded(mold->series, s_cast(buf)); + } + } } -/*********************************************************************** -** -*/ static REBCNT Month_Length(REBCNT month, REBCNT year) -/* -** Given a year, determine the number of days in the month. -** Handles all leap year calculations. -** -***********************************************************************/ +// +// Month_Length: C +// +// Given a year, determine the number of days in the month. +// Handles all leap year calculations. +// +static REBCNT Month_Length(REBCNT month, REBCNT year) { - if (month != 1) - return (REBCNT)Month_Lengths[month]; - - return ( - ((year % 4) == 0) && // divisible by four is a leap year - ( - ((year % 100) != 0) || // except when divisible by 100 - ((year % 400) == 0) // but not when divisible by 400 - ) - ) ? 29 : 28; + if (month != 1) + return Month_Max_Days[month]; + + return ( + ((year % 4) == 0) && // divisible by four is a leap year + ( + ((year % 100) != 0) || // except when divisible by 100 + ((year % 400) == 0) // but not when divisible by 400 + ) + ) ? 29 : 28; } -/*********************************************************************** -** -*/ REBCNT Julian_Date(REBDAT date) -/* -** Given a year, month and day, return the number of days since the -** beginning of that year. -** -***********************************************************************/ +// +// Julian_Date: C +// +// Given a year, month and day, return the number of days since the +// beginning of that year. +// +REBCNT Julian_Date(REBDAT date) { - REBCNT days; - REBCNT i; + REBCNT days; + REBCNT i; - days = 0; + days = 0; - for (i = 0; i < (date.date.month-1); i++) - days += Month_Length(i, date.date.year); + for (i = 0; i < cast(REBCNT, date.date.month - 1); i++) + days += Month_Length(i, date.date.year); - return date.date.day + days; + return date.date.day + days; } -/*********************************************************************** -** -*/ REBINT Diff_Date(REBDAT d1, REBDAT d2) -/* -** Calculate the difference in days between two dates. -** -***********************************************************************/ +// +// Diff_Date: C +// +// Calculate the difference in days between two dates. +// +REBINT Diff_Date(REBDAT d1, REBDAT d2) { - REBCNT days; - REBINT sign; - REBCNT m, y; - REBDAT tmp; - - if (d1.bits == d2.bits) return 0; - - if (d1.bits < d2.bits) { - sign = -1; - tmp = d1; - d1 = d2; - d2 = tmp; - } - else - sign = 1; - - // if not same year, calculate days to end of month, year and - // days in between years plus days in end year - if (d1.date.year > d2.date.year) { - days = Month_Length(d2.date.month-1, d2.date.year) - d2.date.day; - - for (m = d2.date.month; m < 12; m++) - days += Month_Length(m, d2.date.year); - - for (y = d2.date.year + 1; y < d1.date.year; y++) { - days += (((y % 4) == 0) && // divisible by four is a leap year - (((y % 100) != 0) || // except when divisible by 100 - ((y % 400) == 0))) // but not when divisible by 400 - ? 366u : 365u; - } - return sign * (REBINT)(days + Julian_Date(d1)); - } - return sign * (REBINT)(Julian_Date(d1) - Julian_Date(d2)); + REBCNT days; + REBINT sign; + REBCNT m, y; + REBDAT tmp; + + if (d1.bits == d2.bits) return 0; + + if (d1.bits < d2.bits) { + sign = -1; + tmp = d1; + d1 = d2; + d2 = tmp; + } + else + sign = 1; + + // if not same year, calculate days to end of month, year and + // days in between years plus days in end year + if (d1.date.year > d2.date.year) { + days = Month_Length(d2.date.month-1, d2.date.year) - d2.date.day; + + for (m = d2.date.month; m < 12; m++) + days += Month_Length(m, d2.date.year); + + for (y = d2.date.year + 1; y < d1.date.year; y++) { + days += (((y % 4) == 0) && // divisible by four is a leap year + (((y % 100) != 0) || // except when divisible by 100 + ((y % 400) == 0))) // but not when divisible by 400 + ? 366u : 365u; + } + return sign * (REBINT)(days + Julian_Date(d1)); + } + return sign * (REBINT)(Julian_Date(d1) - Julian_Date(d2)); } -/*********************************************************************** -** -*/ REBCNT Week_Day(REBDAT date) -/* -** Return the day of the week for a specific date. -** -***********************************************************************/ +// +// Week_Day: C +// +// Return the day of the week for a specific date. +// +REBCNT Week_Day(REBDAT date) { - REBDAT year1 = {0}; - year1.date.day = 1; - year1.date.month = 1; + REBDAT year1; + CLEARS(&year1); + year1.date.day = 1; + year1.date.month = 1; - return ((Diff_Date(date, year1) + 5) % 7) + 1; + return ((Diff_Date(date, year1) + 5) % 7) + 1; } -/*********************************************************************** -** -*/ void Normalize_Time(REBI64 *sp, REBINT *dp) -/* -** Adjust *dp by number of days and set secs to less than a day. -** -***********************************************************************/ +// +// Normalize_Time: C +// +// Adjust *dp by number of days and set secs to less than a day. +// +void Normalize_Time(REBI64 *sp, REBCNT *dp) { - REBI64 secs = *sp; - REBINT day; + REBI64 secs = *sp; + REBINT day; - if (secs == NO_TIME) return; + if (secs == NO_TIME) return; - // how many days worth of seconds do we have - day = (REBINT)(secs / TIME_IN_DAY); - secs %= TIME_IN_DAY; + // how many days worth of seconds do we have + day = (REBINT)(secs / TIME_IN_DAY); + secs %= TIME_IN_DAY; - if (secs < 0L) { - day--; - secs += TIME_IN_DAY; - } + if (secs < 0L) { + day--; + secs += TIME_IN_DAY; + } - *dp += day; - *sp = secs; + *dp += day; + *sp = secs; } -/*********************************************************************** -** -*/ static REBDAT Normalize_Date(REBINT day, REBINT month, REBINT year, REBINT tz) -/* -** Given a year, month and day, normalize and combine to give a new -** date value. -** -***********************************************************************/ +// +// Normalize_Date: C +// +// Given a year, month and day, normalize and combine to give a new +// date value. +// +static REBDAT Normalize_Date(REBINT day, REBINT month, REBINT year, REBINT tz) { - REBINT d; - REBDAT dr; - - // First we normalize the month to get the right year - if (month<0) { - year-=(-month+11)/12; - month=11-((-month+11)%12); - } - if (month >= 12) { - year += month / 12; - month %= 12; - } - - // Now adjust the days by stepping through each month - while (day >= (d = (REBINT)Month_Length(month, year))) { - day -= d; - if (++month >= 12) { - month = 0; - year++; - } - } - while (day < 0) { - if (month == 0) { - month = 11; - year--; - } - else - month--; - day += (REBINT)Month_Length(month, year); - } - - if (year < 0 || year > MAX_YEAR) Trap1(RE_TYPE_LIMIT, Get_Type(REB_DATE)); - - dr.date.year = year; - dr.date.month = month+1; - dr.date.day = day+1; - dr.date.zone = tz; - - return dr; + REBINT d; + REBDAT dr; + + // First we normalize the month to get the right year + if (month<0) { + year-=(-month+11)/12; + month=11-((-month+11)%12); + } + if (month >= 12) { + year += month / 12; + month %= 12; + } + + // Now adjust the days by stepping through each month + while (day >= (d = (REBINT)Month_Length(month, year))) { + day -= d; + if (++month >= 12) { + month = 0; + year++; + } + } + while (day < 0) { + if (month == 0) { + month = 11; + year--; + } + else + month--; + day += (REBINT)Month_Length(month, year); + } + + if (year < 0 || year > MAX_YEAR) + fail (Error_Type_Limit_Raw(Get_Type(REB_DATE))); + + dr.date.year = year; + dr.date.month = month+1; + dr.date.day = day+1; + dr.date.zone = tz; + + return dr; } -/*********************************************************************** -** -*/ void Adjust_Date_Zone(REBVAL *d, REBFLG to_utc) -/* -** Adjust date and time for the timezone. -** The result should be used for output, not stored. -** -***********************************************************************/ +// +// Adjust_Date_Zone: C +// +// Adjust date and time for the timezone. +// The result should be used for output, not stored. +// +void Adjust_Date_Zone(REBVAL *d, REBOOL to_utc) { - REBI64 secs; - REBCNT n; + REBI64 secs; + REBCNT n; - if (VAL_ZONE(d) == 0) return; + if (VAL_ZONE(d) == 0) return; - if (VAL_TIME(d) == NO_TIME) { - VAL_TIME(d) = VAL_ZONE(d) = 0; - return; - } + if (VAL_NANO(d) == NO_TIME) { + VAL_NANO(d) = 0; + VAL_ZONE(d) = 0; + return; + } - // (compiler should fold the constant) - secs = ((i64)VAL_ZONE(d) * ((i64)ZONE_SECS * SEC_SEC)); - if (to_utc) secs = -secs; - secs += VAL_TIME(d); + // (compiler should fold the constant) + secs = cast(i64, VAL_ZONE(d)) * (cast(i64, ZONE_SECS) * SEC_SEC); + if (to_utc) secs = -secs; + secs += VAL_NANO(d); - VAL_TIME(d) = (secs + TIME_IN_DAY) % TIME_IN_DAY; + VAL_NANO(d) = (secs + TIME_IN_DAY) % TIME_IN_DAY; - n = VAL_DAY(d) - 1; + n = VAL_DAY(d) - 1; - if (secs < 0) n--; - else if (secs >= TIME_IN_DAY) n++; - else return; + if (secs < 0) n--; + else if (secs >= TIME_IN_DAY) n++; + else return; - VAL_DATE(d) = Normalize_Date(n, VAL_MONTH(d)-1, VAL_YEAR(d), VAL_ZONE(d)); + VAL_DATE(d) = Normalize_Date(n, VAL_MONTH(d)-1, VAL_YEAR(d), VAL_ZONE(d)); } -/*********************************************************************** -** -*/ void Subtract_Date(REBVAL *d1, REBVAL *d2, REBVAL *result) -/* -** Called by DIFFERENCE function. -** -***********************************************************************/ +// +// Subtract_Date: C +// +// Called by DIFFERENCE function. +// +void Subtract_Date(REBVAL *d1, REBVAL *d2, REBVAL *result) { - REBINT diff; - REBI64 t1; - REBI64 t2; + REBINT diff; + REBI64 t1; + REBI64 t2; - diff = Diff_Date(VAL_DATE(d1), VAL_DATE(d2)); - if (abs(diff) > (((1U << 31) - 1) / SECS_IN_DAY)) Trap0(RE_OVERFLOW); + diff = Diff_Date(VAL_DATE(d1), VAL_DATE(d2)); + if (cast(REBCNT, abs(diff)) > (((1U << 31) - 1) / SECS_IN_DAY)) + fail (Error_Overflow_Raw()); - t1 = VAL_TIME(d1); - if (t1 == NO_TIME) t1 = 0L; - t2 = VAL_TIME(d2); - if (t2 == NO_TIME) t2 = 0L; + t1 = VAL_NANO(d1); + if (t1 == NO_TIME) t1 = 0L; + t2 = VAL_NANO(d2); + if (t2 == NO_TIME) t2 = 0L; - VAL_SET(result, REB_TIME); - VAL_TIME(result) = (t1 - t2) + ((REBI64)diff * TIME_IN_DAY); + VAL_RESET_HEADER(result, REB_TIME); + VAL_NANO(result) = (t1 - t2) + (cast(REBI64, diff) * TIME_IN_DAY); } -/*********************************************************************** -** -*/ REBINT Cmp_Date(REBVAL *d1, REBVAL *d2) -/* -***********************************************************************/ +// +// Cmp_Date: C +// +REBINT Cmp_Date(const RELVAL *d1, const RELVAL *d2) { - REBINT diff; + REBINT diff; - diff = Diff_Date(VAL_DATE(d1), VAL_DATE(d2)); - if (diff == 0) diff = Cmp_Time(d1, d2); + diff = Diff_Date(VAL_DATE(d1), VAL_DATE(d2)); + if (diff == 0) diff = Cmp_Time(d1, d2); - return diff; + return diff; } -/*********************************************************************** -** -*/ REBFLG MT_Date(REBVAL *val, REBVAL *arg, REBCNT type) -/* -** Given a block of values, construct a date datatype. -** -***********************************************************************/ -{ - REBI64 secs = NO_TIME; - REBINT tz = 0; - REBDAT date; - REBCNT year, month, day; - - if (IS_DATE(arg)) { - *val = *arg; - return TRUE; - } - - if (!IS_INTEGER(arg)) return FALSE; - day = Int32s(arg++, 1); - if (!IS_INTEGER(arg)) return FALSE; - month = Int32s(arg++, 1); - if (!IS_INTEGER(arg)) return FALSE; - if (day > 99) { - year = day; - day = Int32s(arg++, 1); - } else - year = Int32s(arg++, 0); - - if (month < 1 || month > 12) return FALSE; - - if (year > MAX_YEAR || day < 1 || day > (REBINT)(Month_Lengths[month-1])) return FALSE; - - // Check February for leap year or century: - if (month == 2 && day == 29) { - if (((year % 4) != 0) || // not leap year - ((year % 100) == 0 && // century? - (year % 400) != 0)) return FALSE; // not leap century - } - - day--; - month--; - - if (IS_TIME(arg)) { - secs = VAL_TIME(arg); - arg++; - } - - if (IS_TIME(arg)) { - tz = (REBINT)(VAL_TIME(arg) / (ZONE_MINS * MIN_SEC)); - if (tz < -MAX_ZONE || tz > MAX_ZONE) Trap_Range(arg); - arg++; - } - - if (!IS_END(arg)) return FALSE; - - Normalize_Time(&secs, &day); - date = Normalize_Date(day, month, year, tz); - - VAL_SET(val, REB_DATE); - VAL_DATE(val) = date; - VAL_TIME(val) = secs; - Adjust_Date_Zone(val, TRUE); - - return TRUE; +// +// MAKE_Date: C +// +void MAKE_Date(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { + assert(kind == REB_DATE); + UNUSED(kind); + + if (IS_DATE(arg)) { + Move_Value(out, arg); + return; + } + + if (IS_STRING(arg)) { + REBCNT len; + REBYTE *bp = Temp_Byte_Chars_May_Fail(arg, MAX_SCAN_DATE, &len, FALSE); + if (NULL == Scan_Date(out, bp, len)) + goto bad_make; + return; + } + + if (ANY_ARRAY(arg) && VAL_ARRAY_LEN_AT(arg) >= 3) { + const RELVAL *item = VAL_ARRAY_AT(arg); + if (NOT(IS_INTEGER(item))) + goto bad_make; + + REBCNT day = Int32s(item, 1); + + ++item; + if (NOT(IS_INTEGER(item))) + goto bad_make; + + REBCNT month = Int32s(item, 1); + + ++item; + if (NOT(IS_INTEGER(item))) + goto bad_make; + + REBCNT year; + if (day > 99) { + year = day; + day = Int32s(item, 1); + ++item; + } + else { + year = Int32s(item, 0); + ++item; + } + + if (month < 1 || month > 12) + goto bad_make; + + if (year > MAX_YEAR || day < 1 || day > Month_Max_Days[month-1]) + goto bad_make; + + // Check February for leap year or century: + if (month == 2 && day == 29) { + if (((year % 4) != 0) || // not leap year + ((year % 100) == 0 && // century? + (year % 400) != 0)) goto bad_make; // not leap century + } + + day--; + month--; + + REBI64 secs; + REBINT tz; + if (IS_END(item)) { + secs = 0; + tz = 0; + } + else { + if (NOT(IS_TIME(item))) + goto bad_make; + + secs = VAL_NANO(item); + ++item; + + if (IS_END(item)) + tz = 0; + else { + if (NOT(IS_TIME(item))) + goto bad_make; + + tz = cast(REBINT, VAL_NANO(item) / (ZONE_MINS * MIN_SEC)); + if (tz < -MAX_ZONE || tz > MAX_ZONE) + fail (Error_Out_Of_Range(const_KNOWN(item))); + ++item; + } + } + + if (NOT_END(item)) + goto bad_make; + + Normalize_Time(&secs, &day); + + VAL_RESET_HEADER(out, REB_DATE); + VAL_DATE(out) = Normalize_Date(day, month, year, tz); + VAL_NANO(out) = secs; + Adjust_Date_Zone(out, TRUE); + return; + } + +bad_make: + fail (Error_Bad_Make(REB_DATE, arg)); } -/*********************************************************************** -** -*/ REBINT PD_Date(REBPVS *pvs) -/* -***********************************************************************/ -{ - REBVAL *data = pvs->value; - REBVAL *arg = pvs->select; - REBVAL *val = pvs->setval; - REBINT i; - REBINT n; - REBI64 secs; - REBINT tz; - REBDAT date; - REBINT day, month, year; - REBINT num; - REBVAL dat; - REB_TIMEF time; - - // !zone! - adjust date by zone (unless /utc given) - - if (IS_WORD(arg)) { - //!!! change this to an array!? - switch (VAL_WORD_CANON(arg)) { - case SYM_YEAR: i = 0; break; - case SYM_MONTH: i = 1; break; - case SYM_DAY: i = 2; break; - case SYM_TIME: i = 3; break; - case SYM_ZONE: i = 4; break; - case SYM_DATE: i = 5; break; - case SYM_WEEKDAY: i = 6; break; - case SYM_JULIAN: - case SYM_YEARDAY: i = 7; break; - case SYM_UTC: i = 8; break; - case SYM_HOUR: i = 9; break; - case SYM_MINUTE: i = 10; break; - case SYM_SECOND: i = 11; break; - default: return PE_BAD_SELECT; - } - } - else if (IS_INTEGER(arg)) { - i = Int32(arg) - 1; - if (i < 0 || i > 8) return PE_BAD_SELECT; - } - else - return PE_BAD_SELECT; - - if (IS_DATE(data)) { - dat = *data; // recode! - data = &dat; - if (i != 8) Adjust_Date_Zone(data, FALSE); // adjust for timezone - date = VAL_DATE(data); - day = VAL_DAY(data) - 1; - month = VAL_MONTH(data) - 1; - year = VAL_YEAR(data); - secs = VAL_TIME(data); - tz = VAL_ZONE(data); - if (i > 8) Split_Time(secs, &time); - } - - if (val == 0) { - val = pvs->store; - switch(i) { - case 0: - num = year; - break; - case 1: - num = month + 1; - break; - case 2: - num = day + 1; - break; - case 3: - if (secs == NO_TIME) return PE_NONE; - *val = *data; - VAL_SET(val, REB_TIME); - return PE_USE; - case 4: - if (secs == NO_TIME) return PE_NONE; - *val = *data; - VAL_TIME(val) = (i64)tz * ZONE_MINS * MIN_SEC; - VAL_SET(val, REB_TIME); - return PE_USE; - case 5: - // date - *val = *data; - VAL_TIME(val) = NO_TIME; - VAL_ZONE(val) = 0; - return PE_USE; - case 6: - // weekday - num = Week_Day(date); - break; - case 7: - // yearday - num = (REBINT)Julian_Date(date); - break; - case 8: - // utc - *val = *data; - VAL_ZONE(val) = 0; - return PE_USE; - case 9: - num = time.h; - break; - case 10: - num = time.m; - break; - case 11: - if (time.n == 0) num = time.s; - else { - SET_DECIMAL(val, (REBDEC)time.s + (time.n * NANO)); - return PE_USE; - } - break; - - default: - return PE_NONE; - } - SET_INTEGER(val, num); - return PE_USE; - - } else { - - if (IS_INTEGER(val) || IS_DECIMAL(val)) n = Int32s(val, 0); - else if (IS_NONE(val)) n = 0; - else if (IS_TIME(val) && (i == 3 || i == 4)); - else if (IS_DATE(val) && (i == 3 || i == 5)); - else return PE_BAD_SET_TYPE; - - switch(i) { - case 0: - year = n; - break; - case 1: - month = n - 1; - break; - case 2: - day = n - 1; - break; - case 3: - // time - if (IS_NONE(val)) { - secs = NO_TIME; - tz = 0; - break; - } - else if (IS_TIME(val) || IS_DATE(val)) - secs = VAL_TIME(val); - else if (IS_INTEGER(val)) - secs = n * SEC_SEC; - else if (IS_DECIMAL(val)) - secs = DEC_TO_SECS(VAL_DECIMAL(val)); - else return PE_BAD_SET_TYPE; - break; - case 4: - // zone - if (IS_TIME(val)) tz = (REBINT)(VAL_TIME(val) / (ZONE_MINS * MIN_SEC)); - else if (IS_DATE(val)) tz = VAL_ZONE(val); - else tz = n * (60 / ZONE_MINS); - if (tz > MAX_ZONE || tz < -MAX_ZONE) return PE_BAD_RANGE; - break; - case 5: - // date - if (!IS_DATE(val)) return PE_BAD_SET_TYPE; - date = VAL_DATE(val); - goto setDate; - case 9: - time.h = n; - secs = Join_Time(&time); - break; - case 10: - time.m = n; - secs = Join_Time(&time); - break; - case 11: - if (IS_INTEGER(val)) { - time.s = n; - time.n = 0; - } - else { - //if (f < 0.0) Trap_Range(val); - time.s = (REBINT)VAL_DECIMAL(val); - time.n = (REBINT)((VAL_DECIMAL(val) - time.s) * SEC_SEC); - } - secs = Join_Time(&time); - break; - - default: - return PE_BAD_SET; - } - - Normalize_Time(&secs, &day); - date = Normalize_Date(day, month, year, tz); +// +// TO_Date: C +// +void TO_Date(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { + MAKE_Date(out, kind, arg); +} -setDate: - data = pvs->value; - VAL_SET(data, REB_DATE); - VAL_DATE(data) = date; - VAL_TIME(data) = secs; - Adjust_Date_Zone(data, TRUE); - - return PE_USE; - } + +static REBINT Int_From_Date_Arg(const REBVAL *opt_poke) { + if (IS_INTEGER(opt_poke) || IS_DECIMAL(opt_poke)) + return Int32s(opt_poke, 0); + else if (IS_BLANK(opt_poke)) + return 0; + else + fail (opt_poke); } -/*********************************************************************** -** -*/ REBTYPE(Date) -/* -***********************************************************************/ -{ - REBI64 secs; - REBINT tz; - REBDAT date; - REBINT day, month, year; - REBVAL *val; - REBVAL *arg; - REBINT num; - - val = D_ARG(1); - if (IS_DATE(val)) { - date = VAL_DATE(val); - day = VAL_DAY(val) - 1; - month = VAL_MONTH(val) - 1; - year = VAL_YEAR(val); - tz = VAL_ZONE(val); - secs = VAL_TIME(val); - } - - if (DS_ARGC > 1) arg = D_ARG(2); - - if (IS_BINARY_ACT(action)) { - REBINT type = VAL_TYPE(arg); - - if (type == REB_DATE) { - if (action == A_SUBTRACT) { - num = Diff_Date(date, VAL_DATE(arg)); - goto ret_int; - } - } - else if (type == REB_TIME) { - if (secs == NO_TIME) secs = 0; - if (action == A_ADD) { - secs += VAL_TIME(arg); - goto fixTime; - } - if (action == A_SUBTRACT) { - secs -= VAL_TIME(arg); - goto fixTime; - } - } - else if (type == REB_INTEGER) { - num = Int32(arg); - if (action == A_ADD) { - day += num; - goto fixDate; - } - if (action == A_SUBTRACT) { - day -= num; - goto fixDate; - } - } - else if (type == REB_DECIMAL) { - REBDEC dec = Dec64(arg); - if (secs == NO_TIME) secs = 0; - if (action == A_ADD) { - secs += (REBI64)(dec * TIME_IN_DAY); - goto fixTime; - } - if (action == A_SUBTRACT) { - secs -= (REBI64)(dec * TIME_IN_DAY); - goto fixTime; - } - } - } - else { - switch(action) { - case A_EVENQ: day = ~day; - case A_ODDQ: DECIDE((day & 1) == 0); - - case A_PICK: - Pick_Path(val, arg, 0); - return R_TOS; - -/// case A_POKE: -/// Pick_Path(val, arg, D_ARG(3)); -/// return R_ARG3; - - case A_MAKE: - case A_TO: - if (IS_DATE(arg)) { - val = arg; - goto ret_val; - } - if (IS_STRING(arg)) { - REBYTE *bp; - REBCNT len; - // 30-September-10000/12:34:56.123456789AM/12:34 - bp = Qualify_String(arg, 45, &len, FALSE); // can trap, ret diff str - if (Scan_Date(bp, len, D_RET)) return R_RET; - } - else if (ANY_BLOCK(arg) && VAL_BLK_LEN(arg) >= 3) { - if (MT_Date(D_RET, VAL_BLK_DATA(arg), REB_DATE)) { - return R_RET; - } - } -// else if (IS_NONE(arg)) { -// secs = nsec = day = month = year = tz = 0; -// goto fixTime; -// } - Trap_Make(REB_DATE, arg); - - case A_RANDOM: //!!! needs further definition ? random/zero - if (D_REF(2)) { - // Note that nsecs not set often for dates (requires /precise) - Set_Random(((REBI64)year << 48) + ((REBI64)Julian_Date(date) << 32) + secs); - return R_UNSET; - } - if (year == 0) break; - num = D_REF(3); // secure - year = (REBCNT)Random_Range(year, num); - month = (REBCNT)Random_Range(12, num); - day = (REBCNT)Random_Range(31, num); - if (secs != NO_TIME) - secs = Random_Range(TIME_IN_DAY, num); - goto fixDate; - - case A_ABSOLUTE: - goto setDate; - } - } - Trap_Action(REB_DATE, action); +// +// Pick_Or_Poke_Date: C +// +void Pick_Or_Poke_Date( + REBVAL *opt_out, + REBVAL *value, + const REBVAL *picker, + const REBVAL *opt_poke +) { + REBDAT date = VAL_DATE(value); + REBCNT day = VAL_DAY(value) - 1; + REBCNT month = VAL_MONTH(value) - 1; + REBCNT year = VAL_YEAR(value); + + REBI64 secs = VAL_NANO(value); + REBINT tz = VAL_ZONE(value); + + REBSYM sym; + if (IS_WORD(picker)) { + sym = VAL_WORD_SYM(picker); // error later if SYM_0 or not a match + } + else if (IS_INTEGER(picker)) { + switch (Int32(picker)) { + case 1: sym = SYM_YEAR; break; + case 2: sym = SYM_MONTH; break; + case 3: sym = SYM_DAY; break; + case 4: sym = SYM_TIME; break; + case 5: sym = SYM_ZONE; break; + case 6: sym = SYM_DATE; break; + case 7: sym = SYM_WEEKDAY; break; + case 8: sym = SYM_JULIAN; break; // a.k.a. SYM_YEARDAY + case 9: sym = SYM_UTC; break; + case 10: sym = SYM_HOUR; break; + case 11: sym = SYM_MINUTE; break; + case 12: sym = SYM_SECOND; break; + default: + fail (picker); + } + } + else + fail (picker); + + REB_TIMEF time; // only pay for split into this if needed... + + if (opt_poke == NULL) { + assert(opt_out != NULL); + Move_Value(opt_out, value); + + if (sym != SYM_UTC) Adjust_Date_Zone(opt_out, FALSE); + + switch (sym) { + case SYM_YEAR: + Init_Integer(opt_out, year); + break; + + case SYM_MONTH: + Init_Integer(opt_out, month + 1); + break; + + case SYM_DAY: + Init_Integer(opt_out, day + 1); + break; + + case SYM_TIME: + if (secs == NO_TIME) + Init_Void(opt_out); + else + VAL_RESET_HEADER(opt_out, REB_TIME); + break; + + case SYM_ZONE: + if (secs == NO_TIME) + Init_Void(opt_out); + else { + VAL_RESET_HEADER(opt_out, REB_TIME); + VAL_NANO(opt_out) = cast(i64, tz) * ZONE_MINS * MIN_SEC; + } + break; + + case SYM_DATE: + VAL_NANO(opt_out) = NO_TIME; + VAL_ZONE(opt_out) = 0; + break; + + case SYM_WEEKDAY: + Init_Integer(opt_out, Week_Day(date)); + break; + + case SYM_JULIAN: + case SYM_YEARDAY: + Init_Integer(opt_out, cast(REBINT, Julian_Date(date))); + break; + + case SYM_UTC: + VAL_ZONE(opt_out) = 0; + break; + + case SYM_HOUR: + Split_Time(secs, &time); + Init_Integer(opt_out, time.h); + break; + + case SYM_MINUTE: + Split_Time(secs, &time); + Init_Integer(opt_out, time.m); + break; + + case SYM_SECOND: + Split_Time(secs, &time); + if (time.n == 0) + Init_Integer(opt_out, time.s); + else + Init_Decimal(opt_out, cast(REBDEC, time.s) + (time.n * NANO)); + break; + + default: + Init_Void(opt_out); // "out of range" PICK semantics + } + } + else { + assert(opt_out == NULL); + + // Here the desire is to modify the incoming date directly. This is + // done by changing the components that need to change which were + // extracted, and building a new date out of the parts. + + switch (sym) { + case SYM_YEAR: + year = Int_From_Date_Arg(opt_poke); + break; + + case SYM_MONTH: + month = Int_From_Date_Arg(opt_poke) - 1; + break; + + case SYM_DAY: + day = Int_From_Date_Arg(opt_poke) - 1; + break; + + case SYM_TIME: + if (IS_BLANK(opt_poke)) { + secs = NO_TIME; + tz = 0; + break; + } + else if (IS_TIME(opt_poke) || IS_DATE(opt_poke)) + secs = VAL_NANO(opt_poke); + else if (IS_INTEGER(opt_poke)) + secs = Int_From_Date_Arg(opt_poke) * SEC_SEC; + else if (IS_DECIMAL(opt_poke)) + secs = DEC_TO_SECS(VAL_DECIMAL(opt_poke)); + else + fail (opt_poke); + break; + + case SYM_ZONE: + if (IS_TIME(opt_poke)) + tz = cast(REBINT, VAL_NANO(opt_poke) / (ZONE_MINS * MIN_SEC)); + else if (IS_DATE(opt_poke)) + tz = VAL_ZONE(opt_poke); + else tz = Int_From_Date_Arg(opt_poke) * (60 / ZONE_MINS); + if (tz > MAX_ZONE || tz < -MAX_ZONE) + fail (Error_Out_Of_Range(opt_poke)); + break; + + case SYM_JULIAN: + case SYM_WEEKDAY: + case SYM_UTC: + fail (picker); + + case SYM_DATE: + if (!IS_DATE(opt_poke)) + fail (opt_poke); + date = VAL_DATE(opt_poke); + goto set_without_normalize; + + case SYM_HOUR: + Split_Time(secs, &time); + time.h = Int_From_Date_Arg(opt_poke); + secs = Join_Time(&time, FALSE); + break; + + case SYM_MINUTE: + Split_Time(secs, &time); + time.m = Int_From_Date_Arg(opt_poke); + secs = Join_Time(&time, FALSE); + break; + + case SYM_SECOND: + Split_Time(secs, &time); + if (IS_INTEGER(opt_poke)) { + time.s = Int_From_Date_Arg(opt_poke); + time.n = 0; + } + else { + //if (f < 0.0) fail (Error_Out_Of_Range(setval)); + time.s = cast(REBINT, VAL_DECIMAL(opt_poke)); + time.n = cast(REBINT, + (VAL_DECIMAL(opt_poke) - time.s) * SEC_SEC); + } + secs = Join_Time(&time, FALSE); + break; + + default: + fail (picker); + } + + Normalize_Time(&secs, &day); + date = Normalize_Date(day, month, year, tz); + + set_without_normalize: + VAL_RESET_HEADER(value, REB_DATE); + VAL_DATE(value) = date; + VAL_NANO(value) = secs; + Adjust_Date_Zone(value, TRUE); + } +} -fixTime: - Normalize_Time(&secs, &day); -fixDate: - date = Normalize_Date(day, month, year, tz); +// +// PD_Date: C +// +REBINT PD_Date(REBPVS *pvs) +{ + if (pvs->opt_setval) { + // + // !!! SET-PATH! in R3-Alpha could be used on DATE! even though it + // was an immediate value. It would thus modify the evaluated value, + // while not affecting the original (unless it was a literal value + // in source) + // + Pick_Or_Poke_Date( + NULL, KNOWN(pvs->value), pvs->picker, pvs->opt_setval + ); + return PE_OK; + } + + Pick_Or_Poke_Date(pvs->store, KNOWN(pvs->value), pvs->picker, NULL); + return PE_USE_STORE; +} -setDate: - VAL_SET(DS_RETURN, REB_DATE); - VAL_DATE(DS_RETURN) = date; - VAL_TIME(DS_RETURN) = secs; - return R_RET; -ret_int: - DS_RET_INT(num); - return R_RET; +// +// REBTYPE: C +// +REBTYPE(Date) +{ + REBVAL *val = D_ARG(1); + assert(IS_DATE(val)); + + REBDAT date = VAL_DATE(val); + REBCNT day = VAL_DAY(val) - 1; + REBCNT month = VAL_MONTH(val) - 1; + REBCNT year = VAL_YEAR(val); + REBINT tz = VAL_ZONE(val); + REBI64 secs = VAL_NANO(val); + + REBVAL *arg = D_ARGC > 1 ? D_ARG(2) : NULL; + + if (action == SYM_SUBTRACT || action == SYM_ADD) { + REBINT type = VAL_TYPE(arg); + + if (type == REB_DATE) { + if (action == SYM_SUBTRACT) { + Init_Integer(D_OUT, Diff_Date(date, VAL_DATE(arg))); + return R_OUT; + } + } + else if (type == REB_TIME) { + if (secs == NO_TIME) + secs = 0; + if (action == SYM_ADD) { + secs += VAL_NANO(arg); + goto fixTime; + } + if (action == SYM_SUBTRACT) { + secs -= VAL_NANO(arg); + goto fixTime; + } + } + else if (type == REB_INTEGER) { + REBINT num = Int32(arg); + if (action == SYM_ADD) { + day += num; + goto fixDate; + } + if (action == SYM_SUBTRACT) { + day -= num; + goto fixDate; + } + } + else if (type == REB_DECIMAL) { + REBDEC dec = Dec64(arg); + if (secs == NO_TIME) secs = 0; + if (action == SYM_ADD) { + secs += (REBI64)(dec * TIME_IN_DAY); + goto fixTime; + } + if (action == SYM_SUBTRACT) { + secs -= (REBI64)(dec * TIME_IN_DAY); + goto fixTime; + } + } + } + else { + switch(action) { + case SYM_EVEN_Q: + return ((~day) & 1) == 0 ? R_TRUE : R_FALSE; + + case SYM_ODD_Q: + return (day & 1) == 0 ? R_TRUE : R_FALSE; + + case SYM_RANDOM: { + INCLUDE_PARAMS_OF_RANDOM; + + UNUSED(PAR(value)); + + if (REF(only)) + fail (Error_Bad_Refines_Raw()); + + const REBOOL secure = REF(secure); + + if (REF(seed)) { + // + // Note that nsecs not set often for dates (requires /precise) + // + Set_Random( + (cast(REBI64, year) << 48) + + (cast(REBI64, Julian_Date(date)) << 32) + + secs + ); + return R_VOID; + } + + if (year == 0) break; + + year = cast(REBCNT, Random_Range(year, secure)); + month = cast(REBCNT, Random_Range(12, secure)); + day = cast(REBCNT, Random_Range(31, secure)); + + if (secs != NO_TIME) + secs = Random_Range(TIME_IN_DAY, secure); + + goto fixDate; + } + + case SYM_ABSOLUTE: + goto setDate; + + default: + fail (Error_Illegal_Action(REB_DATE, action)); + } + } + fail (Error_Illegal_Action(REB_DATE, action)); -ret_val: - *DS_RETURN = *val; - return R_RET; +fixTime: + Normalize_Time(&secs, &day); -is_false: - return R_FALSE; +fixDate: + date = Normalize_Date(day, month, year, tz); -is_true: - return R_TRUE; +setDate: + VAL_RESET_HEADER(D_OUT, REB_DATE); + VAL_DATE(D_OUT) = date; + VAL_NANO(D_OUT) = secs; + return R_OUT; } diff --git a/src/core/t-decimal.c b/src/core/t-decimal.c index f67564d1bf..e4672fe138 100644 --- a/src/core/t-decimal.c +++ b/src/core/t-decimal.c @@ -1,31 +1,32 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-decimal.c -** Summary: decimal datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-decimal.c +// Summary: "decimal datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" #include @@ -38,448 +39,491 @@ #ifdef NO_GCVT static char *gcvt(double value, int digits, char *buffer) { - sprintf(buffer, "%.*g", digits, value); - return buffer; + sprintf(buffer, "%.*g", digits, value); + return buffer; } #endif /* - Purpose: {defines the almost_equal comparison function} - Properties: { - since floating point numbers are ordered and there is only - a finite quantity of floating point numbers, it is possible - to assign an ordinal (integer) number to any floating point number so, - that the ordinal numbers of neighbors differ by one - - the function compares floating point numbers based on - the difference of their ordinal numbers in the ordering - of floating point numbers - - difference of 0 means exact equality, difference of 1 means, that - the numbers are neighbors. - } - Advantages: { - the function detects approximate equality. - - the function is more strict in the zero neighborhood than - absolute-error-based approaches - - as opposed to relative-error-based approaches the error can be - precisely specified, max_diff = 0 meaning exact match, max_diff = 1 - meaning that neighbors are deemed equal, max_diff = 10 meaning, that - the numbers are deemed equal if at most 9 - distinct floating point numbers can be found between them - - the max_diff value may be one of the system options specified in - the system/options object allowing users to exactly define the - strictness of equality checks - } - Differences: { - The approximate comparison currently used in R3 corresponds to the - almost_equal function using max_diff = 10 (according to my tests). - - The main differences between the currently used comparison and the - one based on the ordinal number comparison are: - - the max_diff parameter can be adjusted, allowing - the user to precisely specify the strictness of the comparison - - the difference rule holds for zero too, which means, that - zero is deemed equal with totally max_diff distinct (tiny) numbers - } - Notes: { - the max_diff parameter does not need to be a REBI64 number, - a smaller range like REBCNT may suffice - } + Purpose: {defines the almost_equal comparison function} + Properties: { + since floating point numbers are ordered and there is only + a finite quantity of floating point numbers, it is possible + to assign an ordinal (integer) number to any floating point number so, + that the ordinal numbers of neighbors differ by one + + the function compares floating point numbers based on + the difference of their ordinal numbers in the ordering + of floating point numbers + + difference of 0 means exact equality, difference of 1 means, that + the numbers are neighbors. + } + Advantages: { + the function detects approximate equality. + + the function is more strict in the zero neighborhood than + absolute-error-based approaches + + as opposed to relative-error-based approaches the error can be + precisely specified, max_diff = 0 meaning exact match, max_diff = 1 + meaning that neighbors are deemed equal, max_diff = 10 meaning, that + the numbers are deemed equal if at most 9 + distinct floating point numbers can be found between them + + the max_diff value may be one of the system options specified in + the system/options object allowing users to exactly define the + strictness of equality checks + } + Differences: { + The approximate comparison currently used in R3 corresponds to the + almost_equal function using max_diff = 10 (according to my tests). + + The main differences between the currently used comparison and the + one based on the ordinal number comparison are: + - the max_diff parameter can be adjusted, allowing + the user to precisely specify the strictness of the comparison + - the difference rule holds for zero too, which means, that + zero is deemed equal with totally max_diff distinct (tiny) numbers + } + Notes: { + the max_diff parameter does not need to be a REBI64 number, + a smaller range like REBCNT may suffice + } */ REBOOL almost_equal(REBDEC a, REBDEC b, REBCNT max_diff) { - union {REBDEC d; REBI64 i;} ua, ub; - REBI64 int_diff; + union {REBDEC d; REBI64 i;} ua, ub; + REBI64 int_diff; - ua.d = a; - ub.d = b; + ua.d = a; + ub.d = b; - /* Make ua.i a twos-complement ordinal number */ - if (ua.i < 0) ua.i = MIN_I64 - ua.i; + /* Make ua.i a twos-complement ordinal number */ + if (ua.i < 0) ua.i = MIN_I64 - ua.i; - /* Make ub.i a twos-complement ordinal number */ - if (ub.i < 0) ub.i = MIN_I64 - ub.i; + /* Make ub.i a twos-complement ordinal number */ + if (ub.i < 0) ub.i = MIN_I64 - ub.i; - int_diff = ua.i - ub.i; - if (int_diff < 0) int_diff = -int_diff; - - return ((REBU64) int_diff <= max_diff); + int_diff = ua.i - ub.i; + if (int_diff < 0) int_diff = -int_diff; + + return LOGICAL(cast(REBU64, int_diff) <= max_diff); } -/*********************************************************************** -** -*/ REBFLG MT_Decimal(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ +// +// Init_Decimal_Bits: C +// +void Init_Decimal_Bits(REBVAL *out, const REBYTE *bp) { - if (!IS_END(data+1)) return FALSE; + VAL_RESET_HEADER(out, REB_DECIMAL); + + REBYTE *dp = cast(REBYTE*, &VAL_DECIMAL(out)); + +#ifdef ENDIAN_LITTLE + REBCNT n; + for (n = 0; n < 8; ++n) + dp[n] = bp[7 - n]; +#elif defined(ENDIAN_BIG) + REBCNT n; + for (n = 0; n < 8; ++n) + dp[n] = bp[n]; +#else + #error "Unsupported CPU endian" +#endif +} - if (IS_DECIMAL(data)) - *out = *data; - else if (IS_INTEGER(data)) { - SET_DECIMAL(out, (REBDEC)VAL_INT64(data)); - } - else return FALSE; - SET_TYPE(out, type); - return TRUE; +// +// MAKE_Decimal: C +// +void MAKE_Decimal(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { + REBDEC d; + + switch (VAL_TYPE(arg)) { + case REB_DECIMAL: + d = VAL_DECIMAL(arg); + goto dont_divide_if_percent; + + case REB_PERCENT: + d = VAL_DECIMAL(arg); + goto dont_divide_if_percent; + + case REB_INTEGER: + d = cast(REBDEC, VAL_INT64(arg)); + goto dont_divide_if_percent; + + case REB_MONEY: + d = deci_to_decimal(VAL_MONEY_AMOUNT(arg)); + goto dont_divide_if_percent; + + case REB_LOGIC: + d = VAL_LOGIC(arg) ? 1.0 : 0.0; + goto dont_divide_if_percent; + + case REB_CHAR: + d = cast(REBDEC, VAL_CHAR(arg)); + goto dont_divide_if_percent; + + case REB_TIME: + d = VAL_NANO(arg) * NANO; + break; + + case REB_STRING: + { + REBCNT len; + REBYTE *bp = Temp_Byte_Chars_May_Fail( + arg, MAX_SCAN_DECIMAL, &len, FALSE + ); + + if (NULL == Scan_Decimal(out, bp, len, LOGICAL(kind != REB_PERCENT))) + goto bad_make; + + d = VAL_DECIMAL(out); // may need to divide if percent, fall through + break; + } + + case REB_BINARY: + if (VAL_LEN_AT(arg) < 8) + fail (arg); + + Init_Decimal_Bits(out, VAL_BIN_AT(arg)); + VAL_RESET_HEADER(out, kind); + d = VAL_DECIMAL(out); + break; + + default: + if (ANY_ARRAY(arg) && VAL_ARRAY_LEN_AT(arg) == 2) { + RELVAL *item = VAL_ARRAY_AT(arg); + if (IS_INTEGER(item)) + d = cast(REBDEC, VAL_INT64(item)); + else if (IS_DECIMAL(item) || IS_PERCENT(item)) + d = VAL_DECIMAL(item); + else { + DECLARE_LOCAL (specific); + Derelativize(specific, item, VAL_SPECIFIER(arg)); + + fail (specific); + } + + ++item; + + REBDEC exp; + if (IS_INTEGER(item)) + exp = cast(REBDEC, VAL_INT64(item)); + else if (IS_DECIMAL(item) || IS_PERCENT(item)) + exp = VAL_DECIMAL(item); + else { + DECLARE_LOCAL (specific); + Derelativize(specific, item, VAL_SPECIFIER(arg)); + fail (specific); + } + + while (exp >= 1) { + // + // !!! Comment here said "funky. There must be a better way" + // + --exp; + d *= 10.0; + if (!FINITE(d)) + fail (Error_Overflow_Raw()); + } + + while (exp <= -1) { + ++exp; + d /= 10.0; + } + } + else + fail (Error_Bad_Make(kind, arg)); + } + + if (kind == REB_PERCENT) + d /= 100.0; + +dont_divide_if_percent: + if (!FINITE(d)) + fail (Error_Overflow_Raw()); + + VAL_RESET_HEADER(out, kind); + VAL_DECIMAL(out) = d; + return; + +bad_make: + fail (Error_Bad_Make(kind, arg)); } -/*********************************************************************** -** -*/ REBFLG Eq_Decimal(REBDEC a, REBDEC b) -/* -***********************************************************************/ +// +// TO_Decimal: C +// +void TO_Decimal(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - return almost_equal(a, b, 10); -#ifdef older - REBDEC d = (COEF * a) - (COEF * b); - static volatile REBDEC c, e; - c = b + d; // These are stored in variables to avoid 80bit - e = a - d; // intermediate math, which creates problems. - if ((c - b) == 0.0 && (e - a) == 0.0) return TRUE; - return FALSE; -#endif + MAKE_Decimal(out, kind, arg); } -/*********************************************************************** -** -*/ REBFLG Eq_Decimal2(REBDEC a, REBDEC b) -/* -***********************************************************************/ +// +// Eq_Decimal: C +// +REBOOL Eq_Decimal(REBDEC a, REBDEC b) { - return almost_equal(a, b, 0); + return almost_equal(a, b, 10); #ifdef older - REBI64 d; - if (a == b) return TRUE; - d = *(REBU64*)&a - *(REBU64*)&b; - if (d < 0) d = ~d; - if (d <= EQ_RANGE) return TRUE; - return FALSE; + REBDEC d = (COEF * a) - (COEF * b); + static volatile REBDEC c, e; + c = b + d; // These are stored in variables to avoid 80bit + e = a - d; // intermediate math, which creates problems. + if ((c - b) == 0.0 && (e - a) == 0.0) return TRUE; + return FALSE; #endif } -/*********************************************************************** -** -*/ REBINT CT_Decimal(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ -{ - if (mode >= 0) { - if (mode <= 1) return almost_equal(VAL_DECIMAL(a), VAL_DECIMAL(b), 10); - if (mode == 2) return almost_equal(VAL_DECIMAL(a), VAL_DECIMAL(b), 0); - return VAL_INT64(a) == VAL_INT64(b); // bits are identical - } - if (mode == -1) return VAL_DECIMAL(a) >= VAL_DECIMAL(b); - return VAL_DECIMAL(a) > VAL_DECIMAL(b); -} -/*********************************************************************** -** -*/ static void Check_Overflow(REBDEC dval) -/* -***********************************************************************/ +// +// Eq_Decimal2: C +// +REBOOL Eq_Decimal2(REBDEC a, REBDEC b) { - if (!FINITE(dval)) Trap0(RE_OVERFLOW); + return almost_equal(a, b, 0); +#ifdef older + REBI64 d; + if (a == b) return TRUE; + d = *(REBU64*)&a - *(REBU64*)&b; + if (d < 0) d = ~d; + if (d <= EQ_RANGE) return TRUE; + return FALSE; +#endif } -/*********************************************************************** -** -*/ static void Binary_To_Decimal(REBVAL *bin, REBVAL *dec) -/* -***********************************************************************/ +// +// CT_Decimal: C +// +REBINT CT_Decimal(const RELVAL *a, const RELVAL *b, REBINT mode) { - REBI64 n = 0; - REBSER *ser = VAL_SERIES(bin); - REBCNT idx = VAL_INDEX(bin); - REBCNT len = VAL_LEN(bin); + if (mode >= 0) { + if (mode == 0) + return almost_equal(VAL_DECIMAL(a), VAL_DECIMAL(b), 10) ? 1 : 0; - if (len > 8) len = 8; + return almost_equal(VAL_DECIMAL(a), VAL_DECIMAL(b), 0) ? 1 : 0; + } - for (; len; len--, idx++) n = (n << 8) | (REBI64)(GET_ANY_CHAR(ser, idx)); + if (mode == -1) + return (VAL_DECIMAL(a) >= VAL_DECIMAL(b)) ? 1 : 0; - VAL_SET(dec, REB_DECIMAL); - VAL_INT64(dec) = n; // aliasing the bits! + return (VAL_DECIMAL(a) > VAL_DECIMAL(b)) ? 1 : 0; } -/*********************************************************************** -** -*/ REBTYPE(Decimal) -/* -***********************************************************************/ +// +// REBTYPE: C +// +REBTYPE(Decimal) { - REBVAL *val = D_ARG(1); - REBDEC d1 = VAL_DECIMAL(val); - REBVAL *arg; - REBDEC d2; - REBINT num; - REBDEC exp; - REBINT type = 0; - - // all binary actions - if (IS_BINARY_ACT(action)) { - - arg = D_ARG(2); - type = VAL_TYPE(arg); - if (type != REB_DECIMAL && ( - type == REB_PAIR || - type == REB_TUPLE || - type == REB_MONEY || - type == REB_TIME - ) && ( - action == A_ADD || - action == A_MULTIPLY - ) - ){ - *D_RET = *D_ARG(2); - *D_ARG(2) = *D_ARG(1); - *D_ARG(1) = *D_RET; - return Value_Dispatch[VAL_TYPE(D_ARG(1))](ds, action); - } - - // If the type of the second arg is something we can handle: - if (type == REB_DECIMAL - || type == REB_INTEGER - || type == REB_PERCENT - || type == REB_MONEY - || type == REB_CHAR - ){ - if (type == REB_DECIMAL) { - d2 = VAL_DECIMAL(arg); - } else if (type == REB_PERCENT) { - d2 = VAL_DECIMAL(arg); - if (action == A_DIVIDE) type = REB_DECIMAL; - else if (!IS_PERCENT(val)) type = VAL_TYPE(val); - } else if (type == REB_MONEY) { - VAL_DECI(val) = decimal_to_deci(VAL_DECIMAL(val)); - VAL_SET(val, REB_MONEY); - return T_Money(ds, action); - } else if (type == REB_CHAR) { - d2 = (REBDEC)VAL_CHAR(arg); - type = REB_DECIMAL; - } else { - d2 = (REBDEC)VAL_INT64(arg); - type = REB_DECIMAL; - } - - switch (action) { - - case A_ADD: - d1 += d2; - goto setDec; - - case A_SUBTRACT: - d1 -= d2; - goto setDec; - - case A_MULTIPLY: - d1 *= d2; - goto setDec; - - case A_DIVIDE: - case A_REMAINDER: - if (d2 == 0.0) Trap0(RE_ZERO_DIVIDE); - if (action == A_DIVIDE) d1 /= d2; - else d1 = fmod(d1, d2); - goto setDec; - - case A_POWER: - if (d1 == 0) goto setDec; - if (d2 == 0) { - d1 = 1.0; - goto setDec; - } - //if (d1 < 0 && d2 < 1 && d2 != -1) - // Trap0(RE_POSITIVE); - d1 = pow(d1, d2); - goto setDec; - - } - } - Trap_Math_Args(VAL_TYPE(val), action); - } - else { - type = VAL_TYPE(val); - - // unary actions - switch (action) { - - case A_NEGATE: - d1 = -d1; - goto setDec; - case A_ABSOLUTE: - if (d1 < 0) d1 = -d1; - goto setDec; - case A_EVENQ: - case A_ODDQ: - d1 = fabs(fmod(d1, 2.0)); - DECIDE((action != A_EVENQ) != ((d1 < 0.5) || (d1 >= 1.5))); - - case A_MAKE: - case A_TO: - // MAKE decimal! 2 and MAKE 1.0 2 formats: - if (IS_DATATYPE(val)) type = VAL_DATATYPE(val); - else type = VAL_TYPE(val); - - val = D_ARG(2); - - switch (VAL_TYPE(val)) { - - case REB_DECIMAL: - d1 = VAL_DECIMAL(val); - goto setDec; - - case REB_PERCENT: - d1 = VAL_DECIMAL(val); - goto setDec; - - case REB_INTEGER: - d1 = (REBDEC)VAL_INT64(val); - goto setDec; - - case REB_MONEY: - d1 = deci_to_decimal(VAL_DECI(val)); - goto setDec; - - case REB_LOGIC: - d1 = VAL_LOGIC(val) ? 1.0 : 0.0; - goto setDec; - - case REB_CHAR: - d1 = (REBDEC)VAL_CHAR(val); - goto setDec; - - case REB_TIME: - d1 = VAL_TIME(val) * NANO; - break; - - case REB_STRING: - { - REBYTE *bp; - REBCNT len; - bp = Qualify_String(val, 24, &len, FALSE); - if (Scan_Decimal(bp, len, D_RET, type != REB_PERCENT)) { - d1 = VAL_DECIMAL(D_RET); - if (type == REB_PERCENT) break; - goto setDec; - } - Trap_Make(type, val); - } - - case REB_BINARY: - Binary_To_Decimal(val, D_RET); - d1 = VAL_DECIMAL(D_RET); - break; - -#ifdef removed -// case REB_ISSUE: - { - REBYTE *bp; - REBCNT len; - bp = Qualify_String(val, MAX_HEX_LEN, &len, FALSE); - if (Scan_Hex(bp, &VAL_INT64(D_RET), len, len) == 0) - Trap_Make(REB_DECIMAL, val); - d1 = VAL_DECIMAL(D_RET); - break; - } -#endif - - default: - if (ANY_BLOCK(val) && VAL_BLK_LEN(val) == 2) { - arg = VAL_BLK_DATA(val); - if (IS_INTEGER(arg)) d1 = (REBDEC)VAL_INT64(arg); - else if (IS_DECIMAL(arg) || IS_PERCENT(val)) d1 = VAL_DECIMAL(arg); - else Trap_Make(REB_DECIMAL, arg); - - if (IS_INTEGER(++arg)) exp = (REBDEC)VAL_INT64(arg); - else if (IS_DECIMAL(arg) || IS_PERCENT(val)) exp = VAL_DECIMAL(arg); - else Trap_Make(REB_DECIMAL, arg); - while (exp >= 1) // funky. There must be a better way - exp--, d1 *= 10.0, Check_Overflow(d1); - while (exp <= -1) - exp++, d1 /= 10.0; - } else - Trap_Make(type, val); - } - - if (type == REB_PERCENT) d1 /= 100.0; - goto setDec; - - case A_ROUND: - arg = D_ARG(3); - num = Get_Round_Flags(ds); - if (D_REF(2)) { // to - if (IS_MONEY(arg)) { - VAL_DECI(D_RET) = Round_Deci(decimal_to_deci(d1), num, VAL_DECI(arg)); - SET_TYPE(D_RET, REB_MONEY); - return R_RET; - } - if (IS_TIME(arg)) Trap_Arg(arg); - - d1 = Round_Dec(d1, num, Dec64(arg)); - if (IS_INTEGER(arg)) { - VAL_INT64(D_RET) = (REBI64)d1; - SET_TYPE(D_RET, REB_INTEGER); - return R_RET; - } - if (IS_PERCENT(arg)) type = REB_PERCENT; - } - else - d1 = Round_Dec(d1, num | 1, type == REB_PERCENT ? 0.01L : 1.0L); // /TO - goto setDec; - - case A_RANDOM: - if (D_REF(2)) { - Set_Random(VAL_INT64(val)); // use IEEE bits - return R_UNSET; - } -#ifdef OLD_METHOD - if (d1 > (double) (((unsigned long) -1)>>1)) - d1 = ((unsigned long) -1)>>1; - i = (REBINT)d1; - if (i == 0) goto setDec; - if (i < 0) d1 = -1.0 * (1.0 + (REBDEC)(Random_Int((REBOOL)D_REF(3)) % abs(i))); - else d1 = 1.0 + (REBDEC)(Random_Int((REBOOL)D_REF(3)) % i); -#else - d1 = Random_Dec(d1, D_REF(3)); -#endif - goto setDec; - - case A_COMPLEMENT: - SET_INTEGER(D_RET, ~(REBINT)d1); - return R_RET; - } - } - Trap_Action(VAL_TYPE(val), action); + REBVAL *val = D_ARG(1); + REBVAL *arg; + REBDEC d2; + enum Reb_Kind type; + + REBDEC d1 = VAL_DECIMAL(val); + + // !!! This used to use IS_BINARY_ACT() which is no longer available with + // symbol-based dispatch. Consider doing this another way. + // + if ( + action == SYM_ADD + || action == SYM_SUBTRACT + || action == SYM_MULTIPLY + || action == SYM_DIVIDE + || action == SYM_REMAINDER + || action == SYM_POWER + ){ + arg = D_ARG(2); + type = VAL_TYPE(arg); + if (type != REB_DECIMAL && ( + type == REB_PAIR || + type == REB_TUPLE || + type == REB_MONEY || + type == REB_TIME + ) && ( + action == SYM_ADD || + action == SYM_MULTIPLY + ) + ){ + Move_Value(D_OUT, D_ARG(2)); + Move_Value(D_ARG(2), D_ARG(1)); + Move_Value(D_ARG(1), D_OUT); + return Value_Dispatch[VAL_TYPE(D_ARG(1))](frame_, action); + } + + // If the type of the second arg is something we can handle: + if (type == REB_DECIMAL + || type == REB_INTEGER + || type == REB_PERCENT + || type == REB_MONEY + || type == REB_CHAR + ){ + if (type == REB_DECIMAL) { + d2 = VAL_DECIMAL(arg); + } else if (type == REB_PERCENT) { + d2 = VAL_DECIMAL(arg); + if (action == SYM_DIVIDE) type = REB_DECIMAL; + else if (!IS_PERCENT(val)) type = VAL_TYPE(val); + } else if (type == REB_MONEY) { + Init_Money(val, decimal_to_deci(VAL_DECIMAL(val))); + return T_Money(frame_, action); + } else if (type == REB_CHAR) { + d2 = (REBDEC)VAL_CHAR(arg); + type = REB_DECIMAL; + } else { + d2 = (REBDEC)VAL_INT64(arg); + type = REB_DECIMAL; + } + + switch (action) { + + case SYM_ADD: + d1 += d2; + goto setDec; + + case SYM_SUBTRACT: + d1 -= d2; + goto setDec; + + case SYM_MULTIPLY: + d1 *= d2; + goto setDec; + + case SYM_DIVIDE: + case SYM_REMAINDER: + if (d2 == 0.0) fail (Error_Zero_Divide_Raw()); + if (action == SYM_DIVIDE) d1 /= d2; + else d1 = fmod(d1, d2); + goto setDec; + + case SYM_POWER: + if (d1 == 0) goto setDec; + if (d2 == 0) { + d1 = 1.0; + goto setDec; + } + //if (d1 < 0 && d2 < 1 && d2 != -1) + // fail (Error_Positive_Raw()); + d1 = pow(d1, d2); + goto setDec; + + default: + fail (Error_Math_Args(VAL_TYPE(val), action)); + } + } + fail (Error_Math_Args(VAL_TYPE(val), action)); + } + else { + type = VAL_TYPE(val); + + // unary actions + switch (action) { + + case SYM_COPY: + Move_Value(D_OUT, val); + return R_OUT; + + case SYM_NEGATE: + d1 = -d1; + goto setDec; + + case SYM_ABSOLUTE: + if (d1 < 0) d1 = -d1; + goto setDec; + + case SYM_EVEN_Q: + d1 = fabs(fmod(d1, 2.0)); + if (d1 < 0.5 || d1 >= 1.5) + return R_TRUE; + return R_FALSE; + + case SYM_ODD_Q: + d1 = fabs(fmod(d1, 2.0)); + if (d1 < 0.5 || d1 >= 1.5) + return R_FALSE; + return R_TRUE; + + case SYM_ROUND: { + INCLUDE_PARAMS_OF_ROUND; + + UNUSED(PAR(value)); + + REBFLGS flags = ( + (REF(to) ? RF_TO : 0) + | (REF(even) ? RF_EVEN : 0) + | (REF(down) ? RF_DOWN : 0) + | (REF(half_down) ? RF_HALF_DOWN : 0) + | (REF(floor) ? RF_FLOOR : 0) + | (REF(ceiling) ? RF_CEILING : 0) + | (REF(half_ceiling) ? RF_HALF_CEILING : 0) + ); + + arg = ARG(scale); + if (REF(to)) { + if (IS_MONEY(arg)) { + Init_Money(D_OUT, Round_Deci( + decimal_to_deci(d1), flags, VAL_MONEY_AMOUNT(arg) + )); + return R_OUT; + } + if (IS_TIME(arg)) + fail (arg); + + d1 = Round_Dec(d1, flags, Dec64(arg)); + if (IS_INTEGER(arg)) { + VAL_RESET_HEADER(D_OUT, REB_INTEGER); + VAL_INT64(D_OUT) = cast(REBI64, d1); + return R_OUT; + } + if (IS_PERCENT(arg)) type = REB_PERCENT; + } + else + d1 = Round_Dec( + d1, flags | RF_TO, type == REB_PERCENT ? 0.01L : 1.0L + ); + goto setDec; } + + case SYM_RANDOM: { + INCLUDE_PARAMS_OF_RANDOM; + + UNUSED(PAR(value)); + if (REF(only)) + fail (Error_Bad_Refines_Raw()); + + if (REF(seed)) { + REBDEC d = VAL_DECIMAL(val); + REBI64 i; + assert(sizeof(d) == sizeof(i)); + memcpy(&i, &d, sizeof(d)); + Set_Random(i); // use IEEE bits + return R_VOID; + } + d1 = Random_Dec(d1, REF(secure)); + goto setDec; } + + case SYM_COMPLEMENT: + Init_Integer(D_OUT, ~(REBINT)d1); + return R_OUT; + + default: + fail (Error_Illegal_Action(VAL_TYPE(val), action)); + } + } + + fail (Error_Illegal_Action(VAL_TYPE(val), action)); setDec: - if (!FINITE(d1)) Trap0(RE_OVERFLOW); -#ifdef not_required - if (type == REB_PERCENT) { - // Keep percent in smaller range (not to use e notation). - if (d1 != 0) { - num = (REBINT)floor(log10(fabs(d1))); - if (num > 12 || num < -6) Trap0(RE_OVERFLOW); // use gcvt - } - } -#endif - VAL_SET(D_RET, type); - VAL_DECIMAL(D_RET) = d1; - ///if (type == REB_MONEY) VAL_MONEY_DENOM(D_RET)[0] = 0; - return R_RET; + if (!FINITE(d1)) fail (Error_Overflow_Raw()); -is_false: - return R_FALSE; + VAL_RESET_HEADER(D_OUT, type); + VAL_DECIMAL(D_OUT) = d1; -is_true: - return R_TRUE; + return R_OUT; } diff --git a/src/core/t-event.c b/src/core/t-event.c index 570f13785d..559a0cb952 100644 --- a/src/core/t-event.c +++ b/src/core/t-event.c @@ -1,535 +1,485 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-event.c -** Summary: event datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** Events are kept compact in order to fit into normal 128 bit -** values cells. This provides high performance for high frequency -** events and also good memory efficiency using standard series. -** -***********************************************************************/ +// +// File: %t-event.c +// Summary: "event datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Events are kept compact in order to fit into normal 128 bit +// values cells. This provides high performance for high frequency +// events and also good memory efficiency using standard series. +// #include "sys-core.h" #include "reb-evtypes.h" -#include "reb-net.h" -/*********************************************************************** -** -*/ REBINT CT_Event(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Event: C +// +REBINT CT_Event(const RELVAL *a, const RELVAL *b, REBINT mode) { - REBINT diff = Cmp_Event(a, b); - if (mode >=0) return diff == 0; - return -1; + REBINT diff = Cmp_Event(a, b); + if (mode >=0) return diff == 0; + return -1; } -/*********************************************************************** -** -*/ REBINT Cmp_Event(REBVAL *t1, REBVAL *t2) -/* -** Given two events, compare them. -** -***********************************************************************/ +// +// Cmp_Event: C +// +// Given two events, compare them. +// +REBINT Cmp_Event(const RELVAL *t1, const RELVAL *t2) { - REBINT diff; + REBINT diff; - if ( - (diff = VAL_EVENT_MODEL(t1) - VAL_EVENT_MODEL(t2)) - || (diff = VAL_EVENT_TYPE(t1) - VAL_EVENT_TYPE(t2)) - || (diff = VAL_EVENT_XY(t1) - VAL_EVENT_XY(t2)) - ) return diff; + if ( + (diff = VAL_EVENT_MODEL(t1) - VAL_EVENT_MODEL(t2)) + || (diff = VAL_EVENT_TYPE(t1) - VAL_EVENT_TYPE(t2)) + || (diff = VAL_EVENT_XY(t1) - VAL_EVENT_XY(t2)) + ) return diff; - return 0; + return 0; } -/*********************************************************************** -** -*/ static REBFLG Set_Event_Var(REBVAL *value, REBVAL *word, REBVAL *val) -/* -***********************************************************************/ +// +// Set_Event_Var: C +// +static REBOOL Set_Event_Var(REBVAL *event, const REBVAL *word, const REBVAL *val) { - REBVAL *arg; - REBINT n; - REBCNT w; - - switch (VAL_WORD_CANON(word)) { - - case SYM_TYPE: - if (!IS_WORD(val) && !IS_LIT_WORD(val)) return FALSE; - arg = Get_System(SYS_VIEW, VIEW_EVENT_TYPES); - if (IS_BLOCK(arg)) { - w = VAL_WORD_CANON(val); - for (n = 0, arg = VAL_BLK(arg); NOT_END(arg); arg++, n++) { - if (IS_WORD(arg) && VAL_WORD_CANON(arg) == w) { - VAL_EVENT_TYPE(value) = n; - return TRUE; - } - } - Trap_Arg(val); - } - return FALSE; - - case SYM_PORT: - if (IS_PORT(val)) { - VAL_EVENT_MODEL(value) = EVM_PORT; - VAL_EVENT_SER(value) = VAL_PORT(val); - } - else if (IS_OBJECT(val)) { - VAL_EVENT_MODEL(value) = EVM_OBJECT; - VAL_EVENT_SER(value) = VAL_OBJ_FRAME(val); - } - else if (IS_NONE(val)) { - VAL_EVENT_MODEL(value) = EVM_GUI; - } else return FALSE; - break; - - case SYM_WINDOW: - case SYM_GOB: - if (IS_GOB(val)) { - VAL_EVENT_MODEL(value) = EVM_GUI; - VAL_EVENT_SER(value) = VAL_GOB(val); - break; - } - return FALSE; - - case SYM_OFFSET: - if (IS_PAIR(val)) { - SET_EVENT_XY(value, Float_Int16(VAL_PAIR_X(val)), Float_Int16(VAL_PAIR_Y(val))); - } - else return FALSE; - break; - - case SYM_KEY: - //VAL_EVENT_TYPE(value) != EVT_KEY && VAL_EVENT_TYPE(value) != EVT_KEY_UP) - VAL_EVENT_MODEL(value) = EVM_GUI; - if (IS_CHAR(val)) { - VAL_EVENT_DATA(value) = VAL_CHAR(val); - } - else if (IS_LIT_WORD(val) || IS_WORD(val)) { - arg = Get_System(SYS_VIEW, VIEW_EVENT_KEYS); - if (IS_BLOCK(arg)) { - arg = VAL_BLK_DATA(arg); - for (n = VAL_INDEX(arg); NOT_END(arg); n++, arg++) { - if (IS_WORD(arg) && VAL_WORD_CANON(arg) == VAL_WORD_CANON(val)) { - VAL_EVENT_DATA(value) = (n+1) << 16; - break; - } - } - if (IS_END(arg)) return FALSE; - break; - } - return FALSE; - } - else return FALSE; - break; - - case SYM_CODE: - if (IS_INTEGER(val)) { - VAL_EVENT_DATA(value) = VAL_INT32(val); - } - else return FALSE; - break; - - default: - return FALSE; - } - - return TRUE; + RELVAL *arg; + REBINT n; + + switch (VAL_WORD_SYM(word)) { + case SYM_TYPE: + if (!IS_WORD(val) && !IS_LIT_WORD(val)) return FALSE; + arg = Get_System(SYS_VIEW, VIEW_EVENT_TYPES); + if (IS_BLOCK(arg)) { + REBSTR *w = VAL_WORD_CANON(val); + for (n = 0, arg = VAL_ARRAY_HEAD(arg); NOT_END(arg); arg++, n++) { + if (IS_WORD(arg) && VAL_WORD_CANON(arg) == w) { + VAL_EVENT_TYPE(event) = n; + return TRUE; + } + } + fail (val); + } + return FALSE; + + case SYM_PORT: + if (IS_PORT(val)) { + VAL_EVENT_MODEL(event) = EVM_PORT; + VAL_EVENT_SER(event) = SER(CTX_VARLIST(VAL_CONTEXT(val))); + } + else if (IS_OBJECT(val)) { + VAL_EVENT_MODEL(event) = EVM_OBJECT; + VAL_EVENT_SER(event) = SER(CTX_VARLIST(VAL_CONTEXT(val))); + } + else if (IS_BLANK(val)) { + VAL_EVENT_MODEL(event) = EVM_GUI; + } else return FALSE; + break; + + case SYM_WINDOW: + case SYM_GOB: + if (IS_GOB(val)) { + VAL_EVENT_MODEL(event) = EVM_GUI; + VAL_EVENT_SER(event) = cast(REBSER*, VAL_GOB(val)); + break; + } + return FALSE; + + case SYM_OFFSET: + if (IS_PAIR(val)) { + SET_EVENT_XY( + event, + Float_Int16(VAL_PAIR_X(val)), + Float_Int16(VAL_PAIR_Y(val)) + ); + } + else return FALSE; + break; + + case SYM_KEY: + //VAL_EVENT_TYPE(event) != EVT_KEY && VAL_EVENT_TYPE(value) != EVT_KEY_UP) + VAL_EVENT_MODEL(event) = EVM_GUI; + if (IS_CHAR(val)) { + VAL_EVENT_DATA(event) = VAL_CHAR(val); + } + else if (IS_LIT_WORD(val) || IS_WORD(val)) { + arg = Get_System(SYS_VIEW, VIEW_EVENT_KEYS); + if (IS_BLOCK(arg)) { + arg = VAL_ARRAY_AT(arg); + for (n = VAL_INDEX(arg); NOT_END(arg); n++, arg++) { + if (IS_WORD(arg) && VAL_WORD_CANON(arg) == VAL_WORD_CANON(val)) { + VAL_EVENT_DATA(event) = (n+1) << 16; + break; + } + } + if (IS_END(arg)) return FALSE; + break; + } + return FALSE; + } + else return FALSE; + break; + + case SYM_CODE: + if (IS_INTEGER(val)) { + VAL_EVENT_DATA(event) = VAL_INT32(val); + } + else return FALSE; + break; + + case SYM_FLAGS: { + if (NOT(IS_BLOCK(val))) + return FALSE; + + VAL_EVENT_FLAGS(event) + &= ~((1 << EVF_DOUBLE) | (1 << EVF_CONTROL) | (1 << EVF_SHIFT)); + + RELVAL *item; + for (item = VAL_ARRAY_HEAD(val); NOT_END(item); ++item) { + if (NOT(IS_WORD(item))) + continue; + + switch (VAL_WORD_SYM(item)) { + case SYM_CONTROL: + SET_FLAG(VAL_EVENT_FLAGS(event), EVF_CONTROL); + break; + + case SYM_SHIFT: + SET_FLAG(VAL_EVENT_FLAGS(event), EVF_SHIFT); + break; + + case SYM_DOUBLE: + SET_FLAG(VAL_EVENT_FLAGS(event), EVF_DOUBLE); + break; + + default: + fail (Error_Invalid_Arg_Core(item, VAL_SPECIFIER(val))); + } + } + break; } + + default: + return FALSE; + } + + return TRUE; } -/*********************************************************************** -** -*/ static void Set_Event_Vars(REBVAL *evt, REBVAL *blk) -/* -***********************************************************************/ +// +// Set_Event_Vars: C +// +void Set_Event_Vars(REBVAL *evt, RELVAL *blk, REBSPC *specifier) { - REBVAL *var; - REBVAL *val; - - while (NOT_END(blk)) { - var = blk++; - val = blk++; - if (IS_END(val)) val = NONE_VALUE; - else val = Get_Simple_Value(val); - if (!Set_Event_Var(evt, var, val)) Trap2(RE_BAD_FIELD_SET, var, Of_Type(val)); - } + DECLARE_LOCAL (var); + DECLARE_LOCAL (val); + + while (NOT_END(blk)) { + Derelativize(var, blk, specifier); + ++blk; + + if (IS_END(blk)) + Init_Blank(val); + else + Get_Simple_Value_Into(val, blk, specifier); + + ++blk; + + if (!Set_Event_Var(evt, var, val)) + fail (Error_Bad_Field_Set_Raw(var, Type_Of(val))); + } } -/*********************************************************************** -** -*/ static REBFLG Get_Event_Var(REBVAL *value, REBCNT sym, REBVAL *val) -/* -***********************************************************************/ +// +// Get_Event_Var: C +// +static REBOOL Get_Event_Var(const REBVAL *value, REBSTR *name, REBVAL *val) { - REBVAL *arg; - REBREQ *req; - REBINT n; - REBSER *ser; - - switch (sym) { - - case SYM_TYPE: - if (VAL_EVENT_TYPE(value) == 0) goto is_none; - arg = Get_System(SYS_VIEW, VIEW_EVENT_TYPES); - if (IS_BLOCK(arg) && VAL_TAIL(arg) >= EVT_MAX) { - *val = *VAL_BLK_SKIP(arg, VAL_EVENT_TYPE(value)); - break; - } - return FALSE; - - case SYM_PORT: - // Most events are for the GUI: - if (IS_EVENT_MODEL(value, EVM_GUI)) { - *val = *Get_System(SYS_VIEW, VIEW_EVENT_PORT); - } - // Event holds a port: - else if (IS_EVENT_MODEL(value, EVM_PORT)) { - SET_PORT(val, VAL_EVENT_SER(value)); - } - // Event holds an object: - else if (IS_EVENT_MODEL(value, EVM_OBJECT)) { - SET_OBJECT(val, VAL_EVENT_SER(value)); - } - else if (IS_EVENT_MODEL(value, EVM_CALLBACK)) { - *val = *Get_System(SYS_PORTS, PORTS_CALLBACK); - } - else { - // assumes EVM_DEVICE - // Event holds the IO-Request, which has the PORT: - req = VAL_EVENT_REQ(value); - if (!req || !req->port) goto is_none; - SET_PORT(val, (REBSER*)(req->port)); - } - break; - - case SYM_WINDOW: - case SYM_GOB: - if (IS_EVENT_MODEL(value, EVM_GUI)) { - if (VAL_EVENT_SER(value)) { - SET_GOB(val, VAL_EVENT_SER(value)); - break; - } - } - return FALSE; - - case SYM_OFFSET: - if (VAL_EVENT_TYPE(value) == EVT_KEY || VAL_EVENT_TYPE(value) == EVT_KEY_UP) - goto is_none; - VAL_SET(val, REB_PAIR); - VAL_PAIR_X(val) = (REBD32)VAL_EVENT_X(value); - VAL_PAIR_Y(val) = (REBD32)VAL_EVENT_Y(value); - break; - - case SYM_KEY: - if (VAL_EVENT_TYPE(value) != EVT_KEY && VAL_EVENT_TYPE(value) != EVT_KEY_UP) - goto is_none; - n = VAL_EVENT_DATA(value); // key-words in top 16, chars in lower 16 - if (n & 0xffff0000) { - arg = Get_System(SYS_VIEW, VIEW_EVENT_KEYS); - n = (n >> 16) - 1; - if (IS_BLOCK(arg) && n < (REBINT)VAL_TAIL(arg)) { - *val = *VAL_BLK_SKIP(arg, n); - break; - } - return FALSE; - } - SET_CHAR(val, n); - break; - - case SYM_FLAGS: - if (VAL_EVENT_FLAGS(value) & (1<= EVT_MAX) { + Derelativize( + val, + VAL_ARRAY_AT_HEAD(arg, VAL_EVENT_TYPE(value)), + VAL_SPECIFIER(arg) + ); + break; + } + return FALSE; + + case SYM_PORT: + // Most events are for the GUI: + if (IS_EVENT_MODEL(value, EVM_GUI)) { + Move_Value(val, Get_System(SYS_VIEW, VIEW_EVENT_PORT)); + } + // Event holds a port: + else if (IS_EVENT_MODEL(value, EVM_PORT)) { + Init_Port(val, CTX(VAL_EVENT_SER(value))); + } + // Event holds an object: + else if (IS_EVENT_MODEL(value, EVM_OBJECT)) { + Init_Object(val, CTX(VAL_EVENT_SER(value))); + } + else if (IS_EVENT_MODEL(value, EVM_CALLBACK)) { + Move_Value(val, Get_System(SYS_PORTS, PORTS_CALLBACK)); + } + else { + // assumes EVM_DEVICE + // Event holds the IO-Request, which has the PORT: + req = VAL_EVENT_REQ(value); + if (!req || !req->port) goto is_blank; + Init_Port(val, CTX(req->port)); + } + break; + + case SYM_WINDOW: + case SYM_GOB: + if (IS_EVENT_MODEL(value, EVM_GUI)) { + if (VAL_EVENT_SER(value)) { + SET_GOB(val, cast(REBGOB*, VAL_EVENT_SER(value))); + break; + } + } + return FALSE; + + case SYM_OFFSET: + if (VAL_EVENT_TYPE(value) == EVT_KEY || VAL_EVENT_TYPE(value) == EVT_KEY_UP) + goto is_blank; + SET_PAIR(val, VAL_EVENT_X(value), VAL_EVENT_Y(value)); + break; + + case SYM_KEY: + if (VAL_EVENT_TYPE(value) != EVT_KEY && VAL_EVENT_TYPE(value) != EVT_KEY_UP) + goto is_blank; + n = VAL_EVENT_DATA(value); // key-words in top 16, chars in lower 16 + if (n & 0xffff0000) { + arg = Get_System(SYS_VIEW, VIEW_EVENT_KEYS); + n = (n >> 16) - 1; + if (IS_BLOCK(arg) && n < cast(REBINT, VAL_LEN_HEAD(arg))) { + Derelativize( + val, + VAL_ARRAY_AT_HEAD(arg, n), + VAL_SPECIFIER(arg) + ); + break; + } + return FALSE; + } + Init_Char(val, n); + break; + + case SYM_FLAGS: + if ( + VAL_EVENT_FLAGS(value) + & (1<select)) { - if (pvs->setval == 0 || NOT_END(pvs->path+1)) { - if (!Get_Event_Var(pvs->value, VAL_WORD_CANON(pvs->select), pvs->store)) return PE_BAD_SELECT; - return PE_USE; - } else { - if (!Set_Event_Var(pvs->value, pvs->select, pvs->setval)) return PE_BAD_SET; - return PE_OK; - } - } - return PE_BAD_SELECT; + if (IS_WORD(pvs->picker)) { + if (!pvs->opt_setval || NOT_END(pvs->item + 1)) { + if (!Get_Event_Var( + KNOWN(pvs->value), VAL_WORD_CANON(pvs->picker), pvs->store + )) { + fail (Error_Bad_Path_Set(pvs)); + } + + return PE_USE_STORE; + } + else { + if (!Set_Event_Var( + KNOWN(pvs->value), pvs->picker, pvs->opt_setval + )) { + fail (Error_Bad_Path_Set(pvs)); + } + + return PE_OK; + } + } + + fail (Error_Bad_Path_Select(pvs)); } -/*********************************************************************** -** -*/ REBTYPE(Event) -/* -***********************************************************************/ +// +// REBTYPE: C +// +REBTYPE(Event) { - REBVAL *value; - REBVAL *arg; - - value = D_ARG(1); - arg = D_ARG(2); - - if (action == A_MAKE) { - // Clone an existing event? - if (IS_EVENT(value)) return R_ARG1; - else if (IS_DATATYPE(value)) { - if (IS_EVENT(arg)) return R_ARG2; - //Trap_Make(REB_EVENT, value); - VAL_SET(D_RET, REB_EVENT); - CLEARS(&(D_RET->data.event)); - } - else -is_arg_error: - Trap_Types(RE_EXPECT_VAL, REB_EVENT, VAL_TYPE(arg)); - - // Initialize GOB from block: - if (IS_BLOCK(arg)) Set_Event_Vars(D_RET, VAL_BLK_DATA(arg)); - else goto is_arg_error; - } - else Trap_Action(REB_EVENT, action); - - return R_RET; + UNUSED(frame_); + + fail (Error_Illegal_Action(REB_EVENT, action)); } -#ifdef ndef -// case A_PATH: - if (IS_WORD(arg)) { - switch (VAL_WORD_CANON(arg)) { - case SYM_TYPE: index = EF_TYPE; break; - case SYM_PORT: index = EF_PORT; break; - case SYM_KEY: index = EF_KEY; break; - case SYM_OFFSET: index = EF_OFFSET; break; - case SYM_MODE: index = EF_MODE; break; - case SYM_TIME: index = EF_TIME; break; -//!!! return these as options flags, not refinements. -// case SYM_SHIFT: index = EF_SHIFT; break; -// case SYM_CONTROL: index = EF_CONTROL; break; -// case SYM_DOUBLE_CLICK: index = EF_DCLICK; break; - default: Trap1(RE_INVALID_PATH, arg); - } - goto pick_it; - } - else if (!IS_INTEGER(arg)) - Trap1(RE_INVALID_PATH, arg); - // fall thru - - - case A_PICK: - index = num = Get_Num_Arg(arg); - if (num > 0) index--; - if (num == 0 || index < 0 || index > EF_DCLICK) { - if (action == A_POKE) Trap_Range(arg); - goto is_none; - } -pick_it: - switch(index) { - case EF_TYPE: - if (VAL_EVENT_TYPE(value) == 0) goto is_none; - arg = Get_System(SYS_VIEW, VIEW_EVENT_TYPES); - if (IS_BLOCK(arg) && VAL_TAIL(arg) >= EVT_MAX) { - *D_RET = *VAL_BLK_SKIP(arg, VAL_EVENT_TYPE(value)); - return R_RET; - } - return R_NONE; - - case EF_PORT: - // Most events are for the GUI: - if (GET_FLAG(VAL_EVENT_FLAGS(value), EVF_NO_REQ)) - *D_RET = *Get_System(SYS_VIEW, VIEW_EVENT_PORT); - else { - req = VAL_EVENT_REQ(value); - if (!req || !req->port) goto is_none; - SET_PORT(D_RET, (REBSER*)(req->port)); - } - return R_RET; - - case EF_KEY: - if (VAL_EVENT_TYPE(value) != EVT_KEY) goto is_none; - if (VAL_EVENT_FLAGS(value)) { // !!!!!!!!!!!!! needs mask - VAL_SET(D_RET, REB_CHAR); - VAL_CHAR(D_RET) = VAL_EVENT_KEY(value) & 0xff; - } else - Init_Word(D_RET, VAL_EVENT_XY(value)); - return R_RET; - - case EF_OFFSET: - VAL_SET(D_RET, REB_PAIR); - VAL_PAIR_X(D_RET) = VAL_EVENT_X(value); - VAL_PAIR_Y(D_RET) = VAL_EVENT_Y(value); - return R_RET; - - case EF_TIME: - VAL_SET(D_RET, REB_INTEGER); -//!! VAL_INT64(D_RET) = VAL_EVENT_TIME(value); - return R_RET; - - case EF_SHIFT: - VAL_SET(D_RET, REB_LOGIC); - VAL_LOGIC(D_RET) = GET_FLAG(VAL_EVENT_FLAGS(value), EVF_SHIFT) != 0; - return R_RET; - - case EF_CONTROL: - VAL_SET(D_RET, REB_LOGIC); - VAL_LOGIC(D_RET) = GET_FLAG(VAL_EVENT_FLAGS(value), EVF_CONTROL) != 0; - return R_RET; - - case EF_DCLICK: - VAL_SET(D_RET, REB_LOGIC); - VAL_LOGIC(D_RET) = GET_FLAG(VAL_EVENT_FLAGS(value), EVF_DOUBLE) != 0; - return R_RET; - -/* case EF_FACE: - { - REBWIN *wp; - if (!IS_BLOCK(BLK_HEAD(Windows) + VAL_EVENT_WIN(value))) return R_RET None_Value; - wp = (REBWIN *)VAL_BLK(BLK_HEAD(Windows) + VAL_EVENT_WIN(value)); - *D_RET = wp->masterFace; - return R_RET; - } -*/ - } - break; - -// These are used to map symbols to event field cases: -enum rebol_event_fields { - EF_TYPE, - EF_KEY, - EF_OFFSET, - EF_TIME, - EF_SHIFT, // Keep these? !!! - EF_CONTROL, - EF_DCLICK, - EF_PORT, - EF_MODE, -}; - -#endif - - -/*********************************************************************** -** -*/ void Mold_Event(REBVAL *value, REB_MOLD *mold) -/* -***********************************************************************/ + +// +// Mold_Event: C +// +void Mold_Event(const REBVAL *value, REB_MOLD *mold) { - REBVAL val; - REBCNT field; - REBCNT fields[] = { - SYM_TYPE, SYM_PORT, SYM_GOB, SYM_OFFSET, SYM_KEY, - SYM_FLAGS, SYM_CODE, SYM_DATA, 0 - }; - - Pre_Mold(value, mold); - Append_Byte(mold->series, '['); - mold->indent++; - - for (field = 0; fields[field]; field++) { - Get_Event_Var(value, fields[field], &val); - if (!IS_NONE(&val)) { - New_Indented_Line(mold); - Append_UTF8(mold->series, Get_Sym_Name(fields[field]), -1); - Append_Bytes(mold->series, ": "); - if (IS_WORD(&val)) Append_Byte(mold->series, '\''); - Mold_Value(mold, &val, TRUE); - } - } - - mold->indent--; - New_Indented_Line(mold); - Append_Byte(mold->series, ']'); - - End_Mold(mold); + REBCNT field; + REBSYM fields[] = { + SYM_TYPE, SYM_PORT, SYM_GOB, SYM_OFFSET, SYM_KEY, + SYM_FLAGS, SYM_CODE, SYM_DATA, SYM_0 + }; + + Pre_Mold(value, mold); + Append_Codepoint_Raw(mold->series, '['); + mold->indent++; + + DECLARE_LOCAL (val); + + for (field = 0; fields[field] != SYM_0; field++) { + Get_Event_Var(value, Canon(fields[field]), val); + if (!IS_BLANK(val)) { + New_Indented_Line(mold); + + REBSTR *canon = Canon(fields[field]); + Append_UTF8_May_Fail( + mold->series, STR_HEAD(canon), STR_NUM_BYTES(canon) + ); + Append_Unencoded(mold->series, ": "); + if (IS_WORD(val)) + Append_Codepoint_Raw(mold->series, '\''); + Mold_Value(mold, val, TRUE); + } + } + + mold->indent--; + New_Indented_Line(mold); + Append_Codepoint_Raw(mold->series, ']'); + + End_Mold(mold); } diff --git a/src/core/t-function.c b/src/core/t-function.c old mode 100644 new mode 100755 index dffa3abd65..7ff6be106c --- a/src/core/t-function.c +++ b/src/core/t-function.c @@ -1,167 +1,399 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-function.c -** Summary: function related datatypes -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-function.c +// Summary: "function related datatypes" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -static REBOOL Same_Func(REBVAL *val, REBVAL *arg) +static REBOOL Same_Func(const RELVAL *val, const RELVAL *arg) { - if (VAL_TYPE(val) == VAL_TYPE(arg) && - VAL_FUNC_SPEC(val) == VAL_FUNC_SPEC(arg) && - VAL_FUNC_ARGS(val) == VAL_FUNC_ARGS(arg) && - VAL_FUNC_CODE(val) == VAL_FUNC_CODE(arg)) return TRUE; - return FALSE; + assert(IS_FUNCTION(val) && IS_FUNCTION(arg)); + + if (VAL_FUNC_PARAMLIST(val) == VAL_FUNC_PARAMLIST(arg)) { + assert(VAL_FUNC_DISPATCHER(val) == VAL_FUNC_DISPATCHER(arg)); + assert(VAL_FUNC_BODY(val) == VAL_FUNC_BODY(arg)); + + // All functions that have the same paramlist are not necessarily the + // "same function". For instance, every RETURN shares a common + // paramlist, but the binding is different in the REBVAL instances + // in order to know where to "exit from". + + return LOGICAL(VAL_BINDING(val) == VAL_BINDING(arg)); + } + + return FALSE; +} + + +// +// CT_Function: C +// +REBINT CT_Function(const RELVAL *a, const RELVAL *b, REBINT mode) +{ + if (mode >= 0) return Same_Func(a, b) ? 1 : 0; + return -1; +} + + +// +// MAKE_Function: C +// +// For REB_FUNCTION and "make spec", there is a function spec block and then +// a block of Rebol code implementing that function. In that case we expect +// that `def` should be: +// +// [[spec] [body]] +// +// With REB_COMMAND, the code is implemented via a C DLL, under a system of +// APIs that pre-date Rebol's open sourcing and hence Ren/C: +// +// [[spec] extension command-num] +// +// See notes in Make_Command() regarding that mechanism and meaning. +// +void MAKE_Function(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) +{ + assert(kind == REB_FUNCTION); + UNUSED(kind); + + if ( + !IS_BLOCK(arg) + || VAL_LEN_AT(arg) != 2 + || !IS_BLOCK(VAL_ARRAY_AT(arg)) + || !IS_BLOCK(VAL_ARRAY_AT(arg) + 1) + ){ + fail (Error_Bad_Make(REB_FUNCTION, arg)); + } + + DECLARE_LOCAL (spec); + Derelativize(spec, VAL_ARRAY_AT(arg), VAL_SPECIFIER(arg)); + + DECLARE_LOCAL (body); + Derelativize(body, VAL_ARRAY_AT(arg) + 1, VAL_SPECIFIER(arg)); + + // Spec-constructed functions do *not* have definitional returns + // added automatically. They are part of the generators. So the + // behavior comes--as with any other generator--from the projected + // code (though round-tripping it via text is not possible in + // general in any case due to loss of bindings.) + // + REBFUN *fun = Make_Interpreted_Function_May_Fail( + spec, body, MKF_ANY_VALUE + ); + + Move_Value(out, FUNC_VALUE(fun)); } -/*********************************************************************** -** -*/ REBINT CT_Function(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// TO_Function: C +// +// `to function! 'x` might be an interesting optimized 0-arity function +// generator, which made a function that returned that value every time you +// called it. Generalized alternative would be like `does [quote x]`, +// which would be slower to generate the function and slower to run. +// +void TO_Function(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - if (mode >= 0) return Same_Func(a, b); - return -1; + assert(kind == REB_FUNCTION); + UNUSED(kind); + + UNUSED(out); + + fail (arg); } -/*********************************************************************** -** -*/ REBSER *As_Typesets(REBSER *types) -/* -***********************************************************************/ +// +// REBTYPE: C +// +REBTYPE(Function) { - REBVAL *val; + REBVAL *value = D_ARG(1); + REBVAL *arg = D_ARGC > 1 ? D_ARG(2) : NULL; + + switch (action) { + case SYM_COPY: { + INCLUDE_PARAMS_OF_COPY; + + UNUSED(PAR(value)); + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(types)) { + UNUSED(ARG(kinds)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(deep)) { + // !!! always "deep", allow it? + } + + // !!! The R3-Alpha theory was that functions could modify "their + // bodies" while running, effectively accruing state that one might + // want to snapshot. See notes on Clonify_Function about why that + // idea is a bad one. + // + // Instead we create another handle which executes the same function + // code, yet has a distinct identity. This means it would not be + // HIJACK'd if the function that it was copied from was. + + REBFUN *underlying = FUNC_UNDERLYING(VAL_FUNC(value)); + + REBARR *proxy_paramlist = Copy_Array_Deep_Managed( + VAL_FUNC_PARAMLIST(value), + SPECIFIED // !!! Note: not actually "deep", just typesets + ); + ARR_HEAD(proxy_paramlist)->payload.function.paramlist + = proxy_paramlist; + SER(proxy_paramlist)->link.meta = VAL_FUNC_META(value); + SET_SER_FLAG(proxy_paramlist, ARRAY_FLAG_PARAMLIST); - types = Copy_Block(types, 1); - for (val = BLK_HEAD(types); NOT_END(val); val++) { - SET_TYPE(val, REB_TYPESET); - } - return types; + // If the function had code, then that code will be bound relative + // to the original paramlist that's getting hijacked. So when the + // proxy is called, we want the frame pushed to be relative to + // whatever underlied the function...even if it was foundational + // so `underlying = VAL_FUNC(value)` + + REBFUN *proxy = Make_Function( + proxy_paramlist, + FUNC_DISPATCHER(VAL_FUNC(value)), + underlying, + NULL // not changing the specialization + ); + + // A new body_holder was created inside Make_Function(). + // + *FUNC_BODY(proxy) = *VAL_FUNC_BODY(value); + + Move_Value(D_OUT, FUNC_VALUE(proxy)); + D_OUT->extra.binding = VAL_BINDING(value); + return R_OUT; } + + case SYM_REFLECT: { + REBSYM sym = VAL_WORD_SYM(arg); + + switch (sym) { + case SYM_ADDR: + if (IS_FUNCTION_RIN(value)) { + // + // The CFUNC is fabricated by the FFI if it's a callback, or + // just the wrapped DLL function if it's an ordinary routine + // + Init_Integer( + D_OUT, cast(REBUPT, RIN_CFUNC(VAL_FUNC_ROUTINE(value))) + ); + return R_OUT; + } + break; + + case SYM_WORDS: + Init_Block(D_OUT, List_Func_Words(value, FALSE)); // no locals + return R_OUT; + + case SYM_BODY: + // + // A Hijacker may or may not need to splice itself in with a + // dispatcher. So if it does, bypass it to get to the real + // function implementation. + // + while (IS_FUNCTION_HIJACKER(value)) + value = KNOWN(VAL_FUNC_BODY(value)); + + if (IS_FUNCTION_INTERPRETED(value)) { + // + // BODY-OF is an example of user-facing code that needs to be + // complicit in the "lie" about the effective bodies of the + // functions made by the optimized generators FUNC and PROC. + // + // Note that since the function body contains relative arrays + // and words, there needs to be some frame to specify them + // before a specific REBVAL can be made. Usually that's the + // frame of the running instance of the function...but because + // we're reflecting data out of it, we have to either unbind + // them or make up a frame. Making up a frame that acts like + // it's off the stack and the variables are dead is easiest + // for now...but long term perhaps unbinding them is better, + // though this is "more informative". See #2221. + + REBOOL is_fake; + REBARR *body = Get_Maybe_Fake_Func_Body(&is_fake, value); + Init_Block( + D_OUT, + Copy_Array_Deep_Managed( + body, + AS_SPECIFIER( + Make_Expired_Frame_Ctx_Managed(VAL_FUNC(value)) + ) + ) + ); + + if (is_fake) Free_Array(body); // was shallow copy + return R_OUT; + } + + // For other function types, leak internal guts and hope for + // the best, temporarily. + // + if (IS_BLOCK(VAL_FUNC_BODY(value))) { + Init_Any_Array( + D_OUT, + REB_BLOCK, + Copy_Array_Deep_Managed( + VAL_ARRAY(VAL_FUNC_BODY(value)), SPECIFIED + ) + ); + } + else { + Init_Blank(D_OUT); + } + return R_OUT; + + case SYM_TYPES: { + REBARR *copy = Make_Array(VAL_FUNC_NUM_PARAMS(value)); + REBVAL *param; + REBVAL *typeset; + + // The typesets have a symbol in them for the parameters, and + // ordinary typesets aren't supposed to have it--that's a + // special feature for object keys and paramlists! So clear + // that symbol out before giving it back. + // + param = VAL_FUNC_PARAMS_HEAD(value); + typeset = SINK(ARR_HEAD(copy)); + for (; NOT_END(param); param++, typeset++) { + assert(VAL_PARAM_SPELLING(param) != NULL); + Move_Value(typeset, param); + INIT_TYPESET_NAME(typeset, NULL); + } + TERM_ARRAY_LEN(copy, VAL_FUNC_NUM_PARAMS(value)); + assert(IS_END(typeset)); + + Init_Block(D_OUT, copy); + return R_OUT; + } + + default: + fail (Error_Cannot_Reflect(VAL_TYPE(value), arg)); + } + break; } + + default: + break; + } + + fail (Error_Illegal_Action(VAL_TYPE(value), action)); } -/*********************************************************************** -** -*/ REBFLG MT_Function(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ +// +// func-class-of: native [ +// +// {Internal-use-only for implementing NATIVE?, ACTION?, CALLBACK?, etc.} +// +// func [function!] +// ] +// +REBNATIVE(func_class_of) +// +// !!! The concept of the VAL_FUNC_CLASS was killed, because functions get +// their classification by way of their dispatch pointers. Generally +// speaking, functions should be a "black box" to user code, and it's only +// at the "meta" level that a function would choose to expose whether it +// is something like a specialization or an adaptation...but that would be +// purely documentary, and could lie. { - return Make_Function(type, out, data); + INCLUDE_PARAMS_OF_FUNC_CLASS_OF; + + REBVAL *value = ARG(func); + REBCNT n; + + if (IS_FUNCTION_INTERPRETED(value)) + n = 2; + else if (IS_FUNCTION_ACTION(value)) + n = 3; + else if (IS_FUNCTION_RIN(value)) { + if (NOT(RIN_IS_CALLBACK(VAL_FUNC_ROUTINE(value)))) + n = 5; + else + n = 6; + } + else if (IS_FUNCTION_SPECIALIZER(value)) + n = 7; + else { + // !!! A shaky guess, but assume native if none of the above. + // (COMMAND! was once 4) + n = 1; + } + + Init_Integer(D_OUT, n); + return R_OUT; } -/*********************************************************************** -** -*/ REBTYPE(Function) -/* -***********************************************************************/ +// +// PD_Function: C +// +REBINT PD_Function(REBPVS *pvs) { - REBVAL *value = D_ARG(1); - REBVAL *arg = D_ARG(2); - REBCNT type = VAL_TYPE(value); - REBCNT n; - - switch (action) { - - case A_MAKE: - case A_TO: - // make function! [[args] [body]] - if (IS_DATATYPE(value)) { - n = VAL_DATATYPE(value); - if (Make_Function(n, value, arg)) break; - Trap_Make(n, arg); - } - - // make :func [] - // make :func [[args]] - // make :func [* [body]] - if (ANY_FUNC(value)) { - if (!IS_BLOCK(arg)) goto bad_arg; - if (!ANY_FUNC(value)) goto bad_arg; - if (!Copy_Function(value, arg)) goto bad_arg; - break; - } - if (!IS_NONE(arg)) goto bad_arg; - // fall thru... - case A_COPY: - Copy_Function(value, 0); - break; - - case A_REFLECT: - n = What_Reflector(arg); // zero on error - switch (n) { - case OF_WORDS: - //if (type == REB_CLOSURE) - Set_Block(value, List_Func_Words(value)); - //else - // Set_Block(value, List_Func_Words(value)); - break; - case OF_BODY: -of_type: - switch (type) { - case REB_FUNCTION: - case REB_CLOSURE: - Set_Block(value, Clone_Block(VAL_FUNC_BODY(value))); - Unbind_Block(VAL_BLK(value), TRUE); - break; - case REB_NATIVE: - case REB_COMMAND: - case REB_ACTION: - SET_NONE(value); - break; - case REB_OP: - type = VAL_GET_EXT(value); // internal datatype - goto of_type; - } - break; - case OF_SPEC: - Set_Block(value, Clone_Block(VAL_FUNC_SPEC(value))); - Unbind_Block(VAL_BLK(value), TRUE); - break; - case OF_TYPES: - Set_Block(value, As_Typesets(VAL_FUNC_ARGS(value))); - break; - case OF_TITLE: - arg = BLK_HEAD(VAL_FUNC_SPEC(value)); - for (; NOT_END(arg) && !IS_STRING(arg) && !IS_WORD(arg); arg++); - if (!IS_STRING(arg)) return R_NONE; - Set_String(value, Copy_Series(VAL_SERIES(arg))); - break; - default: - bad_arg: - Trap_Reflect(type, arg); - } - break; - - default: Trap_Action(type, action); - } - - DS_RET_VALUE(value); - return R_RET; + if (IS_BLANK(pvs->picker)) { + // + // Leave the function value as-is, and continue processing. This + // enables things like `append/(all [foo 'dup])/only`... + // + return PE_OK; + } + + // The first evaluation of a GROUP! and GET-WORD! are processed by the + // general path mechanic before reaching this dispatch. So if it's not + // a word or one of those that evaluated to a word raise an error. + // + if (!IS_WORD(pvs->picker)) + fail (Error_Bad_Refine_Raw(pvs->picker)); + + // We could generate a "refined" function variant at each step: + // + // `append/dup/only` => `ad: :append/dup | ado: :ad/only | ado` + // + // Generating these intermediates would be costly. They'd have updated + // paramlists and tax the garbage collector. So path dispatch is + // understood to push the canonized word to the data stack in the + // function case. + // + DS_PUSH(pvs->picker); + + // Go ahead and canonize the word symbol so we don't have to do it each + // time in order to get a case-insensitive compare. (Note that canons can + // be GC'd, but will not be so long as an instance is on the stack.) + // + Canonize_Any_Word(DS_TOP); + + // Leave the function value as is in pvs->value + // + return PE_OK; } diff --git a/src/core/t-gob.c b/src/core/t-gob.c index 9df6cc1c4b..a7becaba20 100644 --- a/src/core/t-gob.c +++ b/src/core/t-gob.c @@ -1,912 +1,1202 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-gob.c -** Summary: graphical object datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-gob.c +// Summary: "graphical object datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -void Trap_Temp(void) {Trap0(501);} //!!! temp trap function - -const REBCNT Gob_Flag_Words[] = { - SYM_RESIZE, GOBF_RESIZE, - SYM_NO_TITLE, GOBF_NO_TITLE, - SYM_NO_BORDER, GOBF_NO_BORDER, - SYM_DROPABLE, GOBF_DROPABLE, - SYM_TRANSPARENT, GOBF_TRANSPARENT, - SYM_POPUP, GOBF_POPUP, - SYM_MODAL, GOBF_MODAL, - SYM_ON_TOP, GOBF_ON_TOP, - SYM_HIDDEN, GOBF_HIDDEN, - 0, 0 +#include "mem-pools.h" // low-level memory pool access + +const struct { + REBSYM sym; + REBUPT flags; +} Gob_Flag_Words[] = { + {SYM_RESIZE, GOBF_RESIZE}, + {SYM_NO_TITLE, GOBF_NO_TITLE}, + {SYM_NO_BORDER, GOBF_NO_BORDER}, + {SYM_DROPABLE, GOBF_DROPABLE}, + {SYM_TRANSPARENT, GOBF_TRANSPARENT}, + {SYM_POPUP, GOBF_POPUP}, + {SYM_MODAL, GOBF_MODAL}, + {SYM_ON_TOP, GOBF_ON_TOP}, + {SYM_HIDDEN, GOBF_HIDDEN}, + {SYM_ACTIVE, GOBF_ACTIVE}, + {SYM_MINIMIZE, GOBF_MINIMIZE}, + {SYM_MAXIMIZE, GOBF_MAXIMIZE}, + {SYM_RESTORE, GOBF_RESTORE}, + {SYM_FULLSCREEN, GOBF_FULLSCREEN}, + {SYM_0, 0} }; -/*********************************************************************** -** -*/ REBINT CT_Gob(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Gob: C +// +REBINT CT_Gob(const RELVAL *a, const RELVAL *b, REBINT mode) { - if (mode >= 0) - return VAL_GOB(a) == VAL_GOB(b) && VAL_GOB_INDEX(a) == VAL_GOB_INDEX(b); - return -1; + if (mode >= 0) + return VAL_GOB(a) == VAL_GOB(b) && VAL_GOB_INDEX(a) == VAL_GOB_INDEX(b); + return -1; } -/*********************************************************************** -** -*/ REBGOB *Make_Gob(void) -/* -** Allocate a new GOB. -** -***********************************************************************/ +// +// Make_Gob: C +// +// Allocate a new GOB. +// +REBGOB *Make_Gob(void) { - REBGOB *gob = Make_Node(GOB_POOL); - CLEAR(gob, sizeof(REBGOB)); - GOB_W(gob) = 100; - GOB_H(gob) = 100; - USE_GOB(gob); - if ((GC_Ballast -= Mem_Pools[GOB_POOL].wide) <= 0) SET_SIGNAL(SIG_RECYCLE); - return gob; + REBGOB *gob = cast(REBGOB*, Make_Node(GOB_POOL)); + CLEAR(gob, sizeof(REBGOB)); + GOB_W(gob) = 100; + GOB_H(gob) = 100; + GOB_ALPHA(gob) = 255; + gob->header.bits = NODE_FLAG_NODE; + if ((GC_Ballast -= Mem_Pools[GOB_POOL].wide) <= 0) SET_SIGNAL(SIG_RECYCLE); + return gob; } -/*********************************************************************** -** -*/ REBINT Cmp_Gob(REBVAL *g1, REBVAL *g2) -/* -***********************************************************************/ +// +// Cmp_Gob: C +// +REBINT Cmp_Gob(const RELVAL *g1, const RELVAL *g2) { - REBINT n; + REBINT n; - n = VAL_GOB(g2) - VAL_GOB(g1); - if (n != 0) return n; - n = VAL_GOB_INDEX(g2) - VAL_GOB_INDEX(g1); - if (n != 0) return n; - return 0; + n = VAL_GOB(g2) - VAL_GOB(g1); + if (n != 0) return n; + n = VAL_GOB_INDEX(g2) - VAL_GOB_INDEX(g1); + if (n != 0) return n; + return 0; } -/*********************************************************************** -** -*/ static REBFLG Set_Pair(REBXYF *pair, REBVAL *val) -/* -***********************************************************************/ +// +// Set_Pair: C +// +static REBOOL Set_Pair(REBXYF *pair, const REBVAL *val) { - if (IS_PAIR(val)) { - pair->x = VAL_PAIR_X(val); - pair->y = VAL_PAIR_Y(val); - } - else if (IS_INTEGER(val)) { - pair->x = pair->y = (REBD32)VAL_INT64(val); - } - else if (IS_DECIMAL(val)) { - pair->x = pair->y = (REBD32)VAL_DECIMAL(val); - } - else - return FALSE; - - return TRUE; + if (IS_PAIR(val)) { + pair->x = VAL_PAIR_X(val); + pair->y = VAL_PAIR_Y(val); + } + else if (IS_INTEGER(val)) { + pair->x = pair->y = (REBD32)VAL_INT64(val); + } + else if (IS_DECIMAL(val)) { + pair->x = pair->y = (REBD32)VAL_DECIMAL(val); + } + else + return FALSE; + + return TRUE; } -/*********************************************************************** -** -*/ static REBCNT Find_Gob(REBGOB *gob, REBGOB *target) -/* -** Find a target GOB within the pane of another gob. -** Return the index, or a -1 if not found. -** -***********************************************************************/ +// +// Find_Gob: C +// +// Find a target GOB within the pane of another gob. +// Return the index, or a -1 if not found. +// +static REBCNT Find_Gob(REBGOB *gob, REBGOB *target) { - REBCNT len; - REBCNT n; - REBGOB **ptr; - - if (GOB_PANE(gob)) { - len = GOB_TAIL(gob); - ptr = GOB_HEAD(gob); - for (n = 0; n < len; n++, ptr++) - if (*ptr == target) return n; - } - return NOT_FOUND; + REBCNT len; + REBCNT n; + REBGOB **ptr; + + if (GOB_PANE(gob)) { + len = GOB_LEN(gob); + ptr = GOB_HEAD(gob); + for (n = 0; n < len; n++, ptr++) + if (*ptr == target) return n; + } + return NOT_FOUND; } -/*********************************************************************** -** -*/ static void Detach_Gob(REBGOB *gob) -/* -** Remove a gob value from its parent. -** Done normally in advance of inserting gobs into new parent. -** -***********************************************************************/ +// +// Detach_Gob: C +// +// Remove a gob value from its parent. +// Done normally in advance of inserting gobs into new parent. +// +static void Detach_Gob(REBGOB *gob) { - REBGOB *par; - REBINT i; - - par = GOB_PARENT(gob); - if (par && GOB_PANE(par) && (i = Find_Gob(par, gob)) != NOT_FOUND) { - Remove_Series(GOB_PANE(par), i, 1); - } - GOB_PARENT(gob) = 0; + REBGOB *par; + REBCNT i; + + par = GOB_PARENT(gob); + if (par && GOB_PANE(par) && (i = Find_Gob(par, gob)) != NOT_FOUND) { + Remove_Series(GOB_PANE(par), i, 1); + } + GOB_PARENT(gob) = 0; } -/*********************************************************************** -** -*/ static void Insert_Gobs(REBGOB *gob, REBVAL *arg, REBCNT index, REBCNT len, REBFLG change) -/* -** Insert one or more gobs into a pane at the given index. -** If index >= tail, an append occurs. Each gob has its parent -** gob field set. (Call Detach_Gobs() before inserting.) -** -***********************************************************************/ +// +// Insert_Gobs: C +// +// Insert one or more gobs into a pane at the given index. +// If index >= tail, an append occurs. Each gob has its parent +// gob field set. (Call Detach_Gobs() before inserting.) +// +static void Insert_Gobs( + REBGOB *gob, + const RELVAL *arg, + REBCNT index, + REBCNT len, + REBOOL change +) { + REBGOB **ptr; + REBCNT n, count; + const RELVAL *val; + const RELVAL *sarg; + REBINT i; + + // Verify they are gobs: + sarg = arg; + for (n = count = 0; n < len; n++, val++) { + val = arg++; + if (IS_WORD(val)) { + // + // For the moment, assume this GOB-or-WORD! containing block + // only contains non-relative values. + // + val = Get_Opt_Var_May_Fail(val, SPECIFIED); + } + if (IS_GOB(val)) { + count++; + if (GOB_PARENT(VAL_GOB(val))) { + // Check if inserting into same parent: + i = -1; + if (GOB_PARENT(VAL_GOB(val)) == gob) { + i = Find_Gob(gob, VAL_GOB(val)); + if (i > 0 && i == (REBINT)index-1) { // a no-op + SET_GOB_STATE(VAL_GOB(val), GOBS_NEW); + return; + } + } + Detach_Gob(VAL_GOB(val)); + if (i >= 0 && (REBINT)index > i) index--; + } + } + else + fail (Error_Invalid_Arg_Core(val, SPECIFIED)); + } + arg = sarg; + + // Create or expand the pane series: + if (!GOB_PANE(gob)) { + GOB_PANE(gob) = Make_Series(count + 1, sizeof(REBGOB*)); + SET_GOB_LEN(gob, count); + index = 0; + + // !!! A GOB_PANE could theoretically be MKS_UNTRACKED and manually + // memory managed, if that made sense. Does it? + + MANAGE_SERIES(GOB_PANE(gob)); + } + else { + if (change) { + if (index + count > GOB_LEN(gob)) { + EXPAND_SERIES_TAIL(GOB_PANE(gob), index + count - GOB_LEN(gob)); + } + } else { + Expand_Series(GOB_PANE(gob), index, count); + if (index >= GOB_LEN(gob)) index = GOB_LEN(gob)-1; + } + } + + ptr = GOB_AT(gob, index); + for (n = 0; n < len; n++) { + val = arg++; + if (IS_WORD(val)) { + // + // Again, assume no relative values + // + val = Get_Opt_Var_May_Fail(val, SPECIFIED); + } + if (IS_GOB(val)) { + if (GOB_PARENT(VAL_GOB(val)) != NULL) + fail ("GOB! not expected to have parent"); + *ptr++ = VAL_GOB(val); + GOB_PARENT(VAL_GOB(val)) = gob; + SET_GOB_STATE(VAL_GOB(val), GOBS_NEW); + } + } +} + + +// +// Remove_Gobs: C +// +// Remove one or more gobs from a pane at the given index. +// +static void Remove_Gobs(REBGOB *gob, REBCNT index, REBCNT len) +{ + REBGOB **ptr; + REBCNT n; + + ptr = GOB_AT(gob, index); + for (n = 0; n < len; n++, ptr++) { + GOB_PARENT(*ptr) = 0; + } + + Remove_Series(GOB_PANE(gob), index, len); +} + + +// +// Pane_To_Array: C +// +// Convert pane list of gob pointers to a Rebol array of GOB! REBVALs. +// +static REBARR *Pane_To_Array(REBGOB *gob, REBCNT index, REBINT len) { - REBGOB **ptr; - REBCNT n, count; - REBVAL *val, *sarg; - REBINT i; - - // Verify they are gobs: - sarg = arg; - for (n = count = 0; n < len; n++, val++) { - val = arg++; - if (IS_WORD(val)) val = Get_Var(val); - if (IS_GOB(val)) { - count++; - if (GOB_PARENT(VAL_GOB(val))) { - // Check if inserting into same parent: - i = -1; - if (GOB_PARENT(VAL_GOB(val)) == gob) { - i = Find_Gob(gob, VAL_GOB(val)); - if (i > 0 && i == (REBINT)index-1) { // a no-op - SET_GOB_STATE(VAL_GOB(val), GOBS_NEW); - return; - } - } - Detach_Gob(VAL_GOB(val)); - if ((REBINT)index > i) index--; - } - } - } - arg = sarg; - - // Create or expand the pane series: - if (!GOB_PANE(gob)) { - GOB_PANE(gob) = Make_Series(count, sizeof(REBGOB*), 0); - LABEL_SERIES(GOB_PANE(gob), "gob pane"); - GOB_TAIL(gob) = count; - index = 0; - } - else { - if (change) { - if (index + count > GOB_TAIL(gob)) { - EXPAND_SERIES_TAIL(GOB_PANE(gob), index + count - GOB_TAIL(gob)); - } - } else { - Expand_Series(GOB_PANE(gob), index, count); - if (index >= GOB_TAIL(gob)) index = GOB_TAIL(gob)-1; - } - } - - ptr = GOB_SKIP(gob, index); - for (n = 0; n < len; n++) { - val = arg++; - if (IS_WORD(val)) val = Get_Var(val); - if (IS_GOB(val)) { - if GOB_PARENT(VAL_GOB(val)) Trap_Temp(); - *ptr++ = VAL_GOB(val); - GOB_PARENT(VAL_GOB(val)) = gob; - SET_GOB_STATE(VAL_GOB(val), GOBS_NEW); - } - } + REBARR *array; + REBGOB **gp; + REBVAL *val; + + if (len == -1 || (len + index) > GOB_LEN(gob)) len = GOB_LEN(gob) - index; + if (len < 0) len = 0; + + array = Make_Array(len); + TERM_ARRAY_LEN(array, len); + val = SINK(ARR_HEAD(array)); + gp = GOB_HEAD(gob); + for (; len > 0; len--, val++, gp++) { + SET_GOB(val, *gp); + } + assert(IS_END(val)); + + return array; } -/*********************************************************************** -** -*/ static void Remove_Gobs(REBGOB *gob, REBCNT index, REBCNT len) -/* -** Remove one or more gobs from a pane at the given index. -** -***********************************************************************/ +// +// Gob_Flags_To_Array: C +// +static REBARR *Gob_Flags_To_Array(REBGOB *gob) { - REBGOB **ptr; - REBCNT n; + REBARR *array = Make_Array(3); - ptr = GOB_SKIP(gob, index); - for (n = 0; n < len; n++, ptr++) { - GOB_PARENT(*ptr) = 0; - } + REBINT i; + for (i = 0; Gob_Flag_Words[i].sym != SYM_0; ++i) { + if (GET_GOB_FLAG(gob, Gob_Flag_Words[i].flags)) { + REBVAL *val = Alloc_Tail_Array(array); + Init_Word(val, Canon(Gob_Flag_Words[i].sym)); + } + } - Remove_Series(GOB_PANE(gob), index, len); + return array; +} + + +// +// Set_Gob_Flag: C +// +static void Set_Gob_Flag(REBGOB *gob, REBSTR *name) +{ + REBSYM sym = STR_SYMBOL(name); + if (sym == SYM_0) return; // !!! fail? + + REBINT i; + for (i = 0; Gob_Flag_Words[i].sym != SYM_0; ++i) { + if (SAME_SYM_NONZERO(sym, Gob_Flag_Words[i].sym)) { + REBCNT flag = Gob_Flag_Words[i].flags; + SET_GOB_FLAG(gob, flag); + //handle mutual exclusive states + switch (flag) { + case GOBF_RESTORE: + CLR_GOB_FLAG(gob, GOBF_MINIMIZE); + CLR_GOB_FLAG(gob, GOBF_MAXIMIZE); + CLR_GOB_FLAG(gob, GOBF_FULLSCREEN); + break; + case GOBF_MINIMIZE: + CLR_GOB_FLAG(gob, GOBF_MAXIMIZE); + CLR_GOB_FLAG(gob, GOBF_RESTORE); + CLR_GOB_FLAG(gob, GOBF_FULLSCREEN); + break; + case GOBF_MAXIMIZE: + CLR_GOB_FLAG(gob, GOBF_MINIMIZE); + CLR_GOB_FLAG(gob, GOBF_RESTORE); + CLR_GOB_FLAG(gob, GOBF_FULLSCREEN); + break; + case GOBF_FULLSCREEN: + SET_GOB_FLAG(gob, GOBF_NO_TITLE); + SET_GOB_FLAG(gob, GOBF_NO_BORDER); + CLR_GOB_FLAG(gob, GOBF_MINIMIZE); + CLR_GOB_FLAG(gob, GOBF_RESTORE); + CLR_GOB_FLAG(gob, GOBF_MAXIMIZE); + } + break; + } + } +} + + +// +// Set_GOB_Var: C +// +static REBOOL Set_GOB_Var(REBGOB *gob, const REBVAL *word, const REBVAL *val) +{ + switch (VAL_WORD_SYM(word)) { + case SYM_OFFSET: + return Set_Pair(&(gob->offset), val); + + case SYM_SIZE: + return Set_Pair(&gob->size, val); + + case SYM_IMAGE: + CLR_GOB_OPAQUE(gob); + if (IS_IMAGE(val)) { + SET_GOB_TYPE(gob, GOBT_IMAGE); + GOB_W(gob) = (REBD32)VAL_IMAGE_WIDE(val); + GOB_H(gob) = (REBD32)VAL_IMAGE_HIGH(val); + GOB_CONTENT(gob) = VAL_SERIES(val); +// if (!VAL_IMAGE_TRANSP(val)) SET_GOB_OPAQUE(gob); + } + else if (IS_BLANK(val)) SET_GOB_TYPE(gob, GOBT_NONE); + else return FALSE; + break; + + case SYM_DRAW: + CLR_GOB_OPAQUE(gob); + if (IS_BLOCK(val)) { + SET_GOB_TYPE(gob, GOBT_DRAW); + GOB_CONTENT(gob) = VAL_SERIES(val); + } + else if (IS_BLANK(val)) SET_GOB_TYPE(gob, GOBT_NONE); + else return FALSE; + break; + + case SYM_TEXT: + CLR_GOB_OPAQUE(gob); + if (IS_BLOCK(val)) { + SET_GOB_TYPE(gob, GOBT_TEXT); + GOB_CONTENT(gob) = VAL_SERIES(val); + } + else if (IS_STRING(val)) { + SET_GOB_TYPE(gob, GOBT_STRING); + GOB_CONTENT(gob) = VAL_SERIES(val); + } + else if (IS_BLANK(val)) SET_GOB_TYPE(gob, GOBT_NONE); + else return FALSE; + break; + + case SYM_EFFECT: + CLR_GOB_OPAQUE(gob); + if (IS_BLOCK(val)) { + SET_GOB_TYPE(gob, GOBT_EFFECT); + GOB_CONTENT(gob) = VAL_SERIES(val); + } + else if (IS_BLANK(val)) SET_GOB_TYPE(gob, GOBT_NONE); + else return FALSE; + break; + + case SYM_COLOR: + CLR_GOB_OPAQUE(gob); + if (IS_TUPLE(val)) { + SET_GOB_TYPE(gob, GOBT_COLOR); + Set_Pixel_Tuple((REBYTE*)&GOB_CONTENT(gob), val); + if (VAL_TUPLE_LEN(val) < 4 || VAL_TUPLE(val)[3] == 0) + SET_GOB_OPAQUE(gob); + } + else if (IS_BLANK(val)) SET_GOB_TYPE(gob, GOBT_NONE); + break; + + case SYM_PANE: + if (GOB_PANE(gob)) Clear_Series(GOB_PANE(gob)); + if (IS_BLOCK(val)) + Insert_Gobs( + gob, VAL_ARRAY_AT(val), 0, VAL_ARRAY_LEN_AT(val), FALSE + ); + else if (IS_GOB(val)) + Insert_Gobs(gob, val, 0, 1, FALSE); + else if (IS_BLANK(val)) + gob->pane = 0; + else + return FALSE; + break; + + case SYM_ALPHA: + GOB_ALPHA(gob) = Clip_Int(Int32(val), 0, 255); + break; + + case SYM_DATA: + SET_GOB_DTYPE(gob, GOBD_NONE); + if (IS_OBJECT(val)) { + SET_GOB_DTYPE(gob, GOBD_OBJECT); + SET_GOB_DATA(gob, SER(CTX_VARLIST(VAL_CONTEXT(val)))); + } + else if (IS_BLOCK(val)) { + SET_GOB_DTYPE(gob, GOBD_BLOCK); + SET_GOB_DATA(gob, VAL_SERIES(val)); + } + else if (IS_STRING(val)) { + SET_GOB_DTYPE(gob, GOBD_STRING); + SET_GOB_DATA(gob, VAL_SERIES(val)); + } + else if (IS_BINARY(val)) { + SET_GOB_DTYPE(gob, GOBD_BINARY); + SET_GOB_DATA(gob, VAL_SERIES(val)); + } + else if (IS_INTEGER(val)) { + SET_GOB_DTYPE(gob, GOBD_INTEGER); + SET_GOB_DATA(gob, cast(REBSER*, cast(REBIPT, VAL_INT64(val)))); + } + else if (IS_BLANK(val)) + SET_GOB_TYPE(gob, GOBT_NONE); + else return FALSE; + break; + + case SYM_FLAGS: + if (IS_WORD(val)) Set_Gob_Flag(gob, VAL_WORD_SPELLING(val)); + else if (IS_BLOCK(val)) { + //clear only flags defined by words + REBINT i; + for (i = 0; Gob_Flag_Words[i].sym != 0; ++i) + CLR_GOB_FLAG(gob, Gob_Flag_Words[i].flags); + + RELVAL* item; + for (item = VAL_ARRAY_HEAD(val); NOT_END(item); item++) + if (IS_WORD(item)) Set_Gob_Flag(gob, VAL_WORD_CANON(item)); + } + break; + + case SYM_OWNER: + if (IS_GOB(val)) + GOB_TMP_OWNER(gob) = VAL_GOB(val); + else + return FALSE; + break; + + default: + return FALSE; + } + return TRUE; } -/*********************************************************************** -** -*/ static REBSER *Pane_To_Block(REBGOB *gob, REBCNT index, REBINT len) -/* -** Convert pane list of gob pointers to a block of GOB!s. -** -***********************************************************************/ +// +// Get_GOB_Var: C +// +static REBOOL Get_GOB_Var(REBGOB *gob, const REBVAL *word, REBVAL *val) { - REBSER *ser; - REBGOB **gp; - REBVAL *val; - - if (len == -1 || (len + index) > GOB_TAIL(gob)) len = GOB_TAIL(gob) - index; - if (len < 0) len = 0; - - ser = Make_Block(len); - ser->tail = len; - val = BLK_HEAD(ser); - gp = GOB_HEAD(gob); - for (; len > 0; len--, val++, gp++) { - SET_GOB(val, *gp); - } - SET_END(val); - - return ser; + switch (VAL_WORD_SYM(word)) { + + case SYM_OFFSET: + SET_PAIR(val, GOB_X(gob), GOB_Y(gob)); + break; + + case SYM_SIZE: + SET_PAIR(val, GOB_W(gob), GOB_H(gob)); + break; + + case SYM_IMAGE: + if (GOB_TYPE(gob) == GOBT_IMAGE) { + // image + } + else goto is_blank; + break; + + case SYM_DRAW: + if (GOB_TYPE(gob) == GOBT_DRAW) { + // !!! comment said "compiler optimizes" the init "calls below" (?) + Init_Block(val, ARR(GOB_CONTENT(gob))); + } + else goto is_blank; + break; + + case SYM_TEXT: + if (GOB_TYPE(gob) == GOBT_TEXT) { + Init_Block(val, ARR(GOB_CONTENT(gob))); + } + else if (GOB_TYPE(gob) == GOBT_STRING) { + Init_String(val, GOB_CONTENT(gob)); + } + else goto is_blank; + break; + + case SYM_EFFECT: + if (GOB_TYPE(gob) == GOBT_EFFECT) { + Init_Block(val, ARR(GOB_CONTENT(gob))); + } + else goto is_blank; + break; + + case SYM_COLOR: + if (GOB_TYPE(gob) == GOBT_COLOR) { + Set_Tuple_Pixel((REBYTE*)&GOB_CONTENT(gob), val); + } + else goto is_blank; + break; + + case SYM_ALPHA: + Init_Integer(val, GOB_ALPHA(gob)); + break; + + case SYM_PANE: + if (GOB_PANE(gob)) + Init_Block(val, Pane_To_Array(gob, 0, -1)); + else + Init_Block(val, Make_Array(0)); + break; + + case SYM_PARENT: + if (GOB_PARENT(gob)) { + SET_GOB(val, GOB_PARENT(gob)); + } + else +is_blank: + Init_Blank(val); + break; + + case SYM_DATA: + if (GOB_DTYPE(gob) == GOBD_OBJECT) { + Init_Object(val, CTX(GOB_DATA(gob))); + } + else if (GOB_DTYPE(gob) == GOBD_BLOCK) { + Init_Block(val, ARR(GOB_DATA(gob))); + } + else if (GOB_DTYPE(gob) == GOBD_STRING) { + Init_String(val, GOB_DATA(gob)); + } + else if (GOB_DTYPE(gob) == GOBD_BINARY) { + Init_Binary(val, GOB_DATA(gob)); + } + else if (GOB_DTYPE(gob) == GOBD_INTEGER) { + Init_Integer(val, (REBIPT)GOB_DATA(gob)); + } + else goto is_blank; + break; + + case SYM_FLAGS: + Init_Block(val, Gob_Flags_To_Array(gob)); + break; + + default: + return FALSE; + } + return TRUE; } -/*********************************************************************** -** -*/ static REBSER *Flags_To_Block(REBGOB *gob) -/* -***********************************************************************/ +// +// Set_GOB_Vars: C +// +static void Set_GOB_Vars(REBGOB *gob, const RELVAL *blk, REBSPC *specifier) { - REBSER *ser; - REBVAL *val; - REBINT i; + DECLARE_LOCAL (var); + DECLARE_LOCAL (val); - ser = Make_Block(3); + while (NOT_END(blk)) { + assert(!IS_VOID(blk)); - for (i = 0; Gob_Flag_Words[i]; i += 2) { - if (GET_GOB_FLAG(gob, Gob_Flag_Words[i+1])) { - val = Append_Value(ser); - Init_Word(val, Gob_Flag_Words[i]); - } - } + Derelativize(var, blk, specifier); + ++blk; - return ser; + if (!IS_SET_WORD(var)) + fail (Error_Unexpected_Type(REB_SET_WORD, VAL_TYPE(var))); + + if (IS_END(blk)) + fail (Error_Need_Value_Raw(var)); + + Derelativize(val, blk, specifier); + ++blk; + + if (IS_SET_WORD(val)) + fail (Error_Need_Value_Raw(var)); + + if (!Set_GOB_Var(gob, var, val)) + fail (Error_Bad_Field_Set_Raw(var, Type_Of(val))); + } +} + + +// +// Gob_To_Array: C +// +// Used by MOLD to create a block. +// +REBARR *Gob_To_Array(REBGOB *gob) +{ + REBARR *array = Make_Array(10); + REBVAL *val; + REBSYM words[] = {SYM_OFFSET, SYM_SIZE, SYM_ALPHA, SYM_0}; + REBVAL *vals[6]; + REBINT n = 0; + REBVAL *val1; + + for (n = 0; words[n] != SYM_0; ++n) { + val = Alloc_Tail_Array(array); + Init_Set_Word(val, Canon(words[n])); + vals[n] = Alloc_Tail_Array(array); + Init_Blank(vals[n]); + } + + SET_PAIR(vals[0], GOB_X(gob), GOB_Y(gob)); + SET_PAIR(vals[1], GOB_W(gob), GOB_H(gob)); + Init_Integer(vals[2], GOB_ALPHA(gob)); + + if (!GOB_TYPE(gob)) return array; + + if (GOB_CONTENT(gob)) { + val1 = Alloc_Tail_Array(array); + val = Alloc_Tail_Array(array); + + REBSYM sym; + switch (GOB_TYPE(gob)) { + case GOBT_COLOR: + sym = SYM_COLOR; + break; + case GOBT_IMAGE: + sym = SYM_IMAGE; + break; + case GOBT_STRING: + case GOBT_TEXT: + sym = SYM_TEXT; + break; + case GOBT_DRAW: + sym = SYM_DRAW; + break; + case GOBT_EFFECT: + sym = SYM_EFFECT; + break; + default: + fail ("Unknown GOB! type"); + } + Init_Set_Word(val1, Canon(sym)); + Get_GOB_Var(gob, val1, val); + } + + return array; } -/*********************************************************************** -** -*/ static void Set_Gob_Flag(REBGOB *gob, REBVAL *word) -/* -***********************************************************************/ +// +// Return_Gob_Pair: C +// +static void Return_Gob_Pair(REBVAL *out, REBGOB *gob, REBD32 x, REBD32 y) { - REBINT i; - - for (i = 0; Gob_Flag_Words[i]; i += 2) { - if (VAL_WORD_CANON(word) == Gob_Flag_Words[i]) { - SET_GOB_FLAG(gob, Gob_Flag_Words[i+1]); - break; - } - } + REBARR *blk = Make_Array(2); + Init_Block(out, blk); + + SET_GOB(Alloc_Tail_Array(blk), gob); + + REBVAL *val = Alloc_Tail_Array(blk); + VAL_RESET_HEADER(val, REB_PAIR); + VAL_PAIR_X(val) = x; + VAL_PAIR_Y(val) = y; } -/*********************************************************************** -** -*/ static REBFLG Set_GOB_Var(REBGOB *gob, REBVAL *word, REBVAL *val) -/* -***********************************************************************/ +// +// Map_Gob_Inner: C +// +// Map a higher level gob coordinate to a lower level. +// Returns GOB and sets new offset pair. +// +static REBGOB *Map_Gob_Inner(REBGOB *gob, REBXYF *offset) { - switch (VAL_WORD_CANON(word)) { - case SYM_OFFSET: - return Set_Pair(&(gob->offset), val); - - case SYM_SIZE: - return Set_Pair(&gob->size, val); - - case SYM_IMAGE: - CLR_GOB_OPAQUE(gob); - if (IS_IMAGE(val)) { - SET_GOB_TYPE(gob, GOBT_IMAGE); - GOB_W(gob) = (REBD32)VAL_IMAGE_WIDE(val); - GOB_H(gob) = (REBD32)VAL_IMAGE_HIGH(val); - GOB_CONTENT(gob) = VAL_SERIES(val); -// if (!VAL_IMAGE_TRANSP(val)) SET_GOB_OPAQUE(gob); - } - else if (IS_NONE(val)) SET_GOB_TYPE(gob, GOBT_NONE); - else return FALSE; - break; - - case SYM_DRAW: - CLR_GOB_OPAQUE(gob); - if (IS_BLOCK(val)) { - SET_GOB_TYPE(gob, GOBT_DRAW); - GOB_CONTENT(gob) = VAL_SERIES(val); - } - else if (IS_NONE(val)) SET_GOB_TYPE(gob, GOBT_NONE); - else return FALSE; - break; - - case SYM_TEXT: - CLR_GOB_OPAQUE(gob); - if (IS_BLOCK(val)) { - SET_GOB_TYPE(gob, GOBT_TEXT); - GOB_CONTENT(gob) = VAL_SERIES(val); - } - else if (IS_STRING(val)) { - SET_GOB_TYPE(gob, GOBT_STRING); - GOB_CONTENT(gob) = VAL_SERIES(val); - } - else if (IS_NONE(val)) SET_GOB_TYPE(gob, GOBT_NONE); - else return FALSE; - break; - - case SYM_EFFECT: - CLR_GOB_OPAQUE(gob); - if (IS_BLOCK(val)) { - SET_GOB_TYPE(gob, GOBT_EFFECT); - GOB_CONTENT(gob) = VAL_SERIES(val); - } - else if (IS_NONE(val)) SET_GOB_TYPE(gob, GOBT_NONE); - else return FALSE; - break; - - case SYM_COLOR: - CLR_GOB_OPAQUE(gob); - if (IS_TUPLE(val)) { - SET_GOB_TYPE(gob, GOBT_COLOR); - Set_Pixel_Tuple((REBYTE*)&GOB_CONTENT(gob), val); - if (VAL_TUPLE_LEN(val) < 4 || VAL_TUPLE(val)[3] == 0) - SET_GOB_OPAQUE(gob); - } - else if (IS_NONE(val)) SET_GOB_TYPE(gob, GOBT_NONE); - break; - - case SYM_PANE: - if (GOB_PANE(gob)) Clear_Series(GOB_PANE(gob)); - if (IS_BLOCK(val)) - Insert_Gobs(gob, VAL_BLK_DATA(val), 0, VAL_BLK_LEN(val), 0); - else if (IS_GOB(val)) - Insert_Gobs(gob, val, 0, 1, 0); - else if (IS_NONE(val)) - gob->pane = 0; - else - return FALSE; - break; - - case SYM_ALPHA: - GOB_ALPHA(gob) = Clip_Int(Int32(val), 0, 255); - break; - - case SYM_DATA: - SET_GOB_DTYPE(gob, GOBD_NONE); - if (IS_OBJECT(val)) { - SET_GOB_DTYPE(gob, GOBD_OBJECT); - SET_GOB_DATA(gob, VAL_OBJ_FRAME(val)); - } - else if (IS_BLOCK(val)) { - SET_GOB_DTYPE(gob, GOBD_BLOCK); - SET_GOB_DATA(gob, VAL_SERIES(val)); - } - else if (IS_STRING(val)) { - SET_GOB_DTYPE(gob, GOBD_STRING); - SET_GOB_DATA(gob, VAL_SERIES(val)); - } - else if (IS_BINARY(val)) { - SET_GOB_DTYPE(gob, GOBD_BINARY); - SET_GOB_DATA(gob, VAL_SERIES(val)); - } - else if (IS_INTEGER(val)) { - SET_GOB_DTYPE(gob, GOBD_INTEGER); - SET_GOB_DATA(gob, (void*)VAL_INT32(val)); - } - else if (IS_NONE(val)) - SET_GOB_TYPE(gob, GOBT_NONE); - else return FALSE; - break; - - case SYM_FLAGS: - if (IS_WORD(val)) Set_Gob_Flag(gob, val); - else if (IS_BLOCK(val)) { - gob->flags = 0; - for (val = VAL_BLK(val); NOT_END(val); val++) { - if (IS_WORD(val)) Set_Gob_Flag(gob, val); - } - } - break; - - case SYM_OWNER: - if (IS_GOB(val)) - GOB_TMP_OWNER(gob) = VAL_GOB(val); - else - return FALSE; - break; - - default: - return FALSE; - } - return TRUE; + REBD32 xo = offset->x; + REBD32 yo = offset->y; + REBINT n; + REBINT len; + REBGOB **gop; + REBD32 x = 0; + REBD32 y = 0; + REBINT max_depth = 1000; // avoid infinite loops + + while (GOB_PANE(gob) && (max_depth-- > 0)) { + len = GOB_LEN(gob); + gop = GOB_HEAD(gob) + len - 1; + for (n = 0; n < len; n++, gop--) { + if ( + (xo >= x + GOB_X(*gop)) && + (xo < x + GOB_X(*gop) + GOB_W(*gop)) && + (yo >= y + GOB_Y(*gop)) && + (yo < y + GOB_Y(*gop) + GOB_H(*gop)) + ){ + x += GOB_X(*gop); + y += GOB_Y(*gop); + gob = *gop; + break; + } + } + if (n >= len) break; // not found + } + + offset->x -= x; + offset->y -= y; + + return gob; } -/*********************************************************************** -** -*/ static REBFLG Get_GOB_Var(REBGOB *gob, REBVAL *word, REBVAL *val) -/* -***********************************************************************/ +// +// map-event: native [ +// +// {Returns event with inner-most graphical object and coordinate.} +// +// event [event!] +// ] +// +REBNATIVE(map_event) { - switch (VAL_WORD_CANON(word)) { - - case SYM_OFFSET: - SET_PAIR(val, GOB_X(gob), GOB_Y(gob)); - break; - - case SYM_SIZE: - SET_PAIR(val, GOB_W(gob), GOB_H(gob)); - break; - - case SYM_IMAGE: - if (GOB_TYPE(gob) == GOBT_IMAGE) { - // image - } - else goto is_none; - break; - - case SYM_DRAW: - if (GOB_TYPE(gob) == GOBT_DRAW) { - Set_Block(val, GOB_CONTENT(gob)); // Note: compiler optimizes SET_BLOCKs below - } - else goto is_none; - break; - - case SYM_TEXT: - if (GOB_TYPE(gob) == GOBT_TEXT) { - Set_Block(val, GOB_CONTENT(gob)); - } - else if (GOB_TYPE(gob) == GOBT_STRING) { - Set_String(val, GOB_CONTENT(gob)); - } - else goto is_none; - break; - - case SYM_EFFECT: - if (GOB_TYPE(gob) == GOBT_EFFECT) { - Set_Block(val, GOB_CONTENT(gob)); - } - else goto is_none; - break; - - case SYM_COLOR: - if (GOB_TYPE(gob) == GOBT_COLOR) { - Set_Tuple_Pixel((REBYTE*)&GOB_CONTENT(gob), val); - } - else goto is_none; - break; - - case SYM_ALPHA: - SET_INTEGER(val, GOB_ALPHA(gob)); - break; - - case SYM_PANE: - if (GOB_PANE(gob)) - Set_Block(val, Pane_To_Block(gob, 0, -1)); - else - Set_Block(val, Make_Block(0)); - break; - - case SYM_PARENT: - if (GOB_PARENT(gob)) { - SET_GOB(val, GOB_PARENT(gob)); - } - else -is_none: - SET_NONE(val); - break; - - case SYM_DATA: - if (GOB_DTYPE(gob) == GOBD_OBJECT) { - SET_OBJECT(val, GOB_DATA(gob)); - } - else if (GOB_DTYPE(gob) == GOBD_BLOCK) { - Set_Block(val, GOB_DATA(gob)); - } - else if (GOB_DTYPE(gob) == GOBD_STRING) { - Set_String(val, GOB_DATA(gob)); - } - else if (GOB_DTYPE(gob) == GOBD_BINARY) { - SET_BINARY(val, GOB_DATA(gob)); - } - else if (GOB_DTYPE(gob) == GOBD_INTEGER) { - SET_INTEGER(val, (REBINT)GOB_DATA(gob)); - } - else goto is_none; - break; - - case SYM_FLAGS: - Set_Block(val, Flags_To_Block(gob)); - break; - - default: - return FALSE; - } - return TRUE; + INCLUDE_PARAMS_OF_MAP_EVENT; + + REBVAL *val = ARG(event); + REBGOB *gob = cast(REBGOB*, VAL_EVENT_SER(val)); + REBXYF xy; + + if (gob && GET_FLAG(VAL_EVENT_FLAGS(val), EVF_HAS_XY)) { + xy.x = (REBD32)VAL_EVENT_X(val); + xy.y = (REBD32)VAL_EVENT_Y(val); + VAL_EVENT_SER(val) = cast(REBSER*, Map_Gob_Inner(gob, &xy)); + SET_EVENT_XY(val, ROUND_TO_INT(xy.x), ROUND_TO_INT(xy.y)); + } + + Move_Value(D_OUT, ARG(event)); + return R_OUT; } -/*********************************************************************** -** -*/ static void Set_GOB_Vars(REBGOB *gob, REBVAL *blk) -/* -***********************************************************************/ +// +// map-gob-offset: native [ +// +// {Translate gob and offset to deepest gob and offset in it, return as block} +// +// gob [gob!] +// "Starting object" +// xy [pair!] +// "Staring offset" +// /reverse +// "Translate from deeper gob to top gob." +// ] +// +REBNATIVE(map_gob_offset) { - REBVAL *var; - REBVAL *val; - - while (NOT_END(blk)) { - var = blk++; - val = blk++; - if (!IS_SET_WORD(var)) Trap2(RE_EXPECT_VAL, Get_Type(REB_SET_WORD), Of_Type(var)); - if (IS_END(val) || IS_UNSET(val) || IS_SET_WORD(val)) - Trap1(RE_NEED_VALUE, var); - val = Get_Simple_Value(val); - if (!Set_GOB_Var(gob, var, val)) Trap2(RE_BAD_FIELD_SET, var, Of_Type(val)); - } + INCLUDE_PARAMS_OF_MAP_GOB_OFFSET; + + REBGOB *gob = VAL_GOB(ARG(gob)); + REBD32 xo = VAL_PAIR_X(ARG(xy)); + REBD32 yo = VAL_PAIR_Y(ARG(xy)); + + if (REF(reverse)) { + REBINT max_depth = 1000; // avoid infinite loops + while ( + GOB_PARENT(gob) + && (max_depth-- > 0) + && !GET_GOB_FLAG(gob, GOBF_WINDOW) + ){ + xo += GOB_X(gob); + yo += GOB_Y(gob); + gob = GOB_PARENT(gob); + } + } + else { + REBXYF xy; + xy.x = VAL_PAIR_X(ARG(xy)); + xy.y = VAL_PAIR_Y(ARG(xy)); + gob = Map_Gob_Inner(gob, &xy); + xo = xy.x; + yo = xy.y; + } + + Return_Gob_Pair(D_OUT, gob, xo, yo); + + return R_OUT; +} + + +// +// Extend_Gob_Core: C +// +// !!! R3-Alpha's MAKE has been unified with construction syntax, which has +// no "parent" slot (just type and value). To try and incrementally keep +// code working, this parameterized function is called by both REBNATIVE(make) +// REBNATIVE(construct). +// +void Extend_Gob_Core(REBGOB *gob, const REBVAL *arg) { + // + // !!! See notes about derivation in REBNATIVE(make). When deriving, it + // appeared to copy the variables while nulling out the pane and parent + // fields. Then it applied the variables. It also *said* in the case of + // passing in another gob "merge gob provided as argument", but didn't + // seem to do any merging--it just overwrote. So the block and pair cases + // were the only ones "merging". + + if (IS_BLOCK(arg)) { + Set_GOB_Vars(gob, VAL_ARRAY_AT(arg), VAL_SPECIFIER(arg)); + } + else if (IS_PAIR(arg)) { + gob->size.x = VAL_PAIR_X(arg); + gob->size.y = VAL_PAIR_Y(arg); + } + else + fail (Error_Bad_Make(REB_GOB, arg)); } -/*********************************************************************** -** -*/ REBSER *Gob_To_Block(REBGOB *gob) -/* -** Used by MOLD to create a block. -** -***********************************************************************/ +// +// MAKE_Gob: C +// +void MAKE_Gob(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBSER *ser = Make_Block(10); - REBVAL *val; - REBINT words[6] = {SYM_OFFSET, SYM_SIZE, SYM_ALPHA, 0}; - REBVAL *vals[6]; - REBINT n = 0; - REBVAL *val1; - REBCNT sym; - - for (n = 0; words[n]; n++) { - val = Append_Value(ser); - Init_Word(val, words[n]); - VAL_SET(val, REB_SET_WORD); - vals[n] = Append_Value(ser); - } - - SET_PAIR(vals[0], GOB_X(gob), GOB_Y(gob)); - SET_PAIR(vals[1], GOB_W(gob), GOB_H(gob)); - SET_INTEGER(vals[2], GOB_ALPHA(gob)); - - if (!GOB_TYPE(gob)) return ser; - - if (GOB_CONTENT(gob)) { - val1 = Append_Value(ser); - val = Append_Value(ser); - switch (GOB_TYPE(gob)) { - case GOBT_COLOR: - sym = SYM_COLOR; - break; - case GOBT_IMAGE: - sym = SYM_IMAGE; - break; - case GOBT_STRING: - case GOBT_TEXT: - sym = SYM_TEXT; - break; - case GOBT_DRAW: - sym = SYM_DRAW; - break; - case GOBT_EFFECT: - sym = SYM_EFFECT; - break; - } - Init_Word(val1, sym); - VAL_SET(val1, REB_SET_WORD); - Get_GOB_Var(gob, val1, val); - } - - return ser; + assert(kind == REB_GOB); + UNUSED(kind); + + REBGOB *gob = Make_Gob(); + + if (IS_GOB(arg)) { + // + // !!! See notes in Extend_Gob_Core; previously a parent was allowed + // here, but completely overwritten with a GOB! argument. + // + *gob = *VAL_GOB(arg); + gob->pane = NULL; + gob->parent = NULL; + } + else + Extend_Gob_Core(gob, arg); + + SET_GOB(out, gob); } -/*********************************************************************** -** -*/ REBFLG MT_Gob(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ +// +// TO_Gob: C +// +void TO_Gob(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBGOB *ngob; + assert(kind == REB_GOB); + UNUSED(kind); - if (IS_BLOCK(data)) { - ngob = Make_Gob(); - Set_GOB_Vars(ngob, VAL_BLK_DATA(data)); - SET_GOB(out, ngob); - return TRUE; - } + UNUSED(out); - return FALSE; + fail (arg); } -/*********************************************************************** -** -*/ REBINT PD_Gob(REBPVS *pvs) -/* -***********************************************************************/ +// +// PD_Gob: C +// +REBINT PD_Gob(REBPVS *pvs) { - REBGOB *gob = VAL_GOB(pvs->value); - REBCNT index; - REBCNT tail; - - if (IS_WORD(pvs->select)) { - if (pvs->setval == 0 || NOT_END(pvs->path+1)) { - if (!Get_GOB_Var(gob, pvs->select, pvs->store)) return PE_BAD_SELECT; - // Check for SIZE/X: types of cases: - if (pvs->setval && IS_PAIR(pvs->store)) { - REBVAL *sel = pvs->select; - pvs->value = pvs->store; - Next_Path(pvs); // sets value in pvs->store - Set_GOB_Var(gob, sel, pvs->store); // write it back to gob - } - return PE_USE; - } else { - if (!Set_GOB_Var(gob, pvs->select, pvs->setval)) return PE_BAD_SET; - return PE_OK; - } - } - if (IS_INTEGER(pvs->select)) { - if (!GOB_PANE(gob)) return PE_NONE; - tail = GOB_PANE(gob) ? GOB_TAIL(gob) : 0; - index = VAL_GOB_INDEX(pvs->value); - index += Int32(pvs->select) - 1; - if (index >= tail) return PE_NONE; - gob = *GOB_SKIP(gob, index); - index = 0; - VAL_SET(pvs->store, REB_GOB); - VAL_GOB(pvs->store) = gob; - VAL_GOB_INDEX(pvs->store) = 0; - return PE_USE; - } - return PE_BAD_SELECT; + REBGOB *gob = VAL_GOB(pvs->value); + REBCNT index; + REBCNT tail; + + if (IS_WORD(pvs->picker)) { + if (!pvs->opt_setval || NOT_END(pvs->item + 1)) { + if (!Get_GOB_Var(gob, pvs->picker, pvs->store)) + fail (Error_Bad_Path_Select(pvs)); + + // !!! Comment here said: "Check for SIZE/X: types of cases". + // See %c-path.c for an explanation of why this code steps + // outside the ordinary path processing to "look ahead" in the + // case of wanting to make it possible to use a generated PAIR! + // as a way of "writing back" into the values in the GOB! that + // were used to generate the PAIR!. There should be some + // overall solution to facilitating this kind of need. + // + if (pvs->opt_setval && IS_PAIR(pvs->store)) { + // + // !!! Adding to the reasons that this is dodgy, the picker + // can be pointing to a temporary memory cell, and when + // Next_Path_Throws runs arbitrary code it could be GC'd too. + // Have to copy -and- protect. + // + DECLARE_LOCAL (orig_picker); + Move_Value(orig_picker, pvs->picker); + PUSH_GUARD_VALUE(orig_picker); + + pvs->value = pvs->store; + pvs->value_specifier = SPECIFIED; + + if (Next_Path_Throws(pvs)) // sets value in pvs->store + fail (Error_No_Catch_For_Throw(pvs->store)); // Review + + // write it back to gob + // + Set_GOB_Var(gob, orig_picker, pvs->store); + DROP_GUARD_VALUE(orig_picker); + } + return PE_USE_STORE; + } + else { + if (!Set_GOB_Var(gob, pvs->picker, pvs->opt_setval)) + fail (Error_Bad_Path_Set(pvs)); + return PE_OK; + } + } + + if (IS_INTEGER(pvs->picker)) { + if (!GOB_PANE(gob)) return PE_NONE; + + tail = GOB_PANE(gob) ? GOB_LEN(gob) : 0; + index = VAL_GOB_INDEX(pvs->value); + index += Int32(pvs->picker) - 1; + + if (index >= tail) return PE_NONE; + + gob = *GOB_AT(gob, index); + VAL_RESET_HEADER(pvs->store, REB_GOB); + VAL_GOB(pvs->store) = gob; + VAL_GOB_INDEX(pvs->store) = 0; + return PE_USE_STORE; + } + + fail (Error_Bad_Path_Select(pvs)); } -/*********************************************************************** -** -*/ REBTYPE(Gob) -/* -***********************************************************************/ +// +// REBTYPE: C +// +REBTYPE(Gob) { - REBVAL *val; - REBVAL *arg; - REBGOB *gob; - REBGOB *ngob; - REBCNT index; - REBCNT tail; - REBCNT len; - - arg = D_ARG(2); - val = D_RET; - *val = *D_ARG(1); - gob = 0; - - if (IS_GOB(val)) { - gob = VAL_GOB(val); - index = VAL_GOB_INDEX(val); - tail = GOB_PANE(gob) ? GOB_TAIL(gob) : 0; - } - - // unary actions - switch(action) { - - case A_MAKE: - ngob = Make_Gob(); - val = D_ARG(1); - - // Clone an existing GOB: - if (IS_GOB(val)) { // local variable "gob" is valid - *ngob = *gob; // Copy all values - ngob->pane = 0; - ngob->parent = 0; - } - else if (!IS_DATATYPE(val)) goto is_arg_error; - - // Initialize GOB from block: - if (IS_BLOCK(arg)) { - Set_GOB_Vars(ngob, VAL_BLK_DATA(arg)); - } - // Merge GOB provided as argument: - else if (IS_GOB(arg)) { - *ngob = *VAL_GOB(arg); - ngob->pane = 0; - ngob->parent = 0; - } - else if (IS_PAIR(arg)) { - ngob->size.x = VAL_PAIR_X(arg); - ngob->size.y = VAL_PAIR_Y(arg); - } - else - Trap_Make(REB_GOB, arg); - // Allow NONE as argument: -// else if (!IS_NONE(arg)) -// goto is_arg_error; - SET_GOB(DS_RETURN, ngob); - break; - - case A_PICK: - if (!IS_NUMBER(arg) && !IS_NONE(arg)) Trap_Arg(arg); - if (!GOB_PANE(gob)) goto is_none; - index += Get_Num_Arg(arg) - 1; - if (index >= tail) goto is_none; - gob = *GOB_SKIP(gob, index); - index = 0; - goto set_index; - - case A_POKE: - index += Get_Num_Arg(arg) - 1; - arg = D_ARG(3); - case A_CHANGE: - if (!IS_GOB(arg)) goto is_arg_error; - if (!GOB_PANE(gob) || index >= tail) Trap0(RE_PAST_END); - if (action == A_CHANGE && (DS_REF(AN_PART) || DS_REF(AN_ONLY) || DS_REF(AN_DUP))) Trap0(RE_NOT_DONE); - Insert_Gobs(gob, arg, index, 1, 0); - //ngob = *GOB_SKIP(gob, index); - //GOB_PARENT(ngob) = 0; - //*GOB_SKIP(gob, index) = VAL_GOB(arg); - if (action == A_POKE) { - *DS_RETURN = *arg; - return R_RET; - } - index++; - goto set_index; - - case A_APPEND: - index = tail; - case A_INSERT: - if (DS_REF(AN_PART) || DS_REF(AN_ONLY) || DS_REF(AN_DUP)) Trap0(RE_NOT_DONE); - if (IS_GOB(arg)) len = 1; - else if (IS_BLOCK(arg)) { - len = VAL_BLK_LEN(arg); - arg = VAL_BLK_DATA(arg); - } - else goto is_arg_error;; - Insert_Gobs(gob, arg, index, len, 0); - break; - - case A_CLEAR: - if (tail > index) Remove_Gobs(gob, index, tail - index); - break; - - case A_REMOVE: - // /PART length - len = D_REF(2) ? Get_Num_Arg(D_ARG(3)) : 1; - if (index + len > tail) len = tail - index; - if (index < tail && len != 0) Remove_Gobs(gob, index, len); - break; - - case A_TAKE: - len = D_REF(2) ? Get_Num_Arg(D_ARG(3)) : 1; - if (index + len > tail) len = tail - index; - if (index < 0 || index >= tail) goto is_none; - if (!D_REF(2)) { // just one value - VAL_SET(val, REB_GOB); - VAL_GOB(val) = *GOB_SKIP(gob, index); - VAL_GOB_INDEX(val) = 0; - Remove_Gobs(gob, index, 1); - return R_RET; - } else { - Set_Block(D_RET, Pane_To_Block(gob, index, len)); - Remove_Gobs(gob, index, len); - } - return R_RET; - - case A_NEXT: - if (index < tail) index++; - goto set_index; - - case A_BACK: - if (index > 0) index--; - goto set_index; - - case A_AT: - index--; - case A_SKIP: - index += VAL_INT32(arg); - goto set_index; - - case A_HEAD: - index = 0; - goto set_index; - - case A_TAIL: - index = tail; - goto set_index; - - case A_HEADQ: - if (index == 0) goto is_true; - goto is_false; - - case A_TAILQ: - if (index >= tail) goto is_true; - goto is_false; - - case A_PASTQ: - if (index > tail) goto is_true; - goto is_false; - - case A_INDEXQ: - SET_INTEGER(val, index+1); - break; - - case A_LENGTHQ: - index = (tail > index) ? tail - index : 0; - SET_INTEGER(val, index); - break; - - case A_FIND: - if (IS_GOB(arg)) { - index = Find_Gob(gob, VAL_GOB(arg)); - if (index == NOT_FOUND) goto is_none; - goto set_index; - } - goto is_none; - - case A_REVERSE: - for (index = 0; index < tail/2; index++) { - ngob = *GOB_SKIP(gob, tail-index-1); - *GOB_SKIP(gob, tail-index-1) = *GOB_SKIP(gob, index); - *GOB_SKIP(gob, index) = ngob; - } - return R_ARG1; - - default: - Trap_Action(REB_GOB, action); - } - return R_RET; + REBVAL *val = D_ARG(1); + REBVAL *arg = D_ARGC > 1 ? D_ARG(2) : NULL; + REBGOB *gob = NULL; + REBGOB *ngob; + REBCNT index; + REBCNT tail; + REBCNT len; + + Move_Value(D_OUT, val); + + assert(IS_GOB(val)); + gob = VAL_GOB(val); + index = VAL_GOB_INDEX(val); + tail = GOB_PANE(gob) ? GOB_LEN(gob) : 0; + + // unary actions + switch(action) { + // + // !!! Note: PICK* and POKE were unified with path dispatch. The general + // goal is to unify these mechanisms. However, GOB! is tricky in terms + // of what it tried to do with a synthesized PAIR!, calling back into + // Next_Path_Throws(). A logical overhaul of path dispatch is needed. + // This code is left in case there's something to glean from it when + // a GOB!-based path dispatch breaks. + /* + case SYM_PICK_P: + if (NOT(ANY_NUMBER(arg) || IS_BLANK(arg))) + fail (arg); + + if (!GOB_PANE(gob)) goto is_blank; + index += Get_Num_From_Arg(arg) - 1; + if (index >= tail) goto is_blank; + gob = *GOB_AT(gob, index); + index = 0; + goto set_index; + + case SYM_POKE: + index += Get_Num_From_Arg(arg) - 1; + arg = D_ARG(3); + // fallthrough */ + case SYM_CHANGE: { + INCLUDE_PARAMS_OF_CHANGE; + + UNUSED(PAR(series)); + UNUSED(PAR(value)); // handled as `arg` + + if (!IS_GOB(arg)) + goto is_arg_error; + if (!GOB_PANE(gob) || index >= tail) + fail (Error_Past_End_Raw()); + if ( + action == SYM_CHANGE + && (REF(part) || REF(only) || REF(dup)) + ){ + UNUSED(PAR(limit)); + UNUSED(PAR(count)); + fail (Error_Not_Done_Raw()); + } + + Insert_Gobs(gob, arg, index, 1, FALSE); + if (action == SYM_POKE) { + Move_Value(D_OUT, arg); + return R_OUT; + } + index++; + goto set_index; } + + case SYM_APPEND: + index = tail; + // falls through + case SYM_INSERT: { + INCLUDE_PARAMS_OF_INSERT; + + UNUSED(PAR(series)); + UNUSED(PAR(value)); + + if (REF(part) || REF(only) || REF(dup)) { + UNUSED(PAR(limit)); + UNUSED(PAR(count)); + fail (Error_Not_Done_Raw()); + } + + if (IS_GOB(arg)) { + len = 1; + } + else if (IS_BLOCK(arg)) { + len = VAL_ARRAY_LEN_AT(arg); + arg = KNOWN(VAL_ARRAY_AT(arg)); // !!! REVIEW + } + else + goto is_arg_error; + + Insert_Gobs(gob, arg, index, len, FALSE); + break; } + + case SYM_CLEAR: + if (tail > index) Remove_Gobs(gob, index, tail - index); + break; + + case SYM_REMOVE: { + INCLUDE_PARAMS_OF_REMOVE; + + UNUSED(PAR(series)); + + if (REF(map)) { + UNUSED(ARG(key)); + fail (Error_Bad_Refines_Raw()); + } + + len = REF(part) ? Get_Num_From_Arg(ARG(limit)) : 1; + if (index + len > tail) len = tail - index; + if (index < tail && len != 0) Remove_Gobs(gob, index, len); + break; } + + case SYM_TAKE_P: { + INCLUDE_PARAMS_OF_TAKE_P; + + UNUSED(PAR(series)); + + if (REF(deep)) + fail (Error_Bad_Refines_Raw()); + if (REF(last)) + fail (Error_Bad_Refines_Raw()); + + len = REF(part) ? Get_Num_From_Arg(ARG(limit)) : 1; + if (index + len > tail) len = tail - index; + if (index >= tail) goto is_blank; + if (NOT(REF(part))) { // just one value + VAL_RESET_HEADER(D_OUT, REB_GOB); + VAL_GOB(D_OUT) = *GOB_AT(gob, index); + VAL_GOB_INDEX(D_OUT) = 0; + Remove_Gobs(gob, index, 1); + return R_OUT; + } + else { + Init_Block(D_OUT, Pane_To_Array(gob, index, len)); + Remove_Gobs(gob, index, len); + } + return R_OUT; } + + case SYM_AT: + index--; + // falls through + case SYM_SKIP: + index += VAL_INT32(arg); + goto set_index; + + case SYM_HEAD_OF: + index = 0; + goto set_index; + + case SYM_TAIL_OF: + index = tail; + goto set_index; + + case SYM_HEAD_Q: + if (index == 0) goto is_true; + goto is_false; + + case SYM_TAIL_Q: + if (index >= tail) goto is_true; + goto is_false; + + case SYM_PAST_Q: + if (index > tail) goto is_true; + goto is_false; + + case SYM_INDEX_OF: + Init_Integer(D_OUT, index + 1); + break; + + case SYM_LENGTH_OF: + index = (tail > index) ? tail - index : 0; + Init_Integer(D_OUT, index); + break; + + case SYM_FIND: + if (IS_GOB(arg)) { + index = Find_Gob(gob, VAL_GOB(arg)); + if (index == NOT_FOUND) goto is_blank; + goto set_index; + } + goto is_blank; + + case SYM_REVERSE: + for (index = 0; index < tail/2; index++) { + ngob = *GOB_AT(gob, tail-index-1); + *GOB_AT(gob, tail-index-1) = *GOB_AT(gob, index); + *GOB_AT(gob, index) = ngob; + } + Move_Value(D_OUT, D_ARG(1)); + return R_OUT; + + default: + fail (Error_Illegal_Action(REB_GOB, action)); + } + return R_OUT; set_index: - VAL_SET(val, REB_GOB); - VAL_GOB(val) = gob; - VAL_GOB_INDEX(val) = index; - return R_RET; + VAL_RESET_HEADER(D_OUT, REB_GOB); + VAL_GOB(D_OUT) = gob; + VAL_GOB_INDEX(D_OUT) = index; + return R_OUT; -is_none: - return R_NONE; +is_blank: + return R_BLANK; is_arg_error: - Trap_Types(RE_EXPECT_VAL, REB_GOB, VAL_TYPE(arg)); + fail (Error_Unexpected_Type(REB_GOB, VAL_TYPE(arg))); is_false: - return R_FALSE; + return R_FALSE; is_true: - return R_TRUE; + return R_TRUE; } diff --git a/src/core/t-image.c b/src/core/t-image.c index b608866b5f..1beaa23313 100644 --- a/src/core/t-image.c +++ b/src/core/t-image.c @@ -1,1295 +1,1405 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-image.c -** Summary: image datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ -/* -** It's a bit of a shame that alpha channels are represented with -** an inverted level compared to many standards. Alpha zero must -** be opaque in order for RGB tuples to be equal RGBA tuples. -** That is: 10.20.30 = 10.20.30.0 -*/ +// +// File: %t-image.c +// Summary: "image datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + #include "sys-core.h" -#define CLEAR_IMAGE(p, x, y) memset(p, 0, x * y * sizeof(long)) +#define CLEAR_IMAGE(p, x, y) memset(p, 0, x * y * sizeof(u32)) + +#define RESET_IMAGE(p, l) do { \ + REBCNT *start = (REBCNT*)p; \ + REBCNT *stop = start + l; \ + while (start < stop) *start++ = 0xff000000; \ +} while(0) -/*********************************************************************** -** -*/ REBINT CT_Image(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Image: C +// +REBINT CT_Image(const RELVAL *a, const RELVAL *b, REBINT mode) { - REBINT e; + if (mode < 0) + return -1; - if (mode < 0) return -1; - if (mode == 3) return VAL_SERIES(a) == VAL_SERIES(b) && VAL_INDEX(a) == VAL_INDEX(b); - e = VAL_IMAGE_WIDE(a) == VAL_IMAGE_WIDE(a) && VAL_IMAGE_HIGH(b) == VAL_IMAGE_HIGH(b); - if (e) e = (0 == Cmp_Value(a, b, mode > 1)); - return e; + if ( + VAL_IMAGE_WIDE(a) == VAL_IMAGE_WIDE(a) + && VAL_IMAGE_HIGH(b) == VAL_IMAGE_HIGH(b) + ) { + return (0 == Cmp_Value(a, b, LOGICAL(mode == 1))) ? 1 : 0; + } + + return 0; } -/*********************************************************************** -** -*/ REBFLG MT_Image(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ +void Copy_Image_Value(REBVAL *out, const REBVAL *arg, REBINT len) { - if (!Create_Image(data, out, 1)) return FALSE; - VAL_SET(out, REB_IMAGE); - return TRUE; + len = MAX(len, 0); // no negatives + len = MIN(len, cast(REBINT, VAL_IMAGE_LEN(arg))); + + REBINT w = VAL_IMAGE_WIDE(arg); + w = MAX(w, 1); + + REBINT h; + if (len <= w) { + h = 1; + w = len; + } + else + h = len / w; + + if (w == 0) + h = 0; + + REBSER *series = Make_Image(w, h, TRUE); + Init_Image(out, series); + memcpy(VAL_IMAGE_HEAD(out), VAL_IMAGE_DATA(arg), w * h * 4); } -/*********************************************************************** -** -*/ void Reset_Height(REBVAL *value) -/* -** Set height based on tail and width. -** -***********************************************************************/ +// +// MAKE_Image: C +// +void MAKE_Image(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBCNT w = VAL_IMAGE_WIDE(value); - VAL_IMAGE_HIGH(value) = w ? (VAL_TAIL(value) / w) : 0; + if (IS_IMAGE(arg)) { + // + // make image! img + // + Copy_Image_Value(out, arg, VAL_IMAGE_LEN(arg)); + } + else if (IS_BLANK(arg) || (IS_BLOCK(arg) && VAL_ARRAY_LEN_AT(arg) == 0)) { + // + // make image! [] (or none) + // + Init_Image(out, Make_Image(0, 0, TRUE)); + } + else if (IS_PAIR(arg)) { + // + // make image! size + // + REBINT w = VAL_PAIR_X_INT(arg); + REBINT h = VAL_PAIR_Y_INT(arg); + w = MAX(w, 0); + h = MAX(h, 0); + Init_Image(out, Make_Image(w, h, TRUE)); + } + else if (IS_BLOCK(arg)) { + // + // make image! [size rgb alpha index] + // + RELVAL *item = VAL_ARRAY_AT(arg); + + if (!IS_PAIR(item)) goto bad_make; + + REBINT w = VAL_PAIR_X_INT(item); + REBINT h = VAL_PAIR_Y_INT(item); + if (w < 0 || h < 0) goto bad_make; + + REBSER *img = Make_Image(w, h, FALSE); + if (!img) goto bad_make; + + Init_Image(out, img); + + REBYTE *ip = IMG_DATA(img); // image pointer + REBCNT size = w * h; + + ++item; + + if (IS_END(item)) { + // + // make image! [10x20]... already done + } + else if (IS_BINARY(item)) { + + // Load image data: + Bin_To_RGB(ip, size, VAL_BIN_AT(item), VAL_LEN_AT(item) / 3); + ++item; + + // !!! Review handling of END here; was not explicit before and + // just fell through the binary and integer tests... + + // Load alpha channel data: + if (NOT_END(item) && IS_BINARY(item)) { + Bin_To_Alpha(ip, size, VAL_BIN_AT(item), VAL_LEN_AT(item)); + // VAL_IMAGE_TRANSP(value)=VITT_ALPHA; + ++item; + } + + if (NOT_END(item) && IS_INTEGER(item)) { + VAL_INDEX(out) = (Int32s(KNOWN(item), 1) - 1); + ++item; + } + } + else if (IS_TUPLE(item)) { + Fill_Rect(cast(REBCNT*, ip), TO_PIXEL_TUPLE(item), w, w, h, TRUE); + ++item; + if (IS_INTEGER(item)) { + Fill_Alpha_Rect( + cast(REBCNT*, ip), cast(REBYTE, VAL_INT32(item)), w, w, h + ); + // VAL_IMAGE_TRANSP(value)=VITT_ALPHA; + ++item; + } + } + else if (IS_BLOCK(item)) { + REBCNT bad_index; + if (Array_Has_Non_Tuple(&bad_index, item)) { + REBSPC *derived = Derive_Specifier(VAL_SPECIFIER(arg), item); + fail (Error_Invalid_Arg_Core( + VAL_ARRAY_AT_HEAD(item, bad_index), + derived + )); + } + + Tuples_To_RGBA( + ip, size, KNOWN(VAL_ARRAY_AT(item)), VAL_LEN_AT(item) + ); + } + else + goto bad_make; + + assert(IS_IMAGE(out)); + } + else + fail (Error_Invalid_Type(VAL_TYPE(arg))); + + return; + +bad_make: + fail (Error_Bad_Make(kind, arg)); } -/*********************************************************************** -** -*/ void Set_Pixel_Tuple(REBYTE *dp, REBVAL *tuple) -/* -***********************************************************************/ +// +// TO_Image: C +// +void TO_Image(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - // Tuple to pixel. - REBYTE *tup = VAL_TUPLE(tuple); + assert(kind == REB_IMAGE); + UNUSED(kind); + + if (IS_IMAGE(arg)) { + Copy_Image_Value(out, arg, VAL_IMAGE_LEN(arg)); + } + else if (IS_GOB(arg)) { + REBVAL *image = OS_GOB_TO_IMAGE(VAL_GOB(arg)); + if (image == NULL) + fail (Error_Bad_Make(REB_IMAGE, arg)); // not GUI build... + Move_Value(out, image); // what are the GC semantics here? + } + else if (IS_BINARY(arg)) { + REBINT diff = VAL_LEN_AT(arg) / 4; + if (diff == 0) + fail (Error_Bad_Make(REB_IMAGE, arg)); + + REBINT w; + if (diff < 100) w = diff; + else if (diff < 10000) w = 100; + else w = 500; + + REBINT h = diff / w; + if (w * h < diff) h++; // partial line + + REBSER *series = Make_Image(w, h, TRUE); + Init_Image(out, series); + Bin_To_RGBA( + IMG_DATA(series), + w * h, + VAL_BIN_AT(arg), + VAL_LEN_AT(arg) / 4, + FALSE + ); + } + else + fail (Error_Invalid_Type(VAL_TYPE(arg))); +} - dp[C_R] = tup[0]; - dp[C_G] = tup[1]; - dp[C_B] = tup[2]; - if (VAL_TUPLE_LEN(tuple) > 3) dp[C_A] = tup[3]; + +// +// Reset_Height: C +// +// Set height based on tail and width. +// +void Reset_Height(REBVAL *value) +{ + REBCNT w = VAL_IMAGE_WIDE(value); + VAL_IMAGE_HIGH(value) = w ? (VAL_LEN_HEAD(value) / w) : 0; } -/*********************************************************************** -** -*/ void Set_Tuple_Pixel(REBYTE *dp, REBVAL *tuple) -/* -***********************************************************************/ +// +// Set_Pixel_Tuple: C +// +void Set_Pixel_Tuple(REBYTE *dp, const REBVAL *tuple) { - // Pixel to tuple. - REBYTE *tup = VAL_TUPLE(tuple); - - VAL_SET(tuple, REB_TUPLE); - VAL_TUPLE_LEN(tuple) = 4; - tup[0] = dp[C_R]; - tup[1] = dp[C_G]; - tup[2] = dp[C_B]; - tup[3] = dp[C_A]; + // Tuple to pixel. + const REBYTE *tup = VAL_TUPLE(tuple); + + dp[C_R] = tup[0]; + dp[C_G] = tup[1]; + dp[C_B] = tup[2]; + if (VAL_TUPLE_LEN(tuple) > 3) + dp[C_A] = tup[3]; + else + dp[C_A] = 0xff; } -/*********************************************************************** -** -*/ void Fill_Line(REBCNT *ip, REBCNT color, REBCNT len, REBOOL only) -/* -***********************************************************************/ +// +// Set_Tuple_Pixel: C +// +void Set_Tuple_Pixel(REBYTE *dp, REBVAL *tuple) { - if (only) // only RGB, do not touch Alpha - for (; len > 0; len--, ip++) *ip = (*ip & 0xff000000) | color; - else - for (; len > 0; len--) *ip++ = color; + // Pixel to tuple. + REBYTE *tup = VAL_TUPLE(tuple); + + VAL_RESET_HEADER(tuple, REB_TUPLE); + VAL_TUPLE_LEN(tuple) = 4; + tup[0] = dp[C_R]; + tup[1] = dp[C_G]; + tup[2] = dp[C_B]; + tup[3] = dp[C_A]; } +// +// Fill_Line: C +// +void Fill_Line(REBCNT *ip, REBCNT color, REBCNT len, REBOOL only) +{ + if (only) {// only RGB, do not touch Alpha + color &= 0xffffff; + for (; len > 0; len--, ip++) *ip = (*ip & 0xff000000) | color; + } else + for (; len > 0; len--) *ip++ = color; +} -/*********************************************************************** -** -*/ void Fill_Rect(REBCNT *ip, REBCNT color, REBCNT w, REBINT dupx, REBINT dupy, REBOOL only) -/* -***********************************************************************/ + +// +// Fill_Rect: C +// +void Fill_Rect(REBCNT *ip, REBCNT color, REBCNT w, REBINT dupx, REBINT dupy, REBOOL only) { - for (; dupy > 0; dupy--, ip += w) - Fill_Line(ip, color, dupx, only); + for (; dupy > 0; dupy--, ip += w) + Fill_Line(ip, color, dupx, only); } -/*********************************************************************** -** -*/ void Fill_Alpha_Line(REBYTE *rgba, REBYTE alpha, REBINT len) -/* -***********************************************************************/ +// +// Fill_Alpha_Line: C +// +void Fill_Alpha_Line(REBYTE *rgba, REBYTE alpha, REBINT len) { - for (; len > 0; len--, rgba += 4) - rgba[C_A] = alpha; + for (; len > 0; len--, rgba += 4) + rgba[C_A] = alpha; } -/*********************************************************************** -** -*/ void Fill_Alpha_Rect(REBCNT *ip, REBYTE alpha, REBINT w, REBINT dupx, REBINT dupy) -/* -***********************************************************************/ +// +// Fill_Alpha_Rect: C +// +void Fill_Alpha_Rect(REBCNT *ip, REBYTE alpha, REBINT w, REBINT dupx, REBINT dupy) { - for (; dupy > 0; dupy--, ip += w) - Fill_Alpha_Line((REBYTE *)ip, alpha, dupx); + for (; dupy > 0; dupy--, ip += w) + Fill_Alpha_Line((REBYTE *)ip, alpha, dupx); } -/*********************************************************************** -** -*/ REBCNT *Find_Color(REBCNT *ip, REBCNT color, REBCNT len, REBOOL only) -/* -***********************************************************************/ +// +// Find_Color: C +// +REBCNT *Find_Color(REBCNT *ip, REBCNT color, REBCNT len, REBOOL only) { - if (only) { // only RGB, do not touch Alpha - for (; len > 0; len--, ip++) - if (color == (*ip & 0x00ffffff)) return ip; - } else { - for (; len > 0; len--, ip++) - if (color == *ip) return ip; - } - return 0; + if (only) { // only RGB, do not touch Alpha + for (; len > 0; len--, ip++) + if (color == (*ip & 0x00ffffff)) return ip; + } else { + for (; len > 0; len--, ip++) + if (color == *ip) return ip; + } + return 0; } -/*********************************************************************** -** -*/ REBCNT *Find_Alpha(REBCNT *ip, REBCNT alpha, REBCNT len) -/* -***********************************************************************/ +// +// Find_Alpha: C +// +REBCNT *Find_Alpha(REBCNT *ip, REBCNT alpha, REBCNT len) { - for (; len > 0; len--, ip++) { - if (alpha == (*ip >> 24)) return ip; - } - return 0; + for (; len > 0; len--, ip++) { + if (alpha == (*ip >> 24)) return ip; + } + return 0; } -/*********************************************************************** -** -*/ void RGB_To_Bin(REBYTE *bin, REBYTE *rgba, REBINT len, REBOOL alpha) -/* -***********************************************************************/ +// +// RGB_To_Bin: C +// +void RGB_To_Bin(REBYTE *bin, REBYTE *rgba, REBINT len, REBOOL alpha) { - // Convert internal image (integer) to RGB/A order binary string: - if (alpha) { - for (; len > 0; len--, rgba += 4, bin += 4) { - bin[0] = rgba[C_R]; - bin[1] = rgba[C_G]; - bin[2] = rgba[C_B]; - bin[3] = rgba[C_A]; - } - } else { - // Only the RGB part: - for (; len > 0; len--, rgba += 4, bin += 3) { - bin[0] = rgba[C_R]; - bin[1] = rgba[C_G]; - bin[2] = rgba[C_B]; - } - } + // Convert internal image (integer) to RGB/A order binary string: + if (alpha) { + for (; len > 0; len--, rgba += 4, bin += 4) { + bin[0] = rgba[C_R]; + bin[1] = rgba[C_G]; + bin[2] = rgba[C_B]; + bin[3] = rgba[C_A]; + } + } else { + // Only the RGB part: + for (; len > 0; len--, rgba += 4, bin += 3) { + bin[0] = rgba[C_R]; + bin[1] = rgba[C_G]; + bin[2] = rgba[C_B]; + } + } } -/*********************************************************************** -** -*/ void Bin_To_RGB(REBYTE *rgba, REBCNT size, REBYTE *bin, REBCNT len) -/* -***********************************************************************/ +// +// Bin_To_RGB: C +// +void Bin_To_RGB(REBYTE *rgba, REBCNT size, REBYTE *bin, REBCNT len) { - if (len > size) len = size; // avoid over-run - - // Convert RGB binary string to internal image (integer), no alpha: - for (; len > 0; len--, rgba += 4, bin += 3) { - rgba[C_R] = bin[0]; - rgba[C_G] = bin[1]; - rgba[C_B] = bin[2]; - } + if (len > size) len = size; // avoid over-run + + // Convert RGB binary string to internal image (integer), no alpha: + for (; len > 0; len--, rgba += 4, bin += 3) { + rgba[C_R] = bin[0]; + rgba[C_G] = bin[1]; + rgba[C_B] = bin[2]; + } } -/*********************************************************************** -** -*/ void Bin_To_RGBA(REBYTE *rgba, REBCNT size, REBYTE *bin, REBINT len, REBOOL only) -/* -***********************************************************************/ +// +// Bin_To_RGBA: C +// +void Bin_To_RGBA(REBYTE *rgba, REBCNT size, REBYTE *bin, REBINT len, REBOOL only) { - if (len > (REBINT)size) len = size; // avoid over-run - - // Convert from BGRA format to internal image (integer): - for (; len > 0; len--, rgba += 4, bin += 4) { - rgba[C_B] = bin[0]; - rgba[C_G] = bin[1]; - rgba[C_R] = bin[2]; - if (!only) rgba[C_A] = bin[3]; - } + if (len > (REBINT)size) len = size; // avoid over-run + + // Convert from RGBA format to internal image (integer): + for (; len > 0; len--, rgba += 4, bin += 4) { + rgba[C_R] = bin[0]; + rgba[C_G] = bin[1]; + rgba[C_B] = bin[2]; + if (!only) rgba[C_A] = bin[3]; + } } -/*********************************************************************** -** -*/ void Alpha_To_Bin(REBYTE *bin, REBYTE *rgba, REBINT len) -/* -***********************************************************************/ +// +// Alpha_To_Bin: C +// +void Alpha_To_Bin(REBYTE *bin, REBYTE *rgba, REBINT len) { - for (; len > 0; len--, rgba += 4) - *bin++ = rgba[C_A]; + for (; len > 0; len--, rgba += 4) + *bin++ = rgba[C_A]; } -/*********************************************************************** -** -*/ void Bin_To_Alpha(REBYTE *rgba, REBCNT size, REBYTE *bin, REBINT len) -/* -***********************************************************************/ +// +// Bin_To_Alpha: C +// +void Bin_To_Alpha(REBYTE *rgba, REBCNT size, REBYTE *bin, REBINT len) { - if (len > (REBINT)size) len = size; // avoid over-run + if (len > (REBINT)size) len = size; // avoid over-run - for (; len > 0; len--, rgba += 4) - rgba[C_A] = *bin++; + for (; len > 0; len--, rgba += 4) + rgba[C_A] = *bin++; } -/*********************************************************************** -** -*/ REBFLG Valid_Tuples(REBVAL *blk) -/* -***********************************************************************/ +// +// Array_Has_Non_Tuple: C +// +// Checks the given ANY-ARRAY! REBVAL from its current index position to +// the end to see if any of its contents are not TUPLE!. If so, returns +// TRUE and `index_out` will contain the index position from the head of +// the array of the non-tuple. Otherwise returns FALSE. +// +REBOOL Array_Has_Non_Tuple(REBCNT *index_out, RELVAL *blk) { - REBCNT n = VAL_INDEX(blk); - REBCNT len = VAL_LEN(blk); + REBCNT len; + + assert(ANY_ARRAY(blk)); - blk = VAL_BLK_DATA(blk); + len = VAL_LEN_HEAD(blk); + *index_out = VAL_INDEX(blk); - for (; n < len; n++) - if (!IS_TUPLE(blk+n)) return n+1; + for (; *index_out < len; (*index_out)++) + if (!IS_TUPLE(VAL_ARRAY_AT_HEAD(blk, *index_out))) + return TRUE; - return 0; + return FALSE; } -/*********************************************************************** -** -*/ void Tuples_To_RGBA(REBYTE *rgba, REBCNT size, REBVAL *blk, REBCNT len) -/* -***********************************************************************/ +// +// Tuples_To_RGBA: C +// +void Tuples_To_RGBA(REBYTE *rgba, REBCNT size, REBVAL *blk, REBCNT len) { - REBYTE *bin; + REBYTE *bin; - if (len > size) len = size; // avoid over-run + if (len > size) len = size; // avoid over-run - for (; len > 0; len--, rgba += 4, blk++) { - bin = VAL_TUPLE(blk); - rgba[C_R] = bin[0]; - rgba[C_G] = bin[1]; - rgba[C_B] = bin[2]; - rgba[C_A] = bin[3]; - } + for (; len > 0; len--, rgba += 4, blk++) { + bin = VAL_TUPLE(blk); + rgba[C_R] = bin[0]; + rgba[C_G] = bin[1]; + rgba[C_B] = bin[2]; + rgba[C_A] = bin[3]; + } } -/*********************************************************************** -** -*/ void Image_To_BGRA(REBYTE *rgba, REBYTE *bin, REBINT len) -/* -***********************************************************************/ +// +// Image_To_RGBA: C +// +void Image_To_RGBA(REBYTE *rgba, REBYTE *bin, REBINT len) { - // Convert from BGRA format to internal image (integer): - for (; len > 0; len--, rgba += 4, bin += 4) { - bin[0] = rgba[C_B]; - bin[1] = rgba[C_G]; - bin[2] = rgba[C_R]; - bin[3] = rgba[C_A]; - } + // Convert from internal image (integer) to RGBA binary order: + for (; len > 0; len--, rgba += 4, bin += 4) { + bin[0] = rgba[C_R]; + bin[1] = rgba[C_G]; + bin[2] = rgba[C_B]; + bin[3] = rgba[C_A]; + } } -#ifdef ndef -INLINE REBCNT ARGB_To_BGR(REBCNT i) +#ifdef NEED_ARGB_TO_BGR +REBCNT ARGB_To_BGR(REBCNT i) { - return - ((i & 0x00ff0000) >> 16) | // red - ((i & 0x0000ff00)) | // green - ((i & 0x000000ff) << 16); // blue + return + ((i & 0x00ff0000) >> 16) | // red + ((i & 0x0000ff00)) | // green + ((i & 0x000000ff) << 16); // blue } #endif -/*********************************************************************** -** -*/ void Mold_Image_Data(REBVAL *value, REB_MOLD *mold) -/* -***********************************************************************/ +// +// Mold_Image_Data: C +// +void Mold_Image_Data(const REBVAL *value, REB_MOLD *mold) { - REBUNI *up; - REBCNT len; - REBCNT size; - REBCNT *data; + REBUNI *up; + REBCNT len; + REBCNT size; + REBCNT *data; + REBYTE* pixel; - Emit(mold, "IxI #{", VAL_IMAGE_WIDE(value), VAL_IMAGE_HIGH(value)); + Emit(mold, "IxI #{", VAL_IMAGE_WIDE(value), VAL_IMAGE_HIGH(value)); - // Output RGB image: - size = VAL_IMAGE_LEN(value); // # pixels (from index to tail) - data = (REBCNT *)VAL_IMAGE_DATA(value); - up = Prep_Uni_Series(mold, (size * 6) + (size / 10) + 1); + // Output RGB image: + size = VAL_IMAGE_LEN(value); // # pixels (from index to tail) + data = (REBCNT *)VAL_IMAGE_DATA(value); + up = Prep_Uni_Series(mold, (size * 6) + (size / 10) + 1); - for (len = 0; len < size; len++) { - if ((len % 10) == 0) *up++ = LF; - up = Form_RGB_Uni(up, *data++); - } + for (len = 0; len < size; len++) { + pixel = (REBYTE*)data++; + if ((len % 10) == 0) *up++ = LF; + up = Form_RGB_Uni(up, TO_RGBA_COLOR(pixel[C_R],pixel[C_G],pixel[C_B],pixel[C_A])); + } - // Output Alpha channel, if it has one: - if (Image_Has_Alpha(value, FALSE)) { + // Output Alpha channel, if it has one: + if (Image_Has_Alpha(value)) { - Append_Bytes(mold->series, "\n} #{"); + Append_Unencoded(mold->series, "\n} #{"); - up = Prep_Uni_Series(mold, (size * 2) + (size / 10) + 1); + up = Prep_Uni_Series(mold, (size * 2) + (size / 10) + 1); - data = (REBCNT *)VAL_IMAGE_DATA(value); - for (len = 0; len < size; len++) { - if ((len % 10) == 0) *up++ = LF; - up = Form_Hex2_Uni(up, *data++ >> 24); - } - } - *up = 0; // tail already set from Prep. + data = (REBCNT *)VAL_IMAGE_DATA(value); + for (len = 0; len < size; len++) { + if ((len % 10) == 0) *up++ = LF; + up = Form_Hex2_Uni(up, *data++ >> 24); + } + } + *up = 0; // tail already set from Prep. - Append_Bytes(mold->series, "\n}"); + Append_Unencoded(mold->series, "\n}"); } -/*********************************************************************** -** -*/ REBSER *Make_Image_Binary(REBVAL *image) -/* -***********************************************************************/ +// +// Make_Image_Binary: C +// +REBSER *Make_Image_Binary(const REBVAL *image) { - REBSER *ser; - -#ifdef XENDIAN_BIG - ser = Make_Quad(0, VAL_IMAGE_LEN(image)); - ser->tail = VAL_IMAGE_LEN(image) * 4; - Image_To_BGRA(VAL_IMAGE_DATA(image), QUAD_HEAD(ser), VAL_IMAGE_LEN(image)); -#else - ser = Copy_Bytes(VAL_IMAGE_DATA(image), VAL_IMAGE_LEN(image)*4); -#endif - return ser; + REBSER *ser; + REBINT len; + len = VAL_IMAGE_LEN(image) * 4; + ser = Make_Binary(len); + SET_SERIES_LEN(ser, len); + Image_To_RGBA(VAL_IMAGE_DATA(image), QUAD_HEAD(ser), VAL_IMAGE_LEN(image)); + return ser; } -/*********************************************************************** -** -*/ REBSER *Make_Image(REBCNT w, REBCNT h, REBFLG error) -/* -** Allocate and initialize an image. -** If error is TRUE, throw error on bad size. -** Return zero on oversized image. -** -***********************************************************************/ +// +// Make_Image: C +// +// Allocate and initialize an image. +// If error is TRUE, throw error on bad size. +// Return zero on oversized image. +// +REBSER *Make_Image(REBCNT w, REBCNT h, REBOOL error) { - REBSER *img; - - if (w > 0xFFFF || h > 0xFFFF) { - if (error) Trap1(RE_SIZE_LIMIT, Get_Type(REB_IMAGE)); - else return 0; - } - - img = Make_Series(w * h + 1, sizeof(REBINT), FALSE); - LABEL_SERIES(img, "make image"); - img->tail = w * h; - CLEAR(img->data, (img->tail + 1) * sizeof(REBINT)); - IMG_WIDE(img) = w; - IMG_HIGH(img) = h; - return img; + if (w > 0xFFFF || h > 0xFFFF) { + if (error) + fail (Error_Size_Limit_Raw(Get_Type(REB_IMAGE))); + return NULL; + } + + REBSER *img = Make_Series(w * h + 1, sizeof(u32)); + SET_SERIES_LEN(img, w * h); + RESET_IMAGE(SER_DATA_RAW(img), SER_LEN(img)); //length in 'pixels' + IMG_WIDE(img) = w; + IMG_HIGH(img) = h; + return img; } -/*********************************************************************** -** -*/ void Clear_Image(REBVAL *img) -/* -** Clear image data. -** -***********************************************************************/ +// +// Clear_Image: C +// +// Clear image data. +// +void Clear_Image(REBVAL *img) { - REBCNT w = VAL_IMAGE_WIDE(img); - REBCNT h = VAL_IMAGE_HIGH(img); - REBYTE *p = VAL_IMAGE_HEAD(img); - CLEAR_IMAGE(p, w, h); + REBCNT w = VAL_IMAGE_WIDE(img); + REBCNT h = VAL_IMAGE_HIGH(img); + REBYTE *p = VAL_IMAGE_HEAD(img); + CLEAR_IMAGE(p, w, h); } -/*********************************************************************** -** -*/ REBVAL *Create_Image(REBVAL *block, REBVAL *val, REBCNT modes) -/* -** Create an image value from components block [pair rgb alpha]. -** -***********************************************************************/ +// +// Modify_Image: C +// +// Insert or change image +// +REBVAL *Modify_Image(REBFRM *frame_, REBCNT action) { - REBINT w, h; - REBYTE *ip; // image pointer - REBCNT size; - REBSER *img; - - // Check that PAIR is valid: - if (!IS_PAIR(block)) return 0; - w = VAL_PAIR_X_INT(block); - h = VAL_PAIR_Y_INT(block); - if (w < 0 || h < 0) return 0; - - img = Make_Image(w, h, FALSE); - if (img == 0) return 0; - SET_IMAGE(val, img); - - ip = IMG_DATA(img); - size = w * h; - - //len = VAL_BLK_LEN(arg); - block++; - if (IS_BINARY(block)) { - - // Load image data: - Bin_To_RGB(ip, size, VAL_BIN_DATA(block), VAL_LEN(block) / 3); - block++; - - // Load alpha channel data: - if (IS_BINARY(block)) { - Bin_To_Alpha(ip, size, VAL_BIN_DATA(block), VAL_LEN(block)); -// VAL_IMAGE_TRANSP(value)=VITT_ALPHA; - block++; - } - - if (IS_INTEGER(block)) { - VAL_INDEX(val) = (Int32s(block, 1) - 1); - block++; - } - } - else if (IS_TUPLE(block)) { - Fill_Rect((REBCNT *)ip, TO_COLOR_TUPLE(block), w, w, h, TRUE); - block++; - if (IS_INTEGER(block)) { - Fill_Alpha_Rect((REBCNT *)ip, (REBYTE)VAL_INT32(block), w, w, h); -// VAL_IMAGE_TRANSP(value)=VITT_ALPHA; - block++; - } - } - else if (IS_BLOCK(block)) { - if (w = Valid_Tuples(block)) Trap_Arg(block+w-1); - Tuples_To_RGBA(ip, size, VAL_BLK_DATA(block), VAL_LEN(block)); - } - else if (!IS_END(block)) return 0; - - //if (!IS_END(block)) Trap_Arg(block); - - return val; + INCLUDE_PARAMS_OF_INSERT; // currently must have same frame as CHANGE + + REBVAL *value = ARG(series); // !!! confusing, very old (unused?) code! + REBVAL *arg = ARG(value); + REBVAL *len = ARG(limit); // void if no /PART + REBVAL *count = ARG(count); // void if no /DUP + + REBINT part = 1; // /part len + REBINT partx, party; + REBINT dup = 1; // /dup count + REBINT dupx, dupy; + REBOOL only = FALSE; // /only + REBCNT index = VAL_INDEX(value); + REBCNT tail = VAL_LEN_HEAD(value); + REBCNT n; + REBINT x; + REBINT w; + REBINT y; + REBYTE *ip; + + if (!(w = VAL_IMAGE_WIDE(value))) return value; + + if (action == SYM_APPEND) { + index = tail; + action = SYM_INSERT; + } + + x = index % w; // offset on the line + y = index / w; // offset line + + if (REF(only)) + only = TRUE; + + // Validate that block arg is all tuple values: + if (IS_BLOCK(arg) && Array_Has_Non_Tuple(&n, arg)) + fail (Error_Invalid_Arg_Core( + VAL_ARRAY_AT_HEAD(arg, n), VAL_SPECIFIER(arg) + )); + + if (REF(dup)) { // "it specifies fill size" + if (IS_INTEGER(count)) { + dup = VAL_INT32(count); + dup = MAX(dup, 0); + if (dup == 0) return value; + } + else if (IS_PAIR(count)) { // rectangular dup + dupx = VAL_PAIR_X_INT(count); + dupy = VAL_PAIR_Y_INT(count); + dupx = MAX(dupx, 0); + dupx = MIN(dupx, (REBINT)w - x); // clip dup width + dupy = MAX(dupy, 0); + if (action != SYM_INSERT) + dupy = MIN(dupy, (REBINT)VAL_IMAGE_HIGH(value) - y); + else + dup = dupy * w; + if (dupx == 0 || dupy == 0) return value; + } + else + fail (Error_Invalid_Type(VAL_TYPE(count))); + } + + if (REF(part)) { // only allowed when arg is a series + if (IS_BINARY(arg)) { + if (IS_INTEGER(len)) { + part = VAL_INT32(len); + } else if (IS_BINARY(len)) { + part = (VAL_INDEX(len) - VAL_INDEX(arg)) / 4; + } else + fail (len); + part = MAX(part, 0); + } + else if (IS_IMAGE(arg)) { + if (IS_INTEGER(len)) { + part = VAL_INT32(len); + part = MAX(part, 0); + } + else if (IS_IMAGE(len)) { + if (VAL_IMAGE_WIDE(len) == 0) + fail (len); + + partx = VAL_INDEX(len) - VAL_INDEX(arg); + party = partx / VAL_IMAGE_WIDE(len); + party = MAX(party, 1); + partx = MIN(partx, (REBINT)VAL_IMAGE_WIDE(arg)); + goto len_compute; + } + else if (IS_PAIR(len)) { + partx = VAL_PAIR_X_INT(len); + party = VAL_PAIR_Y_INT(len); + len_compute: + partx = MAX(partx, 0); + partx = MIN(partx, (REBINT)w - x); // clip part width + party = MAX(party, 0); + if (action != SYM_INSERT) + party = MIN(party, (REBINT)VAL_IMAGE_HIGH(value) - y); + else + part = party * w; + if (partx == 0 || party == 0) return value; + } + else + fail (Error_Invalid_Type(VAL_TYPE(len))); + } + else + fail (arg); // /part not allowed + } + else { + if (IS_IMAGE(arg)) { // Use image for /part sizes + partx = VAL_IMAGE_WIDE(arg); + party = VAL_IMAGE_HIGH(arg); + partx = MIN(partx, (REBINT)w - x); // clip part width + if (action != SYM_INSERT) + party = MIN(party, (REBINT)VAL_IMAGE_HIGH(value) - y); + else + part = party * w; + } + else if (IS_BINARY(arg)) { + part = VAL_LEN_AT(arg) / 4; + } + else if (IS_BLOCK(arg)) { + part = VAL_LEN_AT(arg); + } + else if (!IS_INTEGER(arg) && !IS_TUPLE(arg)) + fail (Error_Invalid_Type(VAL_TYPE(arg))); + } + + // Expand image data if necessary: + if (action == SYM_INSERT) { + if (index > tail) index = tail; + Expand_Series(VAL_SERIES(value), index, dup * part); + RESET_IMAGE(VAL_BIN(value) + (index * 4), dup * part); //length in 'pixels' + Reset_Height(value); + tail = VAL_LEN_HEAD(value); + only = FALSE; + } + ip = VAL_IMAGE_HEAD(value); + + // Handle the datatype of the argument. + if (IS_INTEGER(arg) || IS_TUPLE(arg)) { // scalars + if (index + dup > tail) dup = tail - index; // clip it + ip += index * 4; + if (IS_INTEGER(arg)) { // Alpha channel + REBINT arg_int = VAL_INT32(arg); + if ((arg_int < 0) || (arg_int > 255)) + fail (Error_Out_Of_Range(arg)); + if (IS_PAIR(count)) // rectangular fill + Fill_Alpha_Rect( + cast(REBCNT*, ip), cast(REBYTE, arg_int), w, dupx, dupy + ); + else + Fill_Alpha_Line(ip, cast(REBYTE, arg_int), dup); + } + else if (IS_TUPLE(arg)) { // RGB + if (IS_PAIR(count)) // rectangular fill + Fill_Rect((REBCNT *)ip, TO_PIXEL_TUPLE(arg), w, dupx, dupy, only); + else + Fill_Line((REBCNT *)ip, TO_PIXEL_TUPLE(arg), dup, only); + } + } else if (IS_IMAGE(arg)) { + Copy_Rect_Data(value, x, y, partx, party, arg, 0, 0); // dst dx dy w h src sx sy + } + else if (IS_BINARY(arg)) { + if (index + part > tail) part = tail - index; // clip it + ip += index * 4; + for (; dup > 0; dup--, ip += part * 4) + Bin_To_RGBA(ip, part, VAL_BIN_AT(arg), part, only); + } + else if (IS_BLOCK(arg)) { + if (index + part > tail) part = tail - index; // clip it + ip += index * 4; + for (; dup > 0; dup--, ip += part * 4) + Tuples_To_RGBA(ip, part, KNOWN(VAL_ARRAY_AT(arg)), part); + } + else + fail (Error_Invalid_Type(VAL_TYPE(arg))); + + Reset_Height(value); + + if (action == SYM_APPEND) VAL_INDEX(value) = 0; + return value; } -/*********************************************************************** -** -*/ REBVAL *Modify_Image(REBVAL *ds, REBCNT action) -/* -** Insert or change image -** ACTION value arg /part len /only /dup count -** -***********************************************************************/ +// +// Find_Image: C +// +// Finds a value in a series and returns the series at the start of it. For +// parameters of FIND, see the action definition. +// +// !!! old and very broken code, untested and probably (hopefully) not +// used by R3-View... (?) +// +void Find_Image(REBFRM *frame_) { - REBVAL *value = D_ARG(1); - REBVAL *arg = D_ARG(2); - REBVAL *len = D_ARG(4); - REBVAL *count = D_ARG(7); - REBINT part = 1; // /part len - REBINT partx, party; - REBINT dup = 1; // /dup count - REBINT dupx, dupy; - REBOOL only = 0; // /only - REBCNT index = VAL_INDEX(value); - REBCNT tail = VAL_TAIL(value); - REBINT n; - REBINT x; - REBINT w; - REBINT y; - REBYTE *ip; - - if (!(w = VAL_IMAGE_WIDE(value))) return value; - - if (action == A_APPEND) { - index = tail; - action = A_INSERT; - } - - x = index % w; // offset on the line - y = index / w; // offset line - - if (D_REF(5)) only = 1; - - // Validate that block arg is all tuple values: - if (IS_BLOCK(arg) && NZ(n = Valid_Tuples(arg))) { - Trap_Arg(VAL_BLK_SKIP(arg, n-1)); - } - - // Get the /dup refinement. It specifies fill size. - if (D_REF(6)) { - if (IS_INTEGER(count)) { - dup = VAL_INT32(count); - dup = MAX(dup, 0); - if (dup == 0) return value; - } else if (IS_PAIR(count)) { // rectangular dup - dupx = VAL_PAIR_X_INT(count); - dupy = VAL_PAIR_Y_INT(count); - dupx = MAX(dupx, 0); - dupx = MIN(dupx, (REBINT)w - x); // clip dup width - dupy = MAX(dupy, 0); - if (action != A_INSERT) - dupy = MIN(dupy, (REBINT)VAL_IMAGE_HIGH(value) - y); - else - dup = dupy * w; - if (dupx == 0 || dupy == 0) return value; - } else - Trap_Type(count); - } - - // Get the /part refinement. Only allowed when arg is a series. - if (D_REF(3)) { - if (IS_BINARY(arg)) { - if (IS_INTEGER(len)) { - part = VAL_INT32(len); - } else if (IS_BINARY(len)) { - part = (VAL_INDEX(len) - VAL_INDEX(arg)) / 4; - } else - Trap_Arg(len); - part = MAX(part, 0); - } else if (IS_IMAGE(arg)) { - if (IS_INTEGER(len)) { - part = VAL_INT32(len); - part = MAX(part, 0); - } else if (IS_IMAGE(len)) { - if (!VAL_IMAGE_WIDE(len)) Trap_Arg(len); - partx = VAL_INDEX(len) - VAL_INDEX(arg); - party = partx / VAL_IMAGE_WIDE(len); - party = MAX(party, 1); - partx = MIN(partx, (REBINT)VAL_IMAGE_WIDE(arg)); - goto len_compute; - } else if (IS_PAIR(len)) { - partx = VAL_PAIR_X_INT(len); - party = VAL_PAIR_Y_INT(len); - len_compute: - partx = MAX(partx, 0); - partx = MIN(partx, (REBINT)w - x); // clip part width - party = MAX(party, 0); - if (action != A_INSERT) - party = MIN(party, (REBINT)VAL_IMAGE_HIGH(value) - y); - else - part = party * w; - if (partx == 0 || party == 0) return value; - } else - Trap_Type(len); - } else - Trap_Arg(arg); // /part not allowed - } else { - if (IS_IMAGE(arg)) { // Use image for /part sizes - partx = VAL_IMAGE_WIDE(arg); - party = VAL_IMAGE_HIGH(arg); - partx = MIN(partx, (REBINT)w - x); // clip part width - if (action != A_INSERT) - party = MIN(party, (REBINT)VAL_IMAGE_HIGH(value) - y); - else - part = party * w; - } else if (IS_BINARY(arg)) { - part = VAL_LEN(arg) / 4; - } else if (IS_BLOCK(arg)) { - part = VAL_LEN(arg); - } else if (! (IS_INTEGER(arg) || IS_TUPLE(arg))) - Trap_Type(arg); - } - - // Expand image data if necessary: - if (action == A_INSERT) { - if (index > tail) index = tail; - Expand_Series(VAL_SERIES(value), index, dup * part); - CLEAR(VAL_BIN(value) + (index * 4), dup * part * 4); - Reset_Height(value); - tail = VAL_TAIL(value); - only = 0; - } - ip = VAL_IMAGE_HEAD(value); - - // Handle the datatype of the argument. - if (IS_INTEGER(arg) || IS_TUPLE(arg)) { // scalars - if (index + dup > tail) dup = tail - index; // clip it - ip += index * 4; - if (IS_INTEGER(arg)) { // Alpha channel - n = VAL_INT32(arg); - if ((n < 0) || (n > 255)) Trap_Range(arg); - if (IS_PAIR(count)) // rectangular fill - Fill_Alpha_Rect((REBCNT *)ip, (REBYTE)n, w, dupx, dupy); - else - Fill_Alpha_Line(ip, (REBYTE)n, dup); - } else if (IS_TUPLE(arg)) { // RGB - if (IS_PAIR(count)) // rectangular fill - Fill_Rect((REBCNT *)ip, TO_COLOR_TUPLE(arg), w, dupx, dupy, only); - else - Fill_Line((REBCNT *)ip, TO_COLOR_TUPLE(arg), dup, only); - } - } else if (IS_IMAGE(arg)) { - Copy_Rect_Data(value, x, y, partx, party, arg, 0, 0); // dst dx dy w h src sx sy - } else if (IS_BINARY(arg)) { - if (index + part > tail) part = tail - index; // clip it - ip += index * 4; - for (; dup > 0; dup--, ip += part * 4) - Bin_To_RGBA(ip, part, VAL_BIN_DATA(arg), part, only); - } else if (IS_BLOCK(arg)) { - if (index + part > tail) part = tail - index; // clip it - ip += index * 4; - for (; dup > 0; dup--, ip += part * 4) - Tuples_To_RGBA(ip, part, VAL_BLK_DATA(arg), part); - } else Trap_Type(arg); - - Reset_Height(value); - - if (action == A_APPEND) VAL_INDEX(value) = 0; - return value; + INCLUDE_PARAMS_OF_FIND; + + REBVAL *value = ARG(series); + REBVAL *arg = ARG(value); + REBCNT index = VAL_INDEX(value); + REBCNT tail = VAL_LEN_HEAD(value); + REBCNT *ip = (REBCNT *)VAL_IMAGE_DATA(value); // NOTE ints not bytes + REBCNT *p; + REBINT n; + + REBCNT len = tail - index; + if (len == 0) { + Init_Void(D_OUT); + return; + } + + // !!! There is a general problem with refinements and actions in R3-Alpha + // in terms of reporting when a refinement was ignored. This is a + // problem that archetype-based dispatch will need to address. + // + if ( + REF(case) + || REF(skip) + || REF(last) + || REF(match) + || REF(part) + || REF(reverse) + ){ + UNUSED(PAR(limit)); + UNUSED(PAR(size)); + fail (Error_Bad_Refines_Raw()); + } + + REBOOL only; // initialization would be crossed by goto + only = FALSE; + if (IS_TUPLE(arg)) { + only = LOGICAL(VAL_TUPLE_LEN(arg) < 4); + if (REF(only)) only = TRUE; + p = Find_Color(ip, TO_PIXEL_TUPLE(arg), len, only); + } + else if (IS_INTEGER(arg)) { + n = VAL_INT32(arg); + if (n < 0 || n > 255) fail (Error_Out_Of_Range(arg)); + p = Find_Alpha(ip, n, len); + } + else if (IS_IMAGE(arg)) { + p = 0; + } + else if (IS_BINARY(arg)) { + p = 0; + } + else + fail (Error_Invalid_Type(VAL_TYPE(arg))); + + if (p == 0) { + Init_Void(D_OUT); + return; + } + + // Post process the search (failure or apply /match and /tail): + + Move_Value(D_OUT, value); + n = (REBCNT)(p - (REBCNT *)VAL_IMAGE_HEAD(value)); + if (REF(match)) { + if (n != cast(REBINT, index)) { + Init_Void(D_OUT); + return; + } + n++; + } + else + if (REF(tail)) + ++n; + + VAL_INDEX(value) = n; + return; } -/*********************************************************************** -** -*/ REBVAL *Find_Image(REBVAL *ds) -/* -** Finds a value in a series and returns the series at the start of it. -** -** 1 image -** 2 value [any-type!] -** 3 /part {Limits the search to a given length or position.} -** 4 range [number! series! port!] -** 5 /only {ignore alpha value.} -** 6 /case - ignored -** 7 /any - ignored -** 8 /with - ignored -** 9 wild - ignored -** 10 /skip - ignored -** 11 size - ignored -** 12 /match {Performs comparison and returns the tail of the match.} -** 13 /tail {Returns the end of the string.} -** 14 /last {Backwards from end of string.} -** 15 /reverse {Backwards from the current position.} -** -***********************************************************************/ +// +// Image_Has_Alpha: C +// +// !!! See code in R3-Alpha for VITT_ALPHA and the `save` flag. +// +REBOOL Image_Has_Alpha(const REBVAL *v) { - REBVAL *value = D_ARG(1); - REBVAL *arg = D_ARG(2); - REBCNT index = VAL_INDEX(value); - REBCNT tail = VAL_TAIL(value); - REBCNT len; - REBCNT *ip = (REBCNT *)VAL_IMAGE_DATA(value); // NOTE ints not bytes - REBCNT *p; - REBINT n; - REBOOL only = FALSE; - REBYTE no_refs[10] = {5, 6, 7, 8, 9, 10, 13, 14}; // ref - 1 (invalid refinements) - - len = tail - index; - if (!len) goto find_none; - - for (n = 0; n < 8; n++) // (zero based) - if (D_REF((REBINT)no_refs[n])) - Trap0(RE_BAD_REFINE); -// Trap2(RE_CANNOT_USE, FRM_WORDS(me, (REBINT)no_refs[n]), Get_Global(REB_IMAGE)); - - if (IS_TUPLE(arg)) { - only = (REBOOL)(VAL_TUPLE_LEN(arg) < 4); - if (D_REF(5)) only = TRUE; // /only flag - p = Find_Color(ip, TO_COLOR_TUPLE(arg), len, only); - } else if (IS_INTEGER(arg)) { - n = VAL_INT32(arg); - if (n < 0 || n > 255) Trap_Range(arg); - p = Find_Alpha(ip, n, len); - } else if (IS_IMAGE(arg)) { - p = 0; - } else if (IS_BINARY(arg)) { - p = 0; - } else - Trap_Type(arg); - - // Post process the search (failure or apply /match and /tail): - if (p) { - n = (REBCNT)(p - (REBCNT *)VAL_IMAGE_HEAD(value)); - if (D_REF(11)) { // match - if (n != (REBINT)index) goto find_none; - n++; - } else if (D_REF(12)) n++; // /tail - index = n; - VAL_INDEX(value) = index; - return value; - } -find_none: - return NONE_VALUE; -} + REBCNT *p = cast(REBCNT*, VAL_IMAGE_HEAD(v)); + int i = VAL_IMAGE_WIDE(v) * VAL_IMAGE_HIGH(v); + for(; i > 0; i--) { + if (~*p++ & 0xff000000) + return TRUE; + } -/*********************************************************************** -** -*/ REBFLG Image_Has_Alpha(REBVAL *v, REBFLG save) -/* -***********************************************************************/ -{ - int i; - REBCNT *p; - -// if (VAL_IMAGE_TRANSP_TYPE(v)==VITT_NONE) return FALSE; -// if (VAL_IMAGE_TRANSP_TYPE(v)==VITT_ALPHA) return TRUE; - - p = (REBCNT *)VAL_IMAGE_HEAD(v); - i = VAL_IMAGE_WIDE(v)*VAL_IMAGE_HIGH(v); - for(; i > 0; i--) { - if (*p++ & 0xff000000) { -// if (save) VAL_IMAGE_TRANSP(v) = VITT_ALPHA; - return TRUE; - } - } -// if (save) VAL_IMAGE_TRANSP(v) = VITT_NONE; - - return FALSE; + return FALSE; } -/*********************************************************************** -** -*/ void Copy_Rect_Data(REBVAL *dst, REBINT dx, REBINT dy, REBINT w, REBINT h, REBVAL *src, REBINT sx, REBINT sy) -/* -***********************************************************************/ +// +// Copy_Rect_Data: C +// +void Copy_Rect_Data(REBVAL *dst, REBINT dx, REBINT dy, REBINT w, REBINT h, REBVAL *src, REBINT sx, REBINT sy) { - REBCNT *sbits, *dbits; + REBCNT *sbits, *dbits; - if (w <= 0 || h <= 0) return; + if (w <= 0 || h <= 0) return; - // Clip at edges: - if ((REBCNT)(dx + w) > VAL_IMAGE_WIDE(dst)) w = VAL_IMAGE_WIDE(dst) - dx; - if ((REBCNT)(dy + h) > VAL_IMAGE_HIGH(dst)) h = VAL_IMAGE_HIGH(dst) - dy; + // Clip at edges: + if ((REBCNT)(dx + w) > VAL_IMAGE_WIDE(dst)) w = VAL_IMAGE_WIDE(dst) - dx; + if ((REBCNT)(dy + h) > VAL_IMAGE_HIGH(dst)) h = VAL_IMAGE_HIGH(dst) - dy; - sbits = VAL_IMAGE_BITS(src) + sy * VAL_IMAGE_WIDE(src) + sx; - dbits = VAL_IMAGE_BITS(dst) + dy * VAL_IMAGE_WIDE(dst) + dx; - while (h--) { - memcpy(dbits, sbits, w*4); - sbits += VAL_IMAGE_WIDE(src); - dbits += VAL_IMAGE_WIDE(dst); - } + sbits = VAL_IMAGE_BITS(src) + sy * VAL_IMAGE_WIDE(src) + sx; + dbits = VAL_IMAGE_BITS(dst) + dy * VAL_IMAGE_WIDE(dst) + dx; + while (h--) { + memcpy(dbits, sbits, w*4); + sbits += VAL_IMAGE_WIDE(src); + dbits += VAL_IMAGE_WIDE(dst); + } } -#ifdef removed_feature -/*********************************************************************** -** -*/ static REBVAL* Xandor_Image(REBCNT action, REBVAL *value, REBVAL *arg) -/* -***********************************************************************/ -{ - REBCNT i; - REBCNT *p3; - REBCNT *p2 = (REBCNT*) VAL_IMAGE_BITS(value); - REBCNT *p1 = (REBCNT*) VAL_IMAGE_BITS(arg); - REBCNT tw, ow, aw; - REBCNT th, oh, ah; - REBCNT j; - - ow = VAL_IMAGE_WIDE(value); - oh = VAL_IMAGE_HIGH(value); - aw = VAL_IMAGE_WIDE(arg); - ah = VAL_IMAGE_HIGH(arg); - tw = MAX(ow, aw); - th = MAX(oh, ah); - - *DS_RETURN = *Make_Image(tw, th); - p3 = (REBCNT*) VAL_IMAGE_HEAD(DS_RETURN); - CLEAR_IMAGE(p3, tw, th); - - for (i = 0; i < th; i++) { - for (j = 0; j < tw; j++) { - if (j < ow && i < oh && j < aw && i < ah) - *(p3 + (i*tw) +j) = (REBCNT) (action == A_AND) ? - *(p2 + (i*ow) + j) & *(p1 + (i*aw) + j) : - (action == A_OR) ? - *(p2 + (i*ow) + j) | *(p1 + (i*aw) + j) : - *(p2 + (i*ow) + j) ^ *(p1 + (i*aw) + j) ; - else { - if (j < ow && i < oh) *(p3 + (i*tw) + j) = *(p2 + (i*ow) + j); - if (j < aw && i < ah) *(p3 + (i*tw) + j) = *(p1 + (i*aw) + j); - } - } - } - - return DS_RETURN; -} -#endif -/*********************************************************************** -** -*/ static REBSER *Complement_Image(REBVAL *value) -/* -***********************************************************************/ +// +// Complement_Image: C +// +static REBSER *Complement_Image(REBVAL *value) { - REBCNT *img = (REBCNT*) VAL_IMAGE_DATA(value); - REBCNT *out; - REBINT len = VAL_IMAGE_LEN(value); - REBSER *ser; + REBCNT *img = (REBCNT*) VAL_IMAGE_DATA(value); + REBCNT *out; + REBINT len = VAL_IMAGE_LEN(value); + REBSER *ser; - ser = Make_Image(VAL_IMAGE_WIDE(value), VAL_IMAGE_HIGH(value), TRUE); - out = (REBCNT*) IMG_DATA(ser); + ser = Make_Image(VAL_IMAGE_WIDE(value), VAL_IMAGE_HIGH(value), TRUE); + out = (REBCNT*) IMG_DATA(ser); - for (; len > 0; len --) *out++ = ~ *img++; + for (; len > 0; len --) *out++ = ~ *img++; - return ser; + return ser; } -/*********************************************************************** -** -*/ REBTYPE(Image) -/* -***********************************************************************/ +// +// REBTYPE: C +// +REBTYPE(Image) { - REBVAL *value = D_ARG(1); - REBVAL *arg = D_ARG(2); - REBSER *series = VAL_SERIES(value); - REBINT index = (REBINT)VAL_INDEX(value); - REBINT tail; - REBINT diff, len, w, h; - REBVAL *val; - - // Clip index if past tail: - if (action != A_MAKE && action != A_TO) { - tail = (REBINT)SERIES_TAIL(series); - if (index > tail) index = tail; - } - - // Check must be in this order (to avoid checking a non-series value); - if (action >= A_TAKE && action <= A_SORT && IS_PROTECT_SERIES(series)) - Trap0(RE_PROTECTED); - - // Dispatch action: - switch (action) { - - case A_HEAD: VAL_INDEX(value) = 0; break; - case A_TAIL: VAL_INDEX(value) = (REBCNT)tail; break; - case A_HEADQ: DECIDE(index == 0); - case A_TAILQ: DECIDE(index >= tail); - case A_NEXT: if (index < tail) VAL_INDEX(value)++; break; - case A_BACK: if (index > 0) VAL_INDEX(value)--; break; - -#ifdef removed_feature - case A_AND: - case A_OR: - case A_XOR: - if (IS_IMAGE(value) && IS_IMAGE(arg)) { - Xandor_Image(action, value, arg); // sets DS_RETURN - return R_RET; - } - else Trap_Action(VAL_TYPE(value), action); -#endif - - case A_COMPLEMENT: - series = Complement_Image(value); - SET_IMAGE(value, series); // use series var not func - break; - - case A_INDEXQ: - if (D_REF(2)) { - VAL_SET(D_RET, REB_PAIR); - VAL_PAIR_X(D_RET) = (REBD32)(index % VAL_IMAGE_WIDE(value)); - VAL_PAIR_Y(D_RET) = (REBD32)(index / VAL_IMAGE_WIDE(value)); - return R_RET; - } else { - DS_RET_INT(index + 1); - return R_RET; - } - case A_LENGTHQ: - DS_RET_INT(tail > index ? tail - index : 0); - return R_RET; - - case A_PICK: - Pick_Path(value, arg, 0); - return R_TOS; - - case A_POKE: - Pick_Path(value, arg, D_ARG(3)); - return R_ARG3; - - case A_SKIP: - case A_AT: - // This logic is somewhat complicated by the fact that INTEGER args use - // base-1 indexing, but PAIR args use base-0. - if (IS_PAIR(arg)) { - if (action == A_AT) action = A_SKIP; - diff = (VAL_PAIR_Y_INT(arg) * VAL_IMAGE_WIDE(value) + VAL_PAIR_X_INT(arg)) + - ((action == A_SKIP) ? 0 : 1); - } else - diff = Get_Num_Arg(arg); - - index += diff; - if (action == A_SKIP) { - if (IS_LOGIC(arg)) index--; - } else { - if (diff > 0) index--; // For at, pick, poke. - } - - if (index > tail) index = tail; - else if (index < 0) index = 0; - VAL_INDEX(value) = (REBCNT)index; - break; + REBVAL *value = D_ARG(1); + REBVAL *arg = D_ARGC > 1 ? D_ARG(2) : NULL; + REBSER *series; + REBINT index; + REBINT tail; + REBINT diff, len, w, h; + REBVAL *val; + + // Clip index if past tail: + series = VAL_SERIES(value); + index = VAL_INDEX(value); + tail = (REBINT)SER_LEN(series); + if (index > tail) index = tail; + + // Check must be in this order (to avoid checking a non-series value); + if (action >= SYM_TAKE_P && action <= SYM_SORT) + FAIL_IF_READ_ONLY_SERIES(series); + + // Dispatch action: + switch (action) { + + case SYM_HEAD_OF: + VAL_INDEX(value) = 0; + break; + + case SYM_TAIL_OF: + VAL_INDEX(value) = (REBCNT)tail; + break; + + case SYM_HEAD_Q: + return (index == 0) ? R_TRUE : R_FALSE; + + case SYM_TAIL_Q: + return (index >= tail) ? R_TRUE : R_FALSE; + + case SYM_COMPLEMENT: + series = Complement_Image(value); + Init_Image(value, series); // use series var not func + break; + + case SYM_INDEX_OF: { + INCLUDE_PARAMS_OF_INDEX_OF; + + UNUSED(PAR(series)); + + if (REF(xy)) { + SET_PAIR( + D_OUT, + index % VAL_IMAGE_WIDE(value), + index / VAL_IMAGE_WIDE(value) + ); + return R_OUT; + } + else { + Init_Integer(D_OUT, index + 1); + return R_OUT; + }} + // fallthrough + case SYM_LENGTH_OF: + Init_Integer(D_OUT, tail > index ? tail - index : 0); + return R_OUT; + + case SYM_SKIP: + case SYM_AT: + // This logic is somewhat complicated by the fact that INTEGER args use + // base-1 indexing, but PAIR args use base-0. + if (IS_PAIR(arg)) { + if (action == SYM_AT) action = SYM_SKIP; + diff = (VAL_PAIR_Y_INT(arg) * VAL_IMAGE_WIDE(value) + VAL_PAIR_X_INT(arg)) + + ((action == SYM_SKIP) ? 0 : 1); + } else + diff = Get_Num_From_Arg(arg); + + index += diff; + if (action == SYM_SKIP) { + if (IS_LOGIC(arg)) index--; + } else { + if (diff > 0) index--; // For at, pick, poke. + } + + if (index > tail) + index = tail; + else if (index < 0) + index = 0; + VAL_INDEX(value) = (REBCNT)index; + break; #ifdef obsolete - if (action == A_SKIP || action == A_AT) { - } - - if (diff == 0 || index < 0 || index >= tail) { - if (action == A_POKE) - Trap_Range(arg); - goto is_none; - } - - if (action == A_POKE) { - REBINT *dp = QUAD_SKIP(series, index)); - REBINT n; - - arg = D_ARG(3); - if (IS_TUPLE(arg) && (IS_IMAGE(value))) { - Set_Pixel_Tuple(QUAD_SKIP(series, index), arg); - //*dp = (long) (VAL_TUPLE_LEN(arg) < 4) ? - // ((*dp & 0xff000000) | (VAL_TUPLE(arg)[0] << 16) | (VAL_TUPLE(arg)[1] << 8) | (VAL_TUPLE(arg)[2])) : - // ((VAL_TUPLE(arg)[3] << 24) | (VAL_TUPLE(arg)[0] << 16) | (VAL_TUPLE(arg)[1] << 8) | (VAL_TUPLE(arg)[2])); - DS_RET_VALUE(arg); - return R_RET; - } - if (IS_INTEGER(arg) && VAL_INT64(arg) > 0 && VAL_INT64(arg) < 255) - n = VAL_INT32(arg); - else if (IS_CHAR(arg)) - n = VAL_CHAR(arg); - else - Trap_Arg(arg); - - *dp = (*dp & 0xffffff) | (n << 24); - DS_RET_VALUE(arg); - return R_RET; //was value; - - } else { - Set_Tuple_Pixel(QUAD_SKIP(series, index), D_RET); - return R_RET; - } - break; + if (action == A_SKIP || action == A_AT) { + } + + if (diff == 0 || index < 0 || index >= tail) { + if (action == A_POKE) + fail (Error_Out_Of_Range(arg)); + goto is_blank; + } + + if (action == A_POKE) { + REBINT *dp = QUAD_SKIP(series, index)); + REBINT n; + + arg = D_ARG(3); + if (IS_TUPLE(arg) && (IS_IMAGE(value))) { + Set_Pixel_Tuple(QUAD_SKIP(series, index), arg); + //*dp = (long) (VAL_TUPLE_LEN(arg) < 4) ? + // ((*dp & 0xff000000) | (VAL_TUPLE(arg)[0] << 16) | (VAL_TUPLE(arg)[1] << 8) | (VAL_TUPLE(arg)[2])) : + // ((VAL_TUPLE(arg)[3] << 24) | (VAL_TUPLE(arg)[0] << 16) | (VAL_TUPLE(arg)[1] << 8) | (VAL_TUPLE(arg)[2])); + Move_Value(D_OUT, arg); + return R_OUT; + } + if (IS_INTEGER(arg) && VAL_INT64(arg) > 0 && VAL_INT64(arg) < 255) + n = VAL_INT32(arg); + else if (IS_CHAR(arg)) + n = VAL_CHAR(arg); + else + fail (arg); + + *dp = (*dp & 0xffffff) | (n << 24); + Move_Value(D_OUT, arg); + return R_OUT; //was value; + + } else { + Set_Tuple_Pixel(QUAD_SKIP(series, index), D_OUT); + return R_OUT; + } + break; #endif - case A_CLEAR: // clear series - if (index < tail) { - VAL_TAIL(value) = (REBCNT)index; - Reset_Height(value); - } - break; - - case A_REMOVE: // remove series /part count - if (D_REF(2)) { - val = D_ARG(3); - if (IS_INTEGER(val)) { - len = VAL_INT32(val); - } else if (IS_IMAGE(val)) { - if (!VAL_IMAGE_WIDE(val)) Trap_Arg(val); - len = VAL_INDEX(val) - VAL_INDEX(value); // may not be same, is ok - } else - Trap_Type(val); - } else len = 1; - index = (REBINT)VAL_INDEX(value); - if (index < tail && len != 0) { - Remove_Series(series, VAL_INDEX(value), len); - } - Reset_Height(value); - break; - - case A_APPEND: - case A_INSERT: // insert ser val /part len /only /dup count - case A_CHANGE: // change ser val /part len /only /dup count - value = Modify_Image(ds, action); // sets DS_RETURN - break; - - case A_FIND: // find ser val /part len /only /case /any /with wild /match /tail - Find_Image(ds); // sets DS_RETURN - break; - - case A_TO: - if (IS_IMAGE(arg)) goto makeCopy; - else if (IS_GOB(arg)) { - //value = Make_Image(ROUND_TO_INT(GOB_W(VAL_GOB(arg))), ROUND_TO_INT(GOB_H(VAL_GOB(arg)))); - //*D_RET = *value; - series = OS_GOB_TO_IMAGE(VAL_GOB(arg)); - if (!series) Trap_Make(REB_IMAGE, arg); - SET_IMAGE(value, series); - break; - } - else if (IS_BINARY(arg)) { - diff = VAL_LEN(arg) / 4; - if (diff == 0) Trap_Make(REB_IMAGE, arg); - if (diff < 100) w = diff; - else if (diff < 10000) w = 100; - else w = 500; - h = diff / w; - if (w * h < diff) h++; // partial line - series = Make_Image(w, h, TRUE); - SET_IMAGE(value, series); - Bin_To_RGBA(IMG_DATA(series), w*h, VAL_BIN_DATA(arg), VAL_LEN(arg)/4, 0); - break; - } - Trap_Type(arg); - break; - - case A_MAKE: - // make image! img - if (IS_IMAGE(arg)) goto makeCopy; - - // make image! [] (or none) - if (IS_IMAGE(value) && (IS_NONE(arg) || (IS_BLOCK(arg) && (VAL_BLK_LEN(arg) == 0)))) { - arg = value; - goto makeCopy; - } - - // make image! size - if (IS_PAIR(arg)) { - w = VAL_PAIR_X_INT(arg); - h = VAL_PAIR_Y_INT(arg); - w = MAX(w, 0); - h = MAX(h, 0); - series = Make_Image(w, h, TRUE); - SET_IMAGE(value, series); - break; - } -// else if (IS_NONE(arg)) { -// *value = *Make_Image(0, 0); -// CLEAR_IMAGE(VAL_IMAGE_HEAD(value), 0, 0); -// break; -// } - // make image! [size rgb alpha index] - else if (IS_BLOCK(arg)) { - if (Create_Image(VAL_BLK_DATA(arg), value, 0)) break; - } - Trap_Type(arg); - break; - - case A_COPY: // copy series /part len - if (!D_REF(2)) { - arg = value; - goto makeCopy; - } - arg = D_ARG(3); // can be image, integer, pair. - if (IS_IMAGE(arg)) { - if (VAL_SERIES(arg) != VAL_SERIES(value)) Trap_Arg(arg); - len = VAL_INDEX(arg) - VAL_INDEX(value); - arg = value; - goto makeCopy2; - } - if (IS_INTEGER(arg)) { - len = VAL_INT32(arg); - arg = value; - goto makeCopy2; - } - if (IS_PAIR(arg)) { - w = VAL_PAIR_X_INT(arg); - h = VAL_PAIR_Y_INT(arg); - w = MAX(w, 0); - h = MAX(h, 0); - diff = MIN(VAL_TAIL(value), VAL_INDEX(value)); // index offset - diff = MAX(0, diff); - index = VAL_IMAGE_WIDE(value); // width - if (index) { - len = diff / index; // compute y offset - diff %= index; // compute x offset - } else len = diff = 0; // avoid div zero - w = MIN(w, index - diff); // img-width - x-pos - h = MIN(h, (int)(VAL_IMAGE_HIGH(value) - len)); // img-high - y-pos - series = Make_Image(w, h, TRUE); - SET_IMAGE(D_RET, series); - Copy_Rect_Data(D_RET, 0, 0, w, h, value, diff, len); -// VAL_IMAGE_TRANSP(D_RET) = VAL_IMAGE_TRANSP(value); - return R_RET; - } - Trap_Type(arg); + case SYM_CLEAR: // clear series + if (index < tail) { + SET_SERIES_LEN(VAL_SERIES(value), cast(REBCNT, index)); + Reset_Height(value); + } + break; + + case SYM_REMOVE: { + INCLUDE_PARAMS_OF_REMOVE; + + UNUSED(PAR(series)); + + if (REF(map)) { + UNUSED(ARG(key)); + fail (Error_Bad_Refines_Raw()); + } + + if (REF(part)) { + val = ARG(limit); + if (IS_INTEGER(val)) { + len = VAL_INT32(val); + } + else if (IS_IMAGE(val)) { + if (!VAL_IMAGE_WIDE(val)) + fail (val); + len = VAL_INDEX(val) - VAL_INDEX(value); // not same is ok + } + else + fail (Error_Invalid_Type(VAL_TYPE(val))); + } + else len = 1; + + index = (REBINT)VAL_INDEX(value); + if (index < tail && len != 0) { + Remove_Series(series, VAL_INDEX(value), len); + } + Reset_Height(value); + break; } + + case SYM_APPEND: + case SYM_INSERT: // insert ser val /part len /only /dup count + case SYM_CHANGE: // change ser val /part len /only /dup count + value = Modify_Image(frame_, action); // sets DS_OUT + break; + + case SYM_FIND: + Find_Image(frame_); // sets DS_OUT + return R_OUT; + + case SYM_COPY: { + INCLUDE_PARAMS_OF_COPY; + + UNUSED(PAR(value)); + + if (REF(deep)) + fail (Error_Bad_Refines_Raw()); + + if (REF(types)) { + UNUSED(ARG(kinds)); + fail (Error_Bad_Refines_Raw()); + } + + if (NOT(REF(part))) { + arg = value; + goto makeCopy; + } + arg = ARG(limit); // can be image, integer, pair. + if (IS_IMAGE(arg)) { + if (VAL_SERIES(arg) != VAL_SERIES(value)) + fail (arg); + len = VAL_INDEX(arg) - VAL_INDEX(value); + arg = value; + goto makeCopy2; + } + if (IS_INTEGER(arg)) { + len = VAL_INT32(arg); + arg = value; + goto makeCopy2; + } + if (IS_PAIR(arg)) { + w = VAL_PAIR_X_INT(arg); + h = VAL_PAIR_Y_INT(arg); + w = MAX(w, 0); + h = MAX(h, 0); + diff = MIN(VAL_LEN_HEAD(value), VAL_INDEX(value)); // index offset + diff = MAX(0, diff); + index = VAL_IMAGE_WIDE(value); // width + if (index) { + len = diff / index; // compute y offset + diff %= index; // compute x offset + } else len = diff = 0; // avoid div zero + w = MIN(w, index - diff); // img-width - x-pos + h = MIN(h, (int)(VAL_IMAGE_HIGH(value) - len)); // img-high - y-pos + series = Make_Image(w, h, TRUE); + Init_Image(D_OUT, series); + Copy_Rect_Data(D_OUT, 0, 0, w, h, value, diff, len); +// VAL_IMAGE_TRANSP(D_OUT) = VAL_IMAGE_TRANSP(value); + return R_OUT; + } + fail (Error_Invalid_Type(VAL_TYPE(arg))); makeCopy: - // Src image is arg. - len = VAL_IMAGE_LEN(arg); + // Src image is arg. + len = VAL_IMAGE_LEN(arg); makeCopy2: - len = MAX(len, 0); // no negatives - len = MIN(len, (REBINT)VAL_IMAGE_LEN(arg)); - w = VAL_IMAGE_WIDE(arg); - w = MAX(w, 1); - if (len <= w) h = 1, w = len; - else h = len / w; - if (w == 0) h = 0; - series = Make_Image(w, h, TRUE); - SET_IMAGE(D_RET, series); - memcpy(VAL_IMAGE_HEAD(D_RET), VAL_IMAGE_DATA(arg), w * h * 4); -// VAL_IMAGE_TRANSP(D_RET) = VAL_IMAGE_TRANSP(arg); - return R_RET; - break; - - default: - Trap_Action(VAL_TYPE(value), action); - } - - *DS_RETURN = *value; - return R_RET; - -is_false: - return R_FALSE; - -is_true: - return R_TRUE; + Copy_Image_Value(D_OUT, arg, len); + return R_OUT; } + + default: + fail (Error_Illegal_Action(VAL_TYPE(value), action)); + } + + Move_Value(D_OUT, value); + return R_OUT; +} + + +inline static REBOOL Adjust_Image_Pick_Index_Is_Valid( + REBINT *index, // gets adjusted + const REBVAL *value, // image + const REBVAL *picker +) { + REBINT n; + if (IS_PAIR(picker)) { + n = ( + VAL_PAIR_Y_INT(picker) * VAL_IMAGE_WIDE(value) + + VAL_PAIR_X_INT(picker) + ) + 1; + } + else if (IS_INTEGER(picker)) + n = VAL_INT32(picker); + else if (IS_DECIMAL(picker)) + n = cast(REBINT, VAL_DECIMAL(picker)); + else if (IS_LOGIC(picker)) + n = VAL_LOGIC(picker) ? 1 : 2; + else + fail (picker); + + *index += n; + if (n > 0) + (*index)--; + + if (n == 0 || *index < 0 || *index >= cast(REBINT, VAL_LEN_HEAD(value))) + return FALSE; // out of range + + return TRUE; +} + + +// +// Pick_Image: C +// +void Pick_Image(REBVAL *out, const REBVAL *value, const REBVAL *picker) +{ + REBSER *series = VAL_SERIES(value); + + REBINT index = cast(REBINT, VAL_INDEX(value)); + REBINT len = VAL_LEN_HEAD(value) - index; + len = MAX(len, 0); + + REBYTE *src = VAL_IMAGE_DATA(value); + + if (IS_WORD(picker)) { + switch (VAL_WORD_SYM(picker)) { + case SYM_SIZE: + SET_PAIR( + out, + VAL_IMAGE_WIDE(value), + VAL_IMAGE_HIGH(value) + ); + break; + + case SYM_RGB: { + REBSER *nser = Make_Binary(len * 3); + SET_SERIES_LEN(nser, len * 3); + RGB_To_Bin(QUAD_HEAD(nser), src, len, FALSE); + Init_Binary(out, nser); + break; } + + case SYM_ALPHA: { + REBSER *nser = Make_Binary(len); + SET_SERIES_LEN(nser, len); + Alpha_To_Bin(QUAD_HEAD(nser), src, len); + Init_Binary(out, nser); + break; } + + default: + fail (picker); + } + return; + } + + if (Adjust_Image_Pick_Index_Is_Valid(&index, value, picker)) + Set_Tuple_Pixel(QUAD_SKIP(series, index), out); + else + Init_Void(out); +} + + +// +// Poke_Image_Fail_If_Read_Only: C +// +void Poke_Image_Fail_If_Read_Only( + REBVAL *value, + const REBVAL *picker, + const REBVAL *poke +) { + REBSER *series = VAL_SERIES(value); + FAIL_IF_READ_ONLY_SERIES(series); + + REBINT index = cast(REBINT, VAL_INDEX(value)); + REBINT len = VAL_LEN_HEAD(value) - index; + len = MAX(len, 0); + + REBYTE *src = VAL_IMAGE_DATA(value); + + if (IS_WORD(picker)) { + switch (VAL_WORD_SYM(picker)) { + case SYM_SIZE: + if (!IS_PAIR(poke) || !VAL_PAIR_X(poke)) + fail (poke); + + VAL_IMAGE_WIDE(value) = VAL_PAIR_X_INT(poke); + VAL_IMAGE_HIGH(value) = MIN( + VAL_PAIR_Y_INT(poke), + cast(REBINT, VAL_LEN_HEAD(value) / VAL_PAIR_X_INT(poke)) + ); + break; + + case SYM_RGB: + if (IS_TUPLE(poke)) { + Fill_Line( + cast(REBCNT*, src), TO_PIXEL_TUPLE(poke), len, TRUE + ); + } else if (IS_INTEGER(poke)) { + REBINT byte = VAL_INT32(poke); + if (byte < 0 || byte > 255) + fail (Error_Out_Of_Range(poke)); + + Fill_Line( + cast(REBCNT*, src), + TO_PIXEL_COLOR(byte, byte, byte, 0xFF), + len, + TRUE + ); + } + else if (IS_BINARY(poke)) { + Bin_To_RGB( + src, + len, + VAL_BIN_AT(poke), + VAL_LEN_AT(poke) / 3 + ); + } + else + fail (poke); + break; + + case SYM_ALPHA: + if (IS_INTEGER(poke)) { + REBINT n = VAL_INT32(poke); + if (n < 0 || n > 255) + fail (Error_Out_Of_Range(poke)); + + Fill_Alpha_Line(src, cast(REBYTE, n), len); + } + else if (IS_BINARY(poke)) { + Bin_To_Alpha( + src, + len, + VAL_BIN_AT(poke), + VAL_LEN_AT(poke) + ); + } + else + fail (poke); + break; + + default: + fail (picker); + } + return; + } + + if (!Adjust_Image_Pick_Index_Is_Valid(&index, value, picker)) + fail (Error_Out_Of_Range(picker)); + + if (IS_TUPLE(poke)) { // set whole pixel + Set_Pixel_Tuple(QUAD_SKIP(series, index), poke); + return; + } + + // set the alpha only + + REBINT alpha; + if ( + IS_INTEGER(poke) + && VAL_INT64(poke) > 0 + && VAL_INT64(poke) < 255 + ) { + alpha = VAL_INT32(poke); + } + else if (IS_CHAR(poke)) + alpha = VAL_CHAR(poke); + else + fail (Error_Out_Of_Range(poke)); + + REBCNT *dp = cast(REBCNT*, QUAD_SKIP(series, index)); + *dp = (*dp & 0xffffff) | (alpha << 24); } -/*********************************************************************** -** -*/ REBINT PD_Image(REBPVS *pvs) -/* -***********************************************************************/ +// +// PD_Image: C +// +REBINT PD_Image(REBPVS *pvs) { - REBVAL *data = pvs->value; - REBVAL *sel = pvs->select; - REBVAL *val = pvs->setval; - REBINT n; - REBINT len; - REBYTE *src; - REBINT index = (REBINT)VAL_INDEX(data); - REBSER *nser; - REBSER *series = VAL_SERIES(data); - REBCNT *dp; - - len = VAL_TAIL(data) - index; - len = MAX(len, 0); - src = VAL_IMAGE_DATA(data); - - if (IS_PAIR(sel)) n = (VAL_PAIR_Y_INT(sel) * VAL_IMAGE_WIDE(data) + VAL_PAIR_X_INT(sel)) + 1; - else if (IS_INTEGER(sel)) n = VAL_INT32(sel); - else if (IS_DECIMAL(sel)) n = (REBINT)VAL_DECIMAL(sel); - else if (IS_LOGIC(sel)) n = (VAL_LOGIC(sel) ? 1 : 2); - else if (IS_WORD(sel)) { - if (val == 0) { - val = pvs->value = pvs->store; - switch (VAL_WORD_CANON(sel)) { - - case SYM_SIZE: - VAL_SET(val, REB_PAIR); - VAL_PAIR_X(val) = (REBD32)VAL_IMAGE_WIDE(data); - VAL_PAIR_Y(val) = (REBD32)VAL_IMAGE_HIGH(data); - break; - - case SYM_RGB: - nser = Make_Binary(len * 3); - SERIES_TAIL(nser) = len * 3; - RGB_To_Bin(QUAD_HEAD(nser), src, len, FALSE); - Set_Binary(val, nser); - break; - - case SYM_ALPHA: - nser = Make_Binary(len); - SERIES_TAIL(nser) = len; - Alpha_To_Bin(QUAD_HEAD(nser), src, len); - Set_Binary(val, nser); - break; - - default: - return PE_BAD_SELECT; - } - return PE_OK; - - } else { - - switch (VAL_WORD_CANON(sel)) { - - case SYM_SIZE: - if (!IS_PAIR(val) || !VAL_PAIR_X(val)) return PE_BAD_SET; - VAL_IMAGE_WIDE(data) = VAL_PAIR_X_INT(val); - VAL_IMAGE_HIGH(data) = MIN(VAL_PAIR_Y_INT(val), (REBINT)VAL_TAIL(data) / VAL_PAIR_X_INT(val)); - break; - - case SYM_RGB: - if (IS_TUPLE(val)) { - Fill_Line((REBCNT *)src, TO_COLOR_TUPLE(val), len, 1); - } else if (IS_INTEGER(val)) { - n = VAL_INT32(val); - if (n < 0 || n > 255) return PE_BAD_RANGE; - Fill_Line((REBCNT *)src, TO_COLOR(n,n,n,0), len, 1); - } else if (IS_BINARY(val)) { - Bin_To_RGB(src, len, VAL_BIN_DATA(val), VAL_LEN(val) / 3); - } else return PE_BAD_SET; - break; - - case SYM_ALPHA: - if (IS_INTEGER(val)) { - n = VAL_INT32(val); - if (n < 0 || n > 255) return PE_BAD_RANGE; - Fill_Alpha_Line(src, (REBYTE)n, len); - } else if (IS_BINARY(val)) { - Bin_To_Alpha(src, len, VAL_BIN_DATA(val), VAL_LEN(val)); - } else return PE_BAD_SET; - break; - - default: - return PE_BAD_SELECT; - } - return PE_OK; - } - } - else return PE_BAD_SELECT; - - // Handle index path: - index += n; - if (n > 0) index--; - - TRAP_PROTECT(series); - - // Out of range: - if (n == 0 || index < 0 || index >= (REBINT)series->tail) { - if (val) return PE_BAD_SET; - return PE_NONE; - } - - // Get the pixel: - if (val == 0) { - Set_Tuple_Pixel(QUAD_SKIP(series, index), pvs->store); - return PE_USE; - } - - // Set the pixel: - if (IS_TUPLE(val) && (IS_IMAGE(data))) { - Set_Pixel_Tuple(QUAD_SKIP(series, index), val); - //*dp = (long) (VAL_TUPLE_LEN(val) < 4) ? - // ((*dp & 0xff000000) | (VAL_TUPLE(val)[0] << 16) | (VAL_TUPLE(val)[1] << 8) | (VAL_TUPLE(val)[2])) : - // ((VAL_TUPLE(val)[3] << 24) | (VAL_TUPLE(val)[0] << 16) | (VAL_TUPLE(val)[1] << 8) | (VAL_TUPLE(val)[2])); - return PE_OK; - } - - // Set the alpha only: - if (IS_INTEGER(val) && VAL_INT64(val) > 0 && VAL_INT64(val) < 255) n = VAL_INT32(val); - else if (IS_CHAR(val)) n = VAL_CHAR(val); - else return PE_BAD_RANGE; - - dp = (REBCNT*)QUAD_SKIP(series, index); - *dp = (*dp & 0xffffff) | (n << 24); - return PE_OK; + if (pvs->opt_setval) { + Poke_Image_Fail_If_Read_Only( + KNOWN(pvs->value), pvs->picker, pvs->opt_setval + ); + return PE_OK; + } + + Pick_Image(pvs->store, KNOWN(pvs->value), pvs->picker); + return PE_USE_STORE; } diff --git a/src/core/t-integer.c b/src/core/t-integer.c index 2585633a2d..0bd24ed02a 100644 --- a/src/core/t-integer.c +++ b/src/core/t-integer.c @@ -1,293 +1,580 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-integer.c -** Summary: integer datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-integer.c +// Summary: "integer datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" #include "sys-deci-funcs.h" +#include "sys-int-funcs.h" -/*********************************************************************** -** -*/ REBINT CT_Integer(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Integer: C +// +REBINT CT_Integer(const RELVAL *a, const RELVAL *b, REBINT mode) { - if (mode >= 0) return (VAL_INT64(a) == VAL_INT64(b)); - if (mode == -1) return (VAL_INT64(a) >= VAL_INT64(b)); - return (VAL_INT64(a) > VAL_INT64(b)); + if (mode >= 0) return (VAL_INT64(a) == VAL_INT64(b)); + if (mode == -1) return (VAL_INT64(a) >= VAL_INT64(b)); + return (VAL_INT64(a) > VAL_INT64(b)); } -/*********************************************************************** -** -*/ REBTYPE(Integer) -/* -***********************************************************************/ +// +// MAKE_Integer: C +// +void MAKE_Integer(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBVAL *val = D_ARG(1); - REBVAL *val2 = D_ARG(2); - REBI64 num; - REBI64 arg; - REBINT n; - - REBU64 p, a, b; // for overflow detection - REBCNT a1, a0, b1, b0; - REBFLG sgn; - REBI64 anum; - - num = VAL_INT64(val); - - if (IS_BINARY_ACT(action)) { - - if (IS_INTEGER(val2)) arg = VAL_INT64(val2); - else if (IS_CHAR(val2)) arg = VAL_CHAR(val2); - else { - // Decimal or other numeric second argument: - n = 0; // use to flag special case - switch(action) { - // Anything added to an integer is same as adding the integer: - case A_ADD: - case A_MULTIPLY: - // Swap parameter order: - *D_RET = *val2; // Use as temp workspace - *val2 = *val; - *val = *D_RET; - return Value_Dispatch[VAL_TYPE(val)](ds, action); - - // Only type valid to subtract from, divide into, is decimal/money: - case A_SUBTRACT: - n = 1; - case A_DIVIDE: - case A_REMAINDER: - case A_POWER: - if (IS_DECIMAL(val2) | IS_PERCENT(val2)) { - SET_DECIMAL(val, (REBDEC)num); // convert main arg - return T_Decimal(ds, action); - } - if (IS_MONEY(val2)) { - VAL_DECI(val) = int_to_deci(VAL_INT64(val)); - VAL_SET(val, REB_MONEY); - return T_Money(ds, action); - } - if (n > 0) { - if (IS_TIME(val2)) { - VAL_TIME(val) = SEC_TIME(VAL_INT64(val)); - SET_TYPE(val, REB_TIME); - return T_Time(ds, action); - } - if (IS_DATE(val2)) return T_Date(ds, action); - } - } - Trap_Math_Args(REB_INTEGER, action); - } - } - - switch (action) { - - case A_ADD: - anum = (REBU64)num + (REBU64)arg; - if ( - ((num < 0) == (arg < 0)) && ((num < 0) != (anum < 0)) - ) Trap0(RE_OVERFLOW); - num = anum; - break; - - case A_SUBTRACT: - anum = (REBU64)num - (REBU64)arg; - if ( - ((num < 0) != (arg < 0)) && ((num < 0) != (anum < 0)) - ) Trap0(RE_OVERFLOW); - num = anum; - break; - - case A_MULTIPLY: - a = num; - sgn = (num < 0); - if (sgn) a = -a; - b = arg; - if (arg < 0) { - sgn = !sgn; - b = -b; - } - p = a * b; - a1 = a>>32; - a0 = a; - b1 = b>>32; - b0 = b; - if ( - (a1 && b1) - || ((REBU64)a0 * b1 + (REBU64)a1 * b0 > p >> 32) - || ((p > (REBU64)MAX_I64) && (!sgn || (p > -(REBU64)MIN_I64))) - ) Trap0(RE_OVERFLOW); - num = sgn ? -p : p; - break; - - case A_DIVIDE: - if (arg == 0) Trap0(RE_ZERO_DIVIDE); - if (num == MIN_I64 && arg == -1) Trap0(RE_OVERFLOW); - if (num % arg == 0) { - num = num / arg; - break; - } - // Fall thru - - case A_POWER: - SET_DECIMAL(val, (REBDEC)num); - SET_DECIMAL(val2, (REBDEC)arg); - return T_Decimal(ds, action); - - case A_REMAINDER: - if (arg == 0) Trap0(RE_ZERO_DIVIDE); - num = REM2(num, arg); - break; - - case A_AND: num &= arg; break; - case A_OR: num |= arg; break; - case A_XOR: num ^= arg; break; - - case A_NEGATE: - if (num == MIN_I64) Trap0(RE_OVERFLOW); - num = -num; - break; - - case A_COMPLEMENT: num = ~num; break; - - case A_ABSOLUTE: - if (num == MIN_I64) Trap0(RE_OVERFLOW); - if (num < 0) num = -num; - break; - - case A_EVENQ: num = ~num; - case A_ODDQ: DECIDE(num & 1); - - case A_ROUND: - val2 = D_ARG(3); - n = Get_Round_Flags(ds); - if (D_REF(2)) { // to - if (IS_MONEY(val2)) { - VAL_DECI(D_RET) = Round_Deci(int_to_deci(num), n, VAL_DECI(val2)); - SET_TYPE(D_RET, REB_MONEY); - return R_RET; - } - if (IS_DECIMAL(val2) || IS_PERCENT(val2)) { - VAL_DECIMAL(D_RET) = Round_Dec((REBDEC)num, n, VAL_DECIMAL(val2)); - SET_TYPE(D_RET, VAL_TYPE(val2)); - return R_RET; - } - if (IS_TIME(val2)) Trap_Arg(val2); - arg = VAL_INT64(val2); - } - else arg = 0L; - num = Round_Int(num, n, arg); - break; - - case A_RANDOM: - if (D_REF(2)) { // seed - Set_Random(num); - return R_UNSET; - } - if (num == 0) break; - num = Random_Range(num, (REBOOL)D_REF(3)); //!!! 64 bits -#ifdef OLD_METHOD - if (num < 0) num = -(1 + (REBI64)(arg % -num)); - else num = 1 + (REBI64)(arg % num); -#endif - break; - - case A_MAKE: - case A_TO: - val = D_ARG(2); - if (IS_DECIMAL(val) || IS_PERCENT(val)) { - if (VAL_DECIMAL(val) < MIN_D64 || VAL_DECIMAL(val) >= MAX_D64) - Trap0(RE_OVERFLOW); - num = (REBI64)VAL_DECIMAL(val); - } - else if (IS_INTEGER(val)) - num = VAL_INT64(val); - else if (IS_MONEY(val)) - num = deci_to_int(VAL_DECI(val)); - else if (IS_ISSUE(val)) { - REBYTE *bp; - REBCNT len; - bp = Get_Word_Name(val); - len = strlen(bp); - n = MIN(MAX_HEX_LEN, len); - if (Scan_Hex(bp, &num, n, n) == 0) goto is_bad; - } - else if (IS_BINARY(val)) { // must be before STRING! - REBYTE *bp; - n = VAL_LEN(val); - if (n > sizeof(REBI64)) n = sizeof(REBI64); - num = 0; - for (bp = VAL_BIN_DATA(val); n; n--, bp++) - num = (num << 8) | *bp; - } - else if (ANY_STR(val)) { - REBYTE *bp; - REBCNT len; - bp = Qualify_String(val, MAX_INT_LEN, &len, FALSE); - if (memchr(bp, '.', len)) { - if (Scan_Decimal(bp, len, DS_RETURN, TRUE)) { - num = (REBINT)VAL_DECIMAL(DS_RETURN); - break; - } - } - if (Scan_Integer(bp, len, DS_RETURN)) - return R_RET; - goto is_bad; - } - else if (IS_LOGIC(val)) { - // No integer is uniquely representative of true, so TO conversions reject - // integer-to-logic conversions. MAKE is more liberal and constructs true - // to 1 and false to 0. - if (action != A_MAKE) goto is_bad; - num = VAL_LOGIC(val) ? 1 : 0; - } - else if (IS_CHAR(val)) - num = VAL_CHAR(val); - // else if (IS_NONE(val)) num = 0; - else if (IS_TIME (val)) num = SECS_IN(VAL_TIME(val)); - else goto is_bad; - break; - - default: - Trap_Action(REB_INTEGER, action); - } - - SET_INTEGER(DS_RETURN, num); - return R_RET; - -is_bad: - Trap_Make(REB_INTEGER, val); - -is_false: - return R_FALSE; - -is_true: - return R_TRUE; + assert(kind == REB_INTEGER); + UNUSED(kind); + + if (IS_LOGIC(arg)) { + // + // !!! Due to Rebol's policies on conditional truth and falsehood, + // it refuses to say TO FALSE is 0. MAKE has shades of meaning + // that are more "dialected", e.g. MAKE BLOCK! 10 creates a block + // with capacity 10 and not literally `[10]` (or a block with ten + // NONE! values in it). Under that liberal umbrella it decides + // that it will make an integer 0 out of FALSE due to it having + // fewer seeming "rules" than TO would. + + if (VAL_LOGIC(arg)) + Init_Integer(out, 1); + else + Init_Integer(out, 0); + + // !!! The same principle could suggest MAKE is not bound by + // the "reversibility" requirement and hence could interpret + // binaries unsigned by default. Before getting things any + // weirder should probably leave it as is. + } + else { + // use signed logic by default (use TO-INTEGER/UNSIGNED to force + // unsigned interpretation or error if that doesn't make sense) + + Value_To_Int64(out, arg, FALSE); + } +} + + +// +// TO_Integer: C +// +void TO_Integer(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) +{ + assert(kind == REB_INTEGER); + UNUSED(kind); + + // use signed logic by default (use TO-INTEGER/UNSIGNED to force + // unsigned interpretation or error if that doesn't make sense) + + Value_To_Int64(out, arg, FALSE); +} + + +// +// Value_To_Int64: C +// +// Interpret `value` as a 64-bit integer and return it in `out`. +// +// If `no_sign` is TRUE then use that to inform an ambiguous conversion +// (e.g. TO-INTEGER/UNSIGNED #{FF} is 255 instead of -1). However, it +// won't contradict the sign of unambiguous source. So the string "-1" +// will raise an error if you try to convert it unsigned. (For this, +// use `abs to-integer "-1"` and not `to-integer/unsigned "-1"`.) +// +// Because Rebol's INTEGER! uses a signed REBI64 and not an unsigned +// REBU64, a request for unsigned interpretation is limited to using +// 63 of those bits. A range error will be thrown otherwise. +// +// If a type is added or removed, update REBNATIVE(to_integer)'s spec +// +void Value_To_Int64(REBVAL *out, const REBVAL *value, REBOOL no_sign) +{ + // !!! Code extracted from REBTYPE(Integer)'s A_MAKE and A_TO cases + // Use SWITCH instead of IF chain? (was written w/ANY_STR test) + + if (IS_INTEGER(value)) { + Move_Value(out, value); + goto check_sign; + } + if (IS_DECIMAL(value) || IS_PERCENT(value)) { + if (VAL_DECIMAL(value) < MIN_D64 || VAL_DECIMAL(value) >= MAX_D64) + fail (Error_Overflow_Raw()); + + Init_Integer(out, cast(REBI64, VAL_DECIMAL(value))); + goto check_sign; + } + else if (IS_MONEY(value)) { + Init_Integer(out, deci_to_int(VAL_MONEY_AMOUNT(value))); + goto check_sign; + } + else if (IS_BINARY(value)) { // must be before ANY_STRING() test... + + // Rebol3 creates 8-byte big endian for signed 64-bit integers. + // Rebol2 created 4-byte big endian for signed 32-bit integers. + // + // Values originating in file formats from other systems vary widely. + // Note that in C the default interpretation of single bytes in most + // implementations of a `char` is signed. + // + // We assume big-Endian for decoding (clients can REVERSE if they + // want little-Endian). Also by default assume that any missing + // sign-extended to 64-bits based on the most significant byte + // + // #{01020304} => #{0000000001020304} + // #{DECAFBAD} => #{FFFFFFFFDECAFBAD} + // + // To override this interpretation and always generate an unsigned + // result, pass in `no_sign`. (Used by TO-INTEGER/UNSIGNED) + // + // If under these rules a number cannot be represented within the + // numeric range of the system's INTEGER!, it will error. This + // attempts to "future-proof" for other integer sizes and as an + // interface could support BigNums in the future. + + REBYTE *bp = VAL_BIN_AT(value); + REBCNT n = VAL_LEN_AT(value); + REBOOL negative; + REBINT fill; + + #if !defined(NDEBUG) + // + // This is what R3-Alpha did. + // + if (LEGACY(OPTIONS_FOREVER_64_BIT_INTS)) { + REBI64 i = 0; + if (n > sizeof(REBI64)) n = sizeof(REBI64); + for (; n; n--, bp++) + i = cast(REBI64, (cast(REBU64, i) << 8) | *bp); + + Init_Integer(out, i); + + // There was no TO-INTEGER/UNSIGNED in R3-Alpha, so even if + // running in compatibility mode we can check the sign if used. + // + goto check_sign; + } + #endif + + if (n == 0) { + // + // !!! Should #{} empty binary be 0 or error? (Historically, 0) + // + Init_Integer(out, 0); + return; + } + + // default signedness interpretation to high-bit of first byte, but + // override if the function was called with `no_sign` + // + negative = no_sign ? FALSE : LOGICAL(*bp >= 0x80); + + // Consume any leading 0x00 bytes (or 0xFF if negative) + // + while (n != 0 && *bp == (negative ? 0xFF : 0x00)) { + ++bp; + --n; + } + + // If we were consuming 0xFFs and passed to a byte that didn't have + // its high bit set, we overstepped our bounds! Go back one. + // + if (negative && n > 0 && *bp < 0x80) { + --bp; + ++n; + } + + // All 0x00 bytes must mean 0 (or all 0xFF means -1 if negative) + // + if (n == 0) { + if (negative) { + assert(!no_sign); + Init_Integer(out, -1); + } else + Init_Integer(out, 0); + return; + } + + // Not using BigNums (yet) so max representation is 8 bytes after + // leading 0x00 or 0xFF stripped away + // + if (n > 8) + fail (Error_Out_Of_Range_Raw(value)); + + REBI64 i = 0; + + // Pad out to make sure any missing upper bytes match sign + for (fill = n; fill < 8; fill++) + i = cast(REBI64, + (cast(REBU64, i) << 8) | (negative ? 0xFF : 0x00) + ); + + // Use binary data bytes to fill in the up-to-8 lower bytes + // + while (n != 0) { + i = cast(REBI64, (cast(REBU64, i) << 8) | *bp); + bp++; + n--; + } + + if (no_sign && i < 0) { + // + // bits may become signed via shift due to 63-bit limit + // + fail (Error_Out_Of_Range_Raw(value)); + } + + Init_Integer(out, i); + return; + } + else if (IS_ISSUE(value)) { + // + // Like converting a binary, except uses a string of codepoints + // from the word name conversion. Does not allow for signed + // interpretations, e.g. #FFFF => 65535, not -1. Unsigned makes + // more sense as these would be hexes likely typed in by users, + // who rarely do 2s-complement math in their head. + + const REBYTE *bp = VAL_WORD_HEAD(value); + REBCNT len = LEN_BYTES(bp); + + if (len > MAX_HEX_LEN) { + // Lacks BINARY!'s accommodation of leading 00s or FFs + fail (Error_Out_Of_Range_Raw(value)); + } + + if (!Scan_Hex(out, bp, len, len)) + fail (Error_Bad_Make(REB_INTEGER, value)); + + // !!! Unlike binary, always assumes unsigned (should it?). Yet still + // might run afoul of 64-bit range limit. + // + if (VAL_INT64(out) < 0) + fail (Error_Out_Of_Range_Raw(value)); + + return; + } + else if (ANY_STRING(value)) { + REBCNT len; + REBYTE *bp = Temp_Byte_Chars_May_Fail( + value, VAL_LEN_AT(value), &len, FALSE + ); + if ( + memchr(bp, '.', len) + || memchr(bp, 'e', len) + || memchr(bp, 'E', len) + ) { + DECLARE_LOCAL (d); + if (Scan_Decimal(d, bp, len, TRUE)) { + if (VAL_DECIMAL(d) < MAX_I64 && VAL_DECIMAL(d) >= MIN_I64) { + Init_Integer(out, cast(REBI64, VAL_DECIMAL(d))); + goto check_sign; + } + + fail (Error_Overflow_Raw()); + } + } + if (Scan_Integer(out, bp, len)) + goto check_sign; + + fail (Error_Bad_Make(REB_INTEGER, value)); + } + else if (IS_LOGIC(value)) { + // + // Rebol's choice is that no integer is uniquely representative of + // "falsehood" condition, e.g. `if 0 [print "this prints"]`. So to + // say TO FALSE is 0 would be disingenuous. + // + fail (Error_Bad_Make(REB_INTEGER, value)); + } + else if (IS_CHAR(value)) { + Init_Integer(out, VAL_CHAR(value)); // always unsigned + return; + } + else if (IS_TIME(value)) { + Init_Integer(out, SECS_FROM_NANO(VAL_NANO(value))); // always unsigned + return; + } + else + fail (Error_Bad_Make(REB_INTEGER, value)); + +check_sign: + if (no_sign && VAL_INT64(out) < 0) + fail (Error_Positive_Raw()); +} + + +// +// to-integer: native [ +// +// {Synonym of TO INTEGER! when used without refinements, adds /UNSIGNED.} +// +// value [ +// integer! decimal! percent! money! char! time! +// issue! binary! any-string! +// ] +// /unsigned +// {For BINARY! interpret as unsigned, otherwise error if signed.} +// ] +// +REBNATIVE(to_integer) +{ + INCLUDE_PARAMS_OF_TO_INTEGER; + + Value_To_Int64(D_OUT, ARG(value), REF(unsigned)); + + return R_OUT; +} + + +// +// REBTYPE: C +// +REBTYPE(Integer) +{ + REBVAL *val = D_ARG(1); + REBVAL *val2 = D_ARGC > 1 ? D_ARG(2) : NULL; + + REBI64 arg; + + REBI64 num = VAL_INT64(val); + + // !!! This used to rely on IS_BINARY_ACT, which is no longer available + // in the symbol based dispatch. Consider doing another way. + // + if ( + action == SYM_ADD + || action == SYM_SUBTRACT + || action == SYM_MULTIPLY + || action == SYM_DIVIDE + || action == SYM_POWER + || action == SYM_AND_T + || action == SYM_OR_T + || action == SYM_XOR_T + || action == SYM_REMAINDER + ){ + if (IS_INTEGER(val2)) + arg = VAL_INT64(val2); + else if (IS_CHAR(val2)) + arg = VAL_CHAR(val2); + else { + // Decimal or other numeric second argument: + REBCNT n = 0; // use to flag special case + switch(action) { + // Anything added to an integer is same as adding the integer: + case SYM_ADD: + case SYM_MULTIPLY: + // Swap parameter order: + Move_Value(D_OUT, val2); // Use as temp workspace + Move_Value(val2, val); + Move_Value(val, D_OUT); + return Value_Dispatch[VAL_TYPE(val)](frame_, action); + + // Only type valid to subtract from, divide into, is decimal/money: + case SYM_SUBTRACT: + n = 1; + /* fall through */ + case SYM_DIVIDE: + case SYM_REMAINDER: + case SYM_POWER: + if (IS_DECIMAL(val2) || IS_PERCENT(val2)) { + Init_Decimal(val, (REBDEC)num); // convert main arg + return T_Decimal(frame_, action); + } + if (IS_MONEY(val2)) { + Init_Money(val, int_to_deci(VAL_INT64(val))); + return T_Money(frame_, action); + } + if (n > 0) { + if (IS_TIME(val2)) { + VAL_NANO(val) = SEC_TIME(VAL_INT64(val)); + VAL_SET_TYPE_BITS(val, REB_TIME); + return T_Time(frame_, action); + } + if (IS_DATE(val2)) + return T_Date(frame_, action); + } + + default: + break; + } + fail (Error_Math_Args(REB_INTEGER, action)); + } + } + else + arg = 0xDECAFBAD; // wasteful, but avoid maybe unassigned warning + + switch (action) { + + case SYM_COPY: + Move_Value(D_OUT, val); + return R_OUT; + + case SYM_ADD: { + REBI64 anum; + if (REB_I64_ADD_OF(num, arg, &anum)) + fail (Error_Overflow_Raw()); + num = anum; + break; } + + case SYM_SUBTRACT: { + REBI64 anum; + if (REB_I64_SUB_OF(num, arg, &anum)) + fail (Error_Overflow_Raw()); + num = anum; + break; } + + case SYM_MULTIPLY: { + REBI64 p; + if (REB_I64_MUL_OF(num, arg, &p)) + fail (Error_Overflow_Raw()); + num = p; + break; } + + case SYM_DIVIDE: + if (arg == 0) + fail (Error_Zero_Divide_Raw()); + if (num == MIN_I64 && arg == -1) + fail (Error_Overflow_Raw()); + if (num % arg == 0) { + num = num / arg; + break; + } + // Fall thru + case SYM_POWER: + Init_Decimal(val, (REBDEC)num); + Init_Decimal(val2, (REBDEC)arg); + return T_Decimal(frame_, action); + + case SYM_REMAINDER: + if (arg == 0) + fail (Error_Zero_Divide_Raw()); + num = (arg != -1) ? (num % arg) : 0; // !!! was macro called REM2 (?) + break; + + case SYM_AND_T: + num &= arg; + break; + + case SYM_OR_T: + num |= arg; + break; + + case SYM_XOR_T: + num ^= arg; + break; + + case SYM_NEGATE: + if (num == MIN_I64) + fail (Error_Overflow_Raw()); + num = -num; + break; + + case SYM_COMPLEMENT: + num = ~num; + break; + + case SYM_ABSOLUTE: + if (num == MIN_I64) + fail (Error_Overflow_Raw()); + if (num < 0) + num = -num; + break; + + case SYM_EVEN_Q: + num = ~num; + // falls through + case SYM_ODD_Q: + if (num & 1) + return R_TRUE; + return R_FALSE; + + case SYM_ROUND: { + INCLUDE_PARAMS_OF_ROUND; + + UNUSED(PAR(value)); + + REBFLGS flags = ( + (REF(to) ? RF_TO : 0) + | (REF(even) ? RF_EVEN : 0) + | (REF(down) ? RF_DOWN : 0) + | (REF(half_down) ? RF_HALF_DOWN : 0) + | (REF(floor) ? RF_FLOOR : 0) + | (REF(ceiling) ? RF_CEILING : 0) + | (REF(half_ceiling) ? RF_HALF_CEILING : 0) + ); + + val2 = ARG(scale); + if (REF(to)) { + if (IS_MONEY(val2)) { + Init_Money(D_OUT, Round_Deci( + int_to_deci(num), flags, VAL_MONEY_AMOUNT(val2) + )); + return R_OUT; + } + if (IS_DECIMAL(val2) || IS_PERCENT(val2)) { + REBDEC dec = Round_Dec( + cast(REBDEC, num), flags, VAL_DECIMAL(val2) + ); + VAL_RESET_HEADER(D_OUT, VAL_TYPE(val2)); + VAL_DECIMAL(D_OUT) = dec; + return R_OUT; + } + if (IS_TIME(val2)) + fail (val2); + arg = VAL_INT64(val2); + } + else + arg = 0L; + num = Round_Int(num, flags, arg); + break; } + + case SYM_RANDOM: { + INCLUDE_PARAMS_OF_RANDOM; + + UNUSED(PAR(value)); + + if (REF(only)) + fail (Error_Bad_Refines_Raw()); + + if (REF(seed)) { + Set_Random(num); + return R_VOID; + } + if (num == 0) + break; + num = Random_Range(num, REF(secure)); // !!! 64 bits + break; } + + default: + fail (Error_Illegal_Action(REB_INTEGER, action)); + } + + Init_Integer(D_OUT, num); + return R_OUT; } diff --git a/src/core/t-library.c b/src/core/t-library.c new file mode 100644 index 0000000000..115ecaa92a --- /dev/null +++ b/src/core/t-library.c @@ -0,0 +1,112 @@ +// +// File: %t-library.c +// Summary: "External Library Support" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2014 Atronix Engineering, Inc. +// Copyright 2014-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + +#include "sys-core.h" + + +// +// CT_Library: C +// +REBINT CT_Library(const RELVAL *a, const RELVAL *b, REBINT mode) +{ + if (mode >= 0) { + return VAL_LIBRARY(a) == VAL_LIBRARY(b); + } + return -1; +} + + +// +// MAKE_Library: C +// +void MAKE_Library(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) +{ + assert(kind == REB_LIBRARY); + UNUSED(kind); + + if (!IS_FILE(arg)) + fail (Error_Unexpected_Type(REB_FILE, VAL_TYPE(arg))); + + REBCNT error = 0; + + REBSER *path = Value_To_OS_Path(arg, FALSE); + void *fd = OS_OPEN_LIBRARY(SER_HEAD(REBCHR, path), &error); + Free_Series(path); + + if (!fd) + fail (Error_Bad_Make(REB_LIBRARY, arg)); + + REBARR *singular = Alloc_Singular_Array(); + VAL_RESET_HEADER(ARR_HEAD(singular), REB_LIBRARY); + ARR_HEAD(singular)->payload.library.singular = singular; + + SER(singular)->misc.fd = fd; + SER(singular)->link.meta = NULL; // build from spec, e.g. arg? + + MANAGE_ARRAY(singular); + Move_Value(out, KNOWN(ARR_HEAD(singular))); +} + + +// +// TO_Library: C +// +void TO_Library(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) +{ + MAKE_Library(out, kind, arg); +} + + +// +// REBTYPE: C +// +REBTYPE(Library) +{ + switch(action) { + case SYM_CLOSE: { + INCLUDE_PARAMS_OF_CLOSE; + + REBVAL *lib = ARG(port); // !!! generic arg name is "port"? + + if (VAL_LIBRARY_FD(lib) == NULL) { + // allow to CLOSE an already closed library + } + else { + OS_CLOSE_LIBRARY(VAL_LIBRARY_FD(lib)); + SER(VAL_LIBRARY(lib))->misc.fd = NULL; + } + return R_VOID; } + + default: + break; + } + + fail (Error_Illegal_Action(REB_LIBRARY, action)); +} diff --git a/src/core/t-logic.c b/src/core/t-logic.c index e4bc99e215..eed4d323a1 100644 --- a/src/core/t-logic.c +++ b/src/core/t-logic.c @@ -1,181 +1,268 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-logic.c -** Summary: logic datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ -/* -** Symbolic bit logic was experimental - but proved not to add much -** value because the overhead of access offset the savings of storage. -** It would be better to add a general purpose bit parsing dialect, -** somewhat similar to R2's struct datatype. -*/ +// +// File: %t-logic.c +// Summary: "logic datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" #include "sys-deci-funcs.h" -/*********************************************************************** -** -*/ REBINT CT_Logic(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// and?: native [ +// +// {Returns true if both values are conditionally true (no "short-circuit")} +// +// value1 [any-value!] +// value2 [any-value!] +// ] +// +REBNATIVE(and_q) { - if (mode >= 0) return (VAL_LOGIC(a) == VAL_LOGIC(b)); - return -1; + INCLUDE_PARAMS_OF_AND_Q; + + if (IS_CONDITIONAL_TRUE(ARG(value1)) && IS_CONDITIONAL_TRUE(ARG(value2))) + return R_TRUE; + + return R_FALSE; +} + + +// +// nor?: native [ +// +// {Returns true if both values are conditionally false (no "short-circuit")} +// +// value1 [any-value!] +// value2 [any-value!] +// ] +// +REBNATIVE(nor_q) +{ + INCLUDE_PARAMS_OF_NOR_Q; + + if (IS_CONDITIONAL_FALSE(ARG(value1)) && IS_CONDITIONAL_FALSE(ARG(value2))) + return R_TRUE; + + return R_FALSE; +} + + +// +// nand?: native [ +// +// {Returns false if both values are conditionally true (no "short-circuit")} +// +// value1 [any-value!] +// value2 [any-value!] +// ] +// +REBNATIVE(nand_q) +{ + INCLUDE_PARAMS_OF_NAND_Q; + + return R_FROM_BOOL(LOGICAL( + IS_CONDITIONAL_TRUE(ARG(value1)) && IS_CONDITIONAL_TRUE(ARG(value2)) + )); +} + + +// +// not?: native [ +// +// "Returns the logic complement." +// +// value [any-value!] +// "(Only LOGIC!'s FALSE and BLANK! return TRUE)" +// ] +// +REBNATIVE(not_q) +{ + INCLUDE_PARAMS_OF_NOT_Q; + + return R_FROM_BOOL(IS_CONDITIONAL_FALSE(ARG(value))); +} + + +// +// or?: native [ +// +// {Returns true if either value is conditionally true (no "short-circuit")} +// +// value1 [any-value!] +// value2 [any-value!] +// ] +// +REBNATIVE(or_q) +{ + INCLUDE_PARAMS_OF_OR_Q; + + return R_FROM_BOOL(LOGICAL( + IS_CONDITIONAL_TRUE(ARG(value1)) || IS_CONDITIONAL_TRUE(ARG(value2)) + )); } -/*********************************************************************** -** -*/ REBFLG MT_Logic(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ +// +// xor?: native [ +// +// {Returns true if only one of the two values is conditionally true.} +// +// value1 [any-value!] +// value2 [any-value!] +// ] +// +REBNATIVE(xor_q) { - if (!IS_INTEGER(data)) return FALSE; - SET_LOGIC(out, VAL_INT64(data) != 0); - return TRUE; + INCLUDE_PARAMS_OF_XOR_Q; + + // Note: no boolean ^^ in C; normalize to booleans and check unequal + // + return R_FROM_BOOL(LOGICAL( + !IS_CONDITIONAL_TRUE(ARG(value1)) != !IS_CONDITIONAL_TRUE(ARG(value2)) + )); } -#ifdef removed -static int find_word(REBVAL *val, REBVAL *word) + +// +// CT_Logic: C +// +REBINT CT_Logic(const RELVAL *a, const RELVAL *b, REBINT mode) { - REBCNT sym = VAL_WORD_CANON(word); - REBINT n; + if (mode >= 0) return (VAL_LOGIC(a) == VAL_LOGIC(b)); + return -1; +} + + +// +// MAKE_Logic: C +// +void MAKE_Logic(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { + assert(kind == REB_LOGIC); + UNUSED(kind); + + // As a construction routine, MAKE takes more liberties in the + // meaning of its parameters, so it lets zero values be false. + // + // !!! Is there a better idea for MAKE that does not hinge on the + // "zero is false" concept? Is there a reason it should? + // + if ( + IS_CONDITIONAL_FALSE(arg) + || (IS_INTEGER(arg) && VAL_INT64(arg) == 0) + || ( + (IS_DECIMAL(arg) || IS_PERCENT(arg)) + && (VAL_DECIMAL(arg) == 0.0) + ) + || (IS_MONEY(arg) && deci_is_zero(VAL_MONEY_AMOUNT(arg))) + ) { + Init_Logic(out, FALSE); + } + else + Init_Logic(out, TRUE); +} + - for (n = 0; NOT_END(val+n); n++) { - if (sym == VAL_WORD_CANON(val+n)) return 1<select)) { - if (!VAL_LOGIC_WORDS(pvs->value) || - !(bit = find_word(BLK_HEAD(VAL_LOGIC_WORDS(pvs->value)), sel))) - return PE_BAD_SELECT; - } - else if (IS_INTEGER(sel)) { - bit = Int32(sel); - if (bit < 0 || bit > 31) return PE_BAD_SELECT; - bit = 1 << bit; - } - else - return PE_BAD_SELECT; - - if (NZ(val = pvs->setval)) { - if (IS_LOGIC(val)) i = VAL_LOGIC(val); - else if (IS_INTEGER(val)) i = Int32(val); - else return PE_BAD_SET; - if (i) VAL_LOGIC(pvs->value) |= bit; - else VAL_LOGIC(pvs->value) &= ~bit; - return PE_OK; - } else { - SET_LOGIC(pvs->store, VAL_LOGIC(pvs->value) & bit); - return PE_USE; - } + if (IS_LOGIC(arg)) + return VAL_LOGIC(arg); + + if (IS_BLANK(arg)) + return FALSE; + + fail (Error_Unexpected_Type(REB_LOGIC, VAL_TYPE(arg))); } -#endif -/*********************************************************************** -** -*/ REBTYPE(Logic) -/* -***********************************************************************/ + +// +// REBTYPE: C +// +REBTYPE(Logic) { - REBCNT val1 = VAL_LOGIC(D_ARG(1)); - REBCNT val2; - REBVAL *arg = D_ARG(2); - - if (IS_BINARY_ACT(action)) { - if (IS_LOGIC(arg)) val2 = VAL_LOGIC(arg); - else if (IS_NONE(arg)) val2 = FALSE; - else Trap_Types(RE_EXPECT_VAL, REB_LOGIC, VAL_TYPE(arg)); - } - - switch (action) { - - case A_AND: val1 &= val2; break; - case A_OR: val1 |= val2; break; - case A_XOR: val1 ^= val2; break; - case A_COMPLEMENT: val1 = 1 & ~val1; break; - - case A_RANDOM: - if (D_REF(2)) { // /seed - // random/seed false restarts; true randomizes - Set_Random(val1 ? (REBINT)OS_DELTA_TIME(0, 0) : 1); - return R_UNSET; - } - DECIDE(Random_Int(D_REF(3)) & 1); // /secure - - case A_MAKE: - case A_TO: - // As a "Rebol conversion", TO falls in line with the rest of the - // interpreter canon that all non-none non-logic values are - // considered effectively "truth". As a construction routine, - // MAKE takes more liberties in the meaning of its parameters, - // so it lets zero values be false. - if (IS_NONE(arg) || - (IS_LOGIC(arg) && !VAL_LOGIC(arg)) || - (IS_INTEGER(arg) && (action == A_MAKE && VAL_INT64(arg) == 0)) || - ((IS_DECIMAL(arg) || IS_PERCENT(arg)) && (action == A_MAKE && VAL_DECIMAL(arg) == 0.0)) || - (IS_MONEY(arg) && (action == A_MAKE && deci_is_zero(VAL_DECI(arg)))) - ) goto is_false; - goto is_true; - -#ifdef removed - case A_CHANGE: - if (IS_NONE(arg)) val1 = 0; - else if (IS_INTEGER(arg)) val1 = Int32(arg); - else if (IS_LOGIC(arg)) val1 = TRUE; - else Trap_Arg(arg); - break; -#endif - - default: - Trap_Action(REB_LOGIC, action); - } - - // Keep other fields AS IS! - VAL_LOGIC(D_ARG(1)) = val1; - return R_ARG1; - -is_false: - return R_FALSE; - -is_true: - return R_TRUE; + REBOOL val1 = VAL_LOGIC(D_ARG(1)); + REBOOL val2; + + switch (action) { + + case SYM_AND_T: + val2 = Math_Arg_For_Logic(D_ARG(2)); + val1 = LOGICAL(val1 && val2); + break; + + case SYM_OR_T: + val2 = Math_Arg_For_Logic(D_ARG(2)); + val1 = LOGICAL(val1 || val2); + break; + + case SYM_XOR_T: + val2 = Math_Arg_For_Logic(D_ARG(2)); + val1 = LOGICAL(!val1 != !val2); + break; + + case SYM_COMPLEMENT: + val1 = NOT(val1); + break; + + case SYM_RANDOM: { + INCLUDE_PARAMS_OF_RANDOM; + + UNUSED(PAR(value)); + + if (REF(only)) + fail (Error_Bad_Refines_Raw()); + + if (REF(seed)) { + // random/seed false restarts; true randomizes + Set_Random(val1 ? (REBINT)OS_DELTA_TIME(0, 0) : 1); + return R_VOID; + } + if (Random_Int(REF(secure)) & 1) + return R_TRUE; + return R_FALSE; } + + default: + fail (Error_Illegal_Action(REB_LOGIC, action)); + } + + return val1 ? R_TRUE : R_FALSE; } diff --git a/src/core/t-map.c b/src/core/t-map.c old mode 100644 new mode 100755 index 680f9ba517..fe8726edcb --- a/src/core/t-map.c +++ b/src/core/t-map.c @@ -1,570 +1,768 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-map.c -** Summary: map datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ -/* - A map is a SERIES that can also include a hash table for faster lookup. - - The hashing method used here is the same as that used for the - REBOL symbol table, with the exception that this method must - also store the value of the symbol (not just its word). - - The structure of the series header for a map is the same as other - series, except that the opt series field is a pointer to a REBCNT - series, the hash table. - - The hash table is an array of REBCNT integers that are index values - into the map series. NOTE: They are one-based to avoid 0 which is an - empty slot. - - Each value in the map consists of a word followed by its value. - - These functions are also used hashing SET operations (e.g. UNION). - - The series/tail / 2 is the number of values stored. - - The hash-series/tail is a prime number that is use for computing - slots in the hash table. -*/ +// +// File: %t-map.c +// Summary: "map datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// See %sys-map.h for an explanation of the map structure. +// #include "sys-core.h" -#define MIN_DICT 8 // size to switch to hashing - - -/*********************************************************************** -** -*/ REBINT CT_Map(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Map: C +// +REBINT CT_Map(const RELVAL *a, const RELVAL *b, REBINT mode) { - if (mode < 0) return -1; - if (mode == 3) return VAL_SERIES(a) == VAL_SERIES(b); - return 0 == Cmp_Block(a, b, 0); + if (mode < 0) return -1; + return 0 == Cmp_Array(a, b, FALSE); } -/*********************************************************************** -** -*/ static REBSER *Make_Map(REBINT size) -/* -** Makes a MAP block (that holds both keys and values). -** Size is the number of key-value pairs. -** If size >= MIN_DICT, then a hash series is also created. -** -***********************************************************************/ +// +// Make_Map: C +// +// Makes a MAP block (that holds both keys and values). +// Capacity is measured in key-value pairings. +// A hash series is also created. +// +static REBMAP *Make_Map(REBCNT capacity) { - REBSER *blk = Make_Block(size*2); - REBSER *ser = 0; + REBARR *pairlist = Make_Array_Core(capacity * 2, ARRAY_FLAG_PAIRLIST); + SER(pairlist)->link.hashlist = Make_Hash_Sequence(capacity); - if (size >= MIN_DICT) ser = Make_Hash_Array(size); + return MAP(pairlist); +} - blk->series = ser; - return blk; +// +// Find_Key_Hashed: C +// +// Returns hash index (either the match or the new one). +// A return of zero is valid (as a hash index); +// +// Wide: width of record (normally 2, a key and a value). +// +// Modes: +// 0 - search, return hash if found or not +// 1 - search, return hash, else return -1 if not +// 2 - search, return hash, else append value and return -1 +// +REBINT Find_Key_Hashed( + REBARR *array, + REBSER *hashlist, + const RELVAL *key, // !!! assumes key is followed by value(s) via ++ + REBSPC *specifier, + REBCNT wide, + REBOOL cased, + REBYTE mode +) { + REBCNT len = SER_LEN(hashlist); + assert(len > 0); + + REBCNT hash = Hash_Value(key); + + // The REBCNT[] hash array size is chosen to try and make a large enough + // table relative to the data that collisions will be hopefully not + // frequent. But they may still collide. The method R3-Alpha chose to + // deal with collisions was to have a "skip" amount that will go try + // another hash bucket until the searched for key is found or a 0 + // entry in the hashlist is found. + // + // Note: if len and skip are co-primes is guaranteed that repeatedly + // adding skip (and subtracting len when needed) all positions are + // visited. 1 <= skip < len, and len is prime, so this is guaranteed. + + REBCNT skip = hash % (len - 1) + 1; + + hash = hash % len; + + // a 'zombie' is a key with void value, that may be overwritten. Set to + // len to indicate zombie not yet encountered. + // + REBCNT zombie = len; + + REBCNT uncased = len; // uncased match not yet encountered + + // Scan hash table for match: + + REBCNT *hashes = SER_HEAD(REBCNT, hashlist); + REBCNT n; + RELVAL *val; + + if (ANY_WORD(key)) { + while ((n = hashes[hash])) { + val = ARR_AT(array, (n - 1) * wide); + if (ANY_WORD(val)) { + if (VAL_WORD_SPELLING(key) == VAL_WORD_SPELLING(val)) + return hash; // exact match + + if (NOT(cased) && uncased == len) // not cased w/no match yet + if (VAL_WORD_CANON(key) == VAL_WORD_CANON(val)) + uncased = hash; // indicate uncased match found + } + else if (wide > 1 && IS_VOID(++val) && zombie == len) { + zombie = hash; + } + hash += skip; + if (hash >= len) hash -= len; + } + } + else if (ANY_BINSTR(key)) { + while ((n = hashes[hash])) { + val = ARR_AT(array, (n - 1) * wide); + if (VAL_TYPE(val) == VAL_TYPE(key)) { + if (0 == Compare_String_Vals(val, key, FALSE)) return hash; + if ( + !cased && uncased == len + && 0 == Compare_String_Vals( + val, key, LOGICAL(!IS_BINARY(key)) + ) + ) { + uncased = hash; + } + } + if (wide > 1 && IS_VOID(++val) && zombie == len) { + zombie = hash; + } + hash += skip; + if (hash >= len) hash -= len; + } + } else { + while ((n = hashes[hash])) { + val = ARR_AT(array, (n - 1) * wide); + if (VAL_TYPE(val) == VAL_TYPE(key)) { + if (0 == Cmp_Value(key, val, TRUE)) { + return hash; + } + if ( + !cased && uncased == len + && REB_CHAR == VAL_TYPE(val) + && 0 == Cmp_Value(key, val, FALSE) + ) { + uncased = hash; + } + } + if (wide > 1 && IS_VOID(++val) && zombie == len) zombie = hash; + hash += skip; + if (hash >= len) hash -= len; + } + } + + //assert(n == 0); + if (!cased && uncased < len) hash = uncased; // uncased< match + else if (zombie < len) { // zombie encountered! + assert(mode == 0); + hash = zombie; + n = hashes[hash]; + // new key overwrite zombie + *ARR_AT(array, (n - 1) * wide) = *key; + } + // Append new value the target series: + if (mode > 1) { + REBCNT index; + const RELVAL *src = key; + hashes[hash] = (ARR_LEN(array) / wide) + 1; + + // This used to use Append_Values_Len, but that is a REBVAL* interface + // !!! Should there be an Append_Values_Core which takes RELVAL*? + // + for (index = 0; index < wide; ++src, ++index) + Append_Value_Core(array, src, specifier); + } + + return (mode > 0) ? NOT_FOUND : hash; } -/*********************************************************************** -** -*/ REBINT Find_Key(REBSER *series, REBSER *hser, REBVAL *key, REBINT wide, REBCNT cased, REBYTE mode) -/* -** Returns hash index (either the match or the new one). -** A return of zero is valid (as a hash index); -** -** Wide: width of record (normally 2, a key and a value). -** -** Modes: -** 0 - search, return hash if found or not -** 1 - search, return hash, else return -1 if not -** 2 - search, return hash, else append value and return -1 -** -***********************************************************************/ +// +// Rehash_Map: C +// +// Recompute the entire hash table for a map. Table must be large enough. +// +static void Rehash_Map(REBMAP *map) { - REBCNT *hashes; - REBCNT skip; - REBCNT hash; - REBCNT len; - REBCNT n; - REBVAL *val; - - // Compute hash for value: - len = hser->tail; - hash = Hash_Value(key, len); - if (!hash) Trap_Type(key); - - // Determine skip and first index: - skip = (len == 0) ? 0 : (hash & 0x0000FFFF) % len; - if (skip == 0) skip = 1; - hash = (len == 0) ? 0 : (hash & 0x00FFFF00) % len; - - // Scan hash table for match: - hashes = (REBCNT*)hser->data; - if (ANY_WORD(key)) { - while (NZ(n = hashes[hash])) { - val = BLK_SKIP(series, (n-1) * wide); - if ( - ANY_WORD(val) && - (VAL_WORD_SYM(key) == VAL_BIND_SYM(val) || - (!cased && VAL_WORD_CANON(key) == VAL_BIND_CANON(val))) - ) return hash; - hash += skip; - if (hash >= len) hash -= len; - } - } - else if (ANY_BINSTR(key)) { - while (NZ(n = hashes[hash])) { - val = BLK_SKIP(series, (n-1) * wide); - if ( - VAL_TYPE(val) == VAL_TYPE(key) - && 0 == Compare_String_Vals(key, val, (REBOOL)(!IS_BINARY(key) && !cased)) - ) return hash; - hash += skip; - if (hash >= len) hash -= len; - } - } else { - while (NZ(n = hashes[hash])) { - val = BLK_SKIP(series, (n-1) * wide); - if (VAL_TYPE(val) == VAL_TYPE(key) && 0 == Cmp_Value(key, val, !cased)) return hash; - hash += skip; - if (hash >= len) hash -= len; - } - } - - // Append new value the target series: - if (mode > 1) { - hashes[hash] = SERIES_TAIL(series)+1; - //Debug_Num("hash:", hashes[hash]); - Append_Series(series, (REBYTE*)key, wide); - //Dump_Series(series, "hash"); - } - - return (mode > 0) ? -1 : hash; + REBSER *hashlist = MAP_HASHLIST(map); + + if (!hashlist) return; + + REBCNT *hashes = SER_HEAD(REBCNT, hashlist); + REBARR *pairlist = MAP_PAIRLIST(map); + + REBVAL *key = KNOWN(ARR_HEAD(pairlist)); + REBCNT n; + + for (n = 0; n < ARR_LEN(pairlist); n += 2, key += 2) { + const REBOOL cased = TRUE; // cased=TRUE is always fine + + if (IS_VOID(key + 1)) { + // + // It's a "zombie", move last key to overwrite it + // + Move_Value( + key, KNOWN(ARR_AT(pairlist, ARR_LEN(pairlist) - 2)) + ); + Move_Value( + &key[1], KNOWN(ARR_AT(pairlist, ARR_LEN(pairlist) - 1)) + ); + SET_ARRAY_LEN_NOTERM(pairlist, ARR_LEN(pairlist) - 2); + } + + REBCNT hash = Find_Key_Hashed( + pairlist, hashlist, key, SPECIFIED, 2, cased, 0 + ); + hashes[hash] = n / 2 + 1; + + // discard zombies at end of pairlist + // + while (IS_VOID(ARR_AT(pairlist, ARR_LEN(pairlist) - 1))) { + SET_ARRAY_LEN_NOTERM(pairlist, ARR_LEN(pairlist) - 2); + } + } } -/*********************************************************************** -** -*/ static void Rehash_Hash(REBSER *series) -/* -** Recompute the entire hash table. Table must be large enough. -** -***********************************************************************/ +// +// Expand_Hash: C +// +// Expand hash series. Clear it but set its tail. +// +void Expand_Hash(REBSER *ser) { - REBVAL *val; - REBCNT n; - REBCNT key; - REBCNT *hashes; - - if (!series->series) return; - - hashes = (REBCNT*)(series->series->data); - - val = BLK_HEAD(series); - for (n = 0; n < series->tail; n += 2, val += 2) { - key = Find_Key(series, series->series, val, 2, 0, 0); - hashes[key] = n/2+1; - } + REBINT pnum = Get_Hash_Prime(SER_LEN(ser) + 1); + if (pnum == 0) { + DECLARE_LOCAL (temp); + Init_Integer(temp, SER_LEN(ser) + 1); + fail (Error_Size_Limit_Raw(temp)); + } + + assert(NOT_SER_FLAG(ser, SERIES_FLAG_ARRAY)); + Remake_Series( + ser, + pnum + 1, + SER_WIDE(ser), + SERIES_FLAG_POWER_OF_2 // NOT(NODE_FLAG_NODE) => don't keep data + ); + + Clear_Series(ser); + SET_SERIES_LEN(ser, pnum); } -/*********************************************************************** -** -*/ static REBCNT Find_Entry(REBSER *series, REBVAL *key, REBVAL *val) -/* -** Try to find the entry in the map. If not found -** and val is SET, create the entry and store the key and -** val. -** -** RETURNS: the index to the VALUE or zero if there is none. -** -***********************************************************************/ -{ - REBSER *hser = series->series; // can be null - REBCNT *hashes; - REBCNT hash; - REBVAL *v; - REBCNT n; - - if (IS_NONE(key)) return 0; - - // We may not be large enough yet for the hash table to - // be worthwhile, so just do a linear search: - if (!hser) { - if (series->tail < MIN_DICT*2) { - v = BLK_HEAD(series); - if (ANY_WORD(key)) { - for (n = 0; n < series->tail; n += 2, v += 2) { - if (ANY_WORD(v) && SAME_SYM(key, v)) { - if (val) *++v = *val; - return n/2+1; - } - } - } - else if (ANY_BINSTR(key)) { - for (n = 0; n < series->tail; n += 2, v += 2) { - if (VAL_TYPE(key) == VAL_TYPE(v) && 0 == Compare_String_Vals(key, v, (REBOOL)!IS_BINARY(v))) { - if (val) { - *++v = *val; -// VAL_SERIES(v) = Copy_Series_Value(val); -// VAL_INDEX(v) = 0; - } - return n/2+1; - } - } - } - else if (IS_INTEGER(key)) { - for (n = 0; n < series->tail; n += 2, v += 2) { - if (IS_INTEGER(v) && VAL_INT64(key) == VAL_INT64(v)) { - if (val) *++v = *val; - return n/2+1; - } - } - } - else if (IS_CHAR(key)) { - for (n = 0; n < series->tail; n += 2, v += 2) { - if (IS_CHAR(v) && VAL_CHAR(key) == VAL_CHAR(v)) { - if (val) *++v = *val; - return n/2+1; - } - } - } - else Trap_Type(key); - - if (!val) return 0; - Append_Val(series, key); - Append_Val(series, val); // no Copy_Series_Value(val) on strings - return series->tail/2; - } - - // Add hash table: - //Print("hash added %d", series->tail); - series->series = hser = Make_Hash_Array(series->tail); - Rehash_Hash(series); - } - - // Get hash table, expand it if needed: - if (series->tail > hser->tail/2) { - Expand_Hash(hser); // modifies size value - Rehash_Hash(series); - } - - hash = Find_Key(series, hser, key, 2, 0, 0); - hashes = (REBCNT*)hser->data; - n = hashes[hash]; - - // Just a GET of value: - if (!val) return n; - - // Must set the value: - if (n) { // re-set it: - *BLK_SKIP(series, ((n-1)*2)+1) = *val; // set it - return n; - } - - // Create new entry: - Append_Val(series, key); - Append_Val(series, val); // no Copy_Series_Value(val) on strings - - return (hashes[hash] = series->tail/2); +// +// Find_Map_Entry: C +// +// Try to find the entry in the map. If not found and val isn't void, create +// the entry and store the key and val. +// +// RETURNS: the index to the VALUE or zero if there is none. +// +static REBCNT Find_Map_Entry( + REBMAP *map, + const RELVAL *key, + REBSPC *key_specifier, + const RELVAL *val, + REBSPC *val_specifier, + REBOOL cased // case-sensitive if true +) { + assert(!IS_VOID(key)); + + REBSER *hashlist = MAP_HASHLIST(map); // can be null + REBARR *pairlist = MAP_PAIRLIST(map); + + assert(hashlist); + + // Get hash table, expand it if needed: + if (ARR_LEN(pairlist) > SER_LEN(hashlist) / 2) { + Expand_Hash(hashlist); // modifies size value + Rehash_Map(map); + } + + REBCNT hash = Find_Key_Hashed( + pairlist, hashlist, key, key_specifier, 2, cased, 0 + ); + + REBCNT *hashes = SER_HEAD(REBCNT, hashlist); + REBCNT n = hashes[hash]; + + // n==0 or pairlist[(n-1)*]=~key + + // Just a GET of value: + if (!val) return n; + + // If not just a GET, it may try to set the value in the map. Which means + // the key may need to be stored. Since copies of keys are never made, + // a SET must always be done with an immutable key...because if it were + // changed, there'd be no notification to rehash the map. + // + if (!Is_Value_Immutable(key)) + fail (Error_Map_Key_Unlocked_Raw(key)); + + // Must set the value: + if (n) { // re-set it: + Derelativize( + ARR_AT(pairlist, ((n - 1) * 2) + 1), + val, + val_specifier + ); + return n; + } + + if (IS_VOID(val)) return 0; // trying to remove non-existing key + + // Create new entry. Note that it does not copy underlying series (e.g. + // the data of a string), which is why the immutability test is necessary + // + Append_Value_Core(pairlist, key, key_specifier); + Append_Value_Core(pairlist, val, val_specifier); + + return (hashes[hash] = (ARR_LEN(pairlist) / 2)); } -/*********************************************************************** -** -*/ REBINT Length_Map(REBSER *series) -/* -***********************************************************************/ +// +// PD_Map: C +// +REBINT PD_Map(REBPVS *pvs) { - REBCNT n, c = 0; - REBVAL *v = BLK_HEAD(series); + REBOOL setting = LOGICAL(pvs->opt_setval && IS_END(pvs->item + 1)); + + assert(IS_MAP(pvs->value)); + + if (setting) + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(pvs->value)); + + REBINT n = Find_Map_Entry( + VAL_MAP(pvs->value), + pvs->picker, + SPECIFIED, + setting ? pvs->opt_setval : NULL, + SPECIFIED, + setting // `cased` flag for case-sensitivity--use when setting only + ); + + if (n == 0) { + Init_Void(pvs->store); + return PE_USE_STORE; + } + + REBVAL *val = KNOWN( + ARR_AT(MAP_PAIRLIST(VAL_MAP(pvs->value)), ((n - 1) * 2) + 1) + ); + if (IS_VOID(val)) { + Init_Void(pvs->store); + return PE_USE_STORE; + } + + pvs->value = val; + pvs->value_specifier = SPECIFIED; + + return PE_OK; +} - for (n = 0; n < series->tail; n += 2, v += 2) { - if (!IS_NONE(v+1)) c++; // must have non-none value - } - return c; +// +// Append_Map: C +// +static void Append_Map( + REBMAP *map, + REBARR *array, + REBCNT index, + REBSPC *specifier, + REBCNT len +) { + RELVAL *item = ARR_AT(array, index); + REBCNT n = 0; + + while (n < len && NOT_END(item)) { + if (IS_END(item + 1)) { + // + // Keys with no value not allowed, e.g. `make map! [1 "foo" 2]` + // + fail (Error_Past_End_Raw()); + } + + Find_Map_Entry( + map, + item, + specifier, + item + 1, + specifier, + TRUE + ); + + item += 2; + n += 2; + } } -/*********************************************************************** -** -*/ REBINT PD_Map(REBPVS *pvs) -/* -***********************************************************************/ +// +// MAKE_Map: C +// +void MAKE_Map(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBVAL *data = pvs->value; - REBVAL *val = 0; - REBINT n = 0; - - if (IS_END(pvs->path+1)) val = pvs->setval; - if (IS_NONE(pvs->select)) return PE_NONE; - - if (!ANY_WORD(pvs->select) && !ANY_BINSTR(pvs->select) && - !IS_INTEGER(pvs->select) && !IS_CHAR(pvs->select)) - return PE_BAD_SELECT; - - n = Find_Entry(VAL_SERIES(data), pvs->select, val); - - if (!n) return PE_NONE; - - TRAP_PROTECT(VAL_SERIES(data)); - pvs->value = VAL_BLK_SKIP(data, ((n-1)*2)+1); - return PE_OK; + if (ANY_NUMBER(arg)) { + REBMAP *map = Make_Map(Int32s(arg, 0)); + Init_Map(out, map); + } + else { + // !!! R3-Alpha TO of MAP! was like MAKE but wouldn't accept just + // being given a size. + // + TO_Map(out, kind, arg); + } } -/*********************************************************************** -** -*/ static void Append_Map(REBSER *ser, REBVAL *arg, REBCNT len) -/* -***********************************************************************/ +// +// TO_Map: C +// +void TO_Map(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBVAL *val; - REBCNT n; - - val = VAL_BLK_DATA(arg); - for (n = 0; n < len && NOT_END(val) && NOT_END(val+1); val += 2, n += 2) { - Find_Entry(ser, val, val+1); - } + assert(kind == REB_MAP); + UNUSED(kind); + + REBARR* array; + REBCNT len; + REBCNT index; + REBSPC *specifier; + + if (IS_BLOCK(arg) || IS_GROUP(arg)) { + // + // make map! [word val word val] + // + array = VAL_ARRAY(arg); + index = VAL_INDEX(arg); + len = VAL_ARRAY_LEN_AT(arg); + specifier = VAL_SPECIFIER(arg); + } + else if (IS_MAP(arg)) { + array = MAP_PAIRLIST(VAL_MAP(arg)); + index = 0;// maps don't have an index/"position" + len = ARR_LEN(array); + specifier = SPECIFIED; // there should be no relative values in a MAP! + } + else + fail (arg); + + REBMAP *map = Make_Map(len / 2); // [key value key value...] + END + Append_Map(map, array, index, specifier, len); + Rehash_Map(map); + Init_Map(out, map); } -/*********************************************************************** -** -*/ REBFLG MT_Map(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ +// +// Map_To_Array: C +// +// what: -1 - words, +1 - values, 0 -both +// +REBARR *Map_To_Array(REBMAP *map, REBINT what) { - REBCNT n; - REBSER *series; - - if (!IS_BLOCK(data) && !IS_MAP(data)) return FALSE; - - n = VAL_BLK_LEN(data); - if (n & 1) return FALSE; - - series = Make_Map(n/2); - - //COPY_BLK_PART(series, VAL_BLK_DATA(data), n); - Append_Map(series, data, UNKNOWN); - - Rehash_Hash(series); - - Set_Series(REB_MAP, out, series); - - return TRUE; + REBCNT count = Length_Map(map); + + // Copy entries to new block: + // + REBARR *array = Make_Array(count * ((what == 0) ? 2 : 1)); + REBVAL *dest = SINK(ARR_HEAD(array)); + REBVAL *val = KNOWN(ARR_HEAD(MAP_PAIRLIST(map))); + for (; NOT_END(val); val += 2) { + assert(NOT_END(val + 1)); + if (!IS_VOID(val + 1)) { + if (what <= 0) { + Move_Value(dest, &val[0]); + ++dest; + } + if (what >= 0) { + Move_Value(dest, &val[1]); + ++dest; + } + } + } + + TERM_ARRAY_LEN(array, cast(RELVAL*, dest) - ARR_HEAD(array)); + assert(IS_END(dest)); + return array; } -/*********************************************************************** -** -*/ REBSER *Map_To_Block(REBSER *mapser, REBINT what) -/* -** mapser = series of the map -** what: -1 - words, +1 - values, 0 -both -** -***********************************************************************/ +// +// Mutate_Array_Into_Map: C +// +// Convert existing array to a map. The array is tested to make sure it is +// not managed, hence it has not been put into any REBVALs that might use +// a non-map-aware access to it. (That would risk making changes to the +// array that did not keep the hashes in sync.) +// +REBMAP *Mutate_Array_Into_Map(REBARR *a) { - REBVAL *val; - REBCNT cnt = 0; - REBSER *blk; - REBVAL *out; - - // Count number of set entries: - for (val = BLK_HEAD(mapser); NOT_END(val) && NOT_END(val+1); val += 2) { - if (!IS_NONE(val+1)) cnt++; // must have non-none value - } - - // Copy entries to new block: - blk = Make_Block(cnt * ((what == 0) ? 2 : 1)); - out = BLK_HEAD(blk); - for (val = BLK_HEAD(mapser); NOT_END(val) && NOT_END(val+1); val += 2) { - if (!IS_NONE(val+1)) { - if (what <= 0) *out++ = val[0]; - if (what >= 0) *out++ = val[1]; - } - } - - SET_END(out); - blk->tail = out - BLK_HEAD(blk); - return blk; -} + REBCNT size = ARR_LEN(a); + // See note above--can't have this array be accessible via some ANY-BLOCK! + // + assert(NOT(IS_ARRAY_MANAGED(a))); -/*********************************************************************** -** -*/ void Block_As_Map(REBSER *blk) -/* -** Convert existing block to a map. -** -***********************************************************************/ -{ - REBSER *ser = 0; - REBCNT size = SERIES_TAIL(blk); + SET_SER_FLAG(a, ARRAY_FLAG_PAIRLIST); + + REBMAP *map = MAP(a); + MAP_HASHLIST(map) = Make_Hash_Sequence(size); - if (size >= MIN_DICT) ser = Make_Hash_Array(size); - blk->series = ser; - Rehash_Hash(blk); + Rehash_Map(map); + return map; } -/*********************************************************************** -** -*/ REBSER *Map_To_Object(REBSER *mapser) -/* -***********************************************************************/ +// +// Alloc_Context_From_Map: C +// +REBCTX *Alloc_Context_From_Map(REBMAP *map) { - REBVAL *val; - REBCNT cnt = 0; - REBSER *frame; - REBVAL *word; - REBVAL *mval; - - // Count number of set entries: - for (mval = BLK_HEAD(mapser); NOT_END(mval) && NOT_END(mval+1); mval += 2) { - if (ANY_WORD(mval) && !IS_NONE(mval+1)) cnt++; - } - - // See Make_Frame() - cannot use it directly because no Collect_Words - frame = Make_Frame(cnt); - - word = FRM_WORD(frame, 1); - val = FRM_VALUE(frame, 1); - for (mval = BLK_HEAD(mapser); NOT_END(mval) && NOT_END(mval+1); mval += 2) { - if (ANY_WORD(mval) && !IS_NONE(mval+1)) { - VAL_SET(word, REB_SET_WORD); - VAL_SET_OPT(word, OPTS_UNWORD); - VAL_BIND_SYM(word) = VAL_WORD_SYM(mval); - VAL_BIND_TYPESET(word) = ~((TYPESET(REB_END) | TYPESET(REB_UNSET))); // not END or UNSET - word++; - *val++ = mval[1]; - } - } - - SET_END(word); - SET_END(val); - FRM_WORD_SERIES(frame)->tail = frame->tail = cnt + 1; - - return frame; + // Doesn't use Length_Map because it only wants to consider words. + // + // !!! Should this fail() if any of the keys aren't words? It seems + // a bit haphazard to have `make object! make map! [x 10 20]` and + // just throw out the 20 case... + + REBVAL *mval = KNOWN(ARR_HEAD(MAP_PAIRLIST(map))); + REBCNT count = 0; + + for (; NOT_END(mval); mval += 2) { + assert(NOT_END(mval + 1)); + if (ANY_WORD(mval) && !IS_VOID(mval + 1)) + ++count; + } + + // See Alloc_Context() - cannot use it directly because no Collect_Words + + REBCTX *context = Alloc_Context(REB_OBJECT, count); + REBVAL *key = CTX_KEYS_HEAD(context); + REBVAL *var = CTX_VARS_HEAD(context); + + mval = KNOWN(ARR_HEAD(MAP_PAIRLIST(map))); + + for (; NOT_END(mval); mval += 2) { + assert(NOT_END(mval + 1)); + if (ANY_WORD(mval) && !IS_VOID(mval + 1)) { + // !!! Used to leave SET_WORD typed values here... but why? + // (Objects did not make use of the set-word vs. other distinctions + // that function specs did.) + Init_Typeset( + key, + // all types except void + ~FLAGIT_KIND(REB_MAX_VOID), + VAL_WORD_SPELLING(mval) + ); + ++key; + Move_Value(var, &mval[1]); + ++var; + } + } + + TERM_ARRAY_LEN(CTX_VARLIST(context), count + 1); + TERM_ARRAY_LEN(CTX_KEYLIST(context), count + 1); + assert(IS_END(key)); + assert(IS_END(var)); + + return context; } -/*********************************************************************** -** -*/ REBTYPE(Map) -/* -***********************************************************************/ +// +// REBTYPE: C +// +REBTYPE(Map) { - REBVAL *val = D_ARG(1); - REBVAL *arg = D_ARG(2); - REBINT n; - REBSER *series = VAL_SERIES(val); - - // Check must be in this order (to avoid checking a non-series value); - if (action >= A_TAKE && action <= A_SORT && IS_PROTECT_SERIES(series)) - Trap0(RE_PROTECTED); - - switch (action) { - - case A_PICK: // same as SELECT for MAP! datatype - case A_SELECT: - n = Find_Entry(series, arg, 0); - if (!n) return R_NONE; - *D_RET = *VAL_BLK_SKIP(val, ((n-1)*2)+1); - break; - - case A_INSERT: - case A_APPEND: - if (!IS_BLOCK(arg)) Trap_Arg(val); - *D_RET = *val; - if (DS_REF(AN_DUP)) { - n = Int32(DS_ARG(AN_COUNT)); - if (n <= 0) break; - } - Append_Map(series, arg, Partial1(arg, D_ARG(AN_LENGTH))); - break; - - case A_POKE: // CHECK all pokes!!! to be sure they check args now !!! - n = Find_Entry(series, arg, D_ARG(3)); - *D_RET = *D_ARG(3); - break; - - case A_LENGTHQ: - n = Length_Map(series); - DS_RET_INT(n); - break; - - case A_MAKE: - case A_TO: - // make map! [word val word val] - if (IS_BLOCK(arg) || IS_PAREN(arg) || IS_MAP(arg)) { - if (MT_Map(D_RET, arg, 0)) return R_RET; - Trap_Arg(arg); -// } else if (IS_NONE(arg)) { -// n = 3; // just a start - // make map! 10000 - } else if (IS_NUMBER(arg)) { - if (action == A_TO) Trap_Arg(arg); - n = Int32s(arg, 0); - } else - Trap_Make(REB_MAP, Of_Type(arg)); - // positive only - series = Make_Map(n); - Set_Series(REB_MAP, D_RET, series); - break; - - case A_COPY: - if (MT_Map(D_RET, val, 0)) return R_RET; - Trap_Arg(val); - - case A_CLEAR: - Clear_Series(series); - if (series->series) Clear_Series(series->series); - Set_Series(REB_MAP, D_RET, series); - break; - - case A_REFLECT: - action = What_Reflector(arg); // zero on error - // Adjust for compatibility with PICK: - if (action == OF_VALUES) n = 1; - else if (action == OF_WORDS) n = -1; - else if (action == OF_BODY) n = 0; - else Trap_Reflect(REB_MAP, arg); - series = Map_To_Block(series, n); - Set_Block(D_RET, series); - break; - - case A_TAILQ: - return (Length_Map(series) == 0) ? R_TRUE : R_FALSE; - - default: - Trap_Action(REB_MAP, action); - } - - return R_RET; + REBVAL *val = D_ARG(1); + REBVAL *arg = D_ARGC > 1 ? D_ARG(2) : NULL; + + REBMAP *map = VAL_MAP(val); + REBCNT tail; + + switch (action) { + case SYM_FIND: + case SYM_SELECT_P: { + INCLUDE_PARAMS_OF_FIND; + + UNUSED(PAR(series)); + UNUSED(PAR(value)); // handled as `arg` + + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(only)) + fail (Error_Bad_Refines_Raw()); + if (REF(skip)) { + UNUSED(ARG(size)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(last)) + fail (Error_Bad_Refines_Raw()); + if (REF(reverse)) + fail (Error_Bad_Refines_Raw()); + if (REF(tail)) + fail (Error_Bad_Refines_Raw()); + if (REF(match)) + fail (Error_Bad_Refines_Raw()); + + REBINT n = Find_Map_Entry( + map, + arg, + SPECIFIED, + NULL, + SPECIFIED, + REF(case) + ); + + if (n == 0) + return action == SYM_FIND ? R_FALSE : R_VOID; + + Move_Value( + D_OUT, + KNOWN(ARR_AT(MAP_PAIRLIST(map), ((n - 1) * 2) + 1)) + ); + + if (action == SYM_FIND) + return IS_VOID(D_OUT) ? R_FALSE : R_TRUE; + + return R_OUT; } + + case SYM_INSERT: + case SYM_APPEND: { + INCLUDE_PARAMS_OF_INSERT; + + FAIL_IF_READ_ONLY_ARRAY(MAP_PAIRLIST(map)); + + UNUSED(PAR(series)); + UNUSED(PAR(value)); // handled as arg + + if (REF(only)) + fail (Error_Bad_Refines_Raw()); + + if (!IS_BLOCK(arg)) + fail (val); + Move_Value(D_OUT, val); + if (REF(dup)) { + if (Int32(ARG(count)) <= 0) break; + } + + UNUSED(REF(part)); + Partial1(arg, ARG(limit), &tail); + Append_Map( + map, + VAL_ARRAY(arg), + VAL_INDEX(arg), + VAL_SPECIFIER(arg), + tail + ); + return R_OUT; } + + case SYM_REMOVE: { + INCLUDE_PARAMS_OF_REMOVE; + + FAIL_IF_READ_ONLY_ARRAY(MAP_PAIRLIST(map)); + + UNUSED(PAR(series)); + + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + if (NOT(REF(map))) + fail (Error_Illegal_Action(REB_MAP, action)); + + Move_Value(D_OUT, val); + Find_Map_Entry( + map, ARG(key), SPECIFIED, VOID_CELL, SPECIFIED, TRUE + ); + return R_OUT; } + + case SYM_LENGTH_OF: + Init_Integer(D_OUT, Length_Map(map)); + return R_OUT; + + case SYM_COPY: { + INCLUDE_PARAMS_OF_COPY; + + UNUSED(PAR(value)); + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(deep)) + fail (Error_Bad_Refines_Raw()); + if (REF(types)) { + UNUSED(ARG(kinds)); + fail (Error_Bad_Refines_Raw()); + } + + // !!! the copying map case should probably not be a MAKE case, but + // implemented here as copy. + // + MAKE_Map(D_OUT, REB_MAP, val); // may fail() + return R_OUT; } + + case SYM_CLEAR: + FAIL_IF_READ_ONLY_ARRAY(MAP_PAIRLIST(map)); + + Reset_Array(MAP_PAIRLIST(map)); + + // !!! Review: should the space for the hashlist be reclaimed? This + // clears all the indices but doesn't scale back the size. + // + Clear_Series(MAP_HASHLIST(map)); + + Init_Map(D_OUT, map); + return R_OUT; + + case SYM_REFLECT: { + REBSYM sym = VAL_WORD_SYM(arg); + + REBINT n; + if (sym == SYM_VALUES) + n = 1; + else if (sym == SYM_WORDS) + n = -1; + else if (sym == SYM_BODY) + n = 0; + else + fail (Error_Cannot_Reflect(REB_MAP, arg)); + + REBARR *array = Map_To_Array(map, n); + Init_Block(D_OUT, array); + return R_OUT; + } + + case SYM_TAIL_Q: + return (Length_Map(map) == 0) ? R_TRUE : R_FALSE; + + default: + break; + } + + fail (Error_Illegal_Action(REB_MAP, action)); } diff --git a/src/core/t-money.c b/src/core/t-money.c index 5089c5e148..7a70f14c94 100644 --- a/src/core/t-money.c +++ b/src/core/t-money.c @@ -1,252 +1,290 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-money.c -** Summary: extended precision datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-money.c +// Summary: "extended precision datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" #include "sys-deci-funcs.h" -/*********************************************************************** -** -*/ REBINT CT_Money(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Money: C +// +REBINT CT_Money(const RELVAL *a, const RELVAL *b, REBINT mode) { - REBFLG e, g; - - if (mode >= 3) e = deci_is_same(VAL_DECI(a), VAL_DECI(b)); - else { - e = deci_is_equal(VAL_DECI(a), VAL_DECI(b)); - g = 0; - if (mode < 0) { - g = deci_is_lesser_or_equal(VAL_DECI(b), VAL_DECI(a)); - if (mode == -1) e |= g; - else e = g & !e; - } - } - return e != 0;; + REBOOL e, g; + + e = deci_is_equal(VAL_MONEY_AMOUNT(a), VAL_MONEY_AMOUNT(b)); + if (mode < 0) { + g = deci_is_lesser_or_equal( + VAL_MONEY_AMOUNT(b), VAL_MONEY_AMOUNT(a) + ); + if (mode == -1) e = LOGICAL(e || g); + else e = LOGICAL(g && !e); + } + return e ? 1 : 0; } -/*********************************************************************** -** -*/ REBINT Emit_Money(REBVAL *value, REBYTE *buf, REBCNT opts) -/* -***********************************************************************/ +// +// MAKE_Money: C +// +void MAKE_Money(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - return deci_to_string(buf, VAL_DECI(value), '$', '.'); + assert(kind == REB_MONEY); + UNUSED(kind); + + switch (VAL_TYPE(arg)) { + case REB_INTEGER: + Init_Money(out, int_to_deci(VAL_INT64(arg))); + break; + + case REB_DECIMAL: + case REB_PERCENT: + Init_Money(out, decimal_to_deci(VAL_DECIMAL(arg))); + break; + + case REB_MONEY: + Move_Value(out, arg); + return; + + case REB_STRING: + { + const REBYTE *end; + REBYTE *str = Temp_Byte_Chars_May_Fail(arg, MAX_SCAN_MONEY, 0, FALSE); + Init_Money(out, string_to_deci(str, &end)); + if (end == str || *end != 0) + goto bad_make; + break; + } + +// case REB_ISSUE: + case REB_BINARY: + Bin_To_Money_May_Fail(out, arg); + break; + + case REB_LOGIC: + Init_Money(out, int_to_deci(VAL_LOGIC(arg) ? 1 : 0)); + break; + + default: + bad_make: + fail (Error_Bad_Make(REB_MONEY, arg)); + } + + VAL_RESET_HEADER(out, REB_MONEY); } -/*********************************************************************** -** -*/ REBINT Bin_To_Money(REBVAL *result, REBVAL *val) -/* -***********************************************************************/ +// +// TO_Money: C +// +void TO_Money(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBCNT len; - REBYTE buf[MAX_HEX_LEN+4] = {0}; // binary to convert - - if (IS_BINARY(val)) { - len = VAL_LEN(val); - if (len > 12) len = 12; - memcpy(buf, VAL_BIN_DATA(val), len); - } -#ifdef removed - else if (IS_ISSUE(val)) { - //if (!(len = Scan_Hex_Bytes(val, 24, buf))) return FALSE; - REBYTE *ap = Get_Word_Name(val); - REBYTE *bp = &buf[0]; - REBCNT alen; - REBUNI c; - len = LEN_BYTES(ap); // UTF-8 len - if (len & 1) return FALSE; // must have even # of chars - len /= 2; - if (len > 12) return FALSE; // valid even for UTF-8 - for (alen = 0; alen < len; alen++) { - if (!Scan_Hex2(ap, &c, 0)) return FALSE; - *bp++ = (REBYTE)c; - ap += 2; - } - } -#endif - memcpy(buf + 12 - len, buf, len); // shift to right side - memset(buf, 0, 12 - len); - VAL_DECI(result) = binary_to_deci(buf); - return TRUE; + MAKE_Money(out, kind, arg); } -/*********************************************************************** -** -*/ REBTYPE(Money) -/* -***********************************************************************/ +// +// Emit_Money: C +// +REBINT Emit_Money(const REBVAL *value, REBYTE *buf, REBFLGS opts) { - REBVAL *val = D_ARG(1); - REBVAL *arg; - REBYTE *str; - REBINT equal = 1; - - if (IS_BINARY_ACT(action)) { - arg = D_ARG(2); - - if (IS_MONEY(arg)) - ; - else if (IS_INTEGER(arg)) { - VAL_DECI(D_RET) = int_to_deci(VAL_INT64(arg)); - arg = D_RET; - } - else if (IS_DECIMAL(arg) || IS_PERCENT(arg)) { - VAL_DECI(D_RET) = decimal_to_deci(VAL_DECIMAL(arg)); - arg = D_RET; - } - else Trap_Math_Args(REB_MONEY, action); - - switch (action) { - case A_ADD: - VAL_DECI(D_RET) = deci_add(VAL_DECI(val), VAL_DECI(arg)); - break; - - case A_SUBTRACT: - VAL_DECI(D_RET) = deci_subtract(VAL_DECI(val), VAL_DECI(arg)); - break; - - case A_MULTIPLY: - VAL_DECI(D_RET) = deci_multiply(VAL_DECI(val), VAL_DECI(arg)); - break; - - case A_DIVIDE: - VAL_DECI(D_RET) = deci_divide(VAL_DECI(val), VAL_DECI(arg)); - break; - - case A_REMAINDER: - VAL_DECI(D_RET) = deci_mod(VAL_DECI(val), VAL_DECI(arg)); - break; - - default: - Trap_Action(REB_MONEY, action); - } - - SET_TYPE(D_RET, REB_MONEY); - return R_RET; - } - - switch(action) { - case A_NEGATE: - VAL_DECI(val).s = !VAL_DECI(val).s; - return R_ARG1; - - case A_ABSOLUTE: - VAL_DECI(val).s = 0; - return R_ARG1; - - case A_ROUND: - arg = D_ARG(3); - if (D_REF(2)) { - if (IS_INTEGER(arg)) VAL_DECI(arg) = int_to_deci(VAL_INT64(arg)); - else if (IS_DECIMAL(arg) || IS_PERCENT(arg)) VAL_DECI(arg) = decimal_to_deci(VAL_DECIMAL(arg)); - else if (!IS_MONEY(arg)) Trap_Arg(arg); - } - VAL_DECI(D_RET) = Round_Deci(VAL_DECI(val), Get_Round_Flags(ds), VAL_DECI(arg)); - if (D_REF(2)) { - if (IS_DECIMAL(arg) || IS_PERCENT(arg)) { - VAL_DECIMAL(D_RET) = deci_to_decimal(VAL_DECI(D_RET)); - SET_TYPE(D_RET, VAL_TYPE(arg)); - return R_RET; - } - if (IS_INTEGER(arg)) { - VAL_INT64(D_RET) = deci_to_int(VAL_DECI(D_RET));; - SET_TYPE(D_RET, REB_INTEGER); - return R_RET; - } - } - break; - - case A_EVENQ: - case A_ODDQ: - equal = 1 & (REBINT)deci_to_int(VAL_DECI(val)); - if (action == A_EVENQ) equal = !equal; - if (equal) goto is_true; - goto is_false; - - case A_MAKE: - case A_TO: - arg = D_ARG(2); - - switch (VAL_TYPE(arg)) { - - case REB_INTEGER: - VAL_DECI(D_RET) = int_to_deci(VAL_INT64(arg)); - break; - - case REB_DECIMAL: - case REB_PERCENT: - VAL_DECI(D_RET) = decimal_to_deci(VAL_DECIMAL(arg)); - break; - - case REB_MONEY: - return R_ARG2; - - case REB_STRING: - { - REBYTE *end; - str = Qualify_String(arg, 36, 0, FALSE); - VAL_DECI(D_RET) = string_to_deci(str, &end); - if (end == str || *end != 0) Trap_Make(REB_MONEY, arg); - break; - } - -// case REB_ISSUE: - case REB_BINARY: - if (!Bin_To_Money(D_RET, arg)) goto err; - break; - - case REB_LOGIC: - equal = !VAL_LOGIC(arg); -// case REB_NONE: // 'equal defaults to 1 - VAL_DECI(D_RET) = int_to_deci(equal ? 0 : 1); - break; - - default: - err: - Trap_Make(REB_MONEY, arg); - } - break; - - default: - Trap_Action(REB_MONEY, action); - } - - SET_TYPE(D_RET, REB_MONEY); - return R_RET; - -is_true: return R_TRUE; -is_false: return R_FALSE; + if (opts & MOPT_LIMIT) { + // !!! In theory, emits should pay attention to the mold options, + // at least the limit. + } + + return deci_to_string(buf, VAL_MONEY_AMOUNT(value), '$', '.'); +} + + +// +// Bin_To_Money_May_Fail: C +// +// Will successfully convert or fail (longjmp) with an error. +// +void Bin_To_Money_May_Fail(REBVAL *result, const REBVAL *val) +{ + REBCNT len; + REBYTE buf[MAX_HEX_LEN+4] = {0}; // binary to convert + + if (IS_BINARY(val)) { + len = VAL_LEN_AT(val); + if (len > 12) len = 12; + memcpy(buf, VAL_BIN_AT(val), len); + } + else + fail (val); + + memcpy(buf + 12 - len, buf, len); // shift to right side + memset(buf, 0, 12 - len); + Init_Money(result, binary_to_deci(buf)); +} + + +static REBVAL *Math_Arg_For_Money(REBVAL *store, REBVAL *arg, REBSYM action) +{ + if (IS_MONEY(arg)) + return arg; + + if (IS_INTEGER(arg)) { + Init_Money(store, int_to_deci(VAL_INT64(arg))); + return store; + } + + if (IS_DECIMAL(arg) || IS_PERCENT(arg)) { + Init_Money(store, decimal_to_deci(VAL_DECIMAL(arg))); + return store; + } + + fail (Error_Math_Args(REB_MONEY, action)); +} + + +// +// REBTYPE: C +// +REBTYPE(Money) +{ + REBVAL *val = D_ARG(1); + REBVAL *arg; + + switch (action) { + case SYM_ADD: + arg = Math_Arg_For_Money(D_OUT, D_ARG(2), action); + Init_Money(D_OUT, deci_add( + VAL_MONEY_AMOUNT(val), VAL_MONEY_AMOUNT(arg) + )); + break; + + case SYM_SUBTRACT: + arg = Math_Arg_For_Money(D_OUT, D_ARG(2), action); + Init_Money(D_OUT, deci_subtract( + VAL_MONEY_AMOUNT(val), VAL_MONEY_AMOUNT(arg) + )); + break; + + case SYM_MULTIPLY: + arg = Math_Arg_For_Money(D_OUT, D_ARG(2), action); + Init_Money(D_OUT, deci_multiply( + VAL_MONEY_AMOUNT(val), VAL_MONEY_AMOUNT(arg) + )); + break; + + case SYM_DIVIDE: + arg = Math_Arg_For_Money(D_OUT, D_ARG(2), action); + Init_Money(D_OUT, deci_divide( + VAL_MONEY_AMOUNT(val), VAL_MONEY_AMOUNT(arg) + )); + break; + + case SYM_REMAINDER: + arg = Math_Arg_For_Money(D_OUT, D_ARG(2), action); + Init_Money(D_OUT, deci_mod( + VAL_MONEY_AMOUNT(val), VAL_MONEY_AMOUNT(arg) + )); + break; + + case SYM_NEGATE: + val->payload.money.s = !val->payload.money.s; + Move_Value(D_OUT, D_ARG(1)); + return R_OUT; + + case SYM_ABSOLUTE: + val->payload.money.s = 0; + Move_Value(D_OUT, D_ARG(1)); + return R_OUT; + + case SYM_ROUND: { + INCLUDE_PARAMS_OF_ROUND; + + UNUSED(PAR(value)); + + REBFLGS flags = ( + (REF(to) ? RF_TO : 0) + | (REF(even) ? RF_EVEN : 0) + | (REF(down) ? RF_DOWN : 0) + | (REF(half_down) ? RF_HALF_DOWN : 0) + | (REF(floor) ? RF_FLOOR : 0) + | (REF(ceiling) ? RF_CEILING : 0) + | (REF(half_ceiling) ? RF_HALF_CEILING : 0) + ); + + REBVAL *scale = ARG(scale); + + DECLARE_LOCAL (temp); + if (REF(to)) { + if (IS_INTEGER(scale)) + Init_Money(temp, int_to_deci(VAL_INT64(scale))); + else if (IS_DECIMAL(scale) || IS_PERCENT(scale)) + Init_Money(temp, decimal_to_deci(VAL_DECIMAL(scale))); + else if (IS_MONEY(scale)) + Move_Value(temp, scale); + else + fail (scale); + } + else + Init_Money(temp, int_to_deci(0)); + + Init_Money(D_OUT, Round_Deci( + VAL_MONEY_AMOUNT(val), + flags, + VAL_MONEY_AMOUNT(temp) + )); + + if (REF(to)) { + if (IS_DECIMAL(scale) || IS_PERCENT(scale)) { + REBDEC dec = deci_to_decimal(VAL_MONEY_AMOUNT(D_OUT)); + VAL_RESET_HEADER(D_OUT, VAL_TYPE(scale)); + VAL_DECIMAL(D_OUT) = dec; + return R_OUT; + } + if (IS_INTEGER(scale)) { + REBI64 i64 = deci_to_int(VAL_MONEY_AMOUNT(D_OUT)); + VAL_RESET_HEADER(D_OUT, REB_INTEGER); + VAL_INT64(D_OUT) = i64; + return R_OUT; + } + } + break; } + + case SYM_EVEN_Q: + case SYM_ODD_Q: { + REBINT result = 1 & cast(REBINT, deci_to_int(VAL_MONEY_AMOUNT(val))); + if (action == SYM_EVEN_Q) result = !result; + return result ? R_TRUE : R_FALSE; } + + default: + fail (Error_Illegal_Action(REB_MONEY, action)); + } + + VAL_RESET_HEADER(D_OUT, REB_MONEY); + return R_OUT; } diff --git a/src/core/t-none.c b/src/core/t-none.c deleted file mode 100644 index b9029947f2..0000000000 --- a/src/core/t-none.c +++ /dev/null @@ -1,90 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-none.c -** Summary: none datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ - -#include "sys-core.h" - -/*********************************************************************** -** -*/ REBINT CT_None(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ -{ - if (mode >= 0) return (VAL_TYPE(a) == VAL_TYPE(b)); - return -1; -} - - -/*********************************************************************** -** -*/ REBFLG MT_None(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ -{ - VAL_SET(out, type); - return TRUE; -} - - -/*********************************************************************** -** -*/ REBTYPE(None) -/* -** ALSO used for unset! -** -***********************************************************************/ -{ - REBVAL *val = D_ARG(1); - - switch (action) { - - case A_MAKE: - case A_TO: - if (IS_DATATYPE(val)) - return VAL_DATATYPE(val) == REB_NONE ? R_NONE : R_UNSET; - else - return IS_NONE(val) ? R_NONE : R_UNSET; - - case A_TAILQ: - if (IS_NONE(val)) return R_TRUE; - goto trap_it; - case A_INDEXQ: - case A_LENGTHQ: - case A_SELECT: - case A_FIND: - case A_REMOVE: - case A_CLEAR: - case A_TAKE: - if (IS_NONE(val)) return R_NONE; - default: - trap_it: - Trap_Action(VAL_TYPE(val), action); - } - - return R_RET; -} diff --git a/src/core/t-object.c b/src/core/t-object.c old mode 100644 new mode 100755 index 2228e88abf..8ceea01811 --- a/src/core/t-object.c +++ b/src/core/t-object.c @@ -1,577 +1,933 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-object.c -** Summary: object datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-object.c +// Summary: "object datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -static REBOOL Same_Object(REBVAL *val, REBVAL *arg) + +static REBOOL Equal_Context(const RELVAL *val, const RELVAL *arg) { - if ( - VAL_TYPE(arg) == VAL_TYPE(val) && - //VAL_OBJ_SPEC(val) == VAL_OBJ_SPEC(arg) && - VAL_OBJ_FRAME(val) == VAL_OBJ_FRAME(arg) - ) return TRUE; - return FALSE; + REBCTX *f1; + REBCTX *f2; + REBVAL *key1; + REBVAL *key2; + REBVAL *var1; + REBVAL *var2; + + // ERROR! and OBJECT! may both be contexts, for instance, but they will + // not compare equal just because their keys and fields are equal + // + if (VAL_TYPE(arg) != VAL_TYPE(val)) return FALSE; + + f1 = VAL_CONTEXT(val); + f2 = VAL_CONTEXT(arg); + + // Short circuit equality: `same?` objects always equal + // + if (f1 == f2) return TRUE; + + // We can't short circuit on unequal frame lengths alone, because hidden + // fields of objects (notably `self`) do not figure into the `equal?` + // of their public portions. + + key1 = CTX_KEYS_HEAD(f1); + key2 = CTX_KEYS_HEAD(f2); + var1 = CTX_VARS_HEAD(f1); + var2 = CTX_VARS_HEAD(f2); + + // Compare each entry, in order. This order dependence suggests that + // an object made with `make object! [[a b][a: 1 b: 2]]` will not be equal + // to `make object! [[b a][b: 1 a: 2]]`. Although Rebol does not allow + // positional picking out of objects, it does allow positional setting + // currently (which it likely should not), hence they are functionally + // distinct for now. Yet those two should probably be `equal?`. + // + for (; NOT_END(key1) && NOT_END(key2); key1++, key2++, var1++, var2++) { + no_advance: + // + // Hidden vars shouldn't affect the comparison. + // + if (GET_VAL_FLAG(key1, TYPESET_FLAG_HIDDEN)) { + key1++; var1++; + if (IS_END(key1)) break; + goto no_advance; + } + if (GET_VAL_FLAG(key2, TYPESET_FLAG_HIDDEN)) { + key2++; var2++; + if (IS_END(key2)) break; + goto no_advance; + } + + // Do ordinary comparison of the typesets + // + if (Cmp_Value(key1, key2, FALSE) != 0) + return FALSE; + + // The typesets contain a symbol as well which must match for + // objects to consider themselves to be equal (but which do not + // count in comparison of the typesets) + // + if (VAL_KEY_CANON(key1) != VAL_KEY_CANON(key2)) + return FALSE; + + // !!! A comment here said "Use Compare_Modify_Values();"...but it + // doesn't... it calls Cmp_Value (?) + // + if (Cmp_Value(var1, var2, FALSE) != 0) + return FALSE; + } + + // Either key1 or key2 is at the end here, but the other might contain + // all hidden values. Which is okay. But if a value isn't hidden, + // they don't line up. + // + for (; NOT_END(key1); key1++, var1++) { + if (NOT_VAL_FLAG(key1, TYPESET_FLAG_HIDDEN)) + return FALSE; + } + for (; NOT_END(key2); key2++, var2++) { + if (NOT_VAL_FLAG(key2, TYPESET_FLAG_HIDDEN)) + return FALSE; + } + + return TRUE; } -static REBOOL Equal_Object(REBVAL *val, REBVAL *arg) +static void Append_To_Context(REBCTX *context, REBVAL *arg) { - REBSER *f1; - REBSER *f2; - REBSER *w1; - REBSER *w2; - REBINT n; - - if (VAL_TYPE(arg) != VAL_TYPE(val)) return FALSE; - - f1 = VAL_OBJ_FRAME(val); - f2 = VAL_OBJ_FRAME(arg); - if (f1 == f2) return TRUE; - if (f1->tail != f2->tail) return FALSE; - - w1 = FRM_WORD_SERIES(f1); - w2 = FRM_WORD_SERIES(f2); - if (w1->tail != w2->tail) return FALSE; - - // Compare each entry: - for (n = 1; n < (REBINT)(f1->tail); n++) { - if (Cmp_Value(BLK_SKIP(w1, n), BLK_SKIP(w2, n), FALSE)) return FALSE; - // Use Compare_Values(); - if (Cmp_Value(BLK_SKIP(f1, n), BLK_SKIP(f2, n), FALSE)) return FALSE; - } - - return TRUE; + // Can be a word: + if (ANY_WORD(arg)) { + if (0 == Find_Canon_In_Context(context, VAL_WORD_CANON(arg), TRUE)) { + Expand_Context(context, 1); // copy word table also + Append_Context(context, 0, VAL_WORD_SPELLING(arg)); + // default of Append_Context is that arg's value is void + } + return; + } + + if (NOT(IS_BLOCK(arg))) + fail (arg); + + // Process word/value argument block: + + RELVAL *item = VAL_ARRAY_AT(arg); + + struct Reb_Binder binder; + INIT_BINDER(&binder); + + Collect_Keys_Start(COLLECT_ANY_WORD); + + // Setup binding table with obj words. Binding table is empty so don't + // bother checking for duplicates. + // + Collect_Context_Keys(&binder, context, FALSE); + + // Examine word/value argument block + + RELVAL *word; + for (word = item; NOT_END(word); word += 2) { + if (!IS_WORD(word) && !IS_SET_WORD(word)) + fail (Error_Invalid_Arg_Core(word, VAL_SPECIFIER(arg))); + + REBSTR *canon = VAL_WORD_CANON(word); + + if (Try_Add_Binder_Index(&binder, canon, ARR_LEN(BUF_COLLECT))) { + // + // Wasn't already collected...so we added it... + // + EXPAND_SERIES_TAIL(SER(BUF_COLLECT), 1); + Init_Typeset( + ARR_LAST(BUF_COLLECT), ALL_64, VAL_WORD_SPELLING(word) + ); + } + if (IS_END(word + 1)) break; // fix bug#708 + } + + TERM_ARRAY_LEN(BUF_COLLECT, ARR_LEN(BUF_COLLECT)); + + // Append new words to obj + // + REBCNT len = CTX_LEN(context) + 1; + Expand_Context(context, ARR_LEN(BUF_COLLECT) - len); + + RELVAL *key; + for (key = ARR_AT(BUF_COLLECT, len); NOT_END(key); key++) { + assert(IS_TYPESET(key)); + Append_Context(context, NULL, VAL_KEY_SPELLING(key)); + } + + // Set new values to obj words + for (word = item; NOT_END(word); word += 2) { + REBCNT i = Try_Get_Binder_Index(&binder, VAL_WORD_CANON(word)); + assert(i != 0); + + REBVAL *key = CTX_KEY(context, i); + REBVAL *var = CTX_VAR(context, i); + + if (GET_VAL_FLAG(var, VALUE_FLAG_PROTECTED)) + fail (Error_Protected_Key(key)); + + if (GET_VAL_FLAG(key, TYPESET_FLAG_HIDDEN)) + fail (Error_Hidden_Raw()); + + if (IS_END(word + 1)) { + Init_Blank(var); + break; // fix bug#708 + } + else { + Derelativize(var, &word[1], VAL_SPECIFIER(arg)); + + // Should the VALUE_FLAG_ENFIXED be preserved here? + // + if (GET_VAL_FLAG(&word[1], VALUE_FLAG_ENFIXED)) + SET_VAL_FLAG(var, VALUE_FLAG_ENFIXED); + + } + } + + // release binding table + Collect_Keys_End(&binder); + + SHUTDOWN_BINDER(&binder); } -static void Append_Obj(REBSER *obj, REBVAL *arg) + +static REBCTX *Trim_Context(REBCTX *context) { - REBCNT i; - REBCNT len = 0; - REBVAL *val; - REBVAL *start = arg; - - // Can be a word: - if (ANY_WORD(arg)) { - if (!Find_Word_Index(obj, VAL_WORD_SYM(arg), TRUE)) { - if (VAL_WORD_CANON(arg) == SYM_SELF) Trap0(RE_SELF_PROTECTED); - Expand_Frame(obj, 1, 1); // copy word table also - Append_Frame(obj, 0, VAL_WORD_SYM(arg)); - // val is UNSET - } - return; - } - - if (!IS_BLOCK(arg)) Trap_Arg(arg); - - // Verify word/value argument block: - for (arg = VAL_BLK_DATA(arg); NOT_END(arg); arg += 2) { - - if (!IS_WORD(arg) && !IS_SET_WORD(arg)) Trap_Arg(arg); - - if (NZ(i = Find_Word_Index(obj, VAL_WORD_SYM(arg), TRUE))) { - // Just change the value, do not append it. - val = FRM_VALUE(obj, i); - if (GET_FLAGS(VAL_OPTS(FRM_WORD(obj, i)), OPTS_HIDE, OPTS_LOCK)) { - // Back out... reset any prior flags: - for (; arg != VAL_BLK_DATA(start); arg -= 2) VAL_CLR_OPT(arg, OPTS_TEMP); - if (VAL_PROTECTED(FRM_WORD(obj, i))) Trap1(RE_LOCKED_WORD, FRM_WORD(obj, i)); - Trap0(RE_HIDDEN); - } - // Problem above: what about prior OPTS_FLAGS? Ok to leave them as is? - if (IS_END(arg+1)) SET_NONE(val); - else *val = arg[1]; - VAL_SET_OPT(arg, OPTS_TEMP); - } else { - if (VAL_WORD_CANON(arg) == SYM_SELF) Trap0(RE_SELF_PROTECTED); - len++; - // was: Trap1(RE_DUP_VARS, arg); - } - - if (IS_END(arg+1)) break; // fix bug#708 - } - - // Append new values to end of frame (if necessary): - if (len > 0) { - Expand_Frame(obj, len, 1); // copy word table also - for (arg = VAL_BLK_DATA(start); NOT_END(arg); arg += 2) { - if (VAL_GET_OPT(arg, OPTS_TEMP)) VAL_CLR_OPT(arg, OPTS_TEMP); - else { - val = Append_Frame(obj, 0, VAL_WORD_SYM(arg)); - if (IS_END(arg+1)) { - SET_NONE(val); - break; - } - else *val = arg[1]; - } - } - } + REBVAL *key; + REBVAL *var; + + REBCNT copy_count = 0; + + // First pass: determine size of new context to create by subtracting out + // any void (unset fields), NONE!, or hidden fields + // + key = CTX_KEYS_HEAD(context); + var = CTX_VARS_HEAD(context); + for (; NOT_END(var); var++, key++) { + if (VAL_TYPE(var) == REB_BLANK) + continue; + if (GET_VAL_FLAG(key, TYPESET_FLAG_HIDDEN)) + continue; + + ++copy_count; + } + + // Create new context based on the size found + // + REBCTX *trimmed = Alloc_Context(VAL_TYPE(CTX_VALUE(context)), copy_count); + + // Second pass: copy the values that were not skipped in the first pass + // + key = CTX_KEYS_HEAD(context); + var = CTX_VARS_HEAD(context); + + REBVAL *var_new = CTX_VARS_HEAD(trimmed); + REBVAL *key_new = CTX_KEYS_HEAD(trimmed); + + for (; NOT_END(var); var++, key++) { + if (VAL_TYPE(var) == REB_BLANK) + continue; + if (GET_VAL_FLAG(key, TYPESET_FLAG_HIDDEN)) + continue; + + Move_Value(var_new, var); + ++var_new; + Move_Value(key_new, key); + ++key_new; + } + + // Terminate the new context + // + TERM_ARRAY_LEN(CTX_VARLIST(trimmed), copy_count + 1); + TERM_ARRAY_LEN(CTX_KEYLIST(trimmed), copy_count + 1); + + return trimmed; } -static REBSER *Trim_Object(REBSER *obj) + +// +// CT_Context: C +// +REBINT CT_Context(const RELVAL *a, const RELVAL *b, REBINT mode) { - REBVAL *val; - REBINT cnt = 0; - REBSER *nobj; - REBVAL *nval; - REBVAL *word; - REBVAL *nwrd; - - word = FRM_WORDS(obj)+1; - for (val = FRM_VALUES(obj)+1; NOT_END(val); val++, word++) { - if (VAL_TYPE(val) > REB_NONE && !VAL_GET_OPT(word, OPTS_HIDE)) - cnt++; - } - - nobj = Make_Frame(cnt); - nval = FRM_VALUES(nobj)+1; - word = FRM_WORDS(obj)+1; - nwrd = FRM_WORDS(nobj)+1; - for (val = FRM_VALUES(obj)+1; NOT_END(val); val++, word++) { - if (VAL_TYPE(val) > REB_NONE && !VAL_GET_OPT(word, OPTS_HIDE)) { - *nval++ = *val; - *nwrd++ = *word; - } - } - SET_END(nval); - SET_END(nwrd); - SERIES_TAIL(nobj) = cnt+1; - SERIES_TAIL(FRM_WORD_SERIES(nobj)) = cnt+1; - - return nobj; + if (mode < 0) return -1; + return Equal_Context(a, b) ? 1 : 0; } -/*********************************************************************** -** -*/ REBINT CT_Object(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// MAKE_Context: C +// +void MAKE_Context(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - if (mode < 0) return -1; - if (mode == 3) return Same_Object(a, b); - return Equal_Object(a, b); + if (kind == REB_FRAME) { + // + // !!! Current experiment for making frames lets you give it + // a FUNCTION! only. + // + if (!IS_FUNCTION(arg)) + fail (Error_Bad_Make(kind, arg)); + + // In order to have the frame survive the call to MAKE and be + // returned to the user it can't be stack allocated, because it + // would immediately become useless. Allocate dynamically. + // + Init_Any_Context(out, REB_FRAME, Make_Frame_For_Function(arg)); + + // The frame's keylist is the same as the function's paramlist, and + // the [0] canon value of that array can be used to find the + // archetype of the function. But if the `arg` is a RETURN with a + // binding in the REBVAL to where to return from, that unique + // instance information must be carried in the REBVAL of the context. + // + assert(VAL_BINDING(out) == VAL_BINDING(arg)); + return; + } + + if (kind == REB_OBJECT && IS_BLANK(arg)) { + // + // Special case (necessary?) to return an empty object. + // + Init_Object( + out, + Construct_Context( + REB_OBJECT, + NULL, // head + SPECIFIED, + NULL + ) + ); + return; + } + + if (kind == REB_OBJECT && IS_BLOCK(arg)) { + // + // Simple object creation with no evaluation, so all values are + // handled "as-is". Should have a spec block and a body block. + // + // Note: In %r3-legacy.r, the old evaluative MAKE OBJECT! is + // done by redefining MAKE itself, and calling the CONSTRUCT + // generator if the make def is not the [[spec][body]] format. + + if ( + VAL_LEN_AT(arg) != 2 + || !IS_BLOCK(VAL_ARRAY_AT(arg)) // spec + || !IS_BLOCK(VAL_ARRAY_AT(arg) + 1) // body + ) { + fail (Error_Bad_Make(kind, arg)); + } + + // !!! Spec block is currently ignored, but required. + + Init_Object( + out, + Construct_Context( + REB_OBJECT, + VAL_ARRAY_AT(VAL_ARRAY_AT(arg) + 1), + VAL_SPECIFIER(arg), + NULL // no parent + ) + ); + + return; + } + + // make error! [....] + // + // arg is block/string, but let Make_Error_Object_Throws do the + // type checking. + // + if (kind == REB_ERROR) { + // + // !!! Evaluation should not happen during a make. FAIL should + // be the primitive that does the evaluations, and then call + // into this with the reduced block. + // + if (Make_Error_Object_Throws(out, arg)) + fail (Error_No_Catch_For_Throw(out)); + + return; + } + + // `make object! 10` - currently not prohibited for any context type + // + if (ANY_NUMBER(arg)) { + // + // !!! Temporary! Ultimately SELF will be a user protocol. + // We use Make_Selfish_Context while MAKE is filling in for + // what will be responsibility of the generators, just to + // get "completely fake SELF" out of index slot [0] + // + REBCTX *context = Make_Selfish_Context_Detect( + kind, // type + END, // values to scan for toplevel set-words (empty) + NULL // parent + ); + + // !!! Allocation when SELF is not the responsibility of MAKE + // will be more basic and look like this. + // + /* + REBINT n = Int32s(arg, 0); + context = Alloc_Context(kind, n); + VAL_RESET_HEADER(CTX_VALUE(context), target); + CTX_SPEC(context) = NULL; + CTX_BODY(context) = NULL; */ + Init_Any_Context(out, kind, context); + + return; + } + + // make object! map! + if (IS_MAP(arg)) { + REBCTX *context = Alloc_Context_From_Map(VAL_MAP(arg)); + Init_Any_Context(out, kind, context); + return; + } + + fail (Error_Bad_Make(kind, arg)); } -/*********************************************************************** -** -*/ REBINT CT_Frame(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// TO_Context: C +// +void TO_Context(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - if (mode < 0) return -1; - return VAL_SERIES(a) == VAL_SERIES(b); + if (kind == REB_ERROR) { + // + // arg is checked to be block or string + // + if (Make_Error_Object_Throws(out, arg)) + fail (Error_No_Catch_For_Throw(out)); + + return; + } + + if (kind == REB_OBJECT) { + if (IS_ERROR(arg)) { + if (VAL_ERR_NUM(arg) < 100) + fail (arg); + } + + // !!! Contexts hold canon values now that are typed, this init + // will assert--a TO conversion would thus need to copy the varlist + // + Init_Object(out, VAL_CONTEXT(arg)); + return; + } + + fail (Error_Bad_Make(kind, arg)); } - -/*********************************************************************** -** -*/ REBFLG MT_Object(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ +// +// PD_Context: C +// +REBINT PD_Context(REBPVS *pvs) { - if (!IS_BLOCK(data)) return FALSE; - VAL_OBJ_FRAME(out) = Construct_Object(0, VAL_BLK_DATA(data), 0); - VAL_SET(out, type); - if (type == REB_ERROR) { - Make_Error_Object(out, out); - } - return TRUE; + REBCTX *c = VAL_CONTEXT(pvs->value); + + if (NOT(IS_WORD(pvs->picker))) + fail (Error_Bad_Path_Select(pvs)); + + REBCNT n = Find_Canon_In_Context( + c, VAL_WORD_CANON(pvs->picker), FALSE + ); + + if (n == 0) { + // + // !!! The logic for allowing a GET-PATH! to be void if it's the last + // lookup that fails here is hacked in, but desirable for parity + // with the behavior of GET-WORD! + // + if (IS_GET_PATH(pvs->orig) && IS_END(pvs->item + 1)) { + Init_Void(pvs->store); + return PE_USE_STORE; + } + fail (Error_Bad_Path_Select(pvs)); + } + + if (CTX_VARS_UNAVAILABLE(c)) + fail (Error_No_Relative_Raw(pvs->picker)); + + if (pvs->opt_setval && IS_END(pvs->item + 1)) { + FAIL_IF_READ_ONLY_CONTEXT(c); + + if (GET_VAL_FLAG(CTX_VAR(c, n), VALUE_FLAG_PROTECTED)) + fail (Error_Protected_Word_Raw(pvs->picker)); + } + + pvs->value = CTX_VAR(c, n); + pvs->value_specifier = SPECIFIED; + + return PE_SET_IF_END; } -/*********************************************************************** -** -*/ REBINT PD_Object(REBPVS *pvs) -/* -***********************************************************************/ +// +// meta-of: native [ +// +// {Get a reference to the "meta" object associated with a value.} +// +// value [function! object! module!] +// ] +// +REBNATIVE(meta_of) +// +// The first implementation of linking a "meta object" to another object +// originates from the original module system--where it was called the +// "module spec". By moving it out of object REBVALs to the misc field of +// a keylist, it becomes possible to change the meta object and have that +// change seen by all references. +// +// As modules are still the first client of this meta information, it works +// a similar way. It is mutable by all references by default, unless +// it is protected. +// +// !!! This feature is under development and expected to extend to functions +// and possibly other types of values--both as the meta information, and +// as being able to have the meta information. { - REBINT n = 0; - - if (!VAL_OBJ_FRAME(pvs->value)) { - return PE_NONE; // Error objects may not have a frame. - } - - if (IS_WORD(pvs->select)) { - n = Find_Word_Index(VAL_OBJ_FRAME(pvs->value), VAL_WORD_SYM(pvs->select), FALSE); - } -// else if (IS_INTEGER(pvs->select)) { -// n = Int32s(pvs->select, 1); -// } - else return PE_BAD_SELECT; - - if (n <= 0 || (REBCNT)n >= SERIES_TAIL(VAL_OBJ_FRAME(pvs->value))) - return PE_BAD_SELECT; - - if (pvs->setval && IS_END(pvs->path+1) && VAL_PROTECTED(VAL_FRM_WORD(pvs->value, n))) - Trap1(RE_LOCKED_WORD, pvs->select); - - pvs->value = VAL_OBJ_VALUES(pvs->value) + n; - return PE_SET; - // if setval, check PROTECT mode!!! - // VAL_FLAGS((VAL_OBJ_VALUES(value) + n)) &= ~FLAGS_CLEAN; + INCLUDE_PARAMS_OF_META_OF; + + REBVAL *value = ARG(value); + + REBCTX *meta; + if (IS_FUNCTION(value)) + meta = VAL_FUNC_META(value); + else { + assert(ANY_CONTEXT(value)); + meta = VAL_CONTEXT_META(value); + } + + if (!meta) return R_BLANK; + + Init_Object(D_OUT, meta); + return R_OUT; } -/*********************************************************************** -** -*/ REBTYPE(Object) -/* -** Handles object! and error! datatypes. -** -***********************************************************************/ +// +// set-meta: native [ +// +// {Set "meta" object associated with all references to a value.} +// +// return: [] +// value [function! object! module!] +// meta [object! blank!] +// ] +// +REBNATIVE(set_meta) +// +// !!! You cannot currently put meta information onto a FRAME!, because the +// slot where the meta information would go is where the meta information +// would live for the function--since frames use a functions "paramlist" +// as their keylist. Types taken are deliberately narrow for the moment. { - REBVAL *value = D_ARG(1); - REBVAL *arg = D_ARG(2); - REBINT n; - REBVAL *val; - REBSER *obj, *src_obj; - REBCNT type = 0; - - switch (action) { - - case A_MAKE: - // make object! | error! | module! | task! - if (IS_DATATYPE(value)) { - - type = VAL_DATATYPE(value); // target type - - if (IS_BLOCK(arg)) { - - // make object! [init] - if (type == REB_OBJECT) { - obj = Make_Object(0, VAL_BLK_DATA(arg)); - SET_OBJECT(ds, obj); // GC save - arg = Do_Bind_Block(obj, arg); // GC-OK - if (THROWN(arg)) { - DS_RET_VALUE(arg); - return R_RET; - } - break; // returns obj - } - - if (type == REB_MODULE) { - *value = *Make_Module(arg); - type = 0; - // VAL_MOD_BODY(value) = VAL_SERIES(arg); - // VAL_SET(value, REB_MODULE); // GC protected - // DO_BLK(arg); - break; // returns value - } - - // make task! [init] - if (type == REB_TASK) { - // Does it include a spec? - if (IS_BLOCK(VAL_BLK(arg))) { - arg = VAL_BLK(arg); - if (!IS_BLOCK(arg+1)) Trap_Make(REB_TASK, value); - obj = Make_Module_Spec(arg); - VAL_MOD_BODY(value) = VAL_SERIES(arg+1); - } else { - obj = Make_Module_Spec(0); - VAL_MOD_BODY(value) = VAL_SERIES(arg); - } - break; // returns obj - } - } - - // make error! [....] - if (type == REB_ERROR) { - Make_Error_Object(arg, value); // arg is block/string, returns value - type = 0; - break; // returns value - } - - // make object! 10 - if (IS_NUMBER(arg)) { - n = Int32s(arg, 0); - obj = Make_Frame(n); - break; // returns obj - } - - // make object! map! - if (IS_MAP(arg)) { - obj = Map_To_Object(VAL_SERIES(arg)); - break; // returns obj - } - - //if (IS_NONE(arg)) {obj = Make_Frame(0); break;} - - Trap_Make(type, arg); - } - - // make parent-object .... - if (IS_OBJECT(value)) { - type = REB_OBJECT; - src_obj = VAL_OBJ_FRAME(value); - - // make parent none | [] - if (IS_NONE(arg) || (IS_BLOCK(arg) && IS_EMPTY(arg))) { - obj = Copy_Block_Values(src_obj, 0, SERIES_TAIL(src_obj), TS_CLONE); - Rebind_Frame(src_obj, obj); - break; // returns obj - } - - // make parent [...] - if (IS_BLOCK(arg)) { - obj = Make_Object(src_obj, VAL_BLK_DATA(arg)); - Rebind_Frame(src_obj, obj); - SET_OBJECT(ds, obj); - arg = Do_Bind_Block(obj, arg); // GC-OK - if (THROWN(arg)) { - DS_RET_VALUE(arg); - return R_RET; - } - break; // returns obj - } - - // make parent-object object - if (IS_OBJECT(arg)) { - obj = Merge_Frames(src_obj, VAL_OBJ_FRAME(arg)); - break; // returns obj - } - } - Trap_Make(VAL_TYPE(value), value); - - case A_TO: - // special conversions to object! | error! | module! - if (IS_DATATYPE(value)) { - type = VAL_DATATYPE(value); - if (type == REB_ERROR) { - Make_Error_Object(arg, value); // arg is block/string, returns value - type = 0; // type already set - break; // returns value - } - else if (type == REB_OBJECT) { - if (IS_ERROR(arg)) { - if (VAL_ERR_NUM(arg) < 100) Trap_Arg(arg); - obj = VAL_ERR_OBJECT(arg); - break; // returns obj - } - } - else if (type == REB_MODULE) { - if (!IS_BLOCK(arg) || IS_EMPTY(arg)) Trap_Make(REB_MODULE, arg); - val = VAL_BLK_DATA(arg); // module spec - if (!IS_OBJECT(val)) Trap_Arg(val); - obj = VAL_OBJ_FRAME(val); - val++; // module object - if (!IS_OBJECT(val)) Trap_Arg(val); - VAL_MOD_SPEC(val) = obj; - *value = *val; - VAL_SET(value, REB_MODULE); - type = 0; // type already set - break; // returns value - } - } - else type = VAL_TYPE(value); - Trap_Make(type, arg); - - case A_APPEND: - TRAP_PROTECT(VAL_SERIES(value)); - if (IS_OBJECT(value)) { - Append_Obj(VAL_OBJ_FRAME(value), arg); - return R_ARG1; - } - else - Trap_Action(VAL_TYPE(value), action); // !!! needs better error - - case A_LENGTHQ: - if (IS_OBJECT(value)) { - DS_RET_INT(SERIES_TAIL(VAL_OBJ_FRAME(value))-1); - return R_RET; - } - Trap_Action(VAL_TYPE(value), action); - - case A_COPY: - // Note: words are not copied and bindings not changed! - { - REBU64 types = 0; - if (D_REF(ARG_COPY_PART)) Trap0(RE_BAD_REFINES); - if (D_REF(ARG_COPY_DEEP)) { - types |= CP_DEEP | (D_REF(ARG_COPY_TYPES) ? 0 : TS_STD_SERIES); - } - if D_REF(ARG_COPY_TYPES) { - arg = D_ARG(ARG_COPY_KINDS); - if (IS_DATATYPE(arg)) types |= TYPESET(VAL_DATATYPE(arg)); - else types |= VAL_TYPESET(arg); - } - VAL_OBJ_FRAME(value) = obj = Copy_Block(VAL_OBJ_FRAME(value), 0); - if (types != 0) Copy_Deep_Values(obj, 1, SERIES_TAIL(obj), types); - break; // returns value - } - case A_SELECT: - case A_FIND: - n = 0; - if (IS_WORD(arg)) - n = Find_Word_Index(VAL_OBJ_FRAME(value), VAL_WORD_SYM(arg), FALSE); - - if (n <= 0 || (REBCNT)n >= SERIES_TAIL(VAL_OBJ_FRAME(value))) - return R_NONE; - - if (action == A_FIND) goto is_true; - - value = VAL_OBJ_VALUES(value) + n; - break; - - case A_REFLECT: - action = What_Reflector(arg); // zero on error - if (action == OF_SPEC) { - if (!VAL_MOD_SPEC(value)) return R_NONE; - VAL_OBJ_FRAME(value) = VAL_MOD_SPEC(value); - VAL_SET(value, REB_OBJECT); - break; - } - // Adjust for compatibility with PICK: - if (action == OF_VALUES) action = 2; - else if (action == OF_BODY) action = 3; - if (action < 1 || action > 3) Trap_Reflect(VAL_TYPE(value), arg); -#ifdef obsolete - goto reflect; - - case A_PICK: - action = Get_Num_Arg(arg); // integer, decimal, logic - if (action < 1 || action > 3) Trap_Arg(arg); - if (action < 3) action |= 4; // add SELF to list -reflect: -#endif - if (THROWN(value)) Trap0(RE_THROW_USAGE); - Set_Block(value, Make_Object_Block(VAL_OBJ_FRAME(value), action)); - break; - - case A_TRIM: - if (Find_Refines(ds, ALL_TRIM_REFS)) Trap0(RE_BAD_REFINES); // none allowed - type = VAL_TYPE(value); - obj = Trim_Object(VAL_OBJ_FRAME(value)); - break; - - case A_TAILQ: - if (IS_OBJECT(value)) { - SET_LOGIC(DS_RETURN, SERIES_TAIL(VAL_OBJ_FRAME(value)) <= 1); - return R_RET; - } - Trap_Action(VAL_TYPE(value), action); - - default: - Trap_Action(VAL_TYPE(value), action); - } - - if (type) { - VAL_SET(value, type); - VAL_OBJ_FRAME(value) = obj; - } - - DS_RET_VALUE(value); - return R_RET; - -is_true: - return R_TRUE; + INCLUDE_PARAMS_OF_SET_META; + + REBCTX *meta; + if (ANY_CONTEXT(ARG(meta))) { + meta = VAL_CONTEXT(ARG(meta)); + } + else { + assert(IS_BLANK(ARG(meta))); + meta = NULL; + } + + REBVAL *value = ARG(value); + + if (IS_FUNCTION(value)) + SER(VAL_FUNC_PARAMLIST(value))->link.meta = meta; + else { + assert(ANY_CONTEXT(value)); + INIT_CONTEXT_META(VAL_CONTEXT(value), meta); + } + + return R_VOID; } -/*********************************************************************** -** -*/ REBINT PD_Frame(REBPVS *pvs) -/* -** pvs->value points to the first value in frame (SELF). -** -***********************************************************************/ +// +// Copy_Context_Core: C +// +// R3-Alpha hadn't factored out a routine to copy objects, it was just in the +// COPY action. This is a basic factoring of that, which had the ability to +// specify what types would be copied and whether they would be done deeply. +// +REBCTX *Copy_Context_Core(REBCTX *original, REBOOL deep, REBU64 types) { - REBCNT sym; - REBCNT s; - REBVAL *word; - REBVAL *val; - - if (IS_WORD(pvs->select)) { - sym = VAL_WORD_SYM(pvs->select); - s = SYMBOL_TO_CANON(sym); - word = BLK_SKIP(VAL_FRM_WORDS(pvs->value), 1); - for (val = pvs->value + 1; NOT_END(val); val++, word++) { - if (sym == VAL_BIND_SYM(word) || s == VAL_BIND_CANON(word)) { - if (VAL_GET_OPT(word, OPTS_HIDE)) break; - if (VAL_PROTECTED(word)) Trap1(RE_LOCKED_WORD, word); - pvs->value = val; - return PE_SET; - } - } - } - return PE_BAD_SELECT; + REBARR *varlist = Copy_Array_Shallow(CTX_VARLIST(original), SPECIFIED); + SET_SER_FLAG(varlist, ARRAY_FLAG_VARLIST); + + // The type information and fields in the rootvar (at head of the varlist) + // are filled in because it's a copy, but the varlist needs to be updated + // in the copy to the one just created. + // + ARR_HEAD(varlist)->payload.any_context.varlist = varlist; + + REBCTX *copy = CTX(varlist); // now a well-formed context + + // Reuse the keylist of the original. (If the context of the source or + // the copy are expanded, the sharing is unlinked and a copy is made). + // + INIT_CTX_KEYLIST_SHARED(copy, CTX_KEYLIST(original)); + + if (types != 0) { + Clonify_Values_Len_Managed( + CTX_VARS_HEAD(copy), + SPECIFIED, + CTX_LEN(copy), + deep, + types + ); + } + + return copy; } -/*********************************************************************** -** -*/ REBTYPE(Frame) -/* -***********************************************************************/ +// +// REBTYPE: C +// +// Handles object!, module!, and error! datatypes. +// +REBTYPE(Context) { - switch (action) { - case A_MAKE: - case A_TO: - Trap_Make(REB_FRAME, D_ARG(2)); - } - - return R_ARG1; + REBVAL *value = D_ARG(1); + REBVAL *arg = D_ARGC > 1 ? D_ARG(2) : NULL; + + switch (action) { + case SYM_APPEND: + FAIL_IF_READ_ONLY_CONTEXT(VAL_CONTEXT(value)); + if (!IS_OBJECT(value) && !IS_MODULE(value)) + fail (Error_Illegal_Action(VAL_TYPE(value), action)); + Append_To_Context(VAL_CONTEXT(value), arg); + Move_Value(D_OUT, D_ARG(1)); + return R_OUT; + + case SYM_LENGTH_OF: + if (!IS_OBJECT(value)) + fail (Error_Illegal_Action(VAL_TYPE(value), action)); + Init_Integer(D_OUT, CTX_LEN(VAL_CONTEXT(value))); + return R_OUT; + + case SYM_COPY: { // Note: words are not copied and bindings not changed! + INCLUDE_PARAMS_OF_COPY; + + UNUSED(PAR(value)); + + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + + REBU64 types; + if (REF(types)) { + if (IS_DATATYPE(ARG(kinds))) + types = FLAGIT_KIND(VAL_TYPE_KIND(ARG(kinds))); + else + types = VAL_TYPESET_BITS(ARG(kinds)); + } + else if (REF(deep)) + types = TS_STD_SERIES; + else + types = 0; + + Init_Any_Context( + D_OUT, + VAL_TYPE(value), + Copy_Context_Core(VAL_CONTEXT(value), REF(deep), types) + ); + return R_OUT; } + + case SYM_SELECT_P: + case SYM_FIND: { + if (!IS_WORD(arg)) + return R_BLANK; + + REBCNT n = Find_Canon_In_Context( + VAL_CONTEXT(value), VAL_WORD_CANON(arg), FALSE + ); + + if (n == 0) + return R_BLANK; + + if (cast(REBCNT, n) > CTX_LEN(VAL_CONTEXT(value))) + return R_BLANK; + + if (action == SYM_FIND) return R_TRUE; + + Move_Value(D_OUT, CTX_VAR(VAL_CONTEXT(value), n)); + return R_OUT; + } + + case SYM_REFLECT: { + REBSYM sym = VAL_WORD_SYM(arg); + REBCNT reflector; + + switch (sym) { + case SYM_WORDS: reflector = 1; break; + case SYM_VALUES: reflector = 2; break; + case SYM_BODY: reflector = 3; break; + default: + fail (Error_Cannot_Reflect(VAL_TYPE(value), arg)); + } + + Init_Block(D_OUT, Context_To_Array(VAL_CONTEXT(value), reflector)); + return R_OUT; } + + case SYM_TRIM: { + INCLUDE_PARAMS_OF_TRIM; + + UNUSED(ARG(series)); + + if ( + REF(head) || REF(tail) + || REF(auto) || REF(all) || REF(lines) + ){ + fail (Error_Bad_Refines_Raw()); + } + + if (REF(with)) { + UNUSED(ARG(str)); + fail (Error_Bad_Refines_Raw()); + } + + Init_Any_Context( + D_OUT, + VAL_TYPE(value), + Trim_Context(VAL_CONTEXT(value)) + ); + return R_OUT; } + + case SYM_TAIL_Q: + if (IS_OBJECT(value)) { + Init_Logic(D_OUT, LOGICAL(CTX_LEN(VAL_CONTEXT(value)) == 0)); + return R_OUT; + } + fail (Error_Illegal_Action(VAL_TYPE(value), action)); + + default: + break; + } + + fail (Error_Illegal_Action(VAL_TYPE(value), action)); } -#ifdef later -/*********************************************************************** -** -** Get_Obj_Mods -- return a block of modified words from an object -** -***********************************************************************/ -REBVAL *Get_Obj_Mods(REBFRM *frame, REBVAL **inter_block) +// +// construct: native [ +// +// "Creates an ANY-CONTEXT! instance" +// +// spec [datatype! block! any-context!] +// "Datatype to create, specification, or parent/prototype context" +// body [block! any-context! blank!] +// "keys and values defining instance contents (bindings modified)" +// /only +// "Values are kept as-is" +// ] +// +REBNATIVE(construct) +// +// CONSTRUCT in Ren-C is an effective replacement for what MAKE ANY-OBJECT! +// was able to do in Rebol2 and R3-Alpha. It takes a spec that can be an +// ANY-CONTEXT! datatype, or it can be a parent ANY-CONTEXT!, or a block that +// represents a "spec". +// +// !!! This assumes you want a SELF defined. The entire concept of SELF +// needs heavy review, but at minimum this needs an override to match the +// ` return` or ` local` for functions. +// +// !!! This mutates the bindings of the body block passed in, should it +// be making a copy instead (at least by default, perhaps with performance +// junkies saying `construct/rebind` or something like that? { - REBVAL *obj = D_ARG(1); - REBVAL *words, *val; - REBFRM *frm = VAL_OBJ_FRAME(obj); - REBSER *ser = Make_Block(2); - REBOOL clear = D_REF(2); - //DISABLE_GC; - - val = BLK_HEAD(frm->values); - words = BLK_HEAD(frm->words); - for (; NOT_END(val); val++, words++) - if (!(VAL_FLAGS(val) & FLAGS_CLEAN)) { - Append_Val(ser, words); - if (clear) VAL_FLAGS(val) |= FLAGS_CLEAN; - } - if (!STR_LEN(ser)) { - ENABLE_GC; - goto is_none; - } - - Bind_Block(frm, BLK_HEAD(ser), FALSE); - VAL_SERIES(Temp_Blk_Value) = ser; - //ENABLE_GC; - return Temp_Blk_Value; + INCLUDE_PARAMS_OF_CONSTRUCT; + + REBVAL *spec = ARG(spec); + REBVAL *body = ARG(body); + REBCTX *parent = NULL; + + enum Reb_Kind target; + REBCTX *context; + + if (IS_STRUCT(spec)) { + // + // !!! Compatibility for `MAKE struct [...]` from Atronix R3. There + // isn't any real "inheritance management" for structs but it allows + // the re-use of the structure's field definitions, so it is a means + // of saving on memory (?) + // + REBSTU *stu = Copy_Struct_Managed(VAL_STRUCT(spec)); + + Move_Value(D_OUT, STU_VALUE(stu)); + + // !!! Comment said "only accept value initialization" + // + Init_Struct_Fields(D_OUT, body); + return R_OUT; + } + else if (IS_GOB(spec)) { + // + // !!! Compatibility for `MAKE gob [...]` or `MAKE gob NxN` from + // R3-Alpha GUI. Start by copying the gob (minus pane and parent), + // then apply delta to its properties from arg. Doesn't save memory, + // or keep any parent linkage--could be done in user code as a copy + // and then apply the difference. + // + REBGOB *gob = Make_Gob(); + *gob = *VAL_GOB(spec); + gob->pane = NULL; + gob->parent = NULL; + + if (!IS_BLOCK(body)) + fail (Error_Bad_Make(REB_GOB, body)); + + Extend_Gob_Core(gob, body); + SET_GOB(D_OUT, gob); + return R_OUT; + } + else if (IS_EVENT(spec)) { + // + // !!! As with GOB!, the 2-argument form of MAKE-ing an event is just + // a shorthand for copy-and-apply. Could be user code. + // + if (!IS_BLOCK(body)) + fail (Error_Bad_Make(REB_EVENT, body)); + + Move_Value(D_OUT, spec); // !!! very "shallow" clone of the event + Set_Event_Vars( + D_OUT, + VAL_ARRAY_AT(body), + VAL_SPECIFIER(body) + ); + return R_OUT; + } + else if (ANY_CONTEXT(spec)) { + parent = VAL_CONTEXT(spec); + target = VAL_TYPE(spec); + } + else if (IS_DATATYPE(spec)) { + // + // Should this be supported, or just assume OBJECT! ? There are + // problems trying to create a FRAME! without a function (for + // instance), and making an ERROR! from scratch is currently dangerous + // as well though you can derive them. + // + fail ("DATATYPE! not supported for SPEC of CONSTRUCT"); + } + else { + assert(IS_BLOCK(spec)); + target = REB_OBJECT; + } + + // This parallels the code originally in CONSTRUCT. Run it if the /ONLY + // refinement was passed in. + // + if (REF(only)) { + Init_Object( + D_OUT, + Construct_Context( + REB_OBJECT, + VAL_ARRAY_AT(body), + VAL_SPECIFIER(body), + parent + ) + ); + return R_OUT; + } + + // This code came from REBTYPE(Context) for implementing MAKE OBJECT!. + // Now that MAKE ANY-CONTEXT! has been pulled back, it no longer does + // any evaluation or creates SELF fields. It also obeys the rule that + // the first argument is an exemplar of the type to create only, bringing + // uniformity to MAKE. + // + if ( + (target == REB_OBJECT || target == REB_MODULE) + && (IS_BLOCK(body) || IS_BLANK(body))) { + + // First we scan the object for top-level set words in + // order to make an appropriately sized context. Then + // we put it into an object in D_OUT to GC protect it. + // + context = Make_Selfish_Context_Detect( + target, // type + // scan for toplevel set-words + IS_BLANK(body) + ? cast(const RELVAL*, END) // needed by gcc/g++ 2.95 (bug) + : VAL_ARRAY_AT(body), + parent + ); + Init_Object(D_OUT, context); + + if (!IS_BLANK(body)) { + // + // !!! This binds the actual body data, not a copy of it + // (functions make a copy of the body they are passed to + // be rebound). This seems wrong. + // + Bind_Values_Deep(VAL_ARRAY_AT(body), context); + + // Do the block into scratch space (we ignore the result, + // unless it is thrown in which case it must be returned. + // + DECLARE_LOCAL (dummy); + if (Do_Any_Array_At_Throws(dummy, body)) { + Move_Value(D_OUT, dummy); + return R_OUT_IS_THROWN; + } + } + + return R_OUT; + } + + // "multiple inheritance" case when both spec and body are objects. + // + // !!! As with most R3-Alpha concepts, this needs review. + // + if ((target == REB_OBJECT) && parent && IS_OBJECT(body)) { + // + // !!! Again, the presumption that the result of a merge is to + // be selfish should not be hardcoded in the C, but part of + // the generator choice by the person doing the derivation. + // + context = Merge_Contexts_Selfish(parent, VAL_CONTEXT(body)); + Init_Object(D_OUT, context); + return R_OUT; + } + + fail ("Unsupported CONSTRUCT arguments"); } -#endif diff --git a/src/core/t-pair.c b/src/core/t-pair.c index 0363950225..24d17218f8 100644 --- a/src/core/t-pair.c +++ b/src/core/t-pair.c @@ -1,364 +1,376 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-pair.c -** Summary: pair datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-pair.c +// Summary: "pair datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/*********************************************************************** -** -*/ REBINT CT_Pair(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Pair: C +// +REBINT CT_Pair(const RELVAL *a, const RELVAL *b, REBINT mode) { - if (mode >= 0) return Cmp_Pair(a, b) == 0; // works for INTEGER=0 too (spans x y) - if (IS_PAIR(b) && 0 == VAL_INT64(b)) { // for negative? and positive? - if (mode == -1) - return (VAL_PAIR_X(a) >= 0 || VAL_PAIR_Y(a) >= 0); // not LT - return (VAL_PAIR_X(a) > 0 && VAL_PAIR_Y(a) > 0); // NOT LTE - } - return -1; + if (mode >= 0) return Cmp_Pair(a, b) == 0; // works for INTEGER=0 too (spans x y) + if (IS_PAIR(b) && 0 == VAL_INT64(b)) { // for negative? and positive? + if (mode == -1) + return (VAL_PAIR_X(a) >= 0 || VAL_PAIR_Y(a) >= 0); // not LT + return (VAL_PAIR_X(a) > 0 && VAL_PAIR_Y(a) > 0); // NOT LTE + } + return -1; } -/*********************************************************************** -** -*/ REBFLG MT_Pair(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ +// +// MAKE_Pair: C +// +void MAKE_Pair(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBD32 x; - REBD32 y; - - if (IS_PAIR(data)) { - *out = *data; - return TRUE; - } + assert(kind == REB_PAIR); + UNUSED(kind); + + if (IS_PAIR(arg)) { + Move_Value(out, arg); + return; + } + + if (IS_STRING(arg)) { + // + // -1234567890x-1234567890 + // + REBCNT len; + REBYTE *bp + = Temp_Byte_Chars_May_Fail(arg, VAL_LEN_AT(arg), &len, FALSE); + + if (NULL == Scan_Pair(out, bp, len)) + goto bad_make; + + return; + } + + REBDEC x; + REBDEC y; + + if (IS_INTEGER(arg)) { + x = VAL_INT32(arg); + y = VAL_INT32(arg); + } + else if (IS_DECIMAL(arg)) { + x = VAL_DECIMAL(arg); + y = VAL_DECIMAL(arg); + } + else if (IS_BLOCK(arg) && VAL_LEN_AT(arg) == 2) { + RELVAL *item = VAL_ARRAY_AT(arg); + + if (IS_INTEGER(item)) + x = cast(REBDEC, VAL_INT64(item)); + else if (IS_DECIMAL(item)) + x = cast(REBDEC, VAL_DECIMAL(item)); + else + goto bad_make; + + ++item; + if (IS_END(item)) + goto bad_make; + + if (IS_INTEGER(item)) + y = cast(REBDEC, VAL_INT64(item)); + else if (IS_DECIMAL(item)) + y = cast(REBDEC, VAL_DECIMAL(item)); + else + goto bad_make; + } + else + goto bad_make; + + SET_PAIR(out, x, y); + return; + +bad_make: + fail (Error_Bad_Make(REB_PAIR, arg)); +} - if (!IS_BLOCK(data)) return FALSE; - data = VAL_BLK_DATA(data); +// +// TO_Pair: C +// +void TO_Pair(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) +{ + MAKE_Pair(out, kind, arg); +} - if (IS_INTEGER(data)) x = (REBD32)VAL_INT64(data); - else if (IS_DECIMAL(data)) x = (REBD32)VAL_DECIMAL(data); - else return FALSE; - data++; - if (IS_INTEGER(data)) y = (REBD32)VAL_INT64(data); - else if (IS_DECIMAL(data)) y = (REBD32)VAL_DECIMAL(data); - else return FALSE; +// +// Cmp_Pair: C +// +// Given two pairs, compare them. +// +REBINT Cmp_Pair(const RELVAL *t1, const RELVAL *t2) +{ + REBDEC diff; - VAL_SET(out, REB_PAIR); - VAL_PAIR_X(out) = x; - VAL_PAIR_Y(out) = y; - return TRUE; + if ((diff = VAL_PAIR_Y(t1) - VAL_PAIR_Y(t2)) == 0) + diff = VAL_PAIR_X(t1) - VAL_PAIR_X(t2); + return (diff > 0.0) ? 1 : ((diff < 0.0) ? -1 : 0); } -/*********************************************************************** -** -*/ REBINT Cmp_Pair(REBVAL *t1, REBVAL *t2) -/* -** Given two pairs, compare them. -** -***********************************************************************/ +// +// Min_Max_Pair: C +// +void Min_Max_Pair(REBVAL *out, const REBVAL *a, const REBVAL *b, REBOOL maxed) { - REBD32 diff; - - if ((diff = VAL_PAIR_Y(t1) - VAL_PAIR_Y(t2)) == 0) - diff = VAL_PAIR_X(t1) - VAL_PAIR_X(t2); - return (diff > 0.0) ? 1 : ((diff < 0.0) ? -1 : 0); + // !!! This used to use REBXYF (a structure containing "X" and "Y" as + // floats). It's not clear why floats would be preferred here, and + // also not clear what the types should be if they were mixed (INTEGER! + // vs. DECIMAL! for the X or Y). REBXYF is now a structure only used + // in GOB! so it is taken out of mention here. + + float ax; + float ay; + if (IS_PAIR(a)) { + ax = VAL_PAIR_X(a); + ay = VAL_PAIR_Y(a); + } + else if (IS_INTEGER(a)) + ax = ay = cast(REBDEC, VAL_INT64(a)); + else + fail (a); + + float bx; + float by; + if (IS_PAIR(b)) { + bx = VAL_PAIR_X(b); + by = VAL_PAIR_Y(b); + } + else if (IS_INTEGER(b)) + bx = by = cast(REBDEC, VAL_INT64(b)); + else + fail (b); + + if (maxed) + SET_PAIR(out, MAX(ax, bx), MAX(ay, by)); + else + SET_PAIR(out, MIN(ax, bx), MIN(ay, by)); } -/*********************************************************************** -** -*/ REBINT Min_Max_Pair(REBVAL *ds, REBFLG maxed) -/* -***********************************************************************/ +// +// PD_Pair: C +// +REBINT PD_Pair(REBPVS *pvs) { - REBXYF aa; - REBXYF bb; - REBXYF *cc; - REBVAL *a = D_ARG(1); - REBVAL *b = D_ARG(2); - REBVAL *c = D_RET; - - if (IS_PAIR(a)) aa = VAL_PAIR(a); - else if (IS_INTEGER(a)) aa.x = aa.y = (REBD32)VAL_INT64(a); - else Trap_Arg(a); - - if (IS_PAIR(b)) bb = VAL_PAIR(b); - else if (IS_INTEGER(b)) bb.x = bb.y = (REBD32)VAL_INT64(b); - else Trap_Arg(b); - - cc = &VAL_PAIR(c); - if (maxed) { - cc->x = MAX(aa.x, bb.x); - cc->y = MAX(aa.y, bb.y); - } - else { - cc->x = MIN(aa.x, bb.x); - cc->y = MIN(aa.y, bb.y); - } - SET_TYPE(c, REB_PAIR); - - return R_RET; + const REBVAL *sel = pvs->picker; + REBINT n = 0; + REBDEC dec; + + if (IS_WORD(sel)) { + if (VAL_WORD_SYM(sel) == SYM_X) + n = 1; + else if (VAL_WORD_SYM(sel) == SYM_Y) + n = 2; + else + fail (Error_Bad_Path_Select(pvs)); + } + else if (IS_INTEGER(sel)) { + n = Int32(sel); + if (n != 1 && n != 2) + fail (Error_Bad_Path_Select(pvs)); + } + else fail (Error_Bad_Path_Select(pvs)); + + if (pvs->opt_setval) { + const REBVAL *setval = pvs->opt_setval; + + if (IS_INTEGER(setval)) + dec = cast(REBDEC, VAL_INT64(setval)); + else if (IS_DECIMAL(setval)) + dec = VAL_DECIMAL(setval); + else + fail (Error_Bad_Path_Set(pvs)); + + if (n == 1) + VAL_PAIR_X(pvs->value) = dec; + else + VAL_PAIR_Y(pvs->value) = dec; + } + else { + dec = (n == 1 ? VAL_PAIR_X(pvs->value) : VAL_PAIR_Y(pvs->value)); + Init_Decimal(pvs->store, dec); + return PE_USE_STORE; + } + + return PE_OK; } -/*********************************************************************** -** -*/ REBINT PD_Pair(REBPVS *pvs) -/* -***********************************************************************/ -{ - REBVAL *sel; - REBVAL *val; - REBINT n = 0; - REBD32 dec; - - if (IS_WORD(sel = pvs->select)) { - if (VAL_WORD_CANON(sel) == SYM_X) n = 1; - else if (VAL_WORD_CANON(sel) == SYM_Y) n = 2; - else return PE_BAD_SELECT; - } - else if (IS_INTEGER(sel)) { - n = Int32(sel); - if (n != 1 && n !=2) return PE_BAD_SELECT; - } - else - return PE_BAD_SELECT; - - if (NZ(val = pvs->setval)) { - if (IS_INTEGER(val)) dec = (REBD32)VAL_INT64(val); - else if (IS_DECIMAL(val)) dec = (REBD32)VAL_DECIMAL(val); - else return PE_BAD_SET; - if (n == 1) VAL_PAIR_X(pvs->value) = dec; - else VAL_PAIR_Y(pvs->value) = dec; - } else { - dec = (n == 1 ? VAL_PAIR_X(pvs->value) : VAL_PAIR_Y(pvs->value)); - SET_DECIMAL(pvs->store, dec); - return PE_USE; - } - - return PE_OK; +static void Get_Math_Arg_For_Pair( + REBDEC *x_out, + REBDEC *y_out, + REBVAL *arg, + REBSYM action +){ + switch (VAL_TYPE(arg)) { + case REB_PAIR: + *x_out = VAL_PAIR_X(arg); + *y_out = VAL_PAIR_Y(arg); + break; + + case REB_INTEGER: + *x_out = *y_out = cast(REBDEC, VAL_INT64(arg)); + break; + + case REB_DECIMAL: + case REB_PERCENT: + *x_out = *y_out = cast(REBDEC, VAL_DECIMAL(arg)); + break; + + default: + fail (Error_Math_Args(REB_PAIR, action)); + } + } -/*********************************************************************** -** -*/ REBTYPE(Pair) -/* -***********************************************************************/ + +// +// REBTYPE: C +// +REBTYPE(Pair) { - REBVAL *val; - REBVAL *arg; - REBINT n; - REBD32 x1, x2; - REBD32 y1, y2; - - val = D_ARG(1); - x1 = VAL_PAIR_X(val); - y1 = VAL_PAIR_Y(val); - if (DS_ARGC > 1) arg = D_ARG(2); - - if (IS_BINARY_ACT(action)) { - n = VAL_TYPE(arg); - - if (n == REB_PAIR) { // handle PAIR - PAIR cases - x2 = VAL_PAIR_X(arg); - y2 = VAL_PAIR_Y(arg); - } - else if (n == REB_INTEGER) { - x2 = y2 = (REBD32)VAL_INT64(arg); - } - else if (n == REB_DECIMAL || n == REB_PERCENT) { - x2 = y2 = (REBD32)VAL_DECIMAL(arg); - } - else - Trap_Math_Args(REB_PAIR, action); - - switch (action) { - - case A_ADD: - x1 += x2; - y1 += y2; - goto setPair; - - case A_SUBTRACT: - x1 -= x2; - y1 -= y2; - goto setPair; - - case A_MULTIPLY: - x1 *= x2; - y1 *= y2; - goto setPair; - - case A_DIVIDE: - case A_REMAINDER: - if (x2 == 0 || y2 == 0) Trap0(RE_ZERO_DIVIDE); - if (action == A_DIVIDE) { - x1 /= x2; - y1 /= y2; - } - else { - x1 = (REBD32)fmod(x1, x2); - y1 = (REBD32)fmod(y1, y2); - } - goto setPair; - } - Trap_Math_Args(REB_PAIR, action); - } - // Unary actions: - else { - switch(action) { - -#ifdef temp - case A_ODDQ: - DECIDE((x1 & 1) && (y1 & 1)); - - case A_EVENQ: - DECIDE((x1 & 1) == 0 && (y1 & 1) == 0); -#endif - case A_NEGATE: - x1 = -x1; - y1 = -y1; - goto setPair; -#ifdef temp - case A_COMPLEMENT: - x1 = ~x1; - y1 = ~y1; - goto setPair; -#endif - case A_ABSOLUTE: - if (x1 < 0) x1 = -x1; - if (y1 < 0) y1 = -y1; - goto setPair; - - case A_ROUND: - { - REBDEC d64; - n = Get_Round_Flags(ds); - if (D_REF(2)) - d64 = Dec64(D_ARG(3)); - else { - d64 = 1.0L; - n |= 1; - } - x1 = (REBD32)Round_Dec(x1, n, d64); - y1 = (REBD32)Round_Dec(y1, n, d64); - } - goto setPair; - - case A_REVERSE: - x2 = x1; - x1 = y1; - y1 = x2; - goto setPair; - - case A_RANDOM: - if (D_REF(2)) Trap0(RE_BAD_REFINES); // seed - x1 = (REBD32)Random_Range((REBINT)x1, (REBOOL)D_REF(3)); - y1 = (REBD32)Random_Range((REBINT)y1, (REBOOL)D_REF(3)); - goto setPair; - - case A_PICK: - if (IS_WORD(arg)) { - if (VAL_WORD_CANON(arg) == SYM_X) n = 0; - else if (VAL_WORD_CANON(arg) == SYM_Y) n = 1; - else Trap_Arg(arg); - } - else { - n = Get_Num_Arg(arg); - if (n < 1 || n > 2) Trap_Range(arg); - n--; - } -/// case A_POKE: -/// if (action == A_POKE) { -/// arg = D_ARG(3); -/// if (IS_INTEGER(arg)) { -/// if (index == 0) x1 = VAL_INT32(arg); -/// else y1 = VAL_INT32(arg); -/// } -/// else if (IS_DECIMAL(arg)) { -/// if (index == 0) x1 = (REBINT)VAL_DECIMAL(arg); -/// else y1 = (REBINT)VAL_DECIMAL(arg); -/// } else -/// Trap_Arg(arg); -/// goto setPair; -/// } - SET_DECIMAL(DS_RETURN, n == 0 ? x1 : y1); - return R_RET; - - case A_MAKE: - case A_TO: - val = D_ARG(2); - x1 = y1 = 0; -// if (IS_NONE(val)) goto setPair; - if (IS_PAIR(val)) { - *DS_RETURN = *val; - return R_RET; - } - if (IS_STRING(val)) { - REBYTE *bp; - REBCNT len; - // -1234567890x-1234567890 - bp = Qualify_String(val, 24, &len, FALSE); - if (Scan_Pair(bp, len, DS_RETURN)) return R_RET; - } - if (IS_INTEGER(val)) { - x1 = y1 = (REBD32)VAL_INT64(val); - goto setPair; - } - if (IS_DECIMAL(val)) { - x1 = y1 = (REBD32)VAL_DECIMAL(val); - goto setPair; - } - if (ANY_BLOCK(val) && VAL_LEN(val) <= 2) { - if (MT_Pair(D_RET, val, REB_PAIR)) - return R_RET; - } - Trap_Make(REB_PAIR, val); - } - } - Trap_Action(REB_PAIR, action); + REBVAL *val = D_ARG(1); + + REBDEC x1 = VAL_PAIR_X(val); + REBDEC y1 = VAL_PAIR_Y(val); + + REBDEC x2; + REBDEC y2; + + switch (action) { + + case SYM_COPY: { + goto setPair; + } + + case SYM_ADD: + Get_Math_Arg_For_Pair(&x2, &y2, D_ARG(2), action); + x1 += x2; + y1 += y2; + goto setPair; + + case SYM_SUBTRACT: + Get_Math_Arg_For_Pair(&x2, &y2, D_ARG(2), action); + x1 -= x2; + y1 -= y2; + goto setPair; + + case SYM_MULTIPLY: + Get_Math_Arg_For_Pair(&x2, &y2, D_ARG(2), action); + x1 *= x2; + y1 *= y2; + goto setPair; + + case SYM_DIVIDE: + case SYM_REMAINDER: + Get_Math_Arg_For_Pair(&x2, &y2, D_ARG(2), action); + if (x2 == 0 || y2 == 0) fail (Error_Zero_Divide_Raw()); + if (action == SYM_DIVIDE) { + x1 /= x2; + y1 /= y2; + } + else { + x1 = cast(REBDEC, fmod(x1, x2)); + y1 = cast(REBDEC, fmod(y1, y2)); + } + goto setPair; + + case SYM_NEGATE: + x1 = -x1; + y1 = -y1; + goto setPair; + + case SYM_ABSOLUTE: + if (x1 < 0) x1 = -x1; + if (y1 < 0) y1 = -y1; + goto setPair; + + case SYM_ROUND: { + INCLUDE_PARAMS_OF_ROUND; + + UNUSED(PAR(value)); + + REBFLGS flags = ( + (REF(to) ? RF_TO : 0) + | (REF(even) ? RF_EVEN : 0) + | (REF(down) ? RF_DOWN : 0) + | (REF(half_down) ? RF_HALF_DOWN : 0) + | (REF(floor) ? RF_FLOOR : 0) + | (REF(ceiling) ? RF_CEILING : 0) + | (REF(half_ceiling) ? RF_HALF_CEILING : 0) + ); + + if (REF(to)) { + x1 = Round_Dec(x1, flags, Dec64(ARG(scale))); + y1 = Round_Dec(y1, flags, Dec64(ARG(scale))); + } + else { + x1 = Round_Dec(x1, flags | RF_TO, 1.0L); + y1 = Round_Dec(y1, flags | RF_TO, 1.0L); + } + goto setPair; } + + case SYM_REVERSE: + x2 = x1; + x1 = y1; + y1 = x2; + goto setPair; + + case SYM_RANDOM: { + INCLUDE_PARAMS_OF_RANDOM; + + UNUSED(PAR(value)); + + if (REF(only)) + fail (Error_Bad_Refines_Raw()); + if (REF(seed)) + fail (Error_Bad_Refines_Raw()); + + x1 = cast(REBDEC, Random_Range(cast(REBINT, x1), REF(secure))); + y1 = cast(REBDEC, Random_Range(cast(REBINT, y1), REF(secure))); + goto setPair; } + + default: + break; + } + + fail (Error_Illegal_Action(REB_PAIR, action)); setPair: - VAL_SET(DS_RETURN, REB_PAIR); - VAL_PAIR_X(DS_RETURN) = x1; - VAL_PAIR_Y(DS_RETURN) = y1; - return R_RET; - -//is_false: -// return R_FALSE; - -//is_true: -// return R_TRUE; + SET_PAIR(D_OUT, x1, y1); + return R_OUT; } diff --git a/src/core/t-port.c b/src/core/t-port.c index feca5d96d7..46e1eadb48 100644 --- a/src/core/t-port.c +++ b/src/core/t-port.c @@ -1,115 +1,193 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-port.c -** Summary: port datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-port.c +// Summary: "port datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/*********************************************************************** -** -*/ REBINT CT_Port(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Port: C +// +REBINT CT_Port(const RELVAL *a, const RELVAL *b, REBINT mode) { - if (mode < 0) return -1; - return VAL_OBJ_FRAME(a) == VAL_OBJ_FRAME(b); + if (mode < 0) return -1; + return VAL_CONTEXT(a) == VAL_CONTEXT(b); } -/*********************************************************************** -** -*/ REBFLG MT_Port(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ +// +// MAKE_Port: C +// +// Create a new port. This is done by calling the MAKE_PORT +// function stored in the system/intrinsic object. +// +void MAKE_Port(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - return FALSE; + assert(kind == REB_PORT); + UNUSED(kind); + + const REBOOL fully = TRUE; // error if not all arguments consumed + + if (Apply_Only_Throws( + out, fully, Sys_Func(SYS_CTX_MAKE_PORT_P), arg, END + )){ + // Gave back an unhandled RETURN, BREAK, CONTINUE, etc... + fail (Error_No_Catch_For_Throw(out)); + } + + // !!! Shouldn't this be testing for !IS_PORT( ) ? + if (IS_BLANK(out)) + fail (Error_Invalid_Spec_Raw(arg)); } -/*********************************************************************** -** -*/ static REBVAL *As_Port(REBVAL *value) -/* -** Make the port object if necessary. -** -***********************************************************************/ +// +// TO_Port: C +// +void TO_Port(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBVAL *ds; - - if (IS_PORT(value)) return value; + assert(kind == REB_PORT); + UNUSED(kind); + + if (!IS_OBJECT(arg)) + fail (Error_Bad_Make(REB_PORT, arg)); + + // !!! cannot convert TO a PORT! without copying the whole context... + // which raises the question of why convert an object to a port, + // vs. making it as a port to begin with (?) Look into why + // system/standard/port is made with CONTEXT and not with MAKE PORT! + // + REBCTX *context = Copy_Context_Shallow(VAL_CONTEXT(arg)); + VAL_RESET_HEADER(CTX_VALUE(context), REB_PORT); + Init_Port(out, context); +} - value = Make_Port(value); - ds = DS_RETURN; - *D_ARG(1) = *value; - return D_ARG(1); +// +// Retrigger_Append_As_Write: C +// +// !!! In R3-Alpha, for the convenience of being able to APPEND to something +// that may be a FILE!-based PORT! or a BINARY! or STRING! with a unified +// interface, the APPEND command was re-interpreted as a WRITE/APPEND. But +// it was done with presumption that APPEND and WRITE had compatible frames, +// which generally speaking they do not. +// +// This moves the functionality to an actual retriggering which calls whatever +// WRITE/APPEND would do in a generic fashion with a new frame. Not all +// ports do this, as some have their own interpretation of APPEND. It's +// hacky, but still not as bad as it was. Review. +// +REB_R Retrigger_Append_As_Write(REBFRM *frame_) { + INCLUDE_PARAMS_OF_APPEND; + + // !!! Something like `write/append %foo.txt "data"` knows to convert + // %foo.txt to a port before trying the write, but if you say + // `append %foo.txt "data"` you get `%foo.txtdata`. Some actions are like + // this, e.g. PICK, where they can't do the automatic conversion. + // + assert(IS_PORT(ARG(series))); // !!! poorly named + UNUSED(ARG(series)); + if (NOT( + IS_BINARY(ARG(value)) + || IS_STRING(ARG(value)) + || IS_BLOCK(ARG(value))) + ){ + fail (ARG(value)); + } + + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(only)) + fail (Error_Bad_Refines_Raw()); + if (REF(dup)) { + UNUSED(ARG(count)); + fail (Error_Bad_Refines_Raw()); + } + + REBARR *a = Make_Array(2); + Move_Value(Alloc_Tail_Array(a), &PG_Write_Action); + Init_Word(Alloc_Tail_Array(a), Canon(SYM_APPEND)); + + DECLARE_LOCAL (write_append); + Init_Path(write_append, a); + + if (Apply_Only_Throws( + D_OUT, TRUE, write_append, D_ARG(1), D_ARG(2), END + )){ + return R_OUT_IS_THROWN; + } + + return R_OUT; } -/*********************************************************************** -** -*/ REBTYPE(Port) -/* -***********************************************************************/ +// +// REBTYPE: C +// +// !!! The concept of port dispatch from R3-Alpha is that it delegates to a +// handler which may be native code or user code. +// +REBTYPE(Port) { - REBVAL *value = D_ARG(1); - REBVAL *arg = D_ARG(2); - - switch (action) { - - case A_READ: - case A_WRITE: - case A_QUERY: - case A_OPEN: - case A_CREATE: - case A_DELETE: - case A_RENAME: - value = As_Port(value); - case A_UPDATE: - default: - return Do_Port_Action(VAL_PORT(value), action); // Result on stack - - case A_REFLECT: - return T_Object(ds, action); - break; - - case A_MAKE: - if (IS_DATATYPE(value)) value = Make_Port(arg); - else Trap_Make(REB_PORT, value); - break; - - case A_TO: - if (!(IS_DATATYPE(value) && IS_OBJECT(arg))) Trap_Make(REB_PORT, arg); - value = arg; - VAL_SET(value, REB_PORT); - break; - } - - DS_Ret_Val(value); - return R_RET; + REBVAL *value = D_ARG(1); + + switch (action) { + + case SYM_READ: + case SYM_WRITE: + case SYM_QUERY: + case SYM_OPEN: + case SYM_CREATE: + case SYM_DELETE: + case SYM_RENAME: { + // !!! We are going to "re-apply" the call frame with routines that + // are going to read the D_ARG(1) slot *implicitly* regardless of + // what value points to. + // + if (!IS_PORT(value)) { + DECLARE_LOCAL (temp); + MAKE_Port(temp, REB_PORT, value); + Move_Value(value, temp); + } + break; } + + case SYM_UPDATE: + break; + + case SYM_REFLECT: + return T_Context(frame_, action); + + default: + break; + } + + return Do_Port_Action(frame_, VAL_CONTEXT(value), action); } diff --git a/src/core/t-routine.c b/src/core/t-routine.c new file mode 100755 index 0000000000..320522e215 --- /dev/null +++ b/src/core/t-routine.c @@ -0,0 +1,1544 @@ +// +// File: %t-routine.c +// Summary: "Support for calling non-Rebol C functions in DLLs w/Rebol args)" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2014 Atronix Engineering, Inc. +// Copyright 2014-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This code was contributed by Atronix Engineering: +// +// http://www.atronixengineering.com/downloads/ +// +// It will only work if your build (-D)efines "-DHAVE_LIBFFI_AVAILABLE". +// +// Not defining HAVE_LIBFFI_AVAILABLE will produce a short list of +// non-working "stubs" that match the interface of . These can +// allow t-routine.c to compile anyway. That assists with maintenance +// of the code and keeping it on the radar--even among those doing core +// coding who are not building against the FFI. +// + +#include "sys-core.h" + +#include "mem-pools.h" // low-level memory pool access + +#if !defined(HAVE_LIBFFI_AVAILABLE) + + ffi_type ffi_type_void = { 0, 0, FFI_TYPE_VOID, NULL }; + ffi_type ffi_type_uint8 = { 0, 0, FFI_TYPE_UINT8, NULL }; + ffi_type ffi_type_sint8 = { 0, 0, FFI_TYPE_SINT8, NULL }; + ffi_type ffi_type_uint16 = { 0, 0, FFI_TYPE_UINT16, NULL }; + ffi_type ffi_type_sint16 = { 0, 0, FFI_TYPE_SINT16, NULL }; + ffi_type ffi_type_uint32 = { 0, 0, FFI_TYPE_UINT32, NULL }; + ffi_type ffi_type_sint32 = { 0, 0, FFI_TYPE_SINT32, NULL }; + ffi_type ffi_type_uint64 = { 0, 0, FFI_TYPE_UINT64, NULL }; + ffi_type ffi_type_sint64 = { 0, 0, FFI_TYPE_SINT64, NULL }; + ffi_type ffi_type_float = { 0, 0, FFI_TYPE_FLOAT, NULL }; + ffi_type ffi_type_double = { 0, 0, FFI_TYPE_DOUBLE, NULL }; + ffi_type ffi_type_pointer = { 0, 0, FFI_TYPE_POINTER, NULL }; + + ffi_status ffi_prep_cif( + ffi_cif *cif, + ffi_abi abi, + unsigned int nargs, + ffi_type *rtype, + ffi_type **atypes + ){ + UNUSED(cif); + UNUSED(abi); + UNUSED(nargs); + UNUSED(rtype); + UNUSED(atypes); + + fail (Error_Not_Ffi_Build_Raw()); + } + + ffi_status ffi_prep_cif_var( + ffi_cif *cif, + ffi_abi abi, + unsigned int nfixedargs, + unsigned int ntotalargs, + ffi_type *rtype, + ffi_type **atypes + ){ + UNUSED(cif); + UNUSED(abi); + UNUSED(nfixedargs); + UNUSED(ntotalargs); + UNUSED(rtype); + UNUSED(atypes); + + fail (Error_Not_Ffi_Build_Raw()); + } + + void ffi_call( + ffi_cif *cif, + void (*fn)(void), + void *rvalue, + void **avalue + ){ + UNUSED(cif); + UNUSED(fn); + UNUSED(rvalue); + UNUSED(avalue); + + fail (Error_Not_Ffi_Build_Raw()); + } + + void *ffi_closure_alloc(size_t size, void **code) { + UNUSED(size); + UNUSED(code); + + fail (Error_Not_Ffi_Build_Raw()); + } + + ffi_status ffi_prep_closure_loc( + ffi_closure *closure, + ffi_cif *cif, + void (*fun)(ffi_cif *, void *, void **, void *), + void *user_data, + void *codeloc + ){ + UNUSED(closure); + UNUSED(cif); + UNUSED(fun); + UNUSED(user_data); + UNUSED(codeloc); + + fail (Error_Not_Ffi_Build_Raw()); + } + + void ffi_closure_free(void *closure) { + UNUSED(closure); + + fail (Error_Not_Ffi_Build_Raw()); + } +#endif + + +// There is a platform-dependent list of legal ABIs which the MAKE-ROUTINE +// and MAKE-CALLBACK natives take as an option via refinement +// +static ffi_abi Abi_From_Word(const REBVAL *word) { + switch (VAL_WORD_SYM(word)) { + case SYM_DEFAULT: + return FFI_DEFAULT_ABI; + +#ifdef X86_WIN64 + case SYM_WIN64: + return FFI_WIN64; + +#elif defined(X86_WIN32) || defined(TO_LINUX_X86) || defined(TO_LINUX_X64) + case SYM_STDCALL: + return FFI_STDCALL; + + case SYM_SYSV: + return FFI_SYSV; + + case SYM_THISCALL: + return FFI_THISCALL; + + case SYM_FASTCALL: + return FFI_FASTCALL; + +#ifdef X86_WIN32 + case SYM_MS_CDECL: + return FFI_MS_CDECL; +#else + case SYM_UNIX64: + return FFI_UNIX64; +#endif //X86_WIN32 + +#elif defined (TO_LINUX_ARM) + case SYM_VFP: + return FFI_VFP; + + case SYM_SYSV: + return FFI_SYSV; + +#elif defined (TO_LINUX_MIPS) + case SYM_O32: + return FFI_O32; + + case SYM_N32: + return FFI_N32; + + case SYM_N64: + return FFI_N64; + + case SYM_O32_SOFT_FLOAT: + return FFI_O32_SOFT_FLOAT; + + case SYM_N32_SOFT_FLOAT: + return FFI_N32_SOFT_FLOAT; + + case SYM_N64_SOFT_FLOAT: + return FFI_N64_SOFT_FLOAT; +#endif //X86_WIN64 + + default: + break; + } + + fail (word); +} + + +// +// Writes into `out` a Rebol value representing the "schema", which describes +// either a basic FFI type or the layout of a STRUCT! (not including data). +// +static void Schema_From_Block_May_Fail( + REBVAL *schema_out, // => INTEGER! or HANDLE! for struct + REBVAL *param_out, // => TYPESET! + const REBVAL *blk +){ + TRASH_CELL_IF_DEBUG(schema_out); + TRASH_CELL_IF_DEBUG(param_out); + + assert(IS_BLOCK(blk)); + if (VAL_LEN_AT(blk) == 0) + fail (blk); + + RELVAL *item = VAL_ARRAY_AT(blk); + + DECLARE_LOCAL (def); + DECLARE_LOCAL (temp); + + if (IS_WORD(item) && VAL_WORD_SYM(item) == SYM_STRUCT_X) { + // + // [struct! [...struct definition...]] + + ++item; + if (IS_END(item) || !IS_BLOCK(item)) + fail (blk); + + // Use the block spec to build a temporary structure through the same + // machinery that implements `make struct! [...]` + + Derelativize(def, item, VAL_SPECIFIER(blk)); + + MAKE_Struct(temp, REB_STRUCT, def); // may fail() + assert(IS_STRUCT(temp)); + + // !!! It should be made possible to create a schema without going + // through a struct creation. There are "raw" structs with no memory, + // which would avoid the data series (not the REBSTU array, though) + // + Init_Block(schema_out, VAL_STRUCT_SCHEMA(temp)); + + // !!! Saying any STRUCT! is legal here in the typeset suggests any + // structure is legal to pass into a routine. Yet structs in C + // have different sizes (and static type checking so you can't pass + // one structure in the place of another. Actual struct compatibility + // is not checked until runtime, when the call happens. + // + Init_Typeset(param_out, FLAGIT_KIND(REB_STRUCT), NULL); + return; + } + + if (VAL_LEN_AT(blk) != 1) + fail (blk); + + if (IS_WORD(item)) { + // + // Drop the binding off word (then note SYM_VOID turns schema to blank) + // + Init_Word(schema_out, VAL_WORD_SPELLING(item)); + + switch (VAL_WORD_SYM(item)) { + case SYM_VOID: + Init_Blank(schema_out); // only valid for return types + Init_Typeset(param_out, FLAGIT_KIND(REB_MAX_VOID), NULL); + break; + + case SYM_UINT8: + Init_Typeset(param_out, FLAGIT_KIND(REB_INTEGER), NULL); + break; + + case SYM_INT8: + Init_Typeset(param_out, FLAGIT_KIND(REB_INTEGER), NULL); + break; + + case SYM_UINT16: + Init_Typeset(param_out, FLAGIT_KIND(REB_INTEGER), NULL); + break; + + case SYM_INT16: + Init_Typeset(param_out, FLAGIT_KIND(REB_INTEGER), NULL); + break; + + case SYM_UINT32: + Init_Typeset(param_out, FLAGIT_KIND(REB_INTEGER), NULL); + break; + + case SYM_INT32: + Init_Typeset(param_out, FLAGIT_KIND(REB_INTEGER), NULL); + break; + + case SYM_UINT64: + Init_Typeset(param_out, FLAGIT_KIND(REB_INTEGER), NULL); + break; + + case SYM_INT64: + Init_Typeset(param_out, FLAGIT_KIND(REB_INTEGER), NULL); + break; + + case SYM_FLOAT: + Init_Typeset(param_out, FLAGIT_KIND(REB_DECIMAL), NULL); + break; + + case SYM_DOUBLE: + Init_Typeset(param_out, FLAGIT_KIND(REB_DECIMAL), NULL); + break; + + case SYM_POINTER: + Init_Typeset( + param_out, + FLAGIT_KIND(REB_INTEGER) + | FLAGIT_KIND(REB_STRING) + | FLAGIT_KIND(REB_BINARY) + | FLAGIT_KIND(REB_VECTOR) + | FLAGIT_KIND(REB_FUNCTION), // legal if routine or callback + NULL + ); + break; + + case SYM_REBVAL: + Init_Typeset(param_out, ALL_64, NULL); + break; + + default: + fail ("Invalid FFI type indicator"); + } + return; + } + + fail (blk); +} + + +// +// According to the libffi documentation, the arguments "must be suitably +// aligned; it is the caller's responsibility to ensure this". +// +// We assume the store's data pointer will have suitable alignment for any +// type (currently Make_Series() is expected to match malloc() in this way). +// This will round the offset positions to an alignment appropriate for the +// type size given. +// +// This means sequential arguments in the store may have padding between them. +// +inline static void *Expand_And_Align_Core( + REBUPT *offset_out, + REBCNT align, + REBSER *store, + REBCNT size +){ + REBCNT padding = SER_LEN(store) % align; + if (padding != 0) + padding = align - padding; + + *offset_out = SER_LEN(store) + padding; + EXPAND_SERIES_TAIL(store, padding + size); + return SER_DATA_RAW(store) + *offset_out; +} + +inline static void *Expand_And_Align( + REBUPT *offset_out, + REBSER *store, + REBCNT size // assumes align == size +){ + return Expand_And_Align_Core(offset_out, size, store, size); +} + + +// +// Convert a Rebol value into a bit pattern suitable for the expectations of +// the FFI for how a C argument would be represented. (e.g. turn an +// INTEGER! into the appropriate representation of an `int` in memory.) +// +static REBUPT arg_to_ffi( + REBSER *store, + void *dest, + const REBVAL *arg, + const REBVAL *schema, + const REBVAL *param +){ + // Only one of dest or store should be non-NULL. This allows to write + // either to a known pointer of sufficient size (dest) or to a series + // that will expand enough to accommodate the data (store). + // + assert(store == NULL ? dest != NULL : dest == NULL); + +#if !defined(NDEBUG) + // + // If the value being converted has a "name"--e.g. the FFI Routine + // interface named it in the spec--then `param` contains that name, for + // reporting any errors in the conversion. + // + // !!! Shouldn't the argument have already had its type checked by the + // calling process? + // + if (param) + assert(arg != NULL && IS_TYPESET(param)); + else + assert(arg == NULL); // return value, so just make space (no arg data) +#endif + + REBFRM *frame_ = FS_TOP; // So you can use the D_xxx macros + + REBUPT offset; + if (!dest) + offset = 0; + + if (IS_BLOCK(schema)) { + REBFLD *top = VAL_ARRAY(schema); + + assert(FLD_IS_STRUCT(top)); + assert(NOT(FLD_IS_ARRAY(top))); // !!! wasn't supported--should be? + + // !!! In theory a struct has to be aligned to its maximal alignment + // needed by a fundamental member. We'll assume that the largest + // is sizeof(void*) here...this may waste some space in the padding + // between arguments, but that shouldn't have any semantic effect. + // + if (!dest) + dest = Expand_And_Align_Core( + &offset, + sizeof(void*), + store, + FLD_WIDE(top) // !!! What about FLD_LEN_BYTES_TOTAL ? + ); + + if (arg == NULL) { + // + // Return values don't have an incoming argument to fill into the + // calling frame. + // + return offset; + } + + // !!! There wasn't any compatibility checking here before (not even + // that the arg was a struct. :-/ It used a stored STRUCT! from + // when the routine was specified to know what the size should be, + // and didn't pay attention to the size of the passed-in struct. + // + // (One reason it didn't use the size of the passed-struct is + // because it couldn't do so in the return case where arg was null) + + if (!IS_STRUCT(arg)) + fail (Error_Arg_Type(D_LABEL_SYM, param, VAL_TYPE(arg))); + + if (STU_SIZE(VAL_STRUCT(arg)) != FLD_WIDE(top)) + fail (Error_Arg_Type(D_LABEL_SYM, param, VAL_TYPE(arg))); + + memcpy(dest, VAL_STRUCT_DATA_AT(arg), STU_SIZE(VAL_STRUCT(arg))); + + return offset; + } + + assert(IS_WORD(schema)); + + switch (VAL_WORD_SYM(schema)) { + case SYM_UINT8:{ + u8 u; + if (!dest) + dest = Expand_And_Align(&offset, store, sizeof(u)); + if (!arg) break; + + if (!IS_INTEGER(arg)) + fail (Error_Arg_Type(D_LABEL_SYM, param, VAL_TYPE(arg))); + + u = cast(u8, VAL_INT64(arg)); + memcpy(dest, &u, sizeof(u)); + break;} + + case SYM_INT8:{ + i8 i; + if (!dest) + dest = Expand_And_Align(&offset, store, sizeof(i)); + if (!arg) break; + + if (!IS_INTEGER(arg)) + fail (Error_Arg_Type(D_LABEL_SYM, param, VAL_TYPE(arg))); + + i = cast(i8, VAL_INT64(arg)); + memcpy(dest, &i, sizeof(i)); + break;} + + case SYM_UINT16:{ + u16 u; + if (!dest) + dest = Expand_And_Align(&offset, store, sizeof(u)); + if (!arg) break; + + if (!IS_INTEGER(arg)) + fail (Error_Arg_Type(D_LABEL_SYM, param, VAL_TYPE(arg))); + + u = cast(u16, VAL_INT64(arg)); + memcpy(dest, &u, sizeof(u)); + break;} + + case SYM_INT16:{ + i16 i; + if (!dest) + dest = Expand_And_Align(&offset, store, sizeof(i)); + if (!arg) break; + + if (!IS_INTEGER(arg)) + fail (Error_Arg_Type(D_LABEL_SYM, param, VAL_TYPE(arg))); + + i = cast(i16, VAL_INT64(arg)); + memcpy(dest, &i, sizeof(i)); + break;} + + case SYM_UINT32:{ + u32 u; + if (!dest) + dest = Expand_And_Align(&offset, store, sizeof(u)); + if (!arg) break; + + if (!IS_INTEGER(arg)) + fail (Error_Arg_Type(D_LABEL_SYM, param, VAL_TYPE(arg))); + + u = cast(u32, VAL_INT64(arg)); + memcpy(dest, &u, sizeof(u)); + break;} + + case SYM_INT32:{ + i32 i; + if (!dest) + dest = Expand_And_Align(&offset, store, sizeof(i)); + if (!arg) break; + + if (!IS_INTEGER(arg)) + fail (Error_Arg_Type(D_LABEL_SYM, param, VAL_TYPE(arg))); + + i = cast(i32, VAL_INT64(arg)); + memcpy(dest, &i, sizeof(i)); + break;} + + case SYM_UINT64: + case SYM_INT64:{ + REBI64 i; + if (!dest) + dest = Expand_And_Align(&offset, store, sizeof(i)); + if (!arg) break; + + if (!IS_INTEGER(arg)) + fail (Error_Arg_Type(D_LABEL_SYM, param, VAL_TYPE(arg))); + + i = VAL_INT64(arg); + memcpy(dest, &i, sizeof(REBI64)); + break;} + + case SYM_POINTER:{ + // + // Note: Function pointers and data pointers may not be same size. + // + if (!dest) + dest = Expand_And_Align(&offset, store, sizeof(void*)); + if (!arg) break; + + switch (VAL_TYPE(arg)) { + case REB_INTEGER:{ + REBIPT ipt = VAL_INT64(arg); // REBIPT is like C99's intptr_t + memcpy(dest, &ipt, sizeof(void*)); + break;} + + case REB_STRING: + case REB_BINARY: + case REB_VECTOR:{ + // !!! This is a questionable idea, giving out pointers directly + // into Rebol series data. One issue is that the recipient of + // the data doesn't know whether to interpret it as REBYTE[] or as + // REBUNI[]...because it's passing the raw data of strings which + // can be wide or not based on things that have happened in the + // lifetime of that string. Another is that the data may be + // relocated in memory if any modifications happen during a + // callback...so the memory is not "stable". + // + REBYTE *raw_ptr = VAL_RAW_DATA_AT(arg); + memcpy(dest, &raw_ptr, sizeof(raw_ptr)); // copies a *pointer*! + break;} + + case REB_FUNCTION:{ + if (!IS_FUNCTION_RIN(arg)) + fail (Error_Only_Callback_Ptr_Raw()); // actually routines too + + CFUNC* cfunc = RIN_CFUNC(VAL_FUNC_ROUTINE(arg)); + if (sizeof(cfunc) != sizeof(void*)) // not necessarily true + fail ("Void pointer size not equal to function pointer size"); + memcpy(dest, &cfunc, sizeof(void*)); + break;} + + default: + fail (Error_Arg_Type(D_LABEL_SYM, param, VAL_TYPE(arg))); + } + break;} // end case FFI_TYPE_POINTER + + case SYM_REBVAL: { + if (!dest) + dest = Expand_And_Align(&offset, store, sizeof(REBVAL*)); + if (!arg) break; + + memcpy(dest, &arg, sizeof(REBVAL*)); // copies a *pointer*! + break; } + + case SYM_FLOAT:{ + float f; + if (!dest) + dest = Expand_And_Align(&offset, store, sizeof(f)); + if (!arg) break; + + if (!IS_DECIMAL(arg)) + fail (Error_Arg_Type(D_LABEL_SYM, param, VAL_TYPE(arg))); + + f = cast(float, VAL_DECIMAL(arg)); + memcpy(dest, &f, sizeof(f)); + break;} + + case SYM_DOUBLE:{ + double d; + if (!dest) + dest = Expand_And_Align(&offset, store, sizeof(d)); + if (!arg) break; + + if (!IS_DECIMAL(arg)) + fail (Error_Arg_Type(D_LABEL_SYM, param, VAL_TYPE(arg))); + + d = VAL_DECIMAL(arg); + memcpy(dest, &d, sizeof(double)); + break;} + + case SYM_STRUCT_X: + // + // structs should be processed above by the HANDLE! case, not WORD! + // + assert(FALSE); + case SYM_VOID: + // + // can't return a meaningful offset for "void"--it's only valid for + // return types, so caller should check and not try to pass it in. + // + assert(FALSE); + default: + fail (arg); + } + + return offset; +} + + +/* convert the return value to rebol + */ +static void ffi_to_rebol( + REBVAL *out, + const REBVAL *schema, + void *ffi_rvalue +) { + if (IS_BLOCK(schema)) { + REBFLD *top = VAL_ARRAY(schema); + + assert(FLD_IS_STRUCT(top)); + assert(NOT(FLD_IS_ARRAY(top))); // !!! wasn't supported, should be? + + REBSTU *stu = Alloc_Singular_Array(); + + REBSER *data = Make_Series( + FLD_WIDE(top), // !!! what about FLD_LEN_BYTES_TOTAL ? + sizeof(REBYTE) + ); + memcpy(SER_HEAD(REBYTE, data), ffi_rvalue, FLD_WIDE(top)); + MANAGE_SERIES(data); + + VAL_RESET_HEADER(out, REB_STRUCT); + out->payload.structure.stu = stu; + out->payload.structure.data = data; + out->extra.struct_offset = 0; + + *ARR_HEAD(stu) = *out; // save canon value + SER(stu)->link.schema = top; + MANAGE_ARRAY(stu); + + assert(STU_DATA_HEAD(stu) == BIN_HEAD(data)); + return; + } + + assert(IS_WORD(schema)); + + switch (VAL_WORD_SYM(schema)) { + case SYM_UINT8: + Init_Integer(out, *cast(u8*, ffi_rvalue)); + break; + + case SYM_INT8: + Init_Integer(out, *cast(i8*, ffi_rvalue)); + break; + + case SYM_UINT16: + Init_Integer(out, *cast(u16*, ffi_rvalue)); + break; + + case SYM_INT16: + Init_Integer(out, *cast(i16*, ffi_rvalue)); + break; + + case SYM_UINT32: + Init_Integer(out, *cast(u32*, ffi_rvalue)); + break; + + case SYM_INT32: + Init_Integer(out, *cast(i32*, ffi_rvalue)); + break; + + case SYM_UINT64: + Init_Integer(out, *cast(u64*, ffi_rvalue)); + break; + + case SYM_INT64: + Init_Integer(out, *cast(i64*, ffi_rvalue)); + break; + + case SYM_POINTER: + Init_Integer(out, cast(REBUPT, *cast(void**, ffi_rvalue))); + break; + + case SYM_FLOAT: + Init_Decimal(out, *cast(float*, ffi_rvalue)); + break; + + case SYM_DOUBLE: + Init_Decimal(out, *cast(double*, ffi_rvalue)); + break; + + case SYM_REBVAL: + Move_Value(out, *cast(const REBVAL**, ffi_rvalue)); + break; + + case SYM_VOID: + assert(FALSE); // not covered by generic routine. + default: + assert(FALSE); + // + // !!! Was reporting Error_Invalid_Arg on uninitialized `out` + // + fail ("Unknown FFI type indicator"); + } +} + + +// +// Routine_Dispatcher: C +// +REB_R Routine_Dispatcher(REBFRM *f) +{ + REBRIN *rin = FUNC_ROUTINE(f->phase); + + if (RIN_LIB(rin) == NULL) { + // + // lib is NULL when routine is constructed from address directly, + // so there's nothing to track whether that gets loaded or unloaded + } + else { + if (IS_LIB_CLOSED(RIN_LIB(rin))) + fail (Error_Bad_Library_Raw()); + } + + REBCNT num_fixed = RIN_NUM_FIXED_ARGS(rin); + + REBCNT num_variable; + REBDSP dsp_orig = DSP; // variadic args pushed to stack, so save base ptr + + if (NOT(RIN_IS_VARIADIC(rin))) + num_variable = 0; + else { + // The function specification should have one extra parameter for + // the variadic source ("...") + // + assert(FUNC_NUM_PARAMS(f->phase) == num_fixed + 1); + + REBVAL *vararg = FRM_ARG(f, num_fixed + 1); // 1-based + assert(IS_VARARGS(vararg) && f->binding == NULL); + + // Evaluate the VARARGS! feed of values to the data stack. This way + // they will be available to be counted, to know how big to make the + // FFI argument series. + // + do { + REB_R r = Do_Vararg_Op_May_Throw(f->out, vararg, VARARG_OP_TAKE); + + if (r == R_OUT_IS_THROWN) + return R_OUT_IS_THROWN; + if (r == R_VOID) + break; + assert(r == R_OUT); + + DS_PUSH(f->out); + SET_END(f->out); // expected by Do_Vararg_Op + } while (TRUE); + + // !!! The Atronix va_list interface required a type to be specified + // for each argument--achieving what you would get if you used a + // C cast on each variadic argument. Such as: + // + // printf reduce ["%d, %f" 10 + 20 [int32] 12.34 [float]] + // + // While this provides generality, it may be useful to use defaulting + // like C's where integer types default to `int` and floating point + // types default to `double`. In the VARARGS!-based syntax it could + // offer several possibilities: + // + // (printf "%d, %f" (10 + 20) 12.34) + // (printf "%d, %f" [int32 10 + 20] 12.34) + // (printf "%d, %f" [int32] 10 + 20 [float] 12.34) + // + // For the moment, this is following the idea that there must be + // pairings of values and then blocks (though the values are evaluated + // expressions). + // + if ((DSP - dsp_orig) % 2 != 0) + fail ("Variadic FFI functions must alternate blocks and values"); + + num_variable = (DSP - dsp_orig) / 2; + } + + REBCNT num_args = num_fixed + num_variable; + + // The FFI arguments are passed by void*. Those void pointers point to + // transformations of the Rebol arguments into ranges of memory of + // various sizes. This is the backing store for those arguments, which + // is appended to for each one. The memory is freed after the call. + // + // The offsets array has one element for each argument. These point at + // indexes of where each FFI variable resides. Offsets are used instead + // of pointers in case the store has to be resized, which may move the + // base of the series. Hence the offsets must be mutated into pointers + // at the last minute before the FFI call. + // + REBSER *store = Make_Series(1, sizeof(REBYTE)); + + void *ret_offset; + if (!IS_BLANK(RIN_RET_SCHEMA(rin))) { + ret_offset = cast(void*, arg_to_ffi( + store, // ffi-converted arg appended here + NULL, // dest pointer must be NULL if store is non-NULL + NULL, // arg: none (we're only making space--leave uninitialized) + RIN_RET_SCHEMA(rin), + NULL // param: none (it's a return value/output) + )); + } + else { + // Shouldn't be used (assigned to NULL later) but avoid maybe + // uninitialized warning. + // + ret_offset = cast(void*, cast(REBUPT, 0xDECAFBAD)); + } + + REBSER *arg_offsets; + if (num_args == 0) + arg_offsets = NULL; // don't waste time with the alloc + free + else + arg_offsets = Make_Series(num_args, sizeof(void*)); + + REBCNT i = 0; + + // First gather the fixed parameters from the frame. They are known to + // be of correct general types (they were checked by Do_Core for the call) + // but a STRUCT! might not be compatible with the type of STRUCT! in + // the parameter specification. They might also be out of range, e.g. + // a too-large or negative INTEGER! passed to a uint8. Could fail() here. + // + for (; i < num_fixed; ++i) { + REBUPT offset = arg_to_ffi( + store, // ffi-converted arg appended here + NULL, // dest pointer must be NULL if store is non-NULL + FRM_ARG(f, i + 1), // 1-based + RIN_ARG_SCHEMA(rin, i), // 0-based + FUNC_PARAM(f->phase, i + 1) // 1-based + ); + *SER_AT(void*, arg_offsets, i) = cast(void*, offset); // convert later + } + + // If an FFI routine takes a fixed number of arguments, then its Call + // InterFace (CIF) can be created just once. This will be in the RIN_CIF. + // However a variadic routine requires a CIF that matches the number + // and types of arguments for that specific call. + // + // Note that because these pointers need to be freed by HANDLE! cleanup, + // they need to remember the size. OS_ALLOC() is used, at least until + // HANDLE! is changed to support sizes. + // + ffi_cif *cif; // pre-made if not variadic, built for this call otherwise + ffi_type **args_fftypes = NULL; // ffi_type*[] if num_variable > 0 + + if (NOT(RIN_IS_VARIADIC(rin))) { + cif = RIN_CIF(rin); + } + else { + assert(IS_BLANK(RIN_AT(rin, IDX_ROUTINE_CIF))); + + // CIF creation requires a C array of argument descriptions that is + // contiguous across both the fixed and variadic parts. Start by + // filling in the ffi_type*s for all the fixed args. + // + args_fftypes = OS_ALLOC_N(ffi_type*, num_fixed + num_variable); + + for (i = 0; i < num_fixed; ++i) + args_fftypes[i] = SCHEMA_FFTYPE(RIN_ARG_SCHEMA(rin, i)); + + DECLARE_LOCAL (schema); + DECLARE_LOCAL (param); + + REBDSP dsp; + for (dsp = dsp_orig + 1; i < num_args; dsp += 2, ++i) { + // + // This param is used with the variadic type spec, and is + // initialized as it would be for an ordinary FFI argument. This + // means its allowed type flags are set, which is not really + // necessary. Whatever symbol name is used here will be seen + // in error reports. + // + Schema_From_Block_May_Fail( + schema, + param, // sets type bits in param + DS_AT(dsp + 1) // will error if this is not a block + ); + + args_fftypes[i] = SCHEMA_FFTYPE(schema); + + INIT_TYPESET_NAME(param, Canon(SYM_ELLIPSIS)); + + *SER_AT(void*, arg_offsets, i) = cast(void*, arg_to_ffi( + store, // data appended to store + NULL, // dest pointer must be NULL if store is non-NULL + DS_AT(dsp), // arg + schema, + param // used for typecheck, VAL_TYPESET_SYM for error msgs + )); + } + + DS_DROP_TO(dsp_orig); // done w/args (converted to bytes in `store`) + + cif = OS_ALLOC(ffi_cif); + + ffi_status status = ffi_prep_cif_var( // "_var"-iadic prep_cif version + cif, + RIN_ABI(rin), + num_fixed, // just fixed + num_args, // fixed plus variable + IS_BLANK(RIN_RET_SCHEMA(rin)) + ? &ffi_type_void + : SCHEMA_FFTYPE(RIN_RET_SCHEMA(rin)), // return FFI type + args_fftypes // arguments FFI types + ); + + if (status != FFI_OK) { + OS_FREE(cif); + OS_FREE(args_fftypes); + fail ("FFI: Couldn't prep CIF_VAR"); + } + } + + // Now that all the additions to store have been made, we want to change + // the offsets of each FFI argument into actual pointers (since the + // data won't be relocated) + { + if (IS_BLANK(RIN_RET_SCHEMA(rin))) + ret_offset = NULL; + else + ret_offset = SER_DATA_RAW(store) + cast(REBUPT, ret_offset); + + REBCNT i; + for (i = 0; i < num_args; ++i) { + REBUPT off = cast(REBUPT, *SER_AT(void*, arg_offsets, i)); + assert(off == 0 || off < SER_LEN(store)); + *SER_AT(void*, arg_offsets, i) = SER_DATA_RAW(store) + off; + } + } + + // ** THE ACTUAL FFI CALL ** + // + // Note that the "offsets" are now actually pointers. + { + SET_UNREADABLE_BLANK(&Callback_Error); // !!! is it already? + + ffi_call( + cif, + RIN_CFUNC(rin), + ret_offset, // actually a real pointer now (no longer an offset) + (num_args == 0) + ? NULL + : SER_HEAD(void*, arg_offsets) // also real pointers now + ); + + if (!IS_BLANK_RAW(&Callback_Error)) + fail (VAL_CONTEXT(&Callback_Error)); // asserts if not ERROR! + } + + if (IS_BLANK(RIN_RET_SCHEMA(rin))) + Init_Void(f->out); + else + ffi_to_rebol(f->out, RIN_RET_SCHEMA(rin), ret_offset); + + if (num_args != 0) + Free_Series(arg_offsets); + + Free_Series(store); + + if (num_variable != 0) { + OS_FREE(cif); + OS_FREE(args_fftypes); + } + + // Note: cannot "throw" a Rebol value across an FFI boundary. + + assert(!THROWN(f->out)); + return R_OUT; +} + + +// The GC-able HANDLE! used by callbacks contains a ffi_closure pointer that +// needs to be freed when the handle references go away (really only one +// reference is likely--in the FUNC_BODY of the callback, but still this is +// how the GC gets hooked in Ren-C) +// +static void cleanup_ffi_closure(const REBVAL *v) { + ffi_closure_free(VAL_HANDLE_POINTER(ffi_closure, v)); +} + +static void cleanup_cif(const REBVAL *v) { + FREE(ffi_cif, VAL_HANDLE_POINTER(ffi_cif, v)); +} + +static void cleanup_args_fftypes(const REBVAL *v) { + FREE_N(ffi_type*, VAL_HANDLE_LEN(v), VAL_HANDLE_POINTER(ffi_type*, v)); +} + + +// +// Callbacks allow C code to call Rebol functions. It does so by creating a +// stub function pointer that can be passed in slots where C code expected +// a C function pointer. When such stubs are triggered, the FFI will call +// this dispatcher--which was registered using ffi_prep_closure_loc(). +// +// An example usage of this feature is in %qsort.r, where the C library +// function qsort() is made to use a custom comparison function that is +// actually written in Rebol. +// +static void callback_dispatcher( + ffi_cif *cif, + void *ret, + void **args, + void *user_data +){ + if (!IS_BLANK_RAW(&Callback_Error)) // !!!is this possible? + return; + + REBRIN *rin = cast(REBRIN*, user_data); + assert(!RIN_IS_VARIADIC(rin)); + assert(cif->nargs == RIN_NUM_FIXED_ARGS(rin)); + + // We do not want to longjmp() out of the callback if there is an error. + // It needs to allow the FFI processing to unwind the stack normally so + // that it's in a good state. Therefore this must trap any fail()s. + // + struct Reb_State state; + REBCTX *error; + + PUSH_TRAP(&error, &state); + +// The first time through the following code 'error' will be NULL, but... +// `fail` can longjmp here, so 'error' won't be NULL *if* that happens! + + if (error) { + Init_Error(&Callback_Error, error); + return; + } + + // Build an array of code to run which represents the call. The first + // item in that array will be the callback function value, and then + // the arguments will be the remaining values. + // + REBARR *code = Make_Array(1 + cif->nargs); + RELVAL *elem = ARR_HEAD(code); + Move_Value(elem, FUNC_VALUE(RIN_CALLBACK_FUNC(rin))); + ++elem; + + REBCNT i; + for (i = 0; i < cif->nargs; ++i, ++elem) + ffi_to_rebol(SINK(elem), RIN_ARG_SCHEMA(rin, i), args[i]); + + TERM_ARRAY_LEN(code, 1 + cif->nargs); + MANAGE_ARRAY(code); // DO requires managed arrays (guarded while running) + + DECLARE_LOCAL (result); + if (Do_At_Throws(result, code, 0, SPECIFIED)) + fail (Error_No_Catch_For_Throw(result)); // !!! Tunnel throws out? + + if (cif->rtype->type == FFI_TYPE_VOID) + assert(IS_BLANK(RIN_RET_SCHEMA(rin))); + else { + DECLARE_LOCAL (param); + Init_Typeset(param, 0, Canon(SYM_RETURN)); + arg_to_ffi( + NULL, // store must be NULL if dest is non-NULL, + ret, // destination pointer + result, + RIN_RET_SCHEMA(rin), + param // parameter used for symbol in error only + ); + } + + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); +} + + +// +// Alloc_Ffi_Function_For_Spec: C +// +// This allocates a REBFUN designed for using with the FFI--though it does +// not fill in the actual code to call. That is done by the caller, which +// needs to be done differently if it runs a C function (routine) or if it +// makes Rebol code callable as if it were a C function (callback). +// +// It has a HANDLE! holding a Routine INfo structure (RIN) which describes +// the FFI argument types. For callbacks, this cannot be automatically +// deduced from the parameters of the Rebol function it wraps--because there +// are multiple possible mappings (e.g. differently sized C types all of +// which are passed in from Rebol's INTEGER!) +// +// The spec format is a block which is similar to the spec for functions: +// +// [ +// "document" +// arg1 [type1 type2] "note" +// arg2 [type3] "note" +// ... +// argn [typen] "note" +// return: [type] "note" +// ] +// +static REBFUN *Alloc_Ffi_Function_For_Spec(REBVAL *ffi_spec, ffi_abi abi) { + assert(IS_BLOCK(ffi_spec)); + + REBRIN *r = Make_Array(IDX_ROUTINE_MAX); + + Init_Integer(RIN_AT(r, IDX_ROUTINE_ABI), abi); + + // Caller will update these in the returned function. + // + SET_UNREADABLE_BLANK(RIN_AT(r, IDX_ROUTINE_CFUNC)); + SET_UNREADABLE_BLANK(RIN_AT(r, IDX_ROUTINE_CLOSURE)); + SET_UNREADABLE_BLANK(RIN_AT(r, IDX_ROUTINE_ORIGIN)); // LIBRARY!/FUNCTION! + + Init_Blank(RIN_AT(r, IDX_ROUTINE_RET_SCHEMA)); // returns void as default + + const REBCNT capacity_guess = 8; // !!! Magic number...why 8? (can grow) + + REBARR *paramlist = Make_Array(capacity_guess); + + // first slot is reserved for the "canon value", see `struct Reb_Function` + // + REBVAL *rootparam = Alloc_Tail_Array(paramlist); + + // arguments can be complex, defined as structures. A "schema" is a + // REBVAL that holds either an INTEGER! for simple types, or a HANDLE! + // for compound ones. + // + // Note that in order to avoid deep walking the schemas after construction + // to convert them from unmanaged to managed, they are managed at the + // time of creation. This means that the array of them has to be + // guarded across any evaluations, since the routine being built is not + // ready for GC visibility. + // + // !!! Should the spec analysis be allowed to do evaluation? (it does) + // + REBARR *args_schemas = Make_Array(capacity_guess); + MANAGE_ARRAY(args_schemas); + PUSH_GUARD_ARRAY(args_schemas); + + REBCNT num_fixed = 0; // number of fixed (non-variadic) arguments + REBOOL is_variadic = FALSE; // default to not being variadic + + RELVAL *item = VAL_ARRAY_AT(ffi_spec); + for (; NOT_END(item); ++item) { + if (IS_STRING(item)) + continue; // !!! TBD: extract FUNC_META information from spec notes + + switch (VAL_TYPE(item)) { + case REB_WORD:{ + REBSTR *name = VAL_WORD_SPELLING(item); + + if (SAME_STR(name, Canon(SYM_ELLIPSIS))) { // variadic + if (is_variadic) + fail ("FFI: Duplicate ... indicating variadic"); + + is_variadic = TRUE; + + REBVAL *param = Alloc_Tail_Array(paramlist); + + // Currently the rule is that if VARARGS! is itself a valid + // parameter type, then the varargs will not chain. We want + // chaining as opposed to passing the parameter pack to the + // C code to process (it wouldn't know what to do with it) + // + Init_Typeset( + param, + ALL_64 & ~FLAGIT_KIND(REB_VARARGS), + Canon(SYM_VARARGS) + ); + SET_VAL_FLAG(param, TYPESET_FLAG_VARIADIC); + INIT_VAL_PARAM_CLASS(param, PARAM_CLASS_NORMAL); + } + else { // ordinary argument + if (is_variadic) + fail ("FFI: Variadic must be final parameter"); + + REBVAL *param = Alloc_Tail_Array(paramlist); + + ++item; + + DECLARE_LOCAL (block); + Derelativize(block, item, VAL_SPECIFIER(ffi_spec)); + + Schema_From_Block_May_Fail( + Alloc_Tail_Array(args_schemas), // schema (out) + param, // param (out) + block // block (in) + ); + + INIT_TYPESET_NAME(param, name); + INIT_VAL_PARAM_CLASS(param, PARAM_CLASS_NORMAL); + ++num_fixed; + } + break;} + + case REB_SET_WORD: + switch (VAL_WORD_SYM(item)) { + case SYM_RETURN:{ + if (!IS_BLANK(RIN_AT(r, IDX_ROUTINE_RET_SCHEMA))) + fail ("FFI: Return already specified"); + + ++item; + + DECLARE_LOCAL (block); + Derelativize(block, item, VAL_SPECIFIER(ffi_spec)); + + DECLARE_LOCAL (param); + Schema_From_Block_May_Fail( + RIN_AT(r, IDX_ROUTINE_RET_SCHEMA), + param, // dummy (a return/output has no arg to typecheck) + block + ); + break;} + + default: + fail (KNOWN(item)); + } + break; + + default: + fail (KNOWN(item)); + } + } + + Init_Logic(RIN_AT(r, IDX_ROUTINE_IS_VARIADIC), is_variadic); + + TERM_ARRAY_LEN(r, IDX_ROUTINE_MAX); + ASSERT_ARRAY(args_schemas); + Init_Block(RIN_AT(r, IDX_ROUTINE_ARG_SCHEMAS), args_schemas); + + if (RIN_IS_VARIADIC(r)) { + // + // Each individual call needs to use `ffi_prep_cif_var` to make the + // proper variadic CIF for that call. + // + Init_Blank(RIN_AT(r, IDX_ROUTINE_CIF)); + Init_Blank(RIN_AT(r, IDX_ROUTINE_ARG_FFTYPES)); + } + else { + // The same CIF can be used for every call of the routine if it is + // not variadic. The CIF must stay alive for the entire the lifetime + // of the args_fftypes, apparently. + // + ffi_cif *cif = ALLOC(ffi_cif); + + ffi_type **args_fftypes; + if (num_fixed == 0) + args_fftypes = NULL; + else + args_fftypes = ALLOC_N(ffi_type*, num_fixed); + + REBCNT i; + for (i = 0; i < num_fixed; ++i) + args_fftypes[i] = SCHEMA_FFTYPE(RIN_ARG_SCHEMA(r, i)); + + if ( + FFI_OK != ffi_prep_cif( + cif, + abi, + num_fixed, + IS_BLANK(RIN_RET_SCHEMA(r)) + ? &ffi_type_void + : SCHEMA_FFTYPE(RIN_RET_SCHEMA(r)), + args_fftypes // NULL if 0 fixed args + ) + ){ + fail ("FFI: Couldn't prep CIF"); + } + + Init_Handle_Managed( + RIN_AT(r, IDX_ROUTINE_CIF), + cif, + 0, + &cleanup_cif + ); + + if (args_fftypes == NULL) + Init_Blank(RIN_AT(r, IDX_ROUTINE_ARG_FFTYPES)); + else + Init_Handle_Managed( + RIN_AT(r, IDX_ROUTINE_ARG_FFTYPES), + args_fftypes, + num_fixed, + &cleanup_args_fftypes + ); // lifetime must match cif lifetime + } + + DROP_GUARD_ARRAY(args_schemas); + + // Now fill in the canon value of the paramlist so it is an actual "REBFUN" + // + VAL_RESET_HEADER(rootparam, REB_FUNCTION); + rootparam->payload.function.paramlist = paramlist; + rootparam->extra.binding = NULL; + + SET_SER_FLAG(paramlist, ARRAY_FLAG_PARAMLIST); + MANAGE_ARRAY(paramlist); + SER(paramlist)->link.meta = NULL; + + REBFUN *fun = Make_Function( + paramlist, + &Routine_Dispatcher, + NULL, // no underlying function, this is fundamental + NULL // not providing a specialization + ); + + // The "body" value of a routine is the routine info array. + // + Init_Block(FUNC_BODY(fun), r); + + return fun; // still needs to have function or callback info added! +} + + +// +// make-routine: native [ +// +// {Create a bridge for interfacing with arbitrary C code in a DLL} +// +// return: [function!] +// lib [library!] +// {Library DLL that function lives in (get with MAKE LIBRARY!)} +// name [string!] +// {Linker name of the function in the DLL} +// ffi-spec [block!] +// {Description of what C argument types the function takes} +// /abi +// {Specify the Application Binary Interface (vs. using default)} +// abi-type [word!] +// {'CDECL, 'FASTCALL, 'STDCALL, etc.} +// ] +// +REBNATIVE(make_routine) +// +// !!! Would be nice if this could just take a filename and the lib management +// was automatic, e.g. no LIBRARY! type. +{ + INCLUDE_PARAMS_OF_MAKE_ROUTINE; + + ffi_abi abi; + if (REF(abi)) + abi = Abi_From_Word(ARG(abi_type)); + else + abi = FFI_DEFAULT_ABI; + + // Make sure library wasn't closed with CLOSE + // + REBLIB *lib = VAL_LIBRARY(ARG(lib)); + if (lib == NULL) + fail (ARG(lib)); + + // Try to find the C function pointer in the DLL, if it's there. + // OS_FIND_FUNCTION takes a char* on both Windows and Posix. The + // string that gets here could be REBUNI wide or BYTE_SIZE(), so + // make sure it's turned into a char* before passing. + // + // !!! Should it error if any bytes need to be UTF8 encoded? + // + REBVAL *name = ARG(name); + REBCNT b_index = VAL_INDEX(name); + REBCNT b_len = VAL_LEN_AT(name); + REBSER *byte_sized = Temp_Bin_Str_Managed(name, &b_index, &b_len); + + CFUNC *cfunc = OS_FIND_FUNCTION( + LIB_FD(lib), + SER_AT(char, byte_sized, b_index) // name may not be at head index + ); + if (cfunc == NULL) + fail ("FFI: Couldn't find function in library"); + + // Process the parameter types into a function, then fill it in + + REBFUN *fun = Alloc_Ffi_Function_For_Spec(ARG(ffi_spec), abi); + REBRIN *r = FUNC_ROUTINE(fun); + + Init_Handle_Cfunc(RIN_AT(r, IDX_ROUTINE_CFUNC), cfunc, 0); + Move_Value(RIN_AT(r, IDX_ROUTINE_ORIGIN), ARG(lib)); + + Move_Value(D_OUT, FUNC_VALUE(fun)); + return R_OUT; +} + + +// +// make-routine-raw: native [ +// +// {Create a bridge for interfacing with a C function, by pointer} +// +// return: [function!] +// pointer [integer!] +// {Raw address of function in memory} +// ffi-spec [block!] +// {Description of what C argument types the function takes} +// /abi +// {Specify the Application Binary Interface (vs. using default)} +// abi-type [word!] +// {'CDECL, 'FASTCALL, 'STDCALL, etc.} +// ] +// +REBNATIVE(make_routine_raw) +// +// !!! Would be nice if this could just take a filename and the lib management +// was automatic, e.g. no LIBRARY! type. +{ + INCLUDE_PARAMS_OF_MAKE_ROUTINE_RAW; + + ffi_abi abi; + if (REF(abi)) + abi = Abi_From_Word(ARG(abi_type)); + else + abi = FFI_DEFAULT_ABI; + + // Cannot cast directly to a function pointer from a 64-bit value + // on 32-bit systems; first cast to (U)nsigned int that holds (P)oin(T)er + // + CFUNC *cfunc = cast(CFUNC*, cast(REBUPT, VAL_INT64(ARG(pointer)))); + if (cfunc == NULL) + fail ("FFI: NULL pointer not allowed for raw MAKE-ROUTINE"); + + REBFUN *fun = Alloc_Ffi_Function_For_Spec(ARG(ffi_spec), abi); + REBRIN *r = FUNC_ROUTINE(fun); + + Init_Handle_Cfunc(RIN_AT(r, IDX_ROUTINE_CFUNC), cfunc, 0); + Init_Blank(RIN_AT(r, IDX_ROUTINE_ORIGIN)); // no LIBRARY! in this case. + + Move_Value(D_OUT, FUNC_VALUE(fun)); + return R_OUT; +} + + +// +// make-callback: native [ +// +// {Wrap function so it can be called in raw C code with a function pointer.} +// +// return: [function!] +// action [function!] +// {The existing Rebol function whose functionality is being wrapped} +// ffi-spec [block!] +// {Description of what C types each Rebol argument should map to} +// /abi +// {Specify the Application Binary Interface (vs. using default)} +// abi-type [word!] +// {'CDECL, 'FASTCALL, 'STDCALL, etc.} +// ] +// +REBNATIVE(make_callback) +{ + INCLUDE_PARAMS_OF_MAKE_CALLBACK; + + ffi_abi abi; + if (REF(abi)) + abi = Abi_From_Word(ARG(abi_type)); + else + abi = FFI_DEFAULT_ABI; + + REBFUN *fun = Alloc_Ffi_Function_For_Spec(ARG(ffi_spec), abi); + REBRIN *r = FUNC_ROUTINE(fun); + + void *thunk; // actually CFUNC (FFI uses void*, may not be same size!) + ffi_closure *closure = cast(ffi_closure*, ffi_closure_alloc( + sizeof(ffi_closure), &thunk + )); + + if (closure == NULL) + fail ("FFI: Couldn't allocate closure"); + + ffi_status status = ffi_prep_closure_loc( + closure, + RIN_CIF(r), + callback_dispatcher, // when thunk is called it calls this function... + r, // ...and this piece of data is passed to callback_dispatcher + thunk + ); + + if (status != FFI_OK) + fail ("FFI: Couldn't prep closure"); + + if (sizeof(void*) != sizeof(CFUNC*)) + fail ("FFI does not work when void* size differs from CFUNC* size"); + + // It's the FFI's fault for using the wrong type for the thunk. Use a + // memcpy in order to get around strict checks that absolutely refuse to + // let you do a cast here. + // + CFUNC *cfunc_thunk; + memcpy(&cfunc_thunk, &thunk, sizeof(cfunc_thunk)); + + Init_Handle_Cfunc(RIN_AT(r, IDX_ROUTINE_CFUNC), cfunc_thunk, 0); + Init_Handle_Managed( + RIN_AT(r, IDX_ROUTINE_CLOSURE), + closure, + 0, + &cleanup_ffi_closure + ); + Move_Value(RIN_AT(r, IDX_ROUTINE_ORIGIN), ARG(action)); + + Move_Value(D_OUT, FUNC_VALUE(fun)); + return R_OUT; +} diff --git a/src/core/t-string.c b/src/core/t-string.c index 26d23a658a..c8c7847513 100644 --- a/src/core/t-string.c +++ b/src/core/t-string.c @@ -1,836 +1,1234 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-string.c -** Summary: string related datatypes -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-string.c +// Summary: "string related datatypes" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -#include "sys-scan.h" #include "sys-deci-funcs.h" +#include "sys-int-funcs.h" -/*********************************************************************** -** -*/ REBINT CT_String(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ + +// +// CT_String: C +// +REBINT CT_String(const RELVAL *a, const RELVAL *b, REBINT mode) { - REBINT num; + REBINT num; - if (mode == 3) - return VAL_SERIES(a) == VAL_SERIES(b) && VAL_INDEX(a) == VAL_INDEX(b); + num = Compare_String_Vals(a, b, NOT(mode == 1)); - num = Compare_String_Vals(a, b, (REBOOL) !(mode > 1)); - if (mode >= 0) return (num == 0); - if (mode == -1) return (num >= 0); - return (num > 0); + if (mode >= 0) return (num == 0) ? 1 : 0; + if (mode == -1) return (num >= 0) ? 1 : 0; + return (num > 0) ? 1 : 0; } /*********************************************************************** ** -** Local Utility Functions +** Local Utility Functions ** ***********************************************************************/ +// !!! "STRING value to CHAR value (save some code space)" <-- what? static void str_to_char(REBVAL *out, REBVAL *val, REBCNT idx) { - // STRING value to CHAR value (save some code space) - SET_CHAR(out, GET_ANY_CHAR(VAL_SERIES(val), idx)); + // Note: out may equal val, do assignment in two steps + REBUNI codepoint = GET_ANY_CHAR(VAL_SERIES(val), idx); + Init_Char(out, codepoint); } + static void swap_chars(REBVAL *val1, REBVAL *val2) { - REBUNI c1; - REBUNI c2; - REBSER *s1 = VAL_SERIES(val1); - REBSER *s2 = VAL_SERIES(val2); - - c1 = GET_ANY_CHAR(s1, VAL_INDEX(val1)); - c2 = GET_ANY_CHAR(s2, VAL_INDEX(val2)); - - if (BYTE_SIZE(s1) && c2 > 0xff) Widen_String(s1); - SET_ANY_CHAR(s1, VAL_INDEX(val1), c2); - - if (BYTE_SIZE(s2) && c1 > 0xff) Widen_String(s2); - SET_ANY_CHAR(s2, VAL_INDEX(val2), c1); + REBUNI c1; + REBUNI c2; + REBSER *s1 = VAL_SERIES(val1); + REBSER *s2 = VAL_SERIES(val2); + + c1 = GET_ANY_CHAR(s1, VAL_INDEX(val1)); + c2 = GET_ANY_CHAR(s2, VAL_INDEX(val2)); + + if (BYTE_SIZE(s1) && c2 > 0xff) Widen_String(s1, TRUE); + SET_ANY_CHAR(s1, VAL_INDEX(val1), c2); + + if (BYTE_SIZE(s2) && c1 > 0xff) Widen_String(s2, TRUE); + SET_ANY_CHAR(s2, VAL_INDEX(val2), c1); } + static void reverse_string(REBVAL *value, REBCNT len) { - REBCNT n; - REBCNT m; - REBUNI c; - - if (VAL_BYTE_SIZE(value)) { - REBYTE *bp = VAL_BIN_DATA(value); - - for (n = 0, m = len-1; n < len / 2; n++, m--) { - c = bp[n]; - bp[n] = bp[m]; - bp[m] = (REBYTE)c; - } - } - else { - REBUNI *up = VAL_UNI_DATA(value); - - for (n = 0, m = len-1; n < len / 2; n++, m--) { - c = up[n]; - up[n] = up[m]; - up[m] = c; - } - } + REBCNT n; + REBCNT m; + REBUNI c; + + if (VAL_BYTE_SIZE(value)) { + REBYTE *bp = VAL_BIN_AT(value); + + for (n = 0, m = len-1; n < len / 2; n++, m--) { + c = bp[n]; + bp[n] = bp[m]; + bp[m] = (REBYTE)c; + } + } + else { + REBUNI *up = VAL_UNI_AT(value); + + for (n = 0, m = len-1; n < len / 2; n++, m--) { + c = up[n]; + up[n] = up[m]; + up[m] = c; + } + } } -static REBCNT find_string(REBSER *series, REBCNT index, REBCNT end, REBVAL *target, REBCNT len, REBCNT flags, REBINT skip) -{ - REBCNT start = index; - - if (flags & (AM_FIND_REVERSE | AM_FIND_LAST)) { - skip = -1; - start = 0; - if (flags & AM_FIND_LAST) index = end - len; - else index--; - } - - if (ANY_BINSTR(target)) { - // Do the optimal search or the general search? - if (BYTE_SIZE(series) && VAL_BYTE_SIZE(target) && !(flags & ~(AM_FIND_CASE|AM_FIND_MATCH))) - return Find_Byte_Str(series, start, VAL_BIN_DATA(target), len, !GET_FLAG(flags, ARG_FIND_CASE-1), GET_FLAG(flags, ARG_FIND_MATCH-1)); - else - return Find_Str_Str(series, start, index, end, skip, VAL_SERIES(target), VAL_INDEX(target), len, flags & (AM_FIND_MATCH|AM_FIND_CASE)); - } - else if (IS_BINARY(target)) { - return Find_Byte_Str(series, start, VAL_BIN_DATA(target), len, 0, GET_FLAG(flags, ARG_FIND_MATCH-1)); - } - else if (IS_CHAR(target)) { - return Find_Str_Char(series, start, index, end, skip, VAL_CHAR(target), flags); - } - else if (IS_INTEGER(target)) { - return Find_Str_Char(series, start, index, end, skip, (REBUNI)VAL_INT32(target), flags); - } - else if (IS_BITSET(target)) { - return Find_Str_Bitset(series, start, index, end, skip, VAL_SERIES(target), flags); - } - - return NOT_FOUND; -} -static REBSER *make_string(REBVAL *arg, REBOOL make) -{ - REBSER *ser = 0; - - // MAKE 123 - if (make && (IS_INTEGER(arg) || IS_DECIMAL(arg))) { - ser = Make_Binary(Int32s(arg, 0)); - } - // MAKE/TO - else if (IS_BINARY(arg)) { - REBYTE *bp = VAL_BIN_DATA(arg); - REBCNT len = VAL_LEN(arg); - switch (What_UTF(bp, len)) { - case 0: - break; - case 8: // UTF-8 encoded - bp += 3; - len -= 3; - break; - default: - Trap0(RE_BAD_DECODE); - } - ser = Decode_UTF_String(bp, len, 8); // UTF-8 - } - // MAKE/TO - else if (ANY_BINSTR(arg)) { - ser = Copy_String(VAL_SERIES(arg), VAL_INDEX(arg), VAL_LEN(arg)); - } - // MAKE/TO - else if (ANY_WORD(arg)) { - ser = Copy_Mold_Value(arg, TRUE); - //ser = Append_UTF8(0, Get_Word_Name(arg), -1); - } - // MAKE/TO #"A" - else if (IS_CHAR(arg)) { - ser = (VAL_CHAR(arg) > 0xff) ? Make_Unicode(2) : Make_Binary(2); - Append_Byte(ser, VAL_CHAR(arg)); - } - // MAKE/TO -// else if (IS_NONE(arg)) { -// ser = Make_Binary(0); -// } - else - ser = Copy_Form_Value(arg, 1<= index); + + if (target_len > end - index) // series not long enough to have target + return NOT_FOUND; + + REBCNT start = index; + + if (flags & (AM_FIND_REVERSE | AM_FIND_LAST)) { + skip = -1; + start = 0; + if (flags & AM_FIND_LAST) index = end - target_len; + else index--; + } + + if (ANY_BINSTR(target)) { + // Do the optimal search or the general search? + if ( + BYTE_SIZE(series) + && VAL_BYTE_SIZE(target) + && !(flags & ~(AM_FIND_CASE|AM_FIND_MATCH)) + ) { + return Find_Byte_Str( + series, + start, + VAL_BIN_AT(target), + target_len, + NOT(flags & AM_FIND_CASE), + LOGICAL(flags & AM_FIND_MATCH) + ); + } + else { + return Find_Str_Str( + series, + start, + index, + end, + skip, + VAL_SERIES(target), + VAL_INDEX(target), + target_len, + flags & (AM_FIND_MATCH|AM_FIND_CASE) + ); + } + } + else if (IS_BINARY(target)) { + const REBOOL uncase = FALSE; + return Find_Byte_Str( + series, + start, + VAL_BIN_AT(target), + target_len, + uncase, // "don't treat case insensitively" + LOGICAL(flags & AM_FIND_MATCH) + ); + } + else if (IS_CHAR(target)) { + return Find_Str_Char( + VAL_CHAR(target), + series, + start, + index, + end, + skip, + flags + ); + } + else if (IS_INTEGER(target)) { + return Find_Str_Char( + cast(REBUNI, VAL_INT32(target)), + series, + start, + index, + end, + skip, + flags + ); + } + else if (IS_BITSET(target)) { + return Find_Str_Bitset( + series, + start, + index, + end, + skip, + VAL_SERIES(target), + flags + ); + } + + return NOT_FOUND; } -static REBSER *Make_Binary_BE64(REBVAL *arg) -{ - REBSER *ser = Make_Binary(9); - REBI64 n = VAL_INT64(arg); - REBINT count; - REBYTE *bp = BIN_HEAD(ser); - - for (count = 7; count >= 0; count--) { - bp[count] = (REBYTE)(n & 0xff); - n >>= 8; - } - bp[8] = 0; - ser->tail = 8; - - return ser; -} -static REBSER *make_binary(REBVAL *arg, REBOOL make) +static REBSER *MAKE_TO_String_Common(const REBVAL *arg) { - REBSER *ser; - - // MAKE BINARY! 123 - switch (VAL_TYPE(arg)) { - case REB_INTEGER: - case REB_DECIMAL: - if (make) ser = Make_Binary(Int32s(arg, 0)); - else ser = Make_Binary_BE64(arg); - break; - - // MAKE/TO BINARY! BINARY! - case REB_BINARY: - ser = Copy_Bytes(VAL_BIN_DATA(arg), VAL_LEN(arg)); - break; - - // MAKE/TO BINARY! - case REB_STRING: - case REB_FILE: - case REB_EMAIL: - case REB_URL: - case REB_TAG: -// case REB_ISSUE: - ser = Encode_UTF8_Value(arg, VAL_LEN(arg), 0); - break; - - case REB_BLOCK: - // Join_Binary returns a shared buffer, so produce a copy: - ser = Copy_Series(Join_Binary(arg)); - break; - - // MAKE/TO BINARY! - case REB_TUPLE: - ser = Copy_Bytes(VAL_TUPLE(arg), VAL_TUPLE_LEN(arg)); - break; - - // MAKE/TO BINARY! - case REB_CHAR: - ser = Make_Binary(6); - ser->tail = Encode_UTF8_Char(BIN_HEAD(ser), VAL_CHAR(arg)); - break; - - // MAKE/TO BINARY! - case REB_BITSET: - ser = Copy_Bytes(VAL_BIN(arg), VAL_TAIL(arg)); - break; - - // MAKE/TO BINARY! - case REB_IMAGE: - ser = Make_Image_Binary(arg); - break; - - case REB_MONEY: - ser = Make_Binary(12); - ser->tail = 12; - deci_to_binary(ser->data, VAL_DECI(arg)); - ser->data[12] = 0; - break; - - default: - ser = 0; - } - - return ser; + REBSER *ser = 0; + + // MAKE/TO + if (IS_BINARY(arg)) { + REBYTE *bp = VAL_BIN_AT(arg); + REBCNT len = VAL_LEN_AT(arg); + switch (What_UTF(bp, len)) { + case 0: + break; + case 8: // UTF-8 encoded + bp += 3; + len -= 3; + break; + default: + fail (Error_Bad_Utf8_Raw()); + } + ser = Decode_UTF_String(bp, len, 8); // UTF-8 + } + // MAKE/TO + else if (ANY_BINSTR(arg)) { + ser = Copy_String_Slimming(VAL_SERIES(arg), VAL_INDEX(arg), VAL_LEN_AT(arg)); + } + // MAKE/TO + else if (ANY_WORD(arg)) { + ser = Copy_Mold_Value(arg, 0 /* opts... MOPT_0? */); + } + // MAKE/TO #"A" + else if (IS_CHAR(arg)) { + ser = (VAL_CHAR(arg) > 0xff) ? Make_Unicode(2) : Make_Binary(2); + Append_Codepoint_Raw(ser, VAL_CHAR(arg)); + } + else + ser = Copy_Form_Value(arg, 1 << MOPT_TIGHT); + + return ser; } -/*********************************************************************** -** -*/ REBFLG MT_String(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ + +static REBSER *Make_Binary_BE64(const REBVAL *arg) { - REBCNT i; - - if (!ANY_BINSTR(data)) return FALSE; - *out = *data++; - VAL_SET(out, type); - i = IS_INTEGER(data) ? Int32(data) - 1 : 0; - if (i > VAL_TAIL(out)) i = VAL_TAIL(out); // clip it - VAL_INDEX(out) = i; - return TRUE; + REBSER *ser = Make_Binary(8); + + REBYTE *bp = BIN_HEAD(ser); + + REBI64 i; + REBDEC d; + const REBYTE *cp; + if (IS_INTEGER(arg)) { + assert(sizeof(REBI64) == 8); + i = VAL_INT64(arg); + cp = cast(const REBYTE*, &i); + } + else { + assert(sizeof(REBDEC) == 8); + d = VAL_DECIMAL(arg); + cp = cast(const REBYTE*, &d); + } + +#ifdef ENDIAN_LITTLE + REBCNT n; + for (n = 0; n < 8; ++n) + bp[n] = cp[7 - n]; +#elif defined(ENDIAN_BIG) + REBCNT n; + for (n = 0; n < 8; ++n) + bp[n] = cp[n]; +#else + #error "Unsupported CPU endian" +#endif + + TERM_BIN_LEN(ser, 8); + return ser; } -/*********************************************************************** -** -*/ static int Compare_Chr(const void *v1, const void *v2) -/* -***********************************************************************/ +static REBSER *make_binary(const REBVAL *arg, REBOOL make) { - return ((int)*(REBYTE*)v1) - ((int)*(REBYTE*)v2); + REBSER *ser; + + // MAKE BINARY! 123 + switch (VAL_TYPE(arg)) { + case REB_INTEGER: + case REB_DECIMAL: + if (make) ser = Make_Binary(Int32s(arg, 0)); + else ser = Make_Binary_BE64(arg); + break; + + // MAKE/TO BINARY! BINARY! + case REB_BINARY: + ser = Copy_Bytes(VAL_BIN_AT(arg), VAL_LEN_AT(arg)); + break; + + // MAKE/TO BINARY! + case REB_STRING: + case REB_FILE: + case REB_EMAIL: + case REB_URL: + case REB_TAG: +// case REB_ISSUE: + ser = Make_UTF8_From_Any_String(arg, VAL_LEN_AT(arg), 0); + break; + + case REB_BLOCK: + // Join_Binary returns a shared buffer, so produce a copy: + ser = Copy_Sequence(Join_Binary(arg, -1)); + break; + + // MAKE/TO BINARY! + case REB_TUPLE: + ser = Copy_Bytes(VAL_TUPLE(arg), VAL_TUPLE_LEN(arg)); + break; + + // MAKE/TO BINARY! + case REB_CHAR: + ser = Make_Binary(6); + TERM_SEQUENCE_LEN(ser, Encode_UTF8_Char(BIN_HEAD(ser), VAL_CHAR(arg))); + break; + + // MAKE/TO BINARY! + case REB_BITSET: + ser = Copy_Bytes(VAL_BIN(arg), VAL_LEN_HEAD(arg)); + break; + + // MAKE/TO BINARY! + case REB_IMAGE: + ser = Make_Image_Binary(arg); + break; + + case REB_MONEY: + ser = Make_Binary(12); + deci_to_binary(BIN_HEAD(ser), VAL_MONEY_AMOUNT(arg)); + TERM_SEQUENCE_LEN(ser, 12); + break; + + default: + ser = 0; + } + + return ser; } -/*********************************************************************** -** -*/ static int Compare_Chr_Rev(const void *v1, const void *v2) -/* -***********************************************************************/ -{ - return ((int)*(REBYTE*)v2) - ((int)*(REBYTE*)v1); +// +// MAKE_String: C +// +void MAKE_String(REBVAL *out, enum Reb_Kind kind, const REBVAL *def) { + REBSER *ser; // goto would cross initialization + + if (IS_INTEGER(def)) { + // + // !!! R3-Alpha tolerated decimal, e.g. `make string! 3.14`, which + // is semantically nebulous (round up, down?) and generally bad. + // + ser = Make_Binary(Int32s(def, 0)); + Init_Any_Series(out, kind, ser); + return; + } + else if (IS_BLOCK(def)) { + // + // The construction syntax for making strings or binaries that are + // preloaded with an offset into the data is #[binary [#{0001} 2]]. + // In R3-Alpha make definitions didn't have to be a single value + // (they are for compatibility between construction syntax and MAKE + // in Ren-C). So the positional syntax was #[binary! #{0001} 2]... + // while #[binary [#{0001} 2]] would join the pieces together in order + // to produce #{000102}. That behavior is not available in Ren-C. + + if (VAL_ARRAY_LEN_AT(def) != 2) + goto bad_make; + + RELVAL *any_binstr = VAL_ARRAY_AT(def); + if (!ANY_BINSTR(any_binstr)) + goto bad_make; + if (IS_BINARY(any_binstr) != LOGICAL(kind == REB_BINARY)) + goto bad_make; + + RELVAL *index = VAL_ARRAY_AT(def) + 1; + if (!IS_INTEGER(index)) + goto bad_make; + + REBINT i = Int32(index) - 1 + VAL_INDEX(any_binstr); + if (i < 0 || i > cast(REBINT, VAL_LEN_AT(any_binstr))) + goto bad_make; + + Init_Any_Series_At(out, kind, VAL_SERIES(any_binstr), i); + return; + } + + if (kind == REB_BINARY) + ser = make_binary(def, TRUE); + else + ser = MAKE_TO_String_Common(def); + + if (!ser) + goto bad_make; + + Init_Any_Series_At(out, kind, ser, 0); + return; + +bad_make: + fail (Error_Bad_Make(kind, def)); } -/*********************************************************************** -** -*/ static void Sort_String(REBVAL *string, REBFLG ccase, REBVAL *skipv, REBVAL *compv, REBVAL *part, REBFLG all, REBFLG rev) -/* -***********************************************************************/ +// +// TO_String: C +// +void TO_String(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBCNT len; - REBCNT skip = 1; - REBCNT size = 1; - int (*sfunc)(const void *v1, const void *v2); - - // Determine length of sort: - len = Partial(string, 0, part, 0); - if (len <= 1) return; - - // Skip factor: - if (!IS_NONE(skipv)) { - skip = Get_Num_Arg(skipv); - if (skip <= 0 || len % skip != 0 || skip > len) - Trap_Arg(skipv); - } - - // Use fast quicksort library function: - if (skip > 1) len /= skip, size *= skip; - sfunc = rev ? Compare_Chr_Rev : Compare_Chr; - - //!!uni - needs to compare wide chars too - qsort((void *)VAL_DATA(string), len, size * SERIES_WIDE(VAL_SERIES(string)), sfunc); -} + REBSER *ser; + if (kind == REB_BINARY) + ser = make_binary(arg, FALSE); + else + ser = MAKE_TO_String_Common(arg); + if (ser == NULL) + fail (arg); -/*********************************************************************** -** -*/ REBINT PD_String(REBPVS *pvs) -/* -***********************************************************************/ -{ - REBVAL *data = pvs->value; - REBVAL *val = pvs->setval; - REBINT n = 0; - REBCNT i; - REBINT c; - REBSER *ser = VAL_SERIES(data); - - if (IS_INTEGER(pvs->select)) { - n = Int32(pvs->select) + VAL_INDEX(data) - 1; - } - else return PE_BAD_SELECT; - - if (val == 0) { - if (n < 0 || (REBCNT)n >= SERIES_TAIL(ser)) return PE_NONE; - if (IS_BINARY(data)) { - SET_INTEGER(pvs->store, *BIN_SKIP(ser, n)); - } else { - SET_CHAR(pvs->store, GET_ANY_CHAR(ser, n)); - } - return PE_USE; - } - - if (n < 0 || (REBCNT)n >= SERIES_TAIL(ser)) return PE_BAD_RANGE; - - if (IS_CHAR(val)) { - c = VAL_CHAR(val); - if (c > MAX_CHAR) return PE_BAD_SET; - } - else if (IS_INTEGER(val)) { - c = Int32(val); - if (c > MAX_CHAR || c < 0) return PE_BAD_SET; - if (IS_BINARY(data)) { // special case for binary - if (c > 0xff) Trap_Range(val); - BIN_HEAD(ser)[n] = (REBYTE)c; - return PE_OK; - } - } - else if (ANY_BINSTR(val)) { - i = VAL_INDEX(val); - if (i >= VAL_TAIL(val)) return PE_BAD_SET; - c = GET_ANY_CHAR(VAL_SERIES(val), i); - } - else - return PE_BAD_SELECT; - - TRAP_PROTECT(ser); - - if (BYTE_SIZE(ser) && c > 0xff) Widen_String(ser); - SET_ANY_CHAR(ser, n, c); - - return PE_OK; + Init_Any_Series(out, kind, ser); } -/*********************************************************************** -** -*/ REBINT PD_File(REBPVS *pvs) -/* -***********************************************************************/ +// +// to-string: native [ +// +// {Like TO STRING! but with additional options.} +// +// value [any-value!] +// {Value to convert to a string.} +// /astral +// {Provide special handling for codepoints bigger than 0xFFFF} +// handler [function! string! char! blank!] +// {If function, receives integer argument of large codepoint value} +// ] +// +REBNATIVE(to_string) { - REBSER *ser; - REB_MOLD mo = {0}; - REBCNT n; - REBUNI c; - REBSER *arg; - - if (pvs->setval) return PE_BAD_SET; + INCLUDE_PARAMS_OF_TO_STRING; + + REBVAL *value = ARG(value); + + if (NOT(REF(astral)) || NOT(IS_BINARY(value))) { + TO_String(D_OUT, REB_STRING, value); // just act like TO STRING! + return R_OUT; + } + + // Ordinarily, UTF8 decoding is done into the unicode buffer. The number + // of unicode codepoints is guaranteed to be <= the number of UTF8 bytes, + // so the length is used as a conservative bound. Since we don't know + // how many astral codepoints there are, it's not easy to know the size + // in advance. So the series may be expanded multiple times. + // + REBSER *ser = Make_Unicode(VAL_LEN_AT(value)); + if (Decode_UTF8_Maybe_Astral_Throws( + D_OUT, + ser, + VAL_BIN_AT(value), + VAL_LEN_AT(value), + TRUE, // cr/lf => lf conversion is done by TO_String (review) + ARG(handler) + )){ + return R_OUT_IS_THROWN; + } + + // !!! Note also that since this conversion does not go through the + // unicode buffer, so it's not copied out with "slimming" if it turns out + // to not contain wide chars. + + Init_String(D_OUT, ser); + return R_OUT; +} - ser = Copy_Series_Value(pvs->value); - n = SERIES_TAIL(ser); - if (n > 0) c = GET_ANY_CHAR(ser, n-1); - if (n == 0 || c != '/') Append_Byte(ser, '/'); +enum COMPARE_CHR_FLAGS { + CC_FLAG_WIDE = 1 << 0, // String is REBUNI[] and not REBYTE[] + CC_FLAG_CASE = 1 << 1, // Case sensitive sort + CC_FLAG_REVERSE = 1 << 2 // Reverse sort order +}; - if (ANY_STR(pvs->select)) - arg = VAL_SERIES(pvs->select); - else { - Reset_Mold(&mo); - Mold_Value(&mo, pvs->select, 0); - arg = mo.series; - } - c = GET_ANY_CHAR(arg, 0); - n = (c == '/' || c == '\\') ? 1 : 0; - Append_String(ser, arg, n, arg->tail-n); +// +// Compare_Chr: C +// +// This function is called by qsort_r, on behalf of the string sort +// function. The `thunk` is an argument passed through from the caller +// and given to us by the sort routine, which tells us about the string +// and the kind of sort that was requested. +// +static int Compare_Chr(void *thunk, const void *v1, const void *v2) +{ + REBCNT * const flags = cast(REBCNT*, thunk); + + REBUNI c1; + REBUNI c2; + if (*flags & CC_FLAG_WIDE) { + c1 = *cast(const REBUNI*, v1); + c2 = *cast(const REBUNI*, v2); + } + else { + c1 = cast(REBUNI, *cast(const REBYTE*, v1)); + c2 = cast(REBUNI, *cast(const REBYTE*, v2)); + } + + if (*flags & CC_FLAG_CASE) { + if (*flags & CC_FLAG_REVERSE) + return *cast(const REBYTE*, v2) - *cast(const REBYTE*, v1); + else + return *cast(const REBYTE*, v1) - *cast(const REBYTE*, v2); + } + else { + if (*flags & CC_FLAG_REVERSE) { + if (c1 < UNICODE_CASES) + c1 = UP_CASE(c1); + if (c2 < UNICODE_CASES) + c2 = UP_CASE(c2); + return c2 - c1; + } + else { + if (c1 < UNICODE_CASES) + c1 = UP_CASE(c1); + if (c2 < UNICODE_CASES) + c2 = UP_CASE(c2); + return c1 - c2; + } + } +} - Set_Series(VAL_TYPE(pvs->value), pvs->store, ser); - return PE_USE; +// +// Sort_String: C +// +static void Sort_String( + REBVAL *string, + REBOOL ccase, + REBVAL *skipv, + REBVAL *compv, + REBVAL *part, + REBOOL rev +) { + if (!IS_VOID(compv)) + fail (Error_Bad_Refine_Raw(compv)); // !!! didn't seem to be supported (?) + + REBCNT len; + REBCNT skip = 1; + REBCNT size = 1; + REBCNT thunk = 0; + + // Determine length of sort: + len = Partial(string, 0, part); + if (len <= 1) return; + + // Skip factor: + if (!IS_VOID(skipv)) { + skip = Get_Num_From_Arg(skipv); + if (skip <= 0 || len % skip != 0 || skip > len) + fail (skipv); + } + + // Use fast quicksort library function: + if (skip > 1) len /= skip, size *= skip; + + if (!VAL_BYTE_SIZE(string)) thunk |= CC_FLAG_WIDE; + if (ccase) thunk |= CC_FLAG_CASE; + if (rev) thunk |= CC_FLAG_REVERSE; + + reb_qsort_r( + VAL_RAW_DATA_AT(string), + len, + size * SER_WIDE(VAL_SERIES(string)), + &thunk, + Compare_Chr + ); } -/*********************************************************************** -** -*/ REBTYPE(String) -/* -***********************************************************************/ +// +// PD_String: C +// +REBINT PD_String(REBPVS *pvs) { - REBVAL *value = D_ARG(1); - REBVAL *arg = D_ARG(2); - REBINT index; - REBINT tail; - REBINT len; - REBSER *ser; - REBCNT type; - REBCNT args; - REBCNT ret; - - if ((IS_FILE(value) || IS_URL(value)) && action >= PORT_ACTIONS) { - return T_Port(ds, action); - } - - len = Do_Series_Action(action, value, arg); - if (len >= 0) return len; - - // Common setup code for all actions: - if (action != A_MAKE && action != A_TO) { - index = (REBINT)VAL_INDEX(value); - tail = (REBINT)VAL_TAIL(value); - } - - // Check must be in this order (to avoid checking a non-series value); - if (action >= A_TAKE && action <= A_SORT && IS_PROTECT_SERIES(VAL_SERIES(value))) - Trap0(RE_PROTECTED); - - switch (action) { - - //-- Modification: - case A_APPEND: - case A_INSERT: - case A_CHANGE: - //Modify_String(action, value, arg); - // Length of target (may modify index): (arg can be anything) - len = Partial1((action == A_CHANGE) ? value : arg, DS_ARG(AN_LENGTH)); - index = VAL_INDEX(value); - args = 0; - if (IS_BINARY(value)) SET_FLAG(args, AN_SERIES); // special purpose - if (DS_REF(AN_PART)) SET_FLAG(args, AN_PART); - index = Modify_String(action, VAL_SERIES(value), index, arg, args, len, DS_REF(AN_DUP) ? Int32(DS_ARG(AN_COUNT)) : 1); - VAL_INDEX(value) = index; - break; - - //-- Search: - case A_SELECT: - ret = ALL_SELECT_REFS; - goto find; - case A_FIND: - ret = ALL_FIND_REFS; -find: - args = Find_Refines(ds, ret); - - if (IS_BINARY(value)) { - args |= AM_FIND_CASE; - if (!IS_BINARY(arg) && !IS_INTEGER(arg) && !IS_BITSET(arg)) Trap0(RE_NOT_SAME_TYPE); - if (IS_INTEGER(arg)) { - if (VAL_INT64(arg) < 0 || VAL_INT64(arg) > 255) Trap_Range(arg); - len = 1; - } - } - else { - if (IS_CHAR(arg) || IS_BITSET(arg)) len = 1; - else if (!ANY_STR(arg)) { - Set_String(arg, Copy_Form_Value(arg, 0)); - } - } - - if (ANY_BINSTR(arg)) len = VAL_LEN(arg); - - if (args & AM_FIND_PART) tail = Partial(value, 0, D_ARG(ARG_FIND_LENGTH), 0); - ret = 1; // skip size - if (args & AM_FIND_SKIP) ret = Partial(value, 0, D_ARG(ARG_FIND_SIZE), 0); - - ret = find_string(VAL_SERIES(value), index, tail, arg, len, args, ret); - - if (ret >= (REBCNT)tail) goto is_none; - if (args & AM_FIND_ONLY) len = 1; - - if (action == A_FIND) { - if (args & (AM_FIND_TAIL | AM_FIND_MATCH)) ret += len; - VAL_INDEX(value) = ret; - } - else { - ret++; - if (ret >= (REBCNT)tail) goto is_none; - if (IS_BINARY(value)) { - SET_INTEGER(value, *BIN_SKIP(VAL_SERIES(value), ret)); - } - else - str_to_char(value, value, ret); - } - break; - - //-- Picking: - case A_PICK: - case A_POKE: - len = Get_Num_Arg(arg); // Position - index += len - 1; - //if (len > 0) index--; - if (index < 0 || index >= tail) { - if (action == A_PICK) goto is_none; - Trap_Range(arg); - } - if (action == A_PICK) { -pick_it: - if (IS_BINARY(value)) { - SET_INTEGER(DS_RETURN, *VAL_BIN_SKIP(value, index)); - } - else - str_to_char(DS_RETURN, value, index); - return R_RET; - } - else { - REBUNI c; - arg = D_ARG(3); - if (IS_CHAR(arg)) - c = VAL_CHAR(arg); - else if (IS_INTEGER(arg) && VAL_UNT64(arg) <= MAX_CHAR) - c = VAL_INT32(arg); - else Trap_Arg(arg); - - ser = VAL_SERIES(value); - if (IS_BINARY(value)) { - if (c > 0xff) Trap_Range(arg); - BIN_HEAD(ser)[index] = (REBYTE)c; - } - else { - if (BYTE_SIZE(ser) && c > 0xff) Widen_String(ser); - SET_ANY_CHAR(ser, index, c); - } - value = arg; - } - break; - - case A_TAKE: - if (D_REF(2)) { - len = Partial(value, 0, D_ARG(3), 0); - if (len == 0) { -zero_str: - Set_Series(VAL_TYPE(value), D_RET, Make_Binary(0)); - return R_RET; - } - } else - len = 1; - - index = VAL_INDEX(value); // /part can change index - - // take/last: - if (D_REF(5)) index = tail - len; - if (index < 0 || index >= tail) { - if (!D_REF(2)) goto is_none; - goto zero_str; - } - - ser = VAL_SERIES(value); - // if no /part, just return value, else return string: - if (!D_REF(2)) { - if (IS_BINARY(value)) { - SET_INTEGER(value, *VAL_BIN_SKIP(value, index)); - } else - str_to_char(value, value, index); - } - else Set_Series(VAL_TYPE(value), value, Copy_String(ser, index, len)); - Remove_Series(ser, index, len); - break; - - case A_CLEAR: - if (index < tail) { - if (index == 0) Reset_Series(VAL_SERIES(value)); - else { - VAL_TAIL(value) = (REBCNT)index; - TERM_SERIES(VAL_SERIES(value)); - } - } - break; - - //-- Creation: - - case A_COPY: - len = Partial(value, 0, D_ARG(3), 0); // Can modify value index. - ser = Copy_String(VAL_SERIES(value), VAL_INDEX(value), len); - goto ser_exit; - - case A_MAKE: - case A_TO: - // Determine the datatype to create: - type = VAL_TYPE(value); - if (type == REB_DATATYPE) type = VAL_DATATYPE(value); - - if (IS_NONE(arg)) Trap_Make(type, arg); - - ser = (type != REB_BINARY) - ? make_string(arg, (REBOOL)(action == A_MAKE)) - : make_binary(arg, (REBOOL)(action == A_MAKE)); - - if (ser) goto str_exit; - Trap_Arg(arg); - - //-- Bitwise: - - case A_AND: - case A_OR: - case A_XOR: - if (!IS_BINARY(arg)) Trap_Arg(arg); - VAL_LIMIT_SERIES(value); - VAL_LIMIT_SERIES(arg); - ser = Xandor_Binary(action, value, arg); - goto ser_exit; - - case A_COMPLEMENT: - if (!IS_BINARY(arg)) Trap_Arg(arg); - ser = Complement_Binary(value); - goto ser_exit; - - //-- Special actions: - - case A_TRIM: - // Check for valid arg combinations: - args = Find_Refines(ds, ALL_TRIM_REFS); - if ( - (args & (AM_TRIM_ALL | AM_TRIM_WITH)) && - (args & (AM_TRIM_HEAD | AM_TRIM_TAIL | AM_TRIM_LINES | AM_TRIM_AUTO)) || - (args & AM_TRIM_AUTO) && - (args & (AM_TRIM_HEAD | AM_TRIM_TAIL | AM_TRIM_LINES | AM_TRIM_ALL | AM_TRIM_WITH)) - ) - Trap0(RE_BAD_REFINES); - - Trim_String(VAL_SERIES(value), VAL_INDEX(value), VAL_LEN(value), args, D_ARG(ARG_TRIM_STR)); - break; - - case A_SWAP: - if (VAL_TYPE(value) != VAL_TYPE(arg)) Trap0(RE_NOT_SAME_TYPE); - if (IS_PROTECT_SERIES(VAL_SERIES(arg))) Trap0(RE_PROTECTED); - if (index < tail && VAL_INDEX(arg) < VAL_TAIL(arg)) - swap_chars(value, arg); - // Trap_Range(arg); // ignore range error - break; - - case A_REVERSE: - len = Partial(value, 0, D_ARG(3), 0); - if (len > 0) reverse_string(value, len); - break; - - case A_SORT: - Sort_String( - value, - D_REF(2), // case sensitive - D_ARG(4), // skip size - D_ARG(6), // comparator - D_ARG(8), // part-length - D_REF(9), // all fields - D_REF(10) // reverse - ); - break; - - case A_RANDOM: - if (D_REF(2)) { // seed - Set_Random(Compute_CRC(VAL_BIN_DATA(value), VAL_LEN(value))); - return R_UNSET; - } - if (D_REF(4)) { // /only - if (index >= tail) goto is_none; - index += (REBCNT)Random_Int(D_REF(3)) % (tail - index); // /secure - goto pick_it; - } - Shuffle_String(value, D_REF(3)); // /secure - break; - - default: - Trap_Action(VAL_TYPE(value), action); - } - - DS_RET_VALUE(value); - return R_RET; - -ser_exit: - type = VAL_TYPE(value); -str_exit: - Set_Series(type, D_RET, ser); - return R_RET; - -is_none: - return R_NONE; + REBSER *ser = VAL_SERIES(pvs->value); + + // Note: There was some more careful management of overflow here in the + // PICK and POKE actions, before unification. But otherwise the code + // was less thorough. Consider integrating this bit, though it seems + // that a more codebase-wide review should be given to the issue. + // + /* + REBINT len = Get_Num_From_Arg(arg); + if ( + REB_I32_SUB_OF(len, 1, &len) + || REB_I32_ADD_OF(index, len, &index) + || index < 0 || index >= tail + ){ + fail (Error_Out_Of_Range(arg)); + } + */ + + if (pvs->opt_setval == NULL) { // PICK-ing + if (IS_INTEGER(pvs->picker)) { + REBINT n = Int32(pvs->picker) + VAL_INDEX(pvs->value) - 1; + if (n < 0 || cast(REBCNT, n) >= SER_LEN(ser)) { + Init_Void(pvs->store); + return PE_USE_STORE; + } + + if (IS_BINARY(pvs->value)) + Init_Integer(pvs->store, *BIN_AT(ser, n)); + else + Init_Char(pvs->store, GET_ANY_CHAR(ser, n)); + + return PE_USE_STORE; + } + + if ( + IS_BINARY(pvs->value) + || NOT(IS_WORD(pvs->picker) || ANY_STRING(pvs->picker)) + ){ + fail (Error_Bad_Path_Select(pvs)); + } + + // !!! This is a historical and questionable feature, where path + // picking a string or word or otherwise out of a FILE! or URL! will + // generate a new FILE! or URL! with a slash in it. + // + // >> x: %foo + // >> type-of quote x/bar + // == path! + // + // >> x/bar + // == %foo/bar ;-- a FILE! + // + // This can only be done with evaluations, since FILE! and URL! have + // slashes in their literal form: + // + // >> type-of quote %foo/bar + // == file! + // + // Because Ren-C unified picking and pathing, this somewhat odd + // feature is now part of PICKing a string from another string. + + REBSER *ser = Copy_Sequence_At_Position(KNOWN(pvs->value)); + + // This makes sure there's always a "/" at the end of the file before + // appending new material via a picker: + // + // >> x: %foo + // >> (x)/("bar") + // == %foo/bar + // + REBCNT len = SER_LEN(ser); + if (len == 0) + Append_Codepoint_Raw(ser, '/'); + else { + REBUNI ch_last = GET_ANY_CHAR(ser, len - 1); + if (ch_last != '/') + Append_Codepoint_Raw(ser, '/'); + } + + REB_MOLD mo; + CLEARS(&mo); + Push_Mold(&mo); + + Mold_Value(&mo, pvs->picker, FALSE); + + // The `skip` logic here regarding slashes and backslashes apparently + // is for an exception to the rule of appending the molded content. + // It doesn't want two slashes in a row: + // + // >> x/("/bar") + // == %foo/bar + // + // !!! Review if this makes sense under a larger philosophy of string + // path composition. + // + REBUNI ch_start = GET_ANY_CHAR(mo.series, mo.start); + REBCNT skip = (ch_start == '/' || ch_start == '\\') ? 1 : 0; + + // !!! Would be nice if there was a better way of doing this that didn't + // involve reaching into mo.start and mo.series. + // + Append_String( + ser, // dst + mo.series, // src + mo.start + skip, // i + SER_LEN(mo.series) - mo.start - skip // len + ); + + Drop_Mold(&mo); + + // Note: pvs->value may point to pvs->store + // + Init_Any_Series(pvs->store, VAL_TYPE(pvs->value), ser); + return PE_USE_STORE; + } + + // Otherwise, POKE-ing + + FAIL_IF_READ_ONLY_SERIES(ser); + + if (NOT(IS_INTEGER(pvs->picker))) + fail (Error_Bad_Path_Select(pvs)); + + REBINT n = Int32(pvs->picker) + VAL_INDEX(pvs->value) - 1; + if (n < 0 || cast(REBCNT, n) >= SER_LEN(ser)) + fail (Error_Bad_Path_Range(pvs)); + + const REBVAL *setval = pvs->opt_setval; + + REBINT c; + if (IS_CHAR(setval)) { + c = VAL_CHAR(setval); + if (c > MAX_CHAR) + fail (Error_Bad_Path_Set(pvs)); + } + else if (IS_INTEGER(setval)) { + c = Int32(setval); + if (c > MAX_CHAR || c < 0) + fail (Error_Bad_Path_Set(pvs)); + } + else if (ANY_BINSTR(setval)) { + REBCNT i = VAL_INDEX(setval); + if (i >= VAL_LEN_HEAD(setval)) + fail (Error_Bad_Path_Set(pvs)); + + c = GET_ANY_CHAR(VAL_SERIES(setval), i); + } + else + fail (Error_Bad_Path_Select(pvs)); + + if (IS_BINARY(pvs->value)) { + if (c > 0xff) + fail (Error_Out_Of_Range(setval)); + + BIN_HEAD(ser)[n] = cast(REBYTE, c); + return PE_OK; + } + + if (BYTE_SIZE(ser) && c > 0xff) + Widen_String(ser, TRUE); + + SET_ANY_CHAR(ser, n, c); + + return PE_OK; } -#ifdef oldcode -/*********************************************************************** -** -x*/ void Modify_StringX(REBCNT action, REBVAL *string, REBVAL *arg) -/* -** Actions: INSERT, APPEND, CHANGE -** -** string [string!] {Series at point to insert} -** value [any-type!] {The value to insert} -** /part {Limits to a given length or position.} -** length [number! series! pair!] -** /only {Inserts a series as a series.} -** /dup {Duplicates the insert a specified number of times.} -** count [number! pair!] -** -***********************************************************************/ +// +// REBTYPE: C +// +REBTYPE(String) { - REBSER *series = VAL_SERIES(string); - REBCNT index = VAL_INDEX(string); - REBCNT tail = VAL_TAIL(string); - REBINT rlen; // length to be removed - REBINT ilen = 1; // length to be inserted - REBINT cnt = 1; // DUP count - REBINT size; - REBVAL *val; - REBSER *arg_ser = 0; // argument series - - // Length of target (may modify index): (arg can be anything) - rlen = Partial1((action == A_CHANGE) ? string : arg, DS_ARG(AN_LENGTH)); - - index = VAL_INDEX(string); - if (action == A_APPEND || index > tail) index = tail; - - // If the arg is not a string, then we need to create a string: - if (IS_BINARY(string)) { - if (IS_INTEGER(arg)) { - if (VAL_INT64(arg) > 255 || VAL_INT64(arg) < 0) - Trap_Range(arg); - arg_ser = Make_Binary(1); - Append_Byte(arg_ser, VAL_CHAR(arg)); // check for size!!! - } - else if (!ANY_BINSTR(arg)) Trap_Arg(arg); - } - else if (IS_BLOCK(arg)) { - // MOVE! - REB_MOLD mo = {0}; - arg_ser = mo.series = Make_Unicode(VAL_BLK_LEN(arg) * 10); // GC!? - for (val = VAL_BLK_DATA(arg); NOT_END(val); val++) - Mold_Value(&mo, val, 0); - } - else if (IS_CHAR(arg)) { - // Optimize this case !!! - arg_ser = Make_Unicode(1); - Append_Byte(arg_ser, VAL_CHAR(arg)); - } - else if (!ANY_STR(arg) || IS_TAG(arg)) { - arg_ser = Copy_Form_Value(arg, 0); - } - if (arg_ser) Set_String(arg, arg_ser); - else arg_ser = VAL_SERIES(arg); - - // Length of insertion: - ilen = (action != A_CHANGE && DS_REF(AN_PART)) ? rlen : VAL_LEN(arg); - - // If Source == Destination we need to prevent possible conflicts. - // Clone the argument just to be safe. - // (Note: It may be possible to optimize special cases like append !!) - if (series == VAL_SERIES(arg)) { - arg_ser = Copy_Series_Part(arg_ser, VAL_INDEX(arg), ilen); // GC!? - } - - // Get /DUP count: - if (DS_REF(AN_DUP)) { - cnt = Int32(DS_ARG(AN_COUNT)); - if (cnt <= 0) return; // no changes - } - - // Total to insert: - size = cnt * ilen; - - if (action != A_CHANGE) { - // Always expand series for INSERT and APPEND actions: - Expand_Series(series, index, size); - } else { - if (size > rlen) - Expand_Series(series, index, size-rlen); - else if (size < rlen && DS_REF(AN_PART)) - Remove_Series(series, index, rlen-size); - else if (size + index > tail) { - EXPAND_SERIES_TAIL(series, size - (tail - index)); - } - } - - // For dup count: - for (; cnt > 0; cnt--) { - Insert_String(series, index, arg_ser, VAL_INDEX(arg), ilen, TRUE); - index += ilen; - } - - TERM_SERIES(series); - - VAL_INDEX(string) = (action == A_APPEND) ? 0 : index; + REBSER *ser; + TRASH_POINTER_IF_DEBUG(ser); // `goto return_ser;` will return this + + REBVAL *value = D_ARG(1); + REBVAL *arg = D_ARGC > 1 ? D_ARG(2) : NULL; + + // Common operations for any series type (length, head, etc.) + { + REB_R r = Series_Common_Action_Maybe_Unhandled(frame_, action); + if (r != R_UNHANDLED) + return r; + } + + // Common setup code for all actions: + // + REBINT index = cast(REBINT, VAL_INDEX(value)); + REBINT tail = cast(REBINT, VAL_LEN_HEAD(value)); + + switch (action) { + + //-- Modification: + case SYM_APPEND: + case SYM_INSERT: + case SYM_CHANGE: { + INCLUDE_PARAMS_OF_INSERT; + + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(value)); + + UNUSED(PAR(series)); + UNUSED(PAR(value)); + + if (REF(only)) { + // !!! Doesn't pay attention...all string appends are /ONLY + } + + REBINT len; + Partial1( + (action == SYM_CHANGE) ? value : arg, + ARG(limit), + cast(REBCNT*, &len) + ); + index = VAL_INDEX(value); + + REBFLGS flags = 0; + if (IS_BINARY(value)) + flags |= AM_BINARY_SERIES; + if (REF(part)) + flags |= AM_PART; + index = Modify_String( + action, + VAL_SERIES(value), + index, + arg, + flags, + len, + REF(dup) ? Int32(ARG(count)) : 1 + ); + ENSURE_SERIES_MANAGED(VAL_SERIES(value)); + VAL_INDEX(value) = index; + break; } + + //-- Search: + case SYM_SELECT_P: + case SYM_FIND: { + INCLUDE_PARAMS_OF_FIND; + + UNUSED(PAR(series)); + UNUSED(PAR(value)); + + REBFLGS flags = ( + (REF(only) ? AM_FIND_ONLY : 0) + | (REF(match) ? AM_FIND_MATCH : 0) + | (REF(reverse) ? AM_FIND_REVERSE : 0) + | (REF(case) ? AM_FIND_CASE : 0) + | (REF(last) ? AM_FIND_LAST : 0) + | (REF(tail) ? AM_FIND_TAIL : 0) + ); + + REBINT len; + if (IS_BINARY(value)) { + flags |= AM_FIND_CASE; + + if (!IS_BINARY(arg) && !IS_INTEGER(arg) && !IS_BITSET(arg)) + fail (Error_Not_Same_Type_Raw()); + + if (IS_INTEGER(arg)) { + if (VAL_INT64(arg) < 0 || VAL_INT64(arg) > 255) + fail (Error_Out_Of_Range(arg)); + len = 1; + } + } + else { + if (IS_CHAR(arg) || IS_BITSET(arg)) + len = 1; + else if (!IS_STRING(arg)) { + // + // !! This FORM creates a temporary value that is then handed + // over to the GC. Not only could the temporary value be + // unmanaged (and freed), a more efficient matching could + // be done e.g. of `FIND "" ` without having + // to create an entire series just to include the delimiters. + // + REBSER *copy = Copy_Form_Value(arg, 0); + Init_String(arg, copy); + } + } + + if (ANY_BINSTR(arg)) + len = VAL_LEN_AT(arg); + + if (REF(part)) + tail = Partial(value, 0, ARG(limit)); + + REBCNT skip; + if (REF(skip)) + skip = Partial(value, 0, ARG(size)); + else + skip = 1; + + REBCNT ret = find_string( + VAL_SERIES(value), index, tail, arg, len, flags, skip + ); + + if (ret >= (REBCNT)tail) + return R_BLANK; + + if (REF(only)) + len = 1; + + if (action == SYM_FIND) { + if (REF(tail) || REF(match)) + ret += len; + VAL_INDEX(value) = ret; + } + else { + ret++; + if (ret >= (REBCNT)tail) return R_BLANK; + if (IS_BINARY(value)) { + Init_Integer(value, *BIN_AT(VAL_SERIES(value), ret)); + } + else + str_to_char(value, value, ret); + } + break; } + + case SYM_TAKE_P: { + INCLUDE_PARAMS_OF_TAKE_P; + + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(value)); + + UNUSED(PAR(series)); + + if (REF(deep)) + fail (Error_Bad_Refines_Raw()); + + REBINT len; + if (REF(part)) { + len = Partial(value, 0, ARG(limit)); + if (len == 0) { + Init_Any_Series(D_OUT, VAL_TYPE(value), Make_Binary(0)); + return R_OUT; + } + } else + len = 1; + + index = VAL_INDEX(value); // /PART can change index + + if (REF(last)) + index = tail - len; + if (index < 0 || index >= tail) { + if (NOT(REF(part))) + return R_BLANK; + Init_Any_Series(D_OUT, VAL_TYPE(value), Make_Binary(0)); + return R_OUT; + } + + ser = VAL_SERIES(value); + + // if no /PART, just return value, else return string + // + if (NOT(REF(part))) { + if (IS_BINARY(value)) { + Init_Integer(value, *VAL_BIN_AT_HEAD(value, index)); + } else + str_to_char(value, value, index); + } + else { + enum Reb_Kind kind = VAL_TYPE(value); + Init_Any_Series( + value, kind, Copy_String_Slimming(ser, index, len) + ); + } + Remove_Series(ser, index, len); + break; } + + case SYM_CLEAR: { + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(value)); + + if (index < tail) { + if (index == 0) + Reset_Sequence(VAL_SERIES(value)); + else + TERM_SEQUENCE_LEN(VAL_SERIES(value), cast(REBCNT, index)); + } + break; } + + //-- Creation: + + case SYM_COPY: { + INCLUDE_PARAMS_OF_COPY; + + UNUSED(PAR(value)); + + if (REF(deep)) + fail (Error_Bad_Refines_Raw()); + if (REF(types)) { + UNUSED(ARG(kinds)); + fail (Error_Bad_Refines_Raw()); + } + + UNUSED(REF(part)); + REBINT len = Partial(value, 0, ARG(limit)); // Can modify value index. + ser = Copy_String_Slimming(VAL_SERIES(value), VAL_INDEX(value), len); + goto return_ser; } + + //-- Bitwise: + + case SYM_AND_T: + case SYM_OR_T: + case SYM_XOR_T: { + if (NOT(IS_BINARY(arg))) + fail (arg); + + if (VAL_INDEX(value) > VAL_LEN_HEAD(value)) + VAL_INDEX(value) = VAL_LEN_HEAD(value); + + if (VAL_INDEX(arg) > VAL_LEN_HEAD(arg)) + VAL_INDEX(arg) = VAL_LEN_HEAD(arg); + + ser = Xandor_Binary(action, value, arg); + goto return_ser; } + + case SYM_COMPLEMENT: { + if (NOT(IS_BINARY(value))) + fail (value); + + ser = Complement_Binary(value); + goto return_ser; } + + // Arithmetic operations are allowed on BINARY!, because it's too limiting + // to not allow `#{4B} + 1` => `#{4C}`. Allowing the operations requires + // a default semantic of binaries as unsigned arithmetic, since one + // does not want `#{FF} + 1` to be #{FE}. It uses a big endian + // interpretation, so `#{00FF} + 1` is #{0100} + // + // Since Rebol is a language with mutable semantics by default, `add x y` + // will mutate x by default (if X is not an immediate type). `+` is an + // enfixing of `add-of` which copies the first argument before adding. + // + // To try and maximize usefulness, the semantic chosen is that any + // arithmetic that would go beyond the bounds of the length is considered + // an overflow. Hence the size of the result binary will equal the size + // of the original binary. This means that `#{0100} - 1` is #{00FF}, + // not #{FF}. + // + // !!! The code below is extremely slow and crude--using an odometer-style + // loop to do the math. What's being done here is effectively "bigint" + // math, and it might be that it would share code with whatever big + // integer implementation was used; e.g. integers which exceeded the size + // of the platform REBI64 would use BINARY! under the hood. + + case SYM_SUBTRACT: + case SYM_ADD: { + if (NOT(IS_BINARY(value))) + fail (value); + + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(value)); + + REBINT amount; + if (IS_INTEGER(arg)) + amount = VAL_INT32(arg); + else if (IS_BINARY(arg)) + fail (arg); // should work + else + fail (arg); // what about other types? + + if (action == SYM_SUBTRACT) + amount = -amount; + + if (amount == 0) { // adding or subtracting 0 works, even #{} + 0 + Move_Value(D_OUT, value); + return R_OUT; + } + else if (VAL_LEN_AT(value) == 0) // add/subtract to #{} otherwise + fail (Error_Overflow_Raw()); + + while (amount != 0) { + REBCNT wheel = VAL_LEN_HEAD(value) - 1; + while (TRUE) { + REBYTE *b = VAL_BIN_AT_HEAD(value, wheel); + if (amount > 0) { + if (*b == 255) { + if (wheel == VAL_INDEX(value)) + fail (Error_Overflow_Raw()); + + *b = 0; + --wheel; + continue; + } + ++(*b); + --amount; + break; + } + else { + if (*b == 0) { + if (wheel == VAL_INDEX(value)) + fail (Error_Overflow_Raw()); + + *b = 255; + --wheel; + continue; + } + --(*b); + ++amount; + break; + } + } + } + Move_Value(D_OUT, value); + return R_OUT; } + + //-- Special actions: + + case SYM_TRIM: { + INCLUDE_PARAMS_OF_TRIM; + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(value)); + + UNUSED(PAR(series)); + + ser = VAL_SERIES(value); + + if (REF(all) || REF(with)) { + if (REF(head) || REF(tail) || REF(lines) || REF(auto)) + fail (Error_Bad_Refines_Raw()); + + Whitespace_Replace_With(ser, index, tail, ARG(str)); + } + else if (REF(auto)) { + if (REF(head) || REF(tail) || REF(lines) || REF(all) || REF(with)) + fail (Error_Bad_Refines_Raw()); + + Trim_String_Auto(ser, index, tail); + } + else if (REF(lines)) { + Trim_String_Lines(ser, index, tail); + } + else { + Trim_String_Head_Tail( + ser, + index, + tail, + REF(head), + REF(tail) + ); + } + break; } + + case SYM_SWAP: { + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(value)); + + if (VAL_TYPE(value) != VAL_TYPE(arg)) + fail (Error_Not_Same_Type_Raw()); + + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(arg)); + + if (index < tail && VAL_INDEX(arg) < VAL_LEN_HEAD(arg)) + swap_chars(value, arg); + break; } + + case SYM_REVERSE: { + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(value)); + + REBINT len = Partial(value, 0, D_ARG(3)); + if (len > 0) + reverse_string(value, len); + break; } + + case SYM_SORT: { + INCLUDE_PARAMS_OF_SORT; + + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(value)); + + UNUSED(PAR(series)); + UNUSED(REF(skip)); + UNUSED(REF(compare)); + UNUSED(REF(part)); + + if (REF(all)) {// Not Supported + fail (Error_Bad_Refine_Raw(ARG(all))); + } + + Sort_String( + value, + REF(case), + ARG(size), // skip size (void if not /SKIP) + ARG(comparator), // (void if not /COMPARE) + ARG(limit), // (void if not /PART) + REF(reverse) + ); + break; } + + case SYM_RANDOM: { + INCLUDE_PARAMS_OF_RANDOM; + + UNUSED(PAR(value)); + + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(value)); + + if (REF(seed)) { + // + // Use the string contents as a seed. R3-Alpha would try and + // treat it as byte-sized hence only take half the data into + // account if it were REBUNI-wide. This multiplies the number + // of bytes by the width and offsets by the size. + // + Set_Random( + Compute_CRC( + SER_AT_RAW( + SER_WIDE(VAL_SERIES(value)), + VAL_SERIES(value), + VAL_INDEX(value) + ), + VAL_LEN_AT(value) * SER_WIDE(VAL_SERIES(value)) + ) + ); + return R_VOID; + } + + if (REF(only)) { + if (index >= tail) + return R_BLANK; + index += (REBCNT)Random_Int(REF(secure)) % (tail - index); + if (IS_BINARY(value)) { // same as PICK + Init_Integer(D_OUT, *VAL_BIN_AT_HEAD(value, index)); + } + else + str_to_char(D_OUT, value, index); + return R_OUT; + } + Shuffle_String(value, REF(secure)); + break; } + + default: + // Let the port system try the action, e.g. OPEN %foo.txt + // + if ((IS_FILE(value) || IS_URL(value))) + return T_Port(frame_, action); + + fail (Error_Illegal_Action(VAL_TYPE(value), action)); + } + + Move_Value(D_OUT, value); + return R_OUT; + +return_ser: + Init_Any_Series(D_OUT, VAL_TYPE(value), ser); + return R_OUT; } -#endif diff --git a/src/core/t-struct.c b/src/core/t-struct.c new file mode 100644 index 0000000000..5106badb0a --- /dev/null +++ b/src/core/t-struct.c @@ -0,0 +1,1611 @@ +// +// File: %t-struct.c +// Summary: "C struct object datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2014 Atronix Engineering, Inc. +// Copyright 2014-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + +#include "sys-core.h" + + +// The managed HANDLE! for a ffi_type will have a reference in structs that +// use it. Basic non-struct FFI_TYPE_XXX use the stock ffi_type_xxx pointers +// that do not have to be freed, so they use simple HANDLE! which do not +// register this cleanup hook. +// +static void cleanup_ffi_type(const REBVAL *v) { + ffi_type *fftype = VAL_HANDLE_POINTER(ffi_type, v); + if (fftype->type == FFI_TYPE_STRUCT) + OS_FREE(fftype->elements); + OS_FREE(fftype); +} + + +static void fail_if_non_accessible(const REBVAL *val) +{ + if (VAL_STRUCT_INACCESSIBLE(val)) { + DECLARE_LOCAL (i); + Init_Integer(i, cast(REBUPT, VAL_STRUCT_DATA_HEAD(val))); + fail (Error_Bad_Memory_Raw(i, val)); + } +} + +static void get_scalar( + REBVAL *out, + REBSTU *stu, + REBFLD *field, + REBCNT n // element index, starting from 0 +){ + assert(n == 0 || FLD_IS_ARRAY(field)); + + REBCNT offset = + STU_OFFSET(stu) + FLD_OFFSET(field) + (n * FLD_WIDE(field)); + + if (FLD_IS_STRUCT(field)) { + // + // In order for the schema to participate in GC it must be a series. + // Currently this series is created with a single value of the root + // schema in the case of a struct expansion. This wouldn't be + // necessary if each field that was a structure offered a REBSER + // already... !!! ?? !!! ... it will be necessary if the schemas + // are to uniquely carry an ffi_type freed when they are GC'd + // + REBSTU *sub_stu = Alloc_Singular_Array(); + SER(sub_stu)->link.schema = field; + REBVAL *single = SINK(ARR_HEAD(sub_stu)); + + // In this case the structure lives at an offset inside another. + // + // Note: The original code allowed this for STU_INACCESSIBLE(stu). + // + VAL_RESET_HEADER(single, REB_STRUCT); + MANAGE_ARRAY(sub_stu); + single->payload.structure.stu = sub_stu; + + // The parent data may be a singular array for a HANDLE! or a BINARY! + // series, depending on whether the data is owned by Rebol or not. + // That series pointer is being referenced again here. + // + single->payload.structure.data = + ARR_HEAD(stu)->payload.structure.data; + single->extra.struct_offset = offset; + + // With all fields initialized, assign canon value as result + // + Move_Value(out, single); + assert(VAL_STRUCT_SIZE(out) == FLD_WIDE(field)); + return; + } + + if (STU_INACCESSIBLE(stu)) { + // + // !!! This just gets void with no error...that seems like a bad idea, + // if the data is truly inaccessible. + // + Init_Void(out); + return; + } + + REBYTE *p = offset + STU_DATA_HEAD(stu); + + switch (FLD_TYPE_SYM(field)) { + case SYM_UINT8: + Init_Integer(out, *cast(u8*, p)); + break; + + case SYM_INT8: + Init_Integer(out, *cast(i8*, p)); + break; + + case SYM_UINT16: + Init_Integer(out, *cast(u16*, p)); + break; + + case SYM_INT16: + Init_Integer(out, *cast(i8*, p)); + break; + + case SYM_UINT32: + Init_Integer(out, *cast(u32*, p)); + break; + + case SYM_INT32: + Init_Integer(out, *cast(i32*, p)); + break; + + case SYM_UINT64: + Init_Integer(out, *cast(u64*, p)); + break; + + case SYM_INT64: + Init_Integer(out, *cast(i64*, p)); + break; + + case SYM_FLOAT: + Init_Decimal(out, *cast(float*, p)); + break; + + case SYM_DOUBLE: + Init_Decimal(out, *cast(double*, p)); + break; + + case SYM_POINTER: + Init_Integer(out, cast(REBUPT, *cast(void**, p))); + break; + + case SYM_REBVAL: + Move_Value(out, cast(const REBVAL*, p)); + break; + + default: + assert(FALSE); + fail ("Unknown FFI type indicator"); + } +} + + +// +// Get_Struct_Var: C +// +static REBOOL Get_Struct_Var(REBVAL *out, REBSTU *stu, const REBVAL *word) +{ + REBARR *fieldlist = STU_FIELDLIST(stu); + + RELVAL *item = ARR_HEAD(fieldlist); + for (; NOT_END(item); ++item) { + REBFLD *field = VAL_ARRAY(item); + if (STR_CANON(FLD_NAME(field)) != VAL_WORD_CANON(word)) + continue; + + if (FLD_IS_ARRAY(field)) { + // + // Structs contain packed data for the field type in an array. + // This data cannot expand or contract, and is not in a + // Rebol-compatible format. A Rebol Array is made by + // extracting the information. + // + // !!! Perhaps a fixed-size VECTOR! could have its data + // pointer into these arrays? + // + REBCNT dimension = FLD_DIMENSION(field); + REBARR *array = Make_Array(dimension); + REBCNT n; + for (n = 0; n < dimension; ++n) { + REBVAL *dest = SINK(ARR_AT(array, n)); + get_scalar(dest, stu, field, n); + } + TERM_ARRAY_LEN(array, dimension); + Init_Block(out, array); + } + else + get_scalar(out, stu, field, 0); + + return TRUE; + } + + return FALSE; // word not found in struct's field symbols +} + + +// +// Struct_To_Array: C +// +// Used by MOLD to create a block. +// +// Cannot fail(), because fail() could call MOLD on a struct!, which will end +// up infinitive recursive calls. +// +REBARR *Struct_To_Array(REBSTU *stu) +{ + REBARR *fieldlist = STU_FIELDLIST(stu); + RELVAL *item = ARR_HEAD(fieldlist); + + REBDSP dsp_orig = DSP; + + // fail_if_non_accessible(STU_TO_VAL(stu)); + + for(; NOT_END(item); ++item) { + REBFLD *field = VAL_ARRAY(item); + + DS_PUSH_TRASH; + Init_Set_Word(DS_TOP, FLD_NAME(field)); // required name + + REBARR *typespec = Make_Array(2); // required type + + if (FLD_IS_STRUCT(field)) { + Init_Word(Alloc_Tail_Array(typespec), Canon(SYM_STRUCT_X)); + + DECLARE_LOCAL (nested); + get_scalar(nested, stu, field, 0); + + PUSH_GUARD_VALUE(nested); // is this guard still necessary? + Init_Block( + Alloc_Tail_Array(typespec), + Struct_To_Array(VAL_STRUCT(nested)) + ); + DROP_GUARD_VALUE(nested); + } + else { + // Elemental type (from a fixed list of known C types) + // + Init_Word(Alloc_Tail_Array(typespec), Canon(FLD_TYPE_SYM(field))); + } + + // "optional dimension and initialization." + // + // !!! Comment said the initialization was optional, but it seems + // that the initialization always happens (?) + // + if (FLD_IS_ARRAY(field)) { + // + // Dimension becomes INTEGER! in a BLOCK! (to look like a C array) + // + REBCNT dimension = FLD_DIMENSION(field); + REBARR *one_int = Alloc_Singular_Array(); + Init_Integer(ARR_HEAD(one_int), dimension); + Init_Block(Alloc_Tail_Array(typespec), one_int); + + // Initialization seems to be just another block after that (?) + // + REBARR *init = Make_Array(dimension); + REBCNT n; + for (n = 0; n < dimension; n ++) { + REBVAL *dest = SINK(ARR_AT(init, n)); + get_scalar(dest, stu, field, n); + } + TERM_ARRAY_LEN(init, dimension); + Init_Block(Alloc_Tail_Array(typespec), init); + } + else { + REBVAL *dest = Alloc_Tail_Array(typespec); + get_scalar(dest, stu, field, 0); + } + + DS_PUSH_TRASH; + Init_Block(DS_TOP, typespec); // required type + } + + return Pop_Stack_Values(dsp_orig); +} + + +static REBOOL same_fields(REBARR *tgt_fieldlist, REBARR *src_fieldlist) +{ + if (ARR_LEN(tgt_fieldlist) != ARR_LEN(src_fieldlist)) + return FALSE; + + RELVAL *tgt_item = ARR_HEAD(tgt_fieldlist); + RELVAL *src_item = ARR_HEAD(src_fieldlist); + + for (; NOT_END(src_item); ++src_item, ++tgt_item) { + REBFLD *src_field = VAL_ARRAY(src_item); + REBFLD *tgt_field = VAL_ARRAY(tgt_item); + + if ( + FLD_IS_STRUCT(tgt_field) && + !same_fields(FLD_FIELDLIST(tgt_field), FLD_FIELDLIST(src_field)) + ){ + return FALSE; + } + + if (NOT( + SAME_SYM_NONZERO( + FLD_TYPE_SYM(tgt_field), FLD_TYPE_SYM(src_field) + ) + )){ + return FALSE; + } + + if (FLD_IS_ARRAY(tgt_field)) { + if (!FLD_IS_ARRAY(src_field)) + return FALSE; + + if (FLD_DIMENSION(tgt_field) != FLD_DIMENSION(src_field)) + return FALSE; + } + + if (FLD_OFFSET(tgt_field) != FLD_OFFSET(src_field)) + return FALSE; + + assert(FLD_WIDE(tgt_field) == FLD_WIDE(src_field)); + } + + assert(IS_END(tgt_item)); + + return TRUE; +} + + +static REBOOL assign_scalar_core( + REBYTE *data_head, + REBCNT offset, + REBFLD *field, + REBCNT n, + const REBVAL *val +){ + assert(n == 0 || FLD_IS_ARRAY(field)); + + void *data = data_head + + offset + FLD_OFFSET(field) + (n * FLD_WIDE(field)); + + if (FLD_IS_STRUCT(field)) { + if (!IS_STRUCT(val)) + fail (Error_Invalid_Type(VAL_TYPE(val))); + + if (FLD_WIDE(field) != VAL_STRUCT_SIZE(val)) + fail (val); + + if (!same_fields(FLD_FIELDLIST(field), VAL_STRUCT_FIELDLIST(val))) + fail (val); + + memcpy(data, VAL_STRUCT_DATA_AT(val), FLD_WIDE(field)); + + return TRUE; + } + + // All other types take numbers + + i64 i; + double d; + + switch (VAL_TYPE(val)) { + case REB_DECIMAL: + d = VAL_DECIMAL(val); + i = cast(i64, d); + break; + + case REB_INTEGER: + i = VAL_INT64(val); + d = cast(double, i); + break; + + default: + fail (Error_Invalid_Type(VAL_TYPE(val))); + } + + switch (FLD_TYPE_SYM(field)) { + case SYM_INT8: + if (i > 0x7f || i < -128) + fail (Error_Overflow_Raw()); + *cast(i8*, data) = cast(i8, i); + break; + + case SYM_UINT8: + if (i > 0xff || i < 0) + fail (Error_Overflow_Raw()); + *cast(u8*, data) = cast(u8, i); + break; + + case SYM_INT16: + if (i > 0x7fff || i < -0x8000) + fail (Error_Overflow_Raw()); + *cast(i16*, data) = cast(i16, i); + break; + + case SYM_UINT16: + if (i > 0xffff || i < 0) + fail (Error_Overflow_Raw()); + *cast(u16*, data) = cast(u16, i); + break; + + case SYM_INT32: + if (i > MAX_I32 || i < MIN_I32) + fail (Error_Overflow_Raw()); + *cast(i32*, data) = cast(i32, i); + break; + + case SYM_UINT32: + if (i > MAX_U32 || i < 0) + fail (Error_Overflow_Raw()); + *cast(u32*, data) = cast(u32, i); + break; + + case SYM_INT64: + *cast(i64*, data) = i; + break; + + case SYM_UINT64: + if (i < 0) + fail (Error_Overflow_Raw()); + *cast(u64*, data) = cast(u64, i); + break; + + case SYM_FLOAT: + *cast(float*, data) = cast(float, d); + break; + + case SYM_DOUBLE: + *cast(double*, data) = d; + break; + + case SYM_POINTER: + if (sizeof(void*) == 4 && i > MAX_U32) + fail (Error_Overflow_Raw()); + *cast(void**, data) = cast(void*, cast(REBUPT, i)); + break; + + case SYM_REBVAL: + // + // !!! This is a dangerous thing to be doing in generic structs, but + // for the main purpose of REBVAL (tunneling) it should be okay so + // long as the REBVAL* that is passed in is actually a pointer into + // a frame's args. + // + *cast(const REBVAL**, data) = val; + break; + + default: + assert(FALSE); + return FALSE; + } + + return TRUE; +} + + +inline static REBOOL assign_scalar( + REBSTU *stu, + REBFLD *field, + REBCNT n, + const REBVAL *val +) { + return assign_scalar_core( + STU_DATA_HEAD(stu), STU_OFFSET(stu), field, n, val + ); +} + + +// +// Set_Struct_Var: C +// +static REBOOL Set_Struct_Var( + REBSTU *stu, + const REBVAL *word, + const REBVAL *elem, + const REBVAL *val +) { + REBARR *fieldlist = STU_FIELDLIST(stu); + RELVAL *item = ARR_HEAD(fieldlist); + + for (; NOT_END(item); ++item) { + REBFLD *field = VAL_ARRAY(item); + + if (VAL_WORD_CANON(word) != STR_CANON(FLD_NAME(field))) + continue; + + if (FLD_IS_ARRAY(field)) { + if (elem == NULL) { // set the whole array + if (!IS_BLOCK(val)) + return FALSE; + + REBCNT dimension = FLD_DIMENSION(field); + if (dimension != VAL_LEN_AT(val)) + return FALSE; + + REBCNT n = 0; + for(n = 0; n < dimension; ++n) { + if (!assign_scalar( + stu, field, n, KNOWN(VAL_ARRAY_AT_HEAD(val, n)) + )) { + return FALSE; + } + } + } + else { // set only one element + if (!IS_INTEGER(elem) || VAL_INT32(elem) != 1) + return FALSE; + + return assign_scalar(stu, field, 0, val); + } + return TRUE; + } + + return assign_scalar(stu, field, 0, val); + } + + return FALSE; +} + + +/* parse struct attribute */ +static void parse_attr (REBVAL *blk, REBINT *raw_size, REBUPT *raw_addr) +{ + REBVAL *attr = KNOWN(VAL_ARRAY_AT(blk)); + + *raw_size = -1; + *raw_addr = 0; + + while (NOT_END(attr)) { + if (NOT(IS_SET_WORD(attr))) + fail (attr); + + switch (VAL_WORD_SYM(attr)) { + case SYM_RAW_SIZE: + ++ attr; + if (NOT_END(attr) && IS_INTEGER(attr)) { + if (*raw_size > 0) + fail ("FFI: duplicate raw size"); + + *raw_size = VAL_INT64(attr); + if (*raw_size <= 0) + fail ("FFI: raw size cannot be zero"); + } + else + fail (attr); + break; + + case SYM_RAW_MEMORY: + ++ attr; + if (NOT_END(attr) && IS_INTEGER(attr)) { + if (*raw_addr != 0) + fail ("FFI: duplicate raw memory"); + + *raw_addr = cast(REBU64, VAL_INT64(attr)); + if (*raw_addr == 0) + fail ("FFI: void pointer illegal for raw memory"); + } + else + fail (attr); + break; + + case SYM_EXTERN: { + ++ attr; + + if (*raw_addr != 0) + fail ("FFI: raw memory is exclusive with extern"); + + if (IS_END(attr) || NOT(IS_BLOCK(attr)) || VAL_LEN_AT(attr) != 2) + fail (attr); + + REBVAL *lib = KNOWN(VAL_ARRAY_AT_HEAD(attr, 0)); + if (NOT(IS_LIBRARY(lib))) + fail (attr); + if (IS_LIB_CLOSED(VAL_LIBRARY(lib))) + fail (Error_Bad_Library_Raw()); + + REBVAL *sym = KNOWN(VAL_ARRAY_AT_HEAD(attr, 1)); + if (NOT(ANY_BINSTR(sym))) + fail (sym); + + CFUNC *addr = OS_FIND_FUNCTION( + VAL_LIBRARY_FD(lib), + s_cast(VAL_RAW_DATA_AT(sym)) + ); + if (addr == NULL) + fail (Error_Symbol_Not_Found_Raw(sym)); + + *raw_addr = cast(REBUPT, addr); + break; } + + // !!! This alignment code was commented out for some reason. + /* + case SYM_ALIGNMENT: + ++ attr; + if (!IS_INTEGER(attr)) + fail (attr); + + alignment = VAL_INT64(attr); + break; + */ + + default: + fail (attr); + } + + ++ attr; + } +} + + +// The managed handle logic always assumes a cleanup function, so it doesn't +// have to test for NULL. +// +static void cleanup_noop(const REBVAL *v) { +#ifdef NDEBUG + UNUSED(v); +#else + assert(IS_HANDLE(v)); +#endif +} + + +// +// set storage memory to external addr: raw_addr +// +// "External Storage" is the idea that a STRUCT! which is modeling a C +// struct doesn't use a BINARY! series as the backing store, rather a pointer +// that is external to the system. When Atronix added the FFI initially, +// this was done by creating a separate type of REBSER that could use an +// external pointer. This uses a managed HANDLE! for the same purpose, as +// a less invasive way of doing the same thing. +// +static REBSER *make_ext_storage( + REBCNT len, + REBINT raw_size, + REBUPT raw_addr +) { + if (raw_size >= 0 && raw_size != cast(REBINT, len)) { + DECLARE_LOCAL (i); + Init_Integer(i, raw_size); + fail (Error_Invalid_Data_Raw(i)); + } + + DECLARE_LOCAL (handle); + Init_Handle_Managed(handle, cast(REBYTE*, raw_addr), len, &cleanup_noop); + + return SER(handle->extra.singular); +} + + +// +// Total_Struct_Dimensionality: C +// +// This recursively counts the total number of data elements inside of a +// struct. This includes for instance every array element inside a +// nested struct's field, along with its fields. +// +// !!! Is this really how char[1000] would be handled in the FFI? By +// creating 1000 ffi_types? :-/ +// +static REBCNT Total_Struct_Dimensionality(REBARR *fields) +{ + REBCNT n_fields = 0; + + RELVAL *item = ARR_HEAD(fields); + for (; NOT_END(item); ++item) { + REBFLD *field = VAL_ARRAY(item); + + if (FLD_IS_STRUCT(field)) + n_fields += Total_Struct_Dimensionality(FLD_FIELDLIST(field)); + else + n_fields += FLD_IS_ARRAY(field) ? FLD_DIMENSION(field) : 1; + } + return n_fields; +} + + +// +// Prepare_Field_For_FFI: C +// +// The main reason structs exist is so that they can be used with the FFI, +// and the FFI requires you to set up a "ffi_type" C struct describing +// each datatype. This is a helper function that sets up proper ffi_type. +// There are stock types for the primitives, but each structure needs its +// own. +// +static void Prepare_Field_For_FFI(REBFLD *schema) +{ + assert(IS_UNREADABLE_IF_DEBUG(FLD_AT(schema, IDX_FIELD_FFTYPE))); + + ffi_type *fftype; + + if (!FLD_IS_STRUCT(schema)) { + fftype = Get_FFType_For_Sym(FLD_TYPE_SYM(schema)); + assert(fftype != NULL); + + // The FFType pointers returned by Get_FFType_For_Sym should not be + // freed, so a "simple" handle is used that just holds the pointer. + // + Init_Handle_Simple(FLD_AT(schema, IDX_FIELD_FFTYPE), fftype, 0); + return; + } + + // For struct fields--on the other hand--it's necessary to do a custom + // allocation for a new type registered with the FFI. + // + fftype = OS_ALLOC(ffi_type); + fftype->type = FFI_TYPE_STRUCT; + + // "This is set by libffi; you should initialize it to zero." + // http://www.atmark-techno.com/~yashi/libffi.html#Structures + // + fftype->size = 0; + fftype->alignment = 0; + + REBARR *fieldlist = FLD_FIELDLIST(schema); + + REBCNT dimensionality = Total_Struct_Dimensionality(fieldlist); + fftype->elements = OS_ALLOC_N(ffi_type*, dimensionality + 1); // NULL term + + RELVAL *item = ARR_HEAD(fieldlist); + + REBCNT j = 0; + for (; NOT_END(item); ++item) { + REBFLD *field = VAL_ARRAY(item); + REBCNT dimension = FLD_IS_ARRAY(field) ? FLD_DIMENSION(field) : 1; + + REBCNT n = 0; + for (n = 0; n < dimension; ++n) + fftype->elements[j++] = FLD_FFTYPE(field); + } + + fftype->elements[j] = NULL; + + Init_Handle_Managed( + FLD_AT(schema, IDX_FIELD_FFTYPE), + fftype, + dimensionality + 1, + &cleanup_ffi_type + ); +} + + +// +// This takes a spec like `[int32 [2]]` and sets the output field's properties +// by recognizing a finite set of FFI type keywords defined in %words.r. +// +// This also allows for embedded structure types. If the type is not being +// included by reference, but rather with a sub-definition inline, then it +// will actually be creating a new `inner` STRUCT! value. Since this value +// is managed and not referred to elsewhere, there can't be evaluations. +// +static void Parse_Field_Type_May_Fail( + REBFLD *field, + REBVAL *spec, + REBVAL *inner // will be set only if STRUCT! +){ + TRASH_CELL_IF_DEBUG(inner); + + RELVAL *val = VAL_ARRAY_AT(spec); + + if (IS_END(val)) + fail ("Empty field type in FFI"); + + if (IS_WORD(val)) { + REBSYM sym = VAL_WORD_SYM(val); + + // Initialize the type symbol with the unbound word by default (will + // be overwritten in the struct cases). + // + Init_Word(FLD_AT(field, IDX_FIELD_TYPE), Canon(sym)); + + switch (sym) { + case SYM_UINT8: + Init_Integer(FLD_AT(field, IDX_FIELD_WIDE), 1); + Prepare_Field_For_FFI(field); + break; + + case SYM_INT8: + Init_Integer(FLD_AT(field, IDX_FIELD_WIDE), 1); + Prepare_Field_For_FFI(field); + break; + + case SYM_UINT16: + Init_Integer(FLD_AT(field, IDX_FIELD_WIDE), 2); + Prepare_Field_For_FFI(field); + break; + + case SYM_INT16: + Init_Integer(FLD_AT(field, IDX_FIELD_WIDE), 2); + Prepare_Field_For_FFI(field); + break; + + case SYM_UINT32: + Init_Integer(FLD_AT(field, IDX_FIELD_WIDE), 4); + Prepare_Field_For_FFI(field); + break; + + case SYM_INT32: + Init_Integer(FLD_AT(field, IDX_FIELD_WIDE), 4); + Prepare_Field_For_FFI(field); + break; + + case SYM_UINT64: + Init_Integer(FLD_AT(field, IDX_FIELD_WIDE), 8); + Prepare_Field_For_FFI(field); + break; + + case SYM_INT64: + Init_Integer(FLD_AT(field, IDX_FIELD_WIDE), 8); + Prepare_Field_For_FFI(field); + break; + + case SYM_FLOAT: + Init_Integer(FLD_AT(field, IDX_FIELD_WIDE), 4); + Prepare_Field_For_FFI(field); + break; + + case SYM_DOUBLE: + Init_Integer(FLD_AT(field, IDX_FIELD_WIDE), 8); + Prepare_Field_For_FFI(field); + break; + + case SYM_POINTER: + Init_Integer(FLD_AT(field, IDX_FIELD_WIDE), sizeof(void*)); + Prepare_Field_For_FFI(field); + break; + + case SYM_STRUCT_X: { + ++ val; + if (!IS_BLOCK(val)) + fail (Error_Unexpected_Type(REB_BLOCK, VAL_TYPE(val))); + + DECLARE_LOCAL (specified); + Derelativize(specified, val, VAL_SPECIFIER(spec)); + MAKE_Struct(inner, REB_STRUCT, specified); // may fail() + + Init_Integer( + FLD_AT(field, IDX_FIELD_WIDE), + VAL_STRUCT_DATA_LEN(inner) + ); + Init_Block( + FLD_AT(field, IDX_FIELD_TYPE), + VAL_STRUCT_FIELDLIST(inner) + ); + + // Borrow the same ffi_type* that was built for the inner struct + // (What about just storing the STRUCT! value itself in the type + // field, instead of the array of fields?) + // + Move_Value( + FLD_AT(field, IDX_FIELD_FFTYPE), + FLD_AT(VAL_STRUCT_SCHEMA(inner), IDX_FIELD_FFTYPE) + ); + break; } + + case SYM_REBVAL: { + // + // While most data types have some kind of proxying of when you + // pass a Rebol value in (such as turning an INTEGER! into bits + // for a C `int`) if the argument is marked as being a REBVAL + // then the VAL_TYPE is ignored, and it acts like a pointer to + // the actual argument in the frame...whatever that may be. + // + // !!! The initial FFI implementation from Atronix would actually + // store sizeof(REBVAL) in the struct, not sizeof(REBVAL*). The + // struct's binary data was then hooked into the garbage collector + // to make sure that cell was marked. Because the intended use + // of the feature is "tunneling" a value from a routine's frame + // to a callback's frame, the lifetime of the REBVAL* should last + // for the entirety of the routine it was passed to. + // + Init_Integer(FLD_AT(field, IDX_FIELD_WIDE), sizeof(REBVAL*)); + Prepare_Field_For_FFI(field); + break; } + + default: + fail (Error_Invalid_Type(VAL_TYPE(val))); + } + } + else if (IS_STRUCT(val)) { + // + // [b: [struct-a] val-a] + // + Init_Integer( + FLD_AT(field, IDX_FIELD_WIDE), + VAL_STRUCT_DATA_LEN(val) + ); + Init_Block( + FLD_AT(field, IDX_FIELD_TYPE), + VAL_STRUCT_FIELDLIST(val) + ); + + // Borrow the same ffi_type* that the struct uses, see above note + // regarding alternative ideas. + // + Move_Value( + FLD_AT(field, IDX_FIELD_FFTYPE), + FLD_AT(VAL_STRUCT_SCHEMA(val), IDX_FIELD_FFTYPE) + ); + Derelativize(inner, val, VAL_SPECIFIER(spec)); + } + else + fail (Error_Invalid_Type(VAL_TYPE(val))); + + ++ val; + + // Find out the array dimension (if there is one) + // + if (IS_END(val)) { + Init_Blank(FLD_AT(field, IDX_FIELD_DIMENSION)); // scalar + } + else if (IS_BLOCK(val)) { + // + // make struct! [a: [int32 [2]] [0 0]] + // + DECLARE_LOCAL (ret); + if (Do_At_Throws( + ret, + VAL_ARRAY(val), + VAL_INDEX(val), + VAL_SPECIFIER(spec) + )) { + // !!! Does not check for thrown cases...what should this + // do in case of THROW, BREAK, QUIT? + fail (Error_No_Catch_For_Throw(ret)); + } + + if (!IS_INTEGER(ret)) + fail (Error_Unexpected_Type(REB_INTEGER, VAL_TYPE(val))); + + Init_Integer(FLD_AT(field, IDX_FIELD_DIMENSION), VAL_INT64(ret)); + ++ val; + } + else + fail (Error_Invalid_Type(VAL_TYPE(val))); +} + + +// +// Init_Struct_Fields: C +// +// a: make struct! [uint 8 i: 1] +// b: make a [i: 10] +// +void Init_Struct_Fields(REBVAL *ret, REBVAL *spec) +{ + REBVAL *blk = NULL; + + for (blk = KNOWN(VAL_ARRAY_AT(spec)); NOT_END(blk); blk += 2) { + REBVAL *word = blk; + REBVAL *fld_val = blk + 1; + + if (IS_BLOCK(word)) { // options: raw-memory, etc + REBINT raw_size = -1; + REBUPT raw_addr = 0; + + // make sure no other field initialization + if (VAL_LEN_HEAD(spec) != 1) + fail (spec); + + parse_attr(word, &raw_size, &raw_addr); + ret->payload.structure.data + = make_ext_storage(VAL_STRUCT_SIZE(ret), raw_size, raw_addr); + + break; + } + else if (NOT(IS_SET_WORD(word))) + fail (word); + + if (IS_END(fld_val)) + fail (Error_Need_Value_Raw(fld_val)); + + REBARR *fieldlist = VAL_STRUCT_FIELDLIST(ret); + RELVAL *item = ARR_HEAD(fieldlist); + + for (; NOT_END(item); ++item) { + REBFLD *field = VAL_ARRAY(item); + + if (STR_CANON(FLD_NAME(field)) != VAL_WORD_CANON(word)) + continue; + + if (FLD_IS_ARRAY(field)) { + if (IS_BLOCK(fld_val)) { + REBCNT dimension = FLD_DIMENSION(field); + + if (VAL_LEN_AT(fld_val) != dimension) + fail (fld_val); + + REBCNT n = 0; + for (n = 0; n < dimension; ++n) { + if (NOT(assign_scalar( + VAL_STRUCT(ret), + field, + n, + KNOWN(VAL_ARRAY_AT_HEAD(fld_val, n)) + ))) { + fail (fld_val); + } + } + } + else if (IS_INTEGER(fld_val)) { // interpret as a data pointer + void *ptr = cast(void *, + cast(REBUPT, VAL_INT64(fld_val)) + ); + + // assuming valid pointer to enough space + memcpy( + VAL_STRUCT_DATA_HEAD(ret) + FLD_OFFSET(field), + ptr, + FLD_LEN_BYTES_TOTAL(field) + ); + } + else + fail (fld_val); + } + else { + if (NOT(assign_scalar(VAL_STRUCT(ret), field, 0, fld_val))) + fail (fld_val); + } + return; + } + + fail ("FFI: field not in the parent struct"); + } +} + + +// +// MAKE_Struct: C +// +// Format: +// make struct! [ +// field1 [type1] +// field2: [type2] field2-init-value +// field3: [struct [field1 [type1]]] +// field4: [type1[3]] +// ... +// ] +// +void MAKE_Struct(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { + assert(kind == REB_STRUCT); + UNUSED(kind); + + if (NOT(IS_BLOCK(arg))) + fail (arg); + + REBINT max_fields = 16; + +// +// SET UP SCHEMA +// + // Every struct has a "schema"--this is a description (potentially + // hierarchical) of its fields, including any nested structs. The + // schema should be shared between common instances of the same struct. + // + REBFLD *schema = Make_Array(IDX_FIELD_MAX); + Init_Blank(FLD_AT(schema, IDX_FIELD_NAME)); // no symbol for struct itself + // we'll be filling in the IDX_FIELD_TYPE slot with an array of fields + Init_Blank(FLD_AT(schema, IDX_FIELD_DIMENSION)); // not an array + + SET_UNREADABLE_BLANK(FLD_AT(schema, IDX_FIELD_FFTYPE)); + + Init_Blank(FLD_AT(schema, IDX_FIELD_OFFSET)); // the offset is not used + // we'll be filling in the IDX_FIELD_WIDE at the end. + + +// +// PROCESS FIELDS +// + + u64 offset = 0; // offset in data + + REBINT raw_size = -1; + REBUPT raw_addr = 0; + + DECLARE_LOCAL (specified); + + RELVAL *item = VAL_ARRAY_AT(arg); + if (NOT_END(item) && IS_BLOCK(item)) { + // + // !!! This would suggest raw-size, raw-addr, or extern can be leading + // in the struct definition, perhaps as: + // + // make struct! [[raw-size] ...] + // + Derelativize(specified, item, VAL_SPECIFIER(arg)); + parse_attr(specified, &raw_size, &raw_addr); + ++item; + } + + // !!! This makes binary data for each struct level? ??? + // + REBSER *data_bin; + if (raw_addr == 0) + data_bin = Make_Binary(max_fields << 2); + else + data_bin = NULL; // not used, but avoid maybe uninitialized warning + + REBINT field_idx = 0; // for field index + REBIXO eval_idx = 0; // for spec block evaluation + + REBDSP dsp_orig = DSP; // use data stack to accumulate fields (BLOCK!s) + + DECLARE_LOCAL (spec); + DECLARE_LOCAL (init); // for result to save in data + + while (NOT_END(item)) { + + // Add another field... + + REBFLD *field = Make_Array(IDX_FIELD_MAX); + + SET_UNREADABLE_BLANK(FLD_AT(field, IDX_FIELD_FFTYPE)); + Init_Integer(FLD_AT(field, IDX_FIELD_OFFSET), offset); + + // Must be a word or a set-word, with set-words initializing + + REBOOL expect_init; + if (IS_SET_WORD(item)) { + expect_init = TRUE; + if (raw_addr) { + // initialization is not allowed for raw memory struct + fail (Error_Invalid_Arg_Core(item, VAL_SPECIFIER(arg))); + } + } + else if (IS_WORD(item)) + expect_init = FALSE; + else + fail (Error_Invalid_Type(VAL_TYPE(item))); + + Init_Word(FLD_AT(field, IDX_FIELD_NAME), VAL_WORD_SPELLING(item)); + + ++item; + if (IS_END(item) || !IS_BLOCK(item)) + fail (Error_Invalid_Arg_Core(item, VAL_SPECIFIER(arg))); + + Derelativize(spec, item, VAL_SPECIFIER(arg)); + + // Fills in the width, dimension, type, and ffi_type (if needed) + // + Parse_Field_Type_May_Fail(field, spec, init); + + REBCNT dimension = FLD_IS_ARRAY(field) ? FLD_DIMENSION(field) : 1; + ++item; + + // !!! Why does the fail take out as an argument? (Copied from below) + + if (FLD_WIDE(field) > MAX_U32) + fail (Error_Size_Limit_Raw(out)); + if (dimension > MAX_U32) + fail (Error_Size_Limit_Raw(out)); + + u64 step = cast(u64, FLD_WIDE(field)) * cast(u64, dimension); + + if (step > VAL_STRUCT_LIMIT) + fail (Error_Size_Limit_Raw(out)); + + if (raw_addr == 0) + EXPAND_SERIES_TAIL(data_bin, step); + + if (expect_init) { + if (IS_END(item)) + fail (arg); + + if (IS_BLOCK(item)) { + Derelativize(specified, item, VAL_SPECIFIER(arg)); + + if (Reduce_Any_Array_Throws( + init, specified, REDUCE_FLAG_DROP_BARS + )){ + fail (Error_No_Catch_For_Throw(init)); + } + + ++item; + } + else { + eval_idx = DO_NEXT_MAY_THROW( + init, + VAL_ARRAY(arg), + item - VAL_ARRAY_AT(arg), + VAL_SPECIFIER(arg) + ); + if (eval_idx == THROWN_FLAG) + fail (Error_No_Catch_For_Throw(init)); + + if (eval_idx == END_FLAG) + item = VAL_ARRAY_TAIL(arg); + else + item = VAL_ARRAY_AT_HEAD(item, cast(REBCNT, eval_idx)); + } + + if (FLD_IS_ARRAY(field)) { + if (IS_INTEGER(init)) { // interpreted as a C pointer + void *ptr = cast(void *, cast(REBUPT, VAL_INT64(init))); + + // assume valid pointer to enough space + memcpy( + SER_AT(REBYTE, data_bin, cast(REBCNT, offset)), + ptr, + FLD_LEN_BYTES_TOTAL(field) + ); + } + else if (IS_BLOCK(init)) { + REBCNT n = 0; + + if (VAL_LEN_AT(init) != FLD_DIMENSION(field)) + fail (init); + + // assign + for (n = 0; n < FLD_DIMENSION(field); n ++) { + if (!assign_scalar_core( + BIN_HEAD(data_bin), + offset, + field, + n, + KNOWN(VAL_ARRAY_AT_HEAD(init, n)) + )) { + fail ("FFI: Failed to assign element value"); + } + } + } + else + fail (Error_Unexpected_Type(REB_BLOCK, VAL_TYPE(item))); + } + else { + // scalar + if (!assign_scalar_core( + BIN_HEAD(data_bin), offset, field, 0, init + )) { + fail ("FFI: Failed to assign scalar value"); + } + } + } + else if (raw_addr == 0) { + if (FLD_IS_STRUCT(field)) { + REBCNT n = 0; + for (n = 0; n < FLD_DIMENSION(field); n ++) { + memcpy( + SER_AT( + REBYTE, + data_bin, + cast(REBCNT, offset) + (n * FLD_WIDE(field)) + ), + VAL_STRUCT_DATA_HEAD(init), + FLD_WIDE(field) + ); + } + } + else { + memset( + SER_AT(REBYTE, data_bin, cast(REBCNT, offset)), + 0, + FLD_LEN_BYTES_TOTAL(field) + ); + } + } + + offset += step; + + //if (alignment != 0) { + // offset = ((offset + alignment - 1) / alignment) * alignment; + + if (offset > VAL_STRUCT_LIMIT) + fail (Error_Size_Limit_Raw(out)); + + ++ field_idx; + + TERM_ARRAY_LEN(field, 6); + ASSERT_ARRAY(field); + + DS_PUSH_TRASH; + Init_Block(DS_TOP, field); // really should be an OBJECT! + } + + REBARR *fieldlist = Pop_Stack_Values(dsp_orig); + ASSERT_ARRAY(fieldlist); + + Init_Block(FLD_AT(schema, IDX_FIELD_TYPE), fieldlist); + Prepare_Field_For_FFI(schema); + + Init_Integer(FLD_AT(schema, IDX_FIELD_WIDE), offset); // total size known + + TERM_ARRAY_LEN(schema, IDX_FIELD_MAX); + ASSERT_ARRAY(schema); + +// +// FINALIZE VALUE +// + + REBSTU *stu = Alloc_Singular_Array(); + + // Set it to blank so the Kill_Series can be called upon in case of error + // thrown before it is fully constructed. + // + Init_Blank(ARR_HEAD(stu)); + + MANAGE_ARRAY(schema); + SER(stu)->link.schema = schema; + + VAL_RESET_HEADER(out, REB_STRUCT); + out->payload.structure.stu = stu; + if (raw_addr) { + out->payload.structure.data + = make_ext_storage( + FLD_LEN_BYTES_TOTAL(schema), raw_size, raw_addr + ); + } + else { + MANAGE_SERIES(data_bin); + out->payload.structure.data = data_bin; + } + out->extra.struct_offset = 0; + + *ARR_HEAD(stu) = *out; + MANAGE_ARRAY(stu); +} + + +// +// TO_Struct: C +// +void TO_Struct(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) +{ + MAKE_Struct(out, kind, arg); +} + + +// +// PD_Struct: C +// +REBINT PD_Struct(REBPVS *pvs) +{ + REBSTU *stu = VAL_STRUCT(pvs->value); + if (!IS_WORD(pvs->picker)) + fail (Error_Bad_Path_Select(pvs)); + + fail_if_non_accessible(KNOWN(pvs->value)); + + if (!pvs->opt_setval || NOT_END(pvs->item + 1)) { + if (NOT(Get_Struct_Var(pvs->store, stu, pvs->picker))) + fail (Error_Bad_Path_Select(pvs)); + + // !!! Comment here said "Setting element to an array in the struct" + // and gave the example `struct/field/1: 0`. What is thus happening + // here is that the ordinary SET-PATH! dispatch which goes one step + // at a time can't work to update something whose storage is not + // a REBVAL*. So (struct/field) produces a temporary BLOCK! out of + // the C array data, and if the set just sets an element in that + // block then it will be forgotten and have no effect. + // + // So the workaround is to bypass ordinary dispatch and call it to + // look ahead manually by one step. Whatever change is made to + // the block is then turned around and re-set in the underlying + // memory that produced it. + // + // A better general mechanism for this kind of problem is needed, + // although it only affects "extension types" which use natively + // packed structures to store their state instead of REBVAL. (See + // a similar technique used by PD_Gob) + // + if ( + pvs->opt_setval + && IS_BLOCK(pvs->store) + && IS_END(pvs->item + 2) + ) { + // !!! This is dodgy; it has to copy (as picker is a pointer to + // a memory cell it may not own), has to guard (as the next path + // evaluation may not protect the result...) + // + DECLARE_LOCAL (sel_orig); + Move_Value(sel_orig, pvs->picker); + PUSH_GUARD_VALUE(sel_orig); + + pvs->value = pvs->store; + pvs->value_specifier = SPECIFIED; + + if (Next_Path_Throws(pvs)) { // updates pvs->store, pvs->picker + DROP_GUARD_VALUE(sel_orig); + fail (Error_No_Catch_For_Throw(pvs->store)); // !!! Review + } + + DECLARE_LOCAL (specific); + Derelativize(specific, pvs->value, pvs->value_specifier); + + if (!Set_Struct_Var(stu, sel_orig, pvs->picker, specific)) + fail (Error_Bad_Path_Set(pvs)); + + DROP_GUARD_VALUE(sel_orig); + + return PE_OK; + } + + return PE_USE_STORE; + } + else { + // setting (because opt_setval is non-NULL, and at end of path) + + if (!Set_Struct_Var(stu, pvs->picker, NULL, pvs->opt_setval)) + fail (Error_Bad_Path_Set(pvs)); + + return PE_OK; + } + + fail (Error_Bad_Path_Select(pvs)); +} + + +// +// Cmp_Struct: C +// +REBINT Cmp_Struct(const RELVAL *s, const RELVAL *t) +{ + REBINT n = VAL_STRUCT_FIELDLIST(s) - VAL_STRUCT_FIELDLIST(t); + fail_if_non_accessible(const_KNOWN(s)); + fail_if_non_accessible(const_KNOWN(t)); + if (n != 0) { + return n; + } + n = VAL_STRUCT(s) - VAL_STRUCT(t); + return n; +} + + +// +// CT_Struct: C +// +REBINT CT_Struct(const RELVAL *a, const RELVAL *b, REBINT mode) +{ + switch (mode) { + case 1: // strict equality + return 0 == Cmp_Struct(a, b); + + case 0: // coerced equality + if (Cmp_Struct(a, b) == 0) + return 1; + + return ( + IS_STRUCT(a) && IS_STRUCT(b) + && same_fields(VAL_STRUCT_FIELDLIST(a), VAL_STRUCT_FIELDLIST(b)) + && VAL_STRUCT_SIZE(a) == VAL_STRUCT_SIZE(b) + && !memcmp( + VAL_STRUCT_DATA_HEAD(a), + VAL_STRUCT_DATA_HEAD(b), + VAL_STRUCT_SIZE(a) + ) + ); + + default: + return -1; + } + return -1; +} + + +// +// Copy_Struct_Managed: C +// +REBSTU *Copy_Struct_Managed(REBSTU *src) +{ + fail_if_non_accessible(STU_VALUE(src)); + + assert(ARR_LEN(src) == 1); + assert(IS_STRUCT(ARR_AT(src, 0))); + + // This doesn't copy the data out of the array, or the schema...just the + // value. In fact, the schema is in the misc field and has to just be + // linked manually. + // + REBSTU *copy = Copy_Array_Shallow(src, SPECIFIED); + SER(copy)->link.schema = SER(src)->link.schema; + + // Update the binary data with a copy of its sequence. + // + // !!! Note that this leaves the offset intact, and will wind up making a + // copy as big as struct the instance is embedded into if nonzero offset. + + REBSER *bin_copy = Make_Binary(STU_DATA_LEN(src)); + memcpy(BIN_HEAD(bin_copy), STU_DATA_HEAD(src), STU_DATA_LEN(src)); + TERM_BIN_LEN(bin_copy, STU_DATA_LEN(src)); + STU_VALUE(copy)->payload.structure.data = bin_copy; + assert(STU_DATA_HEAD(copy) == BIN_HEAD(bin_copy)); + + MANAGE_SERIES(bin_copy); + MANAGE_ARRAY(copy); + return copy; +} + + +// +// REBTYPE: C +// +REBTYPE(Struct) +{ + REBVAL *val; + REBVAL *arg; + + val = D_ARG(1); + + Init_Void(D_OUT); + // unary actions + switch(action) { + + case SYM_CHANGE: { + arg = D_ARG(2); + if (!IS_BINARY(arg)) + fail (Error_Unexpected_Type(REB_BINARY, VAL_TYPE(arg))); + + if (VAL_LEN_AT(arg) != VAL_STRUCT_DATA_LEN(val)) + fail (arg); + + memcpy( + VAL_STRUCT_DATA_HEAD(val), + BIN_HEAD(VAL_SERIES(arg)), + VAL_STRUCT_DATA_LEN(val) + ); + Move_Value(D_OUT, val); + break; } + + case SYM_REFLECT: { + arg = D_ARG(2); + switch (VAL_WORD_SYM(arg)) { + case SYM_VALUES: { + fail_if_non_accessible(val); + REBSER *bin = Make_Binary(VAL_STRUCT_SIZE(val)); + memcpy( + BIN_HEAD(bin), + VAL_STRUCT_DATA_AT(val), + VAL_STRUCT_SIZE(val) + ); + TERM_BIN_LEN(bin, VAL_STRUCT_SIZE(val)); + Init_Binary(D_OUT, bin); + break; } + + case SYM_SPEC: + Init_Block(D_OUT, Struct_To_Array(VAL_STRUCT(val))); + break; + + case SYM_ADDR: + Init_Integer(D_OUT, cast(REBUPT, VAL_STRUCT_DATA_AT(val))); + break; + + default: + fail (Error_Cannot_Reflect(REB_STRUCT, arg)); + } + break; } + + case SYM_LENGTH_OF: + Init_Integer(D_OUT, VAL_STRUCT_DATA_LEN(val)); + break; + + default: + fail (Error_Illegal_Action(REB_STRUCT, action)); + } + return R_OUT; +} + + +// +// destroy-struct-storage: native [ +// +// {Destroy the external memory associated the struct} +// +// struct [struct!] +// /free +// {Specify the function to free the memory} +// free-func [function!] +// ] +// +REBNATIVE(destroy_struct_storage) +{ + INCLUDE_PARAMS_OF_DESTROY_STRUCT_STORAGE; + + REBSER *data = ARG(struct)->payload.structure.data; + if (NOT_SER_FLAG(data, SERIES_FLAG_ARRAY)) + fail (Error_No_External_Storage_Raw()); + + RELVAL *handle = ARR_HEAD(ARR(data)); + + DECLARE_LOCAL (pointer); + Init_Integer(pointer, cast(REBUPT, VAL_HANDLE_POINTER(void, handle))); + + if (VAL_HANDLE_LEN(handle) == 0) + fail (Error_Already_Destroyed_Raw(pointer)); + + // TBD: assert handle length was correct for memory block size + + SET_HANDLE_LEN(handle, 0); + + if (REF(free)) { + if (NOT(IS_FUNCTION_RIN(ARG(free_func)))) + fail (Error_Free_Needs_Routine_Raw()); + + if (Do_Va_Throws(D_OUT, ARG(free_func), pointer, END)) + return R_OUT_IS_THROWN; + } + + return R_VOID; +} diff --git a/src/core/t-time.c b/src/core/t-time.c index 57539f9e05..dfed2ead44 100644 --- a/src/core/t-time.c +++ b/src/core/t-time.c @@ -1,551 +1,705 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-time.c -** Summary: time datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-time.c +// Summary: "time datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/*********************************************************************** -** -*/ void Split_Time(REBI64 t, REB_TIMEF *tf) -/* -***********************************************************************/ +// +// Split_Time: C +// +void Split_Time(REBI64 t, REB_TIMEF *tf) { - // note: negative sign will be lost. - REBI64 h, m, s, n, i; - - if (t < 0) t = -t; - - h = t / HR_SEC; - i = t - (h * HR_SEC); - m = i / MIN_SEC; - i = i - (m * MIN_SEC); - s = i / SEC_SEC; - n = i - (s * SEC_SEC); - - tf->h = (REBCNT)h; - tf->m = (REBCNT)m; - tf->s = (REBCNT)s; - tf->n = (REBCNT)n; + // note: negative sign will be lost. + REBI64 h, m, s, n, i; + + if (t < 0) t = -t; + + h = t / HR_SEC; + i = t - (h * HR_SEC); + m = i / MIN_SEC; + i = i - (m * MIN_SEC); + s = i / SEC_SEC; + n = i - (s * SEC_SEC); + + tf->h = (REBCNT)h; + tf->m = (REBCNT)m; + tf->s = (REBCNT)s; + tf->n = (REBCNT)n; } -/*********************************************************************** -** -*/ REBI64 Join_Time(REB_TIMEF *tf) -/* -***********************************************************************/ +// +// Join_Time: C +// +// !! A REB_TIMEF has lost the sign bit available on the REBI64 +// used for times. If you want to make it negative, you need +// pass in a flag here. (Flag added to help document the +// issue, as previous code falsely tried to judge the sign +// of tf->h, which is always positive.) +// +REBI64 Join_Time(REB_TIMEF *tf, REBOOL neg) { - REBFLG neg = tf->h < 0; - REBI64 t; + REBI64 t; - t = tf->h * HR_SEC + tf->m * MIN_SEC + tf->s * SEC_SEC + tf->n; - return (neg ? -t : t); + t = (tf->h * HR_SEC) + (tf->m * MIN_SEC) + (tf->s * SEC_SEC) + tf->n; + return neg ? -t : t; } -/*********************************************************************** -** -*/ REBYTE *Scan_Time(REBYTE *cp, REBCNT len, REBVAL *value) -/* -** Scan string and convert to time. Return zero if error. -** -***********************************************************************/ +// +// Scan_Time: C +// +// Scan string and convert to time. Return zero if error. +// +const REBYTE *Scan_Time(REBVAL *out, const REBYTE *cp, REBCNT len) { - REBYTE *sp; - REBYTE merid = FALSE; - REBOOL neg = FALSE; - REBINT part1, part2, part3 = -1; - REBINT part4 = -1; - - if (*cp == '-') cp++, neg = TRUE; - else if (*cp == '+') cp++; - - if (*cp == '-' || *cp == '+') return 0; // small hole: --1:23 - - // Can be: - // HH:MM as part1:part2 - // HH:MM:SS as part1:part2:part3 - // HH:MM:SS.DD as part1:part2:part3.part4 - // MM:SS.DD as part1:part2.part4 - cp = Grab_Int(cp, &part1); - if (part1 > MAX_HOUR) return 0; - if (*cp++ != ':') return 0; - sp = Grab_Int(cp, &part2); - if (part2 < 0 || sp == cp) return 0; - cp = sp; - if (*cp == ':') { // optional seconds - sp = cp + 1; - cp = Grab_Int(sp, &part3); - if (part3 < 0 || cp == sp) return 0; //part3 = -1; - } - if (*cp == '.' || *cp == ',') { - sp = ++cp; - cp = Grab_Int_Scale(sp, &part4, 9); - if (part4 == 0) part4 = -1; - } - if ((UP_CASE(*cp) == 'A' || UP_CASE(*cp) == 'P') && (UP_CASE(cp[1]) == 'M')) { - merid = (REBYTE)UP_CASE(*cp); - cp += 2; - } - - if (part3 >= 0 || part4 < 0) { // HH:MM mode - if (merid) { - if (part1 > 12) return 0; - if (part1 == 12) part1 = 0; - if (merid == 'P') part1 += 12; - } - if (part3 < 0) part3 = 0; - VAL_TIME(value) = HOUR_TIME(part1) + MIN_TIME(part2) + SEC_TIME(part3); - } else { // MM:SS mode - if (merid) return 0; // no AM/PM for minutes - VAL_TIME(value) = MIN_TIME(part1) + SEC_TIME(part2); - } - - if (part4 > 0) VAL_TIME(value) += part4; - - if (neg) VAL_TIME(value) = -VAL_TIME(value); - VAL_SET(value, REB_TIME); - - return cp; + TRASH_CELL_IF_DEBUG(out); + cast(void, len); // !!! should len be paid attention to? + + REBOOL neg; + if (*cp == '-') { + ++cp; + neg = TRUE; + } + else if (*cp == '+') { + ++cp; + neg = FALSE; + } + else + neg = FALSE; + + if (*cp == '-' || *cp == '+') + return NULL; // small hole: --1:23 + + // Can be: + // HH:MM as part1:part2 + // HH:MM:SS as part1:part2:part3 + // HH:MM:SS.DD as part1:part2:part3.part4 + // MM:SS.DD as part1:part2.part4 + + REBINT part1 = -1; + cp = Grab_Int(cp, &part1); + if (part1 > MAX_HOUR) + return NULL; + + if (*cp++ != ':') + return NULL; + + const REBYTE *sp; + + REBINT part2 = -1; + sp = Grab_Int(cp, &part2); + if (part2 < 0 || sp == cp) + return NULL; + + cp = sp; + + REBINT part3 = -1; + if (*cp == ':') { // optional seconds + sp = cp + 1; + cp = Grab_Int(sp, &part3); + if (part3 < 0 || cp == sp) + return NULL; + } + + REBINT part4 = -1; + if (*cp == '.' || *cp == ',') { + sp = ++cp; + cp = Grab_Int_Scale(sp, &part4, 9); + if (part4 == 0) + part4 = -1; + } + + REBYTE merid; + if ( + (UP_CASE(*cp) == 'A' || UP_CASE(*cp) == 'P') + && (UP_CASE(cp[1]) == 'M') + ){ + merid = cast(REBYTE, UP_CASE(*cp)); + cp += 2; + } + else + merid = '\0'; + + VAL_RESET_HEADER(out, REB_TIME); + + if (part3 >= 0 || part4 < 0) { // HH:MM mode + if (merid != '\0') { + if (part1 > 12) + return NULL; + + if (part1 == 12) + part1 = 0; + + if (merid == 'P') + part1 += 12; + } + + if (part3 < 0) + part3 = 0; + + VAL_NANO(out) = HOUR_TIME(part1) + MIN_TIME(part2) + SEC_TIME(part3); + } + else { + // MM:SS mode + + if (merid != '\0') + return NULL; // no AM/PM for minutes + + VAL_NANO(out) = MIN_TIME(part1) + SEC_TIME(part2); + } + + if (part4 > 0) + VAL_NANO(out) += part4; + + if (neg) + VAL_NANO(out) = -VAL_NANO(out); + + return cp; } -/*********************************************************************** -** -*/ void Emit_Time(REB_MOLD *mold, REBVAL *value) -/* -***********************************************************************/ +// +// Emit_Time: C +// +void Emit_Time(REB_MOLD *mold, const REBVAL *value) { - REB_TIMEF tf; - REBYTE *fmt; + REB_TIMEF tf; + const char *fmt; - Split_Time(VAL_TIME(value), &tf); // loses sign + Split_Time(VAL_NANO(value), &tf); // loses sign - if (tf.s == 0 && tf.n == 0) fmt = "I:2"; - else fmt = "I:2:2"; + if (tf.s == 0 && tf.n == 0) fmt = "I:2"; + else fmt = "I:2:2"; - if (VAL_TIME(value) < (REBI64)0) Append_Byte(mold->series, '-'); - Emit(mold, fmt, tf.h, tf.m, tf.s, 0); + if (VAL_NANO(value) < cast(REBI64, 0)) + Append_Codepoint_Raw(mold->series, '-'); - if (tf.n > 0) Emit(mold, ".i", tf.n); + Emit(mold, fmt, tf.h, tf.m, tf.s, 0); + + if (tf.n > 0) Emit(mold, ".i", tf.n); } -/*********************************************************************** -** -*/ REBINT CT_Time(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Time: C +// +REBINT CT_Time(const RELVAL *a, const RELVAL *b, REBINT mode) { - REBINT num = Cmp_Time(a, b); - if (mode >= 0) return (num == 0); - if (mode == -1) return (num >= 0); - return (num > 0); + REBINT num = Cmp_Time(a, b); + if (mode >= 0) return (num == 0); + if (mode == -1) return (num >= 0); + return (num > 0); } -/*********************************************************************** -** -*/ REBI64 Make_Time(REBVAL *val) -/* -** Returns NO_TIME if error. -** -***********************************************************************/ +// +// Make_Time: C +// +// Returns NO_TIME if error. +// +REBI64 Make_Time(const REBVAL *val) { - REBI64 secs = 0; - - if (IS_TIME(val)) { - secs = VAL_TIME(val); - } - else if (IS_STRING(val)) { - REBYTE *bp; - REBCNT len; - bp = Qualify_String(val, 30, &len, FALSE); // can trap, ret diff str - if (!Scan_Time(bp, len, val)) goto no_time; - secs = VAL_TIME(val); - } - else if (IS_INTEGER(val)) { - if (VAL_INT64(val) < -MAX_SECONDS || VAL_INT64(val) > MAX_SECONDS) - Trap_Range(val); - secs = VAL_INT64(val) * SEC_SEC; - } - else if (IS_DECIMAL(val)) { - if (VAL_DECIMAL(val) < (REBDEC)(-MAX_SECONDS) || VAL_DECIMAL(val) > (REBDEC)MAX_SECONDS) - Trap_Range(val); - secs = DEC_TO_SECS(VAL_DECIMAL(val)); - } - else if (ANY_BLOCK(val) && VAL_BLK_LEN(val) <= 3) { - REBFLG neg = FALSE; - REBINT i; - - val = VAL_BLK_DATA(val); - if (!IS_INTEGER(val)) goto no_time; - i = Int32(val); - if (i < 0) i = -i, neg = TRUE; - secs = i * 3600; - if (secs > MAX_SECONDS) goto no_time; - - if (NOT_END(++val)) { - if (!IS_INTEGER(val)) goto no_time; - if ((i = Int32(val)) < 0) goto no_time; - secs += i * 60; - if (secs > MAX_SECONDS) goto no_time; - - if (NOT_END(++val)) { - if (IS_INTEGER(val)) { - if ((i = Int32(val)) < 0) goto no_time; - secs += i; - if (secs > MAX_SECONDS) goto no_time; - } - else if (IS_DECIMAL(val)) { - if (secs + (REBI64)VAL_DECIMAL(val) + 1 > MAX_SECONDS) goto no_time; - // added in below - } - else goto no_time; - } - } - secs *= SEC_SEC; - if (IS_DECIMAL(val)) secs += DEC_TO_SECS(VAL_DECIMAL(val)); - if (neg) secs = -secs; - } - else - no_time: return NO_TIME; - - return secs; + if (IS_TIME(val)) { + return VAL_NANO(val); + } + else if (IS_STRING(val)) { + REBCNT len; + REBYTE *bp = Temp_Byte_Chars_May_Fail(val, MAX_SCAN_TIME, &len, FALSE); + + DECLARE_LOCAL (temp); + if (Scan_Time(temp, bp, len) == NULL) + goto no_time; + + return VAL_NANO(temp); + } + else if (IS_INTEGER(val)) { + if (VAL_INT64(val) < -MAX_SECONDS || VAL_INT64(val) > MAX_SECONDS) + fail (Error_Out_Of_Range(val)); + + return VAL_INT64(val) * SEC_SEC; + } + else if (IS_DECIMAL(val)) { + if ( + VAL_DECIMAL(val) < cast(REBDEC, -MAX_SECONDS) + || VAL_DECIMAL(val) > cast(REBDEC, MAX_SECONDS) + ){ + fail (Error_Out_Of_Range(val)); + } + return DEC_TO_SECS(VAL_DECIMAL(val)); + } + else if (ANY_ARRAY(val) && VAL_ARRAY_LEN_AT(val) <= 3) { + RELVAL *item = VAL_ARRAY_AT(val); + if (NOT(IS_INTEGER(item))) + goto no_time; + + REBOOL neg; + REBI64 i = Int32(item); + if (i < 0) { + i = -i; + neg = TRUE; + } + else + neg = FALSE; + + REBI64 secs = i * 3600; + if (secs > MAX_SECONDS) + goto no_time; + + if (NOT_END(++item)) { + if (NOT(IS_INTEGER(item))) + goto no_time; + + if ((i = Int32(item)) < 0) + goto no_time; + + secs += i * 60; + if (secs > MAX_SECONDS) + goto no_time; + + if (NOT_END(++item)) { + if (IS_INTEGER(item)) { + if ((i = Int32(item)) < 0) + goto no_time; + + secs += i; + if (secs > MAX_SECONDS) goto no_time; + } + else if (IS_DECIMAL(item)) { + if (secs + cast(REBI64, VAL_DECIMAL(item)) + 1 > MAX_SECONDS) + goto no_time; + + // added in below + } + else + goto no_time; + } + } + + secs *= SEC_SEC; + if (IS_DECIMAL(item)) + secs += DEC_TO_SECS(VAL_DECIMAL(item)); + + if (neg) + secs = -secs; + + return secs; + } + +no_time: + return NO_TIME; } -/*********************************************************************** -** -*/ REBFLG MT_Time(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ +// +// MAKE_Time: C +// +void MAKE_Time(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBI64 secs = Make_Time(data); + assert(kind == REB_TIME); + UNUSED(kind); + + REBI64 secs = Make_Time(arg); + if (secs == NO_TIME) + fail (Error_Bad_Make(REB_TIME, arg)); - if (secs == NO_TIME) return FALSE; + VAL_RESET_HEADER(out, REB_TIME); + VAL_NANO(out) = secs; + VAL_DATE(out).bits = 0; +} - VAL_SET(out, REB_TIME); - VAL_TIME(out) = secs; - VAL_DATE(out).bits = 0; - return TRUE; +// +// TO_Time: C +// +void TO_Time(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) +{ + MAKE_Time(out, kind, arg); } -/*********************************************************************** -** -*/ REBINT Cmp_Time(REBVAL *v1, REBVAL *v2) -/* -** Given two times, compare them. -** -***********************************************************************/ +// +// Cmp_Time: C +// +// Given two times, compare them. +// +REBINT Cmp_Time(const RELVAL *v1, const RELVAL *v2) { - REBI64 t1 = VAL_TIME(v1); - REBI64 t2 = VAL_TIME(v2); - - if (t1 == NO_TIME) t1 = 0L; - if (t2 == NO_TIME) t2 = 0L; - if (t2 == t1) return 0; - if (t1 > t2) return 1; - return -1; + REBI64 t1 = VAL_NANO(v1); + REBI64 t2 = VAL_NANO(v2); + + if (t1 == NO_TIME) + t1 = 0L; + if (t2 == NO_TIME) + t2 = 0L; + if (t2 == t1) + return 0; + if (t1 > t2) + return 1; + return -1; } -/*********************************************************************** -** -*/ REBINT PD_Time(REBPVS *pvs) -/* -***********************************************************************/ +// +// Pick_Time: C +// +void Pick_Time(REBVAL *out, const REBVAL *value, const REBVAL *picker) { - REBVAL *val; - REBINT i; - REBINT n; - REBDEC f; - REB_TIMEF tf; - - if (IS_WORD(pvs->select)) { - switch (VAL_WORD_CANON(pvs->select)) { - case SYM_HOUR: i = 0; break; - case SYM_MINUTE: i = 1; break; - case SYM_SECOND: i = 2; break; - default: return PE_BAD_SELECT; - } - } - else if (IS_INTEGER(pvs->select)) - i = VAL_INT32(pvs->select) - 1; - else - return PE_BAD_SELECT; - - Split_Time(VAL_TIME(pvs->value), &tf); // loses sign - - if (!(val = pvs->setval)) { - val = pvs->store; - switch(i) { - case 0: // hours - SET_INTEGER(val, tf.h); - break; - case 1: - SET_INTEGER(val, tf.m); - break; - case 2: - if (tf.n == 0) - SET_INTEGER(val, tf.s); - else - SET_DECIMAL(val, (REBDEC)tf.s + (tf.n * NANO)); - break; - default: - return PE_NONE; - } - return PE_USE; - - } else { - if (IS_INTEGER(val) || IS_DECIMAL(val)) n = Int32s(val, 0); - else if (IS_NONE(val)) n = 0; - else return PE_BAD_SET; - - switch(i) { - case 0: - tf.h = n; - break; - case 1: - tf.m = n; - break; - case 2: - if (IS_DECIMAL(val)) { - f = VAL_DECIMAL(val); - if (f < 0.0) Trap_Range(val); - tf.s = (REBINT)f; - tf.n = (REBINT)((f - tf.s) * SEC_SEC); - } - else { - tf.s = n; - tf.n = 0; - } - break; - default: - return PE_BAD_SELECT; - } - - VAL_TIME(pvs->value) = Join_Time(&tf); - return PE_OK; - } + REBINT i; + if (IS_WORD(picker)) { + switch (VAL_WORD_SYM(picker)) { + case SYM_HOUR: i = 0; break; + case SYM_MINUTE: i = 1; break; + case SYM_SECOND: i = 2; break; + default: + fail (picker); + } + } + else if (IS_INTEGER(picker)) + i = VAL_INT32(picker) - 1; + else + fail (picker); + + REB_TIMEF tf; + Split_Time(VAL_NANO(value), &tf); // loses sign + + switch(i) { + case 0: // hours + Init_Integer(out, tf.h); + break; + case 1: // minutes + Init_Integer(out, tf.m); + break; + case 2: // seconds + if (tf.n == 0) + Init_Integer(out, tf.s); + else + Init_Decimal(out, cast(REBDEC, tf.s) + (tf.n * NANO)); + break; + default: + Init_Void(out); // "out of range" behavior for pick + } +} + + +// +// Poke_Time_Immediate: C +// +void Poke_Time_Immediate( + REBVAL *value, + const REBVAL *picker, + const REBVAL *poke +) { + REBINT i; + if (IS_WORD(picker)) { + switch (VAL_WORD_SYM(picker)) { + case SYM_HOUR: i = 0; break; + case SYM_MINUTE: i = 1; break; + case SYM_SECOND: i = 2; break; + default: + fail (picker); + } + } + else if (IS_INTEGER(picker)) + i = VAL_INT32(picker) - 1; + else + fail (picker); + + REB_TIMEF tf; + Split_Time(VAL_NANO(value), &tf); // loses sign + + REBINT n; + if (IS_INTEGER(poke) || IS_DECIMAL(poke)) + n = Int32s(poke, 0); + else if (IS_BLANK(poke)) + n = 0; + else + fail (poke); + + switch(i) { + case 0: + tf.h = n; + break; + case 1: + tf.m = n; + break; + case 2: + if (IS_DECIMAL(poke)) { + REBDEC f = VAL_DECIMAL(poke); + if (f < 0.0) + fail (Error_Out_Of_Range(poke)); + + tf.s = cast(REBINT, f); + tf.n = cast(REBINT, (f - tf.s) * SEC_SEC); + } + else { + tf.s = n; + tf.n = 0; + } + break; + default: + fail (picker); + } + + VAL_NANO(value) = Join_Time(&tf, FALSE); } -/*********************************************************************** -** -*/ REBTYPE(Time) -/* -***********************************************************************/ +// +// PD_Time: C +// +REBINT PD_Time(REBPVS *pvs) { - REBI64 secs; - REBVAL *val; - REBVAL *arg; - REBI64 num; - - val = D_ARG(1); - - secs = VAL_TIME(val); // note: not always valid REB_TIME (e.g. MAKE) - - if (DS_ARGC > 1) arg = D_ARG(2); - - if (IS_BINARY_ACT(action)) { - REBINT type = VAL_TYPE(arg); - - if (type == REB_TIME) { // handle TIME - TIME cases - REBI64 secs2 = VAL_TIME(arg); - REBINT diff; - - diff = Cmp_Time(val, arg); - switch (action) { - - case A_ADD: - secs = Add_Max(REB_TIME, secs, secs2, MAX_TIME); - goto fixTime; - - case A_SUBTRACT: - secs = Add_Max(REB_TIME, secs, -secs2, MAX_TIME); - goto fixTime; - - case A_DIVIDE: - if (secs2 == 0) Trap0(RE_ZERO_DIVIDE); - //secs /= secs2; - VAL_SET(DS_RETURN, REB_DECIMAL); - VAL_DECIMAL(DS_RETURN) = (REBDEC)secs / (REBDEC)secs2; - return R_RET; - - case A_REMAINDER: - if (secs2 == 0) Trap0(RE_ZERO_DIVIDE); - secs %= secs2; - goto setTime; - } - } - else if (type == REB_INTEGER) { // handle TIME - INTEGER cases - - num = VAL_INT64(arg); - - switch(action) { - case A_ADD: - secs = Add_Max(REB_TIME, secs, num * SEC_SEC, MAX_TIME); - goto fixTime; - - case A_SUBTRACT: - secs = Add_Max(REB_TIME, secs, num * -SEC_SEC, MAX_TIME); - goto fixTime; - - case A_MULTIPLY: - secs *= num; - if (secs < -MAX_TIME || secs > MAX_TIME) - Trap1(RE_TYPE_LIMIT, Get_Type(REB_TIME)); - goto setTime; - - case A_DIVIDE: - if (num == 0) Trap0(RE_ZERO_DIVIDE); - secs /= num; - DS_RET_INT(secs); - goto setTime; - - case A_REMAINDER: - if (num == 0) Trap0(RE_ZERO_DIVIDE); - secs %= num; - goto setTime; - } - } - else if (type == REB_DECIMAL) { // handle TIME - DECIMAL cases - REBDEC dec = VAL_DECIMAL(arg); - - switch(action) { - case A_ADD: - secs = Add_Max(REB_TIME, secs, (i64)(dec * SEC_SEC), MAX_TIME); - goto fixTime; - - case A_SUBTRACT: - secs = Add_Max(REB_TIME, secs, (i64)(dec * -SEC_SEC), MAX_TIME); - goto fixTime; - - case A_MULTIPLY: - secs = (REBI64)(secs * dec); - goto setTime; - - case A_DIVIDE: - if (dec == 0.0) Trap0(RE_ZERO_DIVIDE); - secs = (REBI64)(secs / dec); - goto setTime; - -// case A_REMAINDER: -// ld = fmod(ld, VAL_DECIMAL(arg)); -// goto decTime; - } - } - else if (type == REB_DATE && action == A_ADD) { // TIME + DATE case - // Swap args and call DATE datatupe: - *D_ARG(3) = *val; // (temporary location for swap) - *D_ARG(1) = *arg; - *D_ARG(2) = *D_ARG(3); - T_Date(ds, action); - return R_RET; - } - Trap_Math_Args(REB_TIME, action); - } - else { - // unary actions - switch(action) { - - case A_ODDQ: - DECIDE((SECS_IN(secs) & 1) != 0); - - case A_EVENQ: - DECIDE((SECS_IN(secs) & 1) == 0); - - case A_NEGATE: - secs = -secs; - goto setTime; - - case A_ABSOLUTE: - if (secs < 0) secs = -secs; - goto setTime; - - case A_ROUND: - if (D_REF(2)) { - arg = D_ARG(3); - if (IS_TIME(arg)) { - secs = Round_Int(secs, Get_Round_Flags(ds), VAL_TIME(arg)); - } - else if (IS_DECIMAL(arg)) { - VAL_DECIMAL(arg) = Round_Dec((REBDEC)secs, Get_Round_Flags(ds), Dec64(arg) * SEC_SEC) / SEC_SEC; - VAL_SET(arg, REB_DECIMAL); - return R_ARG3; - } - else if (IS_INTEGER(arg)) { - VAL_INT64(arg) = Round_Int(secs, 1, Int32(arg) * SEC_SEC) / SEC_SEC; - VAL_SET(arg, REB_INTEGER); - return R_ARG3; - } - else Trap_Arg(arg); - } - else { - secs = Round_Int(secs, Get_Round_Flags(ds) | 1, SEC_SEC); - } - goto fixTime; - - case A_RANDOM: - if (D_REF(2)) { - Set_Random(secs); - return R_UNSET; - } - secs = Random_Range(secs / SEC_SEC, D_REF(3)) * SEC_SEC; - goto fixTime; - - case A_PICK: - Pick_Path(val, arg, 0); - return R_TOS; - -/// case A_POKE: -/// Pick_Path(val, arg, D_ARG(3)); -/// return R_ARG3; - - case A_MAKE: - case A_TO: - secs = Make_Time(arg); - if (secs == NO_TIME) Trap_Make(REB_TIME, arg); - goto setTime; - } - } - Trap_Action(REB_TIME, action); + if (pvs->opt_setval) { + // + // !!! Since TIME! is an immediate value, allowing a SET-PATH! will + // modify the result of the expression but not the source. + // + Poke_Time_Immediate(KNOWN(pvs->value), pvs->picker, pvs->opt_setval); + return PE_OK; + } + + Pick_Time(pvs->store, KNOWN(pvs->value), pvs->picker); + return PE_USE_STORE; +} -fixTime: -setTime: - VAL_TIME(D_RET) = secs; - VAL_SET(D_RET, REB_TIME); - return R_RET; -is_false: - return R_FALSE; +// +// REBTYPE: C +// +REBTYPE(Time) +{ + REBVAL *val = D_ARG(1); + + REBI64 secs = VAL_NANO(val); + + REBVAL *arg = D_ARGC > 1 ? D_ARG(2) : NULL; + + // !!! This used to use IS_BINARY_ACT(), which is not available under + // the symbol-based dispatch. Consider doing another way. + // + if ( + action == SYM_ADD + || action == SYM_SUBTRACT + || action == SYM_MULTIPLY + || action == SYM_DIVIDE + || action == SYM_REMAINDER + ){ + REBINT type = VAL_TYPE(arg); + + assert(arg); + + if (type == REB_TIME) { // handle TIME - TIME cases + REBI64 secs2 = VAL_NANO(arg); + + switch (action) { + + case SYM_ADD: + secs = Add_Max(REB_TIME, secs, secs2, MAX_TIME); + goto fixTime; + + case SYM_SUBTRACT: + secs = Add_Max(REB_TIME, secs, -secs2, MAX_TIME); + goto fixTime; + + case SYM_DIVIDE: + if (secs2 == 0) fail (Error_Zero_Divide_Raw()); + //secs /= secs2; + VAL_RESET_HEADER(D_OUT, REB_DECIMAL); + VAL_DECIMAL(D_OUT) = (REBDEC)secs / (REBDEC)secs2; + return R_OUT; + + case SYM_REMAINDER: + if (secs2 == 0) fail (Error_Zero_Divide_Raw()); + secs %= secs2; + goto setTime; + + default: + fail (Error_Math_Args(REB_TIME, action)); + } + } + else if (type == REB_INTEGER) { // handle TIME - INTEGER cases + REBI64 num = VAL_INT64(arg); + + switch(action) { + case SYM_ADD: + secs = Add_Max(REB_TIME, secs, num * SEC_SEC, MAX_TIME); + goto fixTime; + + case SYM_SUBTRACT: + secs = Add_Max(REB_TIME, secs, num * -SEC_SEC, MAX_TIME); + goto fixTime; + + case SYM_MULTIPLY: + secs *= num; + if (secs < -MAX_TIME || secs > MAX_TIME) + fail (Error_Type_Limit_Raw(Get_Type(REB_TIME))); + goto setTime; + + case SYM_DIVIDE: + if (num == 0) fail (Error_Zero_Divide_Raw()); + secs /= num; + Init_Integer(D_OUT, secs); + goto setTime; + + case SYM_REMAINDER: + if (num == 0) fail (Error_Zero_Divide_Raw()); + secs %= num; + goto setTime; + + default: + fail (Error_Math_Args(REB_TIME, action)); + } + } + else if (type == REB_DECIMAL) { // handle TIME - DECIMAL cases + REBDEC dec = VAL_DECIMAL(arg); + + switch(action) { + case SYM_ADD: + secs = Add_Max(REB_TIME, secs, (i64)(dec * SEC_SEC), MAX_TIME); + goto fixTime; + + case SYM_SUBTRACT: + secs = Add_Max(REB_TIME, secs, (i64)(dec * -SEC_SEC), MAX_TIME); + goto fixTime; + + case SYM_MULTIPLY: + secs = (REBI64)(secs * dec); + goto setTime; + + case SYM_DIVIDE: + if (dec == 0.0) fail (Error_Zero_Divide_Raw()); + secs = (REBI64)(secs / dec); + goto setTime; + +// case SYM_REMAINDER: +// ld = fmod(ld, VAL_DECIMAL(arg)); +// goto decTime; + + default: + fail (Error_Math_Args(REB_TIME, action)); + } + } + else if (type == REB_DATE && action == SYM_ADD) { // TIME + DATE case + // Swap args and call DATE datatupe: + Move_Value(D_ARG(3), val); // (temporary location for swap) + Move_Value(D_ARG(1), arg); + Move_Value(D_ARG(2), D_ARG(3)); + return T_Date(frame_, action); + } + fail (Error_Math_Args(REB_TIME, action)); + } + else { + // unary actions + switch(action) { + + case SYM_ODD_Q: + return ((SECS_FROM_NANO(secs) & 1) != 0) ? R_TRUE : R_FALSE; + + case SYM_EVEN_Q: + return ((SECS_FROM_NANO(secs) & 1) == 0) ? R_TRUE : R_FALSE; + + case SYM_NEGATE: + secs = -secs; + goto setTime; + + case SYM_ABSOLUTE: + if (secs < 0) secs = -secs; + goto setTime; + + case SYM_ROUND: { + INCLUDE_PARAMS_OF_ROUND; + + UNUSED(PAR(value)); + + REBFLGS flags = ( + (REF(to) ? RF_TO : 0) + | (REF(even) ? RF_EVEN : 0) + | (REF(down) ? RF_DOWN : 0) + | (REF(half_down) ? RF_HALF_DOWN : 0) + | (REF(floor) ? RF_FLOOR : 0) + | (REF(ceiling) ? RF_CEILING : 0) + | (REF(half_ceiling) ? RF_HALF_CEILING : 0) + ); + + if (REF(to)) { + arg = ARG(scale); + if (IS_TIME(arg)) { + secs = Round_Int(secs, flags, VAL_NANO(arg)); + } + else if (IS_DECIMAL(arg)) { + VAL_DECIMAL(arg) = Round_Dec( + cast(REBDEC, secs), + flags, + Dec64(arg) * SEC_SEC + ); + VAL_DECIMAL(arg) /= SEC_SEC; + VAL_RESET_HEADER(arg, REB_DECIMAL); + Move_Value(D_OUT, ARG(scale)); + return R_OUT; + } + else if (IS_INTEGER(arg)) { + VAL_INT64(arg) = Round_Int(secs, 1, Int32(arg) * SEC_SEC) / SEC_SEC; + VAL_RESET_HEADER(arg, REB_INTEGER); + Move_Value(D_OUT, ARG(scale)); + return R_OUT; + } + else + fail (arg); + } + else { + secs = Round_Int(secs, flags | RF_TO, SEC_SEC); + } + goto fixTime; } + + case SYM_RANDOM: { + INCLUDE_PARAMS_OF_RANDOM; + + UNUSED(PAR(value)); + + if (REF(only)) + fail (Error_Bad_Refines_Raw()); + + if (REF(seed)) { + Set_Random(secs); + return R_VOID; + } + secs = Random_Range(secs / SEC_SEC, REF(secure)) * SEC_SEC; + goto fixTime; } + + default: + break; + } + } + fail (Error_Illegal_Action(REB_TIME, action)); -is_true: - return R_TRUE; +fixTime: +setTime: + VAL_RESET_HEADER(D_OUT, REB_TIME); + VAL_NANO(D_OUT) = secs; + return R_OUT; } diff --git a/src/core/t-tuple.c b/src/core/t-tuple.c index cf6c92e970..8947b0f149 100644 --- a/src/core/t-tuple.c +++ b/src/core/t-tuple.c @@ -1,386 +1,495 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-tuple.c -** Summary: tuple datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-tuple.c +// Summary: "tuple datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/*********************************************************************** -** -*/ REBINT CT_Tuple(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// CT_Tuple: C +// +REBINT CT_Tuple(const RELVAL *a, const RELVAL *b, REBINT mode) { - REBINT num = Cmp_Tuple(a, b); - if (mode > 1) return (num == 0 && VAL_TUPLE_LEN(a) == VAL_TUPLE_LEN(b)); - if (mode >= 0) return (num == 0); - if (mode == -1) return (num >= 0); - return (num > 0); + REBINT num = Cmp_Tuple(a, b); + if (mode > 1) return (num == 0 && VAL_TUPLE_LEN(a) == VAL_TUPLE_LEN(b)); + if (mode >= 0) return (num == 0); + if (mode == -1) return (num >= 0); + return (num > 0); } -/*********************************************************************** -** -*/ REBFLG MT_Tuple(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ + +// +// MAKE_Tuple: C +// +void MAKE_Tuple(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBYTE *vp; - REBINT len = 0; - REBINT n; - - vp = VAL_TUPLE(out); - for (; NOT_END(data); data++, vp++, len++) { - if (len >= 10) return FALSE; - if (IS_INTEGER(data)) { - n = Int32(data); - } - else if (IS_CHAR(data)) { - n = VAL_CHAR(data); - } - else return FALSE; - if (n > 255 || n < 0) return FALSE; - *vp = n; - } - - VAL_TUPLE_LEN(out) = len; - - for (; len < 10; len++) *vp++ = 0; - - VAL_SET(out, type); - return TRUE; + assert(kind == REB_TUPLE); + UNUSED(kind); + + if (IS_TUPLE(arg)) { + Move_Value(out, arg); + return; + } + + VAL_RESET_HEADER(out, REB_TUPLE); + REBYTE *vp = VAL_TUPLE(out); + + // !!! Net lookup parses IP addresses out of `tcp://93.184.216.34` or + // similar URL!s. In Rebol3 these captures come back the same type + // as the input instead of as STRING!, which was a latent bug in the + // network code of the 12-Dec-2012 release: + // + // https://github.com/rebol/rebol/blob/master/src/mezz/sys-ports.r#L110 + // + // All attempts to convert a URL!-flavored IP address failed. Taking + // URL! here fixes it, though there are still open questions. + // + if (IS_STRING(arg) || IS_URL(arg)) { + REBCNT len; + REBYTE *ap = Temp_Byte_Chars_May_Fail(arg, MAX_SCAN_TUPLE, &len, FALSE); + if (Scan_Tuple(out, ap, len) != NULL) + return; + fail (arg); + } + + if (ANY_ARRAY(arg)) { + REBCNT len = 0; + REBINT n; + + RELVAL *item = VAL_ARRAY_AT(arg); + + for (; NOT_END(item); ++item, ++vp, ++len) { + if (len >= MAX_TUPLE) + goto bad_make; + if (IS_INTEGER(item)) { + n = Int32(item); + } + else if (IS_CHAR(item)) { + n = VAL_CHAR(item); + } + else + goto bad_make; + + if (n > 255 || n < 0) + goto bad_make; + *vp = n; + } + + VAL_TUPLE_LEN(out) = len; + + for (; len < MAX_TUPLE; len++) *vp++ = 0; + return; + } + + REBCNT alen; + + if (IS_ISSUE(arg)) { + REBUNI c; + const REBYTE *ap = VAL_WORD_HEAD(arg); + REBCNT len = LEN_BYTES(ap); // UTF-8 len + if (len & 1) + fail (arg); // must have even # of chars + len /= 2; + if (len > MAX_TUPLE) + fail (arg); // valid even for UTF-8 + VAL_TUPLE_LEN(out) = len; + for (alen = 0; alen < len; alen++) { + const REBOOL unicode = FALSE; + if (!Scan_Hex2(ap, &c, unicode)) + fail (arg); + *vp++ = cast(REBYTE, c); + ap += 2; + } + } + else if (IS_BINARY(arg)) { + REBYTE *ap = VAL_BIN_AT(arg); + REBCNT len = VAL_LEN_AT(arg); + if (len > MAX_TUPLE) len = MAX_TUPLE; + VAL_TUPLE_LEN(out) = len; + for (alen = 0; alen < len; alen++) *vp++ = *ap++; + } + else + fail (arg); + + for (; alen < MAX_TUPLE; alen++) *vp++ = 0; + return; + +bad_make: + fail (Error_Bad_Make(REB_TUPLE, arg)); } -/*********************************************************************** -** -*/ REBINT Cmp_Tuple(REBVAL *t1, REBVAL *t2) -/* -** Given two tuples, compare them. -** -***********************************************************************/ +// +// TO_Tuple: C +// +void TO_Tuple(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBCNT len; - REBYTE *vp1, *vp2; - REBINT n; - - len = MAX(VAL_TUPLE_LEN(t1), VAL_TUPLE_LEN(t2)); - vp1 = VAL_TUPLE(t1); - vp2 = VAL_TUPLE(t2); - - for (;len > 0; len--, vp1++,vp2++) { - n = (REBINT)(*vp1 - *vp2); - if (n != 0) - return n; - } - return 0; + MAKE_Tuple(out, kind, arg); } -/*********************************************************************** -** -*/ REBINT PD_Tuple(REBPVS *pvs) -/* -** Implements PATH and SET_PATH for tuple. -** Sets DS_TOP if found. Always returns 0. -** -***********************************************************************/ +// +// Cmp_Tuple: C +// +// Given two tuples, compare them. +// +REBINT Cmp_Tuple(const RELVAL *t1, const RELVAL *t2) { - REBVAL *val; - REBINT n; - REBINT i; - REBYTE *dat; - REBINT len; - - dat = VAL_TUPLE(pvs->value); - len = VAL_TUPLE_LEN(pvs->value); - if (len < 3) len = 3; - n = Get_Num_Arg(pvs->select); - - if (NZ(val = pvs->setval)) { - if (n <= 0 || n > MAX_TUPLE) return PE_BAD_SELECT; - if (IS_INTEGER(val) || IS_DECIMAL(val)) i = Int32(val); - else if (IS_NONE(val)) { - n--; - CLEAR(dat+n, MAX_TUPLE-n); - VAL_TUPLE_LEN(pvs->value) = n; - return PE_OK; - } - else return PE_BAD_SET; - if (i < 0) i = 0; - else if (i > 255) i = 255; - dat[n-1] = i; - if (n > len) VAL_TUPLE_LEN(pvs->value) = n; - return PE_OK; - } else { - if (n > 0 && n <= len) { - SET_INTEGER(pvs->store, dat[n-1]); - return PE_USE; - } - else return PE_NONE; - } + REBCNT len; + const REBYTE *vp1, *vp2; + REBINT n; + + len = MAX(VAL_TUPLE_LEN(t1), VAL_TUPLE_LEN(t2)); + vp1 = VAL_TUPLE(t1); + vp2 = VAL_TUPLE(t2); + + for (;len > 0; len--, vp1++,vp2++) { + n = (REBINT)(*vp1 - *vp2); + if (n != 0) + return n; + } + return 0; } -/*********************************************************************** -** -*/ REBINT Emit_Tuple(REBVAL *value, REBYTE *out) -/* -** The out array must be large enough to hold longest tuple. -** Longest is: (3 digits + '.') * 11 nums + 1 term => 45 -** -***********************************************************************/ +// +// Pick_Tuple: C +// +void Pick_Tuple(REBVAL *out, const REBVAL *value, const REBVAL *picker) { - REBCNT len; - REBYTE *tp; - REBYTE *start = out; - - len = VAL_TUPLE_LEN(value); - tp = (REBYTE *)VAL_TUPLE(value); - for (; len > 0; len--, tp++) { - out = Form_Int(out, *tp); - *out++ = '.'; - } - - len = VAL_TUPLE_LEN(value); - while (len++ < 3) { - *out++ = '0'; - *out++ = '.'; - } - *--out = 0; - - return out-start; + const REBYTE *dat = VAL_TUPLE(value); + + REBINT len = VAL_TUPLE_LEN(value); + if (len < 3) + len = 3; + + REBINT n = Get_Num_From_Arg(picker); + if (n > 0 && n <= len) + Init_Integer(out, dat[n - 1]); + else + Init_Void(out); } -/*********************************************************************** -** -*/ REBTYPE(Tuple) -/* -***********************************************************************/ +// +// Poke_Tuple_Immediate: C +// +// !!! Note: In the current implementation, tuples are immediate values. +// So a POKE only changes the `value` in your hand. +// +void Poke_Tuple_Immediate( + REBVAL *value, + const REBVAL *picker, + const REBVAL *poke +) { + REBYTE *dat = VAL_TUPLE(value); + + REBINT len = VAL_TUPLE_LEN(value); + if (len < 3) + len = 3; + + REBINT n = Get_Num_From_Arg(picker); + if (n <= 0 || n > cast(REBINT, MAX_TUPLE)) + fail (Error_Out_Of_Range(picker)); + + REBINT i; + if (IS_INTEGER(poke) || IS_DECIMAL(poke)) + i = Int32(poke); + else if (IS_BLANK(poke)) { + n--; + CLEAR(dat + n, MAX_TUPLE - n); + VAL_TUPLE_LEN(value) = n; + return; + } + else + fail (poke); + + if (i < 0) + i = 0; + else if (i > 255) + i = 255; + + dat[n - 1] = i; + if (n > len) + VAL_TUPLE_LEN(value) = n; +} + + +// +// PD_Tuple: C +// +REBINT PD_Tuple(REBPVS *pvs) { - REBVAL *value; - REBVAL *arg; - REBYTE *vp; - REBYTE *ap; - REBINT len; - REBINT alen; - REBINT v; - REBINT a; - REBDEC dec; - - value = D_ARG(1); - if (IS_TUPLE(value)) { - vp = VAL_TUPLE(value); - len = VAL_TUPLE_LEN(value); - } - arg = D_ARG(2); - - if (IS_BINARY_ACT(action)) { - if (IS_INTEGER(arg)) { - a = VAL_INT32(arg); - ap = 0; - } else if (IS_DECIMAL(arg) || IS_PERCENT(arg)) { - dec=VAL_DECIMAL(arg); - a = (REBINT)dec; - ap = 0; - } else if (IS_TUPLE(arg)) { - ap = VAL_TUPLE(arg); - alen = VAL_TUPLE_LEN(arg); - if (len < alen) - len = VAL_TUPLE_LEN(value) = alen; - } else Trap_Math_Args(REB_TUPLE, action); - - for (;len > 0; len--, vp++) { - v = *vp; - if (ap) - a = (REBINT) *ap++; - switch (action) { - case A_ADD: v += a; break; - case A_SUBTRACT: v -= a; break; - case A_MULTIPLY: - if (IS_DECIMAL(arg) || IS_PERCENT(arg)) - v=(REBINT)(v*dec); - else - v *= a; - break; - case A_DIVIDE: - if (IS_DECIMAL(arg) || IS_PERCENT(arg)) { - if (dec == 0.0) Trap0(RE_ZERO_DIVIDE); - v=(REBINT)Round_Dec(v/dec, 0, 1.0); - } else { - if (a == 0) Trap0(RE_ZERO_DIVIDE); - v /= a; - } - break; - case A_REMAINDER: - if (a == 0) Trap0(RE_ZERO_DIVIDE); - v %= a; - break; - case A_AND: v &= a; break; - case A_OR: v |= a; break; - case A_XOR: v ^= a; break; - default: - Trap_Action(REB_TUPLE, action); - } - - if (v > 255) v = 255; - else if (v < 0) v = 0; - *vp = (REBYTE) v; - } - goto ret_value; - } - - // !!!! merge with SWITCH below !!! - if (action == A_COMPLEMENT) { - for (;len > 0; len--, vp++) - *vp = (REBYTE)~*vp; - goto ret_value; - } - if (action == A_RANDOM) { - if (D_REF(2)) Trap0(RE_BAD_REFINES); // seed - for (;len > 0; len--, vp++) { - if (*vp) - *vp = (REBYTE)(Random_Int(D_REF(3)) % (1+*vp)); - } - goto ret_value; - } -/* - if (action == A_ZEROQ) { - for (;len > 0; len--, vp++) { - if (*vp != 0) - goto is_false; - } - goto is_true; - } -*/ - //a = 1; //??? - switch (action) { - case A_LENGTHQ: - len = MAX(len, 3); - DS_RET_INT(len); - return R_RET; - - case A_PICK: - Pick_Path(value, arg, 0); - return R_TOS; - -/// case A_POKE: -/// Pick_Path(value, arg, D_ARG(3)); -/// return R_ARG3; - - case A_REVERSE: - if (D_REF(2)) { - len = Get_Num_Arg(D_ARG(3)); - if (len < 0) Trap_Range(D_ARG(3)); - len = MIN(len, VAL_TUPLE_LEN(value)); - } - if (len > 0) { - //len = MAX(len, 3); - for (v = 0; v < len/2; v++) { - a = vp[len-v-1]; - vp[len-v-1] = vp[v]; - vp[v] = a; - } - } - goto ret_value; + if (pvs->opt_setval) { + // + // !!! Is this a good idea? It means `x: 10.10.10 | y: (x/2: 20)` does + // result in y being 10.20.10, but x is unchanged. + // + Poke_Tuple_Immediate( + KNOWN(pvs->value), pvs->picker, pvs->opt_setval + ); + return PE_OK; + } + + Pick_Tuple(pvs->store, KNOWN(pvs->value), pvs->picker); + return PE_USE_STORE; +} + + +// +// Emit_Tuple: C +// +// The out array must be large enough to hold longest tuple. +// Longest is: (3 digits + '.') * 11 nums + 1 term => 45 +// +REBINT Emit_Tuple(const REBVAL *value, REBYTE *out) +{ + REBCNT len = VAL_TUPLE_LEN(value); + const REBYTE *tp = cast(const REBYTE *, VAL_TUPLE(value)); + REBYTE *start = out; + + for (; len > 0; len--, tp++) { + out = Form_Int(out, *tp); + *out++ = '.'; + } + + len = VAL_TUPLE_LEN(value); + while (len++ < 3) { + *out++ = '0'; + *out++ = '.'; + } + *--out = 0; + + return out-start; +} + + +// +// REBTYPE: C +// +// !!! The TUPLE type from Rebol is something of an oddity, plus written as +// more-or-less spaghetti code. It is likely to be replaced with something +// generalized better, but is grudgingly kept working in the meantime. +// +REBTYPE(Tuple) +{ + REBVAL *value = D_ARG(1); + REBVAL *arg = D_ARGC > 1 ? D_ARG(2) : NULL; + const REBYTE *ap; + REBCNT len; + REBCNT alen; + REBINT a; + REBDEC dec; + + assert(IS_TUPLE(value)); + + REBYTE *vp = VAL_TUPLE(value); + len = VAL_TUPLE_LEN(value); + + // !!! This used to depend on "IS_BINARY_ACT", a concept that does not + // exist any longer with symbol-based action dispatch. Patch with more + // elegant mechanism. + // + if ( + action == SYM_ADD + || action == SYM_SUBTRACT + || action == SYM_MULTIPLY + || action == SYM_DIVIDE + || action == SYM_REMAINDER + || action == SYM_AND_T + || action == SYM_OR_T + || action == SYM_XOR_T + ){ + assert(vp); + + if (IS_INTEGER(arg)) { + dec = -207.6382; // unused but avoid maybe uninitialized warning + a = VAL_INT32(arg); + ap = 0; + } + else if (IS_DECIMAL(arg) || IS_PERCENT(arg)) { + dec = VAL_DECIMAL(arg); + a = cast(REBINT, dec); + ap = 0; + } + else if (IS_TUPLE(arg)) { + dec = -251.8517; // unused but avoid maybe uninitialized warning + ap = VAL_TUPLE(arg); + alen = VAL_TUPLE_LEN(arg); + if (len < alen) + len = VAL_TUPLE_LEN(value) = alen; + a = 646699; // unused but avoid maybe uninitialized warning + } + else + fail (Error_Math_Args(REB_TUPLE, action)); + + for (;len > 0; len--, vp++) { + REBINT v = *vp; + if (ap) + a = (REBINT) *ap++; + + switch (action) { + case SYM_ADD: v += a; break; + + case SYM_SUBTRACT: v -= a; break; + + case SYM_MULTIPLY: + if (IS_DECIMAL(arg) || IS_PERCENT(arg)) + v = cast(REBINT, v * dec); + else + v *= a; + break; + + case SYM_DIVIDE: + if (IS_DECIMAL(arg) || IS_PERCENT(arg)) { + if (dec == 0.0) + fail (Error_Zero_Divide_Raw()); + + v = cast(REBINT, Round_Dec(v / dec, 0, 1.0)); + } + else { + if (a == 0) + fail (Error_Zero_Divide_Raw()); + v /= a; + } + break; + + case SYM_REMAINDER: + if (a == 0) + fail (Error_Zero_Divide_Raw()); + v %= a; + break; + + case SYM_AND_T: + v &= a; + break; + + case SYM_OR_T: + v |= a; + break; + + case SYM_XOR_T: + v ^= a; + break; + + default: + fail (Error_Illegal_Action(REB_TUPLE, action)); + } + + if (v > 255) v = 255; + else if (v < 0) v = 0; + *vp = (REBYTE) v; + } + goto ret_value; + } + + // !!!! merge with SWITCH below !!! + if (action == SYM_COMPLEMENT) { + for (;len > 0; len--, vp++) + *vp = (REBYTE)~*vp; + goto ret_value; + } + if (action == SYM_RANDOM) { + INCLUDE_PARAMS_OF_RANDOM; + + UNUSED(PAR(value)); + + if (REF(only)) + fail (Error_Bad_Refines_Raw()); + + if (REF(seed)) + fail (Error_Bad_Refines_Raw()); + for (; len > 0; len--, vp++) { + if (*vp) + *vp = cast(REBYTE, Random_Int(REF(secure)) % (1 + *vp)); + } + goto ret_value; + } + + switch (action) { + case SYM_LENGTH_OF: + len = MAX(len, 3); + Init_Integer(D_OUT, len); + return R_OUT; + + case SYM_REVERSE: { + INCLUDE_PARAMS_OF_REVERSE; + + UNUSED(PAR(series)); + + if (REF(part)) { + len = Get_Num_From_Arg(ARG(limit)); + len = MIN(len, VAL_TUPLE_LEN(value)); + } + if (len > 0) { + REBCNT i; + //len = MAX(len, 3); + for (i = 0; i < len/2; i++) { + a = vp[len - i - 1]; + vp[len - i - 1] = vp[i]; + vp[i] = a; + } + } + goto ret_value; } /* poke_it: - a = Get_Num_Arg(arg); - if (a <= 0 || a > len) { - if (action == A_PICK) return R_NONE; - Trap_Range(arg); - } - if (action == A_PICK) { - DS_RET_INT(vp[a-1]); - return R_RET; - } - // Poke: - if (!IS_INTEGER(D_ARG(3))) Trap_Arg(D_ARG(3)); - v = VAL_INT32(D_ARG(3)); - if (v < 0) - v = 0; - if (v > 255) - v = 255; - vp[a-1] = v; - goto ret_value; + a = Get_Num_From_Arg(arg); + if (a <= 0 || a > len) { + if (action == A_PICK) return R_BLANK; + fail (Error_Out_Of_Range(arg)); + } + if (action == A_PICK) { + Init_Integer(D_OUT, vp[a-1]); + return R_OUT; + } + // Poke: + if (NOT(IS_INTEGER(D_ARG(3)))) + fail (D_ARG(3)); + v = VAL_INT32(D_ARG(3)); + if (v < 0) + v = 0; + if (v > 255) + v = 255; + vp[a-1] = v; + goto ret_value; */ - case A_MAKE: - case A_TO: - if (IS_TUPLE(arg)) { - return R_ARG2; - } - if (IS_STRING(arg)) { - ap = Qualify_String(arg, 11*4+1, &len, FALSE); // can trap, ret diff str - if (Scan_Tuple(ap, len, D_RET)) return R_RET; - goto bad_arg; - } - if (ANY_BLOCK(arg)) { - if (!MT_Tuple(D_RET, VAL_BLK_DATA(arg), REB_TUPLE)) Trap_Make(REB_TUPLE, arg); - return R_RET; - } - - VAL_SET(value, REB_TUPLE); - vp = VAL_TUPLE(value); - if (IS_ISSUE(arg)) { - REBUNI c; - ap = Get_Word_Name(arg); - len = LEN_BYTES(ap); // UTF-8 len - if (len & 1) goto bad_arg; // must have even # of chars - len /= 2; - if (len > MAX_TUPLE) goto bad_arg; // valid even for UTF-8 - VAL_TUPLE_LEN(value) = len; - for (alen = 0; alen < len; alen++) { - if (!Scan_Hex2(ap, &c, 0)) goto bad_arg; - *vp++ = (REBYTE)c; - ap += 2; - } - } - else if (IS_BINARY(arg)) { - ap = VAL_BIN_DATA(arg); - len = VAL_LEN(arg); - if (len > MAX_TUPLE) len = MAX_TUPLE; - VAL_TUPLE_LEN(value) = len; - for (alen = 0; alen < len; alen++) *vp++ = *ap++; - } - else goto bad_arg; - - for (; alen < MAX_TUPLE; alen++) *vp++ = 0; - goto ret_value; - -bad_arg: - Trap_Make(REB_TUPLE, arg); - } - - Trap_Action(REB_TUPLE, action); + fail (Error_Bad_Make(REB_TUPLE, arg)); + + default: + break; + } + + fail (Error_Illegal_Action(REB_TUPLE, action)); ret_value: - *DS_RETURN = *value; - return R_RET; + Move_Value(D_OUT, value); + return R_OUT; } diff --git a/src/core/t-typeset.c b/src/core/t-typeset.c index 30b6370e5e..c2a828df8f 100644 --- a/src/core/t-typeset.c +++ b/src/core/t-typeset.c @@ -1,266 +1,339 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-typeset.c -** Summary: typeset datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-typeset.c +// Summary: "typeset datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -/*********************************************************************** -** -*/ const REBU64 Typesets[] = -/* -** Order of symbols is important- used below for Make_Typeset(). -** -************************************************************************/ -{ - 1, 0, // First (0th) typeset is not valid - SYM_ANY_TYPEX, ((REBU64)1< 1) - *Append_Frame(Lib_Context, 0, (REBCNT)(Typesets[n])) = *value; - } + REBDSP dsp_orig = DSP; + + REBINT n; + for (n = 0; Typesets[n].sym != 0; n++) { + // + // Note: the symbol in the typeset is not the symbol of a word holding + // the typesets, rather an extra data field used when the typeset is + // in a context key slot to identify that field's name + // + DS_PUSH_TRASH; + Init_Typeset(DS_TOP, Typesets[n].bits, NULL); + + Move_Value( + Append_Context(Lib_Context, NULL, Canon(Typesets[n].sym)), + DS_TOP + ); + } + + Init_Block(ROOT_TYPESETS, Pop_Stack_Values(dsp_orig)); } -/*********************************************************************** -** -*/ REBFLG Make_Typeset(REBVAL *block, REBVAL *value, REBFLG load) -/* -** block - block of datatypes (datatype words ok too) -** value - value to hold result (can be word-spec type too) -** -***********************************************************************/ +// +// Init_Typeset: C +// +// Name should be set when a typeset is being used as a function parameter +// specifier, or as a key in an object. +// +void Init_Typeset(RELVAL *value, REBU64 bits, REBSTR *opt_name) { - REBVAL *val; - REBCNT sym; - REBSER *types = VAL_SERIES(ROOT_TYPESETS); - - VAL_TYPESET(value) = 0; - - for (; NOT_END(block); block++) { - val = 0; - if (IS_WORD(block)) { - //Print("word: %s", Get_Word_Name(block)); - sym = VAL_WORD_SYM(block); - if (VAL_WORD_FRAME(block)) { // Get word value - val = Get_Var(block); - } else if (sym < REB_MAX) { // Accept datatype word - TYPE_SET(value, VAL_WORD_SYM(block)-1); - continue; - } // Special typeset symbols: - else if (sym >= SYM_ANY_TYPEX && sym <= SYM_ANY_BLOCKX) - val = BLK_SKIP(types, sym - SYM_ANY_TYPEX + 1); - } - if (!val) val = block; - if (IS_DATATYPE(val)) { - TYPE_SET(value, VAL_DATATYPE(val)); - } else if (IS_TYPESET(val)) { - VAL_TYPESET(value) |= VAL_TYPESET(val); - } else { - if (load) return FALSE; - Trap_Arg(block); - } - } - - return TRUE; + VAL_RESET_HEADER(value, REB_TYPESET); + INIT_TYPESET_NAME(value, opt_name); + VAL_TYPESET_BITS(value) = bits; } -/*********************************************************************** -** -*/ REBFLG MT_Typeset(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ -{ - if (!IS_BLOCK(data)) return FALSE; - - if (!Make_Typeset(VAL_BLK(data), out, TRUE)) return FALSE; - VAL_SET(out, REB_TYPESET); - - return TRUE; +// +// Update_Typeset_Bits_Core: C +// +// This sets the bits in a bitset according to a block of datatypes. There +// is special handling by which BAR! will set the "variadic" bit on the +// typeset, which is heeded by functions only. +// +// !!! R3-Alpha supported fixed word symbols for datatypes and typesets. +// Confusingly, this means that if you have said `word!: integer!` and use +// WORD!, you will get the integer type... but if WORD! is unbound then it +// will act as WORD!. Also, is essentially having "keywords" and should be +// reviewed to see if anything actually used it. +// +REBOOL Update_Typeset_Bits_Core( + RELVAL *typeset, + const RELVAL head[], + REBSPC *specifier +) { + assert(IS_TYPESET(typeset)); + VAL_TYPESET_BITS(typeset) = 0; + + const RELVAL *item = head; + if (NOT_END(item) && IS_BLOCK(item)) { // Double blocks signal variadic + if (NOT_END(item + 1)) + fail ("Invalid double-block in typeset"); + + item = VAL_ARRAY_AT(item); + SET_VAL_FLAG(typeset, TYPESET_FLAG_VARIADIC); + } + + for (; NOT_END(item); item++) { + const RELVAL *var = NULL; + + if (IS_WORD(item)) + var = Get_Opt_Var_May_Fail(item, specifier); + + if (var == NULL) + var = item; + + // Though MAKE FUNCTION! at its lowest level attempts to avoid any + // keywords, there are native-optimized function generators that do + // use them. Since this code is shared by both, it may or may not + // set typeset flags as a parameter. Default to always for now. + // + const REBOOL keywords = TRUE; + + if ( + keywords && IS_TAG(item) && ( + 0 == Compare_String_Vals(item, ROOT_ELLIPSIS_TAG, TRUE) + ) + ) { + // Notational convenience for variadic. + // func [x [<...> integer!]] => func [x [[integer!]]] + // + SET_VAL_FLAG(typeset, TYPESET_FLAG_VARIADIC); + } + else if ( + IS_BAR(item) || (keywords && IS_TAG(item) && ( + 0 == Compare_String_Vals(item, ROOT_END_TAG, TRUE) + )) + ) { + // A BAR! in a typeset spec for functions indicates a tolerance + // of endability. Notational convenience: + // + // func [x [ integer!]] => func [x [| integer!]] + // + SET_VAL_FLAG(typeset, TYPESET_FLAG_ENDABLE); + } + else if ( + IS_BLANK(item) || (keywords && IS_TAG(item) && ( + 0 == Compare_String_Vals(item, ROOT_OPT_TAG, TRUE) + )) + ) { + // A BLANK! in a typeset spec for functions indicates a willingness + // to take an optional. (This was once done with the "UNSET!" + // datatype, but now that there isn't a user-exposed unset data + // type this is not done.) Still, since REB_MAX_VOID is available + // internally it is used in the type filtering here. + // + // func [x [ integer!]] => func [x [_ integer!]] + // + // !!! As with BAR! for variadics, review if this makes sense to + // allow with `make typeset!` instead of just function specs. + // Note however that this is required for the legacy compatibility + // of ANY-TYPE!, which included UNSET! because it was a datatype + // in R3-Alpha and Rebol2. + // + TYPE_SET(typeset, REB_MAX_VOID); + } + else if (IS_DATATYPE(var)) { + TYPE_SET(typeset, VAL_TYPE_KIND(var)); + } + else if (IS_TYPESET(var)) { + VAL_TYPESET_BITS(typeset) |= VAL_TYPESET_BITS(var); + } + else + fail (Error_Invalid_Arg_Core(item, specifier)); + } + + return TRUE; } -/*********************************************************************** -** -*/ REBINT Find_Typeset(REBVAL *block) -/* -***********************************************************************/ +// +// MAKE_Typeset: C +// +void MAKE_Typeset(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBVAL value; - REBVAL *val; - REBINT n; + assert(kind == REB_TYPESET); + UNUSED(kind); - VAL_SET(&value, REB_TYPESET); - Make_Typeset(block, &value, 0); + if (IS_TYPESET(arg)) { + Move_Value(out, arg); + return; + } - val = VAL_BLK_SKIP(ROOT_TYPESETS, 1); + if (!IS_BLOCK(arg)) goto bad_make; - for (n = 1; NOT_END(val); val++, n++) { - if (EQUAL_TYPESET(&value, val)){ - //Print("FTS: %d", n); - return n; - } - } + Init_Typeset(out, 0, NULL); + Update_Typeset_Bits_Core(out, VAL_ARRAY_AT(arg), VAL_SPECIFIER(arg)); + return; -// Print("Size Typesets: %d", VAL_TAIL(ROOT_TYPESETS)); - Append_Val(VAL_SERIES(ROOT_TYPESETS), &value); - return n; +bad_make: + fail (Error_Bad_Make(REB_TYPESET, arg)); +} + + +// +// TO_Typeset: C +// +void TO_Typeset(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) +{ + MAKE_Typeset(out, kind, arg); } -/*********************************************************************** -** -*/ REBSER *Typeset_To_Block(REBVAL *tset) -/* -** Converts typeset value to a block of datatypes. -** No order is specified. -** -***********************************************************************/ +// +// Typeset_To_Array: C +// +// Converts typeset value to a block of datatypes. +// No order is specified. +// +REBARR *Typeset_To_Array(const REBVAL *tset) { - REBSER *block; - REBVAL *value; - REBINT n; - REBINT size = 0; - - for (n = 0; n < REB_MAX; n++) { - if (TYPE_CHECK(tset, n)) size++; - } - - block = Make_Block(size); - - // Convert bits to types: - for (n = 0; n < REB_MAX; n++) { - if (TYPE_CHECK(tset, n)) { - value = Append_Value(block); - Set_Datatype(value, n); - } - } - return block; + REBARR *block; + REBVAL *value; + REBINT n; + REBINT size = 0; + + for (n = 0; n < REB_MAX; n++) { + if (TYPE_CHECK(tset, cast(enum Reb_Kind, n))) size++; + } + + block = Make_Array(size); + + // Convert bits to types: + for (n = 0; n < REB_MAX; n++) { + if (TYPE_CHECK(tset, cast(enum Reb_Kind, n))) { + value = Alloc_Tail_Array(block); + if (n == 0) { + // + // !!! A NONE! value is currently supported in typesets to + // indicate that they take optional values. This may wind up + // as a feature of MAKE FUNCTION! only. + // + Init_Blank(value); + } + else + Val_Init_Datatype(value, cast(enum Reb_Kind, n)); + } + } + return block; } -/*********************************************************************** -** -*/ REBTYPE(Typeset) -/* -***********************************************************************/ +// +// REBTYPE: C +// +REBTYPE(Typeset) { - REBVAL *val = D_ARG(1); - REBVAL *arg = D_ARG(2); - - switch (action) { - - case A_FIND: - if (IS_DATATYPE(arg)) { - DECIDE(TYPE_CHECK(val, VAL_DATATYPE(arg))); - } - Trap_Arg(arg); - - case A_MAKE: - case A_TO: - if (IS_BLOCK(arg)) { - VAL_SET(D_RET, REB_TYPESET); - Make_Typeset(VAL_BLK_DATA(arg), D_RET, 0); - return R_RET; - } - // if (IS_NONE(arg)) { - // VAL_SET(arg, REB_TYPESET); - // VAL_TYPESET(arg) = 0L; - // return R_ARG2; - // } - if (IS_TYPESET(arg)) return R_ARG2; - Trap_Make(REB_TYPESET, arg); - - case A_AND: - case A_OR: - case A_XOR: - if (IS_DATATYPE(arg)) VAL_TYPESET(arg) = TYPESET(VAL_DATATYPE(arg)); - else if (!IS_TYPESET(arg)) Trap_Arg(arg); - - if (action == A_OR) VAL_TYPESET(val) |= VAL_TYPESET(arg); - else if (action == A_AND) VAL_TYPESET(val) &= VAL_TYPESET(arg); - else VAL_TYPESET(val) ^= VAL_TYPESET(arg); - return R_ARG1; - - case A_COMPLEMENT: - VAL_TYPESET(val) = ~VAL_TYPESET(val); - return R_ARG1; - - default: - Trap_Action(REB_TYPESET, action); - } - -is_true: - return R_TRUE; - -is_false: - return R_FALSE; + REBVAL *val = D_ARG(1); + REBVAL *arg = D_ARGC > 1 ? D_ARG(2) : NULL; + + switch (action) { + + case SYM_FIND: + if (IS_DATATYPE(arg)) + return R_FROM_BOOL(TYPE_CHECK(val, VAL_TYPE_KIND(arg))); + + fail (arg); + + case SYM_AND_T: + case SYM_OR_T: + case SYM_XOR_T: + if (IS_DATATYPE(arg)) { + VAL_TYPESET_BITS(arg) = FLAGIT_KIND(VAL_TYPE(arg)); + } + else if (NOT(IS_TYPESET(arg))) + fail (arg); + + if (action == SYM_OR_T) + VAL_TYPESET_BITS(val) |= VAL_TYPESET_BITS(arg); + else if (action == SYM_AND_T) + VAL_TYPESET_BITS(val) &= VAL_TYPESET_BITS(arg); + else + VAL_TYPESET_BITS(val) ^= VAL_TYPESET_BITS(arg); + Move_Value(D_OUT, D_ARG(1)); + return R_OUT; + + case SYM_COMPLEMENT: + VAL_TYPESET_BITS(val) = ~VAL_TYPESET_BITS(val); + Move_Value(D_OUT, D_ARG(1)); + return R_OUT; + + default: + fail (Error_Illegal_Action(REB_TYPESET, action)); + } } diff --git a/src/core/t-utype.c b/src/core/t-utype.c deleted file mode 100644 index dc968f183c..0000000000 --- a/src/core/t-utype.c +++ /dev/null @@ -1,94 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-utype.c -** Summary: user defined datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: NOT IMPLEMENTED -** -***********************************************************************/ - -#include "sys-core.h" - -#define SET_UTYPE(v,f) VAL_UTYPE_FUNC(v) = (f), VAL_UTYPE_DATA(v) = 0, VAL_SET(v, REB_UTYPE) - - -/*********************************************************************** -** -*/ REBINT CT_Utype(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ -{ - return FALSE; -} - - -/*********************************************************************** -** -*/ REBFLG MT_Utype(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ -{ - return FALSE; -} - - -/*********************************************************************** -** -*/ REBTYPE(Utype) -/* -***********************************************************************/ -{ - REBVAL *value = D_ARG(1); - REBVAL *arg = D_ARG(2); - REBVAL *spec; - REBVAL *body; - - if (action == A_MAKE) { - // MAKE udef! [spec body] - if (IS_DATATYPE(value)) { - if (!IS_BLOCK(arg)) Trap_Arg(arg); - spec = VAL_BLK(arg); - if (!IS_BLOCK(spec)) Trap_Arg(arg); - body = VAL_BLK_SKIP(arg, 1); - if (!IS_BLOCK(body)) Trap_Arg(arg); - - spec = Get_System(SYS_STANDARD, STD_UTYPE); - if (!IS_OBJECT(spec)) Trap_Arg(spec); - SET_UTYPE(D_RET, Make_Object(VAL_OBJ_FRAME(spec), body)); - VAL_UTYPE_DATA(D_RET) = 0; - return R_RET; - } - else Trap_Arg(arg); - } - - if (!IS_UTYPE(value)) Trap1(RE_INVALID_TYPE, Get_Type(REB_UTYPE)); -// if (!VAL_UTYPE_DATA(D_RET) || SERIES_TAIL(VAL_UTYPE_FUNC(value)) <= action) -// Trap_Action(REB_UTYPE, action); - - body = OFV(VAL_UTYPE_FUNC(value), action); - if (!IS_FUNCTION(body)) Trap_Action(REB_UTYPE, action); - - Do_Function(body); - - return R_RET; -} diff --git a/src/core/t-varargs.c b/src/core/t-varargs.c new file mode 100644 index 0000000000..cc6abc2ae1 --- /dev/null +++ b/src/core/t-varargs.c @@ -0,0 +1,668 @@ +// +// File: %t-varargs.h +// Summary: "Variadic Argument Type and Services" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2016-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The VARARGS! data type implements an abstraction layer over a call frame +// or arbitrary array of values. All copied instances of a REB_VARARGS value +// remain in sync as values are TAKE-d out of them. Once they report +// reaching a TAIL? they will always report TAIL?...until the call that +// spawned them is off the stack, at which point they will report an error. +// + +#include "sys-core.h" + + +#define R_For_Vararg_End(op) \ + ((op) == VARARG_OP_TAIL_Q ? R_TRUE : R_VOID) + + +// Some VARARGS! are generated from a block with no frame, while others +// have a frame. It would be inefficient to force the creation of a frame on +// each call for a BLOCK!-based varargs. So rather than doing so, there's a +// prelude which sees if it can answer the current query just from looking one +// unit ahead. +// +inline static REB_R Vararg_Op_If_No_Advance( + REBVAL *out, + enum Reb_Vararg_Op op, + const RELVAL *look, + REBSPC *specifier, + enum Reb_Param_Class pclass +){ + if (IS_END(look)) + return R_For_Vararg_End(op); // exhausted + + if (IS_BAR(look)) { + // + // Only hard quotes are allowed to see BAR! (and if they do, they + // are *encouraged* to test the evaluated bit and error on literals, + // unless they have a *really* good reason to do otherwise) + // + if (pclass == PARAM_CLASS_HARD_QUOTE) { + if (op == VARARG_OP_TAIL_Q) + return R_FALSE; + if (op == VARARG_OP_FIRST) { + Init_Bar(out); + return R_OUT; + } + assert(op == VARARG_OP_TAKE); + return R_UNHANDLED; // advance frame/array to consume BAR! + } + + return R_For_Vararg_End(op); + } + + if ( + (pclass == PARAM_CLASS_NORMAL || pclass == PARAM_CLASS_TIGHT) + && IS_WORD(look) + ){ + // When a variadic argument is being TAKE-n, deferred left hand side + // argument needs to be seen as end of variadic input. Otherwise, + // `summation 1 2 3 |> 100` acts as `summation 1 2 (3 |> 100)`. + // Deferred operators need to act somewhat as an expression barrier. + // + // Same rule applies for "tight" arguments, `sum 1 2 3 + 4` with + // sum being variadic and tight needs to act as `(sum 1 2 3) + 4` + // + // Look ahead, and if actively bound see if it's to an enfix function + // and the rules apply. Note the raw check is faster, no need to + // separately test for IS_END() + + const REBVAL *child_gotten = Get_Opt_Var_Else_End(look, specifier); + + if (VAL_TYPE_OR_0(child_gotten) == REB_FUNCTION) { + if (GET_VAL_FLAG(child_gotten, VALUE_FLAG_ENFIXED)) { + if ( + pclass == PARAM_CLASS_TIGHT + || GET_VAL_FLAG(child_gotten, FUNC_FLAG_DEFERS_LOOKBACK) + ){ + return R_For_Vararg_End(op); + } + } + } + } + + // The odd circumstances which make things simulate END--as well as an + // actual END--are all taken care of, so we're not "at the TAIL?" + // + if (op == VARARG_OP_TAIL_Q) + return R_FALSE; + + if (op == VARARG_OP_FIRST) { + if (pclass != PARAM_CLASS_HARD_QUOTE) + fail (Error_Varargs_No_Look_Raw()); // hard quote only + + Derelativize(out, look, specifier); + SET_VAL_FLAG(out, VALUE_FLAG_UNEVALUATED); + + return R_OUT; // only a lookahead, no need to advance + } + + return R_UNHANDLED; // must advance, may need to create a frame to do so +} + + +// +// Do_Vararg_Op_May_Throw: C +// +// Service routine for working with a VARARGS!. Supports TAKE-ing or just +// returning whether it's at the end or not. The TAKE is not actually a +// destructive operation on underlying data--merely a semantic chosen to +// convey feeding forward with no way to go back. +// +// Whether the parameter is quoted or evaluated is determined by the typeset +// information of the `param`. The typeset in the param is also used to +// check the result, and if an error is delivered it will use the name of +// the parameter symbol in the fail() message. +// +// * returns THROWN_FLAG if it takes from an evaluating vararg that throws +// +// * returns END_FLAG if it reaches the end of an entire input chain +// +// * returns VA_LIST_FLAG if the input is not exhausted +// +// Note: Returning VA_LIST_FLAG is probably a lie, since the odds of the +// underlying varargs being from a FRAME! running on a C `va_list` aren't +// necessarily that high. For now it is a good enough signal simply because +// it is not an index number, so it is an opaque way of saying "there is +// still more data"--and it's the same type as END_FLAG and THROWN_FLAG. +// +REB_R Do_Vararg_Op_May_Throw( + REBVAL *out, + RELVAL *vararg, + enum Reb_Vararg_Op op +) { + assert(IS_END(out)); + + const RELVAL *param; // for type checking + enum Reb_Param_Class pclass; + + REBVAL *arg; // for updating VALUE_FLAG_UNEVALUATED + REBSTR *label; + + if (vararg->extra.binding == NULL) { + // + // A vararg created from a block AND never passed as an argument + // so no typeset or quoting settings available. Treat as "normal" + // parameter. + // + assert( + NOT_SER_FLAG( + vararg->payload.varargs.feed, ARRAY_FLAG_VARLIST + ) + ); + pclass = PARAM_CLASS_NORMAL; + param = NULL; // doesn't correspond to a real varargs parameter + arg = NULL; // no corresponding varargs argument either + label = Canon(SYM___ANONYMOUS__); + } + else { + REBCTX *context = CTX(vararg->extra.binding); + REBFRM *param_frame = CTX_FRAME_IF_ON_STACK(context); + + // If the VARARGS! has a call frame, then ensure that the call frame + // where the VARARGS! originated is still on the stack. + // + if (param_frame == NULL) + fail (Error_Varargs_No_Stack_Raw()); + + param = FUNC_FACADE_HEAD(param_frame->phase) + + vararg->payload.varargs.param_offset; + pclass = VAL_PARAM_CLASS(param); + + arg = param_frame->args_head + vararg->payload.varargs.param_offset; + + label = FRM_LABEL(param_frame); + } + + REB_R r; + + if (NOT_SER_FLAG(vararg->payload.varargs.feed, ARRAY_FLAG_VARLIST)) { + // + // We are processing an ANY-ARRAY!-based varargs, which came from + // either a MAKE VARARGS! on an ANY-ARRAY! value -or- from a + // MAKE ANY-ARRAY! on a varargs (which reified the varargs into an + // array during that creation, flattening its entire output). + + REBARR *array1 = vararg->payload.varargs.feed; + REBVAL *shared = KNOWN(ARR_HEAD(array1)); + + assert(IS_END(shared) || (IS_BLOCK(shared) && ARR_LEN(array1) == 1)); + + r = Vararg_Op_If_No_Advance( + out, + op, + IS_END(shared) ? END : VAL_ARRAY_AT(shared), + IS_END(shared) ? SPECIFIED : VAL_SPECIFIER(shared), + pclass + ); + + if (r != R_UNHANDLED) + goto type_check_and_return; + + switch (pclass) { + case PARAM_CLASS_NORMAL: + case PARAM_CLASS_TIGHT: { + DECLARE_FRAME (f); + Push_Frame_At( + f, + VAL_ARRAY(shared), + VAL_INDEX(shared), + VAL_SPECIFIER(shared), + pclass == PARAM_CLASS_NORMAL + ? DO_FLAG_FULFILLING_ARG + : DO_FLAG_FULFILLING_ARG | DO_FLAG_NO_LOOKAHEAD + ); + + // Note: Do_Next_In_Subframe_Throws() is not needed here because + // this is a single use frame, whose state can be overwritten. + // + if (Do_Next_In_Frame_Throws(out, f)) { + Drop_Frame(f); + return R_OUT_IS_THROWN; + } + + if (IS_END(f->value)) + SET_END(shared); // signal end to all varargs sharing value + else { + // The indexor is "prefetched", so though the temp_frame would + // be ready to use again we're throwing it away, and need to + // effectively "undo the prefetch" by taking it down by 1. + // + assert(f->index > 0); + VAL_INDEX(shared) = f->index - 1; // seen by all sharings + } + + Drop_Frame(f); + break; } + + case PARAM_CLASS_HARD_QUOTE: + Derelativize(out, VAL_ARRAY_AT(shared), VAL_SPECIFIER(shared)); + SET_VAL_FLAG(out, VALUE_FLAG_UNEVALUATED); + VAL_INDEX(shared) += 1; + break; + + case PARAM_CLASS_SOFT_QUOTE: + if (IS_QUOTABLY_SOFT(VAL_ARRAY_AT(shared))) { + if (Eval_Value_Core_Throws( + out, VAL_ARRAY_AT(shared), VAL_SPECIFIER(shared) + )){ + return R_OUT_IS_THROWN; + } + } + else { // not a soft-"exception" case, quote ordinarily + Derelativize(out, VAL_ARRAY_AT(shared), VAL_SPECIFIER(shared)); + SET_VAL_FLAG(out, VALUE_FLAG_UNEVALUATED); + } + VAL_INDEX(shared) += 1; + break; + + default: + fail ("Invalid variadic parameter class"); + } + } + else { + // "Ordinary" case... use the original frame implied by the VARARGS! + // (so long as it is still live on the stack) + + REBCTX *context = CTX(vararg->payload.varargs.feed); + REBFRM *f = CTX_FRAME_IF_ON_STACK(context); + if (f == NULL) + fail (Error_Varargs_No_Stack_Raw()); + + r = Vararg_Op_If_No_Advance( + out, + op, + f->value, + f->specifier, + pclass + ); + + if (r != R_UNHANDLED) + goto type_check_and_return; + + // Note that evaluative cases here need Do_Next_In_Subframe_Throws(), + // because a function is running and the frame state can't be + // overwritten by an arbitrary evaluation. + // + switch (pclass) { + case PARAM_CLASS_NORMAL: + if (Do_Next_In_Subframe_Throws(out, f, DO_FLAG_FULFILLING_ARG)) + return R_OUT_IS_THROWN; + break; + + case PARAM_CLASS_TIGHT: + if (Do_Next_In_Subframe_Throws( + out, + f, + DO_FLAG_FULFILLING_ARG | DO_FLAG_NO_LOOKAHEAD + )){ + return R_OUT_IS_THROWN; + } + break; + + case PARAM_CLASS_HARD_QUOTE: + Quote_Next_In_Frame(out, f); + break; + + case PARAM_CLASS_SOFT_QUOTE: + if (IS_QUOTABLY_SOFT(f->value)) { + if (Eval_Value_Core_Throws(out, f->value, f->specifier)) + return R_OUT_IS_THROWN; + + Fetch_Next_In_Frame(f); + } + else { // not a soft-"exception" case, quote ordinarily + Quote_Next_In_Frame(out, f); + } + break; + + default: + fail ("Invalid variadic parameter class"); + } + } + + r = R_OUT; + +type_check_and_return: + if (r != R_OUT) { + assert( + op == VARARG_OP_TAIL_Q ? r == R_TRUE || r == R_FALSE : r == R_VOID + ); + return r; + } + + assert(NOT(THROWN(out))); // should have returned above + + if (param && NOT(TYPE_CHECK(param, VAL_TYPE(out)))) + fail (Error_Arg_Type(label, param, VAL_TYPE(out))); + + if (arg) { + if (GET_VAL_FLAG(out, VALUE_FLAG_UNEVALUATED)) + SET_VAL_FLAG(arg, VALUE_FLAG_UNEVALUATED); + else + CLEAR_VAL_FLAG(arg, VALUE_FLAG_UNEVALUATED); + } + + return R_OUT; // may be at end now, but reflect that at *next* call +} + + +// +// MAKE_Varargs: C +// +void MAKE_Varargs(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) +{ + assert(kind == REB_VARARGS); + UNUSED(kind); + + // With MAKE VARARGS! on an ANY-ARRAY!, the array is the backing store + // (shared) that the varargs interface cannot affect, but changes to + // the array will change the varargs. + // + if (ANY_ARRAY(arg)) { + // + // Make a single-element array to hold a reference+index to the + // incoming ANY-ARRAY!. This level of indirection means all + // VARARGS! copied from this will update their indices together. + // + REBARR *array1 = Alloc_Singular_Array(); + Move_Value(ARR_HEAD(array1), arg); + MANAGE_ARRAY(array1); + + VAL_RESET_HEADER(out, REB_VARARGS); + out->extra.binding = NULL; + #if !defined(NDEBUG) + out->payload.varargs.param_offset = -1020; + #endif + out->payload.varargs.feed = array1; + + return; + } + + // !!! Permit FRAME! ? + + fail (Error_Bad_Make(REB_VARARGS, arg)); +} + + +// +// TO_Varargs: C +// +void TO_Varargs(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) +{ + assert(kind == REB_VARARGS); + UNUSED(kind); + + UNUSED(out); + + fail (arg); +} + + +// +// PD_Varargs: C +// +// Implements the PICK* operation. +// +REBINT PD_Varargs(REBPVS *pvs) +{ + if (NOT(IS_INTEGER(pvs->picker))) + fail (pvs->picker); + + if (VAL_INT32(pvs->picker) != 1) + fail (Error_Varargs_No_Look_Raw()); + + DECLARE_LOCAL (specific); + Derelativize(specific, pvs->value, pvs->value_specifier); + + REB_R r = Do_Vararg_Op_May_Throw(pvs->store, specific, VARARG_OP_FIRST); + if (r == R_OUT_IS_THROWN) + assert(FALSE); // VARARG_OP_FIRST can't throw + else if (r == R_VOID) + Init_Void(pvs->store); + else + assert(r == R_OUT); + + return PE_USE_STORE; +} + + +// +// REBTYPE: C +// +// Handles the very limited set of operations possible on a VARARGS! +// (evaluation state inspector/modifier during a DO). +// +REBTYPE(Varargs) +{ + REBVAL *value = D_ARG(1); + + switch (action) { + // !!! SYM_PICK_P moved into PD_Varargs functionality, which PICK* uses + + case SYM_TAIL_Q: { + REB_R r = Do_Vararg_Op_May_Throw( + m_cast(REBVAL*, END), value, VARARG_OP_TAIL_Q // won't write `out` + ); + assert(r == R_TRUE || r == R_FALSE); // cannot throw + return r; } + + case SYM_TAKE_P: { + INCLUDE_PARAMS_OF_TAKE_P; + + UNUSED(PAR(series)); + if (REF(deep)) + fail (Error_Bad_Refines_Raw()); + if (REF(last)) + fail (Error_Varargs_Take_Last_Raw()); + + if (NOT(REF(part))) + return Do_Vararg_Op_May_Throw(D_OUT, value, VARARG_OP_TAKE); + + REBDSP dsp_orig = DSP; + + REBINT limit; + if (IS_INTEGER(ARG(limit))) { + limit = VAL_INT32(ARG(limit)); + if (limit < 0) + limit = 0; + } + else if (IS_BAR(ARG(limit))) { + limit = 0; // not used, but avoid maybe uninitalized warning + } + else + fail (ARG(limit)); + + while (limit-- > 0) { + REB_R r = Do_Vararg_Op_May_Throw(D_OUT, value, VARARG_OP_TAKE); + + if (r == R_OUT_IS_THROWN) + return R_OUT_IS_THROWN; + if (r == R_VOID) + break; + assert(r == R_OUT); + + DS_PUSH(D_OUT); + } + + // !!! What if caller wanted a REB_GROUP, REB_PATH, or an /INTO? + // + Init_Block(D_OUT, Pop_Stack_Values(dsp_orig)); + return R_OUT; + } + + default: + break; + } + + fail (Error_Illegal_Action(REB_VARARGS, action)); +} + + +// +// CT_Varargs: C +// +// Simple comparison function stub (required for every type--rules TBD for +// levels of "exactness" in equality checking, or sort-stable comparison.) +// +REBINT CT_Varargs(const RELVAL *a, const RELVAL *b, REBINT mode) +{ + cast(void, mode); + + // !!! For the moment, say varargs are the same if they have the same + // source feed from which the data comes. (This check will pass even + // expired varargs, because the expired stub should be kept alive as + // long as its identity is needed). + // + if (a->payload.varargs.feed == b->payload.varargs.feed) + return 1; + return 0; +} + + +// +// Mold_Varargs: C +// +// !!! The molding behavior was implemented to help with debugging the type, +// but is not ready for prime-time. Rather than risk crashing or presenting +// incomplete information, it's very minimal for now. Review after the +// VARARGS! have stabilized somewhat just how much information can (or should) +// be given when printing these out (they should not "lookahead") +// +void Mold_Varargs(const REBVAL *v, REB_MOLD *mold) { + assert(IS_VARARGS(v)); + + Pre_Mold(v, mold); // #[varargs! or make varargs! + + Append_Codepoint_Raw(mold->series, '['); + + if (v->extra.binding == NULL) { + Append_Unencoded(mold->series, "???"); + } + else { + REBCTX *context = CTX(v->extra.binding); + REBFRM *param_frame = CTX_FRAME_IF_ON_STACK(context); + + if (param_frame == NULL) { + Append_Unencoded(mold->series, "???"); + } + else { + const RELVAL *param + = FUNC_FACADE_HEAD(param_frame->phase) + + v->payload.varargs.param_offset; + + enum Reb_Param_Class pclass = VAL_PARAM_CLASS(param); + enum Reb_Kind kind; + switch (pclass) { + case PARAM_CLASS_NORMAL: + kind = REB_WORD; + break; + + case PARAM_CLASS_TIGHT: + kind = REB_ISSUE; + break; + + case PARAM_CLASS_HARD_QUOTE: + kind = REB_GET_WORD; + break; + + case PARAM_CLASS_SOFT_QUOTE: + kind = REB_LIT_WORD; + break; + + default: + panic (NULL); + }; + + // Note varargs_param is distinct from f->param! + DECLARE_LOCAL (param_word); + Init_Any_Word( + param_word, kind, VAL_PARAM_SPELLING(param) + ); + + Mold_Value(mold, param_word, TRUE); + } + } + + Append_Unencoded(mold->series, " <= "); + + REBARR *feed = v->payload.varargs.feed; + + if (NOT_SER_FLAG(feed, ARRAY_FLAG_VARLIST)) { + REBARR *array1 = feed; + + { // Just [...] for now + Append_Unencoded(mold->series, "[...]"); + goto skip_complex_mold_for_now; + } + + if (IS_END(ARR_HEAD(array1))) + Append_Unencoded(mold->series, "*exhausted*"); + else + Mold_Value(mold, ARR_HEAD(array1), TRUE); + } + else if (NOT(IS_ARRAY_MANAGED(feed))) { + // + // This can happen if you internally try and PROBE() a varargs + // item that is residing in the argument slots for a function, + // while that function is still fulfilling its arguments. + // + Append_Unencoded(mold->series, "** varargs frame not fulfilled"); + } + else { + REBCTX *context = CTX(feed); + REBFRM *f = CTX_FRAME_IF_ON_STACK(context); + + if (f == NULL) { + Append_Unencoded(mold->series, "**unavailable: call ended **"); + } + else { + {// Just [...] for now + Append_Unencoded(mold->series, "[...]"); + goto skip_complex_mold_for_now; + } + + if (IS_END(f->value)) + Append_Unencoded(mold->series, "*exhausted*"); + else { + Mold_Value(mold, f->value, TRUE); + + if (f->flags.bits & DO_FLAG_VA_LIST) + Append_Unencoded(mold->series, "*C varargs, pending*"); + else + Mold_Array_At( + mold, f->source.array, cast(REBCNT, f->index), NULL + ); + } + } + } + +skip_complex_mold_for_now: + Append_Codepoint_Raw(mold->series, ']'); + + End_Mold(mold); +} diff --git a/src/core/t-vector.c b/src/core/t-vector.c index f6cb0b2ce6..9be02f9152 100644 --- a/src/core/t-vector.c +++ b/src/core/t-vector.c @@ -1,669 +1,756 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-vector.c -** Summary: vector datatype -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-vector.c +// Summary: "vector datatype" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -#define SET_VECTOR(v,s) VAL_SERIES(v)=(s), VAL_INDEX(v)=0, VAL_SET(v, REB_VECTOR) +#define Init_Vector(v,s) \ + Init_Any_Series((v), REB_VECTOR, (s)) // Encoding Format: -// stored in series->size for now -// [d d d d d d d d 0 0 0 0 t s b b] +// stored in series->size for now +// [d d d d d d d d 0 0 0 0 t s b b] // Encoding identifiers: enum { - VTSI08 = 0, - VTSI16, - VTSI32, - VTSI64, - - VTUI08, - VTUI16, - VTUI32, - VTUI64, - - VTSF08, // not used - VTSF16, // not used - VTSF32, - VTSF64, + VTSI08 = 0, + VTSI16, + VTSI32, + VTSI64, + + VTUI08, + VTUI16, + VTUI32, + VTUI64, + + VTSF08, // not used + VTSF16, // not used + VTSF32, + VTSF64 }; -#define VECT_TYPE(s) ((s)->size & 0xff) +#define VECT_TYPE(s) ((s)->misc.size & 0xff) static REBCNT bit_sizes[4] = {8, 16, 32, 64}; REBU64 f_to_u64(float n) { - union { - REBU64 u; - REBDEC d; - } t; - t.d = n; - return t.u; + union { + REBU64 u; + REBDEC d; + } t; + t.d = n; + return t.u; } - + REBU64 get_vect(REBCNT bits, REBYTE *data, REBCNT n) { - switch (bits) { - case VTSI08: - return (REBI64) ((char*)data)[n]; + switch (bits) { + case VTSI08: + return (REBI64) ((i8*)data)[n]; + + case VTSI16: + return (REBI64) ((i16*)data)[n]; - case VTSI16: - return (REBI64) ((short*)data)[n]; + case VTSI32: + return (REBI64) ((i32*)data)[n]; - case VTSI32: - return (REBI64) ((long*)data)[n]; + case VTSI64: + return (REBI64) ((i64*)data)[n]; - case VTSI64: - return (REBI64) ((i64*)data)[n]; + case VTUI08: + return (REBU64) ((u8*)data)[n]; - case VTUI08: - return (REBU64) ((unsigned char*)data)[n]; + case VTUI16: + return (REBU64) ((u16*)data)[n]; - case VTUI16: - return (REBU64) ((unsigned short*)data)[n]; + case VTUI32: + return (REBU64) ((u32*)data)[n]; - case VTUI32: - return (REBU64) ((unsigned long*)data)[n]; + case VTUI64: + return (REBU64) ((i64*)data)[n]; - case VTUI64: - return (REBU64) ((i64*)data)[n]; + case VTSF08: + case VTSF16: + case VTSF32: + return f_to_u64(((float*)data)[n]); - case VTSF08: - case VTSF16: - case VTSF32: - return f_to_u64(((float*)data)[n]); - - case VTSF64: - return ((REBU64*)data)[n]; - } + case VTSF64: + return ((REBU64*)data)[n]; + } - return 0; + return 0; } void set_vect(REBCNT bits, REBYTE *data, REBCNT n, REBI64 i, REBDEC f) { - switch (bits) { - - case VTSI08: - ((char*)data)[n] = (char)i; - break; - - case VTSI16: - ((short*)data)[n] = (short)i; - break; - - case VTSI32: - ((long*)data)[n] = (long)i; - break; - - case VTSI64: - ((i64*)data)[n] = (i64)i; - break; - - case VTUI08: - ((unsigned char*)data)[n] = (unsigned char)i; - break; - - case VTUI16: - ((unsigned short*)data)[n] = (unsigned short)i; - break; - - case VTUI32: - ((unsigned long*)data)[n] = (unsigned long)i; - break; - - case VTUI64: - ((i64*)data)[n] = (u64)i; - break; - - case VTSF08: - case VTSF16: - case VTSF32: - ((float*)data)[n] = (float)f; - break; - - case VTSF64: - ((double*)data)[n] = f; - break; - } + switch (bits) { + + case VTSI08: + ((i8*)data)[n] = (i8)i; + break; + + case VTSI16: + ((i16*)data)[n] = (i16)i; + break; + + case VTSI32: + ((i32*)data)[n] = (i32)i; + break; + + case VTSI64: + ((i64*)data)[n] = (i64)i; + break; + + case VTUI08: + ((u8*)data)[n] = (u8)i; + break; + + case VTUI16: + ((u16*)data)[n] = (u16)i; + break; + + case VTUI32: + ((u32*)data)[n] = (u32)i; + break; + + case VTUI64: + ((i64*)data)[n] = (u64)i; + break; + + case VTSF08: + case VTSF16: + case VTSF32: + ((float*)data)[n] = (float)f; + break; + + case VTSF64: + ((double*)data)[n] = f; + break; + } } -void Set_Vector_Row(REBSER *ser, REBVAL *blk) +void Set_Vector_Row(REBSER *ser, const REBVAL *blk) { - REBCNT idx = VAL_INDEX(blk); - REBCNT len = VAL_LEN(blk); - REBVAL *val; - REBCNT n = 0; - REBCNT bits = VECT_TYPE(ser); - REBI64 i = 0; - REBDEC f = 0; - - if (IS_BLOCK(blk)) { - val = VAL_BLK_DATA(blk); - - for (; NOT_END(val); val++) { - if (IS_INTEGER(val)) { - i = VAL_INT64(val); - if (bits > VTUI64) f = (REBDEC)(i); - } - else if (IS_DECIMAL(val)) { - f = VAL_DECIMAL(val); - if (bits <= VTUI64) i = (REBINT)(f); - } - else Trap_Arg(val); - //if (n >= ser->tail) Expand_Vector(ser); - set_vect(bits, ser->data, n++, i, f); - } - } - else { - REBYTE *data = VAL_BIN_DATA(blk); - for (; len > 0; len--, idx++) { - set_vect(bits, ser->data, n++, (REBI64)(data[idx]), f); - } - } + REBCNT idx = VAL_INDEX(blk); + REBCNT len = VAL_LEN_AT(blk); + RELVAL *val; + REBCNT n = 0; + REBCNT bits = VECT_TYPE(ser); + REBI64 i = 0; + REBDEC f = 0; + + if (IS_BLOCK(blk)) { + val = VAL_ARRAY_AT(blk); + + for (; NOT_END(val); val++) { + if (IS_INTEGER(val)) { + i = VAL_INT64(val); + if (bits > VTUI64) f = (REBDEC)(i); + } + else if (IS_DECIMAL(val)) { + f = VAL_DECIMAL(val); + if (bits <= VTUI64) i = (REBINT)(f); + } + else fail (Error_Invalid_Arg_Core(val, VAL_SPECIFIER(blk))); + //if (n >= ser->tail) Expand_Vector(ser); + set_vect(bits, SER_DATA_RAW(ser), n++, i, f); + } + } + else { + REBYTE *data = VAL_BIN_AT(blk); + for (; len > 0; len--, idx++) { + set_vect( + bits, SER_DATA_RAW(ser), n++, cast(REBI64, data[idx]), f + ); + } + } } -/*********************************************************************** -** -*/ REBSER *Make_Vector_Block(REBVAL *vect) -/* -** Convert a vector to a block. -** -***********************************************************************/ +// +// Vector_To_Array: C +// +// Convert a vector to a block. +// +REBARR *Vector_To_Array(const REBVAL *vect) { - REBCNT len = VAL_LEN(vect); - REBYTE *data = VAL_SERIES(vect)->data; - REBCNT type = VECT_TYPE(VAL_SERIES(vect)); - REBSER *ser = Make_Block(len); - REBCNT n; - REBVAL *val; - - if (len > 0) { - val = BLK_HEAD(ser); - for (n = VAL_INDEX(vect); n < VAL_TAIL(vect); n++, val++) { - VAL_SET(val, (type >= VTSF08) ? REB_DECIMAL : REB_INTEGER); - VAL_INT64(val) = get_vect(type, data, n); // can be int or decimal - } - } - - SET_END(val); - ser->tail = len; - - return ser; + REBCNT len = VAL_LEN_AT(vect); + if (len <= 0) + fail (vect); + + REBARR *array = Make_Array(len); + + REBYTE *data = SER_DATA_RAW(VAL_SERIES(vect)); + REBCNT type = VECT_TYPE(VAL_SERIES(vect)); + + RELVAL *val = ARR_HEAD(array); + REBCNT n; + for (n = VAL_INDEX(vect); n < VAL_LEN_HEAD(vect); n++, val++) { + VAL_RESET_HEADER(val, (type >= VTSF08) ? REB_DECIMAL : REB_INTEGER); + VAL_INT64(val) = get_vect(type, data, n); // can be int or decimal + } + + TERM_ARRAY_LEN(array, len); + assert(IS_END(val)); + + return array; } -/*********************************************************************** -** -*/ REBINT Compare_Vector(REBVAL *v1, REBVAL *v2) -/* -***********************************************************************/ +// +// Compare_Vector: C +// +REBINT Compare_Vector(const RELVAL *v1, const RELVAL *v2) { - REBCNT l1 = VAL_LEN(v1); - REBCNT l2 = VAL_LEN(v2); - REBCNT len = MIN(l1, l2); - REBCNT n; - REBU64 i1; - REBU64 i2; - REBYTE *d1 = VAL_SERIES(v1)->data; - REBYTE *d2 = VAL_SERIES(v2)->data; - REBCNT b1 = VECT_TYPE(VAL_SERIES(v1)); - REBCNT b2 = VECT_TYPE(VAL_SERIES(v2)); - - if ( - (b1 >= VTSF08 && b2 < VTSF08) - || (b2 >= VTSF08 && b1 < VTSF08) - ) Trap0(RE_NOT_SAME_TYPE); - - for (n = 0; n < len; n++) { - i1 = get_vect(b1, d1, n + VAL_INDEX(v1)); - i2 = get_vect(b2, d2, n + VAL_INDEX(v2)); - if (i1 != i2) break; - } - - if (n != len) { - if (i1 > i2) return 1; - return -1; - } - - return l1 - l2; + REBCNT l1 = VAL_LEN_AT(v1); + REBCNT l2 = VAL_LEN_AT(v2); + REBCNT len = MIN(l1, l2); + REBCNT n; + REBU64 i1; + REBU64 i2; + REBYTE *d1 = SER_DATA_RAW(VAL_SERIES(v1)); + REBYTE *d2 = SER_DATA_RAW(VAL_SERIES(v2)); + REBCNT b1 = VECT_TYPE(VAL_SERIES(v1)); + REBCNT b2 = VECT_TYPE(VAL_SERIES(v2)); + + if ((b1 >= VTSF08 && b2 < VTSF08) || (b2 >= VTSF08 && b1 < VTSF08)) + fail (Error_Not_Same_Type_Raw()); + + for (n = 0; n < len; n++) { + i1 = get_vect(b1, d1, n + VAL_INDEX(v1)); + i2 = get_vect(b2, d2, n + VAL_INDEX(v2)); + if (i1 != i2) break; + } + + if (n != len) { + if (i1 > i2) return 1; + return -1; + } + + return l1 - l2; } -/*********************************************************************** -** -*/ void Shuffle_Vector(REBVAL *vect, REBFLG secure) -/* -***********************************************************************/ +// +// Shuffle_Vector: C +// +void Shuffle_Vector(REBVAL *vect, REBOOL secure) { - REBCNT n; - REBCNT k; - REBU64 swap; - REBYTE *data = VAL_SERIES(vect)->data; - REBCNT type = VECT_TYPE(VAL_SERIES(vect)); - REBCNT idx = VAL_INDEX(vect); - - // We can do it as INTS, because we just deal with the bits: - if (type == VTSF32) type = VTUI32; - else if (type == VTSF64) type = VTUI64; - - for (n = VAL_LEN(vect); n > 1;) { - k = idx + (REBCNT)Random_Int(secure) % n; - n--; - swap = get_vect(type, data, k); - set_vect(type, data, k, get_vect(type, data, n + idx), 0); - set_vect(type, data, n + idx, swap, 0); - } + REBCNT n; + REBCNT k; + REBU64 swap; + REBYTE *data = SER_DATA_RAW(VAL_SERIES(vect)); + REBCNT type = VECT_TYPE(VAL_SERIES(vect)); + REBCNT idx = VAL_INDEX(vect); + + // We can do it as INTS, because we just deal with the bits: + if (type == VTSF32) type = VTUI32; + else if (type == VTSF64) type = VTUI64; + + for (n = VAL_LEN_AT(vect); n > 1;) { + k = idx + (REBCNT)Random_Int(secure) % n; + n--; + swap = get_vect(type, data, k); + set_vect(type, data, k, get_vect(type, data, n + idx), 0); + set_vect(type, data, n + idx, swap, 0); + } } -/*********************************************************************** -** -*/ void Set_Vector_Value(REBVAL *var, REBSER *series, REBCNT index) -/* -***********************************************************************/ +// +// Set_Vector_Value: C +// +void Set_Vector_Value(REBVAL *var, REBSER *series, REBCNT index) { - REBYTE *data = series->data; - REBCNT bits = VECT_TYPE(series); - - var->data.integer = get_vect(bits, data, index); - if (bits >= VTSF08) SET_TYPE(var, REB_DECIMAL); - else SET_TYPE(var, REB_INTEGER); + REBYTE *data = SER_DATA_RAW(series); + REBCNT bits = VECT_TYPE(series); + + if (bits >= VTSF08) { + VAL_RESET_HEADER(var, REB_DECIMAL); + REBU64 u = get_vect(bits, data, index); + Init_Decimal_Bits(var, cast(REBYTE*, &u)); + } + else { + VAL_RESET_HEADER(var, REB_INTEGER); + VAL_INT64(var) = get_vect(bits, data, index); + } } -/*********************************************************************** -** -*/ REBSER *Make_Vector(REBINT type, REBINT sign, REBINT dims, REBINT bits, REBINT size) -/* -** type: the datatype -** sign: signed or unsigned -** dims: number of dimensions -** bits: number of bits per unit (8, 16, 32, 64) -** size: size of array ? -** -***********************************************************************/ +// +// Make_Vector: C +// +// type: the datatype +// sign: signed or unsigned +// dims: number of dimensions +// bits: number of bits per unit (8, 16, 32, 64) +// size: size of array ? +// +REBSER *Make_Vector(REBINT type, REBINT sign, REBINT dims, REBINT bits, REBINT size) { - REBCNT len; - REBSER *ser; - - len = size * dims; - if (len > 0x7fffffff) return 0; - ser = Make_Series(len+1, bits/8, TRUE); // !!! can width help extend the len? - LABEL_SERIES(ser, "make vector"); - CLEAR(ser->data, len*bits/8); - ser->tail = len; // !!! another way to do it? - - // Store info about the vector (could be moved to flags if necessary): - switch (bits) { - case 8: bits = 0; break; - case 16: bits = 1; break; - case 32: bits = 2; break; - case 64: bits = 3; break; - } - ser->size = (dims << 8) | (type << 3) | (sign << 2) | bits; - - return ser; + REBCNT len = size * dims; + if (len > 0x7fffffff) + fail ("vector size too big"); + + REBSER *ser = Make_Series_Core(len + 1, bits/8, SERIES_FLAG_POWER_OF_2); + CLEAR(SER_DATA_RAW(ser), (len * bits) / 8); + SET_SERIES_LEN(ser, len); + + // Store info about the vector (could be moved to flags if necessary): + switch (bits) { + case 8: bits = 0; break; + case 16: bits = 1; break; + case 32: bits = 2; break; + case 64: bits = 3; break; + } + ser->misc.size = (dims << 8) | (type << 3) | (sign << 2) | bits; + + return ser; } -/*********************************************************************** -** -*/ REBVAL *Make_Vector_Spec(REBVAL *bp, REBVAL *value) -/* -** Make a vector from a block spec. -** -** make vector! [integer! 32 100] -** make vector! [decimal! 64 100] -** make vector! [unsigned integer! 32] -** Fields: -** signed: signed, unsigned -** datatypes: integer, decimal -** dimensions: 1 - N -** bitsize: 1, 8, 16, 32, 64 -** size: integer units -** init: block of values -** -***********************************************************************/ + +// +// Make_Vector_Spec: C +// +// Make a vector from a block spec. +// +// make vector! [integer! 32 100] +// make vector! [decimal! 64 100] +// make vector! [unsigned integer! 32] +// Fields: +// signed: signed, unsigned +// datatypes: integer, decimal +// dimensions: 1 - N +// bitsize: 1, 8, 16, 32, 64 +// size: integer units +// init: block of values +// +REBOOL Make_Vector_Spec(REBVAL *out, const RELVAL head[], REBSPC *specifier) { - REBINT type = -1; // 0 = int, 1 = float - REBINT sign = -1; // 0 = signed, 1 = unsigned - REBINT dims = 1; - REBINT bits = 32; - REBCNT size = 1; - REBSER *vect; - REBVAL *iblk = 0; - - // UNSIGNED - if (IS_WORD(bp) && VAL_WORD_CANON(bp) == SYM_UNSIGNED) { - sign = 1; - bp++; - } - - // INTEGER! or DECIMAL! - if (IS_WORD(bp)) { - if (VAL_WORD_CANON(bp) == (REB_INTEGER+1)) // integer! symbol - type = 0; - else if (VAL_WORD_CANON(bp) == (REB_DECIMAL+1)) { // decimal! symbol - type = 1; - if (sign > 0) return 0; - } - else return 0; - bp++; - } - - if (type < 0) type = 0; - if (sign < 0) sign = 0; - - // BITS - if (IS_INTEGER(bp)) { - bits = Int32(bp); - if ( - (bits == 32 || bits == 64) - || - (type == 0 && (bits == 8 || bits == 16)) - ) bp++; - else return 0; - } else return 0; - - // SIZE - if (IS_INTEGER(bp)) { - size = Int32(bp); - if (size < 0) return 0; - bp++; - } - - // Initial data: - if (IS_BLOCK(bp) || IS_BINARY(bp)) { - REBCNT len = VAL_LEN(bp); - if (IS_BINARY(bp) && type == 1) return 0; - if (len > size) size = len; - iblk = bp; - bp++; - } - - // Index offset: - if (IS_INTEGER(bp)) { - VAL_INDEX(value) = (Int32s(bp, 1) - 1); - bp++; - } - else VAL_INDEX(value) = 0; - - if (NOT_END(bp)) return 0; - - vect = Make_Vector(type, sign, dims, bits, size); - if (!vect) return 0; - - if (iblk) Set_Vector_Row(vect, iblk); - - SET_TYPE(value, REB_VECTOR); - VAL_SERIES(value) = vect; - // index set earlier - - return value; + REBINT type = -1; // 0 = int, 1 = float + REBINT sign = -1; // 0 = signed, 1 = unsigned + REBINT dims = 1; + REBINT bits = 32; + REBCNT size = 1; + + const RELVAL *item = head; + + if (specifier) { + // + // The specifier would be needed if variables were going to be looked + // up, but isn't required for just symbol comparisons or extracting + // integer values. + } + + // UNSIGNED + if (IS_WORD(item) && VAL_WORD_SYM(item) == SYM_UNSIGNED) { + sign = 1; + ++item; + } + + // INTEGER! or DECIMAL! + if (IS_WORD(item)) { + if (SAME_SYM_NONZERO(VAL_WORD_SYM(item), SYM_FROM_KIND(REB_INTEGER))) + type = 0; + else if ( + SAME_SYM_NONZERO(VAL_WORD_SYM(item), SYM_FROM_KIND(REB_DECIMAL)) + ){ + type = 1; + if (sign > 0) + return FALSE; + } + else + return FALSE; + ++item; + } + + if (type < 0) + type = 0; + if (sign < 0) + sign = 0; + + // BITS + if (IS_INTEGER(item)) { + bits = Int32(item); + if ( + (bits == 32 || bits == 64) + || (type == 0 && (bits == 8 || bits == 16)) + ){ + ++item; + } + else + return FALSE; + } + else + return FALSE; + + // SIZE + if (NOT_END(item) && IS_INTEGER(item)) { + if (Int32(item) < 0) + return FALSE; + size = Int32(item); + ++item; + } + + // Initial data: + + const REBVAL *iblk; + if (NOT_END(item) && (IS_BLOCK(item) || IS_BINARY(item))) { + REBCNT len = VAL_LEN_AT(item); + if (IS_BINARY(item) && type == 1) + return FALSE; + if (len > size) + size = len; + iblk = const_KNOWN(item); + ++item; + } + else + iblk = NULL; + + // Index offset: + REBCNT index; + if (NOT_END(item) && IS_INTEGER(item)) { + index = (Int32s(item, 1) - 1); + ++item; + } + else + index = 0; + + if (NOT_END(item)) + return FALSE; + + REBSER *vect = Make_Vector(type, sign, dims, bits, size); + if (vect == NULL) + return FALSE; + + if (iblk != NULL) + Set_Vector_Row(vect, iblk); + + Init_Any_Series_At(out, REB_VECTOR, vect, index); + return TRUE; } -/*********************************************************************** -** -*/ REBFLG MT_Vector(REBVAL *out, REBVAL *data, REBCNT type) -/* -***********************************************************************/ +// +// MAKE_Vector: C +// +void MAKE_Vector(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - if (Make_Vector_Spec(data, out)) return TRUE; - return FALSE; + // CASE: make vector! 100 + if (IS_INTEGER(arg) || IS_DECIMAL(arg)) { + REBINT size = Int32s(arg, 0); + if (size < 0) goto bad_make; + REBSER *ser = Make_Vector(0, 0, 1, 32, size); + Init_Vector(out, ser); + return; + } + + TO_Vector(out, kind, arg); // may fail() + return; + +bad_make: + fail (Error_Bad_Make(kind, arg)); } -/*********************************************************************** -** -*/ REBINT CT_Vector(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// TO_Vector: C +// +void TO_Vector(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBINT n = Compare_Vector(a, b); // needs to be expanded for equality - if (mode >= 0) { - return n == 0; - } - if (mode == -1) return n >= 0; - return n > 0; + if (IS_BLOCK(arg)) { + if (Make_Vector_Spec(out, VAL_ARRAY_AT(arg), VAL_SPECIFIER(arg))) + return; + } + fail (Error_Bad_Make(kind, arg)); } -/*********************************************************************** -** -*/ REBINT PD_Vector(REBPVS *pvs) -/* -***********************************************************************/ +// +// CT_Vector: C +// +REBINT CT_Vector(const RELVAL *a, const RELVAL *b, REBINT mode) { - REBSER *vect; - REBINT n; - REBINT dims; - REBINT bits; - REBYTE *vp; - REBI64 i; - REBDEC f; - - if (IS_INTEGER(pvs->select) || IS_DECIMAL(pvs->select)) - n = Int32(pvs->select); - else return PE_BAD_SELECT; - - n += VAL_INDEX(pvs->value); - vect = VAL_SERIES(pvs->value); - vp = vect->data; - bits = VECT_TYPE(vect); - dims = vect->size >> 8; - - if (pvs->setval == 0) { - - // Check range: - if (n <= 0 || (REBCNT)n > vect->tail) return PE_NONE; - - // Get element value: - pvs->store->data.integer = get_vect(bits, vp, n-1); // 64 bits - if (bits < VTSF08) { - SET_TYPE(pvs->store, REB_INTEGER); - } else { - SET_TYPE(pvs->store, REB_DECIMAL); - } - - return PE_USE; - } - - //--- Set Value... - TRAP_PROTECT(vect); - - if (n <= 0 || (REBCNT)n > vect->tail) return PE_BAD_RANGE; - - if (IS_INTEGER(pvs->setval)) { - i = VAL_INT64(pvs->setval); - if (bits > VTUI64) f = (REBDEC)(i); - } - else if (IS_DECIMAL(pvs->setval)) { - f = VAL_DECIMAL(pvs->setval); - if (bits <= VTUI64) i = (REBINT)(f); - } - else return PE_BAD_SET; - - set_vect(bits, vp, n-1, i, f); - - return PE_OK; + REBINT n = Compare_Vector(a, b); // needs to be expanded for equality + if (mode >= 0) { + return n == 0; + } + if (mode == -1) return n >= 0; + return n > 0; } -/*********************************************************************** -** -*/ REBTYPE(Vector) -/* -***********************************************************************/ +// +// Pick_Vector: C +// +void Pick_Vector(REBVAL *out, const REBVAL *value, const REBVAL *picker) { + REBSER *vect = VAL_SERIES(value); + + REBINT n; + if (IS_INTEGER(picker) || IS_DECIMAL(picker)) + n = Int32(picker); + else + fail (picker); + + n += VAL_INDEX(value); + + if (n <= 0 || cast(REBCNT, n) > SER_LEN(vect)) { + Init_Void(out); // out of range of vector data + return; + } + + REBYTE *vp = SER_DATA_RAW(vect); + REBINT bits = VECT_TYPE(vect); + + if (bits < VTSF08) + Init_Integer(out, get_vect(bits, vp, n - 1)); // 64-bit + else { + VAL_RESET_HEADER(out, REB_DECIMAL); + REBI64 i = get_vect(bits, vp, n - 1); + Init_Decimal_Bits(out, cast(REBYTE*, &i)); + } +} + + +// +// Poke_Vector_Fail_If_Read_Only: C +// +void Poke_Vector_Fail_If_Read_Only( + REBVAL *value, + const REBVAL *picker, + const REBVAL *poke +) { + REBSER *vect = VAL_SERIES(value); + FAIL_IF_READ_ONLY_SERIES(vect); + + REBINT n; + if (IS_INTEGER(picker) || IS_DECIMAL(picker)) + n = Int32(picker); + else + fail (picker); + + n += VAL_INDEX(value); + + if (n <= 0 || cast(REBCNT, n) > SER_LEN(vect)) + fail (Error_Out_Of_Range(picker)); + + REBYTE *vp = SER_DATA_RAW(vect); + REBINT bits = VECT_TYPE(vect); + + REBI64 i; + REBDEC f; + if (IS_INTEGER(poke)) { + i = VAL_INT64(poke); + if (bits > VTUI64) + f = cast(REBDEC, i); + else { + // !!! REVIEW: f was not set in this case; compiler caught the + // unused parameter. So fill with distinctive garbage to make it + // easier to search for if it ever is. + f = -646.699; + } + } + else if (IS_DECIMAL(poke)) { + f = VAL_DECIMAL(poke); + if (bits <= VTUI64) + i = cast(REBINT, f); + else + i = 0xDECAFBAD; // not used, but avoid maybe uninitalized warning + } + else + fail (poke); + + set_vect(bits, vp, n - 1, i, f); +} + + +// +// PD_Vector: C +// +// Path dispatch acts like PICK for GET-PATH! and POKE for SET-PATH! +// +REBINT PD_Vector(REBPVS *pvs) { - REBVAL *value = D_ARG(1); - REBVAL *arg = D_ARG(2); - REBINT type; - REBCNT size; - REBSER *vect; - REBSER *ser; - - type = Do_Series_Action(action, value, arg); - if (type >= 0) return type; - - vect = VAL_SERIES(value); // not valid for MAKE or TO - - // Check must be in this order (to avoid checking a non-series value); - if (action >= A_TAKE && action <= A_SORT && IS_PROTECT_SERIES(vect)) - Trap0(RE_PROTECTED); - - switch (action) { - - case A_PICK: - Pick_Path(value, arg, 0); - return R_TOS; - - case A_POKE: - Pick_Path(value, arg, D_ARG(3)); - return R_ARG3; - - case A_MAKE: - // We only allow MAKE VECTOR! ... - if (!IS_DATATYPE(value)) goto bad_make; - - // CASE: make vector! 100 - if (IS_INTEGER(arg) || IS_DECIMAL(arg)) { - size = Int32s(arg, 0); - if (size < 0) goto bad_make; - ser = Make_Vector(0, 0, 1, 32, size); - SET_VECTOR(value, ser); - break; - } -// if (IS_NONE(arg)) { -// ser = Make_Vector(0, 0, 1, 32, 0); -// SET_VECTOR(value, ser); -// break; -// } - // fall thru - - case A_TO: - // CASE: make vector! [...] - if (IS_BLOCK(arg) && Make_Vector_Spec(VAL_BLK_DATA(arg), value)) break; - goto bad_make; - - case A_LENGTHQ: - //bits = 1 << (vect->size & 3); - SET_INTEGER(D_RET, vect->tail); - return R_RET; - - case A_COPY: - ser = Copy_Series(vect); - ser->size = vect->size; // attributes - SET_VECTOR(value, ser); - break; - - case A_RANDOM: - if (D_REF(2) || D_REF(4)) Trap0(RE_BAD_REFINES); // /seed /only - Shuffle_Vector(value, D_REF(3)); - return R_ARG1; - - default: - Trap_Action(VAL_TYPE(value), action); - } - - *D_RET = *value; - return R_RET; + if (pvs->opt_setval) { + Poke_Vector_Fail_If_Read_Only( + KNOWN(pvs->value), pvs->picker, pvs->opt_setval + ); + return PE_OK; + } + + Pick_Vector(pvs->store, KNOWN(pvs->value), pvs->picker); + return PE_USE_STORE; +} -bad_make: - Trap_Make(REB_VECTOR, arg); - DEAD_END; + +// +// REBTYPE: C +// +REBTYPE(Vector) +{ + REBVAL *value = D_ARG(1); + REBSER *ser; + + // Common operations for any series type (length, head, etc.) + { + REB_R r = Series_Common_Action_Maybe_Unhandled(frame_, action); + if (r != R_UNHANDLED) + return r; + } + + REBSER *vect = VAL_SERIES(value); + + switch (action) { + + case SYM_LENGTH_OF: + //bits = 1 << (vect->size & 3); + Init_Integer(D_OUT, SER_LEN(vect)); + return R_OUT; + + case SYM_COPY: { + INCLUDE_PARAMS_OF_COPY; + + UNUSED(PAR(value)); + if (REF(part)) { + UNUSED(ARG(limit)); + fail (Error_Bad_Refines_Raw()); + } + if (REF(deep)) + fail (Error_Bad_Refines_Raw()); + if (REF(types)) { + UNUSED(ARG(kinds)); + fail (Error_Bad_Refines_Raw()); + } + + ser = Copy_Sequence(vect); + ser->misc.size = vect->misc.size; // attributes + Init_Vector(value, ser); + break; } + + case SYM_RANDOM: { + INCLUDE_PARAMS_OF_RANDOM; + UNUSED(PAR(value)); + + FAIL_IF_READ_ONLY_SERIES(vect); + + if (REF(seed) || REF(only)) + fail (Error_Bad_Refines_Raw()); + + Shuffle_Vector(value, REF(secure)); + Move_Value(D_OUT, D_ARG(1)); + return R_OUT; } + + default: + fail (Error_Illegal_Action(VAL_TYPE(value), action)); + } + + Move_Value(D_OUT, value); + return R_OUT; } - -/*********************************************************************** -** -*/ void Mold_Vector(REBVAL *value, REB_MOLD *mold, REBFLG molded) -/* -***********************************************************************/ + +// +// Mold_Vector: C +// +void Mold_Vector(const REBVAL *value, REB_MOLD *mold, REBOOL molded) { - REBSER *vect = VAL_SERIES(value); - REBYTE *data = vect->data; - REBCNT bits = VECT_TYPE(vect); -// REBCNT dims = vect->size >> 8; - REBCNT len; - REBCNT n; - REBCNT c; - union {REBU64 i; REBDEC d;} v; - REBYTE buf[32]; - REBYTE l; - - if (GET_MOPT(mold, MOPT_MOLD_ALL)) { - len = VAL_TAIL(value); - n = 0; - } else { - len = VAL_LEN(value); - n = VAL_INDEX(value); - } - - if (molded) { - REBCNT type = (bits >= VTSF08) ? REB_DECIMAL : REB_INTEGER; - Pre_Mold(value, mold); - if (!GET_MOPT(mold, MOPT_MOLD_ALL)) Append_Byte(mold->series, '['); - if (bits >= VTUI08 && bits <= VTUI64) Append_Bytes(mold->series, "unsigned "); - Emit(mold, "N I I [", type+1, bit_sizes[bits & 3], len); - if (len) New_Indented_Line(mold); - } - - c = 0; - for (; n < vect->tail; n++) { - v.i = get_vect(bits, data, n); - if (bits < VTSF08) { - l = Emit_Integer(buf, v.i); - } else { - l = Emit_Decimal(buf, v.d, 0, '.', mold->digits); - } - Append_Bytes_Len(mold->series, buf, l); - - if ((++c > 7) && (n+1 < vect->tail)) { - New_Indented_Line(mold); - c = 0; - } - else - Append_Byte(mold->series, ' '); - } - - if (len) mold->series->tail--; // remove final space - - if (molded) { - if (len) New_Indented_Line(mold); - Append_Byte(mold->series, ']'); - if (!GET_MOPT(mold, MOPT_MOLD_ALL)) { - Append_Byte(mold->series, ']'); - } - else { - Post_Mold(value, mold); - } - } + REBSER *vect = VAL_SERIES(value); + REBYTE *data = SER_DATA_RAW(vect); + REBCNT bits = VECT_TYPE(vect); +// REBCNT dims = vect->size >> 8; + REBCNT len; + REBCNT n; + REBCNT c; + union {REBU64 i; REBDEC d;} v; + REBYTE buf[32]; + REBYTE l; + + if (GET_MOPT(mold, MOPT_MOLD_ALL)) { + len = VAL_LEN_HEAD(value); + n = 0; + } else { + len = VAL_LEN_AT(value); + n = VAL_INDEX(value); + } + + if (molded) { + enum Reb_Kind kind = (bits >= VTSF08) ? REB_DECIMAL : REB_INTEGER; + Pre_Mold(value, mold); + if (!GET_MOPT(mold, MOPT_MOLD_ALL)) + Append_Codepoint_Raw(mold->series, '['); + if (bits >= VTUI08 && bits <= VTUI64) + Append_Unencoded(mold->series, "unsigned "); + Emit( + mold, + "N I I [", + Canon(SYM_FROM_KIND(kind)), + bit_sizes[bits & 3], + len + ); + if (len) + New_Indented_Line(mold); + } + + c = 0; + for (; n < SER_LEN(vect); n++) { + v.i = get_vect(bits, data, n); + if (bits < VTSF08) { + l = Emit_Integer(buf, v.i); + } else { + l = Emit_Decimal(buf, v.d, 0, '.', mold->digits); + } + Append_Unencoded_Len(mold->series, s_cast(buf), l); + + if ((++c > 7) && (n + 1 < SER_LEN(vect))) { + New_Indented_Line(mold); + c = 0; + } + else + Append_Codepoint_Raw(mold->series, ' '); + } + + if (len) { + // + // remove final space (overwritten with terminator) + // + TERM_UNI_LEN(mold->series, UNI_LEN(mold->series) - 1); + } + + if (molded) { + if (len) New_Indented_Line(mold); + Append_Codepoint_Raw(mold->series, ']'); + if (!GET_MOPT(mold, MOPT_MOLD_ALL)) { + Append_Codepoint_Raw(mold->series, ']'); + } + else { + Post_Mold(value, mold); + } + } } diff --git a/src/core/t-word.c b/src/core/t-word.c index 68ea8c8033..4144da37e5 100644 --- a/src/core/t-word.c +++ b/src/core/t-word.c @@ -1,133 +1,169 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: t-word.c -** Summary: word related datatypes -** Section: datatypes -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %t-word.c +// Summary: "word related datatypes" +// Section: datatypes +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "sys-core.h" -#ifdef not_used -/*********************************************************************** -** -** REBFLG MT_Word(REBVAL *out, REBVAL *data, REBCNT type) -** -***********************************************************************/ + +// +// CT_Word: C +// +// !!! The R3-Alpha code did a non-ordering comparison; it only tells whether +// the words are equal or not (1 or 0). This creates bad invariants for +// sorting etc. Review. +// +REBINT CT_Word(const RELVAL *a, const RELVAL *b, REBINT mode) +{ + REBINT e; + REBINT diff; + if (mode >= 0) { + if (mode == 1) { + // + // Symbols must be exact match, case-sensitively + // + if (VAL_WORD_SPELLING(a) != VAL_WORD_SPELLING(b)) + return 0; + } + else { + // Different cases acceptable, only check for a canon match + // + if (VAL_WORD_CANON(a) != VAL_WORD_CANON(b)) + return 0; + } + + return 1; + } + else { + diff = Compare_Word(a, b, FALSE); + if (mode == -1) e = diff >= 0; + else e = diff > 0; + } + return e; +} + + +// +// MAKE_Word: C +// +void MAKE_Word(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - if (!IS_WORD(data)) return FALSE; - *out = *data; - VAL_SET(out, type); - return TRUE; + if (ANY_WORD(arg)) { + // + // Only reset the type, not all the header bits (the bits must + // stay in sync with the binding state) + // + Move_Value(out, arg); + VAL_SET_TYPE_BITS(out, kind); + return; + } + + if (IS_STRING(arg)) { + REBCNT len; + const REBOOL allow_utf8 = TRUE; + + // Set name. Rest is set below. If characters in the source + // string are > 0x80 they will be encoded to UTF8 to be stored + // in the symbol. + // + REBYTE *bp = Temp_Byte_Chars_May_Fail( + arg, MAX_SCAN_WORD, &len, allow_utf8 + ); + + if (kind == REB_ISSUE) { + if (NULL == Scan_Issue(out, bp, len)) + fail (Error_Bad_Char_Raw(arg)); + } + else { + if (NULL == Scan_Any_Word(out, kind, bp, len)) + fail (Error_Bad_Char_Raw(arg)); + } + } + else if (IS_CHAR(arg)) { + REBYTE buf[8]; + REBCNT len = Encode_UTF8_Char(&buf[0], VAL_CHAR(arg)); + if (NULL == Scan_Any_Word(out, kind, &buf[0], len)) + fail (Error_Bad_Char_Raw(arg)); + } + else if (IS_DATATYPE(arg)) { + Init_Any_Word(out, kind, Canon(VAL_TYPE_SYM(arg))); + } + else if (IS_LOGIC(arg)) { + Init_Any_Word( + out, + kind, + VAL_LOGIC(arg) ? Canon(SYM_TRUE) : Canon(SYM_FALSE) + ); + } + else + fail (Error_Unexpected_Type(REB_WORD, VAL_TYPE(arg))); } -#endif -/*********************************************************************** -** -*/ REBINT CT_Word(REBVAL *a, REBVAL *b, REBINT mode) -/* -***********************************************************************/ +// +// TO_Word: C +// +void TO_Word(REBVAL *out, enum Reb_Kind kind, const REBVAL *arg) { - REBINT e; - REBINT diff; - if (mode >= 0) { - e = VAL_WORD_CANON(a) == VAL_WORD_CANON(b); - if (mode == 1) e &= VAL_WORD_INDEX(a) == VAL_WORD_INDEX(b) - && VAL_WORD_FRAME(a) == VAL_WORD_FRAME(b); - else if (mode >= 2) { - e = (VAL_WORD_SYM(a) == VAL_WORD_SYM(b) && - VAL_WORD_INDEX(a) == VAL_WORD_INDEX(b) && - VAL_WORD_FRAME(a) == VAL_WORD_FRAME(b)); - } - } else { - diff = Compare_Word(a, b, FALSE); - if (mode == -1) e = diff >= 0; - else e = diff > 0; - } - return e; + MAKE_Word(out, kind, arg); } -/*********************************************************************** -** -*/ REBTYPE(Word) -/* -***********************************************************************/ +// +// REBTYPE: C +// +// The future plan for WORD! types is that they will be unified somewhat with +// strings...but that bound words will have read-only data. Under such a +// plan, string-converting words would not be necessary for basic textual +// operations. +// +REBTYPE(Word) { - REBVAL *val = D_ARG(1); - REBVAL *arg = D_ARG(2); - REBCNT type = VAL_TYPE(val); - REBINT diff; - REBCNT sym; - - switch (action) { - case A_LENGTHQ: - diff = LEN_BYTES(Get_Sym_Name(VAL_WORD_SYM(val))); - if (type != REB_WORD) diff++; - DS_Ret_Int(diff); - break; - - case A_MAKE: - case A_TO: - // TO word! ... - if (type == REB_DATATYPE) type = (REBCNT)VAL_DATATYPE(val); - if (ANY_WORD(arg)) { - VAL_SET(arg, type); - return R_ARG2; - } - else { - if (IS_STRING(arg)) { - REBYTE *bp; - REBCNT len; - // Set sym. Rest is set below. - bp = Qualify_String(arg, 255, &len, TRUE); - if (type == REB_ISSUE) sym = Scan_Issue(bp, len); - else sym = Scan_Word(bp, len); - if (!sym) Trap1(RE_BAD_CHAR, arg); - } - else if (IS_CHAR(arg)) { - REBYTE buf[8]; - sym = Encode_UTF8_Char(&buf[0], VAL_CHAR(arg)); //returns length - sym = Scan_Word(&buf[0], sym); - if (!sym) Trap1(RE_BAD_CHAR, arg); - } - else if (IS_DATATYPE(arg)) { - sym = VAL_DATATYPE(arg)+1; - } - else if (IS_LOGIC(arg)) { - sym = IS_TRUE(arg) ? SYM_TRUE : SYM_FALSE; - } - else Trap_Types(RE_EXPECT_VAL, REB_WORD, VAL_TYPE(arg)); - Set_Word(D_RET, sym, 0, 0); - VAL_SET(D_RET, type); - } - break; - - default: - Trap_Action(type, action); - } - - return R_RET; + REBVAL *val = D_ARG(1); + + switch (action) { + case SYM_LENGTH_OF: { + const REBYTE *bp = STR_HEAD(VAL_WORD_SPELLING(val)); + REBCNT len = 0; + while (TRUE) { + REBUNI ch; + if (!(bp = Back_Scan_UTF8_Char(&ch, bp, &len))) + fail (Error_Bad_Utf8_Raw()); + if (ch == 0) + break; + } + Init_Integer(D_OUT, len); + return R_OUT; } + + default: + assert(ANY_WORD(val)); + fail (Error_Illegal_Action(VAL_TYPE(val), action)); + } + + return R_OUT; } diff --git a/src/core/u-bmp.c b/src/core/u-bmp.c deleted file mode 100644 index 8ed8c1d58a..0000000000 --- a/src/core/u-bmp.c +++ /dev/null @@ -1,618 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: u-bmp.c -** Summary: conversion to and from BMP graphics format -** Section: utility -** Notes: -** This is an optional part of R3. This file can be replaced by -** library function calls into an updated implementation. -** -***********************************************************************/ - -#include "sys-core.h" - -//********************************************************************** - -#define WADJUST(x) (((x * 3L + 3) / 4) * 4) - -typedef unsigned char BYTE; -typedef unsigned short WORD; -typedef unsigned int DWORD; -typedef int LONG; - -typedef struct tagBITMAP -{ - int bmType; - int bmWidth; - int bmHeight; - int bmWidthBytes; - BYTE bmPlanes; - BYTE bmBitsPixel; - void *bmBits; -} BITMAP; -typedef BITMAP *PBITMAP; -typedef BITMAP *NPBITMAP; -typedef BITMAP *LPBITMAP; - -/* Bitmap Header structures */ -typedef struct tagRGBTRIPLE -{ - BYTE rgbtBlue; - BYTE rgbtGreen; - BYTE rgbtRed; -} RGBTRIPLE; -typedef RGBTRIPLE *LPRGBTRIPLE; - -typedef struct tagRGBQUAD -{ - BYTE rgbBlue; - BYTE rgbGreen; - BYTE rgbRed; - BYTE rgbReserved; -} RGBQUAD; -typedef RGBQUAD *LPRGBQUAD; - -/* structures for defining DIBs */ -typedef struct tagBITMAPCOREHEADER -{ - DWORD bcSize; - short bcWidth; - short bcHeight; - WORD bcPlanes; - WORD bcBitCount; -} BITMAPCOREHEADER; -typedef BITMAPCOREHEADER* PBITMAPCOREHEADER; -typedef BITMAPCOREHEADER *LPBITMAPCOREHEADER; - -char *mapBITMAPCOREHEADER = "lssss"; - -typedef struct tagBITMAPINFOHEADER -{ - DWORD biSize; - LONG biWidth; - LONG biHeight; - WORD biPlanes; - WORD biBitCount; - DWORD biCompression; - DWORD biSizeImage; - LONG biXPelsPerMeter; - LONG biYPelsPerMeter; - DWORD biClrUsed; - DWORD biClrImportant; -} BITMAPINFOHEADER; - -char *mapBITMAPINFOHEADER = "lllssllllll"; - -typedef BITMAPINFOHEADER* PBITMAPINFOHEADER; -typedef BITMAPINFOHEADER *LPBITMAPINFOHEADER; - -/* constants for the biCompression field */ -#define BI_RGB 0L -#define BI_RLE8 1L -#define BI_RLE4 2L - -typedef struct tagBITMAPINFO -{ - BITMAPINFOHEADER bmiHeader; - RGBQUAD bmiColors[1]; -} BITMAPINFO; -typedef BITMAPINFO* PBITMAPINFO; -typedef BITMAPINFO *LPBITMAPINFO; - -typedef struct tagBITMAPCOREINFO -{ - BITMAPCOREHEADER bmciHeader; - RGBTRIPLE bmciColors[1]; -} BITMAPCOREINFO; -typedef BITMAPCOREINFO* PBITMAPCOREINFO; -typedef BITMAPCOREINFO *LPBITMAPCOREINFO; - -typedef struct tagBITMAPFILEHEADER -{ - char bfType[2]; - DWORD bfSize; - WORD bfReserved1; - WORD bfReserved2; - DWORD bfOffBits; -} BITMAPFILEHEADER; -typedef BITMAPFILEHEADER* PBITMAPFILEHEADER; -typedef BITMAPFILEHEADER *LPBITMAPFILEHEADER; - -char *mapBITMAPFILEHEADER = "bblssl"; - -typedef RGBQUAD *RGBQUADPTR; - -//********************************************************************** - -static int longaligned(void) { - static char filldata[] = {0,0,1,1,1,1}; - struct { - unsigned short a; - unsigned int b; - } a={0}; - memcpy(&a, filldata, 6); - if (a.b != 0x01010101) return TRUE; - return FALSE; -} - -void Map_Bytes(void *dstp, REBYTE **srcp, char *map) { - REBYTE *src = *srcp; - REBYTE *dst = dstp; - char c; -#ifdef ENDIAN_LITTLE - while ((c = *map++) != 0) { - switch(c) { - case 'b': - *dst++ = *src++; - break; - - case 's': - *((short *)dst) = *((short *)src); - dst += sizeof(short); - src += 2; - break; - - case 'l': - if (longaligned()) { - while(((unsigned long)dst)&3) - dst++; - } - *((REBCNT *)dst) = *((REBCNT *)src); - dst += sizeof(REBCNT); - src += 4; - break; - } - } -#else - while ((c = *map++) != 0) { - switch(c) { - case 'b': - *dst++ = *src++; - break; - - case 's': - *((short *)dst) = src[0]|(src[1]<<8); - dst += sizeof(short); - src += 2; - break; - - case 'l': - if (longaligned()) { - while (((unsigned long)dst)&3) - dst++; - } - *((REBCNT *)dst) = src[0]|(src[1]<<8)| - (src[2]<<16)|(src[3]<<24); - dst += sizeof(REBCNT); - src += 4; - break; - } - } -#endif - *srcp = src; -} - -void Unmap_Bytes(void *srcp, REBYTE **dstp, char *map) { - REBYTE *src = srcp; - REBYTE *dst = *dstp; - char c; -#ifdef ENDIAN_LITTLE - while ((c = *map++) != 0) { - switch(c) { - case 'b': - *dst++ = *src++; - break; - - case 's': - *((short *)dst) = *((short *)src); - src += sizeof(short); - dst += 2; - break; - - case 'l': - if (longaligned()) { - while(((unsigned long)src)&3) - src++; - } - *((REBCNT *)dst) = *((REBCNT *)src); - src += sizeof(REBCNT); - dst += 4; - break; - } - } -#else - while ((c = *map++) != 0) { - switch(c) { - case 'b': - *dst++ = *src++; - break; - - case 's': - *((short *)dst) = src[0]|(src[1]<<8); - src += sizeof(short); - dst += 2; - break; - - case 'l': - if (longaligned()) { - while (((unsigned long)src)&3) - src++; - } - *((REBCNT *)dst) = src[0]|(src[1]<<8)| - (src[2]<<16)|(src[3]<<24); - src += sizeof(REBCNT); - dst += 4; - break; - } - } -#endif - *dstp = dst; -} - - -/*********************************************************************** -** -*/ static void Decode_BMP_Image(REBCDI *codi) -/* -** Input: BMP encoded image (codi->data, len) -** Output: Image bits (codi->bits, w, h) -** Error: Code in codi->error -** Return: Success as TRUE or FALSE -** -***********************************************************************/ -{ - REBINT i, j, x, y, c; - REBINT colors, compression, bitcount; - REBINT w, h; - BITMAPFILEHEADER bmfh; - BITMAPINFOHEADER bmih; - BITMAPCOREHEADER bmch; - REBYTE *cp, *tp; - REBCNT *dp; - RGBQUADPTR color; - RGBQUADPTR ctab = 0; - - cp = codi->data; - Map_Bytes(&bmfh, &cp, mapBITMAPFILEHEADER); - if (bmfh.bfType[0] != 'B' || bmfh.bfType[1] != 'M') { - codi->error = CODI_ERR_SIGNATURE; - return; - } - if (codi->action == CODI_IDENTIFY) return; // no error means success - - tp = cp; - Map_Bytes(&bmih, &cp, mapBITMAPINFOHEADER); - if (bmih.biSize < sizeof(BITMAPINFOHEADER)) { - cp = tp; - Map_Bytes(&bmch, &cp, mapBITMAPCOREHEADER); - - w = bmch.bcWidth; - h = bmch.bcHeight; - compression = 0; - bitcount = bmch.bcBitCount; - - if (bmch.bcBitCount < 24) - colors = 1 << bmch.bcBitCount; - else - colors = 0; - - if (colors) { - ctab = (RGBQUADPTR)Make_Mem(colors * sizeof(RGBQUAD)); - for (i = 0; idata)) - cp = codi->data + bmfh.bfOffBits; - - codi->w = w; - codi->h = h; - codi->bits = Make_Mem(w * h * 4); - - dp = (REBCNT *) codi->bits; - dp += w * h - w; - - for (y = 0; yrgbRed << 16) | - ((int)color->rgbGreen << 8) | color->rgbBlue; - x >>= 1; - } - i = (w+7) / 8; - break; - - case 4: - for (i = 0; i> 4; - } - else - x = c & 0xf; - if (x > colors) { - codi->error = CODI_ERR_BAD_TABLE; - goto error; - } - color = &ctab[x]; - *dp++ = ((int)color->rgbRed << 16) | - ((int)color->rgbGreen << 8) | color->rgbBlue; - } - i = (w+1) / 2; - break; - - case 8: - for (i = 0; i colors) { - codi->error = CODI_ERR_BAD_TABLE; - goto error; - } - color = &ctab[c]; - *dp++ = ((int)color->rgbRed << 16) | - ((int)color->rgbGreen << 8) | color->rgbBlue; - } - break; - - case 24: - for (i = 0; ierror = CODI_ERR_BIT_LEN; - goto error; - } - while (i++ % 4) - cp++; - break; - - case BI_RLE4: - i = 0; - for (;;) { - c = *cp++ & 0xff; - - if (c == 0) { - c = *cp++ & 0xff; - if (c == 0 || c == 1) - break; - if (c == 2) { - codi->error = CODI_ERR_BAD_TABLE; - goto error; - } - for (j = 0; j>4]; - } - else - color = &ctab[x&0x0f]; - *dp++ = ((int)color->rgbRed << 16) | - ((int)color->rgbGreen << 8) | color->rgbBlue; - } - j = (c+1) / 2; - while (j++%2) - cp++; - } - else { - x = *cp++ & 0xff; - for (j = 0; jerror = CODI_ERR_BAD_TABLE; - goto error; - } - if (j&1) - color = &ctab[x&0x0f]; - else - color = &ctab[x>>4]; - *dp++ = ((int)color->rgbRed << 16) | - ((int)color->rgbGreen << 8) | color->rgbBlue; - } - } - } - break; - - case BI_RLE8: - i = 0; - for (;;) { - c = *cp++ & 0xff; - - if (c == 0) { - c = *cp++ & 0xff; - if (c == 0 || c == 1) - break; - if (c == 2) { - codi->error = CODI_ERR_BAD_TABLE; - goto error; - } - for (j = 0; jrgbRed << 16) | - ((int)color->rgbGreen << 8) | color->rgbBlue; - } - while (j++ % 2) - cp++; - } - else { - x = *cp++ & 0xff; - for (j = 0; jrgbRed << 16) | - ((int)color->rgbGreen << 8) | color->rgbBlue; - } - } - } - break; - - default: - codi->error = CODI_ERR_ENCODING; - goto error; - } - dp -= 2 * w; - } -error: - if (ctab) free(ctab); -} - - -/*********************************************************************** -** -*/ static void Encode_BMP_Image(REBCDI *codi) -/* -** Input: Image bits (codi->bits, w, h) -** Output: BMP encoded image (codi->data, len) -** Error: Code in codi->error -** Return: Success as TRUE or FALSE -** -***********************************************************************/ -{ - REBINT i, y; - REBINT w, h; - REBYTE *cp; - REBCNT *dp, v; - BITMAPFILEHEADER bmfh; - BITMAPINFOHEADER bmih; - - w = codi->w; - h = codi->h; - - memset(&bmfh, 0, sizeof(bmfh)); - bmfh.bfType[0] = 'B'; - bmfh.bfType[1] = 'M'; - bmfh.bfSize = 14 + 40 + h * WADJUST(w); - bmfh.bfOffBits = 14 + 40; - - // Create binary string: - cp = codi->data = Make_Mem(bmfh.bfSize); - codi->len = bmfh.bfSize; - Unmap_Bytes(&bmfh, &cp, mapBITMAPFILEHEADER); - - memset(&bmih, 0, sizeof(bmih)); - bmih.biSize = 40; - bmih.biWidth = w; - bmih.biHeight = h; - bmih.biPlanes = 1; - bmih.biBitCount = 24; - bmih.biCompression = 0; - bmih.biSizeImage = 0; - bmih.biXPelsPerMeter = 0; - bmih.biYPelsPerMeter = 0; - bmih.biClrUsed = 0; - bmih.biClrImportant = 0; - Unmap_Bytes(&bmih, &cp, mapBITMAPINFOHEADER); - - dp = (REBCNT *) codi->bits; - dp += w * h - w; - - for (y = 0; y> 8) & 0xff; - cp[2] = (v >> 16) & 0xff; - cp += 3; - } - i = w * 3; - while (i++ % 4) - *cp++ = 0; - dp -= 2 * w; - } -} - - -/*********************************************************************** -** -*/ REBINT Codec_BMP_Image(REBCDI *codi) -/* -***********************************************************************/ -{ - codi->error = 0; - - if (codi->action == CODI_IDENTIFY) { - Decode_BMP_Image(codi); - return CODI_CHECK; // error code is inverted result - } - - if (codi->action == CODI_DECODE) { - Decode_BMP_Image(codi); - return CODI_IMAGE; - } - - if (codi->action == CODI_ENCODE) { - Encode_BMP_Image(codi); - return CODI_BINARY; - } - - codi->error = CODI_ERR_NA; - return CODI_ERROR; -} - - -/*********************************************************************** -** -*/ void Init_BMP_Codec(void) -/* -***********************************************************************/ -{ - Register_Codec("bmp", Codec_BMP_Image); -} diff --git a/src/core/u-compress.c b/src/core/u-compress.c index 0f3bd35b02..74180f622a 100644 --- a/src/core/u-compress.c +++ b/src/core/u-compress.c @@ -1,137 +1,426 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: u-compress.c -** Summary: interface to zlib compression -** Section: utility -** Notes: -** -***********************************************************************/ +// +// File: %u-compress.c +// Summary: "interface to zlib compression" +// Section: utility +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The Rebol executable includes a version of zlib which has been extracted +// from the GitHub archive and pared down into a single .h and .c file. +// This wraps that functionality into functions that compress and decompress +// BINARY! REBSERs. +// +// Classically, Rebol added a 32-bit size header onto the front of compressed +// data, indicating the uncompressed size. This is the default BINARY! format +// returned by COMPRESS. However, it only used a 32-bit number...gzip also +// includes the length modulo 32. This means that if the data is < 4MB in +// size you can use the length with gzip: +// +// http://stackoverflow.com/a/9213826/211160 +// +// Options are offered for using zlib envelope, gzip envelope, or raw deflate. +// +// !!! Technically zlib is designed to do "streaming" compression. Those +// features are not exposed by this interface, although they are implemented +// in the zlib code. +// #include "sys-core.h" #include "sys-zlib.h" -/* - * This number represents the top file size that, - * if the data is random, will produce a larger output - * file than input. The number is really a bit smaller - * but we like to be safe. -- SN - */ -#define STERLINGS_MAGIC_NUMBER 10000 - -/* - * This number represents the largest that a small file that expands - * on compression can expand. The actual value is closer to - * 500 bytes but why take chances? -- SN - */ -#define STERLINGS_MAGIC_FIX 1024 - -/* - * The why_compress_constant is here to satisfy the condition that - * somebody might just try compressing some big file that is already well - * compressed (or expands for some other wild reason). So we allocate - * a compression buffer a bit larger than the original file size. - * 10% is overkill for really large files so some other limit might - * be a good idea. -*/ -#define WHY_COMPRESS_CONSTANT 0.1 - -/*********************************************************************** -** -*/ REBSER *Compress(REBSER *input, REBINT index, REBINT len, REBFLG use_crc) -/* -** Compress a binary (only). -** data -** /part -** length -** /crc32 -** -** Note: If the file length is "small", it can't overrun on -** compression too much so we use our magic numbers; otherwise, -** we'll just be safe by a percentage of the file size. This may -** be a bit much, though. -** -***********************************************************************/ + +// +// REBCNT_To_Bytes: C +// +// Get endian-independent encoding of a 32-bit unsigned integer to 4 bytes +// +static void REBCNT_To_Bytes(REBYTE *out, REBCNT in) +{ + assert(sizeof(REBCNT) == 4); + out[0] = cast(REBYTE, in); + out[1] = cast(REBYTE, in >> 8); + out[2] = cast(REBYTE, in >> 16); + out[3] = cast(REBYTE, in >> 24); +} + + +// +// Bytes_To_REBCNT: C +// +// Decode endian-independent sequence of 4 bytes back into a 32-bit unsigned +// +static REBCNT Bytes_To_REBCNT(const REBYTE * const in) { - REBCNT size; - REBSER *output; - REBINT err; - REBYTE out_size[4]; - - if (len < 0) Trap0(RE_PAST_END); // !!! better msg needed - size = len + (len > STERLINGS_MAGIC_NUMBER ? len / 10 + 12 : STERLINGS_MAGIC_FIX); - output = Make_Binary(size); - - //DISABLE_GC; // !!! why?? - // dest, dest-len, src, src-len, level - err = Z_compress2(BIN_HEAD(output), (uLongf*)&size, BIN_HEAD(input) + index, len, use_crc); - if (err) { - if (err == Z_MEM_ERROR) Trap0(RE_NO_MEMORY); - SET_INTEGER(DS_RETURN, err); - Trap1(RE_BAD_PRESS, DS_RETURN); //!!!provide error string descriptions - } - SET_STR_END(output, size); - SERIES_TAIL(output) = size; - Long_To_Bytes(out_size, (REBCNT)len); // Tag the size to the end. - Append_Series(output, (REBYTE*)out_size, 4); - if (SERIES_AVAIL(output) > 1024) // Is there wasted space? - output = Copy_Series(output); // Trim it down if too big. !!! Revisit this based on mem alloc alg. - //ENABLE_GC; - - return output; + assert(sizeof(REBCNT) == 4); + return cast(REBCNT, in[0]) + | cast(REBCNT, in[1] << 8) + | cast(REBCNT, in[2] << 16) + | cast(REBCNT, in[3] << 24); } -/*********************************************************************** -** -*/ REBSER *Decompress(REBSER *input, REBCNT index, REBINT len, REBCNT limit, REBFLG use_crc) -/* -** Decompress a binary (only). -** -***********************************************************************/ +// +// Zlib has these magic unnamed bit flags which are passed as windowBits: +// +// "windowBits can also be greater than 15 for optional gzip +// decoding. Add 32 to windowBits to enable zlib and gzip +// decoding with automatic header detection, or add 16 to +// decode only the gzip format (the zlib format will return +// a Z_DATA_ERROR)." +// +// Compression obviously can't read your mind to decide what kind you want, +// but decompression can discern non-raw zlib vs. gzip. It might be useful +// to still be "strict" and demand you to know which kind you have in your +// hand, to make a dependency on gzip explicit (in case you're looking for +// that and want to see if you could use a lighter build without it...) +// +static const int window_bits_zlib = MAX_WBITS; +static const int window_bits_gzip = MAX_WBITS | 16; // "+ 16" +static const int window_bits_detect_zlib_gzip = MAX_WBITS | 32; // "+ 32" +static const int window_bits_zlib_raw = -(MAX_WBITS); +static const int window_bits_gzip_raw = -(MAX_WBITS | 16); // "raw gzip" ?! + + +// +// Error_Compression: C +// +// Zlib gives back string error messages. We use them or fall +// back on the integer code if there is no message. +// +static REBCTX *Error_Compression(const z_stream *strm, int ret) { - REBCNT size; - REBSER *output; - REBINT err; - - if (len < 0 || (index + len > BIN_LEN(input))) len = BIN_LEN(input) - index; - - // Get the size from the end and make the output buffer that size. - if (len <= 4) Trap0(RE_PAST_END); // !!! better msg needed - size = Bytes_To_Long(BIN_SKIP(input, len) - 4); - - if (limit && size > limit) Trap_Num(RE_SIZE_LIMIT, size); - - output = Make_Binary(size); - - //DISABLE_GC; - err = Z_uncompress(BIN_HEAD(output), (uLongf*)&size, BIN_HEAD(input) + index, len, use_crc); - if (err) { - if (PG_Boot_Phase < 2) return 0; - if (err == Z_MEM_ERROR) Trap0(RE_NO_MEMORY); - SET_INTEGER(DS_RETURN, err); - Trap1(RE_BAD_PRESS, DS_RETURN); //!!!provide error string descriptions - } - SET_STR_END(output, size); - SERIES_TAIL(output) = size; - //ENABLE_GC; - return output; + if (ret == Z_MEM_ERROR) { + // + // We do not technically know the amount of memory that zlib asked + // for and did not get. Hence categorizing it as an "out of memory" + // error might be less useful than leaving as a compression error, + // but that is what the old code here historically did. + // + fail (Error_No_Memory(0)); + } + + DECLARE_LOCAL (arg); + if (strm->msg != NULL) + Init_String(arg, Make_UTF8_May_Fail(strm->msg)); + else + Init_Integer(arg, ret); + + return Error_Bad_Compression_Raw(arg); +} + + +// +// Compress: C +// +// !!! Adds 32-bit size info to zlib non-raw compressions for compatibility +// with Rebol2 and R3-Alpha, at the cost of inventing yet-another-format. +// Consider removing. +// +REBSER *Compress( + REBSER *input, + REBINT index, + REBCNT len, + REBOOL gzip, + REBOOL raw +) { + int ret; + + assert(BYTE_SIZE(input)); // must be BINARY! + + // compression level can be a value from 1 to 9, or Z_DEFAULT_COMPRESSION + // if you want it to pick what the library author considers the "worth it" + // tradeoff of time to generally suggest. + // + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + // Should there be detection? (This suppresses unused const warning.) + // + UNUSED(window_bits_detect_zlib_gzip); + + ret = deflateInit2( + &strm, + Z_DEFAULT_COMPRESSION, + Z_DEFLATED, + raw + ? (gzip ? window_bits_gzip_raw : window_bits_zlib_raw) + : (gzip ? window_bits_gzip : window_bits_zlib), + 8, + Z_DEFAULT_STRATEGY + ); + + if (ret != Z_OK) + fail (Error_Compression(&strm, ret)); + + // http://stackoverflow.com/a/4938401/211160 + // + REBCNT buf_size = deflateBound(&strm, len); + + strm.avail_in = len; + strm.next_in = BIN_HEAD(input) + index; + + REBSER *output = Make_Binary(buf_size); + strm.avail_out = buf_size; + strm.next_out = BIN_HEAD(output); + + ret = deflate(&strm, Z_FINISH); + deflateEnd(&strm); + + if (ret != Z_STREAM_END) + fail (Error_Compression(&strm, ret)); + + TERM_BIN_LEN(output, buf_size - strm.avail_out); + + if (gzip) { + #if !defined(NDEBUG) + // + // GZIP contains its own CRC. It also has a 32-bit uncompressed + // length, conveniently (and perhaps confusingly) at the tail in the + // same format that R3-Alpha and Rebol2 used. + + REBCNT gzip_len = Bytes_To_REBCNT( + SER_DATA_RAW(output) + + buf_size + - strm.avail_out + - sizeof(REBCNT) + ); + assert(len == gzip_len); + #endif + } + else if (!raw) { + // + // Add 32-bit length to the end. + // + // !!! In ZLIB format the length can be found by decompressing, but + // not known a priori. So this is for efficiency. It would likely be + // better to not include this as it only confuses matters for those + // expecting the data to be in a known format...though it means that + // clients who wanted to decompress to a known allocation size would + // have to save the size somewhere. + // + REBYTE out_size[sizeof(REBCNT)]; + REBCNT_To_Bytes(out_size, cast(REBCNT, len)); + Append_Series(output, cast(REBYTE*, out_size), sizeof(REBCNT)); + } + + // !!! Trim if more than 1K extra capacity, review logic + // + if (SER_AVAIL(output) > 1024) { + REBSER *smaller = Copy_Sequence(output); + Free_Series(output); + output = smaller; + } + + return output; +} + + +// +// Decompress: C +// +REBSER *Decompress( + const REBYTE *input, + REBCNT len, + REBINT max, + REBOOL gzip, + REBOOL raw +) { + int ret; + + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.total_out = 0; + + // We only subtract out the double-checking size if this came from a + // zlib compression without /ONLY. + // + strm.avail_in = (!raw && !gzip) ? len - sizeof(REBCNT) : len; + strm.next_in = input; + + // !!! Zlib can detect decompression...use window_bits_detect_zlib_gzip? + // + ret = inflateInit2( + &strm, + raw + ? (gzip ? window_bits_gzip_raw : window_bits_zlib_raw) + : (gzip ? window_bits_gzip : window_bits_zlib) + ); + if (ret != Z_OK) + fail (Error_Compression(&strm, ret)); + + // Zlib internally allocates state which must be freed, and is not series + // memory. *But* the following code is a mixture of Zlib code and Rebol + // code (e.g. Extend_Series may run out of memory). If any error is + // raised, a longjmp skips `inflateEnd()` and the Zlib state is leaked, + // ruining the pristine Valgrind output. + // + // Since we do the trap anyway, this is the way we handle explicit errors + // called in the code below also. + // + struct Reb_State state; + REBCTX *error; + + PUSH_UNHALTABLE_TRAP(&error, &state); + +// The first time through the following code 'error' will be NULL, but... +// `fail` can longjmp here, so 'error' won't be NULL *if* that happens! + + if (error) { + // + // output will already have been freed + // + inflateEnd(&strm); + fail (error); + } + + REBCNT buf_size; + if (gzip || !raw) { + // + // Both gzip and Rebol's envelope have the size living in the last + // 4 bytes of the payload. + // + assert(sizeof(REBCNT) == 4); + if (len <= sizeof(REBCNT)) { + // !!! Better error message needed + fail (Error_Past_End_Raw()); + } + buf_size = Bytes_To_REBCNT(input + len - sizeof(REBCNT)); + + // If we know the size is too big go ahead and report an error + // before doing the buffer allocation + // + if (max >= 0 && buf_size > cast(REBCNT, max)) { + DECLARE_LOCAL (temp); + Init_Integer(temp, max); + + // NOTE: You can hit this if you 'make prep' without doing a full + // rebuild. 'make clean' and build again, it should go away. + // + fail (Error_Size_Limit_Raw(temp)); + } + } + else { + // We need some logic for dealing with guessing the size of a zlib + // compression when there's no header. There is no way a priori to + // know what that size will be: + // + // http://stackoverflow.com/q/929757/211160 + // + // If the user's pass in for the "max" seems in the ballpark of a + // compression ratio (as opposed to some egregious large number) + // then use it, because often that will be the exact size. + // + // If the guess is wrong, then the decompression has to keep making + // a bigger buffer and trying to continue. Better heuristics welcome. + + // "Typical zlib compression ratios are from 1:2 to 1:5" + + if (max >= 0 && (cast(REBCNT, max) < len * 6)) + buf_size = max; + else + buf_size = len * 3; + } + + // Since the initialization succeeded, go ahead and make the output buffer + // + REBSER *output = Make_Binary(buf_size); + strm.avail_out = buf_size; + strm.next_out = BIN_HEAD(output); + + // Loop through and allocate a larger buffer each time we find the + // decompression did not run to completion. Stop if we exceed max. + // + while (TRUE) { + + // Perform the inflation + // + ret = inflate(&strm, Z_NO_FLUSH); + + if (ret == Z_STREAM_END) { + // + // Finished with the buffer being big enough... + // + break; + } + + if (ret != Z_OK) + fail (Error_Compression(&strm, ret)); + + // Still more data to come. Use remaining data amount to guess + // size to add. + // + REBCNT old_size = buf_size; + + if (max >= 0 && buf_size >= cast(REBCNT, max)) { + DECLARE_LOCAL (temp); + Init_Integer(temp, max); + + // NOTE: You can hit this on 'make prep' without doing a full + // rebuild. 'make clean' and build again, it should go away. + // + fail (Error_Size_Limit_Raw(temp)); + } + + buf_size = buf_size + strm.avail_in * 3; + if (max >= 0 && buf_size > cast(REBCNT, max)) + buf_size = max; + + assert(strm.avail_out == 0); // !!! is this guaranteed? + assert( + strm.next_out == BIN_HEAD(output) + old_size - strm.avail_out + ); + + Extend_Series(output, buf_size - old_size); + + // Extending keeps the content but may realloc the pointer, so + // put it at the same spot to keep writing to + // + strm.next_out = BIN_HEAD(output) + old_size - strm.avail_out; + + strm.avail_out += buf_size - old_size; + } + + TERM_BIN_LEN(output, strm.total_out); + + // !!! Trim if more than 1K extra capacity, review logic + // + if (SER_AVAIL(output) > 1024) { + REBSER *smaller = Copy_Sequence(output); + Free_Series(output); + output = smaller; + } + + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + + // Make this the last thing done so strm variables can be read up to end + // + inflateEnd(&strm); + + return output; } diff --git a/src/core/u-dialect.c b/src/core/u-dialect.c deleted file mode 100644 index eba3e6dfa6..0000000000 --- a/src/core/u-dialect.c +++ /dev/null @@ -1,548 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: u-dialect.c -** Summary: support for dialecting -** Section: utility -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ - -#include "sys-core.h" -#include "reb-dialect.h" - -typedef struct Reb_Dialect_Parse { - REBSER *dialect; // dialect object - REBSER *fargs; // formal arg block - REBCNT fargi; // start index in fargs - REBSER *args; // argument block - REBCNT argi; // current arg index - REBINT cmd; // command id - REBINT len; // limit of current command - REBSER *out; // result block - REBINT outi; // result block index - REBINT flags; - REBINT missed; // counter of arg misses - REBVAL *contexts; // contexts to search for variables -} REBDIA; - -enum { - RDIA_NO_CMD, // do not store command in block - RDIA_LIT_CMD, // 'command - RDIA_ALL, // all commands, do not reset output -}; - -static REBINT Delect_Debug = 0; -static REBINT Total_Missed = 0; -static char *Dia_Fmt = "DELECT - cmd: %s length: %d missed: %d total: %d"; - - -/*********************************************************************** -** -*/ static int Find_Command(REBSER *dialect, REBVAL *word) -/* -** Given a word, check to see if it is in the dialect object. -** If so, return its index. If not, return 0. -** -***********************************************************************/ -{ - REBINT n; - - if (dialect == VAL_WORD_FRAME(word)) n = VAL_WORD_INDEX(word); - else { - if (NZ(n = Find_Word_Index(dialect, VAL_WORD_SYM(word), FALSE))) { - VAL_WORD_FRAME(word) = dialect; - VAL_WORD_INDEX(word) = n; - } - else return 0; - } - - // If keyword (not command) return negated index: - if (IS_NONE(FRM_VALUES(dialect) + n)) return -n; - return n; -} - - -/*********************************************************************** -** -*/ static int Count_Dia_Args(REBVAL *args) -/* -** Return number of formal args provided to the function. -** This is just a guess, because * repeats count as zero. -** -***********************************************************************/ -{ - REBINT n = 0; - - for (; NOT_END(args); args++) { - if (IS_WORD(args)) { - if (VAL_WORD_SYM(args) == SYM__P) { // skip: * type - if (NOT_END(args+1)) args++; - } else n++; - } - else if (IS_DATATYPE(args) || IS_TYPESET(args)) n++; - } - return n; -} - - -/*********************************************************************** -** -*/ static REBVAL *Eval_Arg(REBDIA *dia) -/* -** Handle all values passed in a dialect. -** -** Contexts can be used for finding a word in a block of -** contexts without using a path. -** -** Returns zero on error. -** Note: stack used to hold temp values -** -***********************************************************************/ -{ - REBVAL *value = BLK_SKIP(dia->args, dia->argi); - - switch (VAL_TYPE(value)) { - - case REB_WORD: - // Only look it up if not part of dialect: - if (Find_Command(dia->dialect, value) == 0) { - REBVAL *val = value; // save - if (dia->contexts) { - value = Find_In_Contexts(VAL_WORD_CANON(value), dia->contexts); - if (value) break; - } - value = Get_Var_No_Trap(val); // may return zero - } - break; - - case REB_PATH: - if (Do_Path(&value, 0)) return 0; - value = DS_TOP; - break; - - case REB_LIT_WORD: - DS_PUSH(value); - value = DS_TOP; - VAL_SET(value, REB_WORD); - break; - - case REB_PAREN: - value = DO_BLK(value); - DS_SKIP; // do not overwrite TOS - break; - } - - return value; -} - - -/*********************************************************************** -** -*/ static REBINT Add_Arg(REBDIA *dia, REBVAL *value) -/* -** Add an actual argument to the output block. -** -** Note that the argument may be out sequence with the formal -** arguments so we must scan for a slot that matches. -** -** Returns: -** 1: arg matches a formal arg and has been stored -** 0: no arg of that type was found -** -N: error (type block contains a bad value) -** -***********************************************************************/ -{ - REBINT type = 0; - REBINT accept = 0; - REBVAL *fargs; - REBINT fargi; - REBVAL *outp; - REBINT rept = 0; - - outp = BLK_SKIP(dia->out, dia->outi); - - // Scan all formal args, looking for one that matches given value: - for (fargi = dia->fargi;; fargi++) { - - //Debug_Fmt("Add_Arg fargi: %d outi: %d", fargi, outi); - - if (IS_END(fargs = BLK_SKIP(dia->fargs, fargi))) return 0; - -again: - // Formal arg can be a word (type or refinement), datatype, or * (repeater): - if (IS_WORD(fargs)) { - - // If word is a datatype name: - type = VAL_WORD_CANON(fargs); - if (type < REB_MAX) { - type--; // the type id - } - else if (type == SYM__P) { - // repeat: * integer! - rept = 1; - fargs++; - goto again; - } - else { - // typeset or refinement - REBVAL *temp; - - type = -1; - - // Is it a refinement word? - if (IS_WORD(value) && VAL_WORD_CANON(fargs) == VAL_WORD_CANON(value)) { - accept = 4; - } - // Is it a typeset? - else if (NZ(temp = Get_Var_No_Trap(fargs)) && IS_TYPESET(temp)) { - if (TYPE_CHECK(temp, VAL_TYPE(value))) accept = 1; - } - else if (!IS_WORD(value)) return 0; // do not search past a refinement - //else return -REB_DIALECT_BAD_SPEC; - } - } - // It's been reduced and is an actual datatype or typeset: - else if (IS_DATATYPE(fargs)) { - type = VAL_DATATYPE(fargs); - } - else if (IS_TYPESET(fargs)) { - if (TYPE_CHECK(fargs, VAL_TYPE(value))) accept = 1; - } else - return -REB_DIALECT_BAD_SPEC; - - // Make room for it in the output block: - if (IS_END(outp)) - outp = Append_Value(dia->out); - else if (!IS_NONE(outp)) { - // There's already an arg in this slot, so skip it... - if (dia->cmd > 1) outp++; - if (!rept) continue; // see if there's another farg that will work for it - // Look for first empty slot: - while (NOT_END(outp) && !IS_NONE(outp)) outp++; - if (IS_END(outp)) outp = Append_Value(dia->out); - } - - // The datatype was correct from above! - if (accept) break; - - //Debug_Fmt("want: %d got: %d rept: %d", type, VAL_TYPE(value), rept); - - // Direct match to datatype or to integer/decimal coersions: - if (type == (REBINT)VAL_TYPE(value)) { - accept = 1; - break; - } - else if (type == REB_INTEGER && IS_DECIMAL(value)) { - accept = 2; - break; - } - else if (type == REB_DECIMAL && IS_INTEGER(value)) { - accept = 3; - break; - } - - dia->missed++; // for debugging - - // Repeat did not match, so stop repeating and remove unused output slot: - if (rept) { - Remove_Last(dia->out); - outp--; - rept = 0; - continue; - } - - if (dia->cmd > 1) outp++; // skip output slot (for non-default values) - } - - // Process the result: - switch (accept) { - - case 1: - *outp = *value; - break; - - case 2: - SET_INTEGER(outp, (REBI64)VAL_DECIMAL(value)); - break; - - case 3: - SET_DECIMAL(outp, (REBDEC)VAL_INT64(value)); - break; - - case 4: // refinement: - dia->fargi = fargs - BLK_HEAD(dia->fargs) + 1; - dia->outi = outp - BLK_HEAD(dia->out) + 1; - *outp = *value; - return 1; - - case 0: - return 0; - } - - // Optimization: arg was in correct order: - if (!rept && fargi == (signed)(dia->fargi)) { - dia->fargi++; - dia->outi++; - } - - return 1; -} - - -/*********************************************************************** -** -*/ static REBINT Do_Cmd(REBDIA *dia) -/* -** Returns the length of command processed or error. See below. -** -***********************************************************************/ -{ - REBVAL *fargs; - REBINT size; - REBVAL *val; - REBINT err; - REBINT n; - - // Get formal arguments block for this command: - fargs = FRM_VALUES(dia->dialect) + dia->cmd; - if (!IS_BLOCK(fargs)) return -REB_DIALECT_BAD_SPEC; - dia->fargs = VAL_SERIES(fargs); - fargs = VAL_BLK_DATA(fargs); - size = Count_Dia_Args(fargs); // approximate - - // Preallocate output block (optimize for large blocks): - if (dia->len > size) size = dia->len; - if (GET_FLAG(dia->flags, RDIA_ALL)) { - Extend_Series(dia->out, size+1); - } - else { - Resize_Series(dia->out, size+1); // tail = 0 - CLEAR_SERIES(dia->out); // Be sure it is entirely cleared - } - - // Insert command word: - if (!GET_FLAG(dia->flags, RDIA_NO_CMD)) { - val = Append_Value(dia->out); - Set_Word(val, FRM_WORD_SYM(dia->dialect, dia->cmd), dia->dialect, dia->cmd); - if (GET_FLAG(dia->flags, RDIA_LIT_CMD)) VAL_SET(val, REB_LIT_WORD); - dia->outi++; - size++; - } - if (dia->cmd > 1) dia->argi++; // default cmd has no word arg - - // Foreach argument provided: - for (n = dia->len; n > 0; n--, dia->argi++) { - val = Eval_Arg(dia); - if (!val) - return -REB_DIALECT_BAD_ARG; - if (IS_END(val)) break; - if (!IS_NONE(val)) { - //Print("n %d len %d argi %d", n, dia->len, dia->argi); - err = Add_Arg(dia, val); // 1: good, 0: no-type, -N: error - if (err == 0) return n; // remainder - if (err < 0) return err; - } - } - - // If not enough args, pad with NONE values: - if (dia->cmd > 1) { - for (n = SERIES_TAIL(dia->out); n < size; n++) { - Append_Value(dia->out); - } - } - - dia->outi = SERIES_TAIL(dia->out); - - return 0; -} - - -/*********************************************************************** -** -*/ static REBINT Do_Dia(REBDIA *dia) -/* -** Process the next command in the dialect. -** Returns the length of command processed. -** Zero indicates end of block. -** Negative indicate error. -** The args holds resulting args. -** -***********************************************************************/ -{ - REBVAL *next = BLK_SKIP(dia->args, dia->argi); - REBVAL *head; - REBINT err; - - if (IS_END(next)) return 0; - - // Find the command if a word is provided: - if (IS_WORD(next) || IS_LIT_WORD(next)) { - if (IS_LIT_WORD(next)) SET_FLAG(dia->flags, RDIA_LIT_CMD); - dia->cmd = Find_Command(dia->dialect, next); - } - - // Handle defaults - process values before a command is reached: - if (dia->cmd <= 1) { - dia->cmd = 1; - dia->len = 1; - err = Do_Cmd(dia); // DEFAULT cmd - // It must be processed, else it is not in the dialect. - // Check for noop result: - if (err > 0) err = -REB_DIALECT_BAD_ARG; - return err; - } - - // Delimit the command - search for next command or end: - for (head = ++next; NOT_END(next); next++) { - if ((IS_WORD(next) || IS_LIT_WORD(next)) && Find_Command(dia->dialect, next) > 1) break; - } - - // Note: command may be shorter than length provided here (defaults): - dia->len = next - head; // length of args, not including command - err = Do_Cmd(dia); - if (GET_FLAG(dia->flags, RDIA_LIT_CMD)) dia->cmd += DIALECT_LIT_CMD; - return err; -} - - -/*********************************************************************** -** -*/ REBINT Do_Dialect(REBSER *dialect, REBSER *block, REBCNT *index, REBSER **out) -/* -** Format for dialect is: -** CMD arg1 arg2 arg3 CMD arg1 arg2 ... -** -** Returns: -** cmd value or error as result (or zero for end) -** index is updated -** if *out is zero, then we create a new output block -** -** The arg sequence is terminated by: -** 1. Maximum # of args for command -** 2. An arg that is not of a specified datatype for CMD -** 3. Encountering a new CMD -** 4. End of the dialect block -** -***********************************************************************/ -{ - REBDIA dia; - REBINT n; - REBINT dsp = DSP; // Save stack position - - CLEARS(&dia); - - if (*index >= SERIES_TAIL(block)) return 0; // end of block - - DISABLE_GC; // Avoid GC during Dialect (prevents unknown crash problem) - - if (!*out) *out = Make_Block(25); - - dia.dialect = dialect; - dia.args = block; - dia.argi = *index; - dia.out = *out; - SET_FLAG(dia.flags, RDIA_NO_CMD); - - //Print("DSP: %d Dinp: %r - %m", DSP, BLK_SKIP(block, *index), block); - n = Do_Dia(&dia); - //Print("DSP: %d Dout: CMD: %d %m", DSP, dia.cmd, *out); - DSP = dsp; // pop any temp values used above - - if (Delect_Debug > 0) { - Total_Missed += dia.missed; - // !!!! debug - if (dia.missed) Debug_Fmt(Dia_Fmt, Get_Field_Name(dia.dialect, dia.cmd), dia.out->tail, dia.missed, Total_Missed); - } - - if (n < 0) return n; //error - *index = dia.argi; - - ENABLE_GC; - - return dia.cmd; -} - - -/*********************************************************************** -** -*/ REBNATIVE(delect) -/* -***********************************************************************/ -{ - REBDIA dia; - REBINT err; - REBFLG all; - - CLEARS(&dia); - - dia.dialect = VAL_OBJ_FRAME(D_ARG(1)); - dia.args = VAL_SERIES(D_ARG(2)); - dia.argi = VAL_INDEX(D_ARG(2)); - dia.out = VAL_SERIES(D_ARG(3)); - dia.outi = VAL_INDEX(D_ARG(3)); - - if (dia.argi >= SERIES_TAIL(dia.args)) return R_NONE; // end of block - - if (D_REF(4)) { // in - if (!IS_BLOCK(dia.contexts = D_ARG(5))) Trap_Arg(dia.contexts); - dia.contexts = VAL_BLK_DATA(dia.contexts); - } - - if (NZ(all = D_REF(6))) { - SET_FLAG(dia.flags, RDIA_ALL); - Resize_Series(dia.out, VAL_LEN(D_ARG(2))); - while (TRUE) { - dia.cmd = 0; - dia.len = 0; - dia.fargi = 0; - err = Do_Dia(&dia); - //Debug_Fmt("Ret: %d argi: %d outi: %d len: %d", err, dia.argi, dia.outi, dia.len); - if (err < 0 || IS_END(BLK_SKIP(dia.args, dia.argi))) break; - } - } - else - err = Do_Dia(&dia); - - DS_RELOAD(ds); - - VAL_INDEX(D_ARG(2)) = MIN(dia.argi, SERIES_TAIL(dia.args)); - - if (Delect_Debug > 0) { - Total_Missed += dia.missed; - if (dia.missed) Debug_Fmt(Dia_Fmt, Get_Field_Name(dia.dialect, dia.cmd), dia.out->tail, dia.missed, Total_Missed); - } - - if (err < 0) Trap_Arg(D_ARG(2)); // !!! needs better error - - return R_ARG2; -} - - -/*********************************************************************** -** -*/ void Trace_Delect(REBINT level) -/* -***********************************************************************/ -{ - Delect_Debug = level; -} diff --git a/src/core/u-gif.c b/src/core/u-gif.c deleted file mode 100644 index fda8b01ea0..0000000000 --- a/src/core/u-gif.c +++ /dev/null @@ -1,347 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: u-gif.c -** Summary: GIF image format conversion -** Section: utility -** Notes: -** This is an optional part of R3. This file can be replaced by -** library function calls into an updated implementation. -** -***********************************************************************/ - -#include "sys-core.h" - - -#define MAX_STACK_SIZE 4096 -#define NULL_CODE (-1) -#define BitSet(byte,bit) (((byte) & (bit)) == (bit)) -#define LSBFirstOrder(x,y) (((y) << 8) | (x)) - -static REBINT interlace_rate[4] = { 8, 8, 4, 2 }, - interlace_start[4] = { 0, 4, 2, 1 }; - - -#ifdef COMP_IMAGES -// Because graphics.c is not included, we must have a copy here. -void Chrom_Key_Alpha(REBVAL *v,REBCNT col,REBINT blitmode) { - REBOOL found=FALSE; - int i; - REBCNT *p; - - p=(REBCNT *)VAL_IMAGE_HEAD(v); - i=VAL_IMAGE_WIDTH(v)*VAL_IMAGE_HEIGHT(v); - switch(blitmode) { - case BLIT_MODE_COLOR: - for(;i>0;i--,p++) { - if(*p==col) { - found=TRUE; - *p=col|0xff000000; - } - } - case BLIT_MODE_LUMA: - for(;i>0;i--,p++) { - if(BRIGHT(((REBRGB *)p))<=col) { - found=TRUE; - *p|=0xff000000; - } - } - break; - } - if(found) - VAL_IMAGE_TRANSP(v)=VITT_ALPHA; -} -#endif - -/*********************************************************************** -** -*/ void Decode_LZW(REBCNT *data, REBYTE **cpp, REBYTE *colortab, REBINT w, REBINT h, REBOOL interlaced) -/* -** Perform LZW decompression. -** -***********************************************************************/ -{ - REBYTE *cp = *cpp; - REBYTE *rp; - REBINT available, clear, code_mask, code_size, end_of_info, in_code; - REBINT old_code, bits, code, count, x, y, data_size, row, i; - REBCNT *dp, datum; - short *prefix; - REBYTE first, *pixel_stack, *suffix, *top_stack; - - suffix = Make_Mem(MAX_STACK_SIZE * (sizeof(REBYTE) + sizeof(REBYTE) + sizeof(short))); - pixel_stack = suffix + MAX_STACK_SIZE; - prefix = (short *)(pixel_stack + MAX_STACK_SIZE); - - data_size = *cp++; - clear = 1 << data_size; - end_of_info = clear + 1; - available = clear + 2; - old_code = NULL_CODE; - code_size = data_size + 1; - code_mask = (1 << code_size) - 1; - - for (code=0; code>= code_size; - bits -= code_size; - - // sanity check - if (code > available || code == end_of_info) - break; - // time to reset the tables - if (code == clear) { - code_size = data_size + 1; - code_mask = (1 << code_size) - 1; - available = clear + 2; - old_code = NULL_CODE; - continue; - } - // if we are the first code, just stack it - if (old_code == NULL_CODE) { - *top_stack++ = suffix[code]; - old_code = code; - first = code; - continue; - } - in_code = code; - if (code == available) { - *top_stack++ = first; - code = old_code; - } - while (code > clear) { - *top_stack++ = suffix[code]; - code = prefix[code]; - } - first = suffix[code]; - - // add a new string to the table - if (available >= MAX_STACK_SIZE) - break; - *top_stack++ = first; - prefix[available] = old_code; - suffix[available++] = first; - if ((available & code_mask) == 0 && available < MAX_STACK_SIZE) { - code_size++; - code_mask += available; - } - old_code = in_code; - } - top_stack--; - rp = colortab + 3 * *top_stack; - *dp++ = rp[2] | (rp[1] << 8) | (rp[0] << 16); - x++; - } - if (interlaced) { - row += interlace_rate[i]; - if (row >= h) { - row = interlace_start[++i]; - } - dp = data + row * w; - } - } - *cpp = cp + count + 1; - Free_Mem(suffix, MAX_STACK_SIZE * (sizeof(REBYTE) + sizeof(REBYTE) + sizeof(short))); -} - - -/*********************************************************************** -** -*/ void Decode_GIF_Image(REBCDI *codi) -/* -** Input: GIF encoded image (codi->data, len) -** Output: Image bits (codi->bits, w, h) -** Error: Code in codi->error -** Return: Success as TRUE or FALSE -** -***********************************************************************/ -{ - REBINT w, h; - REBINT transparency_index; - REBYTE c, *global_colormap, *colormap; - REBCNT global_colors, image_count, local_colormap; - REBCNT colors; - REBYTE *cp; - REBCNT *dp; - REBOOL interlaced; - REBYTE *end; - - cp = codi->data; - end = codi->data + codi->len; - - if (strncmp((char *)cp, "GIF87", 5) != 0 && strncmp((char *)cp, "GIF89", 5) != 0) { - codi->error = CODI_ERR_SIGNATURE; - return; - } - if (codi->action == CODI_IDENTIFY) return; // no error means success - - global_colors = 0; - global_colormap = (unsigned char *) NULL; - if (cp[10] & 0x80) { - // Read global colormap. - global_colors = 1 << ((cp[10] & 0x07) + 1); - global_colormap = cp + 13; - cp += global_colors * 3; - } - cp += 13; - transparency_index = -1; - image_count = 0; - for (;;) { - if (cp >= end) break; - c = *cp++; - - if (c == ';') - break; // terminator - - if (c == '!') { - // GIF Extension block. - c = *cp++; - switch (c) { - case 0xf9: - // Transparency extension block. - while (cp[0] != 0 && cp[5] != 0) - cp += 5; - if ((cp[1] & 0x01) == 1) - transparency_index = cp[4]; - cp += cp[0] + 1 + 1; - break; - - default: - while (cp[0] != 0) - cp += cp[0] + 1; - cp++; - break; - } - } - - if (c != ',') continue; - - image_count++; - interlaced = (cp[8] & 0x40) != 0; - local_colormap = cp[8] & 0x80; - - w = LSBFirstOrder(cp[4],cp[5]); - h = LSBFirstOrder(cp[6],cp[7]); - // if(w * h * 4 > VAL_STR_LEN(img)) - // h = 4 * VAL_STR_LEN(img) / w; - - // Inititialize colormap. - colors = !local_colormap ? global_colors : 1 << ((cp[8] & 0x07)+1); - if (!local_colormap) { - colormap = global_colormap; - } - else { - colormap = cp + 9; - cp += 3 * colors; - } - cp += 9; - -/* - if (image_count == 2) { - VAL_SERIES(Temp2_Value) = Make_Block(0, 0); - VAL_INIT(Temp2_Value, REB_BLOCK); - VAL_INDEX(Temp2_Value) = 0; - Append_Series(VAL_SERIES(Temp2_Value), (REBMEM *)Temp_Value, 1); - } -*/ - dp = codi->bits = (u32 *)Make_Mem(w * h * 4); - codi->w = w; - codi->h = h; - - Decode_LZW(dp, &cp, colormap, w, h, interlaced); - - if(transparency_index >= 0) { - int ADD_alpha_key_detection; - REBYTE *p=colormap+3*transparency_index; - ///Chroma_Key_Alpha(Temp_Value, (REBCNT)(p[2]|(p[1]<<8)|(p[0]<<16)), BLIT_MODE_COLOR); - } - -// if (image_count == 1) -// *Temp2_Value = *Temp_Value; -// else -// Append_Series(VAL_SERIES(Temp2_Value), (REBMEM *)Temp_Value, 1); - } -} - - -/*********************************************************************** -** -*/ REBINT Codec_GIF_Image(REBCDI *codi) -/* -***********************************************************************/ -{ - codi->error = 0; - - if (codi->action == CODI_IDENTIFY) { - Decode_GIF_Image(codi); - return CODI_CHECK; // error code is inverted result - } - - if (codi->action == CODI_DECODE) { - Decode_GIF_Image(codi); - return CODI_IMAGE; - } - - codi->error = CODI_ERR_NA; - return CODI_ERROR; -} - - -/*********************************************************************** -** -*/ void Init_GIF_Codec(void) -/* -***********************************************************************/ -{ - Register_Codec("gif", Codec_GIF_Image); -} diff --git a/src/core/u-md5.c b/src/core/u-md5.c index 958e8707b5..73675819d6 100644 --- a/src/core/u-md5.c +++ b/src/core/u-md5.c @@ -1,126 +1,127 @@ #include "sys-core.h" #include -#include +//#include // !!! No in Ren-C release builds #include #define MD5_DEFINED -#define MD5_CBLOCK 64 -#define MD5_LBLOCK 16 -#define MD5_BLOCK 16 +#define MD5_CBLOCK 64 +#define MD5_LBLOCK 16 +#define MD5_BLOCK 16 #define MD5_LAST_BLOCK 56 #define MD5_LENGTH_BLOCK 8 #define MD5_DIGEST_LENGTH 16 +#define MD5_LONG u32 + typedef struct MD5state_st { - unsigned long A,B,C,D; - unsigned long Nl,Nh; - unsigned long data[MD5_LBLOCK]; - int num; + MD5_LONG A,B,C,D; + MD5_LONG Nl,Nh; + MD5_LONG data[MD5_LBLOCK]; + int num; } MD5_CTX; -void MD5_Init(MD5_CTX *c); -void MD5_Update(MD5_CTX *c, unsigned char *data, unsigned long len); -void MD5_Final(unsigned char *md, MD5_CTX *c); -int MD5_CtxSize(void); -unsigned char *MD5(unsigned char *d, unsigned long n, unsigned char *md); +EXTERN_C void MD5_Init(MD5_CTX *c); +EXTERN_C void MD5_Update(MD5_CTX *c, unsigned char *data, MD5_LONG len); +EXTERN_C void MD5_Final(unsigned char *md, MD5_CTX *c); +EXTERN_C int MD5_CtxSize(void); -#define ULONG unsigned long -#define UCHAR unsigned char -#define UINT unsigned int +#define ULONG MD5_LONG +#define UCHAR unsigned char +#define UINT unsigned int #undef c2l -#define c2l(c,l) (l = ((unsigned long)(*((c)++))) , \ - l|=(((unsigned long)(*((c)++)))<< 8), \ - l|=(((unsigned long)(*((c)++)))<<16), \ - l|=(((unsigned long)(*((c)++)))<<24)) +#define c2l(c,l) (l = ((MD5_LONG)(*((c)++))) , \ + l|=(((MD5_LONG)(*((c)++)))<< 8), \ + l|=(((MD5_LONG)(*((c)++)))<<16), \ + l|=(((MD5_LONG)(*((c)++)))<<24)) #undef p_c2l -#define p_c2l(c,l,n) { \ - switch (n) { \ - case 0: l =((unsigned long)(*((c)++))); \ - case 1: l|=((unsigned long)(*((c)++)))<< 8; \ - case 2: l|=((unsigned long)(*((c)++)))<<16; \ - case 3: l|=((unsigned long)(*((c)++)))<<24; \ - } \ - } +#define p_c2l(c,l,n) { \ + switch (n) { \ + case 0: l =((MD5_LONG)(*((c)++))); \ + case 1: l|=((MD5_LONG)(*((c)++)))<< 8; \ + case 2: l|=((MD5_LONG)(*((c)++)))<<16; \ + case 3: l|=((MD5_LONG)(*((c)++)))<<24; \ + } \ + } /* NOTE the pointer is not incremented at the end of this */ #undef c2l_p -#define c2l_p(c,l,n) { \ - l=0; \ - (c)+=n; \ - switch (n) { \ - case 3: l =((unsigned long)(*(--(c))))<<16; \ - case 2: l|=((unsigned long)(*(--(c))))<< 8; \ - case 1: l|=((unsigned long)(*(--(c)))) ; \ - } \ - } +#define c2l_p(c,l,n) { \ + l=0; \ + (c)+=n; \ + switch (n) { \ + case 3: l =((MD5_LONG)(*(--(c))))<<16; \ + case 2: l|=((MD5_LONG)(*(--(c))))<< 8; \ + case 1: l|=((MD5_LONG)(*(--(c)))) ; \ + } \ + } #undef p_c2l_p #define p_c2l_p(c,l,sc,len) { \ - switch (sc) \ - { \ - case 0: l =((unsigned long)(*((c)++))); \ - if (--len == 0) break; \ - case 1: l|=((unsigned long)(*((c)++)))<< 8; \ - if (--len == 0) break; \ - case 2: l|=((unsigned long)(*((c)++)))<<16; \ - } \ - } + switch (sc) \ + { \ + case 0: l =((MD5_LONG)(*((c)++))); \ + if (--len == 0) break; \ + case 1: l|=((MD5_LONG)(*((c)++)))<< 8; \ + if (--len == 0) break; \ + case 2: l|=((MD5_LONG)(*((c)++)))<<16; \ + } \ + } #undef l2c -#define l2c(l,c) (*((c)++)=(unsigned char)(((l) )&0xff), \ - *((c)++)=(unsigned char)(((l)>> 8)&0xff), \ - *((c)++)=(unsigned char)(((l)>>16)&0xff), \ - *((c)++)=(unsigned char)(((l)>>24)&0xff)) +#define l2c(l,c) (*((c)++)=(unsigned char)(((l) )&0xff), \ + *((c)++)=(unsigned char)(((l)>> 8)&0xff), \ + *((c)++)=(unsigned char)(((l)>>16)&0xff), \ + *((c)++)=(unsigned char)(((l)>>24)&0xff)) /* NOTE - c is not incremented as per l2c */ #undef l2cn -#define l2cn(l1,l2,c,n) { \ - c+=n; \ - switch (n) { \ - case 8: *(--(c))=(unsigned char)(((l2)>>24)&0xff); \ - case 7: *(--(c))=(unsigned char)(((l2)>>16)&0xff); \ - case 6: *(--(c))=(unsigned char)(((l2)>> 8)&0xff); \ - case 5: *(--(c))=(unsigned char)(((l2) )&0xff); \ - case 4: *(--(c))=(unsigned char)(((l1)>>24)&0xff); \ - case 3: *(--(c))=(unsigned char)(((l1)>>16)&0xff); \ - case 2: *(--(c))=(unsigned char)(((l1)>> 8)&0xff); \ - case 1: *(--(c))=(unsigned char)(((l1) )&0xff); \ - } \ - } +#define l2cn(l1,l2,c,n) { \ + c+=n; \ + switch (n) { \ + case 8: *(--(c))=(unsigned char)(((l2)>>24)&0xff); \ + case 7: *(--(c))=(unsigned char)(((l2)>>16)&0xff); \ + case 6: *(--(c))=(unsigned char)(((l2)>> 8)&0xff); \ + case 5: *(--(c))=(unsigned char)(((l2) )&0xff); \ + case 4: *(--(c))=(unsigned char)(((l1)>>24)&0xff); \ + case 3: *(--(c))=(unsigned char)(((l1)>>16)&0xff); \ + case 2: *(--(c))=(unsigned char)(((l1)>> 8)&0xff); \ + case 1: *(--(c))=(unsigned char)(((l1) )&0xff); \ + } \ + } /* A nice byte order reversal from Wei Dai */ #if defined(WIN32) /* 5 instructions with rotate instruction, else 9 */ #define Endian_Reverse32(a) \ - { \ - unsigned long l=(a); \ - (a)=((ROTATE(l,8)&0x00FF00FF)|(ROTATE(l,24)&0xFF00FF00)); \ - } + { \ + MD5_LONG l=(a); \ + (a)=((ROTATE(l,8)&0x00FF00FF)|(ROTATE(l,24)&0xFF00FF00)); \ + } #else /* 6 instructions with rotate instruction, else 8 */ #define Endian_Reverse32(a) \ - { \ - unsigned long l=(a); \ - l=(((l&0xFF00FF00)>>8L)|((l&0x00FF00FF)<<8L)); \ - (a)=ROTATE(l,16L); \ - } + { \ + MD5_LONG l=(a); \ + l=(((l&0xFF00FF00)>>8L)|((l&0x00FF00FF)<<8L)); \ + (a)=ROTATE(l,16L); \ + } #endif /* -#define F(x,y,z) (((x) & (y)) | ((~(x)) & (z))) -#define G(x,y,z) (((x) & (z)) | ((y) & (~(z)))) +#define F(x,y,z) (((x) & (y)) | ((~(x)) & (z))) +#define G(x,y,z) (((x) & (z)) | ((y) & (~(z)))) */ /* As pointed out by Wei Dai , the above can be * simplified to the code below. Wei attributes these optimisations * to Peter Gutmann's SHS code, and he attributes it to Rich Schroeppel. */ -#define F(x,y,z) ((((y) ^ (z)) & (x)) ^ (z)) -#define G(x,y,z) ((((x) ^ (y)) & (z)) ^ (y)) -#define H(x,y,z) ((x) ^ (y) ^ (z)) -#define I(x,y,z) (((x) | (~(z))) ^ (y)) +#define F(x,y,z) ((((y) ^ (z)) & (x)) ^ (z)) +#define G(x,y,z) ((((x) ^ (y)) & (z)) ^ (y)) +#define H(x,y,z) ((x) ^ (y) ^ (z)) +#define I(x,y,z) (((x) | (~(z))) ^ (y)) #undef ROTATE #if defined(WIN32) @@ -131,275 +132,278 @@ unsigned char *MD5(unsigned char *d, unsigned long n, unsigned char *md); #define R0(a,b,c,d,k,s,t) { \ - a+=((k)+(t)+F((b),(c),(d))); \ - a=ROTATE(a,s); \ - a+=b; };\ + a+=((k)+(t)+F((b),(c),(d))); \ + a=ROTATE(a,s); \ + a+=b; };\ #define R1(a,b,c,d,k,s,t) { \ - a+=((k)+(t)+G((b),(c),(d))); \ - a=ROTATE(a,s); \ - a+=b; }; + a+=((k)+(t)+G((b),(c),(d))); \ + a=ROTATE(a,s); \ + a+=b; }; #define R2(a,b,c,d,k,s,t) { \ - a+=((k)+(t)+H((b),(c),(d))); \ - a=ROTATE(a,s); \ - a+=b; }; + a+=((k)+(t)+H((b),(c),(d))); \ + a=ROTATE(a,s); \ + a+=b; }; #define R3(a,b,c,d,k,s,t) { \ - a+=((k)+(t)+I((b),(c),(d))); \ - a=ROTATE(a,s); \ - a+=b; }; + a+=((k)+(t)+I((b),(c),(d))); \ + a=ROTATE(a,s); \ + a+=b; }; -#define INIT_DATA_A (unsigned long)0x67452301L -#define INIT_DATA_B (unsigned long)0xefcdab89L -#define INIT_DATA_C (unsigned long)0x98badcfeL -#define INIT_DATA_D (unsigned long)0x10325476L +#define INIT_DATA_A (MD5_LONG)0x67452301L +#define INIT_DATA_B (MD5_LONG)0xefcdab89L +#define INIT_DATA_C (MD5_LONG)0x98badcfeL +#define INIT_DATA_D (MD5_LONG)0x10325476L -static void md5_block(MD5_CTX *c, unsigned long *p); +static void md5_block(MD5_CTX *c, MD5_LONG *p); void MD5_Init(MD5_CTX *c) { - c->A=INIT_DATA_A; - c->B=INIT_DATA_B; - c->C=INIT_DATA_C; - c->D=INIT_DATA_D; - c->Nl=0; - c->Nh=0; - c->num=0; + c->A=INIT_DATA_A; + c->B=INIT_DATA_B; + c->C=INIT_DATA_C; + c->D=INIT_DATA_D; + c->Nl=0; + c->Nh=0; + c->num=0; } -void MD5_Update(MD5_CTX *c, register unsigned char *data, unsigned long len) +void MD5_Update(MD5_CTX *c, unsigned char *data, MD5_LONG len) { - register ULONG *p; - int sw,sc; - ULONG l; - - if (len == 0) return; - - l=(c->Nl+(len<<3))&0xffffffffL; - /* 95-05-24 eay Fixed a bug with the overflow handling, thanks to - * Wei Dai for pointing it out. */ - if (l < c->Nl) /* overflow */ - c->Nh++; - c->Nh+=(len>>29); - c->Nl=l; - - if (c->num != 0) { - p=c->data; - sw=c->num>>2; - sc=c->num&0x03; - - if ((c->num+len) >= MD5_CBLOCK) { - l= p[sw]; - p_c2l(data,l,sc); - p[sw++]=l; - for (; swnum); - - md5_block(c,p); - c->num=0; - /* drop through and do the rest */ - } else { - int ew,ec; - - c->num+=(int)len; - if ((sc+len) < 4) { /* ugly, add char's to a word */ - l= p[sw]; - p_c2l_p(data,l,sc,len); - p[sw]=l; - } else { - ew=(c->num>>2); - ec=(c->num&0x03); - l= p[sw]; - p_c2l(data,l,sc); - p[sw++]=l; - for (; sw < ew; sw++) - { c2l(data,l); p[sw]=l; } - if (ec) { - c2l_p(data,l,ec); - p[sw]=l; - } - } - return; - } - } - /* we now can process the input data in blocks of MD5_CBLOCK - * chars and save the leftovers to c->data. */ - p=c->data; - while (len >= MD5_CBLOCK) { - memcpy(p,data,MD5_CBLOCK); - data+=MD5_CBLOCK; + ULONG *p; + int sw,sc; + ULONG l; + + if (len == 0) return; + + l=(c->Nl+(len<<3))&0xffffffffL; + /* 95-05-24 eay Fixed a bug with the overflow handling, thanks to + * Wei Dai for pointing it out. */ + if (l < c->Nl) /* overflow */ + c->Nh++; + c->Nh+=(len>>29); + c->Nl=l; + + if (c->num != 0) { + p=c->data; + sw=c->num>>2; + sc=c->num&0x03; + + if ((c->num+len) >= MD5_CBLOCK) { + l= p[sw]; + p_c2l(data,l,sc); + p[sw++]=l; + for (; swnum); + + md5_block(c,p); + c->num=0; + /* drop through and do the rest */ + } else { + int ew,ec; + + c->num+=(int)len; + if ((sc+len) < 4) { /* ugly, add char's to a word */ + l= p[sw]; + p_c2l_p(data,l,sc,len); + p[sw]=l; + } else { + ew=(c->num>>2); + ec=(c->num&0x03); + l= p[sw]; + p_c2l(data,l,sc); + p[sw++]=l; + for (; sw < ew; sw++) + { c2l(data,l); p[sw]=l; } + if (ec) { + c2l_p(data,l,ec); + p[sw]=l; + } + } + return; + } + } + /* we now can process the input data in blocks of MD5_CBLOCK + * chars and save the leftovers to c->data. */ + p=c->data; + while (len >= MD5_CBLOCK) { + memcpy(p,data,MD5_CBLOCK); + data+=MD5_CBLOCK; #ifdef ENDIAN_BIG - for (sw=(MD5_LBLOCK/4); sw; sw--) { - Endian_Reverse32(p[0]); - Endian_Reverse32(p[1]); - Endian_Reverse32(p[2]); - Endian_Reverse32(p[3]); - p+=4; - } + for (sw=(MD5_LBLOCK/4); sw; sw--) { + Endian_Reverse32(p[0]); + Endian_Reverse32(p[1]); + Endian_Reverse32(p[2]); + Endian_Reverse32(p[3]); + p+=4; + } #endif - p=c->data; - md5_block(c,p); - len-=MD5_CBLOCK; - } - sc=(int)len; - c->num=sc; - if (sc) { - sw=sc>>2; /* words to copy */ + p=c->data; + md5_block(c,p); + len-=MD5_CBLOCK; + } + sc=(int)len; + c->num=sc; + if (sc) { + sw=sc>>2; /* words to copy */ #ifdef ENDIAN_LITTLE - p[sw]=0; - memcpy(p,data,sc); + p[sw]=0; + memcpy(p,data,sc); #else - sc&=0x03; - for ( ; sw; sw--) - { c2l(data,l); *(p++)=l; } - c2l_p(data,l,sc); - *p=l; + sc&=0x03; + for ( ; sw; sw--) + { c2l(data,l); *(p++)=l; } + c2l_p(data,l,sc); + *p=l; #endif - } + } } -static void md5_block(MD5_CTX *c, register ULONG *X) +static void md5_block(MD5_CTX *c, ULONG *X) { - register ULONG A,B,C,D; - - A=c->A; - B=c->B; - C=c->C; - D=c->D; - - /* Round 0 */ - R0(A,B,C,D,X[ 0], 7,0xd76aa478L); - R0(D,A,B,C,X[ 1],12,0xe8c7b756L); - R0(C,D,A,B,X[ 2],17,0x242070dbL); - R0(B,C,D,A,X[ 3],22,0xc1bdceeeL); - R0(A,B,C,D,X[ 4], 7,0xf57c0fafL); - R0(D,A,B,C,X[ 5],12,0x4787c62aL); - R0(C,D,A,B,X[ 6],17,0xa8304613L); - R0(B,C,D,A,X[ 7],22,0xfd469501L); - R0(A,B,C,D,X[ 8], 7,0x698098d8L); - R0(D,A,B,C,X[ 9],12,0x8b44f7afL); - R0(C,D,A,B,X[10],17,0xffff5bb1L); - R0(B,C,D,A,X[11],22,0x895cd7beL); - R0(A,B,C,D,X[12], 7,0x6b901122L); - R0(D,A,B,C,X[13],12,0xfd987193L); - R0(C,D,A,B,X[14],17,0xa679438eL); - R0(B,C,D,A,X[15],22,0x49b40821L); - /* Round 1 */ - R1(A,B,C,D,X[ 1], 5,0xf61e2562L); - R1(D,A,B,C,X[ 6], 9,0xc040b340L); - R1(C,D,A,B,X[11],14,0x265e5a51L); - R1(B,C,D,A,X[ 0],20,0xe9b6c7aaL); - R1(A,B,C,D,X[ 5], 5,0xd62f105dL); - R1(D,A,B,C,X[10], 9,0x02441453L); - R1(C,D,A,B,X[15],14,0xd8a1e681L); - R1(B,C,D,A,X[ 4],20,0xe7d3fbc8L); - R1(A,B,C,D,X[ 9], 5,0x21e1cde6L); - R1(D,A,B,C,X[14], 9,0xc33707d6L); - R1(C,D,A,B,X[ 3],14,0xf4d50d87L); - R1(B,C,D,A,X[ 8],20,0x455a14edL); - R1(A,B,C,D,X[13], 5,0xa9e3e905L); - R1(D,A,B,C,X[ 2], 9,0xfcefa3f8L); - R1(C,D,A,B,X[ 7],14,0x676f02d9L); - R1(B,C,D,A,X[12],20,0x8d2a4c8aL); - /* Round 2 */ - R2(A,B,C,D,X[ 5], 4,0xfffa3942L); - R2(D,A,B,C,X[ 8],11,0x8771f681L); - R2(C,D,A,B,X[11],16,0x6d9d6122L); - R2(B,C,D,A,X[14],23,0xfde5380cL); - R2(A,B,C,D,X[ 1], 4,0xa4beea44L); - R2(D,A,B,C,X[ 4],11,0x4bdecfa9L); - R2(C,D,A,B,X[ 7],16,0xf6bb4b60L); - R2(B,C,D,A,X[10],23,0xbebfbc70L); - R2(A,B,C,D,X[13], 4,0x289b7ec6L); - R2(D,A,B,C,X[ 0],11,0xeaa127faL); - R2(C,D,A,B,X[ 3],16,0xd4ef3085L); - R2(B,C,D,A,X[ 6],23,0x04881d05L); - R2(A,B,C,D,X[ 9], 4,0xd9d4d039L); - R2(D,A,B,C,X[12],11,0xe6db99e5L); - R2(C,D,A,B,X[15],16,0x1fa27cf8L); - R2(B,C,D,A,X[ 2],23,0xc4ac5665L); - /* Round 3 */ - R3(A,B,C,D,X[ 0], 6,0xf4292244L); - R3(D,A,B,C,X[ 7],10,0x432aff97L); - R3(C,D,A,B,X[14],15,0xab9423a7L); - R3(B,C,D,A,X[ 5],21,0xfc93a039L); - R3(A,B,C,D,X[12], 6,0x655b59c3L); - R3(D,A,B,C,X[ 3],10,0x8f0ccc92L); - R3(C,D,A,B,X[10],15,0xffeff47dL); - R3(B,C,D,A,X[ 1],21,0x85845dd1L); - R3(A,B,C,D,X[ 8], 6,0x6fa87e4fL); - R3(D,A,B,C,X[15],10,0xfe2ce6e0L); - R3(C,D,A,B,X[ 6],15,0xa3014314L); - R3(B,C,D,A,X[13],21,0x4e0811a1L); - R3(A,B,C,D,X[ 4], 6,0xf7537e82L); - R3(D,A,B,C,X[11],10,0xbd3af235L); - R3(C,D,A,B,X[ 2],15,0x2ad7d2bbL); - R3(B,C,D,A,X[ 9],21,0xeb86d391L); - - c->A+=A&0xffffffffL; - c->B+=B&0xffffffffL; - c->C+=C&0xffffffffL; - c->D+=D&0xffffffffL; + ULONG A,B,C,D; + + A=c->A; + B=c->B; + C=c->C; + D=c->D; + + /* Round 0 */ + R0(A,B,C,D,X[ 0], 7,0xd76aa478L); + R0(D,A,B,C,X[ 1],12,0xe8c7b756L); + R0(C,D,A,B,X[ 2],17,0x242070dbL); + R0(B,C,D,A,X[ 3],22,0xc1bdceeeL); + R0(A,B,C,D,X[ 4], 7,0xf57c0fafL); + R0(D,A,B,C,X[ 5],12,0x4787c62aL); + R0(C,D,A,B,X[ 6],17,0xa8304613L); + R0(B,C,D,A,X[ 7],22,0xfd469501L); + R0(A,B,C,D,X[ 8], 7,0x698098d8L); + R0(D,A,B,C,X[ 9],12,0x8b44f7afL); + R0(C,D,A,B,X[10],17,0xffff5bb1L); + R0(B,C,D,A,X[11],22,0x895cd7beL); + R0(A,B,C,D,X[12], 7,0x6b901122L); + R0(D,A,B,C,X[13],12,0xfd987193L); + R0(C,D,A,B,X[14],17,0xa679438eL); + R0(B,C,D,A,X[15],22,0x49b40821L); + /* Round 1 */ + R1(A,B,C,D,X[ 1], 5,0xf61e2562L); + R1(D,A,B,C,X[ 6], 9,0xc040b340L); + R1(C,D,A,B,X[11],14,0x265e5a51L); + R1(B,C,D,A,X[ 0],20,0xe9b6c7aaL); + R1(A,B,C,D,X[ 5], 5,0xd62f105dL); + R1(D,A,B,C,X[10], 9,0x02441453L); + R1(C,D,A,B,X[15],14,0xd8a1e681L); + R1(B,C,D,A,X[ 4],20,0xe7d3fbc8L); + R1(A,B,C,D,X[ 9], 5,0x21e1cde6L); + R1(D,A,B,C,X[14], 9,0xc33707d6L); + R1(C,D,A,B,X[ 3],14,0xf4d50d87L); + R1(B,C,D,A,X[ 8],20,0x455a14edL); + R1(A,B,C,D,X[13], 5,0xa9e3e905L); + R1(D,A,B,C,X[ 2], 9,0xfcefa3f8L); + R1(C,D,A,B,X[ 7],14,0x676f02d9L); + R1(B,C,D,A,X[12],20,0x8d2a4c8aL); + /* Round 2 */ + R2(A,B,C,D,X[ 5], 4,0xfffa3942L); + R2(D,A,B,C,X[ 8],11,0x8771f681L); + R2(C,D,A,B,X[11],16,0x6d9d6122L); + R2(B,C,D,A,X[14],23,0xfde5380cL); + R2(A,B,C,D,X[ 1], 4,0xa4beea44L); + R2(D,A,B,C,X[ 4],11,0x4bdecfa9L); + R2(C,D,A,B,X[ 7],16,0xf6bb4b60L); + R2(B,C,D,A,X[10],23,0xbebfbc70L); + R2(A,B,C,D,X[13], 4,0x289b7ec6L); + R2(D,A,B,C,X[ 0],11,0xeaa127faL); + R2(C,D,A,B,X[ 3],16,0xd4ef3085L); + R2(B,C,D,A,X[ 6],23,0x04881d05L); + R2(A,B,C,D,X[ 9], 4,0xd9d4d039L); + R2(D,A,B,C,X[12],11,0xe6db99e5L); + R2(C,D,A,B,X[15],16,0x1fa27cf8L); + R2(B,C,D,A,X[ 2],23,0xc4ac5665L); + /* Round 3 */ + R3(A,B,C,D,X[ 0], 6,0xf4292244L); + R3(D,A,B,C,X[ 7],10,0x432aff97L); + R3(C,D,A,B,X[14],15,0xab9423a7L); + R3(B,C,D,A,X[ 5],21,0xfc93a039L); + R3(A,B,C,D,X[12], 6,0x655b59c3L); + R3(D,A,B,C,X[ 3],10,0x8f0ccc92L); + R3(C,D,A,B,X[10],15,0xffeff47dL); + R3(B,C,D,A,X[ 1],21,0x85845dd1L); + R3(A,B,C,D,X[ 8], 6,0x6fa87e4fL); + R3(D,A,B,C,X[15],10,0xfe2ce6e0L); + R3(C,D,A,B,X[ 6],15,0xa3014314L); + R3(B,C,D,A,X[13],21,0x4e0811a1L); + R3(A,B,C,D,X[ 4], 6,0xf7537e82L); + R3(D,A,B,C,X[11],10,0xbd3af235L); + R3(C,D,A,B,X[ 2],15,0x2ad7d2bbL); + R3(B,C,D,A,X[ 9],21,0xeb86d391L); + + c->A+=A&0xffffffffL; + c->B+=B&0xffffffffL; + c->C+=C&0xffffffffL; + c->D+=D&0xffffffffL; } void MD5_Final(unsigned char *md, MD5_CTX *c) { - register int i,j; - register ULONG l; - register ULONG *p; - static unsigned char end[4]={0x80,0x00,0x00,0x00}; - unsigned char *cp=end; - - /* c->num should definitly have room for at least one more byte. */ - p=c->data; - j=c->num; - i=j>>2; - - l=p[i]; - p_c2l(cp,l,j&0x03); - p[i]=l; - i++; - /* i is the next 'undefined word' */ - if (c->num >= MD5_LAST_BLOCK) { - for (; iNl; - p[MD5_LBLOCK-1]=c->Nh; - md5_block(c,p); - cp=md; - l=c->A; l2c(l,cp); - l=c->B; l2c(l,cp); - l=c->C; l2c(l,cp); - l=c->D; l2c(l,cp); - - /* clear stuff, md5_block may be leaving some stuff on the stack - * but I'm not worried :-) */ - c->num=0; -/* memset((char *)&c,0,sizeof(c));*/ + int i,j; + ULONG l; + ULONG *p; + static unsigned char end[4]={0x80,0x00,0x00,0x00}; + unsigned char *cp=end; + + /* c->num should definitly have room for at least one more byte. */ + p=c->data; + j=c->num; + i=j>>2; + + l=p[i]; + p_c2l(cp,l,j&0x03); + p[i]=l; + i++; + /* i is the next 'undefined word' */ + if (c->num >= MD5_LAST_BLOCK) { + for (; iNl; + p[MD5_LBLOCK-1]=c->Nh; + md5_block(c,p); + cp=md; + l=c->A; l2c(l,cp); + l=c->B; l2c(l,cp); + l=c->C; l2c(l,cp); + l=c->D; l2c(l,cp); + + /* clear stuff, md5_block may be leaving some stuff on the stack + * but I'm not worried :-) */ + c->num=0; +/* memset(&c,0,sizeof(c));*/ } int MD5_CtxSize(void) { - return sizeof(MD5_CTX); + return sizeof(MD5_CTX); } -unsigned char *MD5(unsigned char *d, unsigned long n, unsigned char *md) +// +// MD5: C +// +REBYTE *MD5(REBYTE *d, REBCNT n, REBYTE *md) { - MD5_CTX c; - static unsigned char m[MD5_DIGEST_LENGTH]; - - if (!md) md=m; - MD5_Init(&c); - MD5_Update(&c,d,n); - MD5_Final(md,&c); - memset(&c,0,sizeof(c)); /* security consideration */ - return(md); + MD5_CTX c; + static unsigned char m[MD5_DIGEST_LENGTH]; + + if (!md) md=m; + MD5_Init(&c); + MD5_Update(&c,d,n); + MD5_Final(md,&c); + memset(&c,0,sizeof(c)); /* security consideration */ + return(md); } diff --git a/src/core/u-parse.c b/src/core/u-parse.c old mode 100644 new mode 100755 index a7c61a9230..9ed5650b75 --- a/src/core/u-parse.c +++ b/src/core/u-parse.c @@ -1,1320 +1,2334 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: u-parse.c -** Summary: parse dialect interpreter -** Section: utility -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %u-parse.c +// Summary: "parse dialect interpreter" +// Section: utility +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// As a major operational difference from R3-Alpha, each recursion in Ren-C's +// PARSE runs using a "Rebol Stack Frame"--similar to how the DO evaluator +// works. So `[print "abc"]` and `[thru "abc"]` are both seen as "code" and +// iterated using the same mechanic. (The rules are also locked from +// modification during the course of the PARSE, as code is in Ren-C.) +// +// This leverages common services like reporting the start of the last +// "expression" that caused an error. So merely calling `fail()` will use +// the call stack to properly indicate the start of the parse rule that caused +// a problem. But most importantly, debuggers can break in and see the +// state at every step in the parse rule recursions. +// +// The function users see on the stack for each recursion is a native called +// SUBPARSE. Although it is shaped similarly to typical DO code, there are +// differences. The subparse advances the "current evaluation position" in +// the frame as it operates, so it is a variadic function...with the rules as +// the variadic parameter. Calling it directly looks a bit unusual: +// +// >> flags: 0 +// >> subparse "aabb" flags some "a" some "b" +// == 4 +// +// But as far as a debugging tool is concerned, the "where" of each frame +// in the call stack is what you would expect. +// +// !!! The PARSE code in R3-Alpha had gone through significant churn, and +// had a number of cautionary remarks and calls for review. During Ren-C +// development, several edge cases emerged about interactions with the +// garbage collector or throw mechanics...regarding responsibility for +// temporary values or other issues. The code has become more clear in many +// ways, though it is also more complex due to the frame mechanics...and is +// under ongoing cleanup as time permits. +// #include "sys-core.h" -#include "sys-state.h" -// Parser flags: -enum Parse_Flags { - PF_ALL = 1, - PF_CASE = 2, - PF_CASED = 4, // was set as initial option -}; -typedef struct reb_parse { - REBSER *series; - REBCNT type; - REBCNT flags; - REBINT result; - REBVAL retval; -} REBPARSE; +// +// These macros are used to address into the frame directly to get the +// current parse rule, current input series, current parse position in that +// input series, etc. Because the bits inside the frame arguments are +// modified as the parse runs, that means users can see the effects at +// a breakpoint. +// +// (Note: when arguments to natives are viewed under the debugger, the +// debug frames are read only. So it's not possible for the user to change +// the ANY_SERIES! of the current parse position sitting in slot 0 into +// a DECIMAL! and crash the parse, for instance. They are able to change +// usermode authored function arguments only.) +// + +#define P_RULE (f->value + 0) // rvalue, don't change pointer +#define P_RULE_SPECIFIER (f->specifier + 0) // rvalue, don't change pointer + +#define P_INPUT_VALUE (&f->args_head[0]) +#define P_TYPE VAL_TYPE(P_INPUT_VALUE) +#define P_INPUT VAL_SERIES(P_INPUT_VALUE) +#define P_INPUT_SPECIFIER VAL_SPECIFIER(P_INPUT_VALUE) +#define P_POS VAL_INDEX(P_INPUT_VALUE) + +#define P_FIND_FLAGS VAL_INT64(&f->args_head[1]) +#define P_HAS_CASE LOGICAL(P_FIND_FLAGS & AM_FIND_CASE) + +#define P_OUT (f->out) + +#define P_CELL (&f->cell) + +#define FETCH_NEXT_RULE_MAYBE_END(f) \ + Fetch_Next_In_Frame(f) + +#define FETCH_TO_BAR_MAYBE_END(f) \ + while (NOT_END(P_RULE) && !IS_BAR(P_RULE)) \ + { FETCH_NEXT_RULE_MAYBE_END(f); } + +// +// See the notes on `flags` in the main parse loop for how these work. +// enum parse_flags { - PF_SET_OR_COPY, // test PF_COPY first; if false, this means PF_SET - PF_COPY, - PF_NOT, - PF_NOT2, - PF_THEN, - PF_AND, - PF_REMOVE, - PF_INSERT, - PF_CHANGE, - PF_RETURN, - PF_WHILE, + PF_SET = 1 << 0, + PF_COPY = 1 << 1, + PF_NOT = 1 << 2, + PF_NOT2 = 1 << 3, + PF_THEN = 1 << 4, + PF_AND = 1 << 5, + PF_REMOVE = 1 << 6, + PF_INSERT = 1 << 7, + PF_CHANGE = 1 << 8, + PF_RETURN = 1 << 9, + PF_WHILE = 1 << 10 }; -#define MAX_PARSE_DEPTH 512 -// Returns SYMBOL or 0 if not a command: -#define GET_CMD(n) (((n) >= SYM_OR_BAR && (n) <= SYM_END) ? (n) : 0) -#define VAL_CMD(v) GET_CMD(VAL_WORD_CANON(v)) -#define HAS_CASE(p) (p->flags & AM_FIND_CASE) -#define IS_OR_BAR(v) (IS_WORD(v) && VAL_WORD_CANON(v) == SYM_OR_BAR) -#define SKIP_TO_BAR(r) while (NOT_END(r) && !IS_SAME_WORD(r, SYM_OR_BAR)) r++; -#define IS_BLOCK_INPUT(p) (p->type >= REB_BLOCK) +// In %words.r, the parse words are lined up in order so they can be quickly +// filtered, skipping the need for a switch statement if something is not +// a parse command. +// +// !!! This and other efficiency tricks from R3-Alpha should be reviewed to +// see if they're really the best option. +// +inline static REBSYM VAL_CMD(const RELVAL *v) { + REBSYM sym = VAL_WORD_SYM(v); + if (sym >= SYM_SET && sym <= SYM_END) + return sym; + return SYM_0; +} -static REBCNT Parse_Rules_Loop(REBPARSE *parse, REBCNT index, REBVAL *rules, REBCNT depth); -void Print_Parse_Index(REBCNT type, REBVAL *rules, REBSER *series, REBCNT index) -{ - REBVAL val; - Set_Series(type, &val, series); - VAL_INDEX(&val) = index; - Debug_Fmt("%r: %r", rules, &val); +// Subparse_Throws is a helper that sets up a call frame and invokes the +// SUBPARSE native--which represents one level of PARSE recursion. +// +// !!! It is the intent of Ren-C that calling functions be light and fast +// enough through Do_Va() and other mechanisms that a custom frame constructor +// like this one would not be needed. Data should be gathered on how true +// it's possible to make that. +// +// !!! Calling subparse creates another recursion. This recursion means +// that there are new arguments and a new frame spare cell. Callers do not +// evaluate directly into their output slot at this time (except the top +// level parse), because most of them are framed to return other values. +// +static REBOOL Subparse_Throws( + REBOOL *interrupted_out, + REBVAL *out, + RELVAL *input, + REBSPC *input_specifier, + const RELVAL *rules, + REBSPC *rules_specifier, + REBCNT find_flags +) { + DECLARE_FRAME (f); + + SET_END(out); + + assert(ANY_ARRAY(rules)); + assert(ANY_SERIES(input)); + + // Since SUBPARSE is a native that the user can call directly, and it + // is "effectively variadic" reading its instructions inline out of the + // `where` of execution, it has to handle the case where the frame it + // is given is at an END. + // + // However, as long as this wrapper is testing for ends, rather than + // use that test to create an END state to feed to subparse, it can + // just return. This is because no matter what, empty rules means a match + // with no items advanced. + // + if (VAL_INDEX(rules) >= VAL_LEN_HEAD(rules)) { + *interrupted_out = FALSE; + Init_Integer(out, VAL_INDEX(input)); + return FALSE; + } + + f->out = out; + + SET_FRAME_VALUE(f, VAL_ARRAY_AT(rules)); + f->specifier = Derive_Specifier(rules_specifier, rules); + + f->source.array = VAL_ARRAY(rules); + f->index = VAL_INDEX(rules) + 1; + + f->pending = NULL; + f->gotten = END; + +#if defined(NDEBUG) + f->args_head = Push_Value_Chunk_Of_Length(2); +#else + f->args_head = Push_Value_Chunk_Of_Length(3); // real RETURN: for natives + Init_Void(&f->args_head[2]); +#endif + + f->varlist = NULL; + + Derelativize(&f->args_head[0], input, input_specifier); + + // We always want "case-sensitivity" on binary bytes, vs. treating as + // case-insensitive bytes for ASCII characters. + // + Init_Integer(&f->args_head[1], find_flags); + + f->label = Canon(SYM_SUBPARSE); + f->eval_type = REB_FUNCTION; + f->original = f->phase = NAT_FUNC(subparse); + + Init_Endlike_Header(&f->flags, 0); // implicitly terminate f->cell + + f->param = END; // informs infix lookahead + f->arg = m_cast(REBVAL*, END); + f->refine = m_cast(REBVAL*, END); + f->special = m_cast(REBVAL*, END); + + Push_Frame_Core(f); // checks for C stack overflow + + SET_END(&f->cell); // GC requires some initialization of cell + + REB_R r = N_subparse(f); + + assert(NOT_END(out)); + + // Can't just drop f->data.stackvars because the debugger may have + // "reified" the frame into a FRAME!, which means it would now be using + // the f->data.context field. + // + Drop_Function_Args_For_Frame_Core(f, TRUE); + + Drop_Frame_Core(f); + + if (r == R_OUT_IS_THROWN) { + assert(THROWN(out)); + + // ACCEPT and REJECT are special cases that can happen at nested parse + // levels and bubble up through the throw mechanism to break a looping + // construct. + // + // !!! R3-Alpha didn't react to these instructions in general, only in + // the particular case where subparsing was called inside an iterated + // construct. Even then, it could only break through one level of + // depth. Most places would treat them the same as a normal match + // or not found. This returns the interrupted flag which is still + // ignored by most callers, but makes that fact more apparent. + // + if (IS_FUNCTION(out)) { + if (VAL_FUNC(out) == NAT_FUNC(parse_reject)) { + CATCH_THROWN(out, out); + assert(IS_BLANK(out)); + *interrupted_out = TRUE; + return FALSE; + } + + if (VAL_FUNC(out) == NAT_FUNC(parse_accept)) { + CATCH_THROWN(out, out); + assert(IS_INTEGER(out)); + *interrupted_out = TRUE; + return FALSE; + } + } + + return TRUE; + } + + assert(r == R_OUT); + *interrupted_out = FALSE; + return FALSE; } -/*********************************************************************** -** -*/ static REBCNT Parse_Series(REBVAL *val, REBVAL *rules, REBCNT flags, REBCNT depth) -/* -***********************************************************************/ -{ - REBPARSE parse; +// Very generic error. Used to be parameterized with the parse rule in +// question, but now the `where` at the time of failure will indicate the +// location in the parse dialect that's the problem. +// +static REBCTX *Error_Parse_Rule() { + return Error_Parse_Rule_Raw(); +} - parse.series = VAL_SERIES(val); - parse.type = VAL_TYPE(val); - parse.flags = flags; - parse.result = 0; - return Parse_Rules_Loop(&parse, VAL_INDEX(val), rules, depth); +// Also generic. +// +static REBCTX *Error_Parse_End() { + return Error_Parse_End_Raw(); } -/*********************************************************************** -** -*/ static REBCNT Set_Parse_Series(REBPARSE *parse, REBVAL *item) -/* -** Change the series and return the new index. -** -***********************************************************************/ -{ - parse->series = VAL_SERIES(item); - parse->type = VAL_TYPE(item); - if (IS_BINARY(item) || (parse->flags & PF_CASED)) parse->flags |= PF_CASE; - else parse->flags &= ~PF_CASE; - return (VAL_INDEX(item) > VAL_TAIL(item)) ? VAL_TAIL(item) : VAL_INDEX(item); +static void Print_Parse_Index(REBFRM *f) { + DECLARE_LOCAL (input); + Init_Any_Series_At_Core( + input, + P_TYPE, + P_INPUT, + P_POS, + GET_SER_FLAG(P_INPUT, SERIES_FLAG_ARRAY) + ? P_INPUT_SPECIFIER + : SPECIFIED + ); + + // Either the rules or the data could be positioned at the end. The + // data might even be past the end. + // + // !!! Or does PARSE adjust to ensure it never is past the end, e.g. + // when seeking a position given in a variable or modifying? + // + if (IS_END(P_RULE)) { + if (P_POS >= SER_LEN(P_INPUT)) + Debug_Fmt("[]: ** END **"); + else + Debug_Fmt("[]: %r", input); + } + else { + if (P_POS >= SER_LEN(P_INPUT)) + Debug_Fmt("%r: ** END **", P_RULE); + else + Debug_Fmt("%r: %r", P_RULE, input); + } } -/*********************************************************************** -** -*/ static REBVAL *Get_Parse_Value(REBVAL *item) -/* -** Get the value of a word (when not a command) or path. -** Returns all other values as-is. -** -***********************************************************************/ -{ - if (IS_WORD(item)) { - if (!VAL_CMD(item)) item = Get_Var(item); - } - else if (IS_PATH(item)) { - REBVAL *path = item; - if (Do_Path(&path, 0)) return item; // found a function - item = DS_TOP; - } - return item; +// +// Set_Parse_Series: C +// +// Change the series, ensuring the index is not past the end. +// +static void Set_Parse_Series( + REBFRM *f, + const REBVAL *any_series +) { + Move_Value(&f->args_head[0], any_series); + VAL_INDEX(&f->args_head[0]) = + (VAL_INDEX(any_series) > VAL_LEN_HEAD(any_series)) + ? VAL_LEN_HEAD(any_series) + : VAL_INDEX(any_series); + + if (IS_BINARY(any_series) || (P_FIND_FLAGS & AM_FIND_CASE)) + P_FIND_FLAGS |= AM_FIND_CASE; + else + P_FIND_FLAGS &= ~AM_FIND_CASE; } -/*********************************************************************** -** -*/ static REBVAL *Do_Parse_Path(REBVAL *item, REBPARSE *parse, REBCNT *index) -/* -** Handle a PATH, including get and set, that's found in a rule. -** -***********************************************************************/ -{ - REBVAL *path = item; - REBVAL tmp; - - if (IS_PATH(item)) { - if (Do_Path(&path, 0)) return item; // found a function - item = DS_TOP; - } - else if (IS_SET_PATH(item)) { - Set_Series(parse->type, &tmp, parse->series); - VAL_INDEX(&tmp) = *index; - if (Do_Path(&path, &tmp)) return item; // found a function - return 0; - } - else if (IS_GET_PATH(item)) { - if (Do_Path(&path, 0)) return item; // found a function - item = DS_TOP; - // CureCode #1263 change - // if (parse->type != VAL_TYPE(item) || VAL_SERIES(item) != parse->series) - if (!ANY_SERIES(item)) Trap1(RE_PARSE_SERIES, path); - *index = Set_Parse_Series(parse, item); - return 0; - } - - return item; +// +// Get_Parse_Value: C +// +// Gets the value of a word (when not a command) or path. Returns all other +// values as-is. +// +// !!! Because path evaluation does not necessarily wind up pointing to a +// variable that exists in memory, a derived value may be created. R3-Alpha +// would push these on the stack without any corresponding drops, leading +// to leaks and overflows. This requires you to pass in a cell of storage +// which will be good for as long as the returned pointer is used. It may +// not be used--e.g. with a WORD! fetch. +// +static const RELVAL *Get_Parse_Value( + REBVAL *cell, + const RELVAL *rule, + REBSPC *specifier +) { + if (IS_BAR(rule)) + return rule; + + if (IS_WORD(rule)) { + if (VAL_CMD(rule)) + return rule; + + Copy_Opt_Var_May_Fail(cell, rule, specifier); + if (IS_VOID(cell)) + fail (Error_No_Value_Core(rule, specifier)); + + return cell; + } + + if (IS_PATH(rule)) { + // + // !!! REVIEW: how should GET-PATH! be handled? + + if (Do_Path_Throws_Core(cell, NULL, rule, specifier, NULL)) + fail (Error_No_Catch_For_Throw(cell)); + + if (IS_VOID(cell)) + fail (Error_No_Value_Core(rule, specifier)); + + return cell; + } + + return rule; } -/*********************************************************************** -** -*/ static REBCNT Parse_Next_String(REBPARSE *parse, REBCNT index, REBVAL *item, REBCNT depth) -/* -** Match the next item in the string ruleset. -** -** If it matches, return the index just past it. -** Otherwise return NOT_FOUND. -** -***********************************************************************/ -{ - // !!! THIS CODE NEEDS CLEANUP AND REWRITE BASED ON OTHER CHANGES - REBSER *series = parse->series; - REBSER *ser; - REBCNT flags = parse->flags | AM_FIND_MATCH | AM_FIND_TAIL; - int rewrite_needed; - - if (Trace_Level) { - Trace_Value(7, item); - Trace_String(8, STR_SKIP(series, index), series->tail - index); - } - - if (IS_NONE(item)) return index; - - if (index >= series->tail) return NOT_FOUND; - - switch (VAL_TYPE(item)) { - - // Do we match a single character? - case REB_CHAR: - if (HAS_CASE(parse)) - index = (VAL_CHAR(item) == GET_ANY_CHAR(series, index)) ? index+1 : NOT_FOUND; - else - index = (UP_CASE(VAL_CHAR(item)) == UP_CASE(GET_ANY_CHAR(series, index))) ? index+1 : NOT_FOUND; - break; - - case REB_EMAIL: - case REB_STRING: - case REB_BINARY: - index = Find_Str_Str(series, 0, index, SERIES_TAIL(series), 1, VAL_SERIES(item), VAL_INDEX(item), VAL_LEN(item), flags); - break; - - // Do we match to a char set? - case REB_BITSET: - flags = Check_Bit(VAL_SERIES(item), GET_ANY_CHAR(series, index), !HAS_CASE(parse)); - index = flags ? index + 1 : NOT_FOUND; - break; -/* - case REB_DATATYPE: // Currently: integer! - if (VAL_DATATYPE(item) == REB_INTEGER) { - REBCNT begin = index; - while (IS_LEX_NUMBER(*str)) str++, index++; - if (begin == index) index = NOT_FOUND; - } - break; -*/ - case REB_TAG: - case REB_FILE: -// case REB_ISSUE: - // !! Can be optimized (w/o COPY) - ser = Copy_Form_Value(item, 0); - index = Find_Str_Str(series, 0, index, SERIES_TAIL(series), 1, ser, 0, ser->tail, flags); - break; - - case REB_NONE: - break; - - // Parse a sub-rule block: - case REB_BLOCK: - index = Parse_Rules_Loop(parse, index, VAL_BLK_DATA(item), depth); - break; - - // Do an expression: - case REB_PAREN: - item = Do_Block_Value_Throw(item); // might GC - // old: if (IS_ERROR(item)) Throw_Error(VAL_ERR_OBJECT(item)); - index = MIN(index, series->tail); // may affect tail - break; - - default: - Trap1(RE_PARSE_RULE, item); - } - - return index; +// +// Parse_String_One_Rule: C +// +// Match the next rule in the string ruleset. +// +// If it matches, return the index just past it. +// Otherwise return END_FLAG. +// May also return THROWN_FLAG. +// +static REBIXO Parse_String_One_Rule(REBFRM *f, const RELVAL *rule) { + assert(IS_END(P_OUT)); + + REBCNT flags = P_FIND_FLAGS | AM_FIND_MATCH | AM_FIND_TAIL; + + if (Trace_Level) { + Trace_Value("match", rule); + + // !!! This used STR_AT (obsolete) but it's not clear that this is + // necessarily a byte sized series. Switched to BIN_AT, which will + // assert if it's not BYTE_SIZE() + + Trace_String(BIN_AT(P_INPUT, P_POS), BIN_LEN(P_INPUT) - P_POS); + } + + if (P_POS >= SER_LEN(P_INPUT)) + return END_FLAG; + + switch (VAL_TYPE(rule)) { + case REB_BLANK: + return P_POS; // just ignore blanks + + case REB_CHAR: + // + // Try matching character against current string parse position + // + if (P_HAS_CASE) { + if (VAL_CHAR(rule) == GET_ANY_CHAR(P_INPUT, P_POS)) + return P_POS + 1; + } + else { + if ( + UP_CASE(VAL_CHAR(rule)) + == UP_CASE(GET_ANY_CHAR(P_INPUT, P_POS)) + ) { + return P_POS + 1; + } + } + return END_FLAG; + + case REB_EMAIL: + case REB_STRING: + case REB_BINARY: { + REBCNT index = Find_Str_Str( + P_INPUT, + 0, + P_POS, + SER_LEN(P_INPUT), + 1, + VAL_SERIES(rule), + VAL_INDEX(rule), + VAL_LEN_AT(rule), + flags + ); + if (index == NOT_FOUND) + return END_FLAG; + return index; } + + case REB_TAG: + case REB_FILE: { + // + // !!! The content to be matched does not have the delimiters in the + // actual series data. This FORMs it, but could be more optimized. + // + REBSER *formed = Copy_Form_Value(rule, 0); + REBCNT index = Find_Str_Str( + P_INPUT, + 0, + P_POS, + SER_LEN(P_INPUT), + 1, + formed, + 0, + SER_LEN(formed), + flags + ); + Free_Series(formed); + if (index == NOT_FOUND) + return END_FLAG; + return index; } + + case REB_BITSET: + // + // Check the current character against a character set, advance matches + // + if (Check_Bit( + VAL_SERIES(rule), GET_ANY_CHAR(P_INPUT, P_POS), NOT(P_HAS_CASE) + )) { + return P_POS + 1; + } + return END_FLAG; + + case REB_BLOCK: { + // + // This parses a sub-rule block. It may throw, and it may mutate the + // input series. + // + REBOOL interrupted; + if (Subparse_Throws( + &interrupted, + P_CELL, + P_INPUT_VALUE, + SPECIFIED, + rule, + P_RULE_SPECIFIER, + P_FIND_FLAGS + )) { + Move_Value(P_OUT, P_CELL); + return THROWN_FLAG; + } + + // !!! ignore "interrupted"? (e.g. ACCEPT or REJECT ran) + + if (IS_BLANK(P_CELL)) + return END_FLAG; + + return VAL_INT32(P_CELL); } + + case REB_GROUP: { + // + // This runs a GROUP! as code. It may throw, but won't influence the + // input position...although it can change the input series. :-/ + // If the input series is shortened to make P_POS an invalid position, + // then truncate it to the end of series. + // + DECLARE_LOCAL (dummy); + REBSPC *derived = Derive_Specifier(P_RULE_SPECIFIER, rule); + if (Do_At_Throws( + dummy, + VAL_ARRAY(rule), + VAL_INDEX(rule), + derived + )) { + Move_Value(P_OUT, dummy); + return THROWN_FLAG; + } + return MIN(P_POS, SER_LEN(P_INPUT)); } // !!! review truncation concept + + default: + fail (Error_Parse_Rule()); + } } -/*********************************************************************** -** -*/ static REBCNT Parse_Next_Block(REBPARSE *parse, REBCNT index, REBVAL *item, REBCNT depth) -/* -** Used for parsing blocks to match the next item in the ruleset. -** If it matches, return the index just past it. Otherwise, return zero. -** -***********************************************************************/ -{ - // !!! THIS CODE NEEDS CLEANUP AND REWRITE BASED ON OTHER CHANGES - REBSER *series = parse->series; - REBVAL *blk = BLK_SKIP(series, index); - - if (Trace_Level) { - Trace_Value(7, item); - Trace_Value(8, blk); - } - - switch (VAL_TYPE(item)) { - - // Look for specific datattype: - case REB_DATATYPE: - index++; - if (VAL_TYPE(blk) == (REBYTE)VAL_DATATYPE(item)) break; - goto no_result; - - // Look for a set of datatypes: - case REB_TYPESET: - index++; - if (TYPE_CHECK(item, VAL_TYPE(blk))) break; - goto no_result; - - // 'word - case REB_LIT_WORD: - index++; - if (IS_WORD(blk) && (VAL_WORD_CANON(blk) == VAL_WORD_CANON(item))) break; - goto no_result; - - case REB_LIT_PATH: - index++; - if (IS_PATH(blk) && !Cmp_Block(blk, item, 0)) break; - goto no_result; - - case REB_NONE: - break; - - // Parse a sub-rule block: - case REB_BLOCK: - index = Parse_Rules_Loop(parse, index, VAL_BLK_DATA(item), depth); - break; - - // Do an expression: - case REB_PAREN: - item = Do_Block_Value_Throw(item); // might GC - // old: if (IS_ERROR(item)) Throw_Error(VAL_ERR_OBJECT(item)); - index = MIN(index, series->tail); // may affect tail - break; - - // Match with some other value: - default: - index++; - if (Cmp_Value(blk, item, (REBOOL)HAS_CASE(parse))) goto no_result; - } - - return index; - -no_result: - return NOT_FOUND; +// +// Parse_Array_One_Rule_Core: C +// +// Used for parsing ANY-ARRAY! to match the next rule in the ruleset. If it +// matches, return the index just past it. Otherwise, return zero. +// +// This function is called by To_Thru, and as a result it may need to +// process elements other than the current one in the frame. Hence it +// is parameterized by an arbitrary `pos` instead of assuming the P_POS +// that is held by the frame. +// +// The return result is either an integer, END_FLAG, or THROWN_FLAG +// Only in the case of THROWN_FLAG will f->out (aka P_OUT) be affected. +// Otherwise, it should exit the routine as an END marker (as it started); +// +static REBIXO Parse_Array_One_Rule_Core( + REBFRM *f, + REBCNT pos, + const RELVAL *rule +) { + assert(IS_END(P_OUT)); + + REBARR *array = ARR(P_INPUT); + RELVAL *item = ARR_AT(array, pos); + + if (Trace_Level) { + Trace_Value("input", rule); + if (IS_END(item)) { + const char *end_str = "** END **"; + Trace_String(cb_cast(end_str), strlen(end_str)); + } + else + Trace_Value("match", item); + } + + if (IS_END(item)) { + // + // Only the BLANK and BLOCK rules can potentially handle an END input + // For instance, `parse [] [[[_ _ _]]]` should be able to match. + // The other cases would assert if fed an END marker as item. + // + if (!IS_BLANK(rule) && !IS_BLOCK(rule)) + return END_FLAG; + } + + switch (VAL_TYPE(rule)) { + case REB_BLANK: + return pos; // blank rules "match" but don't affect the parse position + + case REB_DATATYPE: + if (VAL_TYPE(item) == VAL_TYPE_KIND(rule)) // specific datatype match + return pos + 1; + return END_FLAG; + + case REB_TYPESET: + if (TYPE_CHECK(rule, VAL_TYPE(item))) // type was found in the typeset + return pos + 1; + return END_FLAG; + + case REB_LIT_WORD: + if (IS_WORD(item) && (VAL_WORD_CANON(item) == VAL_WORD_CANON(rule))) + return pos + 1; + return END_FLAG; + + case REB_LIT_PATH: + if (IS_PATH(item) && Cmp_Array(item, rule, FALSE) == 0) + return pos + 1; + return END_FLAG; + + case REB_GROUP: { + // + // If a GROUP! is hit then it is treated as a match and assumed that + // it should execute. Although the rules series is protected from + // modification during the parse, the input series is not...so the + // index may have to be adjusted to keep it in the array bounds. + // + REBSPC *derived = Derive_Specifier(P_RULE_SPECIFIER, rule); + DECLARE_LOCAL (dummy); + if (Do_At_Throws( + dummy, + VAL_ARRAY(rule), + VAL_INDEX(rule), + derived + )) { + Move_Value(P_OUT, dummy); + return THROWN_FLAG; + } + return MIN(pos, ARR_LEN(array)); } // may affect tail + + case REB_BLOCK: { + // + // Process a subrule. The subrule will run in its own frame, so it + // will not change P_POS directly (it will have its own P_INPUT_VALUE) + // Hence the return value regarding whether a match occurred or not + // has to be based on the result that comes back in P_OUT. + // + REBCNT pos_before = P_POS; + REBOOL interrupted; + + P_POS = pos; // modify input position + + if (Subparse_Throws( + &interrupted, + P_CELL, + P_INPUT_VALUE, // use input value with modified position + SPECIFIED, + rule, + P_RULE_SPECIFIER, + P_FIND_FLAGS + )) { + Move_Value(P_OUT, P_CELL); + return THROWN_FLAG; + } + + // !!! ignore "interrupted"? (e.g. ACCEPT or REJECT ran) + + P_POS = pos_before; // restore input position + + if (IS_BLANK(P_CELL)) + return END_FLAG; + + assert(IS_INTEGER(P_CELL)); + return VAL_INT32(P_CELL); } + + default: + break; + } + + // !!! R3-Alpha said "Match with some other value"... is this a good + // default?! + // + if (Cmp_Value(item, rule, P_HAS_CASE) == 0) + return pos + 1; + + return END_FLAG; } -/*********************************************************************** -** -*/ static To_Thru(REBPARSE *parse, REBCNT index, REBVAL *block, REBFLG is_thru) -/* -***********************************************************************/ -{ - REBSER *series = parse->series; - REBCNT type = parse->type; - REBVAL *blk; - REBVAL *item; - REBCNT cmd; - REBCNT i; - REBCNT len; - - for (; index <= series->tail; index++) { - - for (blk = VAL_BLK(block); NOT_END(blk); blk++) { - - item = blk; - - // Deal with words and commands - if (IS_WORD(item)) { - if (cmd = VAL_CMD(item)) { - if (cmd == SYM_END) { - if (index >= series->tail) { - index = series->tail; - goto found; - } - goto next; - } - else if (cmd == SYM_QUOTE) { - item = ++blk; // next item is the quoted value - if (IS_END(item)) goto bad_target; - if (IS_PAREN(item)) { - item = Do_Block_Value_Throw(item); // might GC - } - - } - else goto bad_target; - } - else { - item = Get_Var(item); - } - } - else if (IS_PATH(item)) { - item = Get_Parse_Value(item); - } - - // Try to match it: - if (type >= REB_BLOCK) { - if (ANY_BLOCK(item)) goto bad_target; - i = Parse_Next_Block(parse, index, item, 0); - if (i != NOT_FOUND) { - if (!is_thru) i--; - index = i; - goto found; - } - } - else if (type == REB_BINARY) { - REBYTE ch1 = *BIN_SKIP(series, index); - - // Handle special string types: - if (IS_CHAR(item)) { - if (VAL_CHAR(item) > 0xff) goto bad_target; - if (ch1 == VAL_CHAR(item)) goto found1; - } - else if (IS_BINARY(item)) { - if (ch1 == *VAL_BIN_DATA(item)) { - len = VAL_LEN(item); - if (len == 1) goto found1; - if (0 == Compare_Bytes(BIN_SKIP(series, index), VAL_BIN_DATA(item), len, 0)) { - if (is_thru) index += len; - goto found; - } - } - } - else if (IS_INTEGER(item)) { - if (VAL_INT64(item) > 0xff) goto bad_target; - if (ch1 == VAL_INT32(item)) goto found1; - } - else goto bad_target; - } - else { // String - REBCNT ch1 = GET_ANY_CHAR(series, index); - REBCNT ch2; - - if (!HAS_CASE(parse)) ch1 = UP_CASE(ch1); - - // Handle special string types: - if (IS_CHAR(item)) { - ch2 = VAL_CHAR(item); - if (!HAS_CASE(parse)) ch2 = UP_CASE(ch2); - if (ch1 == ch2) goto found1; - } - else if (ANY_STR(item)) { - ch2 = VAL_ANY_CHAR(item); - if (!HAS_CASE(parse)) ch2 = UP_CASE(ch2); - if (ch1 == ch2) { - len = VAL_LEN(item); - if (len == 1) goto found1; - i = Find_Str_Str(series, 0, index, SERIES_TAIL(series), 1, VAL_SERIES(item), VAL_INDEX(item), len, AM_FIND_MATCH | parse->flags); - if (i != NOT_FOUND) { - if (is_thru) i += len; - index = i; - goto found; - } - } - } - else if (IS_INTEGER(item)) { - ch1 = GET_ANY_CHAR(series, index); // No casing! - if (ch1 == (REBCNT)VAL_INT32(item)) goto found1; - } - else goto bad_target; - } - -next: // Check for | (required if not end) - blk++; - if (IS_PAREN(blk)) blk++; - if (IS_END(blk)) break; - if (!IS_OR_BAR(blk)) { - item = blk; - goto bad_target; - } - } - } - return NOT_FOUND; +// +// To make clear that the frame's P_POS is usually enough to know the state +// of the parse, this is the version used in the main loop. To_Thru uses +// the random access variation. +// +inline static REBIXO Parse_Array_One_Rule(REBFRM *f, const RELVAL *rule) { + return Parse_Array_One_Rule_Core(f, P_POS, rule); +} -found: - if (IS_PAREN(blk+1)) Do_Block_Value_Throw(blk+1); - return index; -found1: - if (IS_PAREN(blk+1)) Do_Block_Value_Throw(blk+1); - return index + (is_thru ? 1 : 0); +// +// To_Thru_Block_Rule: C +// +// The TO and THRU keywords in PARSE do not necessarily match the direct next +// item, but scan ahead in the series. This scan may be successful or not, +// and how much the match consumes can vary depending on how much THRU +// content was expressed in the rule. +// +// !!! This routine from R3-Alpha is fairly circuitous. As with the rest of +// the code, it gets clarified in small steps. +// +static REBIXO To_Thru_Block_Rule( + REBFRM *f, + const RELVAL *rule_block, + REBOOL is_thru +) { + DECLARE_LOCAL (cell); // holds evaluated rules (use frame cell instead?) + + RELVAL *blk; + + REBCNT pos = P_POS; + for (; pos <= SER_LEN(P_INPUT); ++pos) { + blk = VAL_ARRAY_HEAD(rule_block); + for (; NOT_END(blk); blk++) { + const RELVAL *rule = blk; + + if (IS_BAR(rule)) + fail (Error_Parse_Rule()); // !!! Shouldn't `TO [|]` succed? + + if (IS_WORD(rule)) { + REBSYM cmd = VAL_CMD(rule); + + if (cmd != SYM_0) { + if (cmd == SYM_END) { + if (pos >= SER_LEN(P_INPUT)) { + pos = SER_LEN(P_INPUT); + goto found; + } + goto next_alternate_rule; + } + else if (cmd == SYM_QUOTE) { + rule = ++blk; // next rule is the quoted value + if (IS_END(rule)) + fail (Error_Parse_Rule()); + + if (IS_GROUP(rule)) { + REBSPC *derived = Derive_Specifier( + P_RULE_SPECIFIER, + rule + ); + if (Do_At_Throws( // might GC + cell, + VAL_ARRAY(rule), + VAL_INDEX(rule), + derived + )) { + Move_Value(P_OUT, cell); + return THROWN_FLAG; + } + rule = cell; + } + } + else + fail (Error_Parse_Rule()); + } + else { + Copy_Opt_Var_May_Fail(cell, rule, P_RULE_SPECIFIER); + rule = cell; + } + } + else if (IS_PATH(rule)) + rule = Get_Parse_Value(cell, rule, P_RULE_SPECIFIER); + + // Try to match it: + if (ANY_ARRAY_KIND(P_TYPE)) { + if (ANY_ARRAY(rule)) + fail (Error_Parse_Rule()); + + REBIXO i = Parse_Array_One_Rule_Core(f, pos, rule); + if (i == THROWN_FLAG) { + assert(THROWN(P_OUT)); + return THROWN_FLAG; + } + + if (i != END_FLAG) { + pos = cast(REBCNT, i); + if (!is_thru) pos--; // passed it, so back up if only TO... + goto found; + } + } + else if (P_TYPE == REB_BINARY) { + REBYTE ch1 = *BIN_AT(P_INPUT, pos); + + // Handle special string types: + if (IS_CHAR(rule)) { + if (VAL_CHAR(rule) > 0xff) + fail (Error_Parse_Rule()); + + if (ch1 == VAL_CHAR(rule)) { + if (is_thru) ++pos; + goto found; + } + } + else if (IS_BINARY(rule)) { + if (ch1 == *VAL_BIN_AT(rule)) { + REBCNT len = VAL_LEN_AT(rule); + if (len == 1) { + if (is_thru) ++pos; + goto found; + } + + if (0 == Compare_Bytes( + BIN_AT(P_INPUT, pos), + VAL_BIN_AT(rule), + len, + FALSE + )) { + if (is_thru) pos += len; + goto found; + } + } + } + else if (IS_INTEGER(rule)) { + if (VAL_INT64(rule) > 0xff) + fail (Error_Parse_Rule()); + + if (ch1 == VAL_INT32(rule)) { + if (is_thru) ++pos; + goto found; + } + } + else + fail (Error_Parse_Rule()); + } + else { // String + REBUNI ch_unadjusted = GET_ANY_CHAR(P_INPUT, pos); + REBUNI ch; + if (!P_HAS_CASE) + ch = UP_CASE(ch_unadjusted); + else + ch = ch_unadjusted; + + // Handle special string types: + if (IS_CHAR(rule)) { + REBUNI ch2 = VAL_CHAR(rule); + if (!P_HAS_CASE) + ch2 = UP_CASE(ch2); + if (ch == ch2) { + if (is_thru) ++pos; + goto found; + } + } + // bitset + else if (IS_BITSET(rule)) { + if (Check_Bit(VAL_SERIES(rule), ch, NOT(P_HAS_CASE))) { + if (is_thru) ++pos; + goto found; + } + } + else if (IS_TAG(rule)) { + if (ch == '<') { + // + // !!! This code was adapted from Parse_to, and is + // inefficient in the sense that it forms the tag + // + REBSER *formed = Copy_Form_Value(rule, 0); + REBCNT len = SER_LEN(formed); + REBCNT i = Find_Str_Str( + P_INPUT, + 0, + pos, + SER_LEN(P_INPUT), + 1, + formed, + 0, + len, + AM_FIND_MATCH | P_FIND_FLAGS + ); + Free_Series(formed); + if (i != NOT_FOUND) { + pos = i; + if (is_thru) pos += len; + goto found; + } + } + } + else if (ANY_STRING(rule)) { + REBUNI ch2 = VAL_ANY_CHAR(rule); + if (!P_HAS_CASE) ch2 = UP_CASE(ch2); + + if (ch == ch2) { + REBCNT len = VAL_LEN_AT(rule); + if (len == 1) { + if (is_thru) ++pos; + goto found; + } + + REBCNT i = Find_Str_Str( + P_INPUT, + 0, + pos, + SER_LEN(P_INPUT), + 1, + VAL_SERIES(rule), + VAL_INDEX(rule), + len, + AM_FIND_MATCH | P_FIND_FLAGS + ); + + if (i != NOT_FOUND) { + pos = i; + if (is_thru) pos += len; + goto found; + } + } + } + else if (IS_INTEGER(rule)) { + if (ch_unadjusted == cast(REBUNI, VAL_INT32(rule))) { + if (is_thru) ++pos; + goto found; + } + } + else + fail (Error_Parse_Rule()); + } + + next_alternate_rule:; // alternates are BAR! separated `[a | b | c]` + blk++; + if (IS_END(blk)) + break; + + if (IS_GROUP(blk)) // don't run GROUP!s in the failing rule + blk++; + + if (IS_END(blk)) + break; + + if (!IS_BAR(blk)) + fail (Error_Parse_Rule()); + } + } + return END_FLAG; -bad_target: - Trap1(RE_PARSE_RULE, item); - return 0; +found: + if (NOT_END(blk + 1) && IS_GROUP(blk + 1)) { + DECLARE_LOCAL (dummy); + REBSPC *derived = Derive_Specifier(P_RULE_SPECIFIER, rule_block); + if (Do_At_Throws( + dummy, + VAL_ARRAY(blk + 1), + VAL_INDEX(blk + 1), + derived + )) { + Move_Value(P_OUT, dummy); + return THROWN_FLAG; + } + } + return pos; } -/*********************************************************************** -** -*/ static REBCNT Parse_To(REBPARSE *parse, REBCNT index, REBVAL *item, REBFLG is_thru) -/* -** Parse TO a specific: -** 1. integer - index position -** 2. END - end of input -** 3. value - according to datatype -** 4. block of values - the first one we hit -** -***********************************************************************/ -{ - REBSER *series = parse->series; - REBCNT i; - REBSER *ser; - - // TO a specific index position. - if (IS_INTEGER(item)) { - i = (REBCNT)Int32(item) - (is_thru ? 0 : 1); - if (i > series->tail) i = series->tail; - } - // END - else if (IS_WORD(item) && VAL_WORD_CANON(item) == SYM_END) { - i = series->tail; - } - else if (IS_BLOCK(item)) { - i = To_Thru(parse, index, item, is_thru); - } - else { - if (IS_BLOCK_INPUT(parse)) { - REBVAL word; /// !!!Temp, but where can we put it? - if (IS_LIT_WORD(item)) { // patch to search for word, not lit. - word = *item; - VAL_SET(&word, REB_WORD); - item = &word; - } - ///i = Find_Value(series, index, tail-index, item, 1, (REBOOL)(PF_CASE & flags), FALSE, 1); - i = Find_Block(series, index, series->tail, item, 1, HAS_CASE(parse)?AM_FIND_CASE:0, 1); - if (i != NOT_FOUND && is_thru) i++; - } - else { - // "str" - if (ANY_BINSTR(item)) { - if (!IS_STRING(item) && !IS_BINARY(item)) { - // !!! Can this be optimized not to use COPY? - ser = Copy_Form_Value(item, 0); - i = Find_Str_Str(series, 0, index, series->tail, 1, ser, 0, ser->tail, HAS_CASE(parse)); - if (i != NOT_FOUND && is_thru) i += ser->tail; - } - else { - i = Find_Str_Str(series, 0, index, series->tail, 1, VAL_SERIES(item), VAL_INDEX(item), VAL_LEN(item), HAS_CASE(parse)); - if (i != NOT_FOUND && is_thru) i += VAL_LEN(item); - } - } - // #"A" - else if (IS_CHAR(item)) { - i = Find_Str_Char(series, 0, index, series->tail, 1, VAL_CHAR(item), HAS_CASE(parse)); - if (i != NOT_FOUND && is_thru) i++; - } - // bitset - else if (IS_BITSET(item)) { - i = Find_Str_Bitset(series, 0, index, series->tail, 1, VAL_BITSET(item), HAS_CASE(parse)); - if (i != NOT_FOUND && is_thru) i++; - } - } - } - - return i; +// +// To_Thru_Non_Block_Rule: C +// +static REBIXO To_Thru_Non_Block_Rule( + REBFRM *f, + const RELVAL *rule, + REBOOL is_thru +) { + assert(!IS_BLOCK(rule)); + + if (IS_BLANK(rule)) + return P_POS; // make it a no-op + + if (IS_INTEGER(rule)) { + // + // `TO/THRU (INTEGER!)` JUMPS TO SPECIFIC INDEX POSITION + // + // !!! This allows jumping backward to an index before the parse + // position, while TO generally only goes forward otherwise. Should + // this be done by another operation? (Like SEEK?) + // + // !!! Negative numbers get cast to large integers, needs error! + // But also, should there be an option for relative addressing? + // + REBCNT i = cast(REBCNT, Int32(const_KNOWN(rule))) - (is_thru ? 0 : 1); + if (i > SER_LEN(P_INPUT)) + return SER_LEN(P_INPUT); + return i; + } + + if (IS_WORD(rule) && VAL_WORD_SYM(rule) == SYM_END) { + // + // `TO/THRU END` JUMPS TO END INPUT SERIES (ANY SERIES TYPE) + // + return SER_LEN(P_INPUT); + } + + if (GET_SER_FLAG(P_INPUT, SERIES_FLAG_ARRAY)) { + // + // FOR ARRAY INPUT WITH NON-BLOCK RULES, USE Find_In_Array() + // + // !!! This adjusts it to search for non-literal words, but are there + // other considerations for how non-block rules act with array input? + // + DECLARE_LOCAL (word); + if (IS_LIT_WORD(rule)) { + Derelativize(word, rule, P_RULE_SPECIFIER); + VAL_SET_TYPE_BITS(word, REB_WORD); + rule = word; + } + + REBCNT i = Find_In_Array( + ARR(P_INPUT), + P_POS, + SER_LEN(P_INPUT), + rule, + 1, + P_HAS_CASE ? AM_FIND_CASE : 0, + 1 + ); + + if (i == NOT_FOUND) + return END_FLAG; + + if (is_thru) + return i + 1; + + return i; + } + + //=//// PARSE INPUT IS A STRING OR BINARY, USE A FIND ROUTINE /////////=// + + if (ANY_BINSTR(rule)) { + if (!IS_STRING(rule) && !IS_BINARY(rule)) { + // !!! Can this be optimized not to use COPY? + REBSER *formed = Copy_Form_Value(rule, 0); + REBCNT form_len = SER_LEN(formed); + REBCNT i = Find_Str_Str( + P_INPUT, + 0, + P_POS, + SER_LEN(P_INPUT), + 1, + formed, + 0, + form_len, + (P_FIND_FLAGS & AM_FIND_CASE) + ? AM_FIND_CASE + : 0 + ); + Free_Series(formed); + + if (i == NOT_FOUND) + return END_FLAG; + + if (is_thru) + return i + form_len; + + return i; + } + + REBCNT i = Find_Str_Str( + P_INPUT, + 0, + P_POS, + SER_LEN(P_INPUT), + 1, + VAL_SERIES(rule), + VAL_INDEX(rule), + VAL_LEN_AT(rule), + (P_FIND_FLAGS & AM_FIND_CASE) + ? AM_FIND_CASE + : 0 + ); + + if (i == NOT_FOUND) + return END_FLAG; + + if (is_thru) + return i + VAL_LEN_AT(rule); + + return i; + } + + if (IS_CHAR(rule)) { + REBCNT i = Find_Str_Char( + VAL_CHAR(rule), + P_INPUT, + 0, + P_POS, + SER_LEN(P_INPUT), + 1, + (P_FIND_FLAGS & AM_FIND_CASE) + ? AM_FIND_CASE + : 0 + ); + + if (i == NOT_FOUND) + return END_FLAG; + + if (is_thru) + return i + 1; + + return i; + } + + if (IS_BITSET(rule)) { + REBCNT i = Find_Str_Bitset( + P_INPUT, + 0, + P_POS, + SER_LEN(P_INPUT), + 1, + VAL_BITSET(rule), + (P_FIND_FLAGS & AM_FIND_CASE) + ? AM_FIND_CASE + : 0 + ); + + if (i == NOT_FOUND) + return END_FLAG; + + if (is_thru) + return i + 1; + + return i; + } + + fail (Error_Parse_Rule()); } -/*********************************************************************** -** -*/ static REBCNT Do_Eval_Rule(REBPARSE *parse, REBCNT index, REBVAL **rule) -/* -** Evaluate the input as a code block. Advance input if -** rule succeeds. Return new index or failure. -** -** Examples: -** do skip -** do end -** do "abc" -** do 'abc -** do [...] -** do variable -** do datatype! -** do quote 123 -** do into [...] -** -** Problem: cannot write: set var do datatype! -** -***********************************************************************/ +// +// Do_Eval_Rule: C +// +// Perform a DO/NEXT on the *input* as a code block, and match the following +// rule against the evaluative result. +// +// parse [1 + 2] [do [quote 3]] => true +// +// The rule may be in a block or inline. +// +// parse [reverse copy "abc"] [do "cba"] +// parse [reverse copy "abc"] [do ["cba"]] +// +// !!! Due to failures in the mechanics of "Parse_One_Rule", a block must +// be used on rules that are more than one item in length. +// +// This feature was added to make it easier to do dialect processing where the +// dialect had code inline. It can be a little hard to get one's head around, +// because it says `do [...]` and yet the `...` is a parse rule and not the +// code to be executed. But this is somewhat in the spirit of operations +// like COPY which are not operating on their arguments, but implicitly taking +// the series itself as an argument. +// +// !!! The way this feature was expressed in R3-Alpha isolates it from +// participating in iteration or as the target of an outer rule, e.g. +// +// parse [1 + 2] [set var do [quote 3]] ;-- var gets 1, not 3 +// +// Other problems arise since the caller doesn't know about the trickiness +// of this evaluation, e.g. this won't work either: +// +// parse [1 + 2] [thru do integer!] +// +static REBIXO Do_Eval_Rule(REBFRM *f) { - REBVAL value; - REBVAL *item = *rule; - REBCNT n; - REBPARSE newparse; - - // First, check for end of input: - if (index >= parse->series->tail) { - if (IS_WORD(item) && VAL_CMD(item) == SYM_END) return index; - else return NOT_FOUND; - } - - // Evaluate next N input values: - index = Do_Next(parse->series, index, FALSE); - - // Value is on top of stack (volatile!): - value = *DS_POP; - if (THROWN(&value)) Throw_Break(&value); - - // Get variable or command: - if (IS_WORD(item)) { - - n = VAL_CMD(item); - - if (n == SYM_SKIP) - return (IS_SET(&value)) ? index : NOT_FOUND; - - if (n == SYM_QUOTE) { - item = item + 1; - (*rule)++; - if (IS_END(item)) Trap1(RE_PARSE_END, item-2); - if (IS_PAREN(item)) { - item = Do_Block_Value_Throw(item); // might GC - } - } - else if (n == SYM_INTO) { - item = item + 1; - (*rule)++; - if (IS_END(item)) Trap1(RE_PARSE_END, item-2); - item = Get_Parse_Value(item); // sub-rules - if (!IS_BLOCK(item)) Trap1(RE_PARSE_RULE, item-2); - if (!ANY_BINSTR(&value) && !ANY_BLOCK(&value)) return NOT_FOUND; - return (Parse_Series(&value, VAL_BLK_DATA(item), parse->flags, 0) == VAL_TAIL(&value)) - ? index : NOT_FOUND; - } - else if (n > 0) - Trap1(RE_PARSE_RULE, item); - else - item = Get_Parse_Value(item); // variable - } - else if (IS_PATH(item)) { - item = Get_Parse_Value(item); // variable - } - else if (IS_SET_WORD(item) || IS_GET_WORD(item) || IS_SET_PATH(item) || IS_GET_PATH(item)) - Trap1(RE_PARSE_RULE, item); - - if (IS_NONE(item)) { - return (VAL_TYPE(&value) > REB_NONE) ? NOT_FOUND : index; - } - - // Copy the value into its own block: - newparse.series = Make_Block(1); - SAVE_SERIES(newparse.series); - Append_Val(newparse.series, &value); - newparse.type = REB_BLOCK; - newparse.flags = parse->flags; - newparse.result = 0; - - n = (Parse_Next_Block(&newparse, 0, item, 0) != NOT_FOUND) ? index : NOT_FOUND; - UNSAVE_SERIES(newparse.series); - return n; + if (NOT_SER_FLAG(P_INPUT, SERIES_FLAG_ARRAY)) // can't be an ANY-STRING! + fail (Error_Parse_Rule()); + + if (IS_END(P_RULE)) + fail (Error_Parse_End()); + + // The DO'ing of the input series will generate a single REBVAL. But + // for a parse to run on some input, that input has to be in a series... + // so the single item is put into a block holder. If the item was already + // a block, then the user will have to use INTO to parse into it. + // + // Note: Implicitly handling a block evaluative result as an array would + // make it impossible to tell whether the evaluation produced [1] or 1. + // + REBARR *holder; + + REBIXO indexor; + if (P_POS >= SER_LEN(P_INPUT)) { + // + // We could short circuit and notice if the rule was END or not, but + // that leaves out other potential matches like `[(print "Hi") end]` + // as a rule. Keep it generalized and pass an empty block in as + // the series to process. + // + holder = EMPTY_ARRAY; // read-only + indexor = END_FLAG; + } + else { + // Evaluate next expression from the *input* series (not the rules) + // + indexor = DO_NEXT_MAY_THROW( + P_CELL, ARR(P_INPUT), P_POS, P_INPUT_SPECIFIER + ); + if (indexor == THROWN_FLAG) { // BREAK/RETURN/QUIT/THROW... + Move_Value(P_OUT, P_CELL); + return THROWN_FLAG; + } + + // !!! This copies a single value into a block to use as data, because + // parse input is matched as a series. Can this be avoided? + // + holder = Alloc_Singular_Array(); + Move_Value(ARR_HEAD(holder), P_CELL); + Deep_Freeze_Array(holder); // don't allow modification of temporary + } + + // We want to reuse the same frame we're in, because if you say + // something like `parse [1 + 2] [do [quote 3]]`, the`[quote 3]` rule + // should be consumed. We also want to be able to use a nested rule + // inline, such as `do skip` not only allow `do [skip]`. + // + // So the rules should be processed normally, it's just that for the + // duration of the next rule the *input* is the temporary evaluative + // result. + // + DECLARE_LOCAL (saved_input); + Move_Value(saved_input, P_INPUT_VALUE); // series and P_POS position + PUSH_GUARD_VALUE(saved_input); + Init_Block(P_INPUT_VALUE, holder); + + // !!! There is not a generic form of SUBPARSE/NEXT, but there should be. + // The particular factoring of the one-rule form of parsing makes us + // redo work like fetching words/paths, which should not be needed. + // + DECLARE_LOCAL (cell); + const RELVAL *rule = Get_Parse_Value(cell, P_RULE, P_RULE_SPECIFIER); + + // !!! The actual mechanic here does not permit you to say `do thru x` + // or other multi-argument things. A lot of R3-Alpha's PARSE design was + // rather ad-hoc and hard to adapt. The one rule parsing does not + // advance the position, but it should. + // + REBIXO n = Parse_Array_One_Rule(f, rule); + FETCH_NEXT_RULE_MAYBE_END(f); + + // Restore the input series to what it was before parsing the temporary + // (this restores P_POS, since it's just an alias for the input's index) + // + Move_Value(P_INPUT_VALUE, saved_input); + DROP_GUARD_VALUE(saved_input); + + if (n == THROWN_FLAG) { + assert(THROWN(P_OUT)); + return THROWN_FLAG; + } + + if (n == ARR_LEN(holder)) { + // + // Eval result reaching end means success, so return index advanced + // past the evaluation. + // + // !!! Although DO_NEXT_MAY_THROW uses an END_FLAG-based + // convention when it reaches the end, these parse routines always + // return an array index. + // + return indexor == END_FLAG ? SER_LEN(P_INPUT) : indexor; + } + + return P_POS; // as failure, hand back original position--no advancement } -/*********************************************************************** -** -*/ static REBCNT Parse_Rules_Loop(REBPARSE *parse, REBCNT index, REBVAL *rules, REBCNT depth) -/* -***********************************************************************/ +// +// subparse: native [ +// +// {Internal support function for PARSE (acts as variadic to consume rules)} +// +// return: [integer! blank!] +// input [any-series!] +// find-flags [integer!] +// ] +// +REBNATIVE(subparse) +// +// Rules are matched until one of these things happens: +// +// * A rule fails, and is not then picked up by a later "optional" rule. +// This returns R_OUT with the value in out as BLANK!. +// +// * You run out of rules to apply without any failures or errors, and the +// position in the input series is returned. This may be at the end of +// the input data or not--it's up to the caller to decide if that's relevant. +// This will return R_OUT with out containing an integer index. +// +// !!! The return of an integer index is based on the R3-Alpha convention, +// but needs to be rethought in light of the ability to switch series. It +// does not seem that all callers of Subparse's predecessor were prepared for +// the semantics of switching the series. +// +// * A `fail()`, in which case the function won't return--it will longjmp +// up to the most recently pushed handler. This can happen due to an invalid +// rule pattern, or if there's an error in code that is run in parentheses. +// +// * A throw-style result caused by DO code run in parentheses (e.g. a +// THROW, RETURN, BREAK, CONTINUE). This returns R_OUT_IS_THROWN. +// +// * A special throw to indicate a return out of the PARSE itself, triggered +// by the RETURN instruction. This also returns R_OUT_IS_THROWN, but will +// be caught by PARSE before returning. +// { - REBSER *series = parse->series; - REBVAL *item; // current rule item - REBVAL *word; // active word to be set - REBCNT start; // recovery restart point - REBCNT i; // temp index point - REBCNT begin; // point at beginning of match - REBINT count; // iterated pattern counter - REBINT mincount; // min pattern count - REBINT maxcount; // max pattern count - REBVAL *item_hold; - REBVAL *val; // spare - REBCNT rulen; - REBSER *ser; - REBFLG flags; - REBCNT cmd; - REBVAL *rule_head = rules; - - CHECK_STACK(&flags); - //if (depth > MAX_PARSE_DEPTH) Trap_Word(RE_LIMIT_HIT, SYM_PARSE, 0); - flags = 0; - word = 0; - mincount = maxcount = 1; - start = begin = index; - - // For each rule in the rule block: - while (NOT_END(rules)) { - - //Print_Parse_Index(parse->type, rules, series, index); - - if (--Eval_Count <= 0 || Eval_Signals) Do_Signals(); - - //-------------------------------------------------------------------- - // Pre-Rule Processing Section - // - // For non-iterated rules, including setup for iterated rules. - // The input index is not advanced here, but may be changed by - // a GET-WORD variable. - //-------------------------------------------------------------------- - - item = rules++; - - // If word, set-word, or get-word, process it: - if (VAL_TYPE(item) >= REB_WORD && VAL_TYPE(item) <= REB_GET_WORD) { - - // Is it a command word? - if (cmd = VAL_CMD(item)) { - - if (!IS_WORD(item)) Trap1(RE_PARSE_COMMAND, item); // SET or GET not allowed - - if (cmd <= SYM_BREAK) { // optimization - - switch (cmd) { - - case SYM_OR_BAR: - return index; // reached it successfully - - // Note: mincount = maxcount = 1 on entry - case SYM_WHILE: - SET_FLAG(flags, PF_WHILE); - case SYM_ANY: - mincount = 0; - case SYM_SOME: - maxcount = MAX_I32; - continue; - - case SYM_OPT: - mincount = 0; - continue; - - case SYM_COPY: - SET_FLAG(flags, PF_COPY); - case SYM_SET: - SET_FLAG(flags, PF_SET_OR_COPY); - item = rules++; - if (!(IS_WORD(item) || IS_SET_WORD(item))) Trap1(RE_PARSE_VARIABLE, item); - if (VAL_CMD(item)) Trap1(RE_PARSE_COMMAND, item); - word = item; - continue; - - case SYM_NOT: - SET_FLAG(flags, PF_NOT); - flags ^= (1<result = 1; - return index; - - case SYM_REJECT: - parse->result = -1; - return index; - - case SYM_FAIL: - index = NOT_FOUND; - goto post; - - case SYM_IF: - item = rules++; - if (IS_END(item)) goto bad_end; - if (!IS_PAREN(item)) Trap1(RE_PARSE_RULE, item); - item = Do_Block_Value_Throw(item); // might GC - if (IS_TRUE(item)) continue; - else { - index = NOT_FOUND; - goto post; - } - - case SYM_LIMIT: - Trap0(RE_NOT_DONE); - //val = Get_Parse_Value(rules++); - // if (IS_INTEGER(val)) limit = index + Int32(val); - // else if (ANY_SERIES(val)) limit = VAL_INDEX(val); - // else goto - //goto bad_rule; - // goto post; - - case SYM_QQ: - Print_Parse_Index(parse->type, rules, series, index); - continue; - } - } - // Any other cmd must be a match command, so proceed... - - } else { // It's not a PARSE command, get or set it: - - // word: - if not the target of a COPY or SET operation, this will - // default to setting a variable to the series at current index - if (IS_SET_WORD(item) && - !(GET_FLAG(flags, PF_SET_OR_COPY) || GET_FLAG(flags, PF_COPY))) - { - Set_Var_Series(item, parse->type, series, index); - continue; - } - - // :word - change the index for the series to a new position - if (IS_GET_WORD(item)) { - item = Get_Var(item); - // CureCode #1263 change - //if (parse->type != VAL_TYPE(item) || VAL_SERIES(item) != series) - // Trap1(RE_PARSE_SERIES, rules-1); - if (!ANY_SERIES(item)) Trap1(RE_PARSE_SERIES, rules-1); - index = Set_Parse_Series(parse, item); - series = parse->series; - continue; - } - - // word - some other variable - if (IS_WORD(item)) { - item = Get_Var(item); - } - - // item can still be 'word or /word - } - } - else if (ANY_PATH(item)) { - item = Do_Parse_Path(item, parse, &index); // index can be modified - if (index > series->tail) index = series->tail; - if (item == 0) continue; // for SET and GET cases - } - - if (IS_PAREN(item)) { - Do_Block_Value_Throw(item); // might GC - if (index > series->tail) index = series->tail; - continue; - } - - // Counter? 123 - if (IS_INTEGER(item)) { // Specify count or range count - SET_FLAG(flags, PF_WHILE); - mincount = maxcount = Int32s(item, 0); - item = Get_Parse_Value(rules++); - if (IS_END(item)) Trap1(RE_PARSE_END, rules-2); - if (IS_INTEGER(item)) { - maxcount = Int32s(item, 0); - item = Get_Parse_Value(rules++); - if (IS_END(item)) Trap1(RE_PARSE_END, rules-2); - } - } - // else fall through on other values and words - - //-------------------------------------------------------------------- - // Iterated Rule Matching Section: - // - // Repeats the same rule N times or until the rule fails. - // The index is advanced and stored in a temp variable i until - // the entire rule has been satisfied. - //-------------------------------------------------------------------- - - item_hold = item; // a command or literal match value - if (VAL_TYPE(item) <= REB_UNSET || VAL_TYPE(item) >= REB_NATIVE) goto bad_rule; - begin = index; // input at beginning of match section - rulen = 0; // rules consumed (do not use rule++ below) - i = index; - - //note: rules var already advanced - - for (count = 0; count < maxcount;) { - - item = item_hold; - - if (IS_WORD(item)) { - - switch (cmd = VAL_WORD_CANON(item)) { - - case SYM_SKIP: - i = (index < series->tail) ? index+1 : NOT_FOUND; - break; - - case SYM_END: - i = (index < series->tail) ? NOT_FOUND : series->tail; - break; - - case SYM_TO: - case SYM_THRU: - if (IS_END(rules)) goto bad_end; - item = Get_Parse_Value(rules); - rulen = 1; - i = Parse_To(parse, index, item, cmd == SYM_THRU); - break; - - case SYM_QUOTE: - if (IS_END(rules)) goto bad_end; - rulen = 1; - if (IS_PAREN(rules)) { - item = Do_Block_Value_Throw(rules); // might GC - } - else item = rules; - i = (0 == Cmp_Value(BLK_SKIP(series, index), item, parse->flags & AM_FIND_CASE)) ? index+1 : NOT_FOUND; - break; - - case SYM_INTO: - if (IS_END(rules)) goto bad_end; - rulen = 1; - item = Get_Parse_Value(rules); // sub-rules - if (!IS_BLOCK(item)) goto bad_rule; - val = BLK_SKIP(series, index); - i = ( - (ANY_BINSTR(val) || ANY_BLOCK(val)) - && (Parse_Series(val, VAL_BLK_DATA(item), parse->flags, depth+1) == VAL_TAIL(val)) - ) ? index+1 : NOT_FOUND; - break; - - case SYM_DO: - if (!IS_BLOCK_INPUT(parse)) goto bad_rule; - i = Do_Eval_Rule(parse, index, &rules); - rulen = 1; - break; - - default: - goto bad_rule; - } - } - else if (IS_BLOCK(item)) { - item = VAL_BLK_DATA(item); - //if (IS_END(rules) && item == rule_head) { - // rules = item; - // goto top; - //} - i = Parse_Rules_Loop(parse, index, item, depth+1); - if (parse->result) { - index = (parse->result > 0) ? i : NOT_FOUND; - parse->result = 0; - break; - } - } - // Parse according to datatype: - else { - if (IS_BLOCK_INPUT(parse)) - i = Parse_Next_Block(parse, index, item, depth+1); - else - i = Parse_Next_String(parse, index, item, depth+1); - } - - // Necessary for special cases like: some [to end] - // i: indicates new index or failure of the match, but - // that does not mean failure of the rule, because optional - // matches can still succeed, if if the last match failed. - if (i != NOT_FOUND) { - count++; // may overflow to negative - if (count < 0) count = MAX_I32; // the forever case - // If input did not advance: - if (i == index && !GET_FLAG(flags, PF_WHILE)) { - if (count < mincount) index = NOT_FOUND; // was not enough - break; - } - } - //if (i >= series->tail) { // OLD check: no more input - else { - if (count < mincount) index = NOT_FOUND; // was not enough - else if (i != NOT_FOUND) index = i; - // else keep index as is. - break; - } - index = i; - - // A BREAK word stopped us: - //if (parse->result) {parse->result = 0; break;} - } - - rules += rulen; - - //if (index > series->tail && index != NOT_FOUND) index = series->tail; - if (index > series->tail) index = NOT_FOUND; - - //-------------------------------------------------------------------- - // Post Match Processing: - //-------------------------------------------------------------------- -post: - // Process special flags: - if (flags) { - // NOT before all others: - if (GET_FLAG(flags, PF_NOT)) { - if (GET_FLAG(flags, PF_NOT2) && index != NOT_FOUND) index = NOT_FOUND; - else index = begin; - } - if (index == NOT_FOUND) { // Failure actions: - // not decided: if (word) Set_Var_Basic(word, REB_NONE); - if (GET_FLAG(flags, PF_THEN)) { - SKIP_TO_BAR(rules); - if (!IS_END(rules)) rules++; - } - } - else { // Success actions: - count = (begin > index) ? 0 : index - begin; // how much we advanced the input - if (GET_FLAG(flags, PF_COPY)) { - ser = (IS_BLOCK_INPUT(parse)) - ? Copy_Block_Len(series, begin, count) - : Copy_String(series, begin, count); // condenses - Set_Var_Series(word, parse->type, ser, 0); - } - else if (GET_FLAG(flags, PF_SET_OR_COPY)) { - if (IS_BLOCK_INPUT(parse)) { - item = Get_Var_Safe(word); - if (count == 0) SET_NONE(item); - else *item = *BLK_SKIP(series, begin); - } - else { - item = Get_Var_Safe(word); - if (count == 0) SET_NONE(item); - else { - i = GET_ANY_CHAR(series, begin); - if (parse->type == REB_BINARY) { - SET_INTEGER(item, i); - } else { - SET_CHAR(item, i); - } - } - } - } - if (GET_FLAG(flags, PF_RETURN)) { - ser = (IS_BLOCK_INPUT(parse)) - ? Copy_Block_Len(series, begin, count) - : Copy_String(series, begin, count); // condenses - Throw_Return_Series(parse->type, ser); - } - if (GET_FLAG(flags, PF_REMOVE)) { - if (count) Remove_Series(series, begin, count); - index = begin; - } - if (flags & (1<type == REB_BINARY) cmd |= (1<tail; - REBSER *blk; - REBSER *set; - REBCNT begin; - REBCNT end; - REBOOL skip_spaces = !(flags & PF_ALL); - REBUNI uc; - - blk = BUF_EMIT; // shared series - RESET_SERIES(blk); - - // String of delimiters or single character: - if (IS_STRING(rules) || IS_CHAR(rules)) { - begin = Find_Max_Bit(rules); - if (begin <= ' ') begin = ' ' + 1; - set = Make_Bitset(begin); - Set_Bits(set, rules, TRUE); - } - // None, so use defaults ",;": - else { - set = Make_Bitset(1+MAX(',',';')); - Set_Bit(set, ',', TRUE); - Set_Bit(set, ';', TRUE); - } - SAVE_SERIES(set); - - // If required, make space delimiters too: - if (skip_spaces) { - for (uc = 1; uc <= ' '; uc++) Set_Bit(set, uc, TRUE); - } - - while (index < tail) { - - if (--Eval_Count <= 0 || Eval_Signals) Do_Signals(); - - // Skip whitespace if not /all refinement: - if (skip_spaces) { - uc = 0; - for (; index < tail; index++) { - uc = GET_ANY_CHAR(series, index); - if (!IS_WHITE(uc)) break; - } - } - else - uc = GET_ANY_CHAR(series, index); // prefetch - - if (index < tail) { - - // Handle quoted strings (in a simple way): - if (uc == '"') { - begin = ++index; // eat quote - for (; index < tail; index++) { - uc = GET_ANY_CHAR(series, index); - if (uc == '"') break; - } - end = index; - if (index < tail) index++; - } - // All other tokens: - else { - begin = index; - for (; index < tail; index++) { - if (Check_Bit(set, GET_ANY_CHAR(series, index), !(flags & PF_CASE))) break; - } - end = index; - } - - // Skip trailing spaces: - if (skip_spaces) - for (; index < tail; index++) { - uc = GET_ANY_CHAR(series, index); - if (!IS_WHITE(uc)) break; - } - - // Check for and remove separator: - if (Check_Bit(set, GET_ANY_CHAR(series, index), !(flags & PF_CASE))) index++; - - // Append new string: - Set_String(Append_Value(blk), Copy_String(series, begin, end - begin)); - } - } - UNSAVE_SERIES(set); - - return Copy_Block(blk, 0); +#if !defined(NDEBUG) + // + // These parse state variables live in chunk-stack REBVARs, which can be + // annoying to find to inspect in the debugger. This makes pointers into + // the value payloads so they can be seen more easily. + // + const REBCNT *pos_debug = &P_POS; + (void)pos_debug; // UNUSED() forces corruption in C++11 debug builds + + REBUPT do_count = TG_Do_Count; // helpful to cache for visibility also +#endif + + DECLARE_LOCAL (save); + + REBCNT start = P_POS; // recovery restart point + REBCNT begin = P_POS; // point at beginning of match + + // The loop iterates across each REBVAL's worth of "rule" in the rule + // block. Some of these rules just set `flags` and `continue`, so that + // the flags will apply to the next rule item. If the flag is PF_SET + // or PF_COPY, then the `set_or_copy_word` pointers will be assigned + // at the same time as the active target of the COPY or SET. + // + // !!! This flagging process--established by R3-Alpha--is efficient + // but somewhat haphazard. It may work for `while ["a" | "b"]` to + // "set the PF_WHILE" flag when it sees the `while` and then iterate + // a rule it would have otherwise processed just once. But there are + // a lot of edge cases like `while |` where this method isn't set up + // to notice a "grammar error". It could use review. + // + REBFLGS flags = 0; + const RELVAL *set_or_copy_word = NULL; + + REBINT mincount = 1; // min pattern count + REBINT maxcount = 1; // max pattern count + + while (NOT_END(P_RULE)) { + // + // The rule in the block of rules can be literal, while the "real + // rule" we want to process is the result of a variable fetched from + // that item. If the code makes it to the iterated rule matching + // section, then rule should be set to something non-NULL by then... + // + const RELVAL *rule = NULL; + + // Some rules that make it to the iterated rule section have a + // parameter. For instance `3 into [some "a"]` will actually run + // the INTO `rule` 3 times with the `subrule` of `[some "a"]`. + // Because it is iterated it is only captured the first time through, + // so setting it to NULL indicates for such instructions that it + // has not been captured yet. + // + const RELVAL *subrule = NULL; + + /* Print_Parse_Index(f); */ + UPDATE_EXPRESSION_START(f); + + #if !defined(NDEBUG) + ++TG_Do_Count; + do_count = TG_Do_Count; + cast(void, do_count); // suppress compiler warning about lack of use + #endif + + //==////////////////////////////////////////////////////////////////==// + // + // GARBAGE COLLECTION AND EVENT HANDLING + // + //==////////////////////////////////////////////////////////////////==// + + assert(Eval_Count >= 0); + if (--Eval_Count == 0) { + SET_END(P_CELL); + + if (Do_Signals_Throws(P_CELL)) + fail (Error_No_Catch_For_Throw(P_CELL)); + + assert(IS_END(P_CELL)); + } + + //==////////////////////////////////////////////////////////////////==// + // + // PRE-RULE PROCESSING SECTION + // + //==////////////////////////////////////////////////////////////////==// + + // For non-iterated rules, including setup for iterated rules. + // The input index is not advanced here, but may be changed by + // a GET-WORD variable. + + if (IS_BAR(P_RULE)) { + // + // If a BAR! is hit while processing any rules in the rules + // block, then that means the current option didn't fail out + // first...so it's a success for the rule. Stop processing and + // return the current input position. + // + // (Note this means `[| ...anything...]` is a "no-op" match) + // + Init_Integer(P_OUT, P_POS); + return R_OUT; + } + + // If word, set-word, or get-word, process it: + if (VAL_TYPE(P_RULE) >= REB_WORD && VAL_TYPE(P_RULE) <= REB_GET_WORD) { + + REBSYM cmd = VAL_CMD(P_RULE); + if (cmd != SYM_0) { + if (!IS_WORD(P_RULE)) + fail (Error_Parse_Command_Raw(P_RULE)); // COPY: :THRU ... + + if (cmd <= SYM_BREAK) { // optimization + + switch (cmd) { + // Note: mincount = maxcount = 1 on entry + case SYM_WHILE: + flags |= PF_WHILE; + // falls through + case SYM_ANY: + mincount = 0; + // falls through + case SYM_SOME: + maxcount = MAX_I32; + FETCH_NEXT_RULE_MAYBE_END(f); + continue; + + case SYM_OPT: + mincount = 0; + FETCH_NEXT_RULE_MAYBE_END(f); + continue; + + case SYM_COPY: + flags |= PF_COPY; + goto set_or_copy_pre_rule; + case SYM_SET: + flags |= PF_SET; + // falls through + set_or_copy_pre_rule: + FETCH_NEXT_RULE_MAYBE_END(f); + + if (!(IS_WORD(P_RULE) || IS_SET_WORD(P_RULE))) + fail (Error_Parse_Variable_Raw(P_RULE)); + + if (VAL_CMD(P_RULE)) + fail (Error_Parse_Command_Raw(P_RULE)); + + set_or_copy_word = P_RULE; + FETCH_NEXT_RULE_MAYBE_END(f); + continue; + + case SYM_NOT: + flags |= PF_NOT; + flags ^= PF_NOT2; + FETCH_NEXT_RULE_MAYBE_END(f); + continue; + + case SYM_AND: + flags |= PF_AND; + FETCH_NEXT_RULE_MAYBE_END(f); + continue; + + case SYM_THEN: + flags |= PF_THEN; + FETCH_NEXT_RULE_MAYBE_END(f); + continue; + + case SYM_REMOVE: + flags |= PF_REMOVE; + FETCH_NEXT_RULE_MAYBE_END(f); + continue; + + case SYM_INSERT: + flags |= PF_INSERT; + FETCH_NEXT_RULE_MAYBE_END(f); + goto post_match_processing; + + case SYM_CHANGE: + flags |= PF_CHANGE; + FETCH_NEXT_RULE_MAYBE_END(f); + continue; + + // There are two RETURNs: one is a matching form, so with + // 'parse data [return "abc"]' you are not asking to + // return the literal string "abc" independent of input. + // it will only return if "abc" matches. This works for + // a rule reference as well, such as 'return rule'. + // + // The second option is if you put the value in parens, + // in which case it will just return whatever that value + // happens to be, e.g. 'parse data [return ("abc")]' + + case SYM_RETURN: + FETCH_NEXT_RULE_MAYBE_END(f); + if (IS_GROUP(P_RULE)) { + DECLARE_LOCAL (evaluated); + if (Do_At_Throws( + evaluated, + VAL_ARRAY(P_RULE), + VAL_INDEX(P_RULE), + P_RULE_SPECIFIER + )) { + // If the group evaluation result gives a + // THROW, BREAK, CONTINUE, etc then we'll + // return that + Move_Value(P_OUT, evaluated); + return R_OUT_IS_THROWN; + } + + Move_Value(P_OUT, NAT_VALUE(parse)); + CONVERT_NAME_TO_THROWN(P_OUT, evaluated); + return R_OUT_IS_THROWN; + } + flags |= PF_RETURN; + continue; + + case SYM_ACCEPT: + case SYM_BREAK: { + // + // This has to be throw-style, because it's not enough + // to just say the current rule succeeded...it climbs + // up and affects an enclosing parse loop. + // + DECLARE_LOCAL (thrown_arg); + Init_Integer(thrown_arg, P_POS); + Move_Value(P_OUT, NAT_VALUE(parse_accept)); + CONVERT_NAME_TO_THROWN(P_OUT, thrown_arg); + return R_OUT_IS_THROWN; + } + + case SYM_REJECT: { + // + // Similarly, this is a break/continue style "throw" + // + Move_Value(P_OUT, NAT_VALUE(parse_reject)); + CONVERT_NAME_TO_THROWN(P_OUT, BLANK_VALUE); + return R_OUT; + } + + case SYM_FAIL: + P_POS = NOT_FOUND; + FETCH_NEXT_RULE_MAYBE_END(f); + goto post_match_processing; + + case SYM_IF: { + FETCH_NEXT_RULE_MAYBE_END(f); + if (IS_END(P_RULE)) + fail (Error_Parse_End()); + + if (!IS_GROUP(P_RULE)) + fail (Error_Parse_Rule()); + + // might GC + DECLARE_LOCAL (condition); + if (Do_At_Throws( + condition, + VAL_ARRAY(P_RULE), + VAL_INDEX(P_RULE), + P_RULE_SPECIFIER + )) { + Move_Value(P_OUT, condition); + return R_OUT_IS_THROWN; + } + + FETCH_NEXT_RULE_MAYBE_END(f); + + if (IS_CONDITIONAL_TRUE(condition)) + continue; + + P_POS = NOT_FOUND; + goto post_match_processing; + } + + case SYM_LIMIT: + fail (Error_Not_Done_Raw()); + + case SYM__Q_Q: + Print_Parse_Index(f); + FETCH_NEXT_RULE_MAYBE_END(f); + continue; + + default: //the list above should be exhaustive + assert(FALSE); + } + } + // Any other cmd must be a match command, so proceed... + rule = P_RULE; + } + else { + // It's not a PARSE command, get or set it + + // word: - set a variable to the series at current index + if (IS_SET_WORD(P_RULE)) { + // + // !!! Review meaning of marking the parse in a slot that + // is a target of a rule, e.g. `thru pos: xxx` # + // + // https://github.com/rebol/rebol-issues/issues/2269 + // + // if (flags != 0) fail (Error_Parse_Rule()); + + Move_Value( + Sink_Var_May_Fail(P_RULE, P_RULE_SPECIFIER), + P_INPUT_VALUE + ); + FETCH_NEXT_RULE_MAYBE_END(f); + continue; + } + + // :word - change the index for the series to a new position + if (IS_GET_WORD(P_RULE)) { + DECLARE_LOCAL (temp); + Copy_Opt_Var_May_Fail(temp, P_RULE, P_RULE_SPECIFIER); + if (!ANY_SERIES(temp)) // #1263 + fail (Error_Parse_Series_Raw(P_RULE)); + Set_Parse_Series(f, temp); + + // !!! `continue` is used here without any post-"match" + // processing, so the only way `begin` will get set for + // the next rule is if it's set here, else commands like + // INSERT that follow will insert at the old location. + // + // https://github.com/rebol/rebol-issues/issues/2269 + // + // Without known resolution on #2269, it isn't clear if + // there is legitimate meaning to seeking a parse in mid + // rule or not. So only reset the begin position if the + // seek appears to be a "separate rule" in its own right. + // + if (flags == 0) + begin = P_POS; + + FETCH_NEXT_RULE_MAYBE_END(f); + continue; + } + + // word - some other variable + if (IS_WORD(P_RULE)) { + Copy_Opt_Var_May_Fail(save, P_RULE, P_RULE_SPECIFIER); + rule = save; + if (IS_VOID(rule)) + fail (Error_No_Value_Core(P_RULE, P_RULE_SPECIFIER)); + } + else { + // rule can still be 'word or /word + rule = P_RULE; + } + } + } + else if (ANY_PATH(P_RULE)) { + if (IS_PATH(P_RULE)) { + if (Do_Path_Throws_Core( + save, NULL, P_RULE, P_RULE_SPECIFIER, NULL + )) { + fail (Error_No_Catch_For_Throw(save)); + } + rule = save; + } + else if (IS_SET_PATH(P_RULE)) { + if (Do_Path_Throws_Core( + save, NULL, P_RULE, P_RULE_SPECIFIER, P_INPUT_VALUE + )) { + fail (Error_No_Catch_For_Throw(save)); + } + + // Nothing left to do after storing the parse position in the + // path location...continue. + // + FETCH_NEXT_RULE_MAYBE_END(f); + continue; + } + else if (IS_GET_PATH(P_RULE)) { + + if (Do_Path_Throws_Core( + save, NULL, P_RULE, P_RULE_SPECIFIER, NULL + )) { + fail (Error_No_Catch_For_Throw(save)); + } + + // !!! This allows the series to be changed, as per #1263, + // but note the positions being returned and checked aren't + // prepared for this, they only exchange numbers ATM (!!!) + // + if (!ANY_SERIES(save)) + fail (Error_Parse_Series_Raw(save)); + + Set_Parse_Series(f, save); + FETCH_NEXT_RULE_MAYBE_END(f); + continue; + } + else { + assert(IS_LIT_PATH(P_RULE)); + rule = P_RULE; + } + + if (P_POS > SER_LEN(P_INPUT)) + P_POS = SER_LEN(P_INPUT); + } + else { + rule = P_RULE; + } + + // All cases should have either set `rule` by this point or continued + // + assert(rule != NULL && !IS_VOID(rule)); + + if (IS_GROUP(rule)) { + DECLARE_LOCAL (evaluated); + REBSPC *derived = Derive_Specifier(P_RULE_SPECIFIER, rule); + if (Do_At_Throws( // might GC + evaluated, + VAL_ARRAY(rule), + VAL_INDEX(rule), + derived + )) { + Move_Value(P_OUT, evaluated); + return R_OUT_IS_THROWN; + } + // ignore evaluated if it's not THROWN? + + if (P_POS > SER_LEN(P_INPUT)) P_POS = SER_LEN(P_INPUT); + FETCH_NEXT_RULE_MAYBE_END(f); + continue; + } + + // Counter? 123 + if (IS_INTEGER(rule)) { // Specify count or range count + flags |= PF_WHILE; + mincount = maxcount = Int32s(const_KNOWN(rule), 0); + + FETCH_NEXT_RULE_MAYBE_END(f); + if (IS_END(P_RULE)) + fail (Error_Parse_End()); + + rule = Get_Parse_Value(save, P_RULE, P_RULE_SPECIFIER); + + if (IS_INTEGER(rule)) { + maxcount = Int32s(const_KNOWN(rule), 0); + + FETCH_NEXT_RULE_MAYBE_END(f); + if (IS_END(P_RULE)) + fail (Error_Parse_End()); + + rule = Get_Parse_Value(save, P_RULE, P_RULE_SPECIFIER); + } + } + // else fall through on other values and words + + //==////////////////////////////////////////////////////////////////==// + // + // ITERATED RULE PROCESSING SECTION + // + //==////////////////////////////////////////////////////////////////==// + + // Repeats the same rule N times or until the rule fails. + // The index is advanced and stored in a temp variable i until + // the entire rule has been satisfied. + + FETCH_NEXT_RULE_MAYBE_END(f); + + begin = P_POS;// input at beginning of match section + + REBINT count; // gotos would cross initialization + count = 0; + while (count < maxcount) { + if (IS_BLANK(rule)) // these type tests should be in a switch + break; + + if (IS_BAR(rule)) + fail (Error_Parse_Rule()); // !!! Is this possible? + + REBIXO i; // temp index point + + if (IS_WORD(rule)) { + REBSYM cmd = VAL_CMD(rule); + + switch (cmd) { + case SYM_SKIP: + i = (P_POS < SER_LEN(P_INPUT)) + ? P_POS + 1 + : END_FLAG; + break; + + case SYM_END: + i = (P_POS < SER_LEN(P_INPUT)) + ? END_FLAG + : SER_LEN(P_INPUT); + break; + + case SYM_TO: + case SYM_THRU: { + if (IS_END(P_RULE)) + fail (Error_Parse_End()); + + if (!subrule) { // capture only on iteration #1 + subrule = Get_Parse_Value( + save, P_RULE, P_RULE_SPECIFIER + ); + FETCH_NEXT_RULE_MAYBE_END(f); + } + + REBOOL is_thru = LOGICAL(cmd == SYM_THRU); + + if (IS_BLOCK(subrule)) + i = To_Thru_Block_Rule(f, subrule, is_thru); + else + i = To_Thru_Non_Block_Rule(f, subrule, is_thru); + break; } + + case SYM_QUOTE: { + if (NOT_SER_FLAG(P_INPUT, SERIES_FLAG_ARRAY)) + fail (Error_Parse_Rule()); // see #2253 + + if (IS_END(P_RULE)) + fail (Error_Parse_End()); + + if (!subrule) { // capture only on iteration #1 + subrule = P_RULE; + FETCH_NEXT_RULE_MAYBE_END(f); + } + + RELVAL *cmp = ARR_AT(ARR(P_INPUT), P_POS); + + if (IS_END(cmp)) + i = END_FLAG; + else if (0 == Cmp_Value(cmp, subrule, P_HAS_CASE)) + i = P_POS + 1; + else + i = END_FLAG; + break; + } + + case SYM_INTO: { + if (IS_END(P_RULE)) + fail (Error_Parse_End()); + + if (!subrule) { + subrule = Get_Parse_Value( + save, P_RULE, P_RULE_SPECIFIER + ); + FETCH_NEXT_RULE_MAYBE_END(f); + } + + if (!IS_BLOCK(subrule)) + fail (Error_Parse_Rule()); + + RELVAL *into = ARR_AT(ARR(P_INPUT), P_POS); + + if ( + IS_END(into) + || (!ANY_BINSTR(into) && !ANY_ARRAY(into)) + ){ + i = END_FLAG; + break; + } + + REBOOL interrupted; + if (Subparse_Throws( + &interrupted, + P_CELL, + into, + P_INPUT_SPECIFIER, // val was taken from P_INPUT + subrule, + P_RULE_SPECIFIER, + P_FIND_FLAGS + )) { + Move_Value(P_OUT, P_CELL); + return R_OUT_IS_THROWN; + } + + // !!! ignore interrupted? (e.g. ACCEPT or REJECT ran) + + if (IS_BLANK(P_CELL)) { + i = END_FLAG; + } + else { + assert(IS_INTEGER(P_CELL)); + if (VAL_UNT32(P_CELL) != VAL_LEN_HEAD(into)) + i = END_FLAG; + else + i = P_POS + 1; + } + break; + } + + case SYM_DO: { + if (subrule != NULL) { + // + // Not currently set up for iterating DO rules + // since the Do_Eval_Rule routine expects to be + // able to arbitrarily update P_NEXT_RULE + // + fail ("DO rules currently cannot be iterated"); + } + + subrule = BLANK_VALUE; // cause an error if iterating + + i = Do_Eval_Rule(f); // changes P_RULE (should) + + if (i == THROWN_FLAG) return R_OUT_IS_THROWN; + + break; + } + + default: + fail (Error_Parse_Rule()); + } + } + else if (IS_BLOCK(rule)) { + REBOOL interrupted; + if (Subparse_Throws( + &interrupted, + P_CELL, + P_INPUT_VALUE, + SPECIFIED, + rule, + P_RULE_SPECIFIER, + P_FIND_FLAGS + )) { + Move_Value(P_OUT, P_CELL); + return R_OUT_IS_THROWN; + } + + // Non-breaking out of loop instances of match or not. + + if (IS_BLANK(P_CELL)) + i = END_FLAG; + else { + assert(IS_INTEGER(P_CELL)); + i = VAL_INT32(P_CELL); + } + + if (interrupted) { // ACCEPT or REJECT ran + assert(i != THROWN_FLAG); + if (i == END_FLAG) + P_POS = NOT_FOUND; + else + P_POS = cast(REBCNT, i); + break; + } + } + else { + // Parse according to datatype + + if (GET_SER_FLAG(P_INPUT, SERIES_FLAG_ARRAY)) + i = Parse_Array_One_Rule(f, rule); + else + i = Parse_String_One_Rule(f, rule); + + // i may be THROWN_FLAG + } + + if (i == THROWN_FLAG) + return R_OUT_IS_THROWN; + + // Necessary for special cases like: some [to end] + // i: indicates new index or failure of the match, but + // that does not mean failure of the rule, because optional + // matches can still succeed, if if the last match failed. + // + if (i != END_FLAG) { + count++; // may overflow to negative + + if (count < 0) + count = MAX_I32; // the forever case + + if (i == P_POS && NOT(flags & PF_WHILE)) { + // + // input did not advance + + if (count < mincount) { + P_POS = NOT_FOUND; // was not enough + } + break; + } + } + else { + if (count < mincount) { + P_POS = NOT_FOUND; // was not enough + } + else if (i != END_FLAG) { + P_POS = cast(REBCNT, i); + } + else { + // just keep index as is. + } + break; + } + P_POS = cast(REBCNT, i); + } + + if (P_POS > SER_LEN(P_INPUT)) + P_POS = NOT_FOUND; + + //==////////////////////////////////////////////////////////////////==// + // + // "POST-MATCH PROCESSING" + // + //==////////////////////////////////////////////////////////////////==// + + // The comment here says "post match processing", but it may be a + // failure signal. Or it may have been a success and there could be + // a NOT to apply. Note that failure here doesn't mean returning + // from SUBPARSE, as there still may be alternate rules to apply + // with bar e.g. `[a | b | c]`. + + post_match_processing: + if (flags) { + if (flags & PF_NOT) { + if ((flags & PF_NOT2) && P_POS != NOT_FOUND) + P_POS = NOT_FOUND; + else + P_POS = begin; + } + + if (P_POS == NOT_FOUND) { + if (flags & PF_THEN) { + FETCH_TO_BAR_MAYBE_END(f); + if (NOT_END(P_RULE)) + FETCH_NEXT_RULE_MAYBE_END(f); + } + } + else { + // Set count to how much input was advanced + // + count = (begin > P_POS) ? 0 : P_POS - begin; + + if (flags & PF_COPY) { + DECLARE_LOCAL (temp); + Init_Any_Series( + temp, + P_TYPE, + GET_SER_FLAG(P_INPUT, SERIES_FLAG_ARRAY) + ? SER(Copy_Array_At_Max_Shallow( + ARR(P_INPUT), + begin, + P_INPUT_SPECIFIER, + count + )) + : Copy_String_Slimming(P_INPUT, begin, count) + ); + + Move_Value( + Sink_Var_May_Fail(set_or_copy_word, P_RULE_SPECIFIER), + temp + ); + } + else if (flags & PF_SET) { + if (GET_SER_FLAG(P_INPUT, SERIES_FLAG_ARRAY)) { + if (count != 0) + Derelativize( + Sink_Var_May_Fail( + set_or_copy_word, P_RULE_SPECIFIER + ), + ARR_AT(ARR(P_INPUT), begin), + P_INPUT_SPECIFIER + ); + else + NOOP; // !!! leave as-is on 0 count? + } + else { + if (count != 0) { + REBVAL *var = Sink_Var_May_Fail( + set_or_copy_word, P_RULE_SPECIFIER + ); + REBUNI ch = GET_ANY_CHAR(P_INPUT, begin); + if (P_TYPE == REB_BINARY) + Init_Integer(var, ch); + else + Init_Char(var, ch); + } + else + NOOP; // !!! leave as-is on 0 count? + } + } + + if (flags & PF_RETURN) { + // + // See notes in PARSE native on handling of SYM_RETURN + // + DECLARE_LOCAL (captured); + Init_Any_Series( + captured, + P_TYPE, + GET_SER_FLAG(P_INPUT, SERIES_FLAG_ARRAY) + ? SER(Copy_Array_At_Max_Shallow( + ARR(P_INPUT), + begin, + P_INPUT_SPECIFIER, + count + )) + : Copy_String_Slimming(P_INPUT, begin, count) + ); + + Move_Value(P_OUT, NAT_VALUE(parse)); + CONVERT_NAME_TO_THROWN(P_OUT, captured); + return R_OUT_IS_THROWN; + } + + if (flags & PF_REMOVE) { + if (count) Remove_Series(P_INPUT, begin, count); + P_POS = begin; + } + + if (flags & (PF_INSERT | PF_CHANGE)) { + count = (flags & PF_INSERT) ? 0 : count; + REBCNT mod_flags = (flags & PF_INSERT) ? 0 : AM_PART; + + if (IS_END(P_RULE)) + fail (Error_Parse_End()); + + if (IS_WORD(P_RULE)) { // check for ONLY flag + REBSYM cmd = VAL_CMD(P_RULE); + switch (cmd) { + case SYM_ONLY: + mod_flags |= AM_ONLY; + FETCH_NEXT_RULE_MAYBE_END(f); + if (IS_END(P_RULE)) + fail (Error_Parse_End()); + break; + + case SYM_0: // not a "parse command" word, keep going + break; + + default: // other commands invalid after INSERT/CHANGE + fail (Error_Parse_Rule()); + } + } + + // new value...comment said "CHECK FOR QUOTE!!" + rule = Get_Parse_Value(save, P_RULE, P_RULE_SPECIFIER); + FETCH_NEXT_RULE_MAYBE_END(f); + + if (GET_SER_FLAG(P_INPUT, SERIES_FLAG_ARRAY)) { + DECLARE_LOCAL (specified); + Derelativize(specified, rule, P_RULE_SPECIFIER); + + P_POS = Modify_Array( + (flags & PF_CHANGE) ? SYM_CHANGE : SYM_INSERT, + ARR(P_INPUT), + begin, + specified, + mod_flags, + count, + 1 + ); + + if (IS_LIT_WORD(rule)) + VAL_SET_TYPE_BITS( // keeps binding flags + ARR_AT(ARR(P_INPUT), P_POS - 1), + REB_WORD + ); + } + else { + DECLARE_LOCAL (specified); + Derelativize(specified, rule, P_RULE_SPECIFIER); + + if (P_TYPE == REB_BINARY) + mod_flags |= AM_BINARY_SERIES; + + P_POS = Modify_String( + (flags & PF_CHANGE) ? SYM_CHANGE : SYM_INSERT, + P_INPUT, + begin, + specified, + mod_flags, + count, + 1 + ); + } + } + + if (flags & PF_AND) P_POS = begin; + } + + flags = 0; + set_or_copy_word = NULL; + } + + if (P_POS == NOT_FOUND) { + // + // If a rule fails but "falls through", there may still be other + // options later in the block to consider separated by |. + + FETCH_TO_BAR_MAYBE_END(f); + if (IS_END(P_RULE)) { // no alternate rule + Init_Blank(P_OUT); + return R_OUT; + } + + // Jump to the alternate rule and reset input + // + FETCH_NEXT_RULE_MAYBE_END(f); + P_POS = begin = start; + } + + begin = P_POS; + mincount = maxcount = 1; + } + + Init_Integer(P_OUT, P_POS); // !!! return switched input series?? + return R_OUT; } -/*********************************************************************** -** -*/ REBSER *Parse_Lines(REBSER *src) -/* -** Convert a string buffer to a block of strings. -** Note that the string must already be converted -** to REBOL LF format (no CRs). -** -***********************************************************************/ +// +// parse: native [ +// +// "Parses a series according to grammar rules and returns a result." +// +// input [any-series!] +// "Input series to parse (default result for successful match)" +// rules [block! string! blank!] +// "Rules to parse by (STRING! and BLANK!/none! are deprecated)" +// /case +// "Uses case-sensitive comparison" +// ] +// +REBNATIVE(parse) { - REBSER *blk; - REBUNI c; - REBCNT i; - REBCNT s; - REBVAL *val; - REBOOL uni = !BYTE_SIZE(src); - REBYTE *bp = BIN_HEAD(src); - REBUNI *up = UNI_HEAD(src); - - blk = BUF_EMIT; - RESET_SERIES(blk); - - // Scan string, looking for LF and CR terminators: - for (i = s = 0; i < SERIES_TAIL(src); i++) { - c = uni ? up[i] : bp[i]; - if (c == LF || c == CR) { - val = Append_Value(blk); - Set_String(val, Copy_String(src, s, i - s)); - VAL_SET_LINE(val); - // Skip CRLF if found: - if (c == CR && LF == uni ? up[i] : bp[i]) i++; - s = i; - } - } - - // Partial line (no linefeed): - if (s + 1 != i) { - val = Append_Value(blk); - Set_String(val, Copy_String(src, s, i - s)); - VAL_SET_LINE(val); - } - - return Copy_Block(blk, 0); + INCLUDE_PARAMS_OF_PARSE; + + REBVAL *rules = ARG(rules); + + if (IS_BLANK(rules) || IS_STRING(rules)) { + // + // !!! R3-Alpha supported "simple parse", which was cued by the rules + // being either NONE! or a STRING!. Though this functionality does + // not exist in Ren-C, it's more informative to give an error telling + // where to look for the functionality than a generic "parse doesn't + // take that type" error. + // + fail (Error_Use_Split_Simple_Raw()); + } + + REBOOL interrupted; + if (Subparse_Throws( + &interrupted, + D_OUT, + ARG(input), + SPECIFIED, // input is a non-relative REBVAL + rules, + SPECIFIED, // rules is a non-relative REBVAL + REF(case) || IS_BINARY(ARG(input)) ? AM_FIND_CASE : 0 + // + // We always want "case-sensitivity" on binary bytes, vs. treating + // as case-insensitive bytes for ASCII characters. + )) { + if ( + IS_FUNCTION(D_OUT) + && NAT_FUNC(parse) == VAL_FUNC(D_OUT) + ) { + // Note the difference: + // + // parse "1020" [(return true) not-seen] + // parse "0304" [return [some ["0" skip]]] not-seen] + // + // In the first, a parenthesized evaluation ran a `return`, which + // is aiming to return from a function using a THROWN(). In + // the second case parse interrupted *itself* with a THROWN_FLAG + // to evaluate the expression to the result "0304" from the + // matched pattern. + // + // When parse interrupts itself by throwing, it indicates so + // by using the throw name of its own REB_NATIVE-valued function. + // This handles that branch and catches the result value. + // + CATCH_THROWN(D_OUT, D_OUT); + return R_OUT; + } + + // All other throws should just bubble up uncaught. + // + return R_OUT_IS_THROWN; + } + + // Parse can fail if the match rule state can't process pending input. + // + if (IS_BLANK(D_OUT)) + return R_FALSE; + + assert(IS_INTEGER(D_OUT)); + + // If the match rules all completed, but the parse position didn't end + // at (or beyond) the tail of the input series, the parse also failed. + // + if (VAL_UNT32(D_OUT) < VAL_LEN_HEAD(ARG(input))) + return R_FALSE; + + // The end was reached. Return TRUE. (Alternate thoughts, see #2165) + // + return R_TRUE; } -/*********************************************************************** -** -*/ REBNATIVE(parse) -/* -***********************************************************************/ +// +// parse-accept: native [ +// +// "Accept the current parse rule (Internal Implementation Detail ATM)." +// +// ] +// +REBNATIVE(parse_accept) +// +// !!! This was not created for user usage, but rather as a label for the +// internal throw used to indicate "accept". { - REBVAL *val = D_ARG(1); - REBVAL *arg = D_ARG(2); - REBCNT opts = 0; - - if (D_REF(3)) opts |= PF_ALL; - if (D_REF(4)) opts |= PF_CASE; - - if (IS_BINARY(val)) opts |= PF_ALL | PF_CASE; - - // Is it a simple string? - if (IS_NONE(arg) || IS_STRING(arg) || IS_CHAR(arg)) { - REBSER *ser; - if (!ANY_BINSTR(val)) Trap_Types(RE_EXPECT_VAL, REB_STRING, VAL_TYPE(val)); - ser = Parse_String(VAL_SERIES(val), VAL_INDEX(val), arg, opts); - Set_Block(DS_RETURN, ser); - } - else if (IS_SAME_WORD(arg, SYM_TEXT)) { - Set_Block(DS_RETURN, Parse_Lines(VAL_SERIES(val))); - } - else { - REBCNT n; - REBOL_STATE state; - // Let user RETURN and THROW out of the PARSE. All other errors should relay. - PUSH_STATE(state, Saved_State); - if (SET_JUMP(state)) { - POP_STATE(state, Saved_State); - Catch_Error(arg = DS_RETURN); // Stores error value here - if (VAL_ERR_NUM(arg) == RE_BREAK) { - if (!VAL_ERR_VALUE(arg)) return R_NONE; - *DS_RETURN = *VAL_ERR_VALUE(arg); - return R_RET; - } - if (VAL_ERR_NUM(arg) == RE_RETURN && VAL_ERR_SYM(arg) == SYM_RETURN) { - *DS_RETURN = *VAL_ERR_VALUE(arg); - return R_RET; - } - // How to handle RETURN, BREAK, etc. ???? does not work !!!! - if (THROWN(DS_RETURN)) return R_RET; //Throw_Break(DS_RETURN); - Throw_Error(VAL_ERR_OBJECT(DS_RETURN)); - } - SET_STATE(state, Saved_State); - n = Parse_Series(val, VAL_BLK_DATA(arg), (opts & PF_CASE) ? AM_FIND_CASE : 0, 0); - SET_LOGIC(DS_RETURN, n >= VAL_TAIL(val) && n != NOT_FOUND); - POP_STATE(state, Saved_State); - } - - return R_RET; + UNUSED(frame_); + fail ("PARSE-ACCEPT is for internal PARSE use only"); } - -#ifdef save_unused -/*********************************************************************** -** -*/ static REBFLG Get_Index_Var(REBVAL *item, REBSER *series, REBINT *index) -/* -** Get the series index from a word or path or integer. -** -** Returns: TRUE if value was a series. FALSE if integer. -** -***********************************************************************/ +// +// parse-reject: native [ +// +// "Reject the current parse rule (Internal Implementation Detail ATM)." +// +// ] +// +REBNATIVE(parse_reject) +// +// !!! This was not created for user usage, but rather as a label for the +// internal throw used to indicate "reject". { - REBVAL *hold = item; - - if (IS_END(item)) Trap1(RE_PARSE_END, item); - - if (IS_WORD(item)) { - if (!VAL_CMD(item)) item = Get_Var(item); - } - else if (IS_PATH(item)) { - REBVAL *path = item; - Do_Path(&path, 0); //!!! function! - item = DS_TOP; - } - else if (!IS_INTEGER(item)) - Trap1(RE_PARSE_VARIABLE, hold); - - if (IS_INTEGER(item)) { - *index = Int32(item); - return FALSE; - } - - if (!ANY_SERIES(item) || VAL_SERIES(item) != series) - Trap1(RE_PARSE_SERIES, hold); - - *index = VAL_INDEX(item); - return TRUE; + UNUSED(frame_); + fail ("PARSE-REJECT is for internal PARSE use only"); } -#endif - diff --git a/src/core/u-png.c b/src/core/u-png.c deleted file mode 100644 index f0ea17caaf..0000000000 --- a/src/core/u-png.c +++ /dev/null @@ -1,861 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Module: u-png.c -** Summary: PNG image format conversion -** Section: utility -** Notes: -** This is an optional part of R3. This file can be replaced by -** library function calls into an updated implementation. -** -***********************************************************************/ - -#include "sys-core.h" -#include "sys-zlib.h" -#include // remove this later !!!! - -#if defined(ENDIAN_LITTLE) -#define CVT_END_L(a) a=(a<<24)|(((a>>8)&255)<<16)|(((a>>16)&255)<<8)|(a>>24) -#elif defined(ENDIAN_BIG) -#define CVT_END_L(a) -#else -#error Endianness must be defined in system.h -#endif - -#define int_abs(a) (((a)<0)?(-(a)):(a)) - -/**********************************************************************/ - -static struct png_ihdr { - unsigned int width; - unsigned int height; - unsigned char bit_depth; - unsigned char color_type; - unsigned char compression_method; - unsigned char filter_method; - unsigned char interlace_method; -} png_ihdr; - -static unsigned char colormodes[]={0x1f,0x00,0x18,0x0f,0x18,0x00,0x18}; -static unsigned char colormult[]={1,0,3,1,2,0,4}; - -static unsigned char adam7hoff[]={0,4,0,2,0,1,0}; -static unsigned char adam7hskip[]={8,8,4,4,2,2,1}; -static unsigned char adam7voff[]={0,0,4,0,2,0,1}; -static unsigned char adam7vskip[]={8,8,8,4,4,2,2}; -static unsigned char bytetab2[]={0x00,0x55,0xaa,0xff}; - -static int log2bitdepth; -static char haspalette; -static int bytesperpixel; -static int bitsperpixel; -static int rowlength; -static char hasalpha; -static unsigned char *imgbuffer; -static unsigned int palette[256]; -static unsigned short palette_alpha[256]; -static unsigned int *img_output; -static unsigned int transparent_red,transparent_green,transparent_blue; -static unsigned int transparent_gray; -static void (*process_row)(unsigned char *p,int width,int r,int hoff,int hskip); - -static void process_row_0_1(unsigned char *p,int width,int r,int hoff,int hskip); -static void process_row_0_2(unsigned char *p,int width,int r,int hoff,int hskip); -static void process_row_0_4(unsigned char *p,int width,int r,int hoff,int hskip); -static void process_row_0_8(unsigned char *p,int width,int r,int hoff,int hskip); -static void process_row_0_16(unsigned char *p,int width,int r,int hoff,int hskip); -static void process_row_2_8(unsigned char *p,int width,int r,int hoff,int hskip); -static void process_row_2_16(unsigned char *p,int width,int r,int hoff,int hskip); -static void process_row_3_1(unsigned char *p,int width,int r,int hoff,int hskip); -static void process_row_3_2(unsigned char *p,int width,int r,int hoff,int hskip); -static void process_row_3_4(unsigned char *p,int width,int r,int hoff,int hskip); -static void process_row_3_8(unsigned char *p,int width,int r,int hoff,int hskip); -static void process_row_4_8(unsigned char *p,int width,int r,int hoff,int hskip); -static void process_row_4_16(unsigned char *p,int width,int r,int hoff,int hskip); -static void process_row_6_8(unsigned char *p,int width,int r,int hoff,int hskip); -static void process_row_6_16(unsigned char *p,int width,int r,int hoff,int hskip); - -static void *process_row0[]={(void *)process_row_0_1,(void *)process_row_0_2,(void *)process_row_0_4, - (void *)process_row_0_8,(void *)process_row_0_16}; -static void *process_row2[]={0,0,0,(void *)process_row_2_8,(void *)process_row_2_16}; -static void *process_row3[]={(void *)process_row_3_1,(void *)process_row_3_2,(void *)process_row_3_4, - (void *)process_row_3_8}; -static void *process_row4[]={0,0,0,(void *)process_row_4_8,(void *)process_row_4_16}; -static void *process_row6[]={0,0,0,(void *)process_row_6_8,(void *)process_row_6_16}; - -static void **process_row_lookup[]={process_row0,0,process_row2,process_row3,process_row4,0,process_row6}; - -jmp_buf png_state; - -static void trap_png(void) -{ - longjmp(png_state, 1); -} - -/**********************************************************************/ - -static int find_msb(int val) { - int i; - for(i=30;val<(1<256*3)) - trap_png(); - for(i=0;i256) - length=256; - for(i=0;i>16; - green=(color>>8)&255; - blue=color&255; - return (((65535-alpha)/255)<<24)|(red<<16)|(green<<8)|blue; - } -} - -static void process_row_0_1(unsigned char *p,int width,int r,int hoff,int hskip) { - int c; - unsigned char m; - unsigned int v,*imgp; - - imgp=img_output+r*png_ihdr.width+hoff; - for(c=0;c>7; - if(v==transparent_gray) { - hasalpha=TRUE; - *imgp=0xff000000; - } else - *imgp=(v?0xffffff:0); - imgp+=hskip; - m<<=1; - } -} - -static void process_row_0_2(unsigned char *p,int width,int r,int hoff,int hskip) { - int c; - unsigned char m; - unsigned int v,*imgp; - - imgp=img_output+r*png_ihdr.width+hoff; - for(c=0;c>6; - if(v==transparent_gray) { - hasalpha=TRUE; - *imgp=0xff000000; - } else { - v=bytetab2[v]; - v|=(v<<8)|(v<<16); - *imgp=v; - } - imgp+=hskip; - m<<=2; - } -} - -static void process_row_0_4(unsigned char *p,int width,int r,int hoff,int hskip) { - int c; - unsigned char m; - unsigned int v,*imgp; - - imgp=img_output+r*png_ihdr.width+hoff; - for(c=0;c>4; - if(v==transparent_gray) { - hasalpha=TRUE; - *imgp=0xff000000; - } else { - v|=(v<<4); - v|=(v<<8)|(v<<16); - *imgp=v; - } - imgp+=hskip; - m<<=4; - } -} - -static void process_row_0_8(unsigned char *p,int width,int r,int hoff,int hskip) { - int c; - unsigned int v,*imgp; - - imgp=img_output+r*png_ihdr.width+hoff; - for(c=0;c>=8; - v|=(v<<8)|(v<<16); - *imgp=v; - } - imgp+=hskip; - } -} - -static void process_row_2_8(unsigned char *p,int width,int r,int hoff,int hskip) { - int c; - unsigned int *imgp,red,green,blue; - - imgp=img_output+r*png_ihdr.width+hoff; - for(c=0;c>8)<<16)|(green&0xff00)|(blue>>8); - imgp+=hskip; - } -} - -static void process_row_3_1(unsigned char *p,int width,int r,int hoff,int hskip) { - int c; - unsigned char m; - unsigned int v,*imgp; - - imgp=img_output+r*png_ihdr.width+hoff; - for(c=0;c>7; - *imgp=calc_color(palette[v],palette_alpha[v]); - imgp+=hskip; - m<<=1; - } -} - -static void process_row_3_2(unsigned char *p,int width,int r,int hoff,int hskip) { - int c; - unsigned char m; - unsigned int v,*imgp; - - imgp=img_output+r*png_ihdr.width+hoff; - for(c=0;c>6; - *imgp=calc_color(palette[v],palette_alpha[v]); - imgp+=hskip; - m<<=2; - } -} - -static void process_row_3_4(unsigned char *p,int width,int r,int hoff,int hskip) { - int c; - unsigned char m; - unsigned int v,*imgp; - - imgp=img_output+r*png_ihdr.width+hoff; - for(c=0;c>4; - *imgp=calc_color(palette[v],palette_alpha[v]); - imgp+=hskip; - m<<=4; - } -} - -static void process_row_3_8(unsigned char *p,int width,int r,int hoff,int hskip) { - int c; - unsigned int v,*imgp; - - imgp=img_output+r*png_ihdr.width+hoff; - for(c=0;c4)||(png_ihdr.color_type>6)||png_ihdr.compression_method|| - png_ihdr.filter_method||(png_ihdr.interlace_method>1)|| - (!(colormodes[png_ihdr.color_type]&(1<bits, w, h) -** Output: PNG encoded image (codi->data, len) -** Error: Code in codi->error -** -***********************************************************************/ -{ - REBINT w = codi->w; - REBINT h = codi->h; - struct ihdrchunk ihdr; - struct idatnode *firstidat,*currentidat; - unsigned char *linebuf,*cp; - int x,y,imgsize,ret; - z_stream zstream={0}; - REBCNT *dp,cv; - REBOOL hasalpha; - - hasalpha = codi->alpha; - - ihdr.width=w; - CVT_END_L(ihdr.width); - ihdr.height=h; - CVT_END_L(ihdr.height); - ihdr.depth=8; - ihdr.color_type=(hasalpha?6:2); - ihdr.comp_method=0; - ihdr.filter_method=0; - ihdr.interlace_method=0; - - linebuf=malloc(hasalpha?(4*w+1):(3*w+1)); - firstidat=currentidat=malloc(sizeof(struct idatnode)); - - if(!firstidat) { - free(linebuf); - trap_png(); - } - - currentidat->next=0; - deflateInit(&zstream, Z_DEFAULT_COMPRESSION); - zstream.next_out=currentidat->data; - zstream.avail_out=IDATLENGTH; - dp=codi->bits; - for(y=0;y>16; - *cp++=cv>>8; - *cp++=cv; - if(hasalpha) - *cp++=255-(cv>>24); - } - zstream.next_in=linebuf; - zstream.avail_in=(hasalpha?(4*w+1):(3*w+1)); - while(zstream.avail_in||(y==h-1)) { - if(!zstream.avail_out) - goto refill; - ret=deflate(&zstream,(y==h-1)?Z_FINISH:0); - if((ret==Z_OK)||(ret==Z_BUF_ERROR)) - continue; - if(ret==Z_STREAM_END) - break; - - codi->error = CODI_ERR_ENCODING; - goto error; - - refill: - currentidat->length=IDATLENGTH; - currentidat->next=malloc(sizeof(struct idatnode)); - currentidat=currentidat->next; - currentidat->next=0; - zstream.next_out=currentidat->data; - zstream.avail_out=IDATLENGTH; - } - } - currentidat->length=IDATLENGTH-zstream.avail_out; - deflateEnd(&zstream); - imgsize=8+(12+13)+(12+19)+(12+0); - currentidat=firstidat; - while(currentidat) { - imgsize+=12+currentidat->length; - currentidat=currentidat->next; - } - - codi->data = Make_Mem(imgsize); - codi->len = imgsize; - - cp=(unsigned char *)codi->data; - memcpy(cp,"\211\120\116\107\015\012\032\012", 8); - cp+=8; - emitchunk(&cp,"IHDR",(char *)&ihdr,13); - emitchunk(&cp,"tEXt","Software\000REBOL",14); - currentidat=firstidat; - while(currentidat) { - emitchunk(&cp,"IDAT",(char *)currentidat->data,currentidat->length); - currentidat=currentidat->next; - } - emitchunk(&cp,"IEND",0,0); - -error: - free(linebuf); - while(firstidat) { - currentidat=firstidat->next; - free(firstidat); - firstidat=currentidat; - } -} - - -/*********************************************************************** -** -*/ void Decode_PNG_Image(REBCDI *codi) -/* -** Input: PNG encoded image (codi->data, len) -** Output: Image bits (codi->bits, w, h) -** Error: Code in codi->error -** -***********************************************************************/ -{ - int w, h; - REBOOL alpha = 0; - - if (!png_info(codi->data, codi->len, &w, &h )) trap_png(); - codi->w = w; - codi->h = h; - codi->bits = Make_Mem(w * h * 4); - png_load((unsigned char *)(codi->data), codi->len, (unsigned char *)(codi->bits), &alpha); - - //if(alpha) VAL_IMAGE_TRANSP(Temp_Value)=VITT_ALPHA; -} - - -/*********************************************************************** -** -*/ REBINT Codec_PNG_Image(REBCDI *codi) -/* -***********************************************************************/ -{ - codi->error = 0; - - // Handle JPEG error throw: - if (setjmp(png_state)) { - codi->error = CODI_ERR_BAD_DATA; // generic - if (codi->action == CODI_IDENTIFY) return CODI_CHECK; - return CODI_ERROR; - } - - if (codi->action == CODI_IDENTIFY) { - if (!png_info(codi->data, codi->len, 0, 0)) codi->error = CODI_ERR_SIGNATURE; - return CODI_CHECK; // error code is inverted result - } - - if (codi->action == CODI_DECODE) { - Decode_PNG_Image(codi); - return CODI_IMAGE; - } - - if (codi->action == CODI_ENCODE) { - Encode_PNG_Image(codi); - return CODI_BINARY; - } - - codi->error = CODI_ERR_NA; - return CODI_ERROR; -} - - -/*********************************************************************** -** -*/ void Init_PNG_Codec(void) -/* -***********************************************************************/ -{ - Register_Codec("png", Codec_PNG_Image); -} diff --git a/src/core/u-sha1.c b/src/core/u-sha1.c index 7eb450e256..f5370a3b19 100644 --- a/src/core/u-sha1.c +++ b/src/core/u-sha1.c @@ -56,7 +56,7 @@ * [including the GNU Public Licence.] */ -#include +// #include // !!! No in Ren-C release builds #include #include #include "sys-core.h" @@ -65,146 +65,141 @@ #error Endianness must be defined in rebol.h for builds including SHA1 #endif -#ifdef __cplusplus -extern "C" { -#endif - #define SHA_DEFINED -#define SHA_CBLOCK 64 -#define SHA_LBLOCK 16 -#define SHA_BLOCK 16 +#define SHA_CBLOCK 64 +#define SHA_LBLOCK 16 +#define SHA_BLOCK 16 #define SHA_LAST_BLOCK 56 #define SHA_LENGTH_BLOCK 8 #define SHA_DIGEST_LENGTH 20 +#define SHA_LONG u32 + typedef struct SHAstate_st - { - unsigned long h0,h1,h2,h3,h4; - unsigned long Nl,Nh; - unsigned long data[SHA_LBLOCK]; - int num; - } SHA_CTX; - -void SHA1_Init(SHA_CTX *c); -void SHA1_Update(SHA_CTX *c, unsigned char *data, unsigned long len); -void SHA1_Final(unsigned char *md, SHA_CTX *c); -int SHA1_CtxSize(void); -//unsigned char *SHA1(unsigned char *d, unsigned long n,unsigned char *md); + { + SHA_LONG h0,h1,h2,h3,h4; + SHA_LONG Nl,Nh; + SHA_LONG data[SHA_LBLOCK]; + unsigned int num; + } SHA_CTX; + +EXTERN_C void SHA1_Init(void *c); +EXTERN_C void SHA1_Update(void *c, unsigned char *data, size_t len); +EXTERN_C void SHA1_Final(unsigned char *md, void *c); +EXTERN_C int SHA1_CtxSize(void); + +//unsigned char *SHA1(unsigned char *d, SHA_LONG n,unsigned char *md); //static void SHA1_Transform(SHA_CTX *c, unsigned char *data); -#ifdef __cplusplus -} -#endif - -#define ULONG unsigned long -#define UCHAR unsigned char -#define UINT unsigned int +#define ULONG SHA_LONG +#define UCHAR unsigned char +#define UINT unsigned int #ifdef NOCONST #define const #endif #undef c2nl -#define c2nl(c,l) (l =(((unsigned long)(*((c)++)))<<24), \ - l|=(((unsigned long)(*((c)++)))<<16), \ - l|=(((unsigned long)(*((c)++)))<< 8), \ - l|=(((unsigned long)(*((c)++))) )) +#define c2nl(c,l) (l =(((SHA_LONG)(*((c)++)))<<24), \ + l|=(((SHA_LONG)(*((c)++)))<<16), \ + l|=(((SHA_LONG)(*((c)++)))<< 8), \ + l|=(((SHA_LONG)(*((c)++))) )) #undef p_c2nl -#define p_c2nl(c,l,n) { \ - switch (n) { \ - case 0: l =((unsigned long)(*((c)++)))<<24; \ - case 1: l|=((unsigned long)(*((c)++)))<<16; \ - case 2: l|=((unsigned long)(*((c)++)))<< 8; \ - case 3: l|=((unsigned long)(*((c)++))); \ - } \ - } +#define p_c2nl(c,l,n) { \ + switch (n) { \ + case 0: l =((SHA_LONG)(*((c)++)))<<24; \ + case 1: l|=((SHA_LONG)(*((c)++)))<<16; \ + case 2: l|=((SHA_LONG)(*((c)++)))<< 8; \ + case 3: l|=((SHA_LONG)(*((c)++))); \ + } \ + } #undef c2nl_p /* NOTE the pointer is not incremented at the end of this */ -#define c2nl_p(c,l,n) { \ - l=0; \ - (c)+=n; \ - switch (n) { \ - case 3: l =((unsigned long)(*(--(c))))<< 8; \ - case 2: l|=((unsigned long)(*(--(c))))<<16; \ - case 1: l|=((unsigned long)(*(--(c))))<<24; \ - } \ - } +#define c2nl_p(c,l,n) { \ + l=0; \ + (c)+=n; \ + switch (n) { \ + case 3: l =((SHA_LONG)(*(--(c))))<< 8; \ + case 2: l|=((SHA_LONG)(*(--(c))))<<16; \ + case 1: l|=((SHA_LONG)(*(--(c))))<<24; \ + } \ + } #undef p_c2nl_p #define p_c2nl_p(c,l,sc,len) { \ - switch (sc) \ - { \ - case 0: l =((unsigned long)(*((c)++)))<<24; \ - if (--len == 0) break; \ - case 1: l|=((unsigned long)(*((c)++)))<<16; \ - if (--len == 0) break; \ - case 2: l|=((unsigned long)(*((c)++)))<< 8; \ - } \ - } + switch (sc) \ + { \ + case 0: l =((SHA_LONG)(*((c)++)))<<24; \ + if (--len == 0) break; \ + case 1: l|=((SHA_LONG)(*((c)++)))<<16; \ + if (--len == 0) break; \ + case 2: l|=((SHA_LONG)(*((c)++)))<< 8; \ + } \ + } #undef nl2c -#define nl2c(l,c) (*((c)++)=(unsigned char)(((l)>>24)&0xff), \ - *((c)++)=(unsigned char)(((l)>>16)&0xff), \ - *((c)++)=(unsigned char)(((l)>> 8)&0xff), \ - *((c)++)=(unsigned char)(((l) )&0xff)) +#define nl2c(l,c) (*((c)++)=(unsigned char)(((l)>>24)&0xff), \ + *((c)++)=(unsigned char)(((l)>>16)&0xff), \ + *((c)++)=(unsigned char)(((l)>> 8)&0xff), \ + *((c)++)=(unsigned char)(((l) )&0xff)) #undef c2l -#define c2l(c,l) (l =(((unsigned long)(*((c)++))) ), \ - l|=(((unsigned long)(*((c)++)))<< 8), \ - l|=(((unsigned long)(*((c)++)))<<16), \ - l|=(((unsigned long)(*((c)++)))<<24)) +#define c2l(c,l) (l =(((SHA_LONG)(*((c)++))) ), \ + l|=(((SHA_LONG)(*((c)++)))<< 8), \ + l|=(((SHA_LONG)(*((c)++)))<<16), \ + l|=(((SHA_LONG)(*((c)++)))<<24)) #undef p_c2l -#define p_c2l(c,l,n) { \ - switch (n) { \ - case 0: l =((unsigned long)(*((c)++))); \ - case 1: l|=((unsigned long)(*((c)++)))<< 8; \ - case 2: l|=((unsigned long)(*((c)++)))<<16; \ - case 3: l|=((unsigned long)(*((c)++)))<<24; \ - } \ - } +#define p_c2l(c,l,n) { \ + switch (n) { \ + case 0: l =((SHA_LONG)(*((c)++))); \ + case 1: l|=((SHA_LONG)(*((c)++)))<< 8; \ + case 2: l|=((SHA_LONG)(*((c)++)))<<16; \ + case 3: l|=((SHA_LONG)(*((c)++)))<<24; \ + } \ + } #undef c2l_p /* NOTE the pointer is not incremented at the end of this */ -#define c2l_p(c,l,n) { \ - l=0; \ - (c)+=n; \ - switch (n) { \ - case 3: l =((unsigned long)(*(--(c))))<<16; \ - case 2: l|=((unsigned long)(*(--(c))))<< 8; \ - case 1: l|=((unsigned long)(*(--(c)))); \ - } \ - } +#define c2l_p(c,l,n) { \ + l=0; \ + (c)+=n; \ + switch (n) { \ + case 3: l =((SHA_LONG)(*(--(c))))<<16; \ + case 2: l|=((SHA_LONG)(*(--(c))))<< 8; \ + case 1: l|=((SHA_LONG)(*(--(c)))); \ + } \ + } #undef p_c2l_p #define p_c2l_p(c,l,sc,len) { \ - switch (sc) \ - { \ - case 0: l =((unsigned long)(*((c)++))); \ - if (--len == 0) break; \ - case 1: l|=((unsigned long)(*((c)++)))<< 8; \ - if (--len == 0) break; \ - case 2: l|=((unsigned long)(*((c)++)))<<16; \ - } \ - } + switch (sc) \ + { \ + case 0: l =((SHA_LONG)(*((c)++))); \ + if (--len == 0) break; \ + case 1: l|=((SHA_LONG)(*((c)++)))<< 8; \ + if (--len == 0) break; \ + case 2: l|=((SHA_LONG)(*((c)++)))<<16; \ + } \ + } #undef l2c -#define l2c(l,c) (*((c)++)=(unsigned char)(((l) )&0xff), \ - *((c)++)=(unsigned char)(((l)>> 8)&0xff), \ - *((c)++)=(unsigned char)(((l)>>16)&0xff), \ - *((c)++)=(unsigned char)(((l)>>24)&0xff)) +#define l2c(l,c) (*((c)++)=(unsigned char)(((l) )&0xff), \ + *((c)++)=(unsigned char)(((l)>> 8)&0xff), \ + *((c)++)=(unsigned char)(((l)>>16)&0xff), \ + *((c)++)=(unsigned char)(((l)>>24)&0xff)) #undef ROTATE -#if defined(TO_WIN32) +#if defined(TO_WINDOWS) #define ROTATE(a,n) _lrotl(a,n) #else #if defined(TO_AMIGA_OLD) #define ROTATE(a,n) __builtin_rol(a,n,2) -unsigned long __builtin_rol(unsigned long,int,int); +SHA_LONG __builtin_rol(SHA_LONG,int,int); #else #define ROTATE(a,n) (((a)<<(n))|(((a)&0xffffffff)>>(32-(n)))) #endif @@ -212,21 +207,21 @@ unsigned long __builtin_rol(unsigned long,int,int); /* A nice byte order reversal from Wei Dai */ #if !defined(Endian_Reverse32) -#if defined(TO_WIN32) +#if defined(TO_WINDOWS) /* 5 instructions with rotate instruction, else 9 */ #define Endian_Reverse32(a) \ - { \ - unsigned long l=(a); \ - (a)=((ROTATE(l,8)&0x00FF00FF)|(ROTATE(l,24)&0xFF00FF00)); \ - } + { \ + SHA_LONG l=(a); \ + (a)=((ROTATE(l,8)&0x00FF00FF)|(ROTATE(l,24)&0xFF00FF00)); \ + } #else /* 6 instructions with rotate instruction, else 8 */ #define Endian_Reverse32(a) \ - { \ - unsigned long l=(a); \ - l=(((l&0xFF00FF00)>>8L)|((l&0x00FF00FF)<<8L)); \ - (a)=ROTATE(l,16L); \ - } + { \ + SHA_LONG l=(a); \ + l=(((l&0xFF00FF00)>>8L)|((l&0x00FF00FF)<<8L)); \ + (a)=ROTATE(l,16L); \ + } #endif #endif @@ -237,457 +232,450 @@ unsigned long __builtin_rol(unsigned long,int,int); * I've just become aware of another tweak to be made, again from Wei Dai, * in F_40_59, (x&a)|(y&a) -> (x|y)&a */ -#define F_00_19(b,c,d) ((((c) ^ (d)) & (b)) ^ (d)) -#define F_20_39(b,c,d) ((b) ^ (c) ^ (d)) -#define F_40_59(b,c,d) (((b) & (c)) | (((b)|(c)) & (d))) -#define F_60_79(b,c,d) F_20_39(b,c,d) +#define F_00_19(b,c,d) ((((c) ^ (d)) & (b)) ^ (d)) +#define F_20_39(b,c,d) ((b) ^ (c) ^ (d)) +#define F_40_59(b,c,d) (((b) & (c)) | (((b)|(c)) & (d))) +#define F_60_79(b,c,d) F_20_39(b,c,d) #undef Xupdate #define Xupdate(a,i,ia,ib,ic,id) (a)=\ - (ia[(i)&0x0f]^ib[((i)+2)&0x0f]^ic[((i)+8)&0x0f]^id[((i)+13)&0x0f]);\ - X[(i)&0x0f]=(a)=ROTATE((a),1); + (ia[(i)&0x0f]^ib[((i)+2)&0x0f]^ic[((i)+8)&0x0f]^id[((i)+13)&0x0f]);\ + X[(i)&0x0f]=(a)=ROTATE((a),1); #define BODY_00_15(i,a,b,c,d,e,f,xa) \ - (f)=xa[i]+(e)+K_00_19+ROTATE((a),5)+F_00_19((b),(c),(d)); \ - (b)=ROTATE((b),30); + (f)=xa[i]+(e)+K_00_19+ROTATE((a),5)+F_00_19((b),(c),(d)); \ + (b)=ROTATE((b),30); #define BODY_16_19(i,a,b,c,d,e,f,xa,xb,xc,xd) \ - Xupdate(f,i,xa,xb,xc,xd); \ - (f)+=(e)+K_00_19+ROTATE((a),5)+F_00_19((b),(c),(d)); \ - (b)=ROTATE((b),30); + Xupdate(f,i,xa,xb,xc,xd); \ + (f)+=(e)+K_00_19+ROTATE((a),5)+F_00_19((b),(c),(d)); \ + (b)=ROTATE((b),30); #define BODY_20_31(i,a,b,c,d,e,f,xa,xb,xc,xd) \ - Xupdate(f,i,xa,xb,xc,xd); \ - (f)+=(e)+K_20_39+ROTATE((a),5)+F_20_39((b),(c),(d)); \ - (b)=ROTATE((b),30); + Xupdate(f,i,xa,xb,xc,xd); \ + (f)+=(e)+K_20_39+ROTATE((a),5)+F_20_39((b),(c),(d)); \ + (b)=ROTATE((b),30); #define BODY_32_39(i,a,b,c,d,e,f,xa) \ - Xupdate(f,i,xa,xa,xa,xa); \ - (f)+=(e)+K_20_39+ROTATE((a),5)+F_20_39((b),(c),(d)); \ - (b)=ROTATE((b),30); + Xupdate(f,i,xa,xa,xa,xa); \ + (f)+=(e)+K_20_39+ROTATE((a),5)+F_20_39((b),(c),(d)); \ + (b)=ROTATE((b),30); #define BODY_40_59(i,a,b,c,d,e,f,xa) \ - Xupdate(f,i,xa,xa,xa,xa); \ - (f)+=(e)+K_40_59+ROTATE((a),5)+F_40_59((b),(c),(d)); \ - (b)=ROTATE((b),30); + Xupdate(f,i,xa,xa,xa,xa); \ + (f)+=(e)+K_40_59+ROTATE((a),5)+F_40_59((b),(c),(d)); \ + (b)=ROTATE((b),30); #define BODY_60_79(i,a,b,c,d,e,f,xa) \ - Xupdate(f,i,xa,xa,xa,xa); \ - (f)=X[(i)&0x0f]+(e)+K_60_79+ROTATE((a),5)+F_60_79((b),(c),(d)); \ - (b)=ROTATE((b),30); + Xupdate(f,i,xa,xa,xa,xa); \ + (f)=X[(i)&0x0f]+(e)+K_60_79+ROTATE((a),5)+F_60_79((b),(c),(d)); \ + (b)=ROTATE((b),30); /* Implemented from SHA-1 document - The Secure Hash Algorithm */ -#define INIT_DATA_h0 (unsigned long)0x67452301L -#define INIT_DATA_h1 (unsigned long)0xefcdab89L -#define INIT_DATA_h2 (unsigned long)0x98badcfeL -#define INIT_DATA_h3 (unsigned long)0x10325476L -#define INIT_DATA_h4 (unsigned long)0xc3d2e1f0L +#define INIT_DATA_h0 (SHA_LONG)0x67452301L +#define INIT_DATA_h1 (SHA_LONG)0xefcdab89L +#define INIT_DATA_h2 (SHA_LONG)0x98badcfeL +#define INIT_DATA_h3 (SHA_LONG)0x10325476L +#define INIT_DATA_h4 (SHA_LONG)0xc3d2e1f0L -#define K_00_19 0x5a827999L +#define K_00_19 0x5a827999L #define K_20_39 0x6ed9eba1L #define K_40_59 0x8f1bbcdcL #define K_60_79 0xca62c1d6L # ifdef SHA1_ASM - void sha1_block_x86(SHA_CTX *c, register unsigned long *p, int num); + void sha1_block_x86(SHA_CTX *c, SHA_LONG *p, int num); # define sha1_block sha1_block_x86 # else - static void sha1_block(SHA_CTX *c, register unsigned long *p, int num); + static void sha1_block(SHA_CTX *c, SHA_LONG *p, int num); # endif #if defined(ENDIAN_LITTLE) && defined(SHA1_ASM) -# define M_c2nl c2l -# define M_p_c2nl p_c2l -# define M_c2nl_p c2l_p -# define M_p_c2nl_p p_c2l_p -# define M_nl2c l2c +# define M_c2nl c2l +# define M_p_c2nl p_c2l +# define M_c2nl_p c2l_p +# define M_p_c2nl_p p_c2l_p +# define M_nl2c l2c #else -# define M_c2nl c2nl -# define M_p_c2nl p_c2nl -# define M_c2nl_p c2nl_p -# define M_p_c2nl_p p_c2nl_p -# define M_nl2c nl2c +# define M_c2nl c2nl +# define M_p_c2nl p_c2nl +# define M_c2nl_p c2nl_p +# define M_p_c2nl_p p_c2nl_p +# define M_nl2c nl2c #endif -void SHA1_Init(c) -SHA_CTX *c; - { - c->h0=INIT_DATA_h0; - c->h1=INIT_DATA_h1; - c->h2=INIT_DATA_h2; - c->h3=INIT_DATA_h3; - c->h4=INIT_DATA_h4; - c->Nl=0; - c->Nh=0; - c->num=0; - } - -void SHA1_Update(c, data, len) -SHA_CTX *c; -register unsigned char *data; -unsigned long len; - { - register ULONG *p; - int ew,ec,sw,sc; - ULONG l; - - if (len == 0) return; - - l=(c->Nl+(len<<3))&0xffffffffL; - if (l < c->Nl) /* overflow */ - c->Nh++; - c->Nh+=(len>>29); - c->Nl=l; - - if (c->num != 0) - { - p=c->data; - sw=c->num>>2; - sc=c->num&0x03; - - if ((c->num+len) >= SHA_CBLOCK) - { - l= p[sw]; - M_p_c2nl(data,l,sc); - p[sw++]=l; - for (; swnum); - - sha1_block(c,p,64); - c->num=0; - /* drop through and do the rest */ - } - else - { - c->num+=(int)len; - if ((sc+len) < 4) /* ugly, add char's to a word */ - { - l= p[sw]; - M_p_c2nl_p(data,l,sc,len); - p[sw]=l; - } - else - { - ew=(c->num>>2); - ec=(c->num&0x03); - l= p[sw]; - M_p_c2nl(data,l,sc); - p[sw++]=l; - for (; sw < ew; sw++) - { M_c2nl(data,l); p[sw]=l; } - if (ec) - { - M_c2nl_p(data,l,ec); - p[sw]=l; - } - } - return; - } - } - /* We can only do the following code for assember, the reason - * being that the sha1_block 'C' version changes the values - * in the 'data' array. The assember code avoids this and - * copies it to a local array. I should be able to do this for - * the C version as well.... - */ +void SHA1_Init(void *c_opaque) + { + SHA_CTX *c = (SHA_CTX*)c_opaque; + c->h0=INIT_DATA_h0; + c->h1=INIT_DATA_h1; + c->h2=INIT_DATA_h2; + c->h3=INIT_DATA_h3; + c->h4=INIT_DATA_h4; + c->Nl=0; + c->Nh=0; + c->num=0; + } + +void SHA1_Update(void *c_opaque, unsigned char *data, size_t len) + { + SHA_CTX *c = (SHA_CTX*)c_opaque; + ULONG *p; + int ew,ec,sw,sc; + ULONG l; + + if (len == 0) return; + + l=(c->Nl+((SHA_LONG)len<<3))&(SHA_LONG)-1; + if (l < c->Nl) /* overflow */ + c->Nh++; + c->Nh+=(len>>29); + c->Nl=l; + + if (c->num != 0) + { + p=c->data; + sw=c->num>>2; + sc=c->num&0x03; + + if ((c->num+len) >= SHA_CBLOCK) + { + l= p[sw]; + M_p_c2nl(data,l,sc); + p[sw++]=l; + for (; swnum); + + sha1_block(c,p,64); + c->num=0; + /* drop through and do the rest */ + } + else + { + c->num+=(int)len; + if ((sc+len) < 4) /* ugly, add char's to a word */ + { + l= p[sw]; + M_p_c2nl_p(data,l,sc,len); + p[sw]=l; + } + else + { + ew=(c->num>>2); + ec=(c->num&0x03); + l= p[sw]; + M_p_c2nl(data,l,sc); + p[sw++]=l; + for (; sw < ew; sw++) + { M_c2nl(data,l); p[sw]=l; } + if (ec) + { + M_c2nl_p(data,l,ec); + p[sw]=l; + } + } + return; + } + } + /* We can only do the following code for assember, the reason + * being that the sha1_block 'C' version changes the values + * in the 'data' array. The assember code avoids this and + * copies it to a local array. I should be able to do this for + * the C version as well.... + */ #if defined(ENDIAN_BIG) || defined(SHA1_ASM) - if ((((unsigned long)data)%sizeof(ULONG)) == 0) - { - sw=len/SHA_CBLOCK; - if (sw) - { - sw*=SHA_CBLOCK; - sha1_block(c,(ULONG *)data,sw); - data+=sw; - len-=sw; - } - } + if ((((SHA_LONG)data)%sizeof(ULONG)) == 0) + { + sw=len/SHA_CBLOCK; + if (sw) + { + sw*=SHA_CBLOCK; + sha1_block(c,(ULONG *)data,sw); + data+=sw; + len-=sw; + } + } #endif - /* we now can process the input data in blocks of SHA_CBLOCK - * chars and save the leftovers to c->data. */ - p=c->data; - while (len >= SHA_CBLOCK) - { + /* we now can process the input data in blocks of SHA_CBLOCK + * chars and save the leftovers to c->data. */ + p=c->data; + while (len >= SHA_CBLOCK) + { #if defined(ENDIAN_BIG) || defined(ENDIAN_LITTLE) - if (p != (unsigned long *)data) - memcpy(p,data,SHA_CBLOCK); - data+=SHA_CBLOCK; + if (p != (SHA_LONG *)data) + memcpy(p,data,SHA_CBLOCK); + data+=SHA_CBLOCK; # ifdef ENDIAN_LITTLE # ifndef SHA1_ASM /* Will not happen */ - for (sw=(SHA_LBLOCK/4); sw; sw--) - { - Endian_Reverse32(p[0]); - Endian_Reverse32(p[1]); - Endian_Reverse32(p[2]); - Endian_Reverse32(p[3]); - p+=4; - } - p=c->data; + for (sw=(SHA_LBLOCK/4); sw; sw--) + { + Endian_Reverse32(p[0]); + Endian_Reverse32(p[1]); + Endian_Reverse32(p[2]); + Endian_Reverse32(p[3]); + p+=4; + } + p=c->data; # endif # endif #else - for (sw=(SHA_BLOCK/4); sw; sw--) - { - M_c2nl(data,l); *(p++)=l; - M_c2nl(data,l); *(p++)=l; - M_c2nl(data,l); *(p++)=l; - M_c2nl(data,l); *(p++)=l; - } - p=c->data; + for (sw=(SHA_BLOCK/4); sw; sw--) + { + M_c2nl(data,l); *(p++)=l; + M_c2nl(data,l); *(p++)=l; + M_c2nl(data,l); *(p++)=l; + M_c2nl(data,l); *(p++)=l; + } + p=c->data; #endif - sha1_block(c,p,64); - len-=SHA_CBLOCK; - } - ec=(int)len; - c->num=ec; - ew=(ec>>2); - ec&=0x03; - - for (sw=0; sw < ew; sw++) - { M_c2nl(data,l); p[sw]=l; } - M_c2nl_p(data,l,ec); - p[sw]=l; - } + sha1_block(c,p,64); + len-=SHA_CBLOCK; + } + ec=(int)len; + c->num=ec; + ew=(ec>>2); + ec&=0x03; + + for (sw=0; sw < ew; sw++) + { M_c2nl(data,l); p[sw]=l; } + M_c2nl_p(data,l,ec); + p[sw]=l; + } #if 0 static void SHA1_Transform(c,b) SHA_CTX *c; unsigned char *b; - { - ULONG p[16]; + { + ULONG p[16]; #ifndef ENDIAN_BIG - ULONG *q; - int i; + ULONG *q; + int i; #endif #if defined(ENDIAN_BIG) || defined(ENDIAN_LITTLE) - memcpy(p,b,64); + memcpy(p,b,64); #ifdef ENDIAN_LITTLE - q=p; - for (i=(SHA_LBLOCK/4); i; i--) - { - Endian_Reverse32(q[0]); - Endian_Reverse32(q[1]); - Endian_Reverse32(q[2]); - Endian_Reverse32(q[3]); - q+=4; - } + q=p; + for (i=(SHA_LBLOCK/4); i; i--) + { + Endian_Reverse32(q[0]); + Endian_Reverse32(q[1]); + Endian_Reverse32(q[2]); + Endian_Reverse32(q[3]); + q+=4; + } #endif #else - q=p; - for (i=(SHA_LBLOCK/4); i; i--) - { - ULONG l; - c2nl(b,l); *(q++)=l; - c2nl(b,l); *(q++)=l; - c2nl(b,l); *(q++)=l; - c2nl(b,l); *(q++)=l; - } + q=p; + for (i=(SHA_LBLOCK/4); i; i--) + { + ULONG l; + c2nl(b,l); *(q++)=l; + c2nl(b,l); *(q++)=l; + c2nl(b,l); *(q++)=l; + c2nl(b,l); *(q++)=l; + } #endif - sha1_block(c,p,64); - } + sha1_block(c,p,64); + } #endif #ifndef SHA1_ASM -static void sha1_block(c, W, num) -SHA_CTX *c; -register unsigned long *W; -int num; - { - register ULONG A,B,C,D,E,T; - ULONG X[16]; - - A=c->h0; - B=c->h1; - C=c->h2; - D=c->h3; - E=c->h4; - - for (;;) - { - BODY_00_15( 0,A,B,C,D,E,T,W); - BODY_00_15( 1,T,A,B,C,D,E,W); - BODY_00_15( 2,E,T,A,B,C,D,W); - BODY_00_15( 3,D,E,T,A,B,C,W); - BODY_00_15( 4,C,D,E,T,A,B,W); - BODY_00_15( 5,B,C,D,E,T,A,W); - BODY_00_15( 6,A,B,C,D,E,T,W); - BODY_00_15( 7,T,A,B,C,D,E,W); - BODY_00_15( 8,E,T,A,B,C,D,W); - BODY_00_15( 9,D,E,T,A,B,C,W); - BODY_00_15(10,C,D,E,T,A,B,W); - BODY_00_15(11,B,C,D,E,T,A,W); - BODY_00_15(12,A,B,C,D,E,T,W); - BODY_00_15(13,T,A,B,C,D,E,W); - BODY_00_15(14,E,T,A,B,C,D,W); - BODY_00_15(15,D,E,T,A,B,C,W); - BODY_16_19(16,C,D,E,T,A,B,W,W,W,W); - BODY_16_19(17,B,C,D,E,T,A,W,W,W,W); - BODY_16_19(18,A,B,C,D,E,T,W,W,W,W); - BODY_16_19(19,T,A,B,C,D,E,W,W,W,X); - - BODY_20_31(20,E,T,A,B,C,D,W,W,W,X); - BODY_20_31(21,D,E,T,A,B,C,W,W,W,X); - BODY_20_31(22,C,D,E,T,A,B,W,W,W,X); - BODY_20_31(23,B,C,D,E,T,A,W,W,W,X); - BODY_20_31(24,A,B,C,D,E,T,W,W,X,X); - BODY_20_31(25,T,A,B,C,D,E,W,W,X,X); - BODY_20_31(26,E,T,A,B,C,D,W,W,X,X); - BODY_20_31(27,D,E,T,A,B,C,W,W,X,X); - BODY_20_31(28,C,D,E,T,A,B,W,W,X,X); - BODY_20_31(29,B,C,D,E,T,A,W,W,X,X); - BODY_20_31(30,A,B,C,D,E,T,W,X,X,X); - BODY_20_31(31,T,A,B,C,D,E,W,X,X,X); - BODY_32_39(32,E,T,A,B,C,D,X); - BODY_32_39(33,D,E,T,A,B,C,X); - BODY_32_39(34,C,D,E,T,A,B,X); - BODY_32_39(35,B,C,D,E,T,A,X); - BODY_32_39(36,A,B,C,D,E,T,X); - BODY_32_39(37,T,A,B,C,D,E,X); - BODY_32_39(38,E,T,A,B,C,D,X); - BODY_32_39(39,D,E,T,A,B,C,X); - - BODY_40_59(40,C,D,E,T,A,B,X); - BODY_40_59(41,B,C,D,E,T,A,X); - BODY_40_59(42,A,B,C,D,E,T,X); - BODY_40_59(43,T,A,B,C,D,E,X); - BODY_40_59(44,E,T,A,B,C,D,X); - BODY_40_59(45,D,E,T,A,B,C,X); - BODY_40_59(46,C,D,E,T,A,B,X); - BODY_40_59(47,B,C,D,E,T,A,X); - BODY_40_59(48,A,B,C,D,E,T,X); - BODY_40_59(49,T,A,B,C,D,E,X); - BODY_40_59(50,E,T,A,B,C,D,X); - BODY_40_59(51,D,E,T,A,B,C,X); - BODY_40_59(52,C,D,E,T,A,B,X); - BODY_40_59(53,B,C,D,E,T,A,X); - BODY_40_59(54,A,B,C,D,E,T,X); - BODY_40_59(55,T,A,B,C,D,E,X); - BODY_40_59(56,E,T,A,B,C,D,X); - BODY_40_59(57,D,E,T,A,B,C,X); - BODY_40_59(58,C,D,E,T,A,B,X); - BODY_40_59(59,B,C,D,E,T,A,X); - - BODY_60_79(60,A,B,C,D,E,T,X); - BODY_60_79(61,T,A,B,C,D,E,X); - BODY_60_79(62,E,T,A,B,C,D,X); - BODY_60_79(63,D,E,T,A,B,C,X); - BODY_60_79(64,C,D,E,T,A,B,X); - BODY_60_79(65,B,C,D,E,T,A,X); - BODY_60_79(66,A,B,C,D,E,T,X); - BODY_60_79(67,T,A,B,C,D,E,X); - BODY_60_79(68,E,T,A,B,C,D,X); - BODY_60_79(69,D,E,T,A,B,C,X); - BODY_60_79(70,C,D,E,T,A,B,X); - BODY_60_79(71,B,C,D,E,T,A,X); - BODY_60_79(72,A,B,C,D,E,T,X); - BODY_60_79(73,T,A,B,C,D,E,X); - BODY_60_79(74,E,T,A,B,C,D,X); - BODY_60_79(75,D,E,T,A,B,C,X); - BODY_60_79(76,C,D,E,T,A,B,X); - BODY_60_79(77,B,C,D,E,T,A,X); - BODY_60_79(78,A,B,C,D,E,T,X); - BODY_60_79(79,T,A,B,C,D,E,X); - - c->h0=(c->h0+E)&0xffffffffL; - c->h1=(c->h1+T)&0xffffffffL; - c->h2=(c->h2+A)&0xffffffffL; - c->h3=(c->h3+B)&0xffffffffL; - c->h4=(c->h4+C)&0xffffffffL; - - num-=64; - if (num <= 0) break; - - A=c->h0; - B=c->h1; - C=c->h2; - D=c->h3; - E=c->h4; - - W+=16; - } - } +static void sha1_block(SHA_CTX *c, SHA_LONG *W, int num) + { + ULONG A,B,C,D,E,T; + ULONG X[16]; + + A=c->h0; + B=c->h1; + C=c->h2; + D=c->h3; + E=c->h4; + + for (;;) + { + BODY_00_15( 0,A,B,C,D,E,T,W); + BODY_00_15( 1,T,A,B,C,D,E,W); + BODY_00_15( 2,E,T,A,B,C,D,W); + BODY_00_15( 3,D,E,T,A,B,C,W); + BODY_00_15( 4,C,D,E,T,A,B,W); + BODY_00_15( 5,B,C,D,E,T,A,W); + BODY_00_15( 6,A,B,C,D,E,T,W); + BODY_00_15( 7,T,A,B,C,D,E,W); + BODY_00_15( 8,E,T,A,B,C,D,W); + BODY_00_15( 9,D,E,T,A,B,C,W); + BODY_00_15(10,C,D,E,T,A,B,W); + BODY_00_15(11,B,C,D,E,T,A,W); + BODY_00_15(12,A,B,C,D,E,T,W); + BODY_00_15(13,T,A,B,C,D,E,W); + BODY_00_15(14,E,T,A,B,C,D,W); + BODY_00_15(15,D,E,T,A,B,C,W); + BODY_16_19(16,C,D,E,T,A,B,W,W,W,W); + BODY_16_19(17,B,C,D,E,T,A,W,W,W,W); + BODY_16_19(18,A,B,C,D,E,T,W,W,W,W); + BODY_16_19(19,T,A,B,C,D,E,W,W,W,X); + + BODY_20_31(20,E,T,A,B,C,D,W,W,W,X); + BODY_20_31(21,D,E,T,A,B,C,W,W,W,X); + BODY_20_31(22,C,D,E,T,A,B,W,W,W,X); + BODY_20_31(23,B,C,D,E,T,A,W,W,W,X); + BODY_20_31(24,A,B,C,D,E,T,W,W,X,X); + BODY_20_31(25,T,A,B,C,D,E,W,W,X,X); + BODY_20_31(26,E,T,A,B,C,D,W,W,X,X); + BODY_20_31(27,D,E,T,A,B,C,W,W,X,X); + BODY_20_31(28,C,D,E,T,A,B,W,W,X,X); + BODY_20_31(29,B,C,D,E,T,A,W,W,X,X); + BODY_20_31(30,A,B,C,D,E,T,W,X,X,X); + BODY_20_31(31,T,A,B,C,D,E,W,X,X,X); + BODY_32_39(32,E,T,A,B,C,D,X); + BODY_32_39(33,D,E,T,A,B,C,X); + BODY_32_39(34,C,D,E,T,A,B,X); + BODY_32_39(35,B,C,D,E,T,A,X); + BODY_32_39(36,A,B,C,D,E,T,X); + BODY_32_39(37,T,A,B,C,D,E,X); + BODY_32_39(38,E,T,A,B,C,D,X); + BODY_32_39(39,D,E,T,A,B,C,X); + + BODY_40_59(40,C,D,E,T,A,B,X); + BODY_40_59(41,B,C,D,E,T,A,X); + BODY_40_59(42,A,B,C,D,E,T,X); + BODY_40_59(43,T,A,B,C,D,E,X); + BODY_40_59(44,E,T,A,B,C,D,X); + BODY_40_59(45,D,E,T,A,B,C,X); + BODY_40_59(46,C,D,E,T,A,B,X); + BODY_40_59(47,B,C,D,E,T,A,X); + BODY_40_59(48,A,B,C,D,E,T,X); + BODY_40_59(49,T,A,B,C,D,E,X); + BODY_40_59(50,E,T,A,B,C,D,X); + BODY_40_59(51,D,E,T,A,B,C,X); + BODY_40_59(52,C,D,E,T,A,B,X); + BODY_40_59(53,B,C,D,E,T,A,X); + BODY_40_59(54,A,B,C,D,E,T,X); + BODY_40_59(55,T,A,B,C,D,E,X); + BODY_40_59(56,E,T,A,B,C,D,X); + BODY_40_59(57,D,E,T,A,B,C,X); + BODY_40_59(58,C,D,E,T,A,B,X); + BODY_40_59(59,B,C,D,E,T,A,X); + + BODY_60_79(60,A,B,C,D,E,T,X); + BODY_60_79(61,T,A,B,C,D,E,X); + BODY_60_79(62,E,T,A,B,C,D,X); + BODY_60_79(63,D,E,T,A,B,C,X); + BODY_60_79(64,C,D,E,T,A,B,X); + BODY_60_79(65,B,C,D,E,T,A,X); + BODY_60_79(66,A,B,C,D,E,T,X); + BODY_60_79(67,T,A,B,C,D,E,X); + BODY_60_79(68,E,T,A,B,C,D,X); + BODY_60_79(69,D,E,T,A,B,C,X); + BODY_60_79(70,C,D,E,T,A,B,X); + BODY_60_79(71,B,C,D,E,T,A,X); + BODY_60_79(72,A,B,C,D,E,T,X); + BODY_60_79(73,T,A,B,C,D,E,X); + BODY_60_79(74,E,T,A,B,C,D,X); + BODY_60_79(75,D,E,T,A,B,C,X); + BODY_60_79(76,C,D,E,T,A,B,X); + BODY_60_79(77,B,C,D,E,T,A,X); + BODY_60_79(78,A,B,C,D,E,T,X); + BODY_60_79(79,T,A,B,C,D,E,X); + + c->h0=(c->h0+E)&0xffffffffL; + c->h1=(c->h1+T)&0xffffffffL; + c->h2=(c->h2+A)&0xffffffffL; + c->h3=(c->h3+B)&0xffffffffL; + c->h4=(c->h4+C)&0xffffffffL; + + num-=64; + if (num <= 0) break; + + A=c->h0; + B=c->h1; + C=c->h2; + D=c->h3; + E=c->h4; + + W+=16; + } + } #endif -void SHA1_Final(md, c) -unsigned char *md; -SHA_CTX *c; - { - register int i,j; - register ULONG l; - register ULONG *p; - static unsigned char end[4]={0x80,0x00,0x00,0x00}; - unsigned char *cp=end; - - /* c->num should definitly have room for at least one more byte. */ - p=c->data; - j=c->num; - i=j>>2; +void SHA1_Final(unsigned char *md, void *c_opaque) + { + SHA_CTX *c = (SHA_CTX*)c_opaque; + int i,j; + ULONG l; + ULONG *p; + static unsigned char end[4]={0x80,0x00,0x00,0x00}; + unsigned char *cp=end; + + /* c->num should definitly have room for at least one more byte. */ + p=c->data; + j=c->num; + i=j>>2; #ifdef PURIFY - if ((j&0x03) == 0) p[i]=0; + if ((j&0x03) == 0) p[i]=0; #endif - l=p[i]; - M_p_c2nl(cp,l,j&0x03); - p[i]=l; - i++; - /* i is the next 'undefined word' */ - if (c->num >= SHA_LAST_BLOCK) - { - for (; iNh; - p[SHA_LBLOCK-1]=c->Nl; + l=p[i]; + M_p_c2nl(cp,l,j&0x03); + p[i]=l; + i++; + /* i is the next 'undefined word' */ + if (c->num >= SHA_LAST_BLOCK) + { + for (; iNh; + p[SHA_LBLOCK-1]=c->Nl; #if defined(ENDIAN_LITTLE) && defined(SHA1_ASM) - Endian_Reverse32(p[SHA_LBLOCK-2]); - Endian_Reverse32(p[SHA_LBLOCK-1]); + Endian_Reverse32(p[SHA_LBLOCK-2]); + Endian_Reverse32(p[SHA_LBLOCK-1]); #endif - sha1_block(c,p,64); - cp=md; - l=c->h0; nl2c(l,cp); - l=c->h1; nl2c(l,cp); - l=c->h2; nl2c(l,cp); - l=c->h3; nl2c(l,cp); - l=c->h4; nl2c(l,cp); - - /* clear stuff, sha1_block may be leaving some stuff on the stack - * but I'm not worried :-) */ - c->num=0; -/* memset((char *)&c,0,sizeof(c));*/ - } + sha1_block(c,p,64); + cp=md; + l=c->h0; nl2c(l,cp); + l=c->h1; nl2c(l,cp); + l=c->h2; nl2c(l,cp); + l=c->h3; nl2c(l,cp); + l=c->h4; nl2c(l,cp); + + /* clear stuff, sha1_block may be leaving some stuff on the stack + * but I'm not worried :-) */ + c->num=0; +/* memset(&c,0,sizeof(c));*/ + } int SHA1_CtxSize(void) { - return sizeof(SHA_CTX); + return sizeof(SHA_CTX); } -/*********************************************************************** -** -*/ REBYTE *SHA1(REBYTE *d, REBCNT n, REBYTE *md) -/* -***********************************************************************/ +// +// SHA1: C +// +REBYTE *SHA1(REBYTE *d, REBCNT n, REBYTE *md) { - // d is data, n is length - SHA_CTX c; - static unsigned char m[SHA_DIGEST_LENGTH]; - - if (md == NULL) md=m; - SHA1_Init(&c); - SHA1_Update(&c,d,n); - SHA1_Final(md,&c); - memset(&c,0,sizeof(c)); - return(md); + // d is data, n is length + SHA_CTX c; + static unsigned char m[SHA_DIGEST_LENGTH]; + + if (md == NULL) md = (REBYTE*)m; + SHA1_Init(&c); + SHA1_Update(&c,(unsigned char*)d,n); + SHA1_Final((unsigned char*)md,&c); + memset(&c,0,sizeof(c)); + return md; } diff --git a/src/core/u-zlib.c b/src/core/u-zlib.c index 86adc944bd..abcf45f4a1 100644 --- a/src/core/u-zlib.c +++ b/src/core/u-zlib.c @@ -1,62 +1,1095 @@ -#include "sys-zlib.h" -#include +// +// Extraction of ZLIB compression and decompression routines +// for REBOL [R3] Language Interpreter and Run-time Environment +// This is a code-generated file. +// +// ZLIB Copyright notice: +// +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// +// Jean-loup Gailly Mark Adler +// jloup@gzip.org madler@alumni.caltech.edu +// +// REBOL is a trademark of REBOL Technologies +// Licensed under the Apache License, Version 2.0 +// +// ********************************************************************** +// +// Title: ZLIB aggregated source file +// Build: A0 +// Date: 29-Sep-2013 +// File: u-zlib.c +// +// AUTO-GENERATED FILE - Do not modify. (From: make-zlib.r) +// + +#include "sys-zlib.h" // added by make-zlib.r + +/* crc32.c -- compute the CRC-32 of a data stream + * Copyright (C) 1995-2006, 2010, 2011, 2012 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Thanks to Rodney Brown for his contribution of faster + * CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing + * tables for updating the shift register in one step with three exclusive-ors + * instead of four steps with four exclusive-ors. This results in about a + * factor of two increase in speed on a Power PC G4 (PPC7455) using gcc -O3. + */ + +/* @(#) $Id$ */ + +/* + Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore + protection on the static variables used to control the first-use generation + of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should + first call get_crc_table() to initialize the tables before allowing more than + one thread to use crc32(). + + DYNAMIC_CRC_TABLE and MAKECRCH can be #defined to write out crc32.h. + */ + +#ifdef MAKECRCH +// # include // !!! No in Ren-C release builds +# ifndef DYNAMIC_CRC_TABLE +# define DYNAMIC_CRC_TABLE +# endif /* !DYNAMIC_CRC_TABLE */ +#endif /* MAKECRCH */ + +// #include "zutil.h" /* for STDC and FAR definitions */ /* In sys-zlib.h (see make-zlib.r) */ + +#define local static + +/* Definitions for doing the crc four data bytes at a time. */ +#if !defined(NOBYFOUR) && defined(Z_U4) +# define BYFOUR +#endif +#ifdef BYFOUR + local unsigned long crc32_little OF((unsigned long, + const unsigned char FAR *, unsigned)); + local unsigned long crc32_big OF((unsigned long, + const unsigned char FAR *, unsigned)); +# define TBLS 8 +#else +# define TBLS 1 +#endif /* BYFOUR */ + +/* Local functions for crc concatenation */ +local unsigned long gf2_matrix_times OF((unsigned long *mat, + unsigned long vec)); +local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat)); +local uLong crc32_combine_ OF((uLong crc1, uLong crc2, z_off64_t len2)); + + +#ifdef DYNAMIC_CRC_TABLE + +local volatile int crc_table_empty = 1; +local z_crc_t FAR crc_table[TBLS][256]; +local void make_crc_table OF((void)); +#ifdef MAKECRCH + local void write_table OF((FILE *, const z_crc_t FAR *)); +#endif /* MAKECRCH */ +/* + Generate tables for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The first table is simply the CRC of all possible eight bit values. This is + all the information needed to generate CRCs on data a byte at a time for all + combinations of CRC register values and incoming bytes. The remaining tables + allow for word-at-a-time CRC calculation for both big-endian and little- + endian machines, where a word is four bytes. +*/ +local void make_crc_table() +{ + z_crc_t c; + int n, k; + z_crc_t poly; /* polynomial exclusive-or pattern */ + /* terms of polynomial defining this crc (except x^32): */ + static volatile int first = 1; /* flag to limit concurrent making */ + static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* See if another task is already doing this (not thread-safe, but better + than nothing -- significantly reduces duration of vulnerability in + case the advice about DYNAMIC_CRC_TABLE is ignored) */ + if (first) { + first = 0; + + /* make exclusive-or pattern from polynomial (0xedb88320UL) */ + poly = 0; + for (n = 0; n < (int)(sizeof(p)/sizeof(unsigned char)); n++) + poly |= (z_crc_t)1 << (31 - p[n]); + + /* generate a crc for every 8-bit value */ + for (n = 0; n < 256; n++) { + c = (z_crc_t)n; + for (k = 0; k < 8; k++) + c = c & 1 ? poly ^ (c >> 1) : c >> 1; + crc_table[0][n] = c; + } + +#ifdef BYFOUR + /* generate crc for each value followed by one, two, and three zeros, + and then the byte reversal of those as well as the first table */ + for (n = 0; n < 256; n++) { + c = crc_table[0][n]; + crc_table[4][n] = ZSWAP32(c); + for (k = 1; k < 4; k++) { + c = crc_table[0][c & 0xff] ^ (c >> 8); + crc_table[k][n] = c; + crc_table[k + 4][n] = ZSWAP32(c); + } + } +#endif /* BYFOUR */ + + crc_table_empty = 0; + } + else { /* not first */ + /* wait for the other guy to finish (not efficient, but rare) */ + while (crc_table_empty) + ; + } + +#ifdef MAKECRCH + /* write out CRC tables to crc32.h */ + { + FILE *out; + + out = fopen("crc32.h", "w"); + if (out == NULL) return; + fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n"); + fprintf(out, " * Generated automatically by crc32.c\n */\n\n"); + fprintf(out, "local const z_crc_t FAR "); + fprintf(out, "crc_table[TBLS][256] =\n{\n {\n"); + write_table(out, crc_table[0]); +# ifdef BYFOUR + fprintf(out, "#ifdef BYFOUR\n"); + for (k = 1; k < 8; k++) { + fprintf(out, " },\n {\n"); + write_table(out, crc_table[k]); + } + fprintf(out, "#endif\n"); +# endif /* BYFOUR */ + fprintf(out, " }\n};\n"); + fclose(out); + } +#endif /* MAKECRCH */ +} + +#ifdef MAKECRCH +local void write_table(out, table) + FILE *out; + const z_crc_t FAR *table; +{ + int n; + + for (n = 0; n < 256; n++) + fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ", + (unsigned long)(table[n]), + n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", ")); +} +#endif /* MAKECRCH */ + +#else /* !DYNAMIC_CRC_TABLE */ +/* ======================================================================== + * Tables of CRC-32s of all single-byte values, made by make_crc_table(). + */ +/* crc32.h -- tables for rapid CRC calculation + * Generated automatically by crc32.c + */ + +local const z_crc_t FAR crc_table[TBLS][256] = +{ + { + 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL, + 0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL, + 0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL, + 0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL, + 0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL, + 0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL, + 0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL, + 0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL, + 0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL, + 0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL, + 0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL, + 0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL, + 0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL, + 0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL, + 0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL, + 0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL, + 0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL, + 0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL, + 0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL, + 0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL, + 0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL, + 0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL, + 0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL, + 0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL, + 0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL, + 0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL, + 0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL, + 0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL, + 0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL, + 0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL, + 0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL, + 0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL, + 0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL, + 0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL, + 0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL, + 0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL, + 0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL, + 0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL, + 0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL, + 0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL, + 0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL, + 0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL, + 0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL, + 0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL, + 0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL, + 0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL, + 0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL, + 0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL, + 0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL, + 0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL, + 0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL, + 0x2d02ef8dUL +#ifdef BYFOUR + }, + { + 0x00000000UL, 0x191b3141UL, 0x32366282UL, 0x2b2d53c3UL, 0x646cc504UL, + 0x7d77f445UL, 0x565aa786UL, 0x4f4196c7UL, 0xc8d98a08UL, 0xd1c2bb49UL, + 0xfaefe88aUL, 0xe3f4d9cbUL, 0xacb54f0cUL, 0xb5ae7e4dUL, 0x9e832d8eUL, + 0x87981ccfUL, 0x4ac21251UL, 0x53d92310UL, 0x78f470d3UL, 0x61ef4192UL, + 0x2eaed755UL, 0x37b5e614UL, 0x1c98b5d7UL, 0x05838496UL, 0x821b9859UL, + 0x9b00a918UL, 0xb02dfadbUL, 0xa936cb9aUL, 0xe6775d5dUL, 0xff6c6c1cUL, + 0xd4413fdfUL, 0xcd5a0e9eUL, 0x958424a2UL, 0x8c9f15e3UL, 0xa7b24620UL, + 0xbea97761UL, 0xf1e8e1a6UL, 0xe8f3d0e7UL, 0xc3de8324UL, 0xdac5b265UL, + 0x5d5daeaaUL, 0x44469febUL, 0x6f6bcc28UL, 0x7670fd69UL, 0x39316baeUL, + 0x202a5aefUL, 0x0b07092cUL, 0x121c386dUL, 0xdf4636f3UL, 0xc65d07b2UL, + 0xed705471UL, 0xf46b6530UL, 0xbb2af3f7UL, 0xa231c2b6UL, 0x891c9175UL, + 0x9007a034UL, 0x179fbcfbUL, 0x0e848dbaUL, 0x25a9de79UL, 0x3cb2ef38UL, + 0x73f379ffUL, 0x6ae848beUL, 0x41c51b7dUL, 0x58de2a3cUL, 0xf0794f05UL, + 0xe9627e44UL, 0xc24f2d87UL, 0xdb541cc6UL, 0x94158a01UL, 0x8d0ebb40UL, + 0xa623e883UL, 0xbf38d9c2UL, 0x38a0c50dUL, 0x21bbf44cUL, 0x0a96a78fUL, + 0x138d96ceUL, 0x5ccc0009UL, 0x45d73148UL, 0x6efa628bUL, 0x77e153caUL, + 0xbabb5d54UL, 0xa3a06c15UL, 0x888d3fd6UL, 0x91960e97UL, 0xded79850UL, + 0xc7cca911UL, 0xece1fad2UL, 0xf5facb93UL, 0x7262d75cUL, 0x6b79e61dUL, + 0x4054b5deUL, 0x594f849fUL, 0x160e1258UL, 0x0f152319UL, 0x243870daUL, + 0x3d23419bUL, 0x65fd6ba7UL, 0x7ce65ae6UL, 0x57cb0925UL, 0x4ed03864UL, + 0x0191aea3UL, 0x188a9fe2UL, 0x33a7cc21UL, 0x2abcfd60UL, 0xad24e1afUL, + 0xb43fd0eeUL, 0x9f12832dUL, 0x8609b26cUL, 0xc94824abUL, 0xd05315eaUL, + 0xfb7e4629UL, 0xe2657768UL, 0x2f3f79f6UL, 0x362448b7UL, 0x1d091b74UL, + 0x04122a35UL, 0x4b53bcf2UL, 0x52488db3UL, 0x7965de70UL, 0x607eef31UL, + 0xe7e6f3feUL, 0xfefdc2bfUL, 0xd5d0917cUL, 0xcccba03dUL, 0x838a36faUL, + 0x9a9107bbUL, 0xb1bc5478UL, 0xa8a76539UL, 0x3b83984bUL, 0x2298a90aUL, + 0x09b5fac9UL, 0x10aecb88UL, 0x5fef5d4fUL, 0x46f46c0eUL, 0x6dd93fcdUL, + 0x74c20e8cUL, 0xf35a1243UL, 0xea412302UL, 0xc16c70c1UL, 0xd8774180UL, + 0x9736d747UL, 0x8e2de606UL, 0xa500b5c5UL, 0xbc1b8484UL, 0x71418a1aUL, + 0x685abb5bUL, 0x4377e898UL, 0x5a6cd9d9UL, 0x152d4f1eUL, 0x0c367e5fUL, + 0x271b2d9cUL, 0x3e001cddUL, 0xb9980012UL, 0xa0833153UL, 0x8bae6290UL, + 0x92b553d1UL, 0xddf4c516UL, 0xc4eff457UL, 0xefc2a794UL, 0xf6d996d5UL, + 0xae07bce9UL, 0xb71c8da8UL, 0x9c31de6bUL, 0x852aef2aUL, 0xca6b79edUL, + 0xd37048acUL, 0xf85d1b6fUL, 0xe1462a2eUL, 0x66de36e1UL, 0x7fc507a0UL, + 0x54e85463UL, 0x4df36522UL, 0x02b2f3e5UL, 0x1ba9c2a4UL, 0x30849167UL, + 0x299fa026UL, 0xe4c5aeb8UL, 0xfdde9ff9UL, 0xd6f3cc3aUL, 0xcfe8fd7bUL, + 0x80a96bbcUL, 0x99b25afdUL, 0xb29f093eUL, 0xab84387fUL, 0x2c1c24b0UL, + 0x350715f1UL, 0x1e2a4632UL, 0x07317773UL, 0x4870e1b4UL, 0x516bd0f5UL, + 0x7a468336UL, 0x635db277UL, 0xcbfad74eUL, 0xd2e1e60fUL, 0xf9ccb5ccUL, + 0xe0d7848dUL, 0xaf96124aUL, 0xb68d230bUL, 0x9da070c8UL, 0x84bb4189UL, + 0x03235d46UL, 0x1a386c07UL, 0x31153fc4UL, 0x280e0e85UL, 0x674f9842UL, + 0x7e54a903UL, 0x5579fac0UL, 0x4c62cb81UL, 0x8138c51fUL, 0x9823f45eUL, + 0xb30ea79dUL, 0xaa1596dcUL, 0xe554001bUL, 0xfc4f315aUL, 0xd7626299UL, + 0xce7953d8UL, 0x49e14f17UL, 0x50fa7e56UL, 0x7bd72d95UL, 0x62cc1cd4UL, + 0x2d8d8a13UL, 0x3496bb52UL, 0x1fbbe891UL, 0x06a0d9d0UL, 0x5e7ef3ecUL, + 0x4765c2adUL, 0x6c48916eUL, 0x7553a02fUL, 0x3a1236e8UL, 0x230907a9UL, + 0x0824546aUL, 0x113f652bUL, 0x96a779e4UL, 0x8fbc48a5UL, 0xa4911b66UL, + 0xbd8a2a27UL, 0xf2cbbce0UL, 0xebd08da1UL, 0xc0fdde62UL, 0xd9e6ef23UL, + 0x14bce1bdUL, 0x0da7d0fcUL, 0x268a833fUL, 0x3f91b27eUL, 0x70d024b9UL, + 0x69cb15f8UL, 0x42e6463bUL, 0x5bfd777aUL, 0xdc656bb5UL, 0xc57e5af4UL, + 0xee530937UL, 0xf7483876UL, 0xb809aeb1UL, 0xa1129ff0UL, 0x8a3fcc33UL, + 0x9324fd72UL + }, + { + 0x00000000UL, 0x01c26a37UL, 0x0384d46eUL, 0x0246be59UL, 0x0709a8dcUL, + 0x06cbc2ebUL, 0x048d7cb2UL, 0x054f1685UL, 0x0e1351b8UL, 0x0fd13b8fUL, + 0x0d9785d6UL, 0x0c55efe1UL, 0x091af964UL, 0x08d89353UL, 0x0a9e2d0aUL, + 0x0b5c473dUL, 0x1c26a370UL, 0x1de4c947UL, 0x1fa2771eUL, 0x1e601d29UL, + 0x1b2f0bacUL, 0x1aed619bUL, 0x18abdfc2UL, 0x1969b5f5UL, 0x1235f2c8UL, + 0x13f798ffUL, 0x11b126a6UL, 0x10734c91UL, 0x153c5a14UL, 0x14fe3023UL, + 0x16b88e7aUL, 0x177ae44dUL, 0x384d46e0UL, 0x398f2cd7UL, 0x3bc9928eUL, + 0x3a0bf8b9UL, 0x3f44ee3cUL, 0x3e86840bUL, 0x3cc03a52UL, 0x3d025065UL, + 0x365e1758UL, 0x379c7d6fUL, 0x35dac336UL, 0x3418a901UL, 0x3157bf84UL, + 0x3095d5b3UL, 0x32d36beaUL, 0x331101ddUL, 0x246be590UL, 0x25a98fa7UL, + 0x27ef31feUL, 0x262d5bc9UL, 0x23624d4cUL, 0x22a0277bUL, 0x20e69922UL, + 0x2124f315UL, 0x2a78b428UL, 0x2bbade1fUL, 0x29fc6046UL, 0x283e0a71UL, + 0x2d711cf4UL, 0x2cb376c3UL, 0x2ef5c89aUL, 0x2f37a2adUL, 0x709a8dc0UL, + 0x7158e7f7UL, 0x731e59aeUL, 0x72dc3399UL, 0x7793251cUL, 0x76514f2bUL, + 0x7417f172UL, 0x75d59b45UL, 0x7e89dc78UL, 0x7f4bb64fUL, 0x7d0d0816UL, + 0x7ccf6221UL, 0x798074a4UL, 0x78421e93UL, 0x7a04a0caUL, 0x7bc6cafdUL, + 0x6cbc2eb0UL, 0x6d7e4487UL, 0x6f38fadeUL, 0x6efa90e9UL, 0x6bb5866cUL, + 0x6a77ec5bUL, 0x68315202UL, 0x69f33835UL, 0x62af7f08UL, 0x636d153fUL, + 0x612bab66UL, 0x60e9c151UL, 0x65a6d7d4UL, 0x6464bde3UL, 0x662203baUL, + 0x67e0698dUL, 0x48d7cb20UL, 0x4915a117UL, 0x4b531f4eUL, 0x4a917579UL, + 0x4fde63fcUL, 0x4e1c09cbUL, 0x4c5ab792UL, 0x4d98dda5UL, 0x46c49a98UL, + 0x4706f0afUL, 0x45404ef6UL, 0x448224c1UL, 0x41cd3244UL, 0x400f5873UL, + 0x4249e62aUL, 0x438b8c1dUL, 0x54f16850UL, 0x55330267UL, 0x5775bc3eUL, + 0x56b7d609UL, 0x53f8c08cUL, 0x523aaabbUL, 0x507c14e2UL, 0x51be7ed5UL, + 0x5ae239e8UL, 0x5b2053dfUL, 0x5966ed86UL, 0x58a487b1UL, 0x5deb9134UL, + 0x5c29fb03UL, 0x5e6f455aUL, 0x5fad2f6dUL, 0xe1351b80UL, 0xe0f771b7UL, + 0xe2b1cfeeUL, 0xe373a5d9UL, 0xe63cb35cUL, 0xe7fed96bUL, 0xe5b86732UL, + 0xe47a0d05UL, 0xef264a38UL, 0xeee4200fUL, 0xeca29e56UL, 0xed60f461UL, + 0xe82fe2e4UL, 0xe9ed88d3UL, 0xebab368aUL, 0xea695cbdUL, 0xfd13b8f0UL, + 0xfcd1d2c7UL, 0xfe976c9eUL, 0xff5506a9UL, 0xfa1a102cUL, 0xfbd87a1bUL, + 0xf99ec442UL, 0xf85cae75UL, 0xf300e948UL, 0xf2c2837fUL, 0xf0843d26UL, + 0xf1465711UL, 0xf4094194UL, 0xf5cb2ba3UL, 0xf78d95faUL, 0xf64fffcdUL, + 0xd9785d60UL, 0xd8ba3757UL, 0xdafc890eUL, 0xdb3ee339UL, 0xde71f5bcUL, + 0xdfb39f8bUL, 0xddf521d2UL, 0xdc374be5UL, 0xd76b0cd8UL, 0xd6a966efUL, + 0xd4efd8b6UL, 0xd52db281UL, 0xd062a404UL, 0xd1a0ce33UL, 0xd3e6706aUL, + 0xd2241a5dUL, 0xc55efe10UL, 0xc49c9427UL, 0xc6da2a7eUL, 0xc7184049UL, + 0xc25756ccUL, 0xc3953cfbUL, 0xc1d382a2UL, 0xc011e895UL, 0xcb4dafa8UL, + 0xca8fc59fUL, 0xc8c97bc6UL, 0xc90b11f1UL, 0xcc440774UL, 0xcd866d43UL, + 0xcfc0d31aUL, 0xce02b92dUL, 0x91af9640UL, 0x906dfc77UL, 0x922b422eUL, + 0x93e92819UL, 0x96a63e9cUL, 0x976454abUL, 0x9522eaf2UL, 0x94e080c5UL, + 0x9fbcc7f8UL, 0x9e7eadcfUL, 0x9c381396UL, 0x9dfa79a1UL, 0x98b56f24UL, + 0x99770513UL, 0x9b31bb4aUL, 0x9af3d17dUL, 0x8d893530UL, 0x8c4b5f07UL, + 0x8e0de15eUL, 0x8fcf8b69UL, 0x8a809decUL, 0x8b42f7dbUL, 0x89044982UL, + 0x88c623b5UL, 0x839a6488UL, 0x82580ebfUL, 0x801eb0e6UL, 0x81dcdad1UL, + 0x8493cc54UL, 0x8551a663UL, 0x8717183aUL, 0x86d5720dUL, 0xa9e2d0a0UL, + 0xa820ba97UL, 0xaa6604ceUL, 0xaba46ef9UL, 0xaeeb787cUL, 0xaf29124bUL, + 0xad6fac12UL, 0xacadc625UL, 0xa7f18118UL, 0xa633eb2fUL, 0xa4755576UL, + 0xa5b73f41UL, 0xa0f829c4UL, 0xa13a43f3UL, 0xa37cfdaaUL, 0xa2be979dUL, + 0xb5c473d0UL, 0xb40619e7UL, 0xb640a7beUL, 0xb782cd89UL, 0xb2cddb0cUL, + 0xb30fb13bUL, 0xb1490f62UL, 0xb08b6555UL, 0xbbd72268UL, 0xba15485fUL, + 0xb853f606UL, 0xb9919c31UL, 0xbcde8ab4UL, 0xbd1ce083UL, 0xbf5a5edaUL, + 0xbe9834edUL + }, + { + 0x00000000UL, 0xb8bc6765UL, 0xaa09c88bUL, 0x12b5afeeUL, 0x8f629757UL, + 0x37def032UL, 0x256b5fdcUL, 0x9dd738b9UL, 0xc5b428efUL, 0x7d084f8aUL, + 0x6fbde064UL, 0xd7018701UL, 0x4ad6bfb8UL, 0xf26ad8ddUL, 0xe0df7733UL, + 0x58631056UL, 0x5019579fUL, 0xe8a530faUL, 0xfa109f14UL, 0x42acf871UL, + 0xdf7bc0c8UL, 0x67c7a7adUL, 0x75720843UL, 0xcdce6f26UL, 0x95ad7f70UL, + 0x2d111815UL, 0x3fa4b7fbUL, 0x8718d09eUL, 0x1acfe827UL, 0xa2738f42UL, + 0xb0c620acUL, 0x087a47c9UL, 0xa032af3eUL, 0x188ec85bUL, 0x0a3b67b5UL, + 0xb28700d0UL, 0x2f503869UL, 0x97ec5f0cUL, 0x8559f0e2UL, 0x3de59787UL, + 0x658687d1UL, 0xdd3ae0b4UL, 0xcf8f4f5aUL, 0x7733283fUL, 0xeae41086UL, + 0x525877e3UL, 0x40edd80dUL, 0xf851bf68UL, 0xf02bf8a1UL, 0x48979fc4UL, + 0x5a22302aUL, 0xe29e574fUL, 0x7f496ff6UL, 0xc7f50893UL, 0xd540a77dUL, + 0x6dfcc018UL, 0x359fd04eUL, 0x8d23b72bUL, 0x9f9618c5UL, 0x272a7fa0UL, + 0xbafd4719UL, 0x0241207cUL, 0x10f48f92UL, 0xa848e8f7UL, 0x9b14583dUL, + 0x23a83f58UL, 0x311d90b6UL, 0x89a1f7d3UL, 0x1476cf6aUL, 0xaccaa80fUL, + 0xbe7f07e1UL, 0x06c36084UL, 0x5ea070d2UL, 0xe61c17b7UL, 0xf4a9b859UL, + 0x4c15df3cUL, 0xd1c2e785UL, 0x697e80e0UL, 0x7bcb2f0eUL, 0xc377486bUL, + 0xcb0d0fa2UL, 0x73b168c7UL, 0x6104c729UL, 0xd9b8a04cUL, 0x446f98f5UL, + 0xfcd3ff90UL, 0xee66507eUL, 0x56da371bUL, 0x0eb9274dUL, 0xb6054028UL, + 0xa4b0efc6UL, 0x1c0c88a3UL, 0x81dbb01aUL, 0x3967d77fUL, 0x2bd27891UL, + 0x936e1ff4UL, 0x3b26f703UL, 0x839a9066UL, 0x912f3f88UL, 0x299358edUL, + 0xb4446054UL, 0x0cf80731UL, 0x1e4da8dfUL, 0xa6f1cfbaUL, 0xfe92dfecUL, + 0x462eb889UL, 0x549b1767UL, 0xec277002UL, 0x71f048bbUL, 0xc94c2fdeUL, + 0xdbf98030UL, 0x6345e755UL, 0x6b3fa09cUL, 0xd383c7f9UL, 0xc1366817UL, + 0x798a0f72UL, 0xe45d37cbUL, 0x5ce150aeUL, 0x4e54ff40UL, 0xf6e89825UL, + 0xae8b8873UL, 0x1637ef16UL, 0x048240f8UL, 0xbc3e279dUL, 0x21e91f24UL, + 0x99557841UL, 0x8be0d7afUL, 0x335cb0caUL, 0xed59b63bUL, 0x55e5d15eUL, + 0x47507eb0UL, 0xffec19d5UL, 0x623b216cUL, 0xda874609UL, 0xc832e9e7UL, + 0x708e8e82UL, 0x28ed9ed4UL, 0x9051f9b1UL, 0x82e4565fUL, 0x3a58313aUL, + 0xa78f0983UL, 0x1f336ee6UL, 0x0d86c108UL, 0xb53aa66dUL, 0xbd40e1a4UL, + 0x05fc86c1UL, 0x1749292fUL, 0xaff54e4aUL, 0x322276f3UL, 0x8a9e1196UL, + 0x982bbe78UL, 0x2097d91dUL, 0x78f4c94bUL, 0xc048ae2eUL, 0xd2fd01c0UL, + 0x6a4166a5UL, 0xf7965e1cUL, 0x4f2a3979UL, 0x5d9f9697UL, 0xe523f1f2UL, + 0x4d6b1905UL, 0xf5d77e60UL, 0xe762d18eUL, 0x5fdeb6ebUL, 0xc2098e52UL, + 0x7ab5e937UL, 0x680046d9UL, 0xd0bc21bcUL, 0x88df31eaUL, 0x3063568fUL, + 0x22d6f961UL, 0x9a6a9e04UL, 0x07bda6bdUL, 0xbf01c1d8UL, 0xadb46e36UL, + 0x15080953UL, 0x1d724e9aUL, 0xa5ce29ffUL, 0xb77b8611UL, 0x0fc7e174UL, + 0x9210d9cdUL, 0x2aacbea8UL, 0x38191146UL, 0x80a57623UL, 0xd8c66675UL, + 0x607a0110UL, 0x72cfaefeUL, 0xca73c99bUL, 0x57a4f122UL, 0xef189647UL, + 0xfdad39a9UL, 0x45115eccUL, 0x764dee06UL, 0xcef18963UL, 0xdc44268dUL, + 0x64f841e8UL, 0xf92f7951UL, 0x41931e34UL, 0x5326b1daUL, 0xeb9ad6bfUL, + 0xb3f9c6e9UL, 0x0b45a18cUL, 0x19f00e62UL, 0xa14c6907UL, 0x3c9b51beUL, + 0x842736dbUL, 0x96929935UL, 0x2e2efe50UL, 0x2654b999UL, 0x9ee8defcUL, + 0x8c5d7112UL, 0x34e11677UL, 0xa9362eceUL, 0x118a49abUL, 0x033fe645UL, + 0xbb838120UL, 0xe3e09176UL, 0x5b5cf613UL, 0x49e959fdUL, 0xf1553e98UL, + 0x6c820621UL, 0xd43e6144UL, 0xc68bceaaUL, 0x7e37a9cfUL, 0xd67f4138UL, + 0x6ec3265dUL, 0x7c7689b3UL, 0xc4caeed6UL, 0x591dd66fUL, 0xe1a1b10aUL, + 0xf3141ee4UL, 0x4ba87981UL, 0x13cb69d7UL, 0xab770eb2UL, 0xb9c2a15cUL, + 0x017ec639UL, 0x9ca9fe80UL, 0x241599e5UL, 0x36a0360bUL, 0x8e1c516eUL, + 0x866616a7UL, 0x3eda71c2UL, 0x2c6fde2cUL, 0x94d3b949UL, 0x090481f0UL, + 0xb1b8e695UL, 0xa30d497bUL, 0x1bb12e1eUL, 0x43d23e48UL, 0xfb6e592dUL, + 0xe9dbf6c3UL, 0x516791a6UL, 0xccb0a91fUL, 0x740cce7aUL, 0x66b96194UL, + 0xde0506f1UL + }, + { + 0x00000000UL, 0x96300777UL, 0x2c610eeeUL, 0xba510999UL, 0x19c46d07UL, + 0x8ff46a70UL, 0x35a563e9UL, 0xa395649eUL, 0x3288db0eUL, 0xa4b8dc79UL, + 0x1ee9d5e0UL, 0x88d9d297UL, 0x2b4cb609UL, 0xbd7cb17eUL, 0x072db8e7UL, + 0x911dbf90UL, 0x6410b71dUL, 0xf220b06aUL, 0x4871b9f3UL, 0xde41be84UL, + 0x7dd4da1aUL, 0xebe4dd6dUL, 0x51b5d4f4UL, 0xc785d383UL, 0x56986c13UL, + 0xc0a86b64UL, 0x7af962fdUL, 0xecc9658aUL, 0x4f5c0114UL, 0xd96c0663UL, + 0x633d0ffaUL, 0xf50d088dUL, 0xc8206e3bUL, 0x5e10694cUL, 0xe44160d5UL, + 0x727167a2UL, 0xd1e4033cUL, 0x47d4044bUL, 0xfd850dd2UL, 0x6bb50aa5UL, + 0xfaa8b535UL, 0x6c98b242UL, 0xd6c9bbdbUL, 0x40f9bcacUL, 0xe36cd832UL, + 0x755cdf45UL, 0xcf0dd6dcUL, 0x593dd1abUL, 0xac30d926UL, 0x3a00de51UL, + 0x8051d7c8UL, 0x1661d0bfUL, 0xb5f4b421UL, 0x23c4b356UL, 0x9995bacfUL, + 0x0fa5bdb8UL, 0x9eb80228UL, 0x0888055fUL, 0xb2d90cc6UL, 0x24e90bb1UL, + 0x877c6f2fUL, 0x114c6858UL, 0xab1d61c1UL, 0x3d2d66b6UL, 0x9041dc76UL, + 0x0671db01UL, 0xbc20d298UL, 0x2a10d5efUL, 0x8985b171UL, 0x1fb5b606UL, + 0xa5e4bf9fUL, 0x33d4b8e8UL, 0xa2c90778UL, 0x34f9000fUL, 0x8ea80996UL, + 0x18980ee1UL, 0xbb0d6a7fUL, 0x2d3d6d08UL, 0x976c6491UL, 0x015c63e6UL, + 0xf4516b6bUL, 0x62616c1cUL, 0xd8306585UL, 0x4e0062f2UL, 0xed95066cUL, + 0x7ba5011bUL, 0xc1f40882UL, 0x57c40ff5UL, 0xc6d9b065UL, 0x50e9b712UL, + 0xeab8be8bUL, 0x7c88b9fcUL, 0xdf1ddd62UL, 0x492dda15UL, 0xf37cd38cUL, + 0x654cd4fbUL, 0x5861b24dUL, 0xce51b53aUL, 0x7400bca3UL, 0xe230bbd4UL, + 0x41a5df4aUL, 0xd795d83dUL, 0x6dc4d1a4UL, 0xfbf4d6d3UL, 0x6ae96943UL, + 0xfcd96e34UL, 0x468867adUL, 0xd0b860daUL, 0x732d0444UL, 0xe51d0333UL, + 0x5f4c0aaaUL, 0xc97c0dddUL, 0x3c710550UL, 0xaa410227UL, 0x10100bbeUL, + 0x86200cc9UL, 0x25b56857UL, 0xb3856f20UL, 0x09d466b9UL, 0x9fe461ceUL, + 0x0ef9de5eUL, 0x98c9d929UL, 0x2298d0b0UL, 0xb4a8d7c7UL, 0x173db359UL, + 0x810db42eUL, 0x3b5cbdb7UL, 0xad6cbac0UL, 0x2083b8edUL, 0xb6b3bf9aUL, + 0x0ce2b603UL, 0x9ad2b174UL, 0x3947d5eaUL, 0xaf77d29dUL, 0x1526db04UL, + 0x8316dc73UL, 0x120b63e3UL, 0x843b6494UL, 0x3e6a6d0dUL, 0xa85a6a7aUL, + 0x0bcf0ee4UL, 0x9dff0993UL, 0x27ae000aUL, 0xb19e077dUL, 0x44930ff0UL, + 0xd2a30887UL, 0x68f2011eUL, 0xfec20669UL, 0x5d5762f7UL, 0xcb676580UL, + 0x71366c19UL, 0xe7066b6eUL, 0x761bd4feUL, 0xe02bd389UL, 0x5a7ada10UL, + 0xcc4add67UL, 0x6fdfb9f9UL, 0xf9efbe8eUL, 0x43beb717UL, 0xd58eb060UL, + 0xe8a3d6d6UL, 0x7e93d1a1UL, 0xc4c2d838UL, 0x52f2df4fUL, 0xf167bbd1UL, + 0x6757bca6UL, 0xdd06b53fUL, 0x4b36b248UL, 0xda2b0dd8UL, 0x4c1b0aafUL, + 0xf64a0336UL, 0x607a0441UL, 0xc3ef60dfUL, 0x55df67a8UL, 0xef8e6e31UL, + 0x79be6946UL, 0x8cb361cbUL, 0x1a8366bcUL, 0xa0d26f25UL, 0x36e26852UL, + 0x95770cccUL, 0x03470bbbUL, 0xb9160222UL, 0x2f260555UL, 0xbe3bbac5UL, + 0x280bbdb2UL, 0x925ab42bUL, 0x046ab35cUL, 0xa7ffd7c2UL, 0x31cfd0b5UL, + 0x8b9ed92cUL, 0x1daede5bUL, 0xb0c2649bUL, 0x26f263ecUL, 0x9ca36a75UL, + 0x0a936d02UL, 0xa906099cUL, 0x3f360eebUL, 0x85670772UL, 0x13570005UL, + 0x824abf95UL, 0x147ab8e2UL, 0xae2bb17bUL, 0x381bb60cUL, 0x9b8ed292UL, + 0x0dbed5e5UL, 0xb7efdc7cUL, 0x21dfdb0bUL, 0xd4d2d386UL, 0x42e2d4f1UL, + 0xf8b3dd68UL, 0x6e83da1fUL, 0xcd16be81UL, 0x5b26b9f6UL, 0xe177b06fUL, + 0x7747b718UL, 0xe65a0888UL, 0x706a0fffUL, 0xca3b0666UL, 0x5c0b0111UL, + 0xff9e658fUL, 0x69ae62f8UL, 0xd3ff6b61UL, 0x45cf6c16UL, 0x78e20aa0UL, + 0xeed20dd7UL, 0x5483044eUL, 0xc2b30339UL, 0x612667a7UL, 0xf71660d0UL, + 0x4d476949UL, 0xdb776e3eUL, 0x4a6ad1aeUL, 0xdc5ad6d9UL, 0x660bdf40UL, + 0xf03bd837UL, 0x53aebca9UL, 0xc59ebbdeUL, 0x7fcfb247UL, 0xe9ffb530UL, + 0x1cf2bdbdUL, 0x8ac2bacaUL, 0x3093b353UL, 0xa6a3b424UL, 0x0536d0baUL, + 0x9306d7cdUL, 0x2957de54UL, 0xbf67d923UL, 0x2e7a66b3UL, 0xb84a61c4UL, + 0x021b685dUL, 0x942b6f2aUL, 0x37be0bb4UL, 0xa18e0cc3UL, 0x1bdf055aUL, + 0x8def022dUL + }, + { + 0x00000000UL, 0x41311b19UL, 0x82623632UL, 0xc3532d2bUL, 0x04c56c64UL, + 0x45f4777dUL, 0x86a75a56UL, 0xc796414fUL, 0x088ad9c8UL, 0x49bbc2d1UL, + 0x8ae8effaUL, 0xcbd9f4e3UL, 0x0c4fb5acUL, 0x4d7eaeb5UL, 0x8e2d839eUL, + 0xcf1c9887UL, 0x5112c24aUL, 0x1023d953UL, 0xd370f478UL, 0x9241ef61UL, + 0x55d7ae2eUL, 0x14e6b537UL, 0xd7b5981cUL, 0x96848305UL, 0x59981b82UL, + 0x18a9009bUL, 0xdbfa2db0UL, 0x9acb36a9UL, 0x5d5d77e6UL, 0x1c6c6cffUL, + 0xdf3f41d4UL, 0x9e0e5acdUL, 0xa2248495UL, 0xe3159f8cUL, 0x2046b2a7UL, + 0x6177a9beUL, 0xa6e1e8f1UL, 0xe7d0f3e8UL, 0x2483dec3UL, 0x65b2c5daUL, + 0xaaae5d5dUL, 0xeb9f4644UL, 0x28cc6b6fUL, 0x69fd7076UL, 0xae6b3139UL, + 0xef5a2a20UL, 0x2c09070bUL, 0x6d381c12UL, 0xf33646dfUL, 0xb2075dc6UL, + 0x715470edUL, 0x30656bf4UL, 0xf7f32abbUL, 0xb6c231a2UL, 0x75911c89UL, + 0x34a00790UL, 0xfbbc9f17UL, 0xba8d840eUL, 0x79dea925UL, 0x38efb23cUL, + 0xff79f373UL, 0xbe48e86aUL, 0x7d1bc541UL, 0x3c2ade58UL, 0x054f79f0UL, + 0x447e62e9UL, 0x872d4fc2UL, 0xc61c54dbUL, 0x018a1594UL, 0x40bb0e8dUL, + 0x83e823a6UL, 0xc2d938bfUL, 0x0dc5a038UL, 0x4cf4bb21UL, 0x8fa7960aUL, + 0xce968d13UL, 0x0900cc5cUL, 0x4831d745UL, 0x8b62fa6eUL, 0xca53e177UL, + 0x545dbbbaUL, 0x156ca0a3UL, 0xd63f8d88UL, 0x970e9691UL, 0x5098d7deUL, + 0x11a9ccc7UL, 0xd2fae1ecUL, 0x93cbfaf5UL, 0x5cd76272UL, 0x1de6796bUL, + 0xdeb55440UL, 0x9f844f59UL, 0x58120e16UL, 0x1923150fUL, 0xda703824UL, + 0x9b41233dUL, 0xa76bfd65UL, 0xe65ae67cUL, 0x2509cb57UL, 0x6438d04eUL, + 0xa3ae9101UL, 0xe29f8a18UL, 0x21cca733UL, 0x60fdbc2aUL, 0xafe124adUL, + 0xeed03fb4UL, 0x2d83129fUL, 0x6cb20986UL, 0xab2448c9UL, 0xea1553d0UL, + 0x29467efbUL, 0x687765e2UL, 0xf6793f2fUL, 0xb7482436UL, 0x741b091dUL, + 0x352a1204UL, 0xf2bc534bUL, 0xb38d4852UL, 0x70de6579UL, 0x31ef7e60UL, + 0xfef3e6e7UL, 0xbfc2fdfeUL, 0x7c91d0d5UL, 0x3da0cbccUL, 0xfa368a83UL, + 0xbb07919aUL, 0x7854bcb1UL, 0x3965a7a8UL, 0x4b98833bUL, 0x0aa99822UL, + 0xc9fab509UL, 0x88cbae10UL, 0x4f5def5fUL, 0x0e6cf446UL, 0xcd3fd96dUL, + 0x8c0ec274UL, 0x43125af3UL, 0x022341eaUL, 0xc1706cc1UL, 0x804177d8UL, + 0x47d73697UL, 0x06e62d8eUL, 0xc5b500a5UL, 0x84841bbcUL, 0x1a8a4171UL, + 0x5bbb5a68UL, 0x98e87743UL, 0xd9d96c5aUL, 0x1e4f2d15UL, 0x5f7e360cUL, + 0x9c2d1b27UL, 0xdd1c003eUL, 0x120098b9UL, 0x533183a0UL, 0x9062ae8bUL, + 0xd153b592UL, 0x16c5f4ddUL, 0x57f4efc4UL, 0x94a7c2efUL, 0xd596d9f6UL, + 0xe9bc07aeUL, 0xa88d1cb7UL, 0x6bde319cUL, 0x2aef2a85UL, 0xed796bcaUL, + 0xac4870d3UL, 0x6f1b5df8UL, 0x2e2a46e1UL, 0xe136de66UL, 0xa007c57fUL, + 0x6354e854UL, 0x2265f34dUL, 0xe5f3b202UL, 0xa4c2a91bUL, 0x67918430UL, + 0x26a09f29UL, 0xb8aec5e4UL, 0xf99fdefdUL, 0x3accf3d6UL, 0x7bfde8cfUL, + 0xbc6ba980UL, 0xfd5ab299UL, 0x3e099fb2UL, 0x7f3884abUL, 0xb0241c2cUL, + 0xf1150735UL, 0x32462a1eUL, 0x73773107UL, 0xb4e17048UL, 0xf5d06b51UL, + 0x3683467aUL, 0x77b25d63UL, 0x4ed7facbUL, 0x0fe6e1d2UL, 0xccb5ccf9UL, + 0x8d84d7e0UL, 0x4a1296afUL, 0x0b238db6UL, 0xc870a09dUL, 0x8941bb84UL, + 0x465d2303UL, 0x076c381aUL, 0xc43f1531UL, 0x850e0e28UL, 0x42984f67UL, + 0x03a9547eUL, 0xc0fa7955UL, 0x81cb624cUL, 0x1fc53881UL, 0x5ef42398UL, + 0x9da70eb3UL, 0xdc9615aaUL, 0x1b0054e5UL, 0x5a314ffcUL, 0x996262d7UL, + 0xd85379ceUL, 0x174fe149UL, 0x567efa50UL, 0x952dd77bUL, 0xd41ccc62UL, + 0x138a8d2dUL, 0x52bb9634UL, 0x91e8bb1fUL, 0xd0d9a006UL, 0xecf37e5eUL, + 0xadc26547UL, 0x6e91486cUL, 0x2fa05375UL, 0xe836123aUL, 0xa9070923UL, + 0x6a542408UL, 0x2b653f11UL, 0xe479a796UL, 0xa548bc8fUL, 0x661b91a4UL, + 0x272a8abdUL, 0xe0bccbf2UL, 0xa18dd0ebUL, 0x62defdc0UL, 0x23efe6d9UL, + 0xbde1bc14UL, 0xfcd0a70dUL, 0x3f838a26UL, 0x7eb2913fUL, 0xb924d070UL, + 0xf815cb69UL, 0x3b46e642UL, 0x7a77fd5bUL, 0xb56b65dcUL, 0xf45a7ec5UL, + 0x370953eeUL, 0x763848f7UL, 0xb1ae09b8UL, 0xf09f12a1UL, 0x33cc3f8aUL, + 0x72fd2493UL + }, + { + 0x00000000UL, 0x376ac201UL, 0x6ed48403UL, 0x59be4602UL, 0xdca80907UL, + 0xebc2cb06UL, 0xb27c8d04UL, 0x85164f05UL, 0xb851130eUL, 0x8f3bd10fUL, + 0xd685970dUL, 0xe1ef550cUL, 0x64f91a09UL, 0x5393d808UL, 0x0a2d9e0aUL, + 0x3d475c0bUL, 0x70a3261cUL, 0x47c9e41dUL, 0x1e77a21fUL, 0x291d601eUL, + 0xac0b2f1bUL, 0x9b61ed1aUL, 0xc2dfab18UL, 0xf5b56919UL, 0xc8f23512UL, + 0xff98f713UL, 0xa626b111UL, 0x914c7310UL, 0x145a3c15UL, 0x2330fe14UL, + 0x7a8eb816UL, 0x4de47a17UL, 0xe0464d38UL, 0xd72c8f39UL, 0x8e92c93bUL, + 0xb9f80b3aUL, 0x3cee443fUL, 0x0b84863eUL, 0x523ac03cUL, 0x6550023dUL, + 0x58175e36UL, 0x6f7d9c37UL, 0x36c3da35UL, 0x01a91834UL, 0x84bf5731UL, + 0xb3d59530UL, 0xea6bd332UL, 0xdd011133UL, 0x90e56b24UL, 0xa78fa925UL, + 0xfe31ef27UL, 0xc95b2d26UL, 0x4c4d6223UL, 0x7b27a022UL, 0x2299e620UL, + 0x15f32421UL, 0x28b4782aUL, 0x1fdeba2bUL, 0x4660fc29UL, 0x710a3e28UL, + 0xf41c712dUL, 0xc376b32cUL, 0x9ac8f52eUL, 0xada2372fUL, 0xc08d9a70UL, + 0xf7e75871UL, 0xae591e73UL, 0x9933dc72UL, 0x1c259377UL, 0x2b4f5176UL, + 0x72f11774UL, 0x459bd575UL, 0x78dc897eUL, 0x4fb64b7fUL, 0x16080d7dUL, + 0x2162cf7cUL, 0xa4748079UL, 0x931e4278UL, 0xcaa0047aUL, 0xfdcac67bUL, + 0xb02ebc6cUL, 0x87447e6dUL, 0xdefa386fUL, 0xe990fa6eUL, 0x6c86b56bUL, + 0x5bec776aUL, 0x02523168UL, 0x3538f369UL, 0x087faf62UL, 0x3f156d63UL, + 0x66ab2b61UL, 0x51c1e960UL, 0xd4d7a665UL, 0xe3bd6464UL, 0xba032266UL, + 0x8d69e067UL, 0x20cbd748UL, 0x17a11549UL, 0x4e1f534bUL, 0x7975914aUL, + 0xfc63de4fUL, 0xcb091c4eUL, 0x92b75a4cUL, 0xa5dd984dUL, 0x989ac446UL, + 0xaff00647UL, 0xf64e4045UL, 0xc1248244UL, 0x4432cd41UL, 0x73580f40UL, + 0x2ae64942UL, 0x1d8c8b43UL, 0x5068f154UL, 0x67023355UL, 0x3ebc7557UL, + 0x09d6b756UL, 0x8cc0f853UL, 0xbbaa3a52UL, 0xe2147c50UL, 0xd57ebe51UL, + 0xe839e25aUL, 0xdf53205bUL, 0x86ed6659UL, 0xb187a458UL, 0x3491eb5dUL, + 0x03fb295cUL, 0x5a456f5eUL, 0x6d2fad5fUL, 0x801b35e1UL, 0xb771f7e0UL, + 0xeecfb1e2UL, 0xd9a573e3UL, 0x5cb33ce6UL, 0x6bd9fee7UL, 0x3267b8e5UL, + 0x050d7ae4UL, 0x384a26efUL, 0x0f20e4eeUL, 0x569ea2ecUL, 0x61f460edUL, + 0xe4e22fe8UL, 0xd388ede9UL, 0x8a36abebUL, 0xbd5c69eaUL, 0xf0b813fdUL, + 0xc7d2d1fcUL, 0x9e6c97feUL, 0xa90655ffUL, 0x2c101afaUL, 0x1b7ad8fbUL, + 0x42c49ef9UL, 0x75ae5cf8UL, 0x48e900f3UL, 0x7f83c2f2UL, 0x263d84f0UL, + 0x115746f1UL, 0x944109f4UL, 0xa32bcbf5UL, 0xfa958df7UL, 0xcdff4ff6UL, + 0x605d78d9UL, 0x5737bad8UL, 0x0e89fcdaUL, 0x39e33edbUL, 0xbcf571deUL, + 0x8b9fb3dfUL, 0xd221f5ddUL, 0xe54b37dcUL, 0xd80c6bd7UL, 0xef66a9d6UL, + 0xb6d8efd4UL, 0x81b22dd5UL, 0x04a462d0UL, 0x33cea0d1UL, 0x6a70e6d3UL, + 0x5d1a24d2UL, 0x10fe5ec5UL, 0x27949cc4UL, 0x7e2adac6UL, 0x494018c7UL, + 0xcc5657c2UL, 0xfb3c95c3UL, 0xa282d3c1UL, 0x95e811c0UL, 0xa8af4dcbUL, + 0x9fc58fcaUL, 0xc67bc9c8UL, 0xf1110bc9UL, 0x740744ccUL, 0x436d86cdUL, + 0x1ad3c0cfUL, 0x2db902ceUL, 0x4096af91UL, 0x77fc6d90UL, 0x2e422b92UL, + 0x1928e993UL, 0x9c3ea696UL, 0xab546497UL, 0xf2ea2295UL, 0xc580e094UL, + 0xf8c7bc9fUL, 0xcfad7e9eUL, 0x9613389cUL, 0xa179fa9dUL, 0x246fb598UL, + 0x13057799UL, 0x4abb319bUL, 0x7dd1f39aUL, 0x3035898dUL, 0x075f4b8cUL, + 0x5ee10d8eUL, 0x698bcf8fUL, 0xec9d808aUL, 0xdbf7428bUL, 0x82490489UL, + 0xb523c688UL, 0x88649a83UL, 0xbf0e5882UL, 0xe6b01e80UL, 0xd1dadc81UL, + 0x54cc9384UL, 0x63a65185UL, 0x3a181787UL, 0x0d72d586UL, 0xa0d0e2a9UL, + 0x97ba20a8UL, 0xce0466aaUL, 0xf96ea4abUL, 0x7c78ebaeUL, 0x4b1229afUL, + 0x12ac6fadUL, 0x25c6adacUL, 0x1881f1a7UL, 0x2feb33a6UL, 0x765575a4UL, + 0x413fb7a5UL, 0xc429f8a0UL, 0xf3433aa1UL, 0xaafd7ca3UL, 0x9d97bea2UL, + 0xd073c4b5UL, 0xe71906b4UL, 0xbea740b6UL, 0x89cd82b7UL, 0x0cdbcdb2UL, + 0x3bb10fb3UL, 0x620f49b1UL, 0x55658bb0UL, 0x6822d7bbUL, 0x5f4815baUL, + 0x06f653b8UL, 0x319c91b9UL, 0xb48adebcUL, 0x83e01cbdUL, 0xda5e5abfUL, + 0xed3498beUL + }, + { + 0x00000000UL, 0x6567bcb8UL, 0x8bc809aaUL, 0xeeafb512UL, 0x5797628fUL, + 0x32f0de37UL, 0xdc5f6b25UL, 0xb938d79dUL, 0xef28b4c5UL, 0x8a4f087dUL, + 0x64e0bd6fUL, 0x018701d7UL, 0xb8bfd64aUL, 0xddd86af2UL, 0x3377dfe0UL, + 0x56106358UL, 0x9f571950UL, 0xfa30a5e8UL, 0x149f10faUL, 0x71f8ac42UL, + 0xc8c07bdfUL, 0xada7c767UL, 0x43087275UL, 0x266fcecdUL, 0x707fad95UL, + 0x1518112dUL, 0xfbb7a43fUL, 0x9ed01887UL, 0x27e8cf1aUL, 0x428f73a2UL, + 0xac20c6b0UL, 0xc9477a08UL, 0x3eaf32a0UL, 0x5bc88e18UL, 0xb5673b0aUL, + 0xd00087b2UL, 0x6938502fUL, 0x0c5fec97UL, 0xe2f05985UL, 0x8797e53dUL, + 0xd1878665UL, 0xb4e03addUL, 0x5a4f8fcfUL, 0x3f283377UL, 0x8610e4eaUL, + 0xe3775852UL, 0x0dd8ed40UL, 0x68bf51f8UL, 0xa1f82bf0UL, 0xc49f9748UL, + 0x2a30225aUL, 0x4f579ee2UL, 0xf66f497fUL, 0x9308f5c7UL, 0x7da740d5UL, + 0x18c0fc6dUL, 0x4ed09f35UL, 0x2bb7238dUL, 0xc518969fUL, 0xa07f2a27UL, + 0x1947fdbaUL, 0x7c204102UL, 0x928ff410UL, 0xf7e848a8UL, 0x3d58149bUL, + 0x583fa823UL, 0xb6901d31UL, 0xd3f7a189UL, 0x6acf7614UL, 0x0fa8caacUL, + 0xe1077fbeUL, 0x8460c306UL, 0xd270a05eUL, 0xb7171ce6UL, 0x59b8a9f4UL, + 0x3cdf154cUL, 0x85e7c2d1UL, 0xe0807e69UL, 0x0e2fcb7bUL, 0x6b4877c3UL, + 0xa20f0dcbUL, 0xc768b173UL, 0x29c70461UL, 0x4ca0b8d9UL, 0xf5986f44UL, + 0x90ffd3fcUL, 0x7e5066eeUL, 0x1b37da56UL, 0x4d27b90eUL, 0x284005b6UL, + 0xc6efb0a4UL, 0xa3880c1cUL, 0x1ab0db81UL, 0x7fd76739UL, 0x9178d22bUL, + 0xf41f6e93UL, 0x03f7263bUL, 0x66909a83UL, 0x883f2f91UL, 0xed589329UL, + 0x546044b4UL, 0x3107f80cUL, 0xdfa84d1eUL, 0xbacff1a6UL, 0xecdf92feUL, + 0x89b82e46UL, 0x67179b54UL, 0x027027ecUL, 0xbb48f071UL, 0xde2f4cc9UL, + 0x3080f9dbUL, 0x55e74563UL, 0x9ca03f6bUL, 0xf9c783d3UL, 0x176836c1UL, + 0x720f8a79UL, 0xcb375de4UL, 0xae50e15cUL, 0x40ff544eUL, 0x2598e8f6UL, + 0x73888baeUL, 0x16ef3716UL, 0xf8408204UL, 0x9d273ebcUL, 0x241fe921UL, + 0x41785599UL, 0xafd7e08bUL, 0xcab05c33UL, 0x3bb659edUL, 0x5ed1e555UL, + 0xb07e5047UL, 0xd519ecffUL, 0x6c213b62UL, 0x094687daUL, 0xe7e932c8UL, + 0x828e8e70UL, 0xd49eed28UL, 0xb1f95190UL, 0x5f56e482UL, 0x3a31583aUL, + 0x83098fa7UL, 0xe66e331fUL, 0x08c1860dUL, 0x6da63ab5UL, 0xa4e140bdUL, + 0xc186fc05UL, 0x2f294917UL, 0x4a4ef5afUL, 0xf3762232UL, 0x96119e8aUL, + 0x78be2b98UL, 0x1dd99720UL, 0x4bc9f478UL, 0x2eae48c0UL, 0xc001fdd2UL, + 0xa566416aUL, 0x1c5e96f7UL, 0x79392a4fUL, 0x97969f5dUL, 0xf2f123e5UL, + 0x05196b4dUL, 0x607ed7f5UL, 0x8ed162e7UL, 0xebb6de5fUL, 0x528e09c2UL, + 0x37e9b57aUL, 0xd9460068UL, 0xbc21bcd0UL, 0xea31df88UL, 0x8f566330UL, + 0x61f9d622UL, 0x049e6a9aUL, 0xbda6bd07UL, 0xd8c101bfUL, 0x366eb4adUL, + 0x53090815UL, 0x9a4e721dUL, 0xff29cea5UL, 0x11867bb7UL, 0x74e1c70fUL, + 0xcdd91092UL, 0xa8beac2aUL, 0x46111938UL, 0x2376a580UL, 0x7566c6d8UL, + 0x10017a60UL, 0xfeaecf72UL, 0x9bc973caUL, 0x22f1a457UL, 0x479618efUL, + 0xa939adfdUL, 0xcc5e1145UL, 0x06ee4d76UL, 0x6389f1ceUL, 0x8d2644dcUL, + 0xe841f864UL, 0x51792ff9UL, 0x341e9341UL, 0xdab12653UL, 0xbfd69aebUL, + 0xe9c6f9b3UL, 0x8ca1450bUL, 0x620ef019UL, 0x07694ca1UL, 0xbe519b3cUL, + 0xdb362784UL, 0x35999296UL, 0x50fe2e2eUL, 0x99b95426UL, 0xfcdee89eUL, + 0x12715d8cUL, 0x7716e134UL, 0xce2e36a9UL, 0xab498a11UL, 0x45e63f03UL, + 0x208183bbUL, 0x7691e0e3UL, 0x13f65c5bUL, 0xfd59e949UL, 0x983e55f1UL, + 0x2106826cUL, 0x44613ed4UL, 0xaace8bc6UL, 0xcfa9377eUL, 0x38417fd6UL, + 0x5d26c36eUL, 0xb389767cUL, 0xd6eecac4UL, 0x6fd61d59UL, 0x0ab1a1e1UL, + 0xe41e14f3UL, 0x8179a84bUL, 0xd769cb13UL, 0xb20e77abUL, 0x5ca1c2b9UL, + 0x39c67e01UL, 0x80fea99cUL, 0xe5991524UL, 0x0b36a036UL, 0x6e511c8eUL, + 0xa7166686UL, 0xc271da3eUL, 0x2cde6f2cUL, 0x49b9d394UL, 0xf0810409UL, + 0x95e6b8b1UL, 0x7b490da3UL, 0x1e2eb11bUL, 0x483ed243UL, 0x2d596efbUL, + 0xc3f6dbe9UL, 0xa6916751UL, 0x1fa9b0ccUL, 0x7ace0c74UL, 0x9461b966UL, + 0xf10605deUL +#endif + } +}; +#endif /* DYNAMIC_CRC_TABLE */ + +/* ========================================================================= + * This function can be used by asm versions of crc32() + */ +const z_crc_t FAR * ZEXPORT get_crc_table() +{ +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + return (const z_crc_t FAR *)crc_table; +} + +/* ========================================================================= */ +#define DO1 crc = crc_table[0][((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8) +#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1 + +/* ========================================================================= */ +unsigned long ZEXPORT crc32( + unsigned long crc, + const unsigned char FAR *buf, + uInt len +) { + if (buf == Z_NULL) return 0UL; + +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + +#ifdef BYFOUR + if (sizeof(void *) == sizeof(ptrdiff_t)) { + z_crc_t endian; + + endian = 1; + if (*((unsigned char *)(&endian))) + return crc32_little(crc, buf, len); + else + return crc32_big(crc, buf, len); + } +#endif /* BYFOUR */ + crc = crc ^ 0xffffffffUL; + while (len >= 8) { + DO8; + len -= 8; + } + if (len) do { + DO1; + } while (--len); + return crc ^ 0xffffffffUL; +} + +#ifdef BYFOUR + +/* ========================================================================= */ +#define DOLIT4 c ^= *buf4++; \ + c = crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \ + crc_table[1][(c >> 16) & 0xff] ^ crc_table[0][c >> 24] +#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4 + +/* ========================================================================= */ +local unsigned long crc32_little( + unsigned long crc, + const unsigned char FAR *buf, + unsigned len +) { + z_crc_t c; + const z_crc_t FAR *buf4; + + c = (z_crc_t)crc; + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + len--; + } + + buf4 = (const z_crc_t FAR *)(const void FAR *)buf; + while (len >= 32) { + DOLIT32; + len -= 32; + } + while (len >= 4) { + DOLIT4; + len -= 4; + } + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + } while (--len); + c = ~c; + return (unsigned long)c; +} + +/* ========================================================================= */ +#define DOBIG4 c ^= *++buf4; \ + c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \ + crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24] +#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4 + +/* ========================================================================= */ +local unsigned long crc32_big( + unsigned long crc, + const unsigned char FAR *buf, + unsigned len +) { + z_crc_t c; + const z_crc_t FAR *buf4; + + c = ZSWAP32((z_crc_t)crc); + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + len--; + } + + buf4 = (const z_crc_t FAR *)(const void FAR *)buf; + buf4--; + while (len >= 32) { + DOBIG32; + len -= 32; + } + while (len >= 4) { + DOBIG4; + len -= 4; + } + buf4++; + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + } while (--len); + c = ~c; + return (unsigned long)(ZSWAP32(c)); +} + +#endif /* BYFOUR */ + +#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */ + +/* ========================================================================= */ +local unsigned long gf2_matrix_times( + unsigned long *mat, + unsigned long vec +) { + unsigned long sum; + + sum = 0; + while (vec) { + if (vec & 1) + sum ^= *mat; + vec >>= 1; + mat++; + } + return sum; +} + +/* ========================================================================= */ +local void gf2_matrix_square( + unsigned long *square, + unsigned long *mat +) { + int n; + + for (n = 0; n < GF2_DIM; n++) + square[n] = gf2_matrix_times(mat, mat[n]); +} + +/* ========================================================================= */ +local uLong crc32_combine_( + uLong crc1, + uLong crc2, + z_off64_t len2 +) { + int n; + unsigned long row; + unsigned long even[GF2_DIM]; /* even-power-of-two zeros operator */ + unsigned long odd[GF2_DIM]; /* odd-power-of-two zeros operator */ + + /* degenerate case (also disallow negative lengths) */ + if (len2 <= 0) + return crc1; + + /* put operator for one zero bit in odd */ + odd[0] = 0xedb88320UL; /* CRC-32 polynomial */ + row = 1; + for (n = 1; n < GF2_DIM; n++) { + odd[n] = row; + row <<= 1; + } + + /* put operator for two zero bits in even */ + gf2_matrix_square(even, odd); + + /* put operator for four zero bits in odd */ + gf2_matrix_square(odd, even); + + /* apply len2 zeros to crc1 (first square will put the operator for one + zero byte, eight zero bits, in even) */ + do { + /* apply zeros operator for this bit of len2 */ + gf2_matrix_square(even, odd); + if (len2 & 1) + crc1 = gf2_matrix_times(even, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + if (len2 == 0) + break; + + /* another iteration of the loop with odd and even swapped */ + gf2_matrix_square(odd, even); + if (len2 & 1) + crc1 = gf2_matrix_times(odd, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + } while (len2 != 0); + + /* return combined crc */ + crc1 ^= crc2; + return crc1; +} + +/* ========================================================================= */ +uLong ZEXPORT crc32_combine( + uLong crc1, + uLong crc2, + z_off_t len2 +) { + return crc32_combine_(crc1, crc2, len2); +} + +uLong ZEXPORT crc32_combine64( + uLong crc1, + uLong crc2, + z_off64_t len2 +) { + return crc32_combine_(crc1, crc2, len2); +} +#undef DO1 // see make-zlib.r +#undef DO8 // see make-zlib.r +/* adler32.c -- compute the Adler-32 checksum of a data stream + * Copyright (C) 1995-2011 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +// #include "zutil.h" /* In sys-zlib.h (see make-zlib.r) */ + +#define local static -#define BASE 65521L /* largest prime smaller than 65536 */ +local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2)); + +#define BASE 65521 /* largest prime smaller than 65536 */ #define NMAX 5552 /* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ -#define DO1(buf,i) {s1 += buf[i]; s2 += s1;} +#define DO1(buf,i) {adler += (buf)[i]; sum2 += adler;} #define DO2(buf,i) DO1(buf,i); DO1(buf,i+1); #define DO4(buf,i) DO2(buf,i); DO2(buf,i+2); #define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); #define DO16(buf) DO8(buf,0); DO8(buf,8); -/* adler32.c -- compute the Adler-32 checksum of a data stream - * Copyright (C) 1995-1998 Mark Adler - * For conditions of distribution and use, see copyright notice in zlib.h - */ +/* use NO_DIVIDE if your processor does not do division in hardware -- + try it both ways to see which is faster */ +#ifdef NO_DIVIDE +/* note that this assumes BASE is 65521, where 65536 % 65521 == 15 + (thank you to John Reiser for pointing this out) */ +# define CHOP(a) \ + do { \ + unsigned long tmp = a >> 16; \ + a &= 0xffffUL; \ + a += (tmp << 4) - tmp; \ + } while (0) +# define MOD28(a) \ + do { \ + CHOP(a); \ + if (a >= BASE) a -= BASE; \ + } while (0) +# define MOD(a) \ + do { \ + CHOP(a); \ + MOD28(a); \ + } while (0) +# define MOD63(a) \ + do { /* this assumes a is not negative */ \ + z_off64_t tmp = a >> 32; \ + a &= 0xffffffffL; \ + a += (tmp << 8) - (tmp << 5) + tmp; \ + tmp = a >> 16; \ + a &= 0xffffL; \ + a += (tmp << 4) - tmp; \ + tmp = a >> 16; \ + a &= 0xffffL; \ + a += (tmp << 4) - tmp; \ + if (a >= BASE) a -= BASE; \ + } while (0) +#else +# define MOD(a) a %= BASE +# define MOD28(a) a %= BASE +# define MOD63(a) a %= BASE +#endif -uLong ZEXPORT adler32(adler, buf, len) - uLong adler; - const Bytef *buf; - uInt len; -{ - unsigned long s1 = adler & 0xffff; - unsigned long s2 = (adler >> 16) & 0xffff; - int k; +/* ========================================================================= */ +uLong ZEXPORT adler32( + uLong adler, + const Bytef *buf, + uInt len +) { + unsigned long sum2; + unsigned n; + + /* split Adler-32 into component sums */ + sum2 = (adler >> 16) & 0xffff; + adler &= 0xffff; + + /* in case user likes doing a byte at a time, keep it fast */ + if (len == 1) { + adler += buf[0]; + if (adler >= BASE) + adler -= BASE; + sum2 += adler; + if (sum2 >= BASE) + sum2 -= BASE; + return adler | (sum2 << 16); + } + + /* initial Adler-32 value (deferred check for len == 1 speed) */ + if (buf == Z_NULL) + return 1L; + + /* in case short lengths are provided, keep it somewhat fast */ + if (len < 16) { + while (len--) { + adler += *buf++; + sum2 += adler; + } + if (adler >= BASE) + adler -= BASE; + MOD28(sum2); /* only added so many BASE's */ + return adler | (sum2 << 16); + } - if (buf == Z_NULL) return 1L; + /* do length NMAX blocks -- requires just one modulo operation */ + while (len >= NMAX) { + len -= NMAX; + n = NMAX / 16; /* NMAX is divisible by 16 */ + do { + DO16(buf); /* 16 sums unrolled */ + buf += 16; + } while (--n); + MOD(adler); + MOD(sum2); + } - while (len > 0) { - k = len < NMAX ? (int)len : NMAX; - len -= k; - while (k >= 16) { + /* do remaining bytes (less than NMAX, still just one modulo) */ + if (len) { /* avoid modulos if none remaining */ + while (len >= 16) { + len -= 16; DO16(buf); - buf += 16; - k -= 16; + buf += 16; } - if (k != 0) do { - s1 += *buf++; - s2 += s1; - } while (--k); - s1 %= BASE; - s2 %= BASE; - } - return (s2 << 16) | s1; + while (len--) { + adler += *buf++; + sum2 += adler; + } + MOD(adler); + MOD(sum2); + } + + /* return recombined sums */ + return adler | (sum2 << 16); } -uLong crc32(uLong num, const Bytef *buf, uInt len) -{ -#ifndef CRC_DEFINED - extern uLong Update_CRC32(uLong, unsigned char *, uInt); -#endif - return (len == 0) ? num : Update_CRC32(num, (unsigned char *)buf, len); +/* ========================================================================= */ +local uLong adler32_combine_( + uLong adler1, + uLong adler2, + z_off64_t len2 +) { + unsigned long sum1; + unsigned long sum2; + unsigned rem; + + /* for negative len, return invalid adler32 as a clue for debugging */ + if (len2 < 0) + return 0xffffffffUL; + + /* the derivation of this formula is left as an exercise for the reader */ + MOD63(len2); /* assumes len2 >= 0 */ + rem = (unsigned)len2; + sum1 = adler1 & 0xffff; + sum2 = rem * sum1; + MOD(sum2); + sum1 += (adler2 & 0xffff) + BASE - 1; + sum2 += ((adler1 >> 16) & 0xffff) + ((adler2 >> 16) & 0xffff) + BASE - rem; + if (sum1 >= BASE) sum1 -= BASE; + if (sum1 >= BASE) sum1 -= BASE; + if (sum2 >= (BASE << 1)) sum2 -= (BASE << 1); + if (sum2 >= BASE) sum2 -= BASE; + return sum1 | (sum2 << 16); } -/////////////////////////////////////////// +/* ========================================================================= */ +uLong ZEXPORT adler32_combine( + uLong adler1, + uLong adler2, + z_off_t len2 +) { + return adler32_combine_(adler1, adler2, len2); +} + +uLong ZEXPORT adler32_combine64( + uLong adler1, + uLong adler2, + z_off64_t len2 +) { + return adler32_combine_(adler1, adler2, len2); +} /* deflate.c -- compress data using the deflation algorithm - * Copyright (C) 1995-1998 Jean-loup Gailly. - * For conditions of distribution and use, see copyright notice in zlib.h + * Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h */ /* @@ -93,7 +1126,7 @@ uLong crc32(uLong num, const Bytef *buf, uInt len) * REFERENCES * * Deutsch, L.P.,"DEFLATE Compressed Data Format Specification". - * Available in ftp://ds.internic.net/rfc/rfc1951.txt + * Available in http://tools.ietf.org/html/rfc1951 * * A description of the Rabin and Karp algorithm is given in the book * "Algorithms" by R. Sedgewick, Addison-Wesley, p252. @@ -103,11 +1136,12 @@ uLong crc32(uLong num, const Bytef *buf, uInt len) * */ +/* @(#) $Id$ */ -//rls#include "deflate.h" +// #include "deflate.h" /* In sys-zlib.h (see make-zlib.r) */ -//const char deflate_copyright[] = -// " deflate 1.1.2 Copyright 1995-1998 Jean-loup Gailly "; +const char deflate_copyright[] = + " deflate 1.2.8 Copyright 1995-2013 Jean-loup Gailly and Mark Adler "; /* If you use the zlib library in a product, an acknowledgment is welcome in the documentation of your product. If for some reason you cannot @@ -130,23 +1164,27 @@ typedef block_state (*compress_func) OF((deflate_state *s, int flush)); local void fill_window OF((deflate_state *s)); local block_state deflate_stored OF((deflate_state *s, int flush)); -/* local block_state deflate_fast OF((deflate_state *s, int flush)); */ +local block_state deflate_fast OF((deflate_state *s, int flush)); +#ifndef FASTEST local block_state deflate_slow OF((deflate_state *s, int flush)); +#endif +local block_state deflate_rle OF((deflate_state *s, int flush)); +local block_state deflate_huff OF((deflate_state *s, int flush)); local void lm_init OF((deflate_state *s)); local void putShortMSB OF((deflate_state *s, uInt b)); local void flush_pending OF((z_streamp strm)); local int read_buf OF((z_streamp strm, Bytef *buf, unsigned size)); -/* #ifdef ASMV */ -/* void match_init OF((void)); asm code initialization */ -/* uInt longest_match OF((deflate_state *s, IPos cur_match)); */ -/* #else */ +#ifdef ASMV + void match_init OF((void)); /* asm code initialization */ + uInt longest_match OF((deflate_state *s, IPos cur_match)); +#else local uInt longest_match OF((deflate_state *s, IPos cur_match)); -/* #endif */ +#endif -/* #ifdef DEBUG */ -/* local void check_match OF((deflate_state *s, IPos start, IPos match, */ -/* int length)); */ -/* #endif */ +#ifdef DEBUG +local void check_match OF((deflate_state *s, IPos start, IPos match, + int length)); +#endif /* =========================================================================== * Local data @@ -160,11 +1198,6 @@ local uInt longest_match OF((deflate_state *s, IPos cur_match)); #endif /* Matches of length 3 are discarded if their distance exceeds TOO_FAR */ -#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) -/* Minimum amount of lookahead, except at the end of the input file. - * See deflate.c for comments about the MIN_MATCH+1. - */ - /* Values for max_lazy_match, good_match and max_chain_length, depending on * the desired pack level (0..9). The values given below have been tuned to * exclude worst case performance for pathological files. Better values may be @@ -178,19 +1211,26 @@ typedef struct config_s { compress_func func; } config; +#ifdef FASTEST +local const config configuration_table[2] = { +/* good lazy nice chain */ +/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */ +/* 1 */ {4, 4, 8, 4, deflate_fast}}; /* max speed, no lazy matches */ +#else local const config configuration_table[10] = { /* good lazy nice chain */ /* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */ -/* 1 */ {4, 4, 8, 4, deflate_slow}, /* maximum speed, no lazy matches */ -/* 2 */ {4, 5, 16, 8, deflate_slow}, -/* 3 */ {4, 6, 32, 32, deflate_slow}, +/* 1 */ {4, 4, 8, 4, deflate_fast}, /* max speed, no lazy matches */ +/* 2 */ {4, 5, 16, 8, deflate_fast}, +/* 3 */ {4, 6, 32, 32, deflate_fast}, /* 4 */ {4, 4, 16, 16, deflate_slow}, /* lazy matches */ /* 5 */ {8, 16, 32, 32, deflate_slow}, /* 6 */ {8, 16, 128, 128, deflate_slow}, /* 7 */ {8, 32, 128, 256, deflate_slow}, /* 8 */ {32, 128, 258, 1024, deflate_slow}, -/* 9 */ {32, 258, 258, 4096, deflate_slow}}; /* maximum compression */ +/* 9 */ {32, 258, 258, 4096, deflate_slow}}; /* max compression */ +#endif /* Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4 * For deflate_fast() (levels <= 3) good is ignored and lazy has a different @@ -200,7 +1240,12 @@ local const config configuration_table[10] = { #define EQUAL 0 /* result of memcmp for equal strings */ -struct local_tree_desc_s {int dummy;}; /* for buggy compilers */ +#ifndef NO_DUMMY_DECL +struct static_tree_desc_s {int dummy;}; /* for buggy compilers */ +#endif + +/* rank Z_BLOCK between Z_NO_FLUSH and Z_PARTIAL_FLUSH */ +#define RANK(f) (((f) << 1) - ((f) > 4 ? 9 : 0)) /* =========================================================================== * Update a hash value with the given input byte @@ -221,17 +1266,17 @@ struct local_tree_desc_s {int dummy;}; /* for buggy compilers */ * input characters and the first MIN_MATCH bytes of str are valid * (except for the last MIN_MATCH-1 bytes of the input file). */ -/* #ifdef FASTEST */ -/* #define INSERT_STRING(s, str, match_head) \ */ -/* (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ */ -/* match_head = s->head[s->ins_h], \ */ -/* s->head[s->ins_h] = (Pos)(str)) */ -/* #else */ +#ifdef FASTEST +#define INSERT_STRING(s, str, match_head) \ + (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ + match_head = s->head[s->ins_h], \ + s->head[s->ins_h] = (Pos)(str)) +#else #define INSERT_STRING(s, str, match_head) \ (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ - s->prev[(str) & s->w_mask] = match_head = s->head[s->ins_h], \ + match_head = s->prev[(str) & s->w_mask] = s->head[s->ins_h], \ s->head[s->ins_h] = (Pos)(str)) -/* #endif */ +#endif /* =========================================================================== * Initialize the hash table (avoiding 64K overflow for 16 bit systems). @@ -242,32 +1287,31 @@ struct local_tree_desc_s {int dummy;}; /* for buggy compilers */ zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head)); /* ========================================================================= */ -int ZEXPORT deflateInit_(strm, level, version, stream_size) - z_streamp strm; - int level; - const char *version; - int stream_size; -{ +int ZEXPORT deflateInit_( + z_streamp strm, + int level, + const char *version, + int stream_size +) { return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, - Z_DEFAULT_STRATEGY, version, stream_size); + Z_DEFAULT_STRATEGY, version, stream_size); /* To do: ignore strm->next_in if we use it as window */ } /* ========================================================================= */ -int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, - version, stream_size) - z_streamp strm; - int level; - int method; - int windowBits; - int memLevel; - int strategy; - const char *version; - int stream_size; -{ +int ZEXPORT deflateInit2_( + z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy, + const char *version, + int stream_size +) { deflate_state *s; - int noheader = 0; - local const char* my_version = ZLIB_VERSION; + int wrap = 1; + static const char my_version[] = ZLIB_VERSION; ushf *overlay; /* We overlay pending_buf and d_buf+l_buf. This works since the average @@ -276,39 +1320,55 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, if (version == Z_NULL || version[0] != my_version[0] || stream_size != sizeof(z_stream)) { - return Z_VERSION_ERROR; + return Z_VERSION_ERROR; } if (strm == Z_NULL) return Z_STREAM_ERROR; -// strm->msg = Z_NULL; - if (strm->zalloc == Z_NULL) { - strm->zalloc = zcalloc; - strm->opaque = (voidpf)0; + strm->msg = Z_NULL; + if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; +#endif } - if (strm->zfree == Z_NULL) strm->zfree = zcfree; + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zfree = zcfree; +#endif - if (!strm->checksum) strm->checksum = adler32; - +#ifdef FASTEST + if (level != 0) level = 1; +#else if (level == Z_DEFAULT_COMPRESSION) level = 6; -/* #ifdef FASTEST */ -/* level = 1; */ -/* #endif */ +#endif - if (windowBits < 0) { /* undocumented feature: suppress zlib header */ - noheader = 1; + if (windowBits < 0) { /* suppress zlib wrapper */ + wrap = 0; windowBits = -windowBits; } +#ifdef GZIP + else if (windowBits > 15) { + wrap = 2; /* write gzip wrapper instead */ + windowBits -= 16; + } +#endif if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method != Z_DEFLATED || windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || - strategy < 0 || strategy > Z_HUFFMAN_ONLY) { + strategy < 0 || strategy > Z_FIXED) { return Z_STREAM_ERROR; } + if (windowBits == 8) windowBits = 9; /* until 256-byte window bug fixed */ s = (deflate_state *) ZALLOC(strm, 1, sizeof(deflate_state)); if (s == Z_NULL) return Z_MEM_ERROR; strm->state = (struct internal_state FAR *)s; s->strm = strm; - s->noheader = noheader; + s->wrap = wrap; + s->gzhead = Z_NULL; s->w_bits = windowBits; s->w_size = 1 << s->w_bits; s->w_mask = s->w_size - 1; @@ -322,6 +1382,8 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, s->prev = (Posf *) ZALLOC(strm, s->w_size, sizeof(Pos)); s->head = (Posf *) ZALLOC(strm, s->hash_size, sizeof(Pos)); + s->high_water = 0; /* nothing written to s->window yet */ + s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2); @@ -330,7 +1392,8 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL || s->pending_buf == Z_NULL) { - strm->msg = (char*)ERR_MSG(Z_MEM_ERROR); + s->status = FINISH_STATE; + strm->msg = ERR_MSG(Z_MEM_ERROR); deflateEnd (strm); return Z_MEM_ERROR; } @@ -344,15 +1407,85 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, return deflateReset(strm); } +/* ========================================================================= */ +int ZEXPORT deflateSetDictionary ( + z_streamp strm, + const Bytef *dictionary, + uInt dictLength +) { + deflate_state *s; + uInt str, n; + int wrap; + unsigned avail; + z_const unsigned char *next; + + if (strm == Z_NULL || strm->state == Z_NULL || dictionary == Z_NULL) + return Z_STREAM_ERROR; + s = strm->state; + wrap = s->wrap; + if (wrap == 2 || (wrap == 1 && s->status != INIT_STATE) || s->lookahead) + return Z_STREAM_ERROR; + + /* when using zlib wrappers, compute Adler-32 for provided dictionary */ + if (wrap == 1) + strm->adler = adler32(strm->adler, dictionary, dictLength); + s->wrap = 0; /* avoid computing Adler-32 in read_buf */ + + /* if dictionary would fill window, just replace the history */ + if (dictLength >= s->w_size) { + if (wrap == 0) { /* already empty otherwise */ + CLEAR_HASH(s); + s->strstart = 0; + s->block_start = 0L; + s->insert = 0; + } + dictionary += dictLength - s->w_size; /* use the tail */ + dictLength = s->w_size; + } + + /* insert dictionary into window and hash */ + avail = strm->avail_in; + next = strm->next_in; + strm->avail_in = dictLength; + strm->next_in = (z_const Bytef *)dictionary; + fill_window(s); + while (s->lookahead >= MIN_MATCH) { + str = s->strstart; + n = s->lookahead - (MIN_MATCH-1); + do { + UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); +#ifndef FASTEST + s->prev[str & s->w_mask] = s->head[s->ins_h]; +#endif + s->head[s->ins_h] = (Pos)str; + str++; + } while (--n); + s->strstart = str; + s->lookahead = MIN_MATCH-1; + fill_window(s); + } + s->strstart += s->lookahead; + s->block_start = (long)s->strstart; + s->insert = s->lookahead; + s->lookahead = 0; + s->match_length = s->prev_length = MIN_MATCH-1; + s->match_available = 0; + strm->next_in = next; + strm->avail_in = avail; + s->wrap = wrap; + return Z_OK; +} /* ========================================================================= */ -int ZEXPORT deflateReset (strm) - z_streamp strm; -{ +int ZEXPORT deflateResetKeep ( + z_streamp strm +) { deflate_state *s; - + if (strm == Z_NULL || strm->state == Z_NULL || - strm->zalloc == Z_NULL || strm->zfree == Z_NULL) return Z_STREAM_ERROR; + strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0) { + return Z_STREAM_ERROR; + } strm->total_in = strm->total_out = 0; strm->msg = Z_NULL; /* use zfree if we ever allocate msg dynamically */ @@ -362,125 +1495,485 @@ int ZEXPORT deflateReset (strm) s->pending = 0; s->pending_out = s->pending_buf; - if (s->noheader < 0) { - s->noheader = 0; /* was set to -1 by deflate(..., Z_FINISH); */ + if (s->wrap < 0) { + s->wrap = -s->wrap; /* was made negative by deflate(..., Z_FINISH); */ } - s->status = s->noheader ? BUSY_STATE : INIT_STATE; - strm->adler = (strm->checksum == crc32) ? 0L : 1L; + s->status = s->wrap ? INIT_STATE : BUSY_STATE; + strm->adler = +#ifdef GZIP + s->wrap == 2 ? crc32(0L, Z_NULL, 0) : +#endif + adler32(0L, Z_NULL, 0); s->last_flush = Z_NO_FLUSH; _tr_init(s); - lm_init(s); return Z_OK; } +/* ========================================================================= */ +int ZEXPORT deflateReset ( + z_streamp strm +) { + int ret; + + ret = deflateResetKeep(strm); + if (ret == Z_OK) + lm_init(strm->state); + return ret; +} -/* ========================================================================= - * Put a short in the pending buffer. The 16-bit value is put in MSB order. - * IN assertion: the stream state is correct and there is enough room in - * pending_buf. - */ -local void putShortMSB (s, b) - deflate_state *s; - uInt b; -{ - put_byte(s, (Byte)(b >> 8)); - put_byte(s, (Byte)(b & 0xff)); -} +/* ========================================================================= */ +int ZEXPORT deflateSetHeader ( + z_streamp strm, + gz_headerp head +) { + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + if (strm->state->wrap != 2) return Z_STREAM_ERROR; + strm->state->gzhead = head; + return Z_OK; +} -/* ========================================================================= - * Flush as much pending output as possible. All deflate() output goes - * through this function so some applications may wish to modify it - * to avoid allocating a large strm->next_out buffer and copying into it. - * (See also read_buf()). - */ -local void flush_pending(strm) - z_streamp strm; -{ - unsigned len = strm->state->pending; +/* ========================================================================= */ +int ZEXPORT deflatePending ( + z_streamp strm, + unsigned *pending, + int *bits +) { + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + if (pending != Z_NULL) + *pending = strm->state->pending; + if (bits != Z_NULL) + *bits = strm->state->bi_valid; + return Z_OK; +} - if (len > strm->avail_out) len = strm->avail_out; - if (len == 0) return; +/* ========================================================================= */ +int ZEXPORT deflatePrime ( + z_streamp strm, + int bits, + int value +) { + deflate_state *s; + int put; - zmemcpy(strm->next_out, strm->state->pending_out, len); - strm->next_out += len; - strm->state->pending_out += len; - strm->total_out += len; - strm->avail_out -= len; - strm->state->pending -= len; - if (strm->state->pending == 0) { - strm->state->pending_out = strm->state->pending_buf; - } + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + s = strm->state; + if ((Bytef *)(s->d_buf) < s->pending_out + ((Buf_size + 7) >> 3)) + return Z_BUF_ERROR; + do { + put = Buf_size - s->bi_valid; + if (put > bits) + put = bits; + s->bi_buf |= (ush)((value & ((1 << put) - 1)) << s->bi_valid); + s->bi_valid += put; + _tr_flush_bits(s); + value >>= put; + bits -= put; + } while (bits); + return Z_OK; } /* ========================================================================= */ -int ZEXPORT deflate (strm, flush) - z_streamp strm; - int flush; -{ - int old_flush; /* value of flush param for previous deflate call */ +int ZEXPORT deflateParams( + z_streamp strm, + int level, + int strategy +) { deflate_state *s; + compress_func func; + int err = Z_OK; - if (strm == Z_NULL || strm->state == Z_NULL || - flush > Z_FINISH || flush < 0) { - return Z_STREAM_ERROR; - } + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; s = strm->state; - if (strm->next_out == Z_NULL || - (strm->next_in == Z_NULL && strm->avail_in != 0) || - (s->status == FINISH_STATE && flush != Z_FINISH)) { - ERR_RETURN(strm, Z_STREAM_ERROR); +#ifdef FASTEST + if (level != 0) level = 1; +#else + if (level == Z_DEFAULT_COMPRESSION) level = 6; +#endif + if (level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) { + return Z_STREAM_ERROR; } - if (strm->avail_out == 0) ERR_RETURN(strm, Z_BUF_ERROR); - - s->strm = strm; /* just in case */ - old_flush = s->last_flush; - s->last_flush = flush; + func = configuration_table[s->level].func; + + if ((strategy != s->strategy || func != configuration_table[level].func) && + strm->total_in != 0) { + /* Flush the last buffer: */ + err = deflate(strm, Z_BLOCK); + if (err == Z_BUF_ERROR && s->pending == 0) + err = Z_OK; + } + if (s->level != level) { + s->level = level; + s->max_lazy_match = configuration_table[level].max_lazy; + s->good_match = configuration_table[level].good_length; + s->nice_match = configuration_table[level].nice_length; + s->max_chain_length = configuration_table[level].max_chain; + } + s->strategy = strategy; + return err; +} - /* Write the zlib header */ - if (s->status == INIT_STATE) { +/* ========================================================================= */ +int ZEXPORT deflateTune( + z_streamp strm, + int good_length, + int max_lazy, + int nice_length, + int max_chain +) { + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + s = strm->state; + s->good_match = good_length; + s->max_lazy_match = max_lazy; + s->nice_match = nice_length; + s->max_chain_length = max_chain; + return Z_OK; +} + +/* ========================================================================= + * For the default windowBits of 15 and memLevel of 8, this function returns + * a close to exact, as well as small, upper bound on the compressed size. + * They are coded as constants here for a reason--if the #define's are + * changed, then this function needs to be changed as well. The return + * value for 15 and 8 only works for those exact settings. + * + * For any setting other than those defaults for windowBits and memLevel, + * the value returned is a conservative worst case for the maximum expansion + * resulting from using fixed blocks instead of stored blocks, which deflate + * can emit on compressed data for some combinations of the parameters. + * + * This function could be more sophisticated to provide closer upper bounds for + * every combination of windowBits and memLevel. But even the conservative + * upper bound of about 14% expansion does not seem onerous for output buffer + * allocation. + */ +uLong ZEXPORT deflateBound( + z_streamp strm, + uLong sourceLen +) { + deflate_state *s; + uLong complen, wraplen; + Bytef *str; + + /* conservative upper bound for compressed data */ + complen = sourceLen + + ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 5; + + /* if can't get parameters, return conservative bound plus zlib wrapper */ + if (strm == Z_NULL || strm->state == Z_NULL) + return complen + 6; + + /* compute wrapper length */ + s = strm->state; + switch (s->wrap) { + case 0: /* raw deflate */ + wraplen = 0; + break; + case 1: /* zlib wrapper */ + wraplen = 6 + (s->strstart ? 4 : 0); + break; + case 2: /* gzip wrapper */ + wraplen = 18; + if (s->gzhead != Z_NULL) { /* user-supplied gzip header */ + if (s->gzhead->extra != Z_NULL) + wraplen += 2 + s->gzhead->extra_len; + str = s->gzhead->name; + if (str != Z_NULL) + do { + wraplen++; + } while (*str++); + str = s->gzhead->comment; + if (str != Z_NULL) + do { + wraplen++; + } while (*str++); + if (s->gzhead->hcrc) + wraplen += 2; + } + break; + default: /* for compiler happiness */ + wraplen = 6; + } + + /* if not default parameters, return conservative bound */ + if (s->w_bits != 15 || s->hash_bits != 8 + 7) + return complen + wraplen; + + /* default settings: return tight bound for that case */ + return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + + (sourceLen >> 25) + 13 - 6 + wraplen; +} + +/* ========================================================================= + * Put a short in the pending buffer. The 16-bit value is put in MSB order. + * IN assertion: the stream state is correct and there is enough room in + * pending_buf. + */ +local void putShortMSB ( + deflate_state *s, + uInt b +) { + put_byte(s, (Byte)(b >> 8)); + put_byte(s, (Byte)(b & 0xff)); +} + +/* ========================================================================= + * Flush as much pending output as possible. All deflate() output goes + * through this function so some applications may wish to modify it + * to avoid allocating a large strm->next_out buffer and copying into it. + * (See also read_buf()). + */ +local void flush_pending( + z_streamp strm +) { + unsigned len; + deflate_state *s = strm->state; + + _tr_flush_bits(s); + len = s->pending; + if (len > strm->avail_out) len = strm->avail_out; + if (len == 0) return; + + zmemcpy(strm->next_out, s->pending_out, len); + strm->next_out += len; + s->pending_out += len; + strm->total_out += len; + strm->avail_out -= len; + s->pending -= len; + if (s->pending == 0) { + s->pending_out = s->pending_buf; + } +} + +/* ========================================================================= */ +int ZEXPORT deflate ( + z_streamp strm, + int flush +) { + int old_flush; /* value of flush param for previous deflate call */ + deflate_state *s; + + if (strm == Z_NULL || strm->state == Z_NULL || + flush > Z_BLOCK || flush < 0) { + return Z_STREAM_ERROR; + } + s = strm->state; + + if (strm->next_out == Z_NULL || + (strm->next_in == Z_NULL && strm->avail_in != 0) || + (s->status == FINISH_STATE && flush != Z_FINISH)) { + ERR_RETURN(strm, Z_STREAM_ERROR); + } + if (strm->avail_out == 0) ERR_RETURN(strm, Z_BUF_ERROR); - uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8; - uInt level_flags = (s->level-1) >> 1; + s->strm = strm; /* just in case */ + old_flush = s->last_flush; + s->last_flush = flush; - if (level_flags > 3) level_flags = 3; - header |= (level_flags << 6); - if (s->strstart != 0) header |= PRESET_DICT; - header += 31 - (header % 31); + /* Write the header */ + if (s->status == INIT_STATE) { +#ifdef GZIP + if (s->wrap == 2) { + strm->adler = crc32(0L, Z_NULL, 0); + put_byte(s, 31); + put_byte(s, 139); + put_byte(s, 8); + if (s->gzhead == Z_NULL) { + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, s->level == 9 ? 2 : + (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ? + 4 : 0)); + put_byte(s, OS_CODE); + s->status = BUSY_STATE; + } + else { + put_byte(s, (s->gzhead->text ? 1 : 0) + + (s->gzhead->hcrc ? 2 : 0) + + (s->gzhead->extra == Z_NULL ? 0 : 4) + + (s->gzhead->name == Z_NULL ? 0 : 8) + + (s->gzhead->comment == Z_NULL ? 0 : 16) + ); + put_byte(s, (Byte)(s->gzhead->time & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 8) & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 16) & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 24) & 0xff)); + put_byte(s, s->level == 9 ? 2 : + (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ? + 4 : 0)); + put_byte(s, s->gzhead->os & 0xff); + if (s->gzhead->extra != Z_NULL) { + put_byte(s, s->gzhead->extra_len & 0xff); + put_byte(s, (s->gzhead->extra_len >> 8) & 0xff); + } + if (s->gzhead->hcrc) + strm->adler = crc32(strm->adler, s->pending_buf, + s->pending); + s->gzindex = 0; + s->status = EXTRA_STATE; + } + } + else +#endif + { + uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8; + uInt level_flags; + + if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2) + level_flags = 0; + else if (s->level < 6) + level_flags = 1; + else if (s->level == 6) + level_flags = 2; + else + level_flags = 3; + header |= (level_flags << 6); + if (s->strstart != 0) header |= PRESET_DICT; + header += 31 - (header % 31); + + s->status = BUSY_STATE; + putShortMSB(s, header); + + /* Save the adler32 of the preset dictionary: */ + if (s->strstart != 0) { + putShortMSB(s, (uInt)(strm->adler >> 16)); + putShortMSB(s, (uInt)(strm->adler & 0xffff)); + } + strm->adler = adler32(0L, Z_NULL, 0); + } + } +#ifdef GZIP + if (s->status == EXTRA_STATE) { + if (s->gzhead->extra != Z_NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + + while (s->gzindex < (s->gzhead->extra_len & 0xffff)) { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) + break; + } + put_byte(s, s->gzhead->extra[s->gzindex]); + s->gzindex++; + } + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (s->gzindex == s->gzhead->extra_len) { + s->gzindex = 0; + s->status = NAME_STATE; + } + } + else + s->status = NAME_STATE; + } + if (s->status == NAME_STATE) { + if (s->gzhead->name != Z_NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + int val; - s->status = BUSY_STATE; - putShortMSB(s, header); + do { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) { + val = 1; + break; + } + } + val = s->gzhead->name[s->gzindex++]; + put_byte(s, val); + } while (val != 0); + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (val == 0) { + s->gzindex = 0; + s->status = COMMENT_STATE; + } + } + else + s->status = COMMENT_STATE; + } + if (s->status == COMMENT_STATE) { + if (s->gzhead->comment != Z_NULL) { + uInt beg = s->pending; /* start of bytes to update crc */ + int val; - /* Save the adler32 of the preset dictionary: */ - if (s->strstart != 0) { - putShortMSB(s, (uInt)(strm->adler >> 16)); - putShortMSB(s, (uInt)(strm->adler & 0xffff)); - } - strm->adler = (strm->checksum == crc32) ? 0L : 1L; + do { + if (s->pending == s->pending_buf_size) { + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + flush_pending(strm); + beg = s->pending; + if (s->pending == s->pending_buf_size) { + val = 1; + break; + } + } + val = s->gzhead->comment[s->gzindex++]; + put_byte(s, val); + } while (val != 0); + if (s->gzhead->hcrc && s->pending > beg) + strm->adler = crc32(strm->adler, s->pending_buf + beg, + s->pending - beg); + if (val == 0) + s->status = HCRC_STATE; + } + else + s->status = HCRC_STATE; + } + if (s->status == HCRC_STATE) { + if (s->gzhead->hcrc) { + if (s->pending + 2 > s->pending_buf_size) + flush_pending(strm); + if (s->pending + 2 <= s->pending_buf_size) { + put_byte(s, (Byte)(strm->adler & 0xff)); + put_byte(s, (Byte)((strm->adler >> 8) & 0xff)); + strm->adler = crc32(0L, Z_NULL, 0); + s->status = BUSY_STATE; + } + } + else + s->status = BUSY_STATE; } +#endif /* Flush as much pending output as possible */ if (s->pending != 0) { flush_pending(strm); if (strm->avail_out == 0) { - /* Since avail_out is 0, deflate will be called again with - * more output space, but possibly with both pending and - * avail_in equal to zero. There won't be anything to do, - * but this is not an error situation so make sure we - * return OK instead of BUF_ERROR at next call of deflate: + /* Since avail_out is 0, deflate will be called again with + * more output space, but possibly with both pending and + * avail_in equal to zero. There won't be anything to do, + * but this is not an error situation so make sure we + * return OK instead of BUF_ERROR at next call of deflate: */ - s->last_flush = -1; - return Z_OK; - } + s->last_flush = -1; + return Z_OK; + } /* Make sure there is something to do and avoid duplicate consecutive * flushes. For repeated and useless calls with Z_FINISH, we keep - * returning Z_STREAM_END instead of Z_BUFF_ERROR. + * returning Z_STREAM_END instead of Z_BUF_ERROR. */ - } else if (strm->avail_in == 0 && flush <= old_flush && - flush != Z_FINISH) { + } else if (strm->avail_in == 0 && RANK(flush) <= RANK(old_flush) && + flush != Z_FINISH) { ERR_RETURN(strm, Z_BUF_ERROR); } @@ -495,70 +1988,97 @@ int ZEXPORT deflate (strm, flush) (flush != Z_NO_FLUSH && s->status != FINISH_STATE)) { block_state bstate; - bstate = (*(configuration_table[s->level].func))(s, flush); + bstate = s->strategy == Z_HUFFMAN_ONLY ? deflate_huff(s, flush) : + (s->strategy == Z_RLE ? deflate_rle(s, flush) : + (*(configuration_table[s->level].func))(s, flush)); if (bstate == finish_started || bstate == finish_done) { s->status = FINISH_STATE; } if (bstate == need_more || bstate == finish_started) { - if (strm->avail_out == 0) { - s->last_flush = -1; /* avoid BUF_ERROR next call, see above */ - } - return Z_OK; - /* If flush != Z_NO_FLUSH && avail_out == 0, the next call - * of deflate should use the same flush parameter to make sure - * that the flush is complete. So we don't have to output an - * empty block here, this will be done at next call. This also - * ensures that for a very small output buffer, we emit at most - * one empty block. - */ - } + if (strm->avail_out == 0) { + s->last_flush = -1; /* avoid BUF_ERROR next call, see above */ + } + return Z_OK; + /* If flush != Z_NO_FLUSH && avail_out == 0, the next call + * of deflate should use the same flush parameter to make sure + * that the flush is complete. So we don't have to output an + * empty block here, this will be done at next call. This also + * ensures that for a very small output buffer, we emit at most + * one empty block. + */ + } if (bstate == block_done) { if (flush == Z_PARTIAL_FLUSH) { _tr_align(s); - } else { /* FULL_FLUSH or SYNC_FLUSH */ + } else if (flush != Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */ _tr_stored_block(s, (char*)0, 0L, 0); /* For a full flush, this empty block will be recognized * as a special marker by inflate_sync(). */ if (flush == Z_FULL_FLUSH) { CLEAR_HASH(s); /* forget history */ + if (s->lookahead == 0) { + s->strstart = 0; + s->block_start = 0L; + s->insert = 0; + } } } flush_pending(strm); - if (strm->avail_out == 0) { - s->last_flush = -1; /* avoid BUF_ERROR at next call, see above */ - return Z_OK; - } + if (strm->avail_out == 0) { + s->last_flush = -1; /* avoid BUF_ERROR at next call, see above */ + return Z_OK; + } } } -/* Assert(strm->avail_out > 0, "bug2"); */ + Assert(strm->avail_out > 0, "bug2"); if (flush != Z_FINISH) return Z_OK; - if (s->noheader) return Z_STREAM_END; - - /* Write the zlib trailer (adler32) */ - putShortMSB(s, (uInt)(strm->adler >> 16)); - putShortMSB(s, (uInt)(strm->adler & 0xffff)); + if (s->wrap <= 0) return Z_STREAM_END; + + /* Write the trailer */ +#ifdef GZIP + if (s->wrap == 2) { + put_byte(s, (Byte)(strm->adler & 0xff)); + put_byte(s, (Byte)((strm->adler >> 8) & 0xff)); + put_byte(s, (Byte)((strm->adler >> 16) & 0xff)); + put_byte(s, (Byte)((strm->adler >> 24) & 0xff)); + put_byte(s, (Byte)(strm->total_in & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 8) & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 16) & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 24) & 0xff)); + } + else +#endif + { + putShortMSB(s, (uInt)(strm->adler >> 16)); + putShortMSB(s, (uInt)(strm->adler & 0xffff)); + } flush_pending(strm); /* If avail_out is zero, the application will call deflate again * to flush the rest. */ - s->noheader = -1; /* write the trailer only once! */ + if (s->wrap > 0) s->wrap = -s->wrap; /* write the trailer only once! */ return s->pending != 0 ? Z_OK : Z_STREAM_END; } /* ========================================================================= */ -int ZEXPORT deflateEnd (strm) - z_streamp strm; -{ +int ZEXPORT deflateEnd ( + z_streamp strm +) { int status; if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; status = strm->state->status; - if (status != INIT_STATE && status != BUSY_STATE && - status != FINISH_STATE) { + if (status != INIT_STATE && + status != EXTRA_STATE && + status != NAME_STATE && + status != COMMENT_STATE && + status != HCRC_STATE && + status != BUSY_STATE && + status != FINISH_STATE) { return Z_STREAM_ERROR; } @@ -574,6 +2094,66 @@ int ZEXPORT deflateEnd (strm) return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK; } +/* ========================================================================= + * Copy the source state to the destination state. + * To simplify the source, this is not supported for 16-bit MSDOS (which + * doesn't have enough memory anyway to duplicate compression states). + */ +int ZEXPORT deflateCopy ( + z_streamp dest, + z_streamp source +) { +#ifdef MAXSEG_64K + return Z_STREAM_ERROR; +#else + deflate_state *ds; + deflate_state *ss; + ushf *overlay; + + + if (source == Z_NULL || dest == Z_NULL || source->state == Z_NULL) { + return Z_STREAM_ERROR; + } + + ss = source->state; + + zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream)); + + ds = (deflate_state *) ZALLOC(dest, 1, sizeof(deflate_state)); + if (ds == Z_NULL) return Z_MEM_ERROR; + dest->state = (struct internal_state FAR *) ds; + zmemcpy((voidpf)ds, (voidpf)ss, sizeof(deflate_state)); + ds->strm = dest; + + ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte)); + ds->prev = (Posf *) ZALLOC(dest, ds->w_size, sizeof(Pos)); + ds->head = (Posf *) ZALLOC(dest, ds->hash_size, sizeof(Pos)); + overlay = (ushf *) ZALLOC(dest, ds->lit_bufsize, sizeof(ush)+2); + ds->pending_buf = (uchf *) overlay; + + if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL || + ds->pending_buf == Z_NULL) { + deflateEnd (dest); + return Z_MEM_ERROR; + } + /* following zmemcpy do not work for 16-bit MSDOS */ + zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte)); + zmemcpy((voidpf)ds->prev, (voidpf)ss->prev, ds->w_size * sizeof(Pos)); + zmemcpy((voidpf)ds->head, (voidpf)ss->head, ds->hash_size * sizeof(Pos)); + zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size); + + ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf); + ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush); + ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize; + + ds->l_desc.dyn_tree = ds->dyn_ltree; + ds->d_desc.dyn_tree = ds->dyn_dtree; + ds->bl_desc.dyn_tree = ds->bl_tree; + + return Z_OK; +#endif /* MAXSEG_64K */ +} + /* =========================================================================== * Read a new buffer from the current input stream, update the adler32 * and total number of bytes read. All deflate() input goes through @@ -581,11 +2161,11 @@ int ZEXPORT deflateEnd (strm) * allocating a large strm->next_in buffer and copying from it. * (See also flush_pending()). */ -local int read_buf(strm, buf, size) - z_streamp strm; - Bytef *buf; - unsigned size; -{ +local int read_buf( + z_streamp strm, + Bytef *buf, + unsigned size +) { unsigned len = strm->avail_in; if (len > size) len = size; @@ -593,10 +2173,15 @@ local int read_buf(strm, buf, size) strm->avail_in -= len; - if (!strm->state->noheader) { - strm->adler = strm->checksum(strm->adler, strm->next_in, len); - } zmemcpy(buf, strm->next_in, len); + if (strm->state->wrap == 1) { + strm->adler = adler32(strm->adler, buf, len); + } +#ifdef GZIP + else if (strm->state->wrap == 2) { + strm->adler = crc32(strm->adler, buf, len); + } +#endif strm->next_in += len; strm->total_in += len; @@ -606,9 +2191,9 @@ local int read_buf(strm, buf, size) /* =========================================================================== * Initialize the "longest match" routines for a new zlib stream */ -local void lm_init (s) - deflate_state *s; -{ +local void lm_init ( + deflate_state *s +) { s->window_size = (ulg)2L*s->w_size; CLEAR_HASH(s); @@ -623,14 +2208,18 @@ local void lm_init (s) s->strstart = 0; s->block_start = 0L; s->lookahead = 0; + s->insert = 0; s->match_length = s->prev_length = MIN_MATCH-1; s->match_available = 0; s->ins_h = 0; -/* #ifdef ASMV */ -/* match_init(); //initialize the asm code */ -/* #endif */ +#ifndef FASTEST +#ifdef ASMV + match_init(); /* initialize the asm code */ +#endif +#endif } +#ifndef FASTEST /* =========================================================================== * Set match_start to the longest match starting at the given string and * return its length. Matches shorter or equal to prev_length are discarded, @@ -644,15 +2233,14 @@ local void lm_init (s) /* For 80x86 and 680x0, an optimized version will be provided in match.asm or * match.S. The code will be functionally equivalent. */ -#ifndef FASTEST -local uInt longest_match(s, cur_match) - deflate_state *s; - IPos cur_match; /* current match */ -{ +local uInt longest_match( + deflate_state *s, + IPos cur_match /* current match */ +) { unsigned chain_length = s->max_chain_length;/* max hash chain length */ - register Bytef *scan = s->window + s->strstart; /* current string */ - register Bytef *match; /* matched string */ - register int len; /* length of current match */ + Bytef *scan = s->window + s->strstart; /* current string */ + Bytef *match; /* matched string */ + int len; /* length of current match */ int best_len = s->prev_length; /* best match length so far */ int nice_match = s->nice_match; /* stop if match long enough */ IPos limit = s->strstart > (IPos)MAX_DIST(s) ? @@ -663,23 +2251,23 @@ local uInt longest_match(s, cur_match) Posf *prev = s->prev; uInt wmask = s->w_mask; -/* #ifdef UNALIGNED_OK */ +#ifdef UNALIGNED_OK /* Compare two bytes at a time. Note: this is not always beneficial. * Try with and without -DUNALIGNED_OK to check. */ -/* register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1; */ -/* register ush scan_start = *(ushf*)scan; */ -/* register ush scan_end = *(ushf*)(scan+best_len-1); */ -/* #else */ - register Bytef *strend = s->window + s->strstart + MAX_MATCH; - register Byte scan_end1 = scan[best_len-1]; - register Byte scan_end = scan[best_len]; -/* #endif */ + Bytef *strend = s->window + s->strstart + MAX_MATCH - 1; + ush scan_start = *(ushf*)scan; + ush scan_end = *(ushf*)(scan+best_len-1); +#else + Bytef *strend = s->window + s->strstart + MAX_MATCH; + Byte scan_end1 = scan[best_len-1]; + Byte scan_end = scan[best_len]; +#endif /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. * It is easy to get rid of this optimization if necessary. */ -/* Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); */ + Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); /* Do not waste too much time if we already have a good match: */ if (s->prev_length >= s->good_match) { @@ -690,14 +2278,19 @@ local uInt longest_match(s, cur_match) */ if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; -/* Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); */ + Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); do { -/* Assert(cur_match < s->strstart, "no future"); */ + Assert(cur_match < s->strstart, "no future"); match = s->window + cur_match; /* Skip to next match if the match length cannot increase - * or if the match length is less than 2: + * or if the match length is less than 2. Note that the checks below + * for insufficient lookahead only occur occasionally for performance + * reasons. Therefore uninitialized memory will be accessed, and + * conditional jumps will be made that depend on those values. + * However the length of the match is limited to the lookahead, so + * the output of deflate is not affected by the uninitialized values. */ #if (defined(UNALIGNED_OK) && MAX_MATCH == 258) /* This code assumes sizeof(unsigned short) == 2. Do not use @@ -715,7 +2308,7 @@ local uInt longest_match(s, cur_match) * necessary to put more guard bytes at the end of the window, or * to check more often for insufficient lookahead. */ - //Assert(scan[2] == match[2], "scan[2]?"); + Assert(scan[2] == match[2], "scan[2]?"); scan++, match++; do { } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) && @@ -726,7 +2319,7 @@ local uInt longest_match(s, cur_match) /* The funny "do {}" generates better code on most compilers */ /* Here, scan <= window+strstart+257 */ - //Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); if (*scan == *match) scan++; len = (MAX_MATCH - 1) - (int)(strend-scan); @@ -746,7 +2339,7 @@ local uInt longest_match(s, cur_match) * the hash keys are equal and that HASH_BITS >= 8. */ scan += 2, match++; - //Assert(*scan == *match, "match[2]?"); + Assert(*scan == *match, "match[2]?"); /* We check for insufficient lookahead only every 8th comparison; * the 256th check will be made at strstart+258. @@ -758,7 +2351,7 @@ local uInt longest_match(s, cur_match) *++scan == *++match && *++scan == *++match && scan < strend); - //Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); len = MAX_MATCH - (int)(strend - scan); scan = strend - MAX_MATCH; @@ -769,12 +2362,12 @@ local uInt longest_match(s, cur_match) s->match_start = cur_match; best_len = len; if (len >= nice_match) break; -/* #ifdef UNALIGNED_OK */ -/* scan_end = *(ushf*)(scan+best_len-1); */ -/* #else */ +#ifdef UNALIGNED_OK + scan_end = *(ushf*)(scan+best_len-1); +#else scan_end1 = scan[best_len-1]; scan_end = scan[best_len]; -/* #endif */ +#endif } } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length != 0); @@ -782,28 +2375,30 @@ local uInt longest_match(s, cur_match) if ((uInt)best_len <= s->lookahead) return (uInt)best_len; return s->lookahead; } +#endif /* ASMV */ #else /* FASTEST */ + /* --------------------------------------------------------------------------- - * Optimized version for level == 1 only + * Optimized version for FASTEST only */ -local uInt longest_match(s, cur_match) - deflate_state *s; - IPos cur_match; /* current match */ -{ - register Bytef *scan = s->window + s->strstart; /* current string */ - register Bytef *match; /* matched string */ - register int len; /* length of current match */ - register Bytef *strend = s->window + s->strstart + MAX_MATCH; +local uInt longest_match( + deflate_state *s, + IPos cur_match /* current match */ +) { + Bytef *scan = s->window + s->strstart; /* current string */ + Bytef *match; /* matched string */ + int len; /* length of current match */ + Bytef *strend = s->window + s->strstart + MAX_MATCH; /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. * It is easy to get rid of this optimization if necessary. */ - //Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); - //Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); - //Assert(cur_match < s->strstart, "no future"); + Assert(cur_match < s->strstart, "no future"); match = s->window + cur_match; @@ -818,31 +2413,57 @@ local uInt longest_match(s, cur_match) * the hash keys are equal and that HASH_BITS >= 8. */ scan += 2, match += 2; - //Assert(*scan == *match, "match[2]?"); + Assert(*scan == *match, "match[2]?"); /* We check for insufficient lookahead only every 8th comparison; * the 256th check will be made at strstart+258. */ do { } while (*++scan == *++match && *++scan == *++match && - *++scan == *++match && *++scan == *++match && - *++scan == *++match && *++scan == *++match && - *++scan == *++match && *++scan == *++match && - scan < strend); + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + scan < strend); - //Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); len = MAX_MATCH - (int)(strend - scan); if (len < MIN_MATCH) return MIN_MATCH - 1; s->match_start = cur_match; - return len <= s->lookahead ? len : s->lookahead; + return (uInt)len <= s->lookahead ? (uInt)len : s->lookahead; } + #endif /* FASTEST */ -#endif /* ASMV */ +#ifdef DEBUG +/* =========================================================================== + * Check that the match at match_start is indeed a match. + */ +local void check_match(s, start, match, length) + deflate_state *s, + IPos start, IPos match, + int length +{ + /* check that the match is indeed a match */ + if (zmemcmp(s->window + match, + s->window + start, length) != EQUAL) { + fprintf(stderr, " start %u, match %u, length %d\n", + start, match, length); + do { + fprintf(stderr, "%c%c", s->window[match++], s->window[start++]); + } while (--length != 0); + z_error("invalid match"); + } + if (z_verbose > 1) { + fprintf(stderr,"\\[%d,%d]", start-match, length); + do { putc(s->window[start++], stderr); } while (--length != 0); + } +} +#else # define check_match(s, start, match, length) +#endif /* DEBUG */ /* =========================================================================== * Fill the window when the lookahead becomes insufficient. @@ -854,31 +2475,36 @@ local uInt longest_match(s, cur_match) * performed for at least two bytes (required for the zip translate_eol * option -- not supported here). */ -local void fill_window(s) - deflate_state *s; -{ - register unsigned n, m; - register Posf *p; +local void fill_window( + deflate_state *s +) { + unsigned n, m; + Posf *p; unsigned more; /* Amount of free space at the end of the window. */ uInt wsize = s->w_size; + Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); + do { more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart); /* Deal with !@#$% 64K limit: */ - if (more == 0 && s->strstart == 0 && s->lookahead == 0) { - more = wsize; + if (sizeof(int) <= 2) { + if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + more = wsize; - } else if (more == (unsigned)(-1)) { - /* Very unlikely, but possible on 16 bit machine if strstart == 0 - * and lookahead == 1 (input done one byte at time) - */ - more--; + } else if (more == (unsigned)(-1)) { + /* Very unlikely, but possible on 16 bit machine if + * strstart == 0 && lookahead == 1 (input done a byte at time) + */ + more--; + } + } /* If the window is almost full and there is insufficient lookahead, * move the upper half to the lower one to make room in the upper half. */ - } else if (s->strstart >= wsize+MAX_DIST(s)) { + if (s->strstart >= wsize+MAX_DIST(s)) { zmemcpy(s->window, s->window+wsize, (unsigned)wsize); s->match_start -= wsize; @@ -891,27 +2517,27 @@ local void fill_window(s) later. (Using level 0 permanently is not an optimal usage of zlib, so we don't care about this pathological case.) */ - n = s->hash_size; - p = &s->head[n]; - do { - m = *--p; - *p = (Pos)(m >= wsize ? m-wsize : NIL); - } while (--n); - - n = wsize; + n = s->hash_size; + p = &s->head[n]; + do { + m = *--p; + *p = (Pos)(m >= wsize ? m-wsize : NIL); + } while (--n); + + n = wsize; #ifndef FASTEST - p = &s->prev[n]; - do { - m = *--p; - *p = (Pos)(m >= wsize ? m-wsize : NIL); - /* If n is not on any hash chain, prev[n] is garbage but - * its value will never be used. - */ - } while (--n); + p = &s->prev[n]; + do { + m = *--p; + *p = (Pos)(m >= wsize ? m-wsize : NIL); + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } while (--n); #endif more += wsize; } - if (s->strm->avail_in == 0) return; + if (s->strm->avail_in == 0) break; /* If there was no sliding: * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && @@ -924,46 +2550,94 @@ local void fill_window(s) * Otherwise, window_size == 2*WSIZE so more >= 2. * If there was sliding, more >= WSIZE. So in all cases, more >= 2. */ - //Assert(more >= 2, "more < 2"); + Assert(more >= 2, "more < 2"); n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more); s->lookahead += n; /* Initialize the hash value now that we have some input: */ - if (s->lookahead >= MIN_MATCH) { - s->ins_h = s->window[s->strstart]; - UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); + if (s->lookahead + s->insert >= MIN_MATCH) { + uInt str = s->strstart - s->insert; + s->ins_h = s->window[str]; + UPDATE_HASH(s, s->ins_h, s->window[str + 1]); #if MIN_MATCH != 3 Call UPDATE_HASH() MIN_MATCH-3 more times #endif + while (s->insert) { + UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); +#ifndef FASTEST + s->prev[str & s->w_mask] = s->head[s->ins_h]; +#endif + s->head[s->ins_h] = (Pos)str; + str++; + s->insert--; + if (s->lookahead + s->insert < MIN_MATCH) + break; + } } /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, * but this is not important since only literal bytes will be emitted. */ } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0); + + /* If the WIN_INIT bytes after the end of the current data have never been + * written, then zero those bytes in order to avoid memory check reports of + * the use of uninitialized (or uninitialised as Julian writes) bytes by + * the longest match routines. Update the high water mark for the next + * time through here. WIN_INIT is set to MAX_MATCH since the longest match + * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. + */ + if (s->high_water < s->window_size) { + ulg curr = s->strstart + (ulg)(s->lookahead); + ulg init; + + if (s->high_water < curr) { + /* Previous high water mark below current data -- zero WIN_INIT + * bytes or up to end of window, whichever is less. + */ + init = s->window_size - curr; + if (init > WIN_INIT) + init = WIN_INIT; + zmemzero(s->window + curr, (unsigned)init); + s->high_water = curr + init; + } + else if (s->high_water < (ulg)curr + WIN_INIT) { + /* High water mark at or above current data, but below current data + * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up + * to end of window, whichever is less. + */ + init = (ulg)curr + WIN_INIT - s->high_water; + if (init > s->window_size - s->high_water) + init = s->window_size - s->high_water; + zmemzero(s->window + s->high_water, (unsigned)init); + s->high_water += init; + } + } + + Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, + "not enough room for search"); } /* =========================================================================== * Flush the current block, with given end-of-file flag. * IN assertion: strstart is set to the end of the current match. */ -static void -FLUSH_BLOCK_ONLY(deflate_state *s, int eof) { - _tr_flush_block(s, (s->block_start >= 0L ? - (charf *)&s->window[(unsigned)s->block_start] : - (charf *)Z_NULL), - (ulg)((long)s->strstart - s->block_start), - (eof)); - s->block_start = s->strstart; - flush_pending(s->strm); +#define FLUSH_BLOCK_ONLY(s, last) { \ + _tr_flush_block(s, (s->block_start >= 0L ? \ + (charf *)&s->window[(unsigned)s->block_start] : \ + (charf *)Z_NULL), \ + (ulg)((long)s->strstart - s->block_start), \ + (last)); \ + s->block_start = s->strstart; \ + flush_pending(s->strm); \ + Tracev((stderr,"[FLUSH]")); \ } /* Same but force premature exit if necessary. */ -/* !!! #define */ /* deflate_state *s, int eof */ -#define FLUSH_BLOCK(s, eof) { \ - FLUSH_BLOCK_ONLY(s, eof); \ - if (s->strm->avail_out == 0) return (eof) ? finish_started : need_more; \ +#define FLUSH_BLOCK(s, last) { \ + FLUSH_BLOCK_ONLY(s, last); \ + if (s->strm->avail_out == 0) return (last) ? finish_started : need_more; \ } /* =========================================================================== @@ -975,10 +2649,10 @@ FLUSH_BLOCK_ONLY(deflate_state *s, int eof) { * NOTE: this function should be optimized to avoid extra copying from * window to pending_buf. */ -local block_state deflate_stored(s, flush) - deflate_state *s; - int flush; -{ +local block_state deflate_stored( + deflate_state *s, + int flush +) { /* Stored blocks are limited to 0xffff bytes, pending_buf is limited * to pending_buf_size, and each stored block has a 5 byte header: */ @@ -994,48 +2668,158 @@ local block_state deflate_stored(s, flush) /* Fill the window as much as possible: */ if (s->lookahead <= 1) { - //Assert(s->strstart < s->w_size+MAX_DIST(s) || -// s->block_start >= (long)s->w_size, "slide too late"); + Assert(s->strstart < s->w_size+MAX_DIST(s) || + s->block_start >= (long)s->w_size, "slide too late"); fill_window(s); if (s->lookahead == 0 && flush == Z_NO_FLUSH) return need_more; if (s->lookahead == 0) break; /* flush the current block */ } - //Assert(s->block_start >= 0L, "block gone"); + Assert(s->block_start >= 0L, "block gone"); - s->strstart += s->lookahead; - s->lookahead = 0; + s->strstart += s->lookahead; + s->lookahead = 0; - /* Emit a stored block if pending_buf will be full: */ - max_start = s->block_start + max_block_size; + /* Emit a stored block if pending_buf will be full: */ + max_start = s->block_start + max_block_size; if (s->strstart == 0 || (ulg)s->strstart >= max_start) { - /* strstart == 0 is possible when wraparound on 16-bit machine */ - s->lookahead = (uInt)(s->strstart - max_start); - s->strstart = (uInt)max_start; + /* strstart == 0 is possible when wraparound on 16-bit machine */ + s->lookahead = (uInt)(s->strstart - max_start); + s->strstart = (uInt)max_start; FLUSH_BLOCK(s, 0); - } - /* Flush if we may have to slide, otherwise block_start may become + } + /* Flush if we may have to slide, otherwise block_start may become * negative and the data will be gone: */ if (s->strstart - (uInt)s->block_start >= MAX_DIST(s)) { FLUSH_BLOCK(s, 0); - } + } + } + s->insert = 0; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if ((long)s->strstart > s->block_start) + FLUSH_BLOCK(s, 0); + return block_done; +} + +/* =========================================================================== + * Compress as much as possible from the input stream, return the current + * block state. + * This function does not perform lazy evaluation of matches and inserts + * new strings in the dictionary only for unmatched strings or for short + * matches. It is used only for the fast compression options. + */ +local block_state deflate_fast( + deflate_state *s, + int flush +) { + IPos hash_head; /* head of the hash chain */ + int bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s->lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = NIL; + if (s->lookahead >= MIN_MATCH) { + INSERT_STRING(s, s->strstart, hash_head); + } + + /* Find the longest match, discarding those <= prev_length. + * At this point we have always match_length < MIN_MATCH + */ + if (hash_head != NIL && s->strstart - hash_head <= MAX_DIST(s)) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s->match_length = longest_match (s, hash_head); + /* longest_match() sets match_start */ + } + if (s->match_length >= MIN_MATCH) { + check_match(s, s->strstart, s->match_start, s->match_length); + + _tr_tally_dist(s, s->strstart - s->match_start, + s->match_length - MIN_MATCH, bflush); + + s->lookahead -= s->match_length; + + /* Insert new strings in the hash table only if the match length + * is not too large. This saves time but degrades compression. + */ +#ifndef FASTEST + if (s->match_length <= s->max_insert_length && + s->lookahead >= MIN_MATCH) { + s->match_length--; /* string at strstart already in table */ + do { + s->strstart++; + INSERT_STRING(s, s->strstart, hash_head); + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. + */ + } while (--s->match_length != 0); + s->strstart++; + } else +#endif + { + s->strstart += s->match_length; + s->match_length = 0; + s->ins_h = s->window[s->strstart]; + UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not + * matter since it will be recomputed at next deflate call. + */ + } + } else { + /* No match, output a literal byte */ + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + } + if (bflush) FLUSH_BLOCK(s, 0); } - FLUSH_BLOCK(s, flush == Z_FINISH); - return flush == Z_FINISH ? finish_done : block_done; + s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; } +#ifndef FASTEST /* =========================================================================== * Same as above, but achieves better compression. We use a lazy * evaluation for matches: a match is finally adopted only if there is * no better match at the next window position. */ -local block_state deflate_slow(s, flush) - deflate_state *s; - int flush; -{ - IPos hash_head = NIL; /* head of hash chain */ +local block_state deflate_slow( + deflate_state *s, + int flush +) { + IPos hash_head; /* head of hash chain */ int bflush; /* set if current block must be flushed */ /* Process the input block. */ @@ -1048,14 +2832,15 @@ local block_state deflate_slow(s, flush) if (s->lookahead < MIN_LOOKAHEAD) { fill_window(s); if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { - return need_more; - } + return need_more; + } if (s->lookahead == 0) break; /* flush the current block */ } /* Insert the string window[strstart .. strstart+2] in the * dictionary, and set hash_head to the head of the hash chain: */ + hash_head = NIL; if (s->lookahead >= MIN_MATCH) { INSERT_STRING(s, s->strstart, hash_head); } @@ -1071,14 +2856,15 @@ local block_state deflate_slow(s, flush) * of window index 0 (in particular we have to avoid a match * of the string with itself at the start of the input file). */ - if (s->strategy != Z_HUFFMAN_ONLY) { - s->match_length = longest_match (s, hash_head); - } + s->match_length = longest_match (s, hash_head); /* longest_match() sets match_start */ - if (s->match_length <= 5 && (s->strategy == Z_FILTERED || - (s->match_length == MIN_MATCH && - s->strstart - s->match_start > TOO_FAR))) { + if (s->match_length <= 5 && (s->strategy == Z_FILTERED +#if TOO_FAR <= 32767 + || (s->match_length == MIN_MATCH && + s->strstart - s->match_start > TOO_FAR) +#endif + )) { /* If prev_match is also MIN_MATCH, match_start is garbage * but we will ignore the current match anyway. @@ -1096,7 +2882,7 @@ local block_state deflate_slow(s, flush) check_match(s, s->strstart-1, s->prev_match, s->prev_length); _tr_tally_dist(s, s->strstart -1 - s->prev_match, - s->prev_length - MIN_MATCH, bflush); + s->prev_length - MIN_MATCH, bflush); /* Insert in hash table all strings up to the end of the match. * strstart-1 and strstart are already inserted. If there is not @@ -1121,9 +2907,9 @@ local block_state deflate_slow(s, flush) * single literal. If there was a match but the current match * is longer, truncate the previous match to a single literal. */ -// Tracevv((stderr,"%c", s->window[s->strstart-1])); - _tr_tally_lit(s, s->window[s->strstart-1], bflush); - if (bflush) { + Tracevv((stderr,"%c", s->window[s->strstart-1])); + _tr_tally_lit(s, s->window[s->strstart-1], bflush); + if (bflush) { FLUSH_BLOCK_ONLY(s, 0); } s->strstart++; @@ -1138,689 +2924,152 @@ local block_state deflate_slow(s, flush) s->lookahead--; } } - //Assert (flush != Z_NO_FLUSH, "no flush?"); + Assert (flush != Z_NO_FLUSH, "no flush?"); if (s->match_available) { -// Tracevv((stderr,"%c", s->window[s->strstart-1])); + Tracevv((stderr,"%c", s->window[s->strstart-1])); _tr_tally_lit(s, s->window[s->strstart-1], bflush); s->match_available = 0; } - FLUSH_BLOCK(s, flush == Z_FINISH); - return flush == Z_FINISH ? finish_done : block_done; + s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; } +#endif /* FASTEST */ +/* =========================================================================== + * For Z_RLE, simply look for runs of bytes, generate matches only of distance + * one. Do not maintain a hash table. (It will be regenerated if this run of + * deflate switches away from Z_RLE.) + */ +local block_state deflate_rle( + deflate_state *s, + int flush +) { + int bflush; /* set if current block must be flushed */ + uInt prev; /* byte at distance one to match */ + Bytef *scan, *strend; /* scan goes up to strend for length of run */ -/////////////////////////////////////////////////////////////////////// - - -/* infblock.c -- interpret and process block types to last block - * Copyright (C) 1995-1998 Mark Adler - * For conditions of distribution and use, see copyright notice in zlib.h - */ - -//rls#include "zutil.h" -//rls#include "infblock.h" -//rls#include "inftrees.h" -//rls#include "infcodes.h" -//rls#include "infutil.h" - -//rls struct inflate_codes_state {int dummy;}; /* for buggy compilers */ - -/* simplify the use of the inflate_huft type with some defines */ -#define exop word.what.Exop -#define INFBITS word.what.Bits - -/* Table for deflate from PKZIP's appnote.txt. */ -local const uInt border[] = { /* Order of the bit length code lengths */ - 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; - -/* - Notes beyond the 1.93a appnote.txt: - - 1. Distance pointers never point before the beginning of the output - stream. - 2. Distance pointers can point back across blocks, up to 32k away. - 3. There is an implied maximum of 7 bits for the bit length table and - 15 bits for the actual data. - 4. If only one code exists, then it is encoded using one bit. (Zero - would be more efficient, but perhaps a little confusing.) If two - codes exist, they are coded using one bit each (0 and 1). - 5. There is no way of sending zero distance codes--a dummy must be - sent if there are none. (History: a pre 2.0 version of PKZIP would - store blocks with no distance codes, but this was discovered to be - too harsh a criterion.) Valid only for 1.93a. 2.04c does allow - zero distance codes, which is sent as one code of zero bits in - length. - 6. There are up to 286 literal/length codes. Code 256 represents the - end-of-block. Note however that the static length tree defines - 288 codes just to fill out the Huffman codes. Codes 286 and 287 - cannot be used though, since there is no length base or extra bits - defined for them. Similarily, there are up to 30 distance codes. - However, static trees define 32 codes (all 5 bits) to fill out the - Huffman codes, but the last two had better not show up in the data. - 7. Unzip can check dynamic Huffman blocks for complete code sets. - The exception is that a single code would not be complete (see #4). - 8. The five bits following the block type is really the number of - literal codes sent minus 257. - 9. Length codes 8,16,16 are interpreted as 13 length codes of 8 bits - (1+6+6). Therefore, to output three times the length, you output - three codes (1+1+1), whereas to output four times the same length, - you only need two codes (1+3). Hmm. - 10. In the tree reconstruction algorithm, Code = Code + Increment - only if BitLength(i) is not zero. (Pretty obvious.) - 11. Correction: 4 Bits: # of Bit Length codes - 4 (4 - 19) - 12. Note: length code 284 can represent 227-258, but length code 285 - really is 258. The last length deserves its own, short code - since it gets used a lot in very redundant files. The length - 258 is special since 258 - 3 (the min match length) is 255. - 13. The literal/length and distance code bit lengths are read as a - single stream of lengths. It is possible (and advantageous) for - a repeat code (16, 17, or 18) to go across the boundary between - the two sets of lengths. - */ - - -void inflate_blocks_reset(s, z, c) -inflate_blocks_statef *s; -z_streamp z; -uLongf *c; -{ - if (c != Z_NULL) - *c = s->check; - if (s->mode == BTREE || s->mode == DTREE) - ZFREE(z, s->sub.trees.blens); - if (s->mode == CODES) - inflate_codes_free(s->sub.decode.codes, z); - s->mode = TYPE; - s->bitk = 0; - s->bitb = 0; - s->read = s->write = s->window; - if (s->checkfn != Z_NULL) - z->adler = s->check = (*s->checkfn)(0L, (const Bytef *)Z_NULL, 0); - //Tracev((stderr, "inflate: blocks reset\n")); -} - - -inflate_blocks_statef *inflate_blocks_new(z, c, w) -z_streamp z; -check_func c; -uInt w; -{ - inflate_blocks_statef *s; - - if ((s = (inflate_blocks_statef *)ZALLOC - (z,1,sizeof(struct inflate_blocks_state))) == Z_NULL) - return s; - if ((s->hufts = - (inflate_huft *)ZALLOC(z, sizeof(inflate_huft), MANY)) == Z_NULL) - { - ZFREE(z, s); - return Z_NULL; - } - if ((s->window = (Bytef *)ZALLOC(z, 1, w)) == Z_NULL) - { - ZFREE(z, s->hufts); - ZFREE(z, s); - return Z_NULL; - } - s->end = s->window + w; - s->checkfn = c; - s->mode = TYPE; - //Tracev((stderr, "inflate: blocks allocated\n")); - inflate_blocks_reset(s, z, Z_NULL); - return s; -} - - -int inflate_blocks(s, z, r) -inflate_blocks_statef *s; -z_streamp z; -int r; -{ - uInt t; /* temporary storage */ - uLong b; /* bit buffer */ - uInt k; /* bits in bit buffer */ - Bytef *p; /* input data pointer */ - uInt n; /* bytes available there */ - Bytef *q; /* output window write pointer */ - uInt m; /* bytes to end of window or read pointer */ - - /* copy input/output information to locals (UPDATE macro restores) */ - LOAD - - /* process input based on current state */ - while (1) switch (s->mode) - { - case TYPE: - NEEDBITS(3) - t = (uInt)b & 7; - s->last = t & 1; - switch (t >> 1) - { - case 0: /* stored */ - //Tracev((stderr, "inflate: stored block%s\n", -// s->last ? " (last)" : "")); - DUMPBITS(3) - t = k & 7; /* go to byte boundary */ - DUMPBITS(t) - s->mode = LENS; /* get length of stored block */ - break; - case 1: /* fixed */ - //Tracev((stderr, "inflate: fixed codes block%s\n", -// s->last ? " (last)" : "")); - { - uInt bl, bd; - inflate_huft *tl, *td; - - inflate_trees_fixed(&bl, &bd, &tl, &td, z); - s->sub.decode.codes = inflate_codes_new(bl, bd, tl, td, z); - if (s->sub.decode.codes == Z_NULL) - { - r = Z_MEM_ERROR; - LEAVE + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the longest run, plus one for the unrolled loop. + */ + if (s->lookahead <= MAX_MATCH) { + fill_window(s); + if (s->lookahead <= MAX_MATCH && flush == Z_NO_FLUSH) { + return need_more; } - } - DUMPBITS(3) - s->mode = CODES; - break; - case 2: /* dynamic */ - //Tracev((stderr, "inflate: dynamic codes block%s\n", -// s->last ? " (last)" : "")); - DUMPBITS(3) - s->mode = TABLE; - break; - case 3: /* illegal */ - DUMPBITS(3) - s->mode = BAD; -// z->msg = (char*)"invalid block type"; - r = Z_DATA_ERROR; - LEAVE - } - break; - case LENS: - NEEDBITS(32) - if ((((~b) >> 16) & 0xffff) != (b & 0xffff)) - { - s->mode = BAD; -// z->msg = (char*)"invalid stored block lengths"; - r = Z_DATA_ERROR; - LEAVE - } - s->sub.left = (uInt)b & 0xffff; - b = k = 0; /* dump bits */ - //Tracev((stderr, "inflate: stored length %u\n", s->sub.left)); - s->mode = s->sub.left ? STORED : (s->last ? DRY : TYPE); - break; - case STORED: - if (n == 0) - LEAVE - NEEDOUT - t = s->sub.left; - if (t > n) t = n; - if (t > m) t = m; - zmemcpy(q, p, t); - p += t; n -= t; - q += t; m -= t; - if ((s->sub.left -= t) != 0) - break; - //Tracev((stderr, "inflate: stored end, %lu total out\n", - // z->total_out + (q >= s->read ? q - s->read : -// (s->end - s->read) + (q - s->window)))); - s->mode = s->last ? DRY : TYPE; - break; - case TABLE: - NEEDBITS(14) - s->sub.trees.table = t = (uInt)b & 0x3fff; -#ifndef PKZIP_BUG_WORKAROUND - if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29) - { - s->mode = BAD; -// z->msg = (char*)"too many length or distance symbols"; - r = Z_DATA_ERROR; - LEAVE - } -#endif - t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f); - if ((s->sub.trees.blens = (uIntf*)ZALLOC(z, t, sizeof(uInt))) == Z_NULL) - { - r = Z_MEM_ERROR; - LEAVE - } - DUMPBITS(14) - s->sub.trees.index = 0; - //Tracev((stderr, "inflate: table sizes ok\n")); - s->mode = BTREE; - case BTREE: - while (s->sub.trees.index < 4 + (s->sub.trees.table >> 10)) - { - NEEDBITS(3) - s->sub.trees.blens[border[s->sub.trees.index++]] = (uInt)b & 7; - DUMPBITS(3) - } - while (s->sub.trees.index < 19) - s->sub.trees.blens[border[s->sub.trees.index++]] = 0; - s->sub.trees.bb = 7; - t = inflate_trees_bits(s->sub.trees.blens, &s->sub.trees.bb, - &s->sub.trees.tb, s->hufts, z); - if (t != Z_OK) - { - ZFREE(z, s->sub.trees.blens); - r = t; - if (r == Z_DATA_ERROR) - s->mode = BAD; - LEAVE - } - s->sub.trees.index = 0; - //Tracev((stderr, "inflate: bits tree ok\n")); - s->mode = DTREE; - case DTREE: - while (t = s->sub.trees.table, - s->sub.trees.index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f)) - { - inflate_huft *h; - uInt i, j, c; - - t = s->sub.trees.bb; - NEEDBITS(t) - h = s->sub.trees.tb + ((uInt)b & inflate_mask[t]); - t = h->INFBITS; - c = h->base; - if (c < 16) - { - DUMPBITS(t) - s->sub.trees.blens[s->sub.trees.index++] = c; - } - else /* c == 16..18 */ - { - i = c == 18 ? 7 : c - 14; - j = c == 18 ? 11 : 3; - NEEDBITS(t + i) - DUMPBITS(t) - j += (uInt)b & inflate_mask[i]; - DUMPBITS(i) - i = s->sub.trees.index; - t = s->sub.trees.table; - if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || - (c == 16 && i < 1)) - { - ZFREE(z, s->sub.trees.blens); - s->mode = BAD; -// z->msg = (char*)"invalid bit length repeat"; - r = Z_DATA_ERROR; - LEAVE - } - c = c == 16 ? s->sub.trees.blens[i - 1] : 0; - do { - s->sub.trees.blens[i++] = c; - } while (--j); - s->sub.trees.index = i; - } - } - s->sub.trees.tb = Z_NULL; - { - uInt bl, bd; - inflate_huft *tl, *td; - inflate_codes_statef *c; - - bl = 9; /* must be <= 9 for lookahead assumptions */ - bd = 6; /* must be <= 9 for lookahead assumptions */ - t = s->sub.trees.table; - t = inflate_trees_dynamic(257 + (t & 0x1f), 1 + ((t >> 5) & 0x1f), - s->sub.trees.blens, &bl, &bd, &tl, &td, - s->hufts, z); - ZFREE(z, s->sub.trees.blens); - if (t != Z_OK) - { - if (t == (uInt)Z_DATA_ERROR) - s->mode = BAD; - r = t; - LEAVE - } - //Tracev((stderr, "inflate: trees ok\n")); - if ((c = inflate_codes_new(bl, bd, tl, td, z)) == Z_NULL) - { - r = Z_MEM_ERROR; - LEAVE + if (s->lookahead == 0) break; /* flush the current block */ } - s->sub.decode.codes = c; - } - s->mode = CODES; - case CODES: - UPDATE - if ((r = inflate_codes(s, z, r)) != Z_STREAM_END) - return inflate_flush(s, z, r); - r = Z_OK; - inflate_codes_free(s->sub.decode.codes, z); - LOAD - //Tracev((stderr, "inflate: codes end, %lu total out\n", -// z->total_out + (q >= s->read ? q - s->read : -// (s->end - s->read) + (q - s->window)))); - if (!s->last) - { - s->mode = TYPE; - break; - } - if (k > 7) /* return unused byte, if any */ - { -// Assert(k < 16, "inflate_codes grabbed too many bytes") - k -= 8; - n++; - p--; /* can always return one */ - } - s->mode = DRY; - case DRY: - FLUSH - if (s->read != s->write) - LEAVE - s->mode = DONE; - case DONE: - r = Z_STREAM_END; - LEAVE - case BAD: - r = Z_DATA_ERROR; - LEAVE - default: - r = Z_STREAM_ERROR; - LEAVE - } -} + /* See how many times the previous byte repeats */ + s->match_length = 0; + if (s->lookahead >= MIN_MATCH && s->strstart > 0) { + scan = s->window + s->strstart - 1; + prev = *scan; + if (prev == *++scan && prev == *++scan && prev == *++scan) { + strend = s->window + s->strstart + MAX_MATCH; + do { + } while (prev == *++scan && prev == *++scan && + prev == *++scan && prev == *++scan && + prev == *++scan && prev == *++scan && + prev == *++scan && prev == *++scan && + scan < strend); + s->match_length = MAX_MATCH - (int)(strend - scan); + if (s->match_length > s->lookahead) + s->match_length = s->lookahead; + } + Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); + } -int inflate_blocks_free(s, z) -inflate_blocks_statef *s; -z_streamp z; -{ - inflate_blocks_reset(s, z, Z_NULL); - ZFREE(z, s->window); - ZFREE(z, s->hufts); - ZFREE(z, s); - //Tracev((stderr, "inflate: blocks freed\n")); - return Z_OK; -} - - -/* void inflate_set_dictionary(s, d, n) */ -/* inflate_blocks_statef *s; */ -/* const Bytef *d; */ -/* uInt n; */ -/* { */ -/* zmemcpy(s->window, d, n); */ -/* s->read = s->write = s->window + n; */ -/* } */ - - -///////////////////////////////////////////////////////////////////////// - - -/* infcodes.c -- process literals and length/distance pairs - * Copyright (C) 1995-1998 Mark Adler - * For conditions of distribution and use, see copyright notice in zlib.h - */ - -//rls#include "zutil.h" -//rls#include "inftrees.h" -//rls#include "infblock.h" -//rls#include "infcodes.h" -//rls#include "infutil.h" - -/* simplify the use of the inflate_huft type with some defines */ -#define exop word.what.Exop -#define INFBITS word.what.Bits - -typedef enum { /* waiting for "i:"=input, "o:"=output, "x:"=nothing */ - START, /* x: set up for LEN */ - LEN, /* i: get length/literal/eob next */ - LENEXT, /* i: getting length extra (have base) */ - DIST, /* i: get distance next */ - DISTEXT, /* i: getting distance extra */ - COPY, /* o: copying bytes in window, waiting for space */ - LIT, /* o: got literal, waiting for output space */ - WASH, /* o: got eob, possibly still output waiting */ - END, /* x: got eob and all data flushed */ - BADCODE} /* x: got error */ -inflate_codes_mode; - -/* inflate codes private state */ -struct inflate_codes_state { - - /* mode */ - inflate_codes_mode mode; /* current inflate_codes mode */ - - /* mode dependent information */ - uInt len; - union { - struct { - inflate_huft *tree; /* pointer into tree */ - uInt need; /* bits needed */ - } code; /* if LEN or DIST, where in tree */ - uInt lit; /* if LIT, literal */ - struct { - uInt get; /* bits to get for extra */ - uInt dist; /* distance back to copy from */ - } copy; /* if EXT or COPY, where and how much */ - } sub; /* submode */ - - /* mode independent information */ - Byte lbits; /* ltree bits decoded per branch */ - Byte dbits; /* dtree bits decoder per branch */ - inflate_huft *ltree; /* literal/length/eob tree */ - inflate_huft *dtree; /* distance tree */ - -}; - + /* Emit match if have run of MIN_MATCH or longer, else emit literal */ + if (s->match_length >= MIN_MATCH) { + check_match(s, s->strstart, s->strstart - 1, s->match_length); -inflate_codes_statef *inflate_codes_new(bl, bd, tl, td, z) -uInt bl, bd; -inflate_huft *tl; -inflate_huft *td; /* need separate declaration for Borland C++ */ -z_streamp z; -{ - inflate_codes_statef *c; + _tr_tally_dist(s, 1, s->match_length - MIN_MATCH, bflush); - if ((c = (inflate_codes_statef *) - ZALLOC(z,1,sizeof(struct inflate_codes_state))) != Z_NULL) - { - c->mode = START; - c->lbits = (Byte)bl; - c->dbits = (Byte)bd; - c->ltree = tl; - c->dtree = td; -// Tracev((stderr, "inflate: codes new\n")); - } - return c; + s->lookahead -= s->match_length; + s->strstart += s->match_length; + s->match_length = 0; + } else { + /* No match, output a literal byte */ + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + } + if (bflush) FLUSH_BLOCK(s, 0); + } + s->insert = 0; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; } +/* =========================================================================== + * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. + * (It will be regenerated if this run of deflate switches away from Huffman.) + */ +local block_state deflate_huff( + deflate_state *s, + int flush +) { + int bflush; /* set if current block must be flushed */ -int inflate_codes(s, z, r) -inflate_blocks_statef *s; -z_streamp z; -int r; -{ - uInt j; /* temporary storage */ - inflate_huft *t; /* temporary pointer */ - uInt e; /* extra bits or operation */ - uLong b; /* bit buffer */ - uInt k; /* bits in bit buffer */ - Bytef *p; /* input data pointer */ - uInt n; /* bytes available there */ - Bytef *q; /* output window write pointer */ - uInt m; /* bytes to end of window or read pointer */ - Bytef *f; /* pointer to copy strings from */ - inflate_codes_statef *c = s->sub.decode.codes; /* codes state */ - - /* copy input/output information to locals (UPDATE macro restores) */ - LOAD - - /* process input and output based on current state */ - while (1) switch (c->mode) - { /* waiting for "i:"=input, "o:"=output, "x:"=nothing */ - case START: /* x: set up for LEN */ -//#ifndef SLOW -// if (m >= 258 && n >= 10) -// { -// UPDATE -// r = inflate_fast(c->lbits, c->dbits, c->ltree, c->dtree, s, z); -// LOAD -// if (r != Z_OK) -// { -// c->mode = r == Z_STREAM_END ? WASH : BADCODE; -// break; -// } -// } -//#endif /* !SLOW */ - c->sub.code.need = c->lbits; - c->sub.code.tree = c->ltree; - c->mode = LEN; - case LEN: /* i: get length/literal/eob next */ - j = c->sub.code.need; - NEEDBITS(j) - t = c->sub.code.tree + ((uInt)b & inflate_mask[j]); - DUMPBITS(t->INFBITS) - e = (uInt)(t->exop); - if (e == 0) /* literal */ - { - c->sub.lit = t->base; -// Tracevv((stderr, t->base >= 0x20 && t->base < 0x7f ? -// "inflate: literal '%c'\n" : -// "inflate: literal 0x%02x\n", t->base)); - c->mode = LIT; - break; - } - if (e & 16) /* length */ - { - c->sub.copy.get = e & 15; - c->len = t->base; - c->mode = LENEXT; - break; - } - if ((e & 64) == 0) /* next table */ - { - c->sub.code.need = e; - c->sub.code.tree = t + t->base; - break; - } - if (e & 32) /* end of block */ - { -// Tracevv((stderr, "inflate: end of block\n")); - c->mode = WASH; - break; - } - c->mode = BADCODE; /* invalid code */ -// z->msg = (char*)"invalid literal/length code"; - r = Z_DATA_ERROR; - LEAVE - case LENEXT: /* i: getting length extra (have base) */ - j = c->sub.copy.get; - NEEDBITS(j) - c->len += (uInt)b & inflate_mask[j]; - DUMPBITS(j) - c->sub.code.need = c->dbits; - c->sub.code.tree = c->dtree; -// Tracevv((stderr, "inflate: length %u\n", c->len)); - c->mode = DIST; - case DIST: /* i: get distance next */ - j = c->sub.code.need; - NEEDBITS(j) - t = c->sub.code.tree + ((uInt)b & inflate_mask[j]); - DUMPBITS(t->INFBITS) - e = (uInt)(t->exop); - if (e & 16) /* distance */ - { - c->sub.copy.get = e & 15; - c->sub.copy.dist = t->base; - c->mode = DISTEXT; - break; - } - if ((e & 64) == 0) /* next table */ - { - c->sub.code.need = e; - c->sub.code.tree = t + t->base; - break; - } - c->mode = BADCODE; /* invalid code */ -// z->msg = (char*)"invalid distance code"; - r = Z_DATA_ERROR; - LEAVE - case DISTEXT: /* i: getting distance extra */ - j = c->sub.copy.get; - NEEDBITS(j) - c->sub.copy.dist += (uInt)b & inflate_mask[j]; - DUMPBITS(j) -// Tracevv((stderr, "inflate: distance %u\n", c->sub.copy.dist)); - c->mode = COPY; - case COPY: /* o: copying bytes in window, waiting for space */ -#ifndef __TURBOC__ /* Turbo C bug for following expression */ - f = (uInt)(q - s->window) < c->sub.copy.dist ? - s->end - (c->sub.copy.dist - (q - s->window)) : - q - c->sub.copy.dist; -#else - f = q - c->sub.copy.dist; - if ((uInt)(q - s->window) < c->sub.copy.dist) - f = s->end - (c->sub.copy.dist - (uInt)(q - s->window)); -#endif - while (c->len) - { - NEEDOUT - OUTBYTE(*f++) - if (f == s->end) - f = s->window; - c->len--; - } - c->mode = START; - break; - case LIT: /* o: got literal, waiting for output space */ - NEEDOUT - OUTBYTE(c->sub.lit) - c->mode = START; - break; - case WASH: /* o: got eob, possibly more output */ - FLUSH - if (s->read != s->write) - LEAVE - c->mode = END; - case END: - r = Z_STREAM_END; - LEAVE - case BADCODE: /* x: got error */ - r = Z_DATA_ERROR; - LEAVE - default: - r = Z_STREAM_ERROR; - LEAVE - } -#ifdef NEED_DUMMY_RETURN - return Z_STREAM_ERROR; /* Some dumb compilers complain without this */ -#endif -} - + for (;;) { + /* Make sure that we have a literal to write. */ + if (s->lookahead == 0) { + fill_window(s); + if (s->lookahead == 0) { + if (flush == Z_NO_FLUSH) + return need_more; + break; /* flush the current block */ + } + } -void inflate_codes_free(c, z) -inflate_codes_statef *c; -z_streamp z; -{ - ZFREE(z, c); -// Tracev((stderr, "inflate: codes free\n")); + /* Output a literal byte */ + s->match_length = 0; + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + if (bflush) FLUSH_BLOCK(s, 0); + } + s->insert = 0; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; } - - -///////////////////////////////////////////////////////////////////////////// - - /* zutil.c -- target dependent utility functions for the compression library - * Copyright (C) 1995-1998 Jean-loup Gailly. - * For conditions of distribution and use, see copyright notice in zlib.h + * Copyright (C) 1995-2005, 2010, 2011, 2012 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h */ +/* @(#) $Id$ */ -//rls#include "zutil.h" - -//rls struct internal_state {int dummy;}; /* for buggy compilers */ +// #include "zutil.h" /* In sys-zlib.h (see make-zlib.r) */ +#ifndef Z_SOLO +// # include "gzguts.h" /* In sys-zlib.h (see make-zlib.r) */ +#endif -#ifndef STDC -extern void exit OF((int)); +#ifndef NO_DUMMY_DECL +struct internal_state {int dummy;}; /* for buggy compilers */ #endif -#ifdef DONTSTRIPZLIB -const char *z_errmsg[10] = { +z_const char * const z_errmsg[10] = { "need dictionary", /* Z_NEED_DICT 2 */ "stream end", /* Z_STREAM_END 1 */ "", /* Z_OK 0 */ @@ -1831,55 +3080,145 @@ const char *z_errmsg[10] = { "buffer error", /* Z_BUF_ERROR (-5) */ "incompatible version",/* Z_VERSION_ERROR (-6) */ ""}; + + +const char * ZEXPORT zlibVersion() +{ + return ZLIB_VERSION; +} + +uLong ZEXPORT zlibCompileFlags() +{ + uLong flags; + + flags = 0; + switch ((int)(sizeof(uInt))) { + case 2: break; + case 4: flags += 1; break; + case 8: flags += 2; break; + default: flags += 3; + } + switch ((int)(sizeof(uLong))) { + case 2: break; + case 4: flags += 1 << 2; break; + case 8: flags += 2 << 2; break; + default: flags += 3 << 2; + } + switch ((int)(sizeof(voidpf))) { + case 2: break; + case 4: flags += 1 << 4; break; + case 8: flags += 2 << 4; break; + default: flags += 3 << 4; + } + switch ((int)(sizeof(z_off_t))) { + case 2: break; + case 4: flags += 1 << 6; break; + case 8: flags += 2 << 6; break; + default: flags += 3 << 6; + } +#ifdef DEBUG + flags += 1 << 8; +#endif +#if defined(ASMV) || defined(ASMINF) + flags += 1 << 9; +#endif +#ifdef ZLIB_WINAPI + flags += 1 << 10; +#endif +#ifdef BUILDFIXED + flags += 1 << 12; +#endif +#ifdef DYNAMIC_CRC_TABLE + flags += 1 << 13; +#endif +#ifdef NO_GZCOMPRESS + flags += 1L << 16; +#endif +#ifdef NO_GZIP + flags += 1L << 17; +#endif +#ifdef PKZIP_BUG_WORKAROUND + flags += 1L << 20; +#endif +#ifdef FASTEST + flags += 1L << 21; +#endif +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifdef NO_vsnprintf + flags += 1L << 25; +# ifdef HAS_vsprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_vsnprintf_void + flags += 1L << 26; +# endif +# endif #else -const char *z_errmsg[10] = { -"", /* Z_NEED_DICT 2 */ -"", /* Z_STREAM_END 1 */ -"", /* Z_OK 0 */ -"", /* Z_ERRNO (-1) */ -"", /* Z_STREAM_ERROR (-2) */ -"", /* Z_DATA_ERROR (-3) */ -"", /* Z_MEM_ERROR (-4) */ -"", /* Z_BUF_ERROR (-5) */ -"",/* Z_VERSION_ERROR (-6) */ -""}; + flags += 1L << 24; +# ifdef NO_snprintf + flags += 1L << 25; +# ifdef HAS_sprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_snprintf_void + flags += 1L << 26; +# endif +# endif #endif + return flags; +} -/* #ifdef DEBUG */ +#ifdef DEBUG -/* # ifndef verbose */ -/* # define verbose 0 */ -/* # endif */ -/* int z_verbose = verbose; */ +# ifndef verbose +# define verbose 0 +# endif +int ZLIB_INTERNAL z_verbose = verbose; -/* void z_error (m) */ -/* char *m; */ -/* { */ -/* fprintf(stderr, "%s\n", m); */ -/* exit(1); */ -/* } */ -/* #endif */ +void ZLIB_INTERNAL z_error (m) + char *m; +{ + fprintf(stderr, "%s\n", m); + exit(1); +} +#endif /* exported to allow conversion of error code to string for compress() and * uncompress() */ +const char * ZEXPORT zError( + int err +) { + return ERR_MSG(err); +} + +#if defined(_WIN32_WCE) + /* The Microsoft C Run-Time Library for Windows CE doesn't have + * errno. We define it as a global variable to simplify porting. + * Its value is always 0 and should not be used. + */ + int errno = 0; +#endif + #ifndef HAVE_MEMCPY -void zmemcpy(dest, source, len) +void ZLIB_INTERNAL zmemcpy(dest, source, len) Bytef* dest; - Bytef* source; + const Bytef* source; uInt len; -{ +) { if (len == 0) return; do { *dest++ = *source++; /* ??? to be unrolled */ } while (--len != 0); } -int zmemcmp(s1, s2, len) - Bytef* s1; - Bytef* s2; - uInt len; +int ZLIB_INTERNAL zmemcmp(s1, s2, len) + const Bytef* s1, + const Bytef* s2, + uInt len { uInt j; @@ -1889,9 +3228,9 @@ int zmemcmp(s1, s2, len) return 0; } -void zmemzero(dest, len) - Bytef* dest; - uInt len; +void ZLIB_INTERNAL zmemzero(dest, len) + Bytef* dest, + uInt len { if (len == 0) return; do { @@ -1900,11 +3239,13 @@ void zmemzero(dest, len) } #endif +#ifndef Z_SOLO + +#ifdef SYS16BIT + #ifdef __TURBOC__ -#if (defined( __BORLANDC__) || !defined(SMALL_MEDIUM)) && !defined(__32BIT__) -/* Small and medium model in Turbo C are for now limited to near allocation - * with reduced MAX_WBITS and MAX_MEM_LEVEL - */ +/* Turbo C in 16-bit mode */ + # define MY_ZCALLOC /* Turbo C malloc() does not allow dynamic allocation of 64K bytes @@ -1931,7 +3272,7 @@ local ptr_table table[MAX_PTR]; * a protected system like OS/2. Use Microsoft C instead. */ -voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size) { voidpf buf = opaque; /* just to make some compilers happy */ ulg bsize = (ulg)items*size; @@ -1955,7 +3296,7 @@ voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) return buf; } -void zcfree (voidpf opaque, voidpf ptr) +void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) { int n; if (*(ush*)&ptr != 0) { /* object < 64K */ @@ -1974,80 +3315,77 @@ void zcfree (voidpf opaque, voidpf ptr) return; } ptr = opaque; /* just to make some compilers happy */ -// Assert(0, "zcfree: ptr not found"); + Assert(0, "zcfree: ptr not found"); } -#endif + #endif /* __TURBOC__ */ -#if defined(M_I86) && !defined(__32BIT__) +#ifdef M_I86 /* Microsoft C in 16-bit mode */ # define MY_ZCALLOC -#if (!defined(_MSC_VER) || (_MSC_VER < 600)) +#if (!defined(_MSC_VER) || (_MSC_VER <= 600)) # define _halloc halloc # define _hfree hfree #endif -voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, uInt items, uInt size) { if (opaque) opaque = 0; /* to make compiler happy */ return _halloc((long)items, size); } -void zcfree (voidpf opaque, voidpf ptr) +void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) { if (opaque) opaque = 0; /* to make compiler happy */ _hfree(ptr); } -#endif /* MSC */ +#endif /* M_I86 */ + +#endif /* SYS16BIT */ -#if 1 #ifndef MY_ZCALLOC /* Any system without a special alloc function */ #ifndef STDC +extern voidp malloc OF((uInt size)); extern voidp calloc OF((uInt items, uInt size)); extern void free OF((voidpf ptr)); #endif -#if defined(TO_WINCE) -extern voidp calloc(uInt items, uInt size); -#endif -voidpf zcalloc (opaque, items, size) - voidpf opaque; - unsigned items; - unsigned size; -{ -// if (opaque) items += size - size; /* make compiler happy */ - return (voidpf)calloc(items, size); +voidpf ZLIB_INTERNAL zcalloc ( + voidpf opaque, + unsigned items, + unsigned size +) { + if (opaque) items += size - size; /* make compiler happy */ + return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) : + (voidpf)calloc(items, size); } -void zcfree (opaque, ptr) - voidpf opaque; - voidpf ptr; -{ +void ZLIB_INTERNAL zcfree ( + voidpf opaque, + voidpf ptr +) { free(ptr); -// if (opaque) return; /* make compiler happy */ + if (opaque) return; /* make compiler happy */ } -#endif - #endif /* MY_ZCALLOC */ - -//////////////////////////////////////////////////////////////// - - +#endif /* !Z_SOLO */ /* compress.c -- compress a memory buffer - * Copyright (C) 1995-1998 Jean-loup Gailly. - * For conditions of distribution and use, see copyright notice in zlib.h + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h */ +/* @(#) $Id$ */ -//rls#include "zlib.h" +#define ZLIB_INTERNAL +// #include "zlib.h" /* In sys-zlib.h (see make-zlib.r) */ /* =========================================================================== Compresses the source buffer into the destination buffer. The level @@ -2060,24 +3398,22 @@ void zcfree (opaque, ptr) memory, Z_BUF_ERROR if there was not enough room in the output buffer, Z_STREAM_ERROR if the level parameter is invalid. */ -int ZEXPORT compress2 (dest, destLen, source, sourceLen, use_crc) - Bytef *dest; - uLongf *destLen; - const Bytef *source; - uLong sourceLen; - int use_crc; -{ +int ZEXPORT compress2 ( + Bytef *dest, + uLongf *destLen, + const Bytef *source, + uLong sourceLen, + int level +) { z_stream stream; int err; - stream.checksum = use_crc ? crc32 : adler32; - - stream.next_in = (Bytef*)source; + stream.next_in = (z_const Bytef *)source; stream.avail_in = (uInt)sourceLen; -/* #ifdef MAXSEG_64K */ -/* // Check for source > 64K on 16-bit machine: */ -/* if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; */ -/* #endif */ +#ifdef MAXSEG_64K + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; +#endif stream.next_out = dest; stream.avail_out = (uInt)*destLen; if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; @@ -2086,7 +3422,7 @@ int ZEXPORT compress2 (dest, destLen, source, sourceLen, use_crc) stream.zfree = (free_func)0; stream.opaque = (voidpf)0; - err = deflateInit(&stream, -1); + err = deflateInit(&stream, level); if (err != Z_OK) return err; err = deflate(&stream, Z_FINISH); @@ -2100,15 +3436,36 @@ int ZEXPORT compress2 (dest, destLen, source, sourceLen, use_crc) return err; } -//////////////////////////////////////////////////////////////////////// +/* =========================================================================== + */ +int ZEXPORT compress ( + Bytef *dest, + uLongf *destLen, + const Bytef *source, + uLong sourceLen +) { + return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION); +} +/* =========================================================================== + If the default memLevel or windowBits for deflateInit() is changed, then + this function needs to be updated. + */ +uLong ZEXPORT compressBound ( + uLong sourceLen +) { + return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + + (sourceLen >> 25) + 13; +} /* uncompr.c -- decompress a memory buffer - * Copyright (C) 1995-1998 Jean-loup Gailly. - * For conditions of distribution and use, see copyright notice in zlib.h + * Copyright (C) 1995-2003, 2010 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h */ +/* @(#) $Id$ */ -//rls#include "zlib.h" +#define ZLIB_INTERNAL +// #include "zlib.h" /* In sys-zlib.h (see make-zlib.r) */ /* =========================================================================== Decompresses the source buffer into the destination buffer. sourceLen is @@ -2118,26 +3475,21 @@ int ZEXPORT compress2 (dest, destLen, source, sourceLen, use_crc) been saved previously by the compressor and transmitted to the decompressor by some mechanism outside the scope of this compression library.) Upon exit, destLen is the actual size of the compressed buffer. - This function can be used to decompress a whole file at once if the - input file is mmap'ed. uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough memory, Z_BUF_ERROR if there was not enough room in the output buffer, or Z_DATA_ERROR if the input data was corrupted. */ -int ZEXPORT uncompress (dest, destLen, source, sourceLen, use_crc) - Bytef *dest; - uLongf *destLen; - const Bytef *source; - uLong sourceLen; - int use_crc; -{ +int ZEXPORT uncompress ( + Bytef *dest, + uLongf *destLen, + const Bytef *source, + uLong sourceLen +) { z_stream stream; int err; - stream.checksum = use_crc ? crc32 : adler32; - - stream.next_in = (Bytef*)source; + stream.next_in = (z_const Bytef *)source; stream.avail_in = (uInt)sourceLen; /* Check for source > 64K on 16-bit machine: */ if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; @@ -2155,113 +3507,19 @@ int ZEXPORT uncompress (dest, destLen, source, sourceLen, use_crc) err = inflate(&stream, Z_FINISH); if (err != Z_STREAM_END) { inflateEnd(&stream); - return err == Z_OK ? Z_BUF_ERROR : err; + if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0)) + return Z_DATA_ERROR; + return err; } *destLen = stream.total_out; err = inflateEnd(&stream); return err; } - -////////////////////////////////////////////////////////////////////////////// - - -/* inflate_util.c -- data and routines common to blocks and codes - * Copyright (C) 1995-1998 Mark Adler - * For conditions of distribution and use, see copyright notice in zlib.h - */ - -//rls#include "zutil.h" -//rls#include "infblock.h" -//rls#include "inftrees.h" -//rls#include "infcodes.h" -//rls#include "infutil.h" - -//rls struct inflate_codes_state {int dummy;}; /* for buggy compilers */ - -/* And'ing with mask[n] masks the lower n bits */ -uInt inflate_mask[17] = { - 0x0000, - 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, - 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff -}; - - -/* copy as much as possible from the sliding window to the output area */ -int inflate_flush(s, z, r) -inflate_blocks_statef *s; -z_streamp z; -int r; -{ - uInt n; - Bytef *p; - Bytef *q; - - /* local copies of source and destination pointers */ - p = z->next_out; - q = s->read; - - /* compute number of bytes to copy as far as end of window */ - n = (uInt)((q <= s->write ? s->write : s->end) - q); - if (n > z->avail_out) n = z->avail_out; - if (n && r == Z_BUF_ERROR) r = Z_OK; - - /* update counters */ - z->avail_out -= n; - z->total_out += n; - - /* update check information */ - if (s->checkfn != Z_NULL) - z->adler = s->check = (*s->checkfn)(s->check, q, n); - - /* copy as far as end of window */ - zmemcpy(p, q, n); - p += n; - q += n; - - /* see if more to copy at beginning of window */ - if (q == s->end) - { - /* wrap pointers */ - q = s->window; - if (s->write == s->end) - s->write = s->window; - - /* compute bytes to copy */ - n = (uInt)(s->write - q); - if (n > z->avail_out) n = z->avail_out; - if (n && r == Z_BUF_ERROR) r = Z_OK; - - /* update counters */ - z->avail_out -= n; - z->total_out += n; - - /* update check information */ - if (s->checkfn != Z_NULL) - z->adler = s->check = (*s->checkfn)(s->check, q, n); - - /* copy */ - zmemcpy(p, q, n); - p += n; - q += n; - } - - /* update pointers */ - z->next_out = p; - s->read = q; - - /* done */ - return r; -} - - - -//////////////////////////////////////////////////////////////////////////// - - /* trees.c -- output deflated data using Huffman coding - * Copyright (C) 1995-1998 Jean-loup Gailly - * For conditions of distribution and use, see copyright notice in zlib.h + * Copyright (C) 1995-2012 Jean-loup Gailly + * detect_data_type() function provided freely by Cosmin Truta, 2006 + * For conditions of distribution and use, see copyright notice in zlib.h */ /* @@ -2290,14 +3548,15 @@ int r; * Addison-Wesley, 1983. ISBN 0-201-06672-6. */ +/* @(#) $Id$ */ /* #define GEN_TREES_H */ -//rls#include "deflate.h" +// #include "deflate.h" /* In sys-zlib.h (see make-zlib.r) */ -/* #ifdef DEBUG */ -/* # include */ -/* #endif */ +#ifdef DEBUG +# include +#endif /* =========================================================================== * Constants @@ -2333,11 +3592,6 @@ local const uch bl_order[BL_CODES] * probability, to avoid transmitting the lengths for unused bit length codes. */ -#define Buf_size (8 * 2*sizeof(char)) -/* Number of bits used within bi_buf. (bi_buf might be implemented on - * more than 16 bits on some systems.) - */ - /* =========================================================================== * Local data. These are initialized only once. */ @@ -2375,7 +3629,6 @@ local int base_dist[D_CODES]; /* First normalized distance for each code (0 = distance of 1) */ #else -//# include "ztrees.h" /* header created automatically with -DGEN_TREES_H */ local const ct_data static_ltree[L_CODES+2] = { @@ -2448,7 +3701,7 @@ local const ct_data static_dtree[D_CODES] = { {{19},{ 5}}, {{11},{ 5}}, {{27},{ 5}}, {{ 7},{ 5}}, {{23},{ 5}} }; -const uch _dist_code[DIST_CODE_LEN] = { +const uch ZLIB_INTERNAL _dist_code[DIST_CODE_LEN] = { 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, @@ -2477,7 +3730,7 @@ const uch _dist_code[DIST_CODE_LEN] = { 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 }; -const uch _length_code[MAX_MATCH-MIN_MATCH+1]= { +const uch ZLIB_INTERNAL _length_code[MAX_MATCH-MIN_MATCH+1]= { 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, @@ -2538,9 +3791,9 @@ local void send_tree OF((deflate_state *s, ct_data *tree, int max_code)); local int build_bl_tree OF((deflate_state *s)); local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes, int blcodes)); -local void compress_block OF((deflate_state *s, ct_data *ltree, - ct_data *dtree)); -local void set_data_type OF((deflate_state *s)); +local void compress_block OF((deflate_state *s, const ct_data *ltree, + const ct_data *dtree)); +local int detect_data_type OF((deflate_state *s)); local unsigned bi_reverse OF((unsigned value, int length)); local void bi_windup OF((deflate_state *s)); local void bi_flush OF((deflate_state *s)); @@ -2574,25 +3827,49 @@ local void gen_trees_header OF((void)); * Send a value on a given number of bits. * IN assertion: length <= 16 and value fits in length bits. */ - -/* !!! #define */ -static void send_bits(deflate_state *s, int value, int length) -{ int len = length; - if (s->bi_valid > (int)Buf_size - len) { - int val = value; - s->bi_buf |= (val << s->bi_valid); - put_short(s, s->bi_buf); - s->bi_buf = (ush)val >> (Buf_size - s->bi_valid); - s->bi_valid += len - Buf_size; - } else { - s->bi_buf |= (value) << s->bi_valid; - s->bi_valid += len; - } +#ifdef DEBUG +local void send_bits OF((deflate_state *s, int value, int length)); + +local void send_bits( + deflate_state *s, + int value, /* value to send */ + int length /* number of bits */ +) { + Tracevv((stderr," l %2d v %4x ", length, value)); + Assert(length > 0 && length <= 15, "invalid length"); + s->bits_sent += (ulg)length; + + /* If not enough room in bi_buf, use (valid) bits from bi_buf and + * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid)) + * unused bits in value. + */ + if (s->bi_valid > (int)Buf_size - length) { + s->bi_buf |= (ush)value << s->bi_valid; + put_short(s, s->bi_buf); + s->bi_buf = (ush)value >> (Buf_size - s->bi_valid); + s->bi_valid += length - Buf_size; + } else { + s->bi_buf |= (ush)value << s->bi_valid; + s->bi_valid += length; + } +} +#else /* !DEBUG */ + +#define send_bits(s, value, length) \ +{ int len = length;\ + if (s->bi_valid > (int)Buf_size - len) {\ + int val = value;\ + s->bi_buf |= (ush)val << s->bi_valid;\ + put_short(s, s->bi_buf);\ + s->bi_buf = (ush)val >> (Buf_size - s->bi_valid);\ + s->bi_valid += len - Buf_size;\ + } else {\ + s->bi_buf |= (ush)(value) << s->bi_valid;\ + s->bi_valid += len;\ + }\ } +#endif /* DEBUG */ -#ifndef MAX -#define MAX(a,b) (a >= b ? a : b) -#endif /* the arguments must not have side effects */ @@ -2613,6 +3890,15 @@ local void tr_static_init() if (static_init_done) return; + /* For some embedded targets, global variables are not initialized: */ +#ifdef NO_INIT_GLOBAL_POINTERS + static_l_desc.static_tree = static_ltree; + static_l_desc.extra_bits = extra_lbits; + static_d_desc.static_tree = static_dtree; + static_d_desc.extra_bits = extra_dbits; + static_bl_desc.extra_bits = extra_blbits; +#endif + /* Initialize the mapping length (0..255) -> length code (0..28) */ length = 0; for (code = 0; code < LENGTH_CODES-1; code++) { @@ -2621,7 +3907,7 @@ local void tr_static_init() _length_code[length++] = (uch)code; } } -// Assert (length == 256, "tr_static_init: length != 256"); + Assert (length == 256, "tr_static_init: length != 256"); /* Note that the length 255 (match length 258) can be represented * in two different ways: code 284 + 5 bits or code 285, so we * overwrite length_code[255] to use the best encoding: @@ -2636,7 +3922,7 @@ local void tr_static_init() _dist_code[dist++] = (uch)code; } } -// Assert (dist == 256, "tr_static_init: dist != 256"); + Assert (dist == 256, "tr_static_init: dist != 256"); dist >>= 7; /* from now on, all distances are divided by 128 */ for ( ; code < D_CODES; code++) { base_dist[code] = dist << 7; @@ -2644,7 +3930,7 @@ local void tr_static_init() _dist_code[256 + dist++] = (uch)code; } } -// Assert (dist == 256, "tr_static_init: 256+dist != 512"); + Assert (dist == 256, "tr_static_init: 256+dist != 512"); /* Construct the codes of the static literal tree */ for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0; @@ -2675,74 +3961,73 @@ local void tr_static_init() /* =========================================================================== * Genererate the file trees.h describing the static trees. */ -/* #ifdef GEN_TREES_H */ -/* # ifndef DEBUG */ -/* # include */ -/* # endif */ - -/* # define SEPARATOR(i, last, width) \ */ -/* ((i) == (last)? "\n};\n\n" : \ */ -/* ((i) % (width) == (width)-1 ? ",\n" : ", ")) */ - -/* void gen_trees_header() */ -/* { */ -/* FILE *header = fopen("trees.h", "w"); */ -/* int i; */ - -/* // Assert (header != NULL, "Can't open trees.h"); */ -/* fprintf(header, */ -/* // header created automatically with -DGEN_TREES_H \n\n"); */ - -/* fprintf(header, "local const ct_data static_ltree[L_CODES+2] = {\n"); */ - /* for (i = 0; i < L_CODES+2; i++) { */ -/* fprintf(header, "{{%3u},{%3u}}%s", static_ltree[i].Code, */ -/* static_ltree[i].Len, SEPARATOR(i, L_CODES+1, 5)); */ -/* } */ - -/* fprintf(header, "local const ct_data static_dtree[D_CODES] = {\n"); */ -/* for (i = 0; i < D_CODES; i++) { */ -/* fprintf(header, "{{%2u},{%2u}}%s", static_dtree[i].Code, */ -/* static_dtree[i].Len, SEPARATOR(i, D_CODES-1, 5)); */ -/* } */ - -/* fprintf(header, "const uch _dist_code[DIST_CODE_LEN] = {\n"); */ -/* for (i = 0; i < DIST_CODE_LEN; i++) { */ -/* fprintf(header, "%2u%s", _dist_code[i], */ -/* SEPARATOR(i, DIST_CODE_LEN-1, 20)); */ -/* } */ - -/* fprintf(header, "const uch _length_code[MAX_MATCH-MIN_MATCH+1]= {\n"); */ -/* for (i = 0; i < MAX_MATCH-MIN_MATCH+1; i++) { */ -/* fprintf(header, "%2u%s", _length_code[i], */ -/* SEPARATOR(i, MAX_MATCH-MIN_MATCH, 20)); */ -/* } */ - -/* fprintf(header, "local const int base_length[LENGTH_CODES] = {\n"); */ -/* for (i = 0; i < LENGTH_CODES; i++) { */ -/* fprintf(header, "%1u%s", base_length[i], */ -/* SEPARATOR(i, LENGTH_CODES-1, 20)); */ -/* } */ - -/* fprintf(header, "local const int base_dist[D_CODES] = {\n"); */ -/* for (i = 0; i < D_CODES; i++) { */ -/* fprintf(header, "%5u%s", base_dist[i], */ -/* SEPARATOR(i, D_CODES-1, 10)); */ -/* } */ - -/* fclose(header); */ -/* } */ -/* #endif / GEN_TREES_H */ +#ifdef GEN_TREES_H +# ifndef DEBUG +// # include // !!! No in Ren-C release builds +# endif + +# define SEPARATOR(i, last, width) \ + ((i) == (last)? "\n};\n\n" : \ + ((i) % (width) == (width)-1 ? ",\n" : ", ")) + +void gen_trees_header() +{ + FILE *header = fopen("trees.h", "w"); + int i; + + Assert (header != NULL, "Can't open trees.h"); + fprintf(header, + "/* header created automatically with -DGEN_TREES_H */\n\n"); + + fprintf(header, "local const ct_data static_ltree[L_CODES+2] = {\n"); + for (i = 0; i < L_CODES+2; i++) { + fprintf(header, "{{%3u},{%3u}}%s", static_ltree[i].Code, + static_ltree[i].Len, SEPARATOR(i, L_CODES+1, 5)); + } + + fprintf(header, "local const ct_data static_dtree[D_CODES] = {\n"); + for (i = 0; i < D_CODES; i++) { + fprintf(header, "{{%2u},{%2u}}%s", static_dtree[i].Code, + static_dtree[i].Len, SEPARATOR(i, D_CODES-1, 5)); + } + + fprintf(header, "const uch ZLIB_INTERNAL _dist_code[DIST_CODE_LEN] = {\n"); + for (i = 0; i < DIST_CODE_LEN; i++) { + fprintf(header, "%2u%s", _dist_code[i], + SEPARATOR(i, DIST_CODE_LEN-1, 20)); + } + + fprintf(header, + "const uch ZLIB_INTERNAL _length_code[MAX_MATCH-MIN_MATCH+1]= {\n"); + for (i = 0; i < MAX_MATCH-MIN_MATCH+1; i++) { + fprintf(header, "%2u%s", _length_code[i], + SEPARATOR(i, MAX_MATCH-MIN_MATCH, 20)); + } + + fprintf(header, "local const int base_length[LENGTH_CODES] = {\n"); + for (i = 0; i < LENGTH_CODES; i++) { + fprintf(header, "%1u%s", base_length[i], + SEPARATOR(i, LENGTH_CODES-1, 20)); + } + + fprintf(header, "local const int base_dist[D_CODES] = {\n"); + for (i = 0; i < D_CODES; i++) { + fprintf(header, "%5u%s", base_dist[i], + SEPARATOR(i, D_CODES-1, 10)); + } + + fclose(header); +} +#endif /* GEN_TREES_H */ /* =========================================================================== * Initialize the tree data structures for a new zlib stream. */ -void _tr_init(s) - deflate_state *s; -{ +void ZLIB_INTERNAL _tr_init( + deflate_state *s +) { tr_static_init(); - s->compressed_len = 0L; - s->l_desc.dyn_tree = s->dyn_ltree; s->l_desc.stat_desc = &static_l_desc; @@ -2754,7 +4039,10 @@ void _tr_init(s) s->bi_buf = 0; s->bi_valid = 0; - s->last_eob_len = 8; /* enough lookahead for inflate */ +#ifdef DEBUG + s->compressed_len = 0L; + s->bits_sent = 0L; +#endif /* Initialize the first block of the first file: */ init_block(s); @@ -2763,9 +4051,9 @@ void _tr_init(s) /* =========================================================================== * Initialize a new block. */ -local void init_block(s) - deflate_state *s; -{ +local void init_block( + deflate_state *s +) { int n; /* iterates over tree elements */ /* Initialize the trees. */ @@ -2807,11 +4095,11 @@ local void init_block(s) * when the heap property is re-established (each father smaller than its * two sons). */ -local void pqdownheap(s, tree, k) - deflate_state *s; - ct_data *tree; /* the tree to restore */ - int k; /* node to move down */ -{ +local void pqdownheap( + deflate_state *s, + ct_data *tree, /* the tree to restore */ + int k /* node to move down */ +) { int v = s->heap[k]; int j = k << 1; /* left son of k */ while (j <= s->heap_len) { @@ -2842,10 +4130,10 @@ local void pqdownheap(s, tree, k) * The length opt_len is updated; static_len is also updated if stree is * not null. */ -local void gen_bitlen(s, desc) - deflate_state *s; - tree_desc *desc; /* the tree descriptor */ -{ +local void gen_bitlen( + deflate_state *s, + tree_desc *desc /* the tree descriptor */ +) { ct_data *tree = desc->dyn_tree; int max_code = desc->max_code; const ct_data *stree = desc->stat_desc->static_tree; @@ -2884,7 +4172,7 @@ local void gen_bitlen(s, desc) } if (overflow == 0) return; -// Trace((stderr,"\nbit length overflow\n")); + Trace((stderr,"\nbit length overflow\n")); /* This happens for example on obj2 and pic of the Calgary corpus */ /* Find the first bit length which could increase: */ @@ -2910,8 +4198,8 @@ local void gen_bitlen(s, desc) while (n != 0) { m = s->heap[--h]; if (m > max_code) continue; - if (tree[m].Len != (unsigned) bits) { -// Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); + if ((unsigned) tree[m].Len != (unsigned) bits) { + Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); s->opt_len += ((long)bits - (long)tree[m].Len) *(long)tree[m].Freq; tree[m].Len = (ush)bits; @@ -2929,11 +4217,11 @@ local void gen_bitlen(s, desc) * OUT assertion: the field code is set for all tree elements of non * zero code length. */ -local void gen_codes (tree, max_code, bl_count) - ct_data *tree; /* the tree to decorate */ - int max_code; /* largest code with non zero frequency */ - ushf *bl_count; /* number of codes at each bit length */ -{ +local void gen_codes ( + ct_data *tree, /* the tree to decorate */ + int max_code, /* largest code with non zero frequency */ + ushf *bl_count /* number of codes at each bit length */ +) { ush next_code[MAX_BITS+1]; /* next code value for each bit length */ ush code = 0; /* running code value */ int bits; /* bit index */ @@ -2948,9 +4236,9 @@ local void gen_codes (tree, max_code, bl_count) /* Check that the bit counts in bl_count are consistent. The last code * must be all ones. */ -// Assert (code + bl_count[MAX_BITS]-1 == (1<dyn_tree; const ct_data *stree = desc->stat_desc->static_tree; int elems = desc->stat_desc->elems; @@ -3029,8 +4317,15 @@ local void build_tree(s, desc) /* Create a new node father of n and m */ tree[node].Freq = tree[n].Freq + tree[m].Freq; - s->depth[node] = (uch) (MAX(s->depth[n], s->depth[m]) + 1); + s->depth[node] = (uch)((s->depth[n] >= s->depth[m] ? + s->depth[n] : s->depth[m]) + 1); tree[n].Dad = tree[m].Dad = (ush)node; +#ifdef DUMP_BL_TREE + if (tree == s->bl_tree) { + fprintf(stderr,"\nnode %d(%d), sons %d(%d) %d(%d)", + node, tree[node].Freq, n, tree[n].Freq, m, tree[m].Freq); + } +#endif /* and insert the new node in the heap */ s->heap[SMALLEST] = node++; pqdownheap(s, tree, SMALLEST); @@ -3052,11 +4347,11 @@ local void build_tree(s, desc) * Scan a literal or distance tree to determine the frequencies of the codes * in the bit length tree. */ -local void scan_tree (s, tree, max_code) - deflate_state *s; - ct_data *tree; /* the tree to be scanned */ - int max_code; /* and its largest code of non zero frequency */ -{ +local void scan_tree ( + deflate_state *s, + ct_data *tree, /* the tree to be scanned */ + int max_code /* and its largest code of non zero frequency */ +) { int n; /* iterates over all tree elements */ int prevlen = -1; /* last emitted length */ int curlen; /* length of current code */ @@ -3097,11 +4392,11 @@ local void scan_tree (s, tree, max_code) * Send a literal or distance tree in compressed form, using the codes in * bl_tree. */ -local void send_tree (s, tree, max_code) - deflate_state *s; - ct_data *tree; /* the tree to be scanned */ - int max_code; /* and its largest code of non zero frequency */ -{ +local void send_tree ( + deflate_state *s, + ct_data *tree, /* the tree to be scanned */ + int max_code /* and its largest code of non zero frequency */ +) { int n; /* iterates over all tree elements */ int prevlen = -1; /* last emitted length */ int curlen; /* length of current code */ @@ -3124,7 +4419,7 @@ local void send_tree (s, tree, max_code) if (curlen != prevlen) { send_code(s, curlen, s->bl_tree); count--; } -// Assert(count >= 3 && count <= 6, " 3_6?"); + Assert(count >= 3 && count <= 6, " 3_6?"); send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2); } else if (count <= 10) { @@ -3148,9 +4443,9 @@ local void send_tree (s, tree, max_code) * Construct the Huffman tree for the bit lengths and return the index in * bl_order of the last bit length code to send. */ -local int build_bl_tree(s) - deflate_state *s; -{ +local int build_bl_tree( + deflate_state *s +) { int max_blindex; /* index of last bit length code of non zero freq */ /* Determine the bit length frequencies for literal and distance trees */ @@ -3172,8 +4467,8 @@ local int build_bl_tree(s) } /* Update opt_len to include the bit length tree and counts */ s->opt_len += 3*(max_blindex+1) + 5+5+4; -// Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", -// s->opt_len, s->static_len)); + Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", + s->opt_len, s->static_len)); return max_blindex; } @@ -3183,201 +4478,235 @@ local int build_bl_tree(s) * lengths of the bit length codes, the literal tree and the distance tree. * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. */ -local void send_all_trees(s, lcodes, dcodes, blcodes) - deflate_state *s; - int lcodes, dcodes, blcodes; /* number of codes for each tree */ -{ +local void send_all_trees( + deflate_state *s, + int lcodes, int dcodes, int blcodes /* number of codes for each tree */ +) { int rank; /* index in bl_order */ -// Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); -// Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, -// "too many codes"); -// Tracev((stderr, "\nbl counts: ")); + Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, + "too many codes"); + Tracev((stderr, "\nbl counts: ")); send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */ send_bits(s, dcodes-1, 5); send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */ for (rank = 0; rank < blcodes; rank++) { -// Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + Tracev((stderr, "\nbl code %2d ", bl_order[rank])); send_bits(s, s->bl_tree[bl_order[rank]].Len, 3); } -// Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); + Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */ -// Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); + Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */ -// Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); + Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); } /* =========================================================================== * Send a stored block */ -void _tr_stored_block(s, buf, stored_len, eof) - deflate_state *s; - charf *buf; /* input block */ - ulg stored_len; /* length of input block */ - int eof; /* true if this is the last block for a file */ -{ - send_bits(s, (STORED_BLOCK<<1)+eof, 3); /* send block type */ +void ZLIB_INTERNAL _tr_stored_block( + deflate_state *s, + charf *buf, /* input block */ + ulg stored_len, /* length of input block */ + int last /* one if this is the last block for a file */ +) { + send_bits(s, (STORED_BLOCK<<1)+last, 3); /* send block type */ +#ifdef DEBUG s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L; s->compressed_len += (stored_len + 4) << 3; - +#endif copy_block(s, buf, (unsigned)stored_len, 1); /* with header */ } /* =========================================================================== - * Send one empty static block to give enough lookahead for inflate. - * This takes 10 bits, of which 7 may remain in the bit buffer. - * The current inflate code requires 9 bits of lookahead. If the - * last two codes for the previous block (real code plus EOB) were coded - * on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode - * the last real code. In this case we send two empty static blocks instead - * of one. (There are no problems if the previous block is stored or fixed.) - * To simplify the code, we assume the worst case of last real code encoded - * on one bit only. + * Flush the bits in the bit buffer to pending output (leaves at most 7 bits) */ -void _tr_align(s) - deflate_state *s; -{ +void ZLIB_INTERNAL _tr_flush_bits( + deflate_state *s +) { + bi_flush(s); +} + +/* =========================================================================== + * Send one empty static block to give enough lookahead for inflate. + * This takes 10 bits, of which 7 may remain in the bit buffer. + */ +void ZLIB_INTERNAL _tr_align( + deflate_state *s +) { send_bits(s, STATIC_TREES<<1, 3); send_code(s, END_BLOCK, static_ltree); +#ifdef DEBUG s->compressed_len += 10L; /* 3 for block type, 7 for EOB */ +#endif bi_flush(s); - /* Of the 10 bits for the empty block, we have already sent - * (10 - bi_valid) bits. The lookahead for the last real code (before - * the EOB of the previous block) was thus at least one plus the length - * of the EOB plus what we have just sent of the empty static block. - */ - if (1 + s->last_eob_len + 10 - s->bi_valid < 9) { - send_bits(s, STATIC_TREES<<1, 3); - send_code(s, END_BLOCK, static_ltree); - s->compressed_len += 10L; - bi_flush(s); - } - s->last_eob_len = 7; } /* =========================================================================== * Determine the best encoding for the current block: dynamic trees, static - * trees or store, and output the encoded block to the zip file. This function - * returns the total compressed length for the file so far. + * trees or store, and output the encoded block to the zip file. */ -ulg _tr_flush_block(s, buf, stored_len, eof) - deflate_state *s; - charf *buf; /* input block, or NULL if too old */ - ulg stored_len; /* length of input block */ - int eof; /* true if this is the last block for a file */ -{ +void ZLIB_INTERNAL _tr_flush_block( + deflate_state *s, + charf *buf, /* input block, or NULL if too old */ + ulg stored_len, /* length of input block */ + int last /* one if this is the last block for a file */ +) { ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */ int max_blindex = 0; /* index of last bit length code of non zero freq */ /* Build the Huffman trees unless a stored block is forced */ if (s->level > 0) { - /* Check if the file is ascii or binary */ - if (s->data_type == Z_UNKNOWN) set_data_type(s); + /* Check if the file is binary or text */ + if (s->strm->data_type == Z_UNKNOWN) + s->strm->data_type = detect_data_type(s); - /* Construct the literal and distance trees */ - build_tree(s, (tree_desc *)(&(s->l_desc))); -// Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, -// s->static_len)); + /* Construct the literal and distance trees */ + build_tree(s, (tree_desc *)(&(s->l_desc))); + Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, + s->static_len)); - build_tree(s, (tree_desc *)(&(s->d_desc))); -// Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, -// s->static_len)); - /* At this point, opt_len and static_len are the total bit lengths of - * the compressed block data, excluding the tree representations. - */ + build_tree(s, (tree_desc *)(&(s->d_desc))); + Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, + s->static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ - /* Build the bit length tree for the above two trees, and get the index - * in bl_order of the last bit length code to send. - */ - max_blindex = build_bl_tree(s); + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(s); - /* Determine the best encoding. Compute first the block length in bytes*/ - opt_lenb = (s->opt_len+3+7)>>3; - static_lenb = (s->static_len+3+7)>>3; + /* Determine the best encoding. Compute the block lengths in bytes. */ + opt_lenb = (s->opt_len+3+7)>>3; + static_lenb = (s->static_len+3+7)>>3; -// Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", -// opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, -// s->last_lit)); + Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", + opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, + s->last_lit)); - if (static_lenb <= opt_lenb) opt_lenb = static_lenb; + if (static_lenb <= opt_lenb) opt_lenb = static_lenb; } else { -// Assert(buf != (char*)0, "lost buf"); - opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ + Assert(buf != (char*)0, "lost buf"); + opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ } - /* If compression failed and this is the first and last block, - * and if the .zip file can be seeked (to rewrite the local header), - * the whole file is transformed into a stored file: - */ -/* #ifdef STORED_FILE_OK */ -/* # ifdef FORCE_STORED_FILE */ -/* if (eof && s->compressed_len == 0L) { // force stored file */ -/* # else */ -/* if (stored_len <= opt_lenb && eof && s->compressed_len==0L && seekable()) { */ -/* # endif */ -/* // Since LIT_BUFSIZE <= 2*WSIZE, the input data must be there: */ -/* if (buf == (charf*)0) error ("block vanished"); */ - -/* copy_block(buf, (unsigned)stored_len, 0); // without header */ -/* s->compressed_len = stored_len << 3; */ -/* s->method = STORED; */ -/* } else */ -/* #endif // STORED_FILE_OK */ - -/* #ifdef FORCE_STORED */ -/* if (buf != (char*)0) { // force stored block */ -/* #else */ +#ifdef FORCE_STORED + if (buf != (char*)0) { /* force stored block */ +#else if (stored_len+4 <= opt_lenb && buf != (char*)0) { /* 4: two words for the lengths */ -/* #endif */ +#endif /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. * Otherwise we can't have processed more than WSIZE input bytes since * the last block flush, because compression would have been * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to * transform a block into a stored block. */ - _tr_stored_block(s, buf, stored_len, eof); - -/* #ifdef FORCE_STATIC */ -/* } else if (static_lenb >= 0) { // force static trees */ -/* #else */ - } else if (static_lenb == opt_lenb) { -/* #endif */ - send_bits(s, (STATIC_TREES<<1)+eof, 3); - compress_block(s, (ct_data *)static_ltree, (ct_data *)static_dtree); + _tr_stored_block(s, buf, stored_len, last); + +#ifdef FORCE_STATIC + } else if (static_lenb >= 0) { /* force static trees */ +#else + } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) { +#endif + send_bits(s, (STATIC_TREES<<1)+last, 3); + compress_block(s, (const ct_data *)static_ltree, + (const ct_data *)static_dtree); +#ifdef DEBUG s->compressed_len += 3 + s->static_len; +#endif } else { - send_bits(s, (DYN_TREES<<1)+eof, 3); + send_bits(s, (DYN_TREES<<1)+last, 3); send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1, max_blindex+1); - compress_block(s, (ct_data *)s->dyn_ltree, (ct_data *)s->dyn_dtree); + compress_block(s, (const ct_data *)s->dyn_ltree, + (const ct_data *)s->dyn_dtree); +#ifdef DEBUG s->compressed_len += 3 + s->opt_len; +#endif } -// Assert (s->compressed_len == s->bits_sent, "bad compressed size"); + Assert (s->compressed_len == s->bits_sent, "bad compressed size"); + /* The above check is made mod 2^32, for files larger than 512 MB + * and uLong implemented on 32 bits. + */ init_block(s); - if (eof) { + if (last) { bi_windup(s); +#ifdef DEBUG s->compressed_len += 7; /* align on byte boundary */ +#endif } -// Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, -// s->compressed_len-7*eof)); + Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, + s->compressed_len-7*last)); +} - return s->compressed_len >> 3; +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +int ZLIB_INTERNAL _tr_tally ( + deflate_state *s, + unsigned dist, /* distance of matched string */ + unsigned lc /* match length-MIN_MATCH or unmatched char (if dist==0) */ +) { + s->d_buf[s->last_lit] = (ush)dist; + s->l_buf[s->last_lit++] = (uch)lc; + if (dist == 0) { + /* lc is the unmatched char */ + s->dyn_ltree[lc].Freq++; + } else { + s->matches++; + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + Assert((ush)dist < (ush)MAX_DIST(s) && + (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && + (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); + + s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++; + s->dyn_dtree[d_code(dist)].Freq++; + } + +#ifdef TRUNCATE_BLOCK + /* Try to guess if it is profitable to stop the current block here */ + if ((s->last_lit & 0x1fff) == 0 && s->level > 2) { + /* Compute an upper bound for the compressed length */ + ulg out_length = (ulg)s->last_lit*8L; + ulg in_length = (ulg)((long)s->strstart - s->block_start); + int dcode; + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += (ulg)s->dyn_dtree[dcode].Freq * + (5L+extra_dbits[dcode]); + } + out_length >>= 3; + Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ", + s->last_lit, in_length, out_length, + 100L - out_length*100L/in_length)); + if (s->matches < s->last_lit/2 && out_length < in_length/2) return 1; + } +#endif + return (s->last_lit == s->lit_bufsize-1); + /* We avoid equality with lit_bufsize because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ } /* =========================================================================== * Send the block data compressed using the given Huffman trees */ -local void compress_block(s, ltree, dtree) - deflate_state *s; - ct_data *ltree; /* literal tree */ - ct_data *dtree; /* distance tree */ -{ +local void compress_block( + deflate_state *s, + const ct_data *ltree, /* literal tree */ + const ct_data *dtree /* distance tree */ +) { unsigned dist; /* distance of matched string */ int lc; /* match length or unmatched char (if dist == 0) */ unsigned lx = 0; /* running index in l_buf */ @@ -3389,7 +4718,7 @@ local void compress_block(s, ltree, dtree) lc = s->l_buf[lx++]; if (dist == 0) { send_code(s, lc, ltree); /* send a literal byte */ -// Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + Tracecv(isgraph(lc), (stderr," '%c' ", lc)); } else { /* Here, lc is the match length - MIN_MATCH */ code = _length_code[lc]; @@ -3401,7 +4730,7 @@ local void compress_block(s, ltree, dtree) } dist--; /* dist is now the match distance - 1 */ code = d_code(dist); -// Assert (code < D_CODES, "bad d_code"); + Assert (code < D_CODES, "bad d_code"); send_code(s, code, dtree); /* send the distance code */ extra = extra_dbits[code]; @@ -3412,30 +4741,54 @@ local void compress_block(s, ltree, dtree) } /* literal or match pair ? */ /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */ -// Assert(s->pending < s->lit_bufsize + 2*lx, "pendingBuf overflow"); + Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx, + "pendingBuf overflow"); } while (lx < s->last_lit); send_code(s, END_BLOCK, ltree); - s->last_eob_len = ltree[END_BLOCK].Len; } /* =========================================================================== - * Set the data type to ASCII or BINARY, using a crude approximation: - * binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise. - * IN assertion: the fields freq of dyn_ltree are set and the total of all - * frequencies does not exceed 64K (to fit in an int on 16 bit machines). + * Check if the data type is TEXT or BINARY, using the following algorithm: + * - TEXT if the two conditions below are satisfied: + * a) There are no non-portable control characters belonging to the + * "black list" (0..6, 14..25, 28..31). + * b) There is at least one printable character belonging to the + * "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). + * - BINARY otherwise. + * - The following partially-portable control characters form a + * "gray list" that is ignored in this detection algorithm: + * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). + * IN assertion: the fields Freq of dyn_ltree are set. */ -local void set_data_type(s) - deflate_state *s; -{ - int n = 0; - unsigned ascii_freq = 0; - unsigned bin_freq = 0; - while (n < 7) bin_freq += s->dyn_ltree[n++].Freq; - while (n < 128) ascii_freq += s->dyn_ltree[n++].Freq; - while (n < LITERALS) bin_freq += s->dyn_ltree[n++].Freq; - s->data_type = (Byte)(bin_freq > (ascii_freq >> 2) ? Z_BINARY : Z_ASCII); +local int detect_data_type( + deflate_state *s +) { + /* black_mask is the bit mask of black-listed bytes + * set bits 0..6, 14..25, and 28..31 + * 0xf3ffc07f = binary 11110011111111111100000001111111 + */ + unsigned long black_mask = 0xf3ffc07fUL; + int n; + + /* Check for non-textual ("black-listed") bytes. */ + for (n = 0; n <= 31; n++, black_mask >>= 1) + if ((black_mask & 1) && (s->dyn_ltree[n].Freq != 0)) + return Z_BINARY; + + /* Check for textual ("white-listed") bytes. */ + if (s->dyn_ltree[9].Freq != 0 || s->dyn_ltree[10].Freq != 0 + || s->dyn_ltree[13].Freq != 0) + return Z_TEXT; + for (n = 32; n < LITERALS; n++) + if (s->dyn_ltree[n].Freq != 0) + return Z_TEXT; + + /* There are no "black-listed" or "white-listed" bytes: + * this stream either is empty or has tolerated ("gray-listed") bytes only. + */ + return Z_BINARY; } /* =========================================================================== @@ -3443,11 +4796,11 @@ local void set_data_type(s) * method would use a table) * IN assertion: 1 <= len <= 15 */ -local unsigned bi_reverse(code, len) - unsigned code; /* the value to invert */ - int len; /* its bit length */ -{ - register unsigned res = 0; +local unsigned bi_reverse( + unsigned code, /* the value to invert */ + int len /* its bit length */ +) { + unsigned res = 0; do { res |= code & 1; code >>= 1, res <<= 1; @@ -3458,9 +4811,9 @@ local unsigned bi_reverse(code, len) /* =========================================================================== * Flush the bit buffer, keeping at most 7 bits in it. */ -local void bi_flush(s) - deflate_state *s; -{ +local void bi_flush( + deflate_state *s +) { if (s->bi_valid == 16) { put_short(s, s->bi_buf); s->bi_buf = 0; @@ -3475,9 +4828,9 @@ local void bi_flush(s) /* =========================================================================== * Flush the bit buffer and align the output on a byte boundary */ -local void bi_windup(s) - deflate_state *s; -{ +local void bi_windup( + deflate_state *s +) { if (s->bi_valid > 8) { put_short(s, s->bi_buf); } else if (s->bi_valid > 0) { @@ -3485,801 +4838,2480 @@ local void bi_windup(s) } s->bi_buf = 0; s->bi_valid = 0; -/* #ifdef DEBUG */ -/* s->bits_sent = (s->bits_sent+7) & ~7; */ -/* #endif */ +#ifdef DEBUG + s->bits_sent = (s->bits_sent+7) & ~7; +#endif } /* =========================================================================== * Copy a stored block, storing first the length and its * one's complement if requested. */ -local void copy_block(s, buf, len, header) - deflate_state *s; - charf *buf; /* the input data */ - unsigned len; /* its length */ - int header; /* true if block header must be written */ -{ +local void copy_block( + deflate_state *s, + charf *buf, /* the input data */ + unsigned len, /* its length */ + int header /* true if block header must be written */ +) { bi_windup(s); /* align on byte boundary */ - s->last_eob_len = 8; /* enough lookahead for inflate */ if (header) { - put_short(s, (ush)len); + put_short(s, (ush)len); put_short(s, (ush)~len); -/* #ifdef DEBUG */ -/* s->bits_sent += 2*16; */ -/* #endif */ +#ifdef DEBUG + s->bits_sent += 2*16; +#endif } -/* #ifdef DEBUG */ -/* s->bits_sent += (ulg)len<<3; */ -/* #endif */ +#ifdef DEBUG + s->bits_sent += (ulg)len<<3; +#endif while (len--) { put_byte(s, *buf++); } } +/* inftrees.h -- header to use inftrees.c + * Copyright (C) 1995-2005, 2010 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ -////////////////////////////////////////////////// - +/* Structure for decoding tables. Each entry provides either the + information needed to do the operation requested by the code that + indexed that table entry, or it provides a pointer to another + table that indexes more bits of the code. op indicates whether + the entry is a pointer to another table, a literal, a length or + distance, an end-of-block, or an invalid code. For a table + pointer, the low four bits of op is the number of index bits of + that table. For a length or distance, the low four bits of op + is the number of extra bits to get after the code. bits is + the number of bits in this code or part of the code to drop off + of the bit buffer. val is the actual byte to output in the case + of a literal, the base length or distance, or the offset from + the current table to the next table. Each entry is four bytes. */ +typedef struct { + unsigned char op; /* operation, extra bits, table bits */ + unsigned char bits; /* bits in this part of the code */ + unsigned short val; /* offset in table or code value */ +} code; + +/* op values as set by inflate_table(): + 00000000 - literal + 0000tttt - table link, tttt != 0 is the number of table index bits + 0001eeee - length or distance, eeee is the number of extra bits + 01100000 - end of block + 01000000 - invalid code + */ +/* Maximum size of the dynamic table. The maximum number of code structures is + 1444, which is the sum of 852 for literal/length codes and 592 for distance + codes. These values were found by exhaustive searches using the program + examples/enough.c found in the zlib distribtution. The arguments to that + program are the number of symbols, the initial root table size, and the + maximum bit length of a code. "enough 286 9 15" for literal/length codes + returns returns 852, and "enough 30 6 15" for distance codes returns 592. + The initial root table size (9 or 6) is found in the fifth argument of the + inflate_table() calls in inflate.c and infback.c. If the root table size is + changed, then these maximum sizes would be need to be recalculated and + updated. */ +#define ENOUGH_LENS 852 +#define ENOUGH_DISTS 592 +#define ENOUGH (ENOUGH_LENS+ENOUGH_DISTS) + +/* Type of code to build for inflate_table() */ +typedef enum { + CODES, + LENS, + DISTS +} codetype; + +int ZLIB_INTERNAL inflate_table OF((codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, + unsigned FAR *bits, unsigned short FAR *work)); /* inftrees.c -- generate Huffman trees for efficient decoding - * Copyright (C) 1995-1998 Mark Adler - * For conditions of distribution and use, see copyright notice in zlib.h + * Copyright (C) 1995-2013 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h */ -//rls#include "zutil.h" -//rls#include "inftrees.h" +// #include "zutil.h" /* In sys-zlib.h (see make-zlib.r) */ +// #include "inftrees.h" /* In sys-zlib.h (see make-zlib.r) */ -#define BUILDFIXED -/* #if !defined(BUILDFIXED) && !defined(STDC) */ -/* # define BUILDFIXED // non ANSI compilers may not accept inffixed.h */ -/* #endif */ +#define MAXBITS 15 -//rls struct internal_state {int dummy;}; /* for buggy compilers */ - -/* simplify the use of the inflate_huft type with some defines */ -#define exop word.what.Exop -#define INFBITS word.what.Bits - - -local int huft_build OF(( - uIntf *, /* code lengths in bits */ - uInt, /* number of codes */ - uInt, /* number of "simple" codes */ - const uIntf *, /* list of base values for non-simple codes */ - const uIntf *, /* list of extra bits for non-simple codes */ - inflate_huft * FAR*,/* result: starting table */ - uIntf *, /* maximum lookup bits (returns actual) */ - inflate_huft *, /* space for trees */ - uInt *, /* hufts used in space */ - uIntf * )); /* space for values */ +const char inflate_copyright[] = + " inflate 1.2.8 Copyright 1995-2013 Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ -/* Tables for deflate from PKZIP's appnote.txt. */ -local const uInt cplens[31] = { /* Copy lengths for literal codes 257..285 */ +/* + Build a set of tables to decode the provided canonical Huffman code. + The code lengths are lens[0..codes-1]. The result starts at *table, + whose indices are 0..2^bits-1. work is a writable array of at least + lens shorts, which is used as a work area. type is the type of code + to be generated, CODES, LENS, or DISTS. On return, zero is success, + -1 is an invalid code, and +1 means that ENOUGH isn't enough. table + on return points to the next available entry's address. bits is the + requested root table index bits, and on return it is the actual root + table index bits. It will differ if the request is greater than the + longest code or if it is less than the shortest code. + */ +int ZLIB_INTERNAL inflate_table( + codetype type, + unsigned short FAR *lens, + unsigned codes, + code FAR * FAR *table, + unsigned FAR *bits, + unsigned short FAR *work +) { + unsigned len; /* a code's length in bits */ + unsigned sym; /* index of code symbols */ + unsigned min, max; /* minimum and maximum code lengths */ + unsigned root; /* number of index bits for root table */ + unsigned curr; /* number of index bits for current table */ + unsigned drop; /* code bits to drop for sub-table */ + int left; /* number of prefix codes available */ + unsigned used; /* code entries in table used */ + unsigned huff; /* Huffman code */ + unsigned incr; /* for incrementing code, index */ + unsigned fill; /* index for replicating entries */ + unsigned low; /* low bits for current root entry */ + unsigned mask; /* mask for low root bits */ + code here; /* table entry for duplication */ + code FAR *next; /* next available space in table */ + const unsigned short FAR *base; /* base value table to use */ + const unsigned short FAR *extra; /* extra bits table to use */ + int end; /* use base and extra for symbol > end */ + unsigned short count[MAXBITS+1]; /* number of codes of each length */ + unsigned short offs[MAXBITS+1]; /* offsets in table for each length */ + static const unsigned short lbase[31] = { /* Length codes 257..285 base */ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; - /* see note #13 above about 258 */ -local const uInt cplext[31] = { /* Extra bits for literal codes 257..285 */ - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, - 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 112, 112}; /* 112==invalid */ -local const uInt cpdist[30] = { /* Copy offsets for distance codes 0..29 */ + static const unsigned short lext[31] = { /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78}; + static const unsigned short dbase[32] = { /* Distance codes 0..29 base */ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, - 8193, 12289, 16385, 24577}; -local const uInt cpdext[30] = { /* Extra bits for distance codes */ - 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, - 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, - 12, 12, 13, 13}; + 8193, 12289, 16385, 24577, 0, 0}; + static const unsigned short dext[32] = { /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64}; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ -/* - Huffman code decoding is performed using a multi-level table lookup. - The fastest way to decode is to simply build a lookup table whose - size is determined by the longest code. However, the time it takes - to build this table can also be a factor if the data being decoded - is not very long. The most common codes are necessarily the - shortest codes, so those codes dominate the decoding time, and hence - the speed. The idea is you can have a shorter table that decodes the - shorter, more probable codes, and then point to subsidiary tables for - the longer codes. The time it costs to decode the longer codes is - then traded against the time it takes to make longer tables. - - This results of this trade are in the variables lbits and dbits - below. lbits is the number of bits the first level table for literal/ - length codes can decode in one step, and dbits is the same thing for - the distance codes. Subsequent tables are also less than or equal to - those sizes. These values may be adjusted either when all of the - codes are shorter than that, in which case the longest code length in - bits is used, or when the shortest code is *longer* than the requested - table size, in which case the length of the shortest code in bits is - used. - - There are two different values for the two tables, since they code a - different number of possibilities each. The literal/length table - codes 286 possible values, or in a flat code, a little over eight - bits. The distance table codes 30 possible values, or a little less - than five bits, flat. The optimum values for speed end up being - about one bit more than those, so lbits is 8+1 and dbits is 5+1. - The optimum values may differ though from machine to machine, and - possibly even between compilers. Your mileage may vary. - */ + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) + count[len] = 0; + for (sym = 0; sym < codes; sym++) + count[lens[sym]]++; + + /* bound code lengths, force root to be within code lengths */ + root = *bits; + for (max = MAXBITS; max >= 1; max--) + if (count[max] != 0) break; + if (root > max) root = max; + if (max == 0) { /* no symbols to code at all */ + here.op = (unsigned char)64; /* invalid code marker */ + here.bits = (unsigned char)1; + here.val = (unsigned short)0; + *(*table)++ = here; /* make a table to force an error */ + *(*table)++ = here; + *bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min < max; min++) + if (count[min] != 0) break; + if (root < min) root = min; + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) return -1; /* over-subscribed */ + } + if (left > 0 && (type == CODES || max != 1)) + return -1; /* incomplete set */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + count[len]; + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) + if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym; + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked for LENS and DIST tables against + the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in + the initial root table size constants. See the comments in inftrees.h + for more information. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + /* set up for code type */ + switch (type) { + case CODES: + base = extra = work; /* dummy value--not used */ + end = 19; + break; + case LENS: + base = lbase; + base -= 257; + extra = lext; + extra -= 257; + end = 256; + break; + default: /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } -/* If BMAX needs to be larger than 16, then h and x[] should be uLong. */ -#define BMAX 15 /* maximum bit length of any code */ - -local int huft_build(b, n, s, d, e, t, m, hp, hn, v) -uIntf *b; /* code lengths in bits (all assumed <= BMAX) */ -uInt n; /* number of codes (assumed <= 288) */ -uInt s; /* number of simple-valued codes (0..s-1) */ -const uIntf *d; /* list of base values for non-simple codes */ -const uIntf *e; /* list of extra bits for non-simple codes */ -inflate_huft * FAR *t; /* result: starting table */ -uIntf *m; /* maximum lookup bits, returns actual */ -inflate_huft *hp; /* space for trees */ -uInt *hn; /* hufts used in space */ -uIntf *v; /* working area: values in order of bit length */ -/* Given a list of code lengths and a maximum table size, make a set of - tables to decode that set of codes. Return Z_OK on success, Z_BUF_ERROR - if the given code set is incomplete (the tables are still built in this - case), Z_DATA_ERROR if the input is invalid (an over-subscribed set of - lengths), or Z_MEM_ERROR if not enough memory. */ -{ + /* initialize state for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = *table; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = (unsigned)(-1); /* trigger new sub-table when len > root */ + used = 1U << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if ((type == LENS && used > ENOUGH_LENS) || + (type == DISTS && used > ENOUGH_DISTS)) + return 1; + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + here.bits = (unsigned char)(len - drop); + if ((int)(work[sym]) < end) { + here.op = (unsigned char)0; + here.val = work[sym]; + } + else if ((int)(work[sym]) > end) { + here.op = (unsigned char)(extra[work[sym]]); + here.val = base[work[sym]]; + } + else { + here.op = (unsigned char)(32 + 64); /* end of block */ + here.val = 0; + } - uInt a; /* counter for codes of length k */ - uInt c[BMAX+1]; /* bit length count table */ - uInt f; /* i repeats in table every f entries */ - int g; /* maximum code length */ - int h; /* table level */ - register uInt i; /* counter, current code */ - register uInt j; /* counter */ - register int k; /* number of bits in current code */ - int l; /* bits per table (returned in m) */ - uInt mask; /* (1 << w) - 1, to avoid cc -O bug on HP */ - register uIntf *p; /* pointer into c[], b[], or v[] */ - inflate_huft *q; /* points to current table */ - struct inflate_huft_s r; /* table entry for structure assignment */ - inflate_huft *u[BMAX]; /* table stack */ - register int w; /* bits before this table == (l * h) */ - uInt x[BMAX+1]; /* bit offsets, then code stack */ - uIntf *xp; /* pointer into x */ - int y; /* number of dummy codes added */ - uInt z; /* number of entries in current table */ - - - /* Generate counts for each bit length */ - p = c; -#define C0 *p++ = 0; -#define C2 C0 C0 C0 C0 -#define C4 C2 C2 C2 C2 - C4 /* clear c[]--assume BMAX+1 is 16 */ - p = b; i = n; - do { - c[*p++]++; /* assume all entries <= BMAX */ - } while (--i); - if (c[0] == n) /* null input--all zero length codes */ - { - *t = (inflate_huft *)Z_NULL; - *m = 0; - return Z_OK; - } + /* replicate for those indices with low len bits equal to huff */ + incr = 1U << (len - drop); + fill = 1U << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + next[(huff >> drop) + fill] = here; + } while (fill != 0); + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + /* go to next symbol, update count, len */ + sym++; + if (--(count[len]) == 0) { + if (len == max) break; + len = lens[work[sym]]; + } - /* Find minimum and maximum length, bound *m by those */ - l = *m; - for (j = 1; j <= BMAX; j++) - if (c[j]) - break; - k = j; /* minimum code length */ - if ((uInt)l < j) - l = j; - for (i = BMAX; i; i--) - if (c[i]) - break; - g = i; /* maximum code length */ - if ((uInt)l > i) - l = i; - *m = l; - - - /* Adjust last length count to fill out codes, if needed */ - for (y = 1 << j; j < i; j++, y <<= 1) - if ((y -= c[j]) < 0) - return Z_DATA_ERROR; - if ((y -= c[i]) < 0) - return Z_DATA_ERROR; - c[i] += y; + /* create new sub-table if needed */ + if (len > root && (huff & mask) != low) { + /* if first time, transition to sub-tables */ + if (drop == 0) + drop = root; + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = (int)(1 << curr); + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) break; + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1U << curr; + if ((type == LENS && used > ENOUGH_LENS) || + (type == DISTS && used > ENOUGH_DISTS)) + return 1; + + /* point entry in root table to sub-table */ + low = huff & mask; + (*table)[low].op = (unsigned char)curr; + (*table)[low].bits = (unsigned char)root; + (*table)[low].val = (unsigned short)(next - *table); + } + } + /* fill in remaining table entry if code is incomplete (guaranteed to have + at most one remaining entry, since if the code is incomplete, the + maximum code length that was allowed to get this far is one bit) */ + if (huff != 0) { + here.op = (unsigned char)64; /* invalid code marker */ + here.bits = (unsigned char)(len - drop); + here.val = (unsigned short)0; + next[huff] = here; + } - /* Generate starting offsets into the value table for each length */ - x[1] = j = 0; - p = c + 1; xp = x + 2; - while (--i) { /* note that i == g from above */ - *xp++ = (j += *p++); - } + /* set return parameters */ + *table += used; + *bits = root; + return 0; +} +/* inffast.h -- header to use inffast.c + * Copyright (C) 1995-2003, 2010 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ - /* Make a table of values in order of bit lengths */ - p = b; i = 0; - do { - if ((j = *p++) != 0) - v[x[j]++] = i; - } while (++i < n); - n = x[g]; /* set n to length of v */ +void ZLIB_INTERNAL inflate_fast OF((z_streamp strm, unsigned start)); +/* inflate.h -- internal inflate state definition + * Copyright (C) 1995-2009 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ - /* Generate the Huffman codes and for each, make the table entries */ - x[0] = i = 0; /* first Huffman code is zero */ - p = v; /* grab values in bit order */ - h = -1; /* no tables yet--level -1 */ - w = -l; /* bits decoded == (l * h) */ - u[0] = (inflate_huft *)Z_NULL; /* just to keep compilers happy */ - q = (inflate_huft *)Z_NULL; /* ditto */ - z = 0; /* ditto */ +/* define NO_GZIP when compiling if you want to disable gzip header and + trailer decoding by inflate(). NO_GZIP would be used to avoid linking in + the crc code when it is not needed. For shared libraries, gzip decoding + should be left enabled. */ +#ifndef NO_GZIP +# define GUNZIP +#endif - /* go through the bit lengths (k already is bits in shortest code) */ - for (; k <= g; k++) - { - a = c[k]; - while (a--) - { - /* here i is the Huffman code of length k bits for value *p */ - /* make tables up to required level */ - while (k > w + l) - { - h++; - w += l; /* previous table always l bits */ - - /* compute minimum size table less than or equal to l bits */ - z = g - w; - z = z > (uInt)l ? l : z; /* table size upper limit */ - if ((f = 1 << (j = k - w)) > a + 1) /* try a k-w bit table */ - { /* too few codes for k-w bit table */ - f -= a + 1; /* deduct codes from patterns left */ - xp = c + k; - if (j < z) - while (++j < z) /* try smaller tables up to z bits */ - { - if ((f <<= 1) <= *++xp) - break; /* enough codes to use up j bits */ - f -= *xp; /* else deduct codes from patterns */ +/* Possible inflate modes between inflate() calls */ +typedef enum { + HEAD, /* i: waiting for magic header */ + FLAGS, /* i: waiting for method and flags (gzip) */ + TIME, /* i: waiting for modification time (gzip) */ + OS, /* i: waiting for extra flags and operating system (gzip) */ + EXLEN, /* i: waiting for extra length (gzip) */ + EXTRA, /* i: waiting for extra bytes (gzip) */ + NAME, /* i: waiting for end of file name (gzip) */ + COMMENT, /* i: waiting for end of comment (gzip) */ + HCRC, /* i: waiting for header crc (gzip) */ + DICTID, /* i: waiting for dictionary check value */ + DICT, /* waiting for inflateSetDictionary() call */ + TYPE, /* i: waiting for type bits, including last-flag bit */ + TYPEDO, /* i: same, but skip check to exit inflate on new block */ + STORED, /* i: waiting for stored size (length and complement) */ + COPY_, /* i/o: same as COPY below, but only first time in */ + COPY, /* i/o: waiting for input or output to copy stored block */ + TABLE, /* i: waiting for dynamic block table lengths */ + LENLENS, /* i: waiting for code length code lengths */ + CODELENS, /* i: waiting for length/lit and distance code lengths */ + LEN_, /* i: same as LEN below, but only first time in */ + LEN, /* i: waiting for length/lit/eob code */ + LENEXT, /* i: waiting for length extra bits */ + DIST, /* i: waiting for distance code */ + DISTEXT, /* i: waiting for distance extra bits */ + MATCH, /* o: waiting for output space to copy string */ + LIT, /* o: waiting for output space to write literal */ + CHECK, /* i: waiting for 32-bit check value */ + LENGTH, /* i: waiting for 32-bit length (gzip) */ + DONE, /* finished check, done -- remain here until reset */ + BAD, /* got a data error -- remain here until reset */ + MEM, /* got an inflate() memory error -- remain here until reset */ + SYNC /* looking for synchronization bytes to restart inflate() */ +} inflate_mode; + +/* + State transitions between above modes - + + (most modes can go to BAD or MEM on error -- not shown for clarity) + + Process header: + HEAD -> (gzip) or (zlib) or (raw) + (gzip) -> FLAGS -> TIME -> OS -> EXLEN -> EXTRA -> NAME -> COMMENT -> + HCRC -> TYPE + (zlib) -> DICTID or TYPE + DICTID -> DICT -> TYPE + (raw) -> TYPEDO + Read deflate blocks: + TYPE -> TYPEDO -> STORED or TABLE or LEN_ or CHECK + STORED -> COPY_ -> COPY -> TYPE + TABLE -> LENLENS -> CODELENS -> LEN_ + LEN_ -> LEN + Read deflate codes in fixed or dynamic block: + LEN -> LENEXT or LIT or TYPE + LENEXT -> DIST -> DISTEXT -> MATCH -> LEN + LIT -> LEN + Process trailer: + CHECK -> LENGTH -> DONE + */ + +/* state maintained between inflate() calls. Approximately 10K bytes. */ +struct inflate_state { + inflate_mode mode; /* current inflate mode */ + int last; /* true if processing last block */ + int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ + int havedict; /* true if dictionary provided */ + int flags; /* gzip header method and flags (0 if zlib) */ + unsigned dmax; /* zlib header max distance (INFLATE_STRICT) */ + unsigned long check; /* protected copy of check value */ + unsigned long total; /* protected copy of output count */ + gz_headerp head; /* where to save gzip header information */ + /* sliding window */ + unsigned wbits; /* log base 2 of requested window size */ + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned wnext; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if needed */ + /* bit accumulator */ + unsigned long hold; /* input bit accumulator */ + unsigned bits; /* number of bits in "in" */ + /* for string and stored block copying */ + unsigned length; /* literal or length of data to copy */ + unsigned offset; /* distance back to copy string from */ + /* for table and code decoding */ + unsigned extra; /* extra bits needed */ + /* fixed and dynamic code tables */ + code const FAR *lencode; /* starting table for length/literal codes */ + code const FAR *distcode; /* starting table for distance codes */ + unsigned lenbits; /* index bits for lencode */ + unsigned distbits; /* index bits for distcode */ + /* dynamic table building */ + unsigned ncode; /* number of code length code lengths */ + unsigned nlen; /* number of length code lengths */ + unsigned ndist; /* number of distance code lengths */ + unsigned have; /* number of code lengths in lens[] */ + code FAR *next; /* next available space in codes[] */ + unsigned short lens[320]; /* temporary storage for code lengths */ + unsigned short work[288]; /* work area for code table building */ + code codes[ENOUGH]; /* space for code tables */ + int sane; /* if false, allow invalid distance too far */ + int back; /* bits back of last unprocessed length/lit */ + unsigned was; /* initial length of match */ +}; +/* inffast.c -- fast decoding + * Copyright (C) 1995-2008, 2010, 2013 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +// #include "zutil.h" /* In sys-zlib.h (see make-zlib.r) */ +// #include "inftrees.h" /* In sys-zlib.h (see make-zlib.r) */ +// #include "inflate.h" /* In sys-zlib.h (see make-zlib.r) */ +// #include "inffast.h" /* In sys-zlib.h (see make-zlib.r) */ + +#ifndef ASMINF + +/* Allow machine dependent optimization for post-increment or pre-increment. + Based on testing to date, + Pre-increment preferred for: + - PowerPC G3 (Adler) + - MIPS R5000 (Randers-Pehrson) + Post-increment preferred for: + - none + No measurable difference: + - Pentium III (Anderson) + - M68060 (Nikl) + */ +#ifdef POSTINC +# define OFF 0 +# define PUP(a) *(a)++ +#else +# define OFF 1 +# define PUP(a) *++(a) +#endif + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state->mode == LEN + strm->avail_in >= 6 + strm->avail_out >= 258 + start >= strm->avail_out + state->bits < 8 + + On return, state->mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm->avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm->avail_out >= 258 for each loop to avoid checking for + output space. + */ +void ZLIB_INTERNAL inflate_fast( + z_streamp strm, + unsigned start /* inflate()'s starting value for strm->avail_out */ +) { + struct inflate_state FAR *state; + z_const unsigned char FAR *in; /* local strm->next_in */ + z_const unsigned char FAR *last; /* have enough input while in < last */ + unsigned char FAR *out; /* local strm->next_out */ + unsigned char FAR *beg; /* inflate()'s initial strm->next_out */ + unsigned char FAR *end; /* while out < end, enough space available */ +#ifdef INFLATE_STRICT + unsigned dmax; /* maximum distance from zlib header */ +#endif + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned wnext; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if wsize != 0 */ + unsigned long hold; /* local strm->hold */ + unsigned bits; /* local strm->bits */ + code const FAR *lcode; /* local strm->lencode */ + code const FAR *dcode; /* local strm->distcode */ + unsigned lmask; /* mask for first level of length codes */ + unsigned dmask; /* mask for first level of distance codes */ + code here; /* retrieved table entry */ + unsigned op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + unsigned len; /* match length, unused bytes */ + unsigned dist; /* match distance */ + unsigned char FAR *from; /* where to copy match from */ + + /* copy state to local variables */ + state = (struct inflate_state FAR *)strm->state; + in = strm->next_in - OFF; + last = in + (strm->avail_in - 5); + out = strm->next_out - OFF; + beg = out - (start - strm->avail_out); + end = out + (strm->avail_out - 257); +#ifdef INFLATE_STRICT + dmax = state->dmax; +#endif + wsize = state->wsize; + whave = state->whave; + wnext = state->wnext; + window = state->window; + hold = state->hold; + bits = state->bits; + lcode = state->lencode; + dcode = state->distcode; + lmask = (1U << state->lenbits) - 1; + dmask = (1U << state->distbits) - 1; + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + do { + if (bits < 15) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + here = lcode[hold & lmask]; + dolen: + op = (unsigned)(here.bits); + hold >>= op; + bits -= op; + op = (unsigned)(here.op); + if (op == 0) { /* literal */ + Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", here.val)); + PUP(out) = (unsigned char)(here.val); + } + else if (op & 16) { /* length base */ + len = (unsigned)(here.val); + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + len += (unsigned)hold & ((1U << op) - 1); + hold >>= op; + bits -= op; + } + Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; } + here = dcode[hold & dmask]; + dodist: + op = (unsigned)(here.bits); + hold >>= op; + bits -= op; + op = (unsigned)(here.op); + if (op & 16) { /* distance base */ + dist = (unsigned)(here.val); + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + } + dist += (unsigned)hold & ((1U << op) - 1); +#ifdef INFLATE_STRICT + if (dist > dmax) { + strm->msg = "invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + hold >>= op; + bits -= op; + Tracevv((stderr, "inflate: distance %u\n", dist)); + op = (unsigned)(out - beg); /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + if (state->sane) { + strm->msg = + "invalid distance too far back"; + state->mode = BAD; + break; + } +#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + if (len <= op - whave) { + do { + PUP(out) = 0; + } while (--len); + continue; + } + len -= op - whave; + do { + PUP(out) = 0; + } while (--op > whave); + if (op == 0) { + from = out - dist; + do { + PUP(out) = PUP(from); + } while (--len); + continue; + } +#endif + } + from = window - OFF; + if (wnext == 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + else if (wnext < op) { /* wrap around window */ + from += wsize + wnext - op; + op -= wnext; + if (op < len) { /* some from end of window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = window - OFF; + if (wnext < len) { /* some from start of window */ + op = wnext; + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + } + else { /* contiguous in window */ + from += wnext - op; + if (op < len) { /* some from window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + while (len > 2) { + PUP(out) = PUP(from); + PUP(out) = PUP(from); + PUP(out) = PUP(from); + len -= 3; + } + if (len) { + PUP(out) = PUP(from); + if (len > 1) + PUP(out) = PUP(from); + } + } + else { + from = out - dist; /* copy direct from output */ + do { /* minimum length is three */ + PUP(out) = PUP(from); + PUP(out) = PUP(from); + PUP(out) = PUP(from); + len -= 3; + } while (len > 2); + if (len) { + PUP(out) = PUP(from); + if (len > 1) + PUP(out) = PUP(from); + } + } + } + else if ((op & 64) == 0) { /* 2nd level distance code */ + here = dcode[here.val + (hold & ((1U << op) - 1))]; + goto dodist; + } + else { + strm->msg = "invalid distance code"; + state->mode = BAD; + break; + } + } + else if ((op & 64) == 0) { /* 2nd level length code */ + here = lcode[here.val + (hold & ((1U << op) - 1))]; + goto dolen; + } + else if (op & 32) { /* end-of-block */ + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; } - z = 1 << j; /* table entries for j-bit table */ + else { + strm->msg = "invalid literal/length code"; + state->mode = BAD; + break; + } + } while (in < last && out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + in -= len; + bits -= len << 3; + hold &= (1U << bits) - 1; + + /* update state and return */ + strm->next_in = in + OFF; + strm->next_out = out + OFF; + strm->avail_in = (unsigned)(in < last ? 5 + (last - in) : 5 - (in - last)); + strm->avail_out = (unsigned)(out < end ? + 257 + (end - out) : 257 - (out - end)); + state->hold = hold; + state->bits = bits; + return; +} - /* allocate new table */ - if (*hn + z > MANY) /* (note: doesn't matter for fixed) */ - return Z_MEM_ERROR; /* not enough memory */ - u[h] = q = hp + *hn; - *hn += z; +/* + inflate_fast() speedups that turned out slower (on a PowerPC G3 750CXe): + - Using bit fields for code structure + - Different op definition to avoid & for extra bits (do & for table bits) + - Three separate decoding do-loops for direct, window, and wnext == 0 + - Special case for distance > 1 copies to do overlapped load and store copy + - Explicit branch predictions (based on measured branch probabilities) + - Deferring match copy and interspersed it with decoding subsequent codes + - Swapping literal/length else + - Swapping window/direct else + - Larger unrolled copy loops (three is about right) + - Moving len -= 3 statement into middle of loop + */ - /* connect to last table, if there is one */ - if (h) - { - x[h] = i; /* save pattern for backing up */ - r.INFBITS = (Byte)l; /* bits to dump before this table */ - r.exop = (Byte)j; /* bits in this table */ - j = i >> (w - l); - r.base = (uInt)(q - u[h-1] - j); /* offset to this table */ - u[h-1][j] = r; /* connect to last table */ - } - else - *t = q; /* first table is returned result */ - } - - /* set up table entry in r */ - r.INFBITS = (Byte)(k - w); - if (p >= v + n) - r.exop = 128 + 64; /* out of values--invalid code */ - else if (*p < s) - { - r.exop = (Byte)(*p < 256 ? 0 : 32 + 64); /* 256 is end-of-block */ - r.base = *p++; /* simple code is just the value */ - } - else - { - r.exop = (Byte)(e[*p - s] + 16 + 64);/* non-simple--look up in lists */ - r.base = d[*p++ - s]; - } - - /* fill code-like entries with r */ - f = 1 << (k - w); - for (j = i >> w; j < z; j += f) - q[j] = r; - - /* backwards increment the k-bit code i */ - for (j = 1 << (k - 1); i & j; j >>= 1) - i ^= j; - i ^= j; - - /* backup over finished tables */ - mask = (1 << w) - 1; /* needed on HP, cc -O bug */ - while ((i & mask) != x[h]) - { - h--; /* don't need to update q */ - w -= l; - mask = (1 << w) - 1; - } - } - } +#endif /* !ASMINF */ +/* inflate.c -- zlib decompression + * Copyright (C) 1995-2012 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * Change history: + * + * 1.2.beta0 24 Nov 2002 + * - First version -- complete rewrite of inflate to simplify code, avoid + * creation of window when not needed, minimize use of window when it is + * needed, make inffast.c even faster, implement gzip decoding, and to + * improve code readability and style over the previous zlib inflate code + * + * 1.2.beta1 25 Nov 2002 + * - Use pointers for available input and output checking in inffast.c + * - Remove input and output counters in inffast.c + * - Change inffast.c entry and loop from avail_in >= 7 to >= 6 + * - Remove unnecessary second byte pull from length extra in inffast.c + * - Unroll direct copy to three copies per loop in inffast.c + * + * 1.2.beta2 4 Dec 2002 + * - Change external routine names to reduce potential conflicts + * - Correct filename to inffixed.h for fixed tables in inflate.c + * - Make hbuf[] unsigned char to match parameter type in inflate.c + * - Change strm->next_out[-state->offset] to *(strm->next_out - state->offset) + * to avoid negation problem on Alphas (64 bit) in inflate.c + * + * 1.2.beta3 22 Dec 2002 + * - Add comments on state->bits assertion in inffast.c + * - Add comments on op field in inftrees.h + * - Fix bug in reuse of allocated window after inflateReset() + * - Remove bit fields--back to byte structure for speed + * - Remove distance extra == 0 check in inflate_fast()--only helps for lengths + * - Change post-increments to pre-increments in inflate_fast(), PPC biased? + * - Add compile time option, POSTINC, to use post-increments instead (Intel?) + * - Make MATCH copy in inflate() much faster for when inflate_fast() not used + * - Use local copies of stream next and avail values, as well as local bit + * buffer and bit count in inflate()--for speed when inflate_fast() not used + * + * 1.2.beta4 1 Jan 2003 + * - Split ptr - 257 statements in inflate_table() to avoid compiler warnings + * - Move a comment on output buffer sizes from inffast.c to inflate.c + * - Add comments in inffast.c to introduce the inflate_fast() routine + * - Rearrange window copies in inflate_fast() for speed and simplification + * - Unroll last copy for window match in inflate_fast() + * - Use local copies of window variables in inflate_fast() for speed + * - Pull out common wnext == 0 case for speed in inflate_fast() + * - Make op and len in inflate_fast() unsigned for consistency + * - Add FAR to lcode and dcode declarations in inflate_fast() + * - Simplified bad distance check in inflate_fast() + * - Added inflateBackInit(), inflateBack(), and inflateBackEnd() in new + * source file infback.c to provide a call-back interface to inflate for + * programs like gzip and unzip -- uses window as output buffer to avoid + * window copying + * + * 1.2.beta5 1 Jan 2003 + * - Improved inflateBack() interface to allow the caller to provide initial + * input in strm. + * - Fixed stored blocks bug in inflateBack() + * + * 1.2.beta6 4 Jan 2003 + * - Added comments in inffast.c on effectiveness of POSTINC + * - Typecasting all around to reduce compiler warnings + * - Changed loops from while (1) or do {} while (1) to for (;;), again to + * make compilers happy + * - Changed type of window in inflateBackInit() to unsigned char * + * + * 1.2.beta7 27 Jan 2003 + * - Changed many types to unsigned or unsigned short to avoid warnings + * - Added inflateCopy() function + * + * 1.2.0 9 Mar 2003 + * - Changed inflateBack() interface to provide separate opaque descriptors + * for the in() and out() functions + * - Changed inflateBack() argument and in_func typedef to swap the length + * and buffer address return values for the input function + * - Check next_in and next_out for Z_NULL on entry to inflate() + * + * The history for versions after 1.2.0 are in ChangeLog in zlib distribution. + */ + +// #include "zutil.h" /* In sys-zlib.h (see make-zlib.r) */ +// #include "inftrees.h" /* In sys-zlib.h (see make-zlib.r) */ +// #include "inflate.h" /* In sys-zlib.h (see make-zlib.r) */ +// #include "inffast.h" /* In sys-zlib.h (see make-zlib.r) */ + +#ifdef MAKEFIXED +# ifndef BUILDFIXED +# define BUILDFIXED +# endif +#endif +/* function prototypes */ +local void fixedtables OF((struct inflate_state FAR *state)); +local int updatewindow OF((z_streamp strm, const unsigned char FAR *end, + unsigned copy)); +#ifdef BUILDFIXED + void makefixed OF((void)); +#endif +local unsigned syncsearch OF((unsigned FAR *have, const unsigned char FAR *buf, + unsigned len)); - /* Return Z_BUF_ERROR if we were given an incomplete table */ - return y != 0 && g != 1 ? Z_BUF_ERROR : Z_OK; +int ZEXPORT inflateResetKeep( + z_streamp strm +) { + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + strm->total_in = strm->total_out = state->total = 0; + strm->msg = Z_NULL; + if (state->wrap) /* to support ill-conceived Java test suite */ + strm->adler = state->wrap & 1; + state->mode = HEAD; + state->last = 0; + state->havedict = 0; + state->dmax = 32768U; + state->head = Z_NULL; + state->hold = 0; + state->bits = 0; + state->lencode = state->distcode = state->next = state->codes; + state->sane = 1; + state->back = -1; + Tracev((stderr, "inflate: reset\n")); + return Z_OK; } +int ZEXPORT inflateReset( + z_streamp strm +) { + struct inflate_state FAR *state; -int inflate_trees_bits(c, bb, tb, hp, z) -uIntf *c; /* 19 code lengths */ -uIntf *bb; /* bits tree desired/actual depth */ -inflate_huft * FAR *tb; /* bits tree result */ -inflate_huft *hp; /* space for trees */ -z_streamp z; /* for messages */ -{ - int r; - uInt hn = 0; /* hufts used in space */ - uIntf *v; /* work area for huft_build */ - - if ((v = (uIntf*)ZALLOC(z, 19, sizeof(uInt))) == Z_NULL) - return Z_MEM_ERROR; - r = huft_build(c, 19, 19, (uIntf*)Z_NULL, (uIntf*)Z_NULL, - tb, bb, hp, &hn, v); - if (r == Z_DATA_ERROR){ -// z->msg = (char*)"oversubscribed dynamic bit lengths tree"; - } - - else if (r == Z_BUF_ERROR || *bb == 0) - { -// z->msg = (char*)"incomplete dynamic bit lengths tree"; - r = Z_DATA_ERROR; - } - ZFREE(z, v); - return r; + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + state->wsize = 0; + state->whave = 0; + state->wnext = 0; + return inflateResetKeep(strm); } +int ZEXPORT inflateReset2( + z_streamp strm, + int windowBits +) { + int wrap; + struct inflate_state FAR *state; -int inflate_trees_dynamic(nl, nd, c, bl, bd, tl, td, hp, z) -uInt nl; /* number of literal/length codes */ -uInt nd; /* number of distance codes */ -uIntf *c; /* that many (total) code lengths */ -uIntf *bl; /* literal desired/actual bit depth */ -uIntf *bd; /* distance desired/actual bit depth */ -inflate_huft * FAR *tl; /* literal/length tree result */ -inflate_huft * FAR *td; /* distance tree result */ -inflate_huft *hp; /* space for trees */ -z_streamp z; /* for messages */ -{ - int r; - uInt hn = 0; /* hufts used in space */ - uIntf *v; /* work area for huft_build */ + /* get the state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; - /* allocate work area */ - if ((v = (uIntf*)ZALLOC(z, 288, sizeof(uInt))) == Z_NULL) - return Z_MEM_ERROR; + /* extract wrap request from windowBits parameter */ + if (windowBits < 0) { + wrap = 0; + windowBits = -windowBits; + } + else { + wrap = (windowBits >> 4) + 1; +#ifdef GUNZIP + if (windowBits < 48) + windowBits &= 15; +#endif + } - /* build literal/length tree */ - r = huft_build(c, nl, 257, cplens, cplext, tl, bl, hp, &hn, v); - if (r != Z_OK || *bl == 0) - { - if (r == Z_DATA_ERROR){ -// z->msg = (char*)"oversubscribed literal/length tree"; - } - - else if (r != Z_MEM_ERROR) - { -// z->msg = (char*)"incomplete literal/length tree"; - r = Z_DATA_ERROR; + /* set number of window bits, free window if different */ + if (windowBits && (windowBits < 8 || windowBits > 15)) + return Z_STREAM_ERROR; + if (state->window != Z_NULL && state->wbits != (unsigned)windowBits) { + ZFREE(strm, state->window); + state->window = Z_NULL; } - ZFREE(z, v); - return r; - } - /* build distance tree */ - r = huft_build(c + nl, nd, 0, cpdist, cpdext, td, bd, hp, &hn, v); - if (r != Z_OK || (*bd == 0 && nl > 257)) - { - if (r == Z_DATA_ERROR){ -// z->msg = (char*)"oversubscribed distance tree"; - } - - else if (r == Z_BUF_ERROR) { -#ifdef PKZIP_BUG_WORKAROUND - r = Z_OK; + /* update state and reset the rest of it */ + state->wrap = wrap; + state->wbits = (unsigned)windowBits; + return inflateReset(strm); +} + +int ZEXPORT inflateInit2_( + z_streamp strm, + int windowBits, + const char *version, + int stream_size +) { + int ret; + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL) return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; +#endif } + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; #else -// z->msg = (char*)"incomplete distance tree"; - r = Z_DATA_ERROR; + strm->zfree = zcfree; +#endif + state = (struct inflate_state FAR *) + ZALLOC(strm, 1, sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (struct internal_state FAR *)state; + state->window = Z_NULL; + ret = inflateReset2(strm, windowBits); + if (ret != Z_OK) { + ZFREE(strm, state); + strm->state = Z_NULL; + } + return ret; +} + +int ZEXPORT inflateInit_( + z_streamp strm, + const char *version, + int stream_size +) { + return inflateInit2_(strm, DEF_WBITS, version, stream_size); +} + +int ZEXPORT inflatePrime( + z_streamp strm, + int bits, + int value +) { + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (bits < 0) { + state->hold = 0; + state->bits = 0; + return Z_OK; } - else if (r != Z_MEM_ERROR) + if (bits > 16 || state->bits + bits > 32) return Z_STREAM_ERROR; + value &= (1L << bits) - 1; + state->hold += value << state->bits; + state->bits += bits; + return Z_OK; +} + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +local void fixedtables( + struct inflate_state FAR *state +) { +#ifdef BUILDFIXED + static int virgin = 1; + static code *lenfix, *distfix; + static code fixed[544]; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + unsigned sym, bits; + static code *next; + + /* literal/length table */ + sym = 0; + while (sym < 144) state->lens[sym++] = 8; + while (sym < 256) state->lens[sym++] = 9; + while (sym < 280) state->lens[sym++] = 7; + while (sym < 288) state->lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work); + + /* distance table */ + sym = 0; + while (sym < 32) state->lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work); + + /* do this just once */ + virgin = 0; + } +#else /* !BUILDFIXED */ + /* inffixed.h -- table for decoding fixed codes + * Generated automatically by makefixed(). + */ + + /* WARNING: this file should *not* be used by applications. + It is part of the implementation of this library and is + subject to change. Applications should only use zlib.h. + */ + + static const code lenfix[512] = { + {96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48}, + {0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128}, + {0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59}, + {0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176}, + {0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20}, + {21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100}, + {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8}, + {0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216}, + {18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76}, + {0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114}, + {0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2}, + {0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148}, + {20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42}, + {0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86}, + {0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15}, + {0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236}, + {16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62}, + {0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142}, + {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31}, + {0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162}, + {0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25}, + {0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105}, + {0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4}, + {0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202}, + {17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69}, + {0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125}, + {0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13}, + {0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195}, + {19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35}, + {0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91}, + {0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19}, + {0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246}, + {16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55}, + {0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135}, + {0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99}, + {0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190}, + {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16}, + {20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96}, + {0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6}, + {0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209}, + {17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72}, + {0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116}, + {0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4}, + {0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153}, + {20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44}, + {0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82}, + {0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11}, + {0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229}, + {16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58}, + {0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138}, + {0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51}, + {0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173}, + {0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30}, + {0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110}, + {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0}, + {0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195}, + {16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65}, + {0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121}, + {0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9}, + {0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258}, + {19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37}, + {0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93}, + {0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23}, + {0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251}, + {16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51}, + {0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131}, + {0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67}, + {0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183}, + {0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23}, + {64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103}, + {0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9}, + {0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223}, + {18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79}, + {0,9,255} + }; + + static const code distfix[32] = { + {16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025}, + {21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193}, + {18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385}, + {19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577}, + {16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073}, + {22,5,193},{64,5,0} + }; +#endif /* BUILDFIXED */ + state->lencode = lenfix; + state->lenbits = 9; + state->distcode = distfix; + state->distbits = 5; +} + +#ifdef MAKEFIXED +// #include // !!! No in Ren-C release builds + +/* + Write out the inffixed.h that is #include'd above. Defining MAKEFIXED also + defines BUILDFIXED, so the tables are built on the fly. makefixed() writes + those tables to stdout, which would be piped to inffixed.h. A small program + can simply call makefixed to do this: + + void makefixed(void); + + int main(void) { -// z->msg = (char*)"empty distance tree with lengths"; - r = Z_DATA_ERROR; + makefixed(); + return 0; } - ZFREE(z, v); - return r; -#endif - } - /* done */ - ZFREE(z, v); - return Z_OK; + Then that can be linked with zlib built with MAKEFIXED defined and run: + + a.out > inffixed.h + */ +void makefixed() +{ + unsigned low, size; + struct inflate_state state; + + fixedtables(&state); + puts(" /* inffixed.h -- table for decoding fixed codes"); + puts(" * Generated automatically by makefixed()."); + puts(" */"); + puts(""); + puts(" /* WARNING: this file should *not* be used by applications."); + puts(" It is part of the implementation of this library and is"); + puts(" subject to change. Applications should only use zlib.h."); + puts(" */"); + puts(""); + size = 1U << 9; + printf(" static const code lenfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 7) == 0) printf("\n "); + printf("{%u,%u,%d}", (low & 127) == 99 ? 64 : state.lencode[low].op, + state.lencode[low].bits, state.lencode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); + size = 1U << 5; + printf("\n static const code distfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 6) == 0) printf("\n "); + printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits, + state.distcode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); } +#endif /* MAKEFIXED */ +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +local int updatewindow( + z_streamp strm, + const Bytef *end, + unsigned copy +) { + struct inflate_state FAR *state; + unsigned dist; + + state = (struct inflate_state FAR *)strm->state; + + /* if it hasn't been done already, allocate space for the window */ + if (state->window == Z_NULL) { + state->window = (unsigned char FAR *) + ZALLOC(strm, 1U << state->wbits, + sizeof(unsigned char)); + if (state->window == Z_NULL) return 1; + } -/* build fixed tables only once--keep them here */ -#ifdef BUILDFIXED -local int fixed_built = 0; -#define FIXEDH 544 /* number of hufts used by fixed tables */ -local inflate_huft fixed_mem[FIXEDH]; -local uInt fixed_bl; -local uInt fixed_bd; -local inflate_huft *fixed_tl; -local inflate_huft *fixed_td; + /* if window not in use yet, initialize */ + if (state->wsize == 0) { + state->wsize = 1U << state->wbits; + state->wnext = 0; + state->whave = 0; + } + + /* copy state->wsize or less output bytes into the circular window */ + if (copy >= state->wsize) { + zmemcpy(state->window, end - state->wsize, state->wsize); + state->wnext = 0; + state->whave = state->wsize; + } + else { + dist = state->wsize - state->wnext; + if (dist > copy) dist = copy; + zmemcpy(state->window + state->wnext, end - copy, dist); + copy -= dist; + if (copy) { + zmemcpy(state->window, end - copy, copy); + state->wnext = copy; + state->whave = state->wsize; + } + else { + state->wnext += dist; + if (state->wnext == state->wsize) state->wnext = 0; + if (state->whave < state->wsize) state->whave += dist; + } + } + return 0; +} + +/* Macros for inflate(): */ + +/* check function to use adler32() for zlib or crc32() for gzip */ +#ifdef GUNZIP +# define UPDATE(check, buf, len) \ + (state->flags ? crc32(check, buf, len) : adler32(check, buf, len)) #else -//#include "inffixed.h" +# define UPDATE(check, buf, len) adler32(check, buf, len) #endif - -int inflate_trees_fixed(bl, bd, tl, td, z) -uIntf *bl; /* literal desired/actual bit depth */ -uIntf *bd; /* distance desired/actual bit depth */ -inflate_huft * FAR *tl; /* literal/length tree result */ -inflate_huft * FAR *td; /* distance tree result */ -z_streamp z; /* for memory allocation */ -{ -#ifdef BUILDFIXED - /* build fixed tables if not already */ - if (!fixed_built) - { - int k; /* temporary variable */ - uInt f = 0; /* number of hufts used in fixed_mem */ - uIntf *c; /* length list for huft_build */ - uIntf *v; /* work area for huft_build */ - - /* allocate memory */ - if ((c = (uIntf*)ZALLOC(z, 288, sizeof(uInt))) == Z_NULL) - return Z_MEM_ERROR; - if ((v = (uIntf*)ZALLOC(z, 288, sizeof(uInt))) == Z_NULL) - { - ZFREE(z, c); - return Z_MEM_ERROR; - } - - /* literal table */ - for (k = 0; k < 144; k++) - c[k] = 8; - for (; k < 256; k++) - c[k] = 9; - for (; k < 280; k++) - c[k] = 7; - for (; k < 288; k++) - c[k] = 8; - fixed_bl = 9; - huft_build(c, 288, 257, cplens, cplext, &fixed_tl, &fixed_bl, - fixed_mem, &f, v); - - /* distance table */ - for (k = 0; k < 30; k++) - c[k] = 5; - fixed_bd = 5; - huft_build(c, 30, 0, cpdist, cpdext, &fixed_td, &fixed_bd, - fixed_mem, &f, v); - - /* done */ - ZFREE(z, v); - ZFREE(z, c); - fixed_built = 1; - } +/* check macros for header crc */ +#ifdef GUNZIP +# define CRC2(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + check = crc32(check, hbuf, 2); \ + } while (0) + +# define CRC4(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + hbuf[2] = (unsigned char)((word) >> 16); \ + hbuf[3] = (unsigned char)((word) >> 24); \ + check = crc32(check, hbuf, 4); \ + } while (0) #endif - *bl = fixed_bl; - *bd = fixed_bd; - *tl = fixed_tl; - *td = fixed_td; - return Z_OK; -} -///////////////////////////////////////////////////////////////////// +/* Load registers with state in inflate() for speed */ +#define LOAD() \ + do { \ + put = strm->next_out; \ + left = strm->avail_out; \ + next = strm->next_in; \ + have = strm->avail_in; \ + hold = state->hold; \ + bits = state->bits; \ + } while (0) + +/* Restore state from registers in inflate() */ +#define RESTORE() \ + do { \ + strm->next_out = put; \ + strm->avail_out = left; \ + strm->next_in = next; \ + strm->avail_in = have; \ + state->hold = hold; \ + state->bits = bits; \ + } while (0) + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflate() + if there is no input available. */ +#define PULLBYTE() \ + do { \ + if (have == 0) goto inf_leave; \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflate(). */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n < 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* + inflate() uses a state machine to process as much input data and generate as + much output data as possible before returning. The state machine is + structured roughly as follows: + + for (;;) switch (state) { + ... + case STATEn: + if (not enough input data or output space to make progress) + return; + ... make progress ... + state = STATEm; + break; + ... + } -/* inflate.c -- zlib interface to inflate modules - * Copyright (C) 1995-1998 Mark Adler - * For conditions of distribution and use, see copyright notice in zlib.h + so when inflate() is called again, the same case is attempted again, and + if the appropriate resources are provided, the machine proceeds to the + next state. The NEEDBITS() macro is usually the way the state evaluates + whether it can proceed or should return. NEEDBITS() does the return if + the requested bits are not available. The typical use of the BITS macros + is: + + NEEDBITS(n); + ... do something with BITS(n) ... + DROPBITS(n); + + where NEEDBITS(n) either returns from inflate() if there isn't enough + input left to load n bits into the accumulator, or it continues. BITS(n) + gives the low n bits in the accumulator. When done, DROPBITS(n) drops + the low n bits off the accumulator. INITBITS() clears the accumulator + and sets the number of available bits to zero. BYTEBITS() discards just + enough bits to put the accumulator on a byte boundary. After BYTEBITS() + and a NEEDBITS(8), then BITS(8) would return the next byte in the stream. + + NEEDBITS(n) uses PULLBYTE() to get an available byte of input, or to return + if there is no input available. The decoding of variable length codes uses + PULLBYTE() directly in order to pull just enough bytes to decode the next + code, and no more. + + Some states loop until they get enough input, making sure that enough + state information is maintained to continue the loop where it left off + if NEEDBITS() returns in the loop. For example, want, need, and keep + would all have to actually be part of the saved state in case NEEDBITS() + returns: + + case STATEw: + while (want < need) { + NEEDBITS(n); + keep[want++] = BITS(n); + DROPBITS(n); + } + state = STATEx; + case STATEx: + + As shown above, if the next state is also the next case, then the break + is omitted. + + A state may also return if there is not enough output space available to + complete that state. Those states are copying stored data, writing a + literal byte, and copying a matching string. + + When returning, a "goto inf_leave" is used to update the total counters, + update the check value, and determine whether any progress has been made + during that inflate() call in order to return the proper return code. + Progress is defined as a change in either strm->avail_in or strm->avail_out. + When there is a window, goto inf_leave will update the window with the last + output written. If a goto inf_leave occurs in the middle of decompression + and there is no window currently, goto inf_leave will create one and copy + output to the window for the next call of inflate(). + + In this implementation, the flush parameter of inflate() only affects the + return code (per zlib.h). inflate() always writes as much as possible to + strm->next_out, given the space available and the provided input--the effect + documented in zlib.h of Z_SYNC_FLUSH. Furthermore, inflate() always defers + the allocation of and copying into a sliding window until necessary, which + provides the effect documented in zlib.h for Z_FINISH when the entire input + stream available. So the only thing the flush parameter actually does is: + when flush is set to Z_FINISH, inflate() cannot return Z_OK. Instead it + will return Z_BUF_ERROR if it has not reached the end of the stream. */ -//rls#include "zutil.h" -//rls#include "infblock.h" +int ZEXPORT inflate( + z_streamp strm, + int flush +) { + struct inflate_state FAR *state; + z_const unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have, left; /* available input and output */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned in, out; /* save starting available input and output */ + unsigned copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code here; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ +#ifdef GUNZIP + unsigned char hbuf[4]; /* buffer for gzip header crc calculation */ +#endif + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; -//rlsstruct inflate_blocks_state {int dummy;}; /* for buggy compilers */ + if (strm == Z_NULL || strm->state == Z_NULL || strm->next_out == Z_NULL || + (strm->next_in == Z_NULL && strm->avail_in != 0)) + return Z_STREAM_ERROR; -typedef enum { - METHOD, /* waiting for method byte */ - FLAG, /* waiting for flag byte */ - DICT4, /* four dictionary check bytes to go */ - DICT3, /* three dictionary check bytes to go */ - DICT2, /* two dictionary check bytes to go */ - DICT1, /* one dictionary check byte to go */ - DICT0, /* waiting for inflateSetDictionary */ - BLOCKS, /* decompressing blocks */ - CHECK4, /* four check bytes to go */ - CHECK3, /* three check bytes to go */ - CHECK2, /* two check bytes to go */ - CHECK1, /* one check byte to go */ - IM_DONE, /* finished check, done */ - IM_BAD} /* got an error--stay here */ -inflate_mode; - -/* inflate private state */ -struct inflate_internal_state { - - /* mode */ - inflate_mode mode; /* current inflate mode */ - - /* mode dependent information */ - union { - uInt method; /* if FLAGS, method byte */ - struct { - uLong was; /* computed check value */ - uLong need; /* stream check value */ - } check; /* if CHECK, check values to compare */ - uInt marker; /* if IM_BAD, inflateSync's marker bytes count */ - } sub; /* submode */ - - /* mode independent information */ - int nowrap; /* flag for no wrapper */ - uInt wbits; /* log2(window size) (8..15, defaults to 15) */ - inflate_blocks_statef - *blocks; /* current inflate_blocks state */ + state = (struct inflate_state FAR *)strm->state; + if (state->mode == TYPE) state->mode = TYPEDO; /* skip check */ + LOAD(); + in = have; + out = left; + ret = Z_OK; + for (;;) + switch (state->mode) { + case HEAD: + if (state->wrap == 0) { + state->mode = TYPEDO; + break; + } + NEEDBITS(16); +#ifdef GUNZIP + if ((state->wrap & 2) && hold == 0x8b1f) { /* gzip header */ + state->check = crc32(0L, Z_NULL, 0); + CRC2(state->check, hold); + INITBITS(); + state->mode = FLAGS; + break; + } + state->flags = 0; /* expect zlib header */ + if (state->head != Z_NULL) + state->head->done = -1; + if (!(state->wrap & 1) || /* check if zlib header allowed */ +#else + if ( +#endif + ((BITS(8) << 8) + (hold >> 8)) % 31) { + strm->msg = "incorrect header check"; + state->mode = BAD; + break; + } + if (BITS(4) != Z_DEFLATED) { + strm->msg = "unknown compression method"; + state->mode = BAD; + break; + } + DROPBITS(4); + len = BITS(4) + 8; + if (state->wbits == 0) + state->wbits = len; + else if (len > state->wbits) { + strm->msg = "invalid window size"; + state->mode = BAD; + break; + } + state->dmax = 1U << len; + Tracev((stderr, "inflate: zlib header ok\n")); + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = hold & 0x200 ? DICTID : TYPE; + INITBITS(); + break; +#ifdef GUNZIP + case FLAGS: + NEEDBITS(16); + state->flags = (int)(hold); + if ((state->flags & 0xff) != Z_DEFLATED) { + strm->msg = "unknown compression method"; + state->mode = BAD; + break; + } + if (state->flags & 0xe000) { + strm->msg = "unknown header flags set"; + state->mode = BAD; + break; + } + if (state->head != Z_NULL) + state->head->text = (int)((hold >> 8) & 1); + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + state->mode = TIME; + case TIME: + NEEDBITS(32); + if (state->head != Z_NULL) + state->head->time = hold; + if (state->flags & 0x0200) CRC4(state->check, hold); + INITBITS(); + state->mode = OS; + case OS: + NEEDBITS(16); + if (state->head != Z_NULL) { + state->head->xflags = (int)(hold & 0xff); + state->head->os = (int)(hold >> 8); + } + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + state->mode = EXLEN; + case EXLEN: + if (state->flags & 0x0400) { + NEEDBITS(16); + state->length = (unsigned)(hold); + if (state->head != Z_NULL) + state->head->extra_len = (unsigned)hold; + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + } + else if (state->head != Z_NULL) + state->head->extra = Z_NULL; + state->mode = EXTRA; + case EXTRA: + if (state->flags & 0x0400) { + copy = state->length; + if (copy > have) copy = have; + if (copy) { + if (state->head != Z_NULL && + state->head->extra != Z_NULL) { + len = state->head->extra_len - state->length; + zmemcpy(state->head->extra + len, next, + len + copy > state->head->extra_max ? + state->head->extra_max - len : copy); + } + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + state->length -= copy; + } + if (state->length) goto inf_leave; + } + state->length = 0; + state->mode = NAME; + case NAME: + if (state->flags & 0x0800) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->name != Z_NULL && + state->length < state->head->name_max) + state->head->name[state->length++] = len; + } while (len && copy < have); + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->name = Z_NULL; + state->length = 0; + state->mode = COMMENT; + case COMMENT: + if (state->flags & 0x1000) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->comment != Z_NULL && + state->length < state->head->comm_max) + state->head->comment[state->length++] = len; + } while (len && copy < have); + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->comment = Z_NULL; + state->mode = HCRC; + case HCRC: + if (state->flags & 0x0200) { + NEEDBITS(16); + if (hold != (state->check & 0xffff)) { + strm->msg = "header crc mismatch"; + state->mode = BAD; + break; + } + INITBITS(); + } + if (state->head != Z_NULL) { + state->head->hcrc = (int)((state->flags >> 9) & 1); + state->head->done = 1; + } + strm->adler = state->check = crc32(0L, Z_NULL, 0); + state->mode = TYPE; + break; +#endif + case DICTID: + NEEDBITS(32); + strm->adler = state->check = ZSWAP32(hold); + INITBITS(); + state->mode = DICT; + case DICT: + if (state->havedict == 0) { + RESTORE(); + return Z_NEED_DICT; + } + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = TYPE; + case TYPE: + if (flush == Z_BLOCK || flush == Z_TREES) goto inf_leave; + case TYPEDO: + if (state->last) { + BYTEBITS(); + state->mode = CHECK; + break; + } + NEEDBITS(3); + state->last = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + state->last ? " (last)" : "")); + state->mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + Tracev((stderr, "inflate: fixed codes block%s\n", + state->last ? " (last)" : "")); + state->mode = LEN_; /* decode codes */ + if (flush == Z_TREES) { + DROPBITS(2); + goto inf_leave; + } + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + state->last ? " (last)" : "")); + state->mode = TABLE; + break; + case 3: + strm->msg = "invalid block type"; + state->mode = BAD; + } + DROPBITS(2); + break; + case STORED: + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = "invalid stored block lengths"; + state->mode = BAD; + break; + } + state->length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %u\n", + state->length)); + INITBITS(); + state->mode = COPY_; + if (flush == Z_TREES) goto inf_leave; + case COPY_: + state->mode = COPY; + case COPY: + copy = state->length; + if (copy) { + if (copy > have) copy = have; + if (copy > left) copy = left; + if (copy == 0) goto inf_leave; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + state->length -= copy; + break; + } + Tracev((stderr, "inflate: stored end\n")); + state->mode = TYPE; + break; + case TABLE: + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); +#ifndef PKZIP_BUG_WORKAROUND + if (state->nlen > 286 || state->ndist > 30) { + strm->msg = "too many length or distance symbols"; + state->mode = BAD; + break; + } +#endif + Tracev((stderr, "inflate: table sizes ok\n")); + state->have = 0; + state->mode = LENLENS; + case LENLENS: + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + state->lencode = (const code FAR *)(state->next); + state->lenbits = 7; + ret = inflate_table(CODES, state->lens, 19, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = "invalid code lengths set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + state->have = 0; + state->mode = CODELENS; + case CODELENS: + while (state->have < state->nlen + state->ndist) { + for (;;) { + here = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.val < 16) { + DROPBITS(here.bits); + state->lens[state->have++] = here.val; + } + else { + if (here.val == 16) { + NEEDBITS(here.bits + 2); + DROPBITS(here.bits); + if (state->have == 0) { + strm->msg = "invalid bit length repeat"; + state->mode = BAD; + break; + } + len = state->lens[state->have - 1]; + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (here.val == 17) { + NEEDBITS(here.bits + 3); + DROPBITS(here.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(here.bits + 7); + DROPBITS(here.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = "invalid bit length repeat"; + state->mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } -}; + /* handle error breaks in while */ + if (state->mode == BAD) break; + /* check for end-of-block code (better have one) */ + if (state->lens[256] == 0) { + strm->msg = "invalid code -- missing end-of-block"; + state->mode = BAD; + break; + } -int ZEXPORT inflateReset(z) -z_streamp z; -{ - if (z == Z_NULL || z->state == Z_NULL) - return Z_STREAM_ERROR; - z->total_in = z->total_out = 0; - z->msg = Z_NULL; - ((struct inflate_internal_state*)z->state)->mode = ((struct inflate_internal_state*)z->state)->nowrap ? BLOCKS : METHOD; - inflate_blocks_reset(((struct inflate_internal_state*)z->state)->blocks, z, Z_NULL); -// Tracev((stderr, "inflate: reset\n")); - return Z_OK; -} + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state->next = state->codes; + state->lencode = (const code FAR *)(state->next); + state->lenbits = 9; + ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = "invalid literal/lengths set"; + state->mode = BAD; + break; + } + state->distcode = (const code FAR *)(state->next); + state->distbits = 6; + ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, + &(state->next), &(state->distbits), state->work); + if (ret) { + strm->msg = "invalid distances set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + state->mode = LEN_; + if (flush == Z_TREES) goto inf_leave; + case LEN_: + state->mode = LEN; + case LEN: + if (have >= 6 && left >= 258) { + RESTORE(); + inflate_fast(strm, out); + LOAD(); + if (state->mode == TYPE) + state->back = -1; + break; + } + state->back = 0; + for (;;) { + here = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.op && (here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = state->lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + state->back += last.bits; + } + DROPBITS(here.bits); + state->back += here.bits; + state->length = (unsigned)here.val; + if ((int)(here.op) == 0) { + Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", here.val)); + state->mode = LIT; + break; + } + if (here.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + state->back = -1; + state->mode = TYPE; + break; + } + if (here.op & 64) { + strm->msg = "invalid literal/length code"; + state->mode = BAD; + break; + } + state->extra = (unsigned)(here.op) & 15; + state->mode = LENEXT; + case LENEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->length += BITS(state->extra); + DROPBITS(state->extra); + state->back += state->extra; + } + Tracevv((stderr, "inflate: length %u\n", state->length)); + state->was = state->length; + state->mode = DIST; + case DIST: + for (;;) { + here = state->distcode[BITS(state->distbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if ((here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = state->distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + state->back += last.bits; + } + DROPBITS(here.bits); + state->back += here.bits; + if (here.op & 64) { + strm->msg = "invalid distance code"; + state->mode = BAD; + break; + } + state->offset = (unsigned)here.val; + state->extra = (unsigned)(here.op) & 15; + state->mode = DISTEXT; + case DISTEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->offset += BITS(state->extra); + DROPBITS(state->extra); + state->back += state->extra; + } +#ifdef INFLATE_STRICT + if (state->offset > state->dmax) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + Tracevv((stderr, "inflate: distance %u\n", state->offset)); + state->mode = MATCH; + case MATCH: + if (left == 0) goto inf_leave; + copy = out - left; + if (state->offset > copy) { /* copy from window */ + copy = state->offset - copy; + if (copy > state->whave) { + if (state->sane) { + strm->msg = "invalid distance too far back"; + state->mode = BAD; + break; + } +#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + Trace((stderr, "inflate.c too far\n")); + copy -= state->whave; + if (copy > state->length) copy = state->length; + if (copy > left) copy = left; + left -= copy; + state->length -= copy; + do { + *put++ = 0; + } while (--copy); + if (state->length == 0) state->mode = LEN; + break; +#endif + } + if (copy > state->wnext) { + copy -= state->wnext; + from = state->window + (state->wsize - copy); + } + else + from = state->window + (state->wnext - copy); + if (copy > state->length) copy = state->length; + } + else { /* copy from output */ + from = put - state->offset; + copy = state->length; + } + if (copy > left) copy = left; + left -= copy; + state->length -= copy; + do { + *put++ = *from++; + } while (--copy); + if (state->length == 0) state->mode = LEN; + break; + case LIT: + if (left == 0) goto inf_leave; + *put++ = (unsigned char)(state->length); + left--; + state->mode = LEN; + break; + case CHECK: + if (state->wrap) { + NEEDBITS(32); + out -= left; + strm->total_out += out; + state->total += out; + if (out) + strm->adler = state->check = + UPDATE(state->check, put - out, out); + out = left; + if (( +#ifdef GUNZIP + state->flags ? hold : +#endif + ZSWAP32(hold)) != state->check) { + strm->msg = "incorrect data check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: check matches trailer\n")); + } +#ifdef GUNZIP + state->mode = LENGTH; + case LENGTH: + if (state->wrap && state->flags) { + NEEDBITS(32); + if (hold != (state->total & 0xffffffffUL)) { + strm->msg = "incorrect length check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: length matches trailer\n")); + } +#endif + state->mode = DONE; + case DONE: + ret = Z_STREAM_END; + goto inf_leave; + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + case MEM: + return Z_MEM_ERROR; + case SYNC: + default: + return Z_STREAM_ERROR; + } + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + inf_leave: + RESTORE(); + if (state->wsize || (out != strm->avail_out && state->mode < BAD && + (state->mode < CHECK || flush != Z_FINISH))) + if (updatewindow(strm, strm->next_out, out - strm->avail_out)) { + state->mode = MEM; + return Z_MEM_ERROR; + } + in -= strm->avail_in; + out -= strm->avail_out; + strm->total_in += in; + strm->total_out += out; + state->total += out; + if (state->wrap && out) + strm->adler = state->check = + UPDATE(state->check, strm->next_out - out, out); + strm->data_type = state->bits + (state->last ? 64 : 0) + + (state->mode == TYPE ? 128 : 0) + + (state->mode == LEN_ || state->mode == COPY_ ? 256 : 0); + if (((in == 0 && out == 0) || flush == Z_FINISH) && ret == Z_OK) + ret = Z_BUF_ERROR; + return ret; +} -int ZEXPORT inflateEnd(z) -z_streamp z; -{ - if (z == Z_NULL || z->state == Z_NULL || z->zfree == Z_NULL) - return Z_STREAM_ERROR; - if (((struct inflate_internal_state*)z->state)->blocks != Z_NULL) - inflate_blocks_free(((struct inflate_internal_state*)z->state)->blocks, z); - ZFREE(z, z->state); - z->state = Z_NULL; -// Tracev((stderr, "inflate: end\n")); - return Z_OK; +int ZEXPORT inflateEnd( + z_streamp strm +) { + struct inflate_state FAR *state; + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->window != Z_NULL) ZFREE(strm, state->window); + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; } +int ZEXPORT inflateGetDictionary( + z_streamp strm, + Bytef *dictionary, + uInt *dictLength +) { + struct inflate_state FAR *state; -int ZEXPORT inflateInit2_(z, w, version, stream_size) -z_streamp z; -int w; -const char *version; -int stream_size; -{ - if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || - stream_size != sizeof(z_stream)) - return Z_VERSION_ERROR; + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* copy dictionary */ + if (state->whave && dictionary != Z_NULL) { + zmemcpy(dictionary, state->window + state->wnext, + state->whave - state->wnext); + zmemcpy(dictionary + state->whave - state->wnext, + state->window, state->wnext); + } + if (dictLength != Z_NULL) + *dictLength = state->whave; + return Z_OK; +} - /* initialize state */ - if (z == Z_NULL) - return Z_STREAM_ERROR; - z->msg = Z_NULL; - if (z->zalloc == Z_NULL) - { - z->zalloc = zcalloc; - z->opaque = (voidpf)0; - } - if (z->zfree == Z_NULL) z->zfree = zcfree; - if ((z->state = (struct internal_state FAR *) - ZALLOC(z,1,sizeof(struct internal_state))) == Z_NULL) - return Z_MEM_ERROR; - ((struct inflate_internal_state*)z->state)->blocks = Z_NULL; - - /* handle undocumented nowrap option (no zlib header or check) */ - ((struct inflate_internal_state*)z->state)->nowrap = 0; - if (w < 0) - { - w = - w; - ((struct inflate_internal_state*)z->state)->nowrap = 1; - } +int ZEXPORT inflateSetDictionary( + z_streamp strm, + const Bytef *dictionary, + uInt dictLength +) { + struct inflate_state FAR *state; + unsigned long dictid; + int ret; - /* set window size */ - if (w < 8 || w > 15) - { - inflateEnd(z); - return Z_STREAM_ERROR; - } - ((struct inflate_internal_state*)z->state)->wbits = (uInt)w; + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->wrap != 0 && state->mode != DICT) + return Z_STREAM_ERROR; - /* create inflate_blocks state */ - if (!z->checksum) z->checksum = adler32; - if ((((struct inflate_internal_state*)z->state)->blocks = - inflate_blocks_new(z, ((struct inflate_internal_state*)z->state)->nowrap ? Z_NULL : z->checksum, (uInt)1 << w)) - == Z_NULL) - { - inflateEnd(z); - return Z_MEM_ERROR; - } -// Tracev((stderr, "inflate: allocated\n")); + /* check for correct dictionary identifier */ + if (state->mode == DICT) { + dictid = adler32(0L, Z_NULL, 0); + dictid = adler32(dictid, dictionary, dictLength); + if (dictid != state->check) + return Z_DATA_ERROR; + } - /* reset state */ - inflateReset(z); - return Z_OK; + /* copy dictionary to window using updatewindow(), which will amend the + existing dictionary if appropriate */ + ret = updatewindow(strm, dictionary + dictLength, dictLength); + if (ret) { + state->mode = MEM; + return Z_MEM_ERROR; + } + state->havedict = 1; + Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK; } +int ZEXPORT inflateGetHeader( + z_streamp strm, + gz_headerp head +) { + struct inflate_state FAR *state; -int ZEXPORT inflateInit_(z, version, stream_size) -z_streamp z; -const char *version; -int stream_size; -{ - return inflateInit2_(z, DEF_WBITS, version, stream_size); + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if ((state->wrap & 2) == 0) return Z_STREAM_ERROR; + + /* save header structure */ + state->head = head; + head->done = 0; + return Z_OK; } +/* + Search buf[0..len-1] for the pattern: 0, 0, 0xff, 0xff. Return when found + or when out of input. When called, *have is the number of pattern bytes + found in order so far, in 0..3. On return *have is updated to the new + state. If on return *have equals four, then the pattern was found and the + return value is how many bytes were read including the last byte of the + pattern. If *have is less than four, then the pattern has not been found + yet and the return value is len. In the latter case, syncsearch() can be + called again with more data and the *have state. *have is initialized to + zero for the first call. + */ +local unsigned syncsearch( + unsigned FAR *have, + const unsigned char FAR *buf, + unsigned len +) { + unsigned got; + unsigned next; + + got = *have; + next = 0; + while (next < len && got < 4) { + if ((int)(buf[next]) == (got < 2 ? 0 : 0xff)) + got++; + else if (buf[next]) + got = 0; + else + got = 4 - got; + next++; + } + *have = got; + return next; +} -/* #define NEEDBYTE {if(z->avail_in==0) goto ret; r=f;} */ -#define INFLATENEEDBYTE {if(z->avail_in==0)return r;r=f;} -#define INFLATENEXTBYTE (z->avail_in--,z->total_in++,*z->next_in++) +int ZEXPORT inflateSync( + z_streamp strm +) { + unsigned len; /* number of bytes to look at or looked at */ + unsigned long in, out; /* temporary to save total_in and total_out */ + unsigned char buf[4]; /* to restore bit buffer to byte string */ + struct inflate_state FAR *state; -int ZEXPORT inflate(z, f) -z_streamp z; -int f; -{ - int r; - uInt b; - - if (z == Z_NULL || z->state == Z_NULL || z->next_in == Z_NULL) - return Z_STREAM_ERROR; - f = f == Z_FINISH ? Z_BUF_ERROR : Z_OK; - r = Z_BUF_ERROR; - while (1) switch (((struct inflate_internal_state*)z->state)->mode) - { - case METHOD: - INFLATENEEDBYTE - if (((((struct inflate_internal_state*)z->state)->sub.method = INFLATENEXTBYTE) & 0xf) != Z_DEFLATED) - { - ((struct inflate_internal_state*)z->state)->mode = IM_BAD; -// z->msg = (char*)"unknown compression method"; - ((struct inflate_internal_state*)z->state)->sub.marker = 5; /* can't try inflateSync */ - break; - } - if ((((struct inflate_internal_state*)z->state)->sub.method >> 4) + 8 > ((struct inflate_internal_state*)z->state)->wbits) - { - ((struct inflate_internal_state*)z->state)->mode = IM_BAD; -// z->msg = (char*)"invalid window size"; - ((struct inflate_internal_state*)z->state)->sub.marker = 5; /* can't try inflateSync */ - break; - } - ((struct inflate_internal_state*)z->state)->mode = FLAG; - case FLAG: - INFLATENEEDBYTE - b = INFLATENEXTBYTE; - if (((((struct inflate_internal_state*)z->state)->sub.method << 8) + b) % 31) - { - ((struct inflate_internal_state*)z->state)->mode = IM_BAD; -// z->msg = (char*)"incorrect header check"; - ((struct inflate_internal_state*)z->state)->sub.marker = 5; /* can't try inflateSync */ - break; - } -// Tracev((stderr, "inflate: zlib header ok\n")); - if (!(b & PRESET_DICT)) - { - ((struct inflate_internal_state*)z->state)->mode = BLOCKS; - break; - } - ((struct inflate_internal_state*)z->state)->mode = DICT4; - case DICT4: - INFLATENEEDBYTE - ((struct inflate_internal_state*)z->state)->sub.check.need = (uLong)INFLATENEXTBYTE << 24; - ((struct inflate_internal_state*)z->state)->mode = DICT3; - case DICT3: - INFLATENEEDBYTE - ((struct inflate_internal_state*)z->state)->sub.check.need += (uLong)INFLATENEXTBYTE << 16; - ((struct inflate_internal_state*)z->state)->mode = DICT2; - case DICT2: - INFLATENEEDBYTE - ((struct inflate_internal_state*)z->state)->sub.check.need += (uLong)INFLATENEXTBYTE << 8; - ((struct inflate_internal_state*)z->state)->mode = DICT1; - case DICT1: - INFLATENEEDBYTE - ((struct inflate_internal_state*)z->state)->sub.check.need += (uLong)INFLATENEXTBYTE; - z->adler = ((struct inflate_internal_state*)z->state)->sub.check.need; - ((struct inflate_internal_state*)z->state)->mode = DICT0; - return Z_NEED_DICT; - case DICT0: - ((struct inflate_internal_state*)z->state)->mode = IM_BAD; -// z->msg = (char*)"need dictionary"; - ((struct inflate_internal_state*)z->state)->sub.marker = 0; /* can try inflateSync */ - return Z_STREAM_ERROR; - case BLOCKS: - r = inflate_blocks(((struct inflate_internal_state*)z->state)->blocks, z, r); - if (r == Z_DATA_ERROR) - { - ((struct inflate_internal_state*)z->state)->mode = IM_BAD; - ((struct inflate_internal_state*)z->state)->sub.marker = 0; /* can try inflateSync */ - break; - } - if (r == Z_OK) - r = f; - if (r != Z_STREAM_END) - return r; - r = f; - inflate_blocks_reset(((struct inflate_internal_state*)z->state)->blocks, z, &((struct inflate_internal_state*)z->state)->sub.check.was); - if (((struct inflate_internal_state*)z->state)->nowrap) - { - ((struct inflate_internal_state*)z->state)->mode = IM_DONE; - break; - } - ((struct inflate_internal_state*)z->state)->mode = CHECK4; - case CHECK4: - INFLATENEEDBYTE - ((struct inflate_internal_state*)z->state)->sub.check.need = (uLong)INFLATENEXTBYTE << 24; - ((struct inflate_internal_state*)z->state)->mode = CHECK3; - case CHECK3: - INFLATENEEDBYTE - ((struct inflate_internal_state*)z->state)->sub.check.need += (uLong)INFLATENEXTBYTE << 16; - ((struct inflate_internal_state*)z->state)->mode = CHECK2; - case CHECK2: - INFLATENEEDBYTE - ((struct inflate_internal_state*)z->state)->sub.check.need += (uLong)INFLATENEXTBYTE << 8; - ((struct inflate_internal_state*)z->state)->mode = CHECK1; - case CHECK1: - INFLATENEEDBYTE - ((struct inflate_internal_state*)z->state)->sub.check.need += (uLong)INFLATENEXTBYTE; - - if (((struct inflate_internal_state*)z->state)->sub.check.was != ((struct inflate_internal_state*)z->state)->sub.check.need) - { - ((struct inflate_internal_state*)z->state)->mode = IM_BAD; -// z->msg = (char*)"incorrect data check"; - ((struct inflate_internal_state*)z->state)->sub.marker = 5; /* can't try inflateSync */ - break; - } -// Tracev((stderr, "inflate: zlib check ok\n")); - ((struct inflate_internal_state*)z->state)->mode = IM_DONE; - case IM_DONE: - return Z_STREAM_END; - case IM_BAD: - return Z_DATA_ERROR; - default: - return Z_STREAM_ERROR; - } -#ifdef NEED_DUMMY_RETURN - return Z_STREAM_ERROR; /* Some dumb compilers complain without this */ -#endif -/* ret: */ -/* return retval; */ + /* check parameters */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (strm->avail_in == 0 && state->bits < 8) return Z_BUF_ERROR; + + /* if first time, start search in bit buffer */ + if (state->mode != SYNC) { + state->mode = SYNC; + state->hold <<= state->bits & 7; + state->bits -= state->bits & 7; + len = 0; + while (state->bits >= 8) { + buf[len++] = (unsigned char)(state->hold); + state->hold >>= 8; + state->bits -= 8; + } + state->have = 0; + syncsearch(&(state->have), buf, len); + } + + /* search available input */ + len = syncsearch(&(state->have), strm->next_in, strm->avail_in); + strm->avail_in -= len; + strm->next_in += len; + strm->total_in += len; + + /* return no joy or set up to restart inflate() on a new block */ + if (state->have != 4) return Z_DATA_ERROR; + in = strm->total_in; out = strm->total_out; + inflateReset(strm); + strm->total_in = in; strm->total_out = out; + state->mode = TYPE; + return Z_OK; } +/* + Returns true if inflate is currently at the end of a block generated by + Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + implementation to provide an additional safety check. PPP uses + Z_SYNC_FLUSH but removes the length bytes of the resulting empty stored + block. When decompressing, PPP checks that at the end of input packet, + inflate is waiting for these length bytes. + */ +int ZEXPORT inflateSyncPoint( + z_streamp strm +) { + struct inflate_state FAR *state; -/* int ZEXPORT inflateSetDictionary(z, dictionary, dictLength) */ -/* z_streamp z; */ -/* const Bytef *dictionary; */ -/* uInt dictLength; */ -/* { */ -/* uInt length = dictLength; */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + return state->mode == STORED && state->bits == 0; +} -/* if (z == Z_NULL || z->state == Z_NULL || z->state->mode != DICT0) */ -/* return Z_STREAM_ERROR; */ +int ZEXPORT inflateCopy( + z_streamp dest, + z_streamp source +) { + struct inflate_state FAR *state; + struct inflate_state FAR *copy; + unsigned char FAR *window; + unsigned wsize; + + /* check input */ + if (dest == Z_NULL || source == Z_NULL || source->state == Z_NULL || + source->zalloc == (alloc_func)0 || source->zfree == (free_func)0) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)source->state; + + /* allocate space */ + copy = (struct inflate_state FAR *) + ZALLOC(source, 1, sizeof(struct inflate_state)); + if (copy == Z_NULL) return Z_MEM_ERROR; + window = Z_NULL; + if (state->window != Z_NULL) { + window = (unsigned char FAR *) + ZALLOC(source, 1U << state->wbits, sizeof(unsigned char)); + if (window == Z_NULL) { + ZFREE(source, copy); + return Z_MEM_ERROR; + } + } -/* if (adler32(1L, dictionary, dictLength) != z->adler) return Z_DATA_ERROR; */ -/* z->adler = 1L; */ + /* copy state */ + zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream)); + zmemcpy((voidpf)copy, (voidpf)state, sizeof(struct inflate_state)); + if (state->lencode >= state->codes && + state->lencode <= state->codes + ENOUGH - 1) { + copy->lencode = copy->codes + (state->lencode - state->codes); + copy->distcode = copy->codes + (state->distcode - state->codes); + } + copy->next = copy->codes + (state->next - state->codes); + if (window != Z_NULL) { + wsize = 1U << state->wbits; + zmemcpy(window, state->window, wsize); + } + copy->window = window; + dest->state = (struct internal_state FAR *)copy; + return Z_OK; +} + +int ZEXPORT inflateUndermine( + z_streamp strm, + int subvert +) { + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + state->sane = !subvert; +#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + return Z_OK; +#else + state->sane = 1; + return Z_DATA_ERROR; +#endif +} -/* if (length >= ((uInt)1<state->wbits)) */ -/* { */ -/* length = (1<state->wbits)-1; */ -/* dictionary += dictLength - length; */ -/* } */ -/* inflate_set_dictionary(z->state->blocks, dictionary, length); */ -/* z->state->mode = BLOCKS; */ -/* return Z_OK; */ -/* } */ +long ZEXPORT inflateMark( + z_streamp strm +) { + struct inflate_state FAR *state; + if (strm == Z_NULL || strm->state == Z_NULL) return -(1L << 16); + state = (struct inflate_state FAR *)strm->state; + return ((long)(state->back) << 16) + + (state->mode == COPY ? state->length : + (state->mode == MATCH ? state->was - state->length : 0)); +} diff --git a/src/extensions/bmp/ext-bmp.c b/src/extensions/bmp/ext-bmp.c new file mode 100644 index 0000000000..9858bba8f3 --- /dev/null +++ b/src/extensions/bmp/ext-bmp.c @@ -0,0 +1,64 @@ +// +// File: %ext-bmp.c +// Summary: "BMP codec" +// Section: Extension +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ================================================================== +// +#include "sys-core.h" +#include "sys-ext.h" + +static const REBYTE script_bytes[] = +"REBOL [" + "Title: \"BMP Codec Extension\"\n" + "name: 'BMP\n" + "type: 'Extension\n" + "version: 1.0.0\n" + "license: {Apache 2.0}\n" +"]\n" +"sys/register-codec* 'bmp %.bmp\n" + "get in import 'bmp 'identify-bmp?\n" + "get in import 'bmp 'decode-bmp\n" + "get in import 'bmp 'encode-bmp\n" +; + +#define MODULE_INCLUDE_DECLARATION_ONLY +#include "tmp-mod-bmp-last.h" + +DEFINE_EXT_INIT(BMP, //name of the extension + script_bytes, // REBOL script for the extension in the source form + { + // init all modules in this extension + int init = CALL_MODULE_INIT(BMP); + if (init < 0) return init; + } +) + +DEFINE_EXT_QUIT(BMP, +{ + return CALL_MODULE_QUIT(BMP); +} +) + diff --git a/src/extensions/bmp/mod-bmp.c b/src/extensions/bmp/mod-bmp.c new file mode 100644 index 0000000000..582467104e --- /dev/null +++ b/src/extensions/bmp/mod-bmp.c @@ -0,0 +1,627 @@ +// +// File: %mod-bmp.c +// Summary: "conversion to and from BMP graphics format" +// Section: Extension +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This is an optional part of R3. This file can be replaced by +// library function calls into an updated implementation. +// + +#include "sys-core.h" +#include "sys-ext.h" +#include "tmp-mod-bmp-first.h" + +//********************************************************************** + +#define WADJUST(x) (((x * 3L + 3) / 4) * 4) + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned int DWORD; +typedef int LONG; + +typedef struct tagBITMAP +{ + int bmType; + int bmWidth; + int bmHeight; + int bmWidthBytes; + BYTE bmPlanes; + BYTE bmBitsPixel; + void *bmBits; +} BITMAP; +typedef BITMAP *PBITMAP; +typedef BITMAP *NPBITMAP; +typedef BITMAP *LPBITMAP; + +/* Bitmap Header structures */ +typedef struct tagRGBTRIPLE +{ + BYTE rgbtBlue; + BYTE rgbtGreen; + BYTE rgbtRed; +} RGBTRIPLE; +typedef RGBTRIPLE *LPRGBTRIPLE; + +typedef struct tagRGBQUAD +{ + BYTE rgbBlue; + BYTE rgbGreen; + BYTE rgbRed; + BYTE rgbReserved; +} RGBQUAD; +typedef RGBQUAD *LPRGBQUAD; + +/* structures for defining DIBs */ +typedef struct tagBITMAPCOREHEADER +{ + DWORD bcSize; + short bcWidth; + short bcHeight; + WORD bcPlanes; + WORD bcBitCount; +} BITMAPCOREHEADER; +typedef BITMAPCOREHEADER* PBITMAPCOREHEADER; +typedef BITMAPCOREHEADER *LPBITMAPCOREHEADER; + +const char *mapBITMAPCOREHEADER = "lssss"; + +typedef struct tagBITMAPINFOHEADER +{ + DWORD biSize; + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount; + DWORD biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; +} BITMAPINFOHEADER; + +const char *mapBITMAPINFOHEADER = "lllssllllll"; + +typedef BITMAPINFOHEADER* PBITMAPINFOHEADER; +typedef BITMAPINFOHEADER *LPBITMAPINFOHEADER; + +/* constants for the biCompression field */ +#define BI_RGB 0L +#define BI_RLE8 1L +#define BI_RLE4 2L + +typedef struct tagBITMAPINFO +{ + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[1]; +} BITMAPINFO; +typedef BITMAPINFO* PBITMAPINFO; +typedef BITMAPINFO *LPBITMAPINFO; + +typedef struct tagBITMAPCOREINFO +{ + BITMAPCOREHEADER bmciHeader; + RGBTRIPLE bmciColors[1]; +} BITMAPCOREINFO; +typedef BITMAPCOREINFO* PBITMAPCOREINFO; +typedef BITMAPCOREINFO *LPBITMAPCOREINFO; + +typedef struct tagBITMAPFILEHEADER +{ + char bfType[2]; + DWORD bfSize; + WORD bfReserved1; + WORD bfReserved2; + DWORD bfOffBits; +} BITMAPFILEHEADER; +typedef BITMAPFILEHEADER* PBITMAPFILEHEADER; +typedef BITMAPFILEHEADER *LPBITMAPFILEHEADER; + +const char *mapBITMAPFILEHEADER = "bblssl"; + +typedef RGBQUAD *RGBQUADPTR; + +//********************************************************************** + +static REBOOL longaligned(void) { + static char filldata[] = {0,0,1,1,1,1}; + struct { + unsigned short a; + unsigned int b; + } a; + memset(&a, '\0', sizeof(a)); + memcpy(&a, filldata, 6); + if (a.b != 0x01010101) return TRUE; + return FALSE; +} + +void Map_Bytes(void *dstp, const REBYTE **srcp, const char *map) { + const REBYTE *src = *srcp; + REBYTE *dst = cast(REBYTE*, dstp); + char c; +#ifdef ENDIAN_LITTLE + while ((c = *map++) != 0) { + switch(c) { + case 'b': + *dst++ = *src++; + break; + + case 's': + *((short *)dst) = *((const short *)src); + dst += sizeof(short); + src += 2; + break; + + case 'l': + if (longaligned()) { + while(((REBUPT)dst)&3) + dst++; + } + *((REBCNT *)dst) = *((const REBCNT *)src); + dst += sizeof(REBCNT); + src += 4; + break; + } + } +#else + while ((c = *map++) != 0) { + switch(c) { + case 'b': + *dst++ = *src++; + break; + + case 's': + *((short *)dst) = src[0]|(src[1]<<8); + dst += sizeof(short); + src += 2; + break; + + case 'l': + if (longaligned()) { + while (((unsigned long)dst)&3) + dst++; + } + *((REBCNT *)dst) = src[0]|(src[1]<<8)| + (src[2]<<16)|(src[3]<<24); + dst += sizeof(REBCNT); + src += 4; + break; + } + } +#endif + *srcp = src; +} + +void Unmap_Bytes(void *srcp, REBYTE **dstp, const char *map) { + REBYTE *src = cast(REBYTE*, srcp); + REBYTE *dst = *dstp; + char c; +#ifdef ENDIAN_LITTLE + while ((c = *map++) != 0) { + switch(c) { + case 'b': + *dst++ = *src++; + break; + + case 's': + *((short *)dst) = *((short *)src); + src += sizeof(short); + dst += 2; + break; + + case 'l': + if (longaligned()) { + while(((REBUPT)src)&3) + src++; + } + *((REBCNT *)dst) = *((REBCNT *)src); + src += sizeof(REBCNT); + dst += 4; + break; + } + } +#else + while ((c = *map++) != 0) { + switch(c) { + case 'b': + *dst++ = *src++; + break; + + case 's': + *((short *)dst) = src[0]|(src[1]<<8); + src += sizeof(short); + dst += 2; + break; + + case 'l': + if (longaligned()) { + while (((unsigned long)src)&3) + src++; + } + *((REBCNT *)dst) = src[0]|(src[1]<<8)| + (src[2]<<16)|(src[3]<<24); + src += sizeof(REBCNT); + dst += 4; + break; + } + } +#endif + *dstp = dst; +} + + +static REBOOL Has_Valid_BITMAPFILEHEADER(const REBYTE *data, REBCNT len) { + if (len < sizeof(BITMAPFILEHEADER)) + return FALSE; + + BITMAPFILEHEADER bmfh; + Map_Bytes(&bmfh, &data, mapBITMAPFILEHEADER); + + if (bmfh.bfType[0] != 'B' || bmfh.bfType[1] != 'M') + return FALSE; + + return TRUE; +} + + +// +// identify-bmp?: native [ +// +// {Codec for identifying BINARY! data for a BMP} +// +// return: [logic!] +// data [binary!] +// ] +// +REBNATIVE(identify_bmp_q) +{ + INCLUDE_PARAMS_OF_IDENTIFY_BMP_Q; + + const REBYTE *data = VAL_BIN_AT(ARG(data)); + REBCNT len = VAL_LEN_AT(ARG(data)); + + // Assume signature matching is good enough (will get a fail() on + // decode if it's a false positive). + // + return R_FROM_BOOL(Has_Valid_BITMAPFILEHEADER(data, len)); +} + + +// +// decode-bmp: native [ +// +// {Codec for decoding BINARY! data for a BMP} +// +// return: [image!] +// data [binary!] +// ] +// +REBNATIVE(decode_bmp) +{ + INCLUDE_PARAMS_OF_DECODE_BMP; + + const REBYTE *data = VAL_BIN_AT(ARG(data)); + REBCNT len = VAL_LEN_AT(ARG(data)); + + if (NOT(Has_Valid_BITMAPFILEHEADER(data, len))) + fail (Error_Bad_Media_Raw()); + + REBINT i, j, x, y, c; + REBINT colors, compression, bitcount; + REBINT w, h; + BITMAPINFOHEADER bmih; + BITMAPCOREHEADER bmch; + RGBQUADPTR color; + RGBQUADPTR ctab = 0; + + const REBYTE *cp = data; + + // !!! It strangely appears that passing &data instead of &cp to this + // Map_Bytes call causes bugs below. Not clear why that would be. + // + BITMAPFILEHEADER bmfh; + Map_Bytes(&bmfh, &cp, mapBITMAPFILEHEADER); // length already checked + + const REBYTE *tp = cp; + Map_Bytes(&bmih, &cp, mapBITMAPINFOHEADER); + if (bmih.biSize < sizeof(BITMAPINFOHEADER)) { + cp = tp; + Map_Bytes(&bmch, &cp, mapBITMAPCOREHEADER); + + w = bmch.bcWidth; + h = bmch.bcHeight; + compression = 0; + bitcount = bmch.bcBitCount; + + if (bmch.bcBitCount < 24) + colors = 1 << bmch.bcBitCount; + else + colors = 0; + + if (colors) { + ctab = ALLOC_N(RGBQUAD, colors); + for (i = 0; irgbRed, color->rgbGreen, color->rgbBlue, 0xff); + x >>= 1; + } + i = (w+7) / 8; + break; + + case 4: + for (i = 0; i> 4; + } + else + x = c & 0xf; + if (x > colors) { + goto bad_table_error; + } + color = &ctab[x]; + *dp++ = TO_PIXEL_COLOR(color->rgbRed, color->rgbGreen, color->rgbBlue, 0xff); + } + i = (w+1) / 2; + break; + + case 8: + for (i = 0; i colors) { + goto bad_table_error; + } + color = &ctab[c]; + *dp++ = TO_PIXEL_COLOR(color->rgbRed, color->rgbGreen, color->rgbBlue, 0xff); + } + break; + + case 24: + for (i = 0; i>4]; + } + else + color = &ctab[x&0x0f]; + *dp++ = TO_PIXEL_COLOR(color->rgbRed, color->rgbGreen, color->rgbBlue, 0xff); + } + j = (c+1) / 2; + while (j++%2) + cp++; + } + else { + x = *cp++ & 0xff; + for (j = 0; j>4]; + *dp++ = TO_PIXEL_COLOR(color->rgbRed, color->rgbGreen, color->rgbBlue, 0xff); + } + } + } + break; + + case BI_RLE8: + i = 0; + for (;;) { + c = *cp++ & 0xff; + + if (c == 0) { + c = *cp++ & 0xff; + if (c == 0 || c == 1) + break; + if (c == 2) { + goto bad_table_error; + } + for (j = 0; jrgbRed, color->rgbGreen, color->rgbBlue, 0xff); + } + while (j++ % 2) + cp++; + } + else { + x = *cp++ & 0xff; + for (j = 0; jrgbRed, color->rgbGreen, color->rgbBlue, 0xff); + } + } + } + break; + + default: + goto bad_encoding_error; + } + dp -= 2 * w; + } + + Init_Image(D_OUT, ser); + return R_OUT; + +bit_len_error: +bad_encoding_error: +bad_table_error: + if (ctab) free(ctab); + fail (Error_Bad_Media_Raw()); // better error? +} + + +// +// encode-bmp: native [ +// +// {Codec for encoding a BMP image} +// +// return: [binary!] +// image [image!] +// ] +// +REBNATIVE(encode_bmp) +{ + INCLUDE_PARAMS_OF_ENCODE_BMP; + + REBINT i, y; + REBYTE *cp, *v; + REBCNT *dp; + BITMAPFILEHEADER bmfh; + BITMAPINFOHEADER bmih; + + REBINT w = VAL_IMAGE_WIDE(ARG(image)); + REBINT h = VAL_IMAGE_HIGH(ARG(image)); + + memset(&bmfh, 0, sizeof(bmfh)); + bmfh.bfType[0] = 'B'; + bmfh.bfType[1] = 'M'; + bmfh.bfSize = 14 + 40 + h * WADJUST(w); + bmfh.bfOffBits = 14 + 40; + + // Create binary string: + REBSER *bin = Make_Binary(bmfh.bfSize); + cp = BIN_HEAD(bin); + Unmap_Bytes(&bmfh, &cp, mapBITMAPFILEHEADER); + + memset(&bmih, 0, sizeof(bmih)); + bmih.biSize = 40; + bmih.biWidth = w; + bmih.biHeight = h; + bmih.biPlanes = 1; + bmih.biBitCount = 24; + bmih.biCompression = 0; + bmih.biSizeImage = 0; + bmih.biXPelsPerMeter = 0; + bmih.biYPelsPerMeter = 0; + bmih.biClrUsed = 0; + bmih.biClrImportant = 0; + Unmap_Bytes(&bmih, &cp, mapBITMAPINFOHEADER); + + dp = cast(REBCNT *, VAL_IMAGE_BITS(ARG(image))); + dp += w * h - w; + + for (y = 0; y +#ifdef WIN32 +#include +#else +#include +#endif +#include "aes.h" + +#define rot1(x) (((x) << 24) | ((x) >> 8)) +#define rot2(x) (((x) << 16) | ((x) >> 16)) +#define rot3(x) (((x) << 8) | ((x) >> 24)) + +/* + * This cute trick does 4 'mul by two' at once. Stolen from + * Dr B. R. Gladman but I'm sure the u-(u>>7) is + * a standard graphics trick + * The key to this is that we need to xor with 0x1b if the top bit is set. + * a 1xxx xxxx 0xxx 0xxx First we mask the 7bit, + * b 1000 0000 0000 0000 then we shift right by 7 putting the 7bit in 0bit, + * c 0000 0001 0000 0000 we then subtract (c) from (b) + * d 0111 1111 0000 0000 and now we and with our mask + * e 0001 1011 0000 0000 + */ +#define mt 0x80808080 +#define ml 0x7f7f7f7f +#define mh 0xfefefefe +#define mm 0x1b1b1b1b +#define mul2(x,t) ((t)=((x)&mt), \ + ((((x)+(x))&mh)^(((t)-((t)>>7))&mm))) + +#define inv_mix_col(x,f2,f4,f8,f9) (\ + (f2)=mul2(x,f2), \ + (f4)=mul2(f2,f4), \ + (f8)=mul2(f4,f8), \ + (f9)=(x)^(f8), \ + (f8)=((f2)^(f4)^(f8)), \ + (f2)^=(f9), \ + (f4)^=(f9), \ + (f8)^=rot3(f2), \ + (f8)^=rot2(f4), \ + (f8)^rot1(f9)) + +/* + * AES S-box + */ +static const uint8_t aes_sbox[256] = +{ + 0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5, + 0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76, + 0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0, + 0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0, + 0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC, + 0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15, + 0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A, + 0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75, + 0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0, + 0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84, + 0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B, + 0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF, + 0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85, + 0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8, + 0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5, + 0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2, + 0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17, + 0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73, + 0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88, + 0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB, + 0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C, + 0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79, + 0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9, + 0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08, + 0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6, + 0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A, + 0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E, + 0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E, + 0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94, + 0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF, + 0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68, + 0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16, +}; + +/* + * AES is-box + */ +static const uint8_t aes_isbox[256] = +{ + 0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38, + 0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, + 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87, + 0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, + 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d, + 0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, + 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2, + 0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, + 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16, + 0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, + 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda, + 0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, + 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a, + 0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, + 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02, + 0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, + 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea, + 0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, + 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85, + 0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, + 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89, + 0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, + 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20, + 0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, + 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31, + 0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, + 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d, + 0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, + 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0, + 0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, + 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26, + 0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d +}; + +static const unsigned char Rcon[30]= +{ + 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80, + 0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f, + 0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4, + 0xb3,0x7d,0xfa,0xef,0xc5,0x91, +}; + +/* ----- static functions ----- */ +static void AES_encrypt(const AES_CTX *ctx, uint32_t *data); +static void AES_decrypt(const AES_CTX *ctx, uint32_t *data); + +/* Perform doubling in Galois Field GF(2^8) using the irreducible polynomial + x^8+x^4+x^3+x+1 */ +static unsigned char AES_xtime(uint32_t x) +{ + return (x&0x80) ? (x<<1)^0x1b : x<<1; +} + +/** + * Set up AES with the key/iv and cipher size. + */ +void AES_set_key(AES_CTX *ctx, const uint8_t *key, + const uint8_t *iv, AES_MODE mode) +{ + int i, ii; + uint32_t *W, tmp, tmp2; + const unsigned char *ip; + int words; + + switch (mode) + { + case AES_MODE_128: + i = 10; + words = 4; + break; + + case AES_MODE_256: + i = 14; + words = 8; + break; + + default: /* fail silently */ + return; + } + + ctx->key_mode = AES_MODE_ENCRYPT; //default mode + ctx->rounds = i; + ctx->key_size = words; + W = ctx->ks; + for (i = 0; i < words; i+=2) + { + W[i+0]= ((uint32_t)key[ 0]<<24)| + ((uint32_t)key[ 1]<<16)| + ((uint32_t)key[ 2]<< 8)| + ((uint32_t)key[ 3] ); + W[i+1]= ((uint32_t)key[ 4]<<24)| + ((uint32_t)key[ 5]<<16)| + ((uint32_t)key[ 6]<< 8)| + ((uint32_t)key[ 7] ); + key += 8; + } + + ip = Rcon; + ii = 4 * (ctx->rounds+1); + for (i = words; i> 8)&0xff]<<16; + tmp2|=(uint32_t)aes_sbox[(tmp>>16)&0xff]<<24; + tmp2|=(uint32_t)aes_sbox[(tmp>>24) ]; + tmp=tmp2^(((unsigned int)*ip)<<24); + ip++; + } + + if ((words == 8) && ((i % words) == 4)) + { + tmp2 =(uint32_t)aes_sbox[(tmp )&0xff] ; + tmp2|=(uint32_t)aes_sbox[(tmp>> 8)&0xff]<< 8; + tmp2|=(uint32_t)aes_sbox[(tmp>>16)&0xff]<<16; + tmp2|=(uint32_t)aes_sbox[(tmp>>24) ]<<24; + tmp=tmp2; + } + + W[i]=W[i-words]^tmp; + } + + /* copy the iv across */ + memcpy(ctx->iv, iv, 16); +} + +/** + * Change a key for decryption. + */ +void AES_convert_key(AES_CTX *ctx) +{ + int i; + uint32_t *k,w,t1,t2,t3,t4; + + ctx->key_mode = AES_MODE_DECRYPT; //change mode + + k = ctx->ks; + k += 4; + + for (i= ctx->rounds*4; i > 4; i--) + { + w= *k; + w = inv_mix_col(w,t1,t2,t3,t4); + *k++ =w; + } +} + +/** + * Encrypt a byte sequence (with a block size 16) using the AES cipher. + */ +void AES_cbc_encrypt(AES_CTX *ctx, const uint8_t *msg, uint8_t *out, int length) +{ + int i; + uint32_t tin[4], tout[4], iv[4]; + + memcpy(iv, ctx->iv, AES_IV_SIZE); + for (i = 0; i < 4; i++) + tout[i] = ntohl(iv[i]); + + for (length -= AES_BLOCKSIZE; length >= 0; length -= AES_BLOCKSIZE) + { + uint32_t msg_32[4]; + uint32_t out_32[4]; + memcpy(msg_32, msg, AES_BLOCKSIZE); + msg += AES_BLOCKSIZE; + + for (i = 0; i < 4; i++) + tin[i] = ntohl(msg_32[i])^tout[i]; + + AES_encrypt(ctx, tin); + + for (i = 0; i < 4; i++) + { + tout[i] = tin[i]; + out_32[i] = htonl(tout[i]); + } + + memcpy(out, out_32, AES_BLOCKSIZE); + out += AES_BLOCKSIZE; + } + + for (i = 0; i < 4; i++) + iv[i] = htonl(tout[i]); + memcpy(ctx->iv, iv, AES_IV_SIZE); +} + +/** + * Decrypt a byte sequence (with a block size 16) using the AES cipher. + */ +void AES_cbc_decrypt(AES_CTX *ctx, const uint8_t *msg, uint8_t *out, int length) +{ + int i; + uint32_t tin[4], xxor[4], tout[4], data[4], iv[4]; + + memcpy(iv, ctx->iv, AES_IV_SIZE); + for (i = 0; i < 4; i++) + xxor[i] = ntohl(iv[i]); + + for (length -= 16; length >= 0; length -= 16) + { + uint32_t msg_32[4]; + uint32_t out_32[4]; + memcpy(msg_32, msg, AES_BLOCKSIZE); + msg += AES_BLOCKSIZE; + + for (i = 0; i < 4; i++) + { + tin[i] = ntohl(msg_32[i]); + data[i] = tin[i]; + } + + AES_decrypt(ctx, data); + + for (i = 0; i < 4; i++) + { + tout[i] = data[i]^xxor[i]; + xxor[i] = tin[i]; + out_32[i] = htonl(tout[i]); + } + + memcpy(out, out_32, AES_BLOCKSIZE); + out += AES_BLOCKSIZE; + } + + for (i = 0; i < 4; i++) + iv[i] = htonl(xxor[i]); + memcpy(ctx->iv, iv, AES_IV_SIZE); +} + +/** + * Encrypt a single block (16 bytes) of data + */ +static void AES_encrypt(const AES_CTX *ctx, uint32_t *data) +{ + /* To make this code smaller, generate the sbox entries on the fly. + * This will have a really heavy effect upon performance. + */ + uint32_t tmp[4]; + uint32_t tmp1, old_a0, a0, a1, a2, a3, row; + int curr_rnd; + int rounds = ctx->rounds; + const uint32_t *k = ctx->ks; + + /* Pre-round key addition */ + for (row = 0; row < 4; row++) + data[row] ^= *(k++); + + /* Encrypt one block. */ + for (curr_rnd = 0; curr_rnd < rounds; curr_rnd++) + { + /* Perform ByteSub and ShiftRow operations together */ + for (row = 0; row < 4; row++) + { + a0 = (uint32_t)aes_sbox[(data[row%4]>>24)&0xFF]; + a1 = (uint32_t)aes_sbox[(data[(row+1)%4]>>16)&0xFF]; + a2 = (uint32_t)aes_sbox[(data[(row+2)%4]>>8)&0xFF]; + a3 = (uint32_t)aes_sbox[(data[(row+3)%4])&0xFF]; + + /* Perform MixColumn iff not last round */ + if (curr_rnd < (rounds - 1)) + { + tmp1 = a0 ^ a1 ^ a2 ^ a3; + old_a0 = a0; + a0 ^= tmp1 ^ AES_xtime(a0 ^ a1); + a1 ^= tmp1 ^ AES_xtime(a1 ^ a2); + a2 ^= tmp1 ^ AES_xtime(a2 ^ a3); + a3 ^= tmp1 ^ AES_xtime(a3 ^ old_a0); + } + + tmp[row] = ((a0 << 24) | (a1 << 16) | (a2 << 8) | a3); + } + + /* KeyAddition - note that it is vital that this loop is separate from + the MixColumn operation, which must be atomic...*/ + for (row = 0; row < 4; row++) + data[row] = tmp[row] ^ *(k++); + } +} + +/** + * Decrypt a single block (16 bytes) of data + */ +static void AES_decrypt(const AES_CTX *ctx, uint32_t *data) +{ + uint32_t tmp[4]; + uint32_t xt0,xt1,xt2,xt3,xt4,xt5,xt6; + uint32_t a0, a1, a2, a3, row; + int curr_rnd; + int rounds = ctx->rounds; + const uint32_t *k = ctx->ks + ((rounds+1)*4); + + /* pre-round key addition */ + for (row=4; row > 0;row--) + data[row-1] ^= *(--k); + + /* Decrypt one block */ + for (curr_rnd = 0; curr_rnd < rounds; curr_rnd++) + { + /* Perform ByteSub and ShiftRow operations together */ + for (row = 4; row > 0; row--) + { + a0 = aes_isbox[(data[(row+3)%4]>>24)&0xFF]; + a1 = aes_isbox[(data[(row+2)%4]>>16)&0xFF]; + a2 = aes_isbox[(data[(row+1)%4]>>8)&0xFF]; + a3 = aes_isbox[(data[row%4])&0xFF]; + + /* Perform MixColumn iff not last round */ + if (curr_rnd<(rounds-1)) + { + /* The MDS cofefficients (0x09, 0x0B, 0x0D, 0x0E) + are quite large compared to encryption; this + operation slows decryption down noticeably. */ + xt0 = AES_xtime(a0^a1); + xt1 = AES_xtime(a1^a2); + xt2 = AES_xtime(a2^a3); + xt3 = AES_xtime(a3^a0); + xt4 = AES_xtime(xt0^xt1); + xt5 = AES_xtime(xt1^xt2); + xt6 = AES_xtime(xt4^xt5); + + xt0 ^= a1^a2^a3^xt4^xt6; + xt1 ^= a0^a2^a3^xt5^xt6; + xt2 ^= a0^a1^a3^xt4^xt6; + xt3 ^= a0^a1^a2^xt5^xt6; + tmp[row-1] = ((xt0<<24)|(xt1<<16)|(xt2<<8)|xt3); + } + else + tmp[row-1] = ((a0<<24)|(a1<<16)|(a2<<8)|a3); + } + + for (row = 4; row > 0; row--) + data[row-1] = tmp[row-1] ^ *(--k); + } +} + diff --git a/src/extensions/crypt/aes/aes.h b/src/extensions/crypt/aes/aes.h new file mode 100644 index 0000000000..0b387352e7 --- /dev/null +++ b/src/extensions/crypt/aes/aes.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include // uint{8,16,32}_t + +/************************************************************************** + * AES declarations + **************************************************************************/ + +#define AES_MAXROUNDS 14 +#define AES_BLOCKSIZE 16 +#define AES_IV_SIZE 16 + +typedef enum +{ + AES_MODE_128, + AES_MODE_256, + AES_MODE_ENCRYPT, + AES_MODE_DECRYPT +} AES_MODE; + +typedef struct aes_key_st +{ + uint16_t rounds; + uint16_t key_size; + uint32_t ks[(AES_MAXROUNDS+1)*8]; + uint8_t iv[AES_IV_SIZE]; + AES_MODE key_mode; +} AES_CTX; + +void AES_set_key(AES_CTX *ctx, const uint8_t *key, + const uint8_t *iv, AES_MODE mode); +void AES_cbc_encrypt(AES_CTX *ctx, const uint8_t *msg, + uint8_t *out, int length); +void AES_cbc_decrypt(AES_CTX *ks, const uint8_t *in, uint8_t *out, int length); +void AES_convert_key(AES_CTX *ctx); diff --git a/src/extensions/crypt/bigint/bigint.c b/src/extensions/crypt/bigint/bigint.c new file mode 100644 index 0000000000..773f0463a1 --- /dev/null +++ b/src/extensions/crypt/bigint/bigint.c @@ -0,0 +1,1516 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @defgroup bigint_api Big Integer API + * @brief The bigint implementation as used by the axTLS project. + * + * The bigint library is for RSA encryption/decryption as well as signing. + * This code tries to minimise use of malloc/free by maintaining a small + * cache. A bigint context may maintain state by being made "permanent". + * It be be later released with a bi_depermanent() and bi_free() call. + * + * It supports the following reduction techniques: + * - Classical + * - Barrett + * - Montgomery + * + * It also implements the following: + * - Karatsuba multiplication + * - Squaring + * - Sliding window exponentiation + * - Chinese Remainder Theorem (implemented in rsa.c). + * + * All the algorithms used are pretty standard, and designed for different + * data bus sizes. Negative numbers are not dealt with at all, so a subtraction + * may need to be tested for negativity. + * + * This library steals some ideas from Jef Poskanzer + * + * and GMP . It gets most of its implementation + * detail from "The Handbook of Applied Cryptography" + * + * @{ + */ + +#include +#include +#include +//#include // !!! No in Ren-C release builds +#include +//#include "os_port.h" +#include "bigint_config.h" +#include "bigint_impl.h" +#include "bigint.h" + +#define V1 v->comps[v->size-1] /**< v1 for division */ +#define V2 v->comps[v->size-2] /**< v2 for division */ +#define U(j) tmp_u->comps[tmp_u->size-j-1] /**< uj for division */ +#define Q(j) quotient->comps[quotient->size-j-1] /**< qj for division */ + +static bigint *bi_int_multiply(BI_CTX *ctx, bigint *bi, comp i); +static bigint *bi_int_divide(BI_CTX *ctx, bigint *biR, comp denom); +static bigint *alloc(BI_CTX *ctx, int size); +static bigint *trim(bigint *bi); +static void more_comps(bigint *bi, int n); +#if defined(CONFIG_BIGINT_KARATSUBA) || defined(CONFIG_BIGINT_BARRETT) || \ + defined(CONFIG_BIGINT_MONTGOMERY) +static bigint *comp_right_shift(bigint *biR, int num_shifts); +static bigint *comp_left_shift(bigint *biR, int num_shifts); +#endif + +#ifdef CONFIG_BIGINT_CHECK_ON +static void check(const bigint *bi); +#else +#define check(A) /**< disappears in normal production mode */ +#endif + + +/** + * @brief Start a new bigint context. + * @return A bigint context. + */ +BI_CTX *bi_initialize(void) +{ + /* calloc() sets everything to zero */ + BI_CTX *ctx = (BI_CTX *)calloc(1, sizeof(BI_CTX)); + + /* the radix */ + ctx->bi_radix = alloc(ctx, 2); + ctx->bi_radix->comps[0] = 0; + ctx->bi_radix->comps[1] = 1; + bi_permanent(ctx->bi_radix); + return ctx; +} + +/** + * @brief Close the bigint context and free any resources. + * + * Free up any used memory - a check is done if all objects were not + * properly freed. + * @param ctx [in] The bigint session context. + */ +void bi_terminate(BI_CTX *ctx) +{ + bi_depermanent(ctx->bi_radix); + bi_free(ctx, ctx->bi_radix); + + if (ctx->active_count != 0) + { +#ifdef CONFIG_BIGINT_DEBUG + printf("bi_terminate: there were %d un-freed bigints\n", + ctx->active_count); +#endif + abort(); + } + + bi_clear_cache(ctx); + free(ctx); +} + +/** + *@brief Clear the memory cache. + */ +void bi_clear_cache(BI_CTX *ctx) +{ + bigint *p, *pn; + + if (ctx->free_list == NULL) + return; + + for (p = ctx->free_list; p != NULL; p = pn) + { + pn = p->next; + free(p->comps); + free(p); + } + + ctx->free_count = 0; + ctx->free_list = NULL; +} + +/** + * @brief Increment the number of references to this object. + * It does not do a full copy. + * @param bi [in] The bigint to copy. + * @return A reference to the same bigint. + */ +bigint *bi_copy(bigint *bi) +{ + check(bi); + if (bi->refs != PERMANENT) + bi->refs++; + return bi; +} + +/** + * @brief Simply make a bigint object "unfreeable" if bi_free() is called on it. + * + * For this object to be freed, bi_depermanent() must be called. + * @param bi [in] The bigint to be made permanent. + */ +void bi_permanent(bigint *bi) +{ + check(bi); + if (bi->refs != 1) + { +#ifdef CONFIG_BIGINT_DEBUG + printf("bi_permanent: refs was not 1\n"); +#endif + abort(); + } + + bi->refs = PERMANENT; +} + +/** + * @brief Take a permanent object and make it eligible for freedom. + * @param bi [in] The bigint to be made back to temporary. + */ +void bi_depermanent(bigint *bi) +{ + check(bi); + if (bi->refs != PERMANENT) + { +#ifdef CONFIG_BIGINT_DEBUG + printf("bi_depermanent: bigint was not permanent\n"); +#endif + abort(); + } + + bi->refs = 1; +} + +/** + * @brief Free a bigint object so it can be used again. + * + * The memory itself it not actually freed, just tagged as being available + * @param ctx [in] The bigint session context. + * @param bi [in] The bigint to be freed. + */ +void bi_free(BI_CTX *ctx, bigint *bi) +{ + check(bi); + if (bi->refs == PERMANENT) + { + return; + } + + if (--bi->refs > 0) + { + return; + } + + bi->next = ctx->free_list; + ctx->free_list = bi; + ctx->free_count++; + + if (--ctx->active_count < 0) + { +#ifdef CONFIG_BIGINT_DEBUG + printf("bi_free: active_count went negative " + "- double-freed bigint?\n"); +#endif + abort(); + } +} + +/** + * @brief Convert an (unsigned) integer into a bigint. + * @param ctx [in] The bigint session context. + * @param i [in] The (unsigned) integer to be converted. + * + */ +bigint *int_to_bi(BI_CTX *ctx, comp i) +{ + bigint *biR = alloc(ctx, 1); + biR->comps[0] = i; + return biR; +} + +/** + * @brief Do a full copy of the bigint object. + * @param ctx [in] The bigint session context. + * @param bi [in] The bigint object to be copied. + */ +bigint *bi_clone(BI_CTX *ctx, const bigint *bi) +{ + bigint *biR = alloc(ctx, bi->size); + check(bi); + memcpy(biR->comps, bi->comps, bi->size*COMP_BYTE_SIZE); + return biR; +} + +/** + * @brief Perform an addition operation between two bigints. + * @param ctx [in] The bigint session context. + * @param bia [in] A bigint. + * @param bib [in] Another bigint. + * @return The result of the addition. + */ +bigint *bi_add(BI_CTX *ctx, bigint *bia, bigint *bib) +{ + int n; + comp carry = 0; + comp *pa, *pb; + + check(bia); + check(bib); + + n = max(bia->size, bib->size); + more_comps(bia, n+1); + more_comps(bib, n); + pa = bia->comps; + pb = bib->comps; + + do + { + comp sl, rl, cy1; + sl = *pa + *pb++; + rl = sl + carry; + cy1 = sl < *pa; + carry = cy1 | (rl < sl); + *pa++ = rl; + } while (--n != 0); + + *pa = carry; /* do overflow */ + bi_free(ctx, bib); + return trim(bia); +} + +/** + * @brief Perform a subtraction operation between two bigints. + * @param ctx [in] The bigint session context. + * @param bia [in] A bigint. + * @param bib [in] Another bigint. + * @param is_negative [out] If defined, indicates that the result was negative. + * is_negative may be null. + * @return The result of the subtraction. The result is always positive. + */ +bigint *bi_subtract(BI_CTX *ctx, + bigint *bia, bigint *bib, int *is_negative) +{ + int n = bia->size; + comp *pa, *pb, carry = 0; + + check(bia); + check(bib); + + more_comps(bib, n); + pa = bia->comps; + pb = bib->comps; + + do + { + comp sl, rl, cy1; + sl = *pa - *pb++; + rl = sl - carry; + cy1 = sl > *pa; + carry = cy1 | (rl > sl); + *pa++ = rl; + } while (--n != 0); + + if (is_negative) /* indicate a negative result */ + { + *is_negative = carry; + } + + bi_free(ctx, trim(bib)); /* put bib back to the way it was */ + return trim(bia); +} + +/** + * Perform a multiply between a bigint an an (unsigned) integer + */ +static bigint *bi_int_multiply(BI_CTX *ctx, bigint *bia, comp b) +{ + int j = 0, n = bia->size; + bigint *biR = alloc(ctx, n + 1); + comp carry = 0; + comp *r = biR->comps; + comp *a = bia->comps; + + check(bia); + + /* clear things to start with */ + memset(r, 0, ((n+1)*COMP_BYTE_SIZE)); + + do + { + long_comp tmp = *r + (long_comp)a[j]*b + carry; + *r++ = (comp)tmp; /* downsize */ + carry = (comp)(tmp >> COMP_BIT_SIZE); + } while (++j < n); + + *r = carry; + bi_free(ctx, bia); + return trim(biR); +} + +/** + * @brief Does both division and modulo calculations. + * + * Used extensively when doing classical reduction. + * @param ctx [in] The bigint session context. + * @param u [in] A bigint which is the numerator. + * @param v [in] Either the denominator or the modulus depending on the mode. + * @param is_mod [n] Determines if this is a normal division (0) or a reduction + * (1). + * @return The result of the division/reduction. + */ +bigint *bi_divide(BI_CTX *ctx, bigint *u, bigint *v, int is_mod) +{ + int n = v->size, m = u->size-n; + int j = 0, orig_u_size = u->size; + uint8_t mod_offset = ctx->mod_offset; + comp d; + bigint *quotient, *tmp_u; + comp q_dash; + + check(u); + check(v); + + /* if doing reduction and we are < mod, then return mod */ + if (is_mod && bi_compare(v, u) > 0) + { + bi_free(ctx, v); + return u; + } + + quotient = alloc(ctx, m+1); + tmp_u = alloc(ctx, n+1); + v = trim(v); /* make sure we have no leading 0's */ + d = (comp)((long_comp)COMP_RADIX/(((long_comp)V1)+1)); + + /* clear things to start with */ + memset(quotient->comps, 0, ((quotient->size)*COMP_BYTE_SIZE)); + + /* normalise */ + if (d > 1) + { + u = bi_int_multiply(ctx, u, d); + + if (is_mod) + { + v = ctx->bi_normalised_mod[mod_offset]; + } + else + { + v = bi_int_multiply(ctx, v, d); + } + } + + if (orig_u_size == u->size) /* new digit position u0 */ + { + more_comps(u, orig_u_size + 1); + } + + do + { + /* get a temporary short version of u */ + memcpy(tmp_u->comps, &u->comps[u->size-n-1-j], (n+1)*COMP_BYTE_SIZE); + + /* calculate q' */ + if (U(0) == V1) + { + q_dash = COMP_RADIX-1; + } + else + { + q_dash = (comp)(((long_comp)U(0)*COMP_RADIX + U(1))/V1); + + if (v->size > 1 && V2) + { + /* we are implementing the following: + if (V2*q_dash > (((U(0)*COMP_RADIX + U(1) - + q_dash*V1)*COMP_RADIX) + U(2))) ... */ + comp inner = (comp)((long_comp)COMP_RADIX*U(0) + U(1) - + (long_comp)q_dash*V1); + if ((long_comp)V2*q_dash > (long_comp)inner*COMP_RADIX + U(2)) + { + q_dash--; + } + } + } + + /* multiply and subtract */ + if (q_dash) + { + int is_negative; + tmp_u = bi_subtract(ctx, tmp_u, + bi_int_multiply(ctx, bi_copy(v), q_dash), &is_negative); + more_comps(tmp_u, n+1); + + Q(j) = q_dash; + + /* add back */ + if (is_negative) + { + Q(j)--; + tmp_u = bi_add(ctx, tmp_u, bi_copy(v)); + + /* lop off the carry */ + tmp_u->size--; + v->size--; + } + } + else + { + Q(j) = 0; + } + + /* copy back to u */ + memcpy(&u->comps[u->size-n-1-j], tmp_u->comps, (n+1)*COMP_BYTE_SIZE); + } while (++j <= m); + + bi_free(ctx, tmp_u); + bi_free(ctx, v); + + if (is_mod) /* get the remainder */ + { + bi_free(ctx, quotient); + return bi_int_divide(ctx, trim(u), d); + } + else /* get the quotient */ + { + bi_free(ctx, u); + return trim(quotient); + } +} + +/* + * Perform an integer divide on a bigint. + */ +static bigint *bi_int_divide(BI_CTX *ctx, bigint *biR, comp denom) +{ + (void)ctx; // !!! Not used? + + int i = biR->size - 1; + long_comp r = 0; + + check(biR); + + do + { + r = (r<comps[i]; + biR->comps[i] = (comp)(r / denom); + r %= denom; + } while (--i >= 0); + + return trim(biR); +} + +#ifdef CONFIG_BIGINT_MONTGOMERY +/** + * There is a need for the value of integer N' such that B^-1(B-1)-N^-1N'=1, + * where B^-1(B-1) mod N=1. Actually, only the least significant part of + * N' is needed, hence the definition N0'=N' mod b. We reproduce below the + * simple algorithm from an article by Dusse and Kaliski to efficiently + * find N0' from N0 and b */ +static comp modular_inverse(bigint *bim) +{ + int i; + comp t = 1; + comp two_2_i_minus_1 = 2; /* 2^(i-1) */ + long_comp two_2_i = 4; /* 2^i */ + comp N = bim->comps[0]; + + for (i = 2; i <= COMP_BIT_SIZE; i++) + { + if ((long_comp)N*t%two_2_i >= two_2_i_minus_1) + { + t += two_2_i_minus_1; + } + + two_2_i_minus_1 <<= 1; + two_2_i <<= 1; + } + + return (comp)(COMP_RADIX-t); +} +#endif + +#if defined(CONFIG_BIGINT_KARATSUBA) || defined(CONFIG_BIGINT_BARRETT) || \ + defined(CONFIG_BIGINT_MONTGOMERY) +/** + * Take each component and shift down (in terms of components) + */ +static bigint *comp_right_shift(bigint *biR, int num_shifts) +{ + int i = biR->size-num_shifts; + comp *x = biR->comps; + comp *y = &biR->comps[num_shifts]; + + check(biR); + + if (i <= 0) /* have we completely right shifted? */ + { + biR->comps[0] = 0; /* return 0 */ + biR->size = 1; + return biR; + } + + do + { + *x++ = *y++; + } while (--i > 0); + + biR->size -= num_shifts; + return biR; +} + +/** + * Take each component and shift it up (in terms of components) + */ +static bigint *comp_left_shift(bigint *biR, int num_shifts) +{ + int i = biR->size-1; + comp *x, *y; + + check(biR); + + if (num_shifts <= 0) + { + return biR; + } + + more_comps(biR, biR->size + num_shifts); + + x = &biR->comps[i+num_shifts]; + y = &biR->comps[i]; + + do + { + *x-- = *y--; + } while (i--); + + memset(biR->comps, 0, num_shifts*COMP_BYTE_SIZE); /* zero LS comps */ + return biR; +} +#endif + +/** + * @brief Allow a binary sequence to be imported as a bigint. + * @param ctx [in] The bigint session context. + * @param data [in] The data to be converted. + * @param size [in] The number of bytes of data. + * @return A bigint representing this data. + */ +bigint *bi_import(BI_CTX *ctx, const uint8_t *data, int size) +{ + bigint *biR = alloc(ctx, (size+COMP_BYTE_SIZE-1)/COMP_BYTE_SIZE); + int i, j = 0, offset = 0; + + memset(biR->comps, 0, biR->size*COMP_BYTE_SIZE); + + for (i = size-1; i >= 0; i--) + { + biR->comps[offset] += data[i] << (j*8); + + if (++j == COMP_BYTE_SIZE) + { + j = 0; + offset ++; + } + } + + return trim(biR); +} + +#ifdef CONFIG_BIGINT_DEBUG +/** + * @brief The testharness uses this code to import text hex-streams and + * convert them into bigints. + * @param ctx [in] The bigint session context. + * @param data [in] A string consisting of hex characters. The characters must + * be in upper case. + * @return A bigint representing this data. + */ +bigint *bi_str_import(BI_CTX *ctx, const char *data) +{ + int size = strlen(data); + bigint *biR = alloc(ctx, (size+COMP_NUM_NIBBLES-1)/COMP_NUM_NIBBLES); + int i, j = 0, offset = 0; + memset(biR->comps, 0, biR->size*COMP_BYTE_SIZE); + + for (i = size-1; i >= 0; i--) + { + int num = (data[i] <= '9') ? (data[i] - '0') : (data[i] - 'A' + 10); + biR->comps[offset] += num << (j*4); + + if (++j == COMP_NUM_NIBBLES) + { + j = 0; + offset ++; + } + } + + return biR; +} + +void bi_print(const char *label, bigint *x) +{ + int i, j; + + if (x == NULL) + { + printf("%s: (null)\n", label); + return; + } + + printf("%s: (size %d)\n", label, x->size); + for (i = x->size-1; i >= 0; i--) + { + for (j = COMP_NUM_NIBBLES-1; j >= 0; j--) + { + comp mask = 0x0f << (j*4); + comp num = (x->comps[i] & mask) >> (j*4); + putc((num <= 9) ? (num + '0') : (num + 'A' - 10), stdout); + } + } + + printf("\n"); +} +#endif + +/** + * @brief Take a bigint and convert it into a byte sequence. + * + * This is useful after a decrypt operation. + * @param ctx [in] The bigint session context. + * @param x [in] The bigint to be converted. + * @param data [out] The converted data as a byte stream. + * @param size [in] The maximum size of the byte stream. Unused bytes will be + * zeroed. + */ +void bi_export(BI_CTX *ctx, bigint *x, uint8_t *data, int size) +{ + int i, j, k = size-1; + + check(x); + memset(data, 0, size); /* ensure all leading 0's are cleared */ + + for (i = 0; i < x->size; i++) + { + for (j = 0; j < COMP_BYTE_SIZE; j++) + { + comp mask = 0xff << (j*8); + int num = (x->comps[i] & mask) >> (j*8); + data[k--] = num; + + if (k < 0) + { + goto buf_done; + } + } + } +buf_done: + + bi_free(ctx, x); +} + +/** + * @brief Pre-calculate some of the expensive steps in reduction. + * + * This function should only be called once (normally when a session starts). + * When the session is over, bi_free_mod() should be called. bi_mod_power() + * relies on this function being called. + * @param ctx [in] The bigint session context. + * @param bim [in] The bigint modulus that will be used. + * @param mod_offset [in] There are three moduluii that can be stored - the + * standard modulus, and its two primes p and q. This offset refers to which + * modulus we are referring to. + * @see bi_free_mod(), bi_mod_power(). + */ +void bi_set_mod(BI_CTX *ctx, bigint *bim, int mod_offset) +{ + int k = bim->size; + comp d = (comp)((long_comp)COMP_RADIX/(((long_comp)bim->comps[k-1])+1)); +#ifdef CONFIG_BIGINT_MONTGOMERY + bigint *R, *R2; +#endif + + ctx->bi_mod[mod_offset] = bim; + bi_permanent(ctx->bi_mod[mod_offset]); + ctx->bi_normalised_mod[mod_offset] = bi_int_multiply(ctx, bim, d); + bi_permanent(ctx->bi_normalised_mod[mod_offset]); + +#if defined(CONFIG_BIGINT_MONTGOMERY) + /* set montgomery variables */ + R = comp_left_shift(bi_clone(ctx, ctx->bi_radix), k-1); /* R */ + R2 = comp_left_shift(bi_clone(ctx, ctx->bi_radix), k*2-1); /* R^2 */ + ctx->bi_RR_mod_m[mod_offset] = bi_mod(ctx, R2); /* R^2 mod m */ + ctx->bi_R_mod_m[mod_offset] = bi_mod(ctx, R); /* R mod m */ + + bi_permanent(ctx->bi_RR_mod_m[mod_offset]); + bi_permanent(ctx->bi_R_mod_m[mod_offset]); + + ctx->N0_dash[mod_offset] = modular_inverse(ctx->bi_mod[mod_offset]); + +#elif defined (CONFIG_BIGINT_BARRETT) + ctx->bi_mu[mod_offset] = + bi_divide(ctx, comp_left_shift( + bi_clone(ctx, ctx->bi_radix), k*2-1), ctx->bi_mod[mod_offset], 0); + bi_permanent(ctx->bi_mu[mod_offset]); +#endif +} + +/** + * @brief Used when cleaning various bigints at the end of a session. + * @param ctx [in] The bigint session context. + * @param mod_offset [in] The offset to use. + * @see bi_set_mod(). + */ +void bi_free_mod(BI_CTX *ctx, int mod_offset) +{ + bi_depermanent(ctx->bi_mod[mod_offset]); + bi_free(ctx, ctx->bi_mod[mod_offset]); +#if defined (CONFIG_BIGINT_MONTGOMERY) + bi_depermanent(ctx->bi_RR_mod_m[mod_offset]); + bi_depermanent(ctx->bi_R_mod_m[mod_offset]); + bi_free(ctx, ctx->bi_RR_mod_m[mod_offset]); + bi_free(ctx, ctx->bi_R_mod_m[mod_offset]); +#elif defined(CONFIG_BIGINT_BARRETT) + bi_depermanent(ctx->bi_mu[mod_offset]); + bi_free(ctx, ctx->bi_mu[mod_offset]); +#endif + bi_depermanent(ctx->bi_normalised_mod[mod_offset]); + bi_free(ctx, ctx->bi_normalised_mod[mod_offset]); +} + +/** + * Perform a standard multiplication between two bigints. + * + * Barrett reduction has no need for some parts of the product, so ignore bits + * of the multiply. This routine gives Barrett its big performance + * improvements over Classical/Montgomery reduction methods. + */ +static bigint *regular_multiply(BI_CTX *ctx, bigint *bia, bigint *bib, + int inner_partial, int outer_partial) +{ + int i = 0, j; + int n = bia->size; + int t = bib->size; + bigint *biR = alloc(ctx, n + t); + comp *sr = biR->comps; + comp *sa = bia->comps; + comp *sb = bib->comps; + + check(bia); + check(bib); + + /* clear things to start with */ + memset(biR->comps, 0, ((n+t)*COMP_BYTE_SIZE)); + + do + { + long_comp tmp; + comp carry = 0; + int r_index = i; + j = 0; + + if (outer_partial && outer_partial-i > 0 && outer_partial < n) + { + r_index = outer_partial-1; + j = outer_partial-i-1; + } + + do + { + if (inner_partial && r_index >= inner_partial) + { + break; + } + + tmp = sr[r_index] + ((long_comp)sa[j])*sb[i] + carry; + sr[r_index++] = (comp)tmp; /* downsize */ + carry = tmp >> COMP_BIT_SIZE; + } while (++j < n); + + sr[r_index] = carry; + } while (++i < t); + + bi_free(ctx, bia); + bi_free(ctx, bib); + return trim(biR); +} + +#ifdef CONFIG_BIGINT_KARATSUBA +/* + * Karatsuba improves on regular multiplication due to only 3 multiplications + * being done instead of 4. The additional additions/subtractions are O(N) + * rather than O(N^2) and so for big numbers it saves on a few operations + */ +static bigint *karatsuba(BI_CTX *ctx, bigint *bia, bigint *bib, int is_square) +{ + bigint *x0, *x1; + bigint *p0, *p1, *p2; + int m; + + if (is_square) + { + m = (bia->size + 1)/2; + } + else + { + m = (max(bia->size, bib->size) + 1)/2; + } + + x0 = bi_clone(ctx, bia); + x0->size = m; + x1 = bi_clone(ctx, bia); + comp_right_shift(x1, m); + bi_free(ctx, bia); + + /* work out the 3 partial products */ + if (is_square) + { + p0 = bi_square(ctx, bi_copy(x0)); + p2 = bi_square(ctx, bi_copy(x1)); + p1 = bi_square(ctx, bi_add(ctx, x0, x1)); + } + else /* normal multiply */ + { + bigint *y0, *y1; + y0 = bi_clone(ctx, bib); + y0->size = m; + y1 = bi_clone(ctx, bib); + comp_right_shift(y1, m); + bi_free(ctx, bib); + + p0 = bi_multiply(ctx, bi_copy(x0), bi_copy(y0)); + p2 = bi_multiply(ctx, bi_copy(x1), bi_copy(y1)); + p1 = bi_multiply(ctx, bi_add(ctx, x0, x1), bi_add(ctx, y0, y1)); + } + + p1 = bi_subtract(ctx, + bi_subtract(ctx, p1, bi_copy(p2), NULL), bi_copy(p0), NULL); + + comp_left_shift(p1, m); + comp_left_shift(p2, 2*m); + return bi_add(ctx, p1, bi_add(ctx, p0, p2)); +} +#endif + +/** + * @brief Perform a multiplication operation between two bigints. + * @param ctx [in] The bigint session context. + * @param bia [in] A bigint. + * @param bib [in] Another bigint. + * @return The result of the multiplication. + */ +bigint *bi_multiply(BI_CTX *ctx, bigint *bia, bigint *bib) +{ + check(bia); + check(bib); + +#ifdef CONFIG_BIGINT_KARATSUBA + if (min(bia->size, bib->size) < MUL_KARATSUBA_THRESH) + { + return regular_multiply(ctx, bia, bib, 0, 0); + } + + return karatsuba(ctx, bia, bib, 0); +#else + return regular_multiply(ctx, bia, bib, 0, 0); +#endif +} + +#ifdef CONFIG_BIGINT_SQUARE +/* + * Perform the actual square operion. It takes into account overflow. + */ +static bigint *regular_square(BI_CTX *ctx, bigint *bi) +{ + int t = bi->size; + int i = 0, j; + bigint *biR = alloc(ctx, t*2+1); + comp *w = biR->comps; + comp *x = bi->comps; + long_comp carry; + memset(w, 0, biR->size*COMP_BYTE_SIZE); + + do + { + long_comp tmp = w[2*i] + (long_comp)x[i]*x[i]; + w[2*i] = (comp)tmp; + carry = tmp >> COMP_BIT_SIZE; + + for (j = i+1; j < t; j++) + { + uint8_t c = 0; + long_comp xx = (long_comp)x[i]*x[j]; + if ((COMP_MAX-xx) < xx) + c = 1; + + tmp = (xx<<1); + + if ((COMP_MAX-tmp) < w[i+j]) + c = 1; + + tmp += w[i+j]; + + if ((COMP_MAX-tmp) < carry) + c = 1; + + tmp += carry; + w[i+j] = (comp)tmp; + carry = tmp >> COMP_BIT_SIZE; + + if (c) + carry += COMP_RADIX; + } + + tmp = w[i+t] + carry; + w[i+t] = (comp)tmp; + w[i+t+1] = tmp >> COMP_BIT_SIZE; + } while (++i < t); + + bi_free(ctx, bi); + return trim(biR); +} + +/** + * @brief Perform a square operation on a bigint. + * @param ctx [in] The bigint session context. + * @param bia [in] A bigint. + * @return The result of the multiplication. + */ +bigint *bi_square(BI_CTX *ctx, bigint *bia) +{ + check(bia); + +#ifdef CONFIG_BIGINT_KARATSUBA + if (bia->size < SQU_KARATSUBA_THRESH) + { + return regular_square(ctx, bia); + } + + return karatsuba(ctx, bia, NULL, 1); +#else + return regular_square(ctx, bia); +#endif +} +#endif + +/** + * @brief Compare two bigints. + * @param bia [in] A bigint. + * @param bib [in] Another bigint. + * @return -1 if smaller, 1 if larger and 0 if equal. + */ +int bi_compare(bigint *bia, bigint *bib) +{ + int r, i; + + check(bia); + check(bib); + + if (bia->size > bib->size) + r = 1; + else if (bia->size < bib->size) + r = -1; + else + { + comp *a = bia->comps; + comp *b = bib->comps; + + /* Same number of components. Compare starting from the high end + * and working down. */ + r = 0; + i = bia->size - 1; + + do + { + if (a[i] > b[i]) + { + r = 1; + break; + } + else if (a[i] < b[i]) + { + r = -1; + break; + } + } while (--i >= 0); + } + + return r; +} + +/* + * Allocate and zero more components. Does not consume bi. + */ +static void more_comps(bigint *bi, int n) +{ + if (n > bi->max_comps) + { + bi->max_comps = max(bi->max_comps * 2, n); + bi->comps = (comp*)realloc(bi->comps, bi->max_comps * COMP_BYTE_SIZE); + } + + if (n > bi->size) + { + memset(&bi->comps[bi->size], 0, (n-bi->size)*COMP_BYTE_SIZE); + } + + bi->size = n; +} + +/* + * Make a new empty bigint. It may just use an old one if one is available. + * Otherwise get one off the heap. + */ +static bigint *alloc(BI_CTX *ctx, int size) +{ + bigint *biR; + + /* Can we recycle an old bigint? */ + if (ctx->free_list != NULL) + { + biR = ctx->free_list; + ctx->free_list = biR->next; + ctx->free_count--; + + if (biR->refs != 0) + { +#ifdef CONFIG_BIGINT_DEBUG + printf("alloc: refs was not 0\n"); +#endif + abort(); /* create a stack trace from a core dump */ + } + + more_comps(biR, size); + } + else + { + /* No free bigints available - create a new one. */ + biR = (bigint *)malloc(sizeof(bigint)); + biR->comps = (comp*)malloc(size * COMP_BYTE_SIZE); + biR->max_comps = size; /* give some space to spare */ + } + + biR->size = size; + biR->refs = 1; + biR->next = NULL; + ctx->active_count++; + return biR; +} + +/* + * Work out the highest '1' bit in an exponent. Used when doing sliding-window + * exponentiation. + */ +static int find_max_exp_index(bigint *biexp) +{ + int i = COMP_BIT_SIZE-1; + comp shift = COMP_RADIX/2; + comp test = biexp->comps[biexp->size-1]; /* assume no leading zeroes */ + + check(biexp); + + do + { + if (test & shift) + { + return i+(biexp->size-1)*COMP_BIT_SIZE; + } + + shift >>= 1; + } while (i-- != 0); + + return -1; /* error - must have been a leading 0 */ +} + +/* + * Is a particular bit is an exponent 1 or 0? Used when doing sliding-window + * exponentiation. + */ +static int exp_bit_is_one(bigint *biexp, int offset) +{ + comp test = biexp->comps[offset / COMP_BIT_SIZE]; + int num_shifts = offset % COMP_BIT_SIZE; + comp shift = 1; + int i; + + check(biexp); + + for (i = 0; i < num_shifts; i++) + { + shift <<= 1; + } + + return (test & shift) != 0; +} + +#ifdef CONFIG_BIGINT_CHECK_ON +/* + * Perform a sanity check on bi. + */ +static void check(const bigint *bi) +{ + if (bi->refs <= 0) + { + printf("check: zero or negative refs in bigint\n"); + abort(); + } + + if (bi->next != NULL) + { + printf("check: attempt to use a bigint from " + "the free list\n"); + abort(); + } +} +#endif + +/* + * Delete any leading 0's (and allow for 0). + */ +static bigint *trim(bigint *bi) +{ + check(bi); + + while (bi->comps[bi->size-1] == 0 && bi->size > 1) + { + bi->size--; + } + + return bi; +} + +#if defined(CONFIG_BIGINT_MONTGOMERY) +/** + * @brief Perform a single montgomery reduction. + * @param ctx [in] The bigint session context. + * @param bixy [in] A bigint. + * @return The result of the montgomery reduction. + */ +bigint *bi_mont(BI_CTX *ctx, bigint *bixy) +{ + int i = 0, n; + uint8_t mod_offset = ctx->mod_offset; + bigint *bim = ctx->bi_mod[mod_offset]; + comp mod_inv = ctx->N0_dash[mod_offset]; + + check(bixy); + + if (ctx->use_classical) /* just use classical instead */ + { + return bi_mod(ctx, bixy); + } + + n = bim->size; + + do + { + bixy = bi_add(ctx, bixy, comp_left_shift( + bi_int_multiply(ctx, bim, bixy->comps[i]*mod_inv), i)); + } while (++i < n); + + comp_right_shift(bixy, n); + + if (bi_compare(bixy, bim) >= 0) + { + bixy = bi_subtract(ctx, bixy, bim, NULL); + } + + return bixy; +} + +#elif defined(CONFIG_BIGINT_BARRETT) +/* + * Stomp on the most significant components to give the illusion of a "mod base + * radix" operation + */ +static bigint *comp_mod(bigint *bi, int mod) +{ + check(bi); + + if (bi->size > mod) + { + bi->size = mod; + } + + return bi; +} + +/** + * @brief Perform a single Barrett reduction. + * @param ctx [in] The bigint session context. + * @param bi [in] A bigint. + * @return The result of the Barrett reduction. + */ +bigint *bi_barrett(BI_CTX *ctx, bigint *bi) +{ + bigint *q1, *q2, *q3, *r1, *r2, *r; + uint8_t mod_offset = ctx->mod_offset; + bigint *bim = ctx->bi_mod[mod_offset]; + int k = bim->size; + + check(bi); + check(bim); + + /* use Classical method instead - Barrett cannot help here */ + if (bi->size > k*2) + { + return bi_mod(ctx, bi); + } + + q1 = comp_right_shift(bi_clone(ctx, bi), k-1); + + /* do outer partial multiply */ + q2 = regular_multiply(ctx, q1, ctx->bi_mu[mod_offset], 0, k-1); + q3 = comp_right_shift(q2, k+1); + r1 = comp_mod(bi, k+1); + + /* do inner partial multiply */ + r2 = comp_mod(regular_multiply(ctx, q3, bim, k+1, 0), k+1); + r = bi_subtract(ctx, r1, r2, NULL); + + /* if (r >= m) r = r - m; */ + if (bi_compare(r, bim) >= 0) + { + r = bi_subtract(ctx, r, bim, NULL); + } + + return r; +} +#endif /* CONFIG_BIGINT_BARRETT */ + +#ifdef CONFIG_BIGINT_SLIDING_WINDOW +/* + * Work out g1, g3, g5, g7... etc for the sliding-window algorithm + */ +static void precompute_slide_window(BI_CTX *ctx, int window, bigint *g1) +{ + int k = 1, i; + bigint *g2; + + for (i = 0; i < window-1; i++) /* compute 2^(window-1) */ + { + k <<= 1; + } + + ctx->g = (bigint **)malloc(k*sizeof(bigint *)); + ctx->g[0] = bi_clone(ctx, g1); + bi_permanent(ctx->g[0]); + g2 = bi_residue(ctx, bi_square(ctx, ctx->g[0])); /* g^2 */ + + for (i = 1; i < k; i++) + { + ctx->g[i] = bi_residue(ctx, bi_multiply(ctx, ctx->g[i-1], bi_copy(g2))); + bi_permanent(ctx->g[i]); + } + + bi_free(ctx, g2); + ctx->window = k; +} +#endif + +/** + * @brief Perform a modular exponentiation. + * + * This function requires bi_set_mod() to have been called previously. This is + * one of the optimisations used for performance. + * @param ctx [in] The bigint session context. + * @param bi [in] The bigint on which to perform the mod power operation. + * @param biexp [in] The bigint exponent. + * @return The result of the mod exponentiation operation + * @see bi_set_mod(). + */ +bigint *bi_mod_power(BI_CTX *ctx, bigint *bi, bigint *biexp) +{ + int i = find_max_exp_index(biexp), j, window_size = 1; + bigint *biR = int_to_bi(ctx, 1); + +#if defined(CONFIG_BIGINT_MONTGOMERY) + uint8_t mod_offset = ctx->mod_offset; + if (!ctx->use_classical) + { + /* preconvert */ + bi = bi_mont(ctx, + bi_multiply(ctx, bi, ctx->bi_RR_mod_m[mod_offset])); /* x' */ + bi_free(ctx, biR); + biR = ctx->bi_R_mod_m[mod_offset]; /* A */ + } +#endif + + check(bi); + check(biexp); + +#ifdef CONFIG_BIGINT_SLIDING_WINDOW + for (j = i; j > 32; j /= 5) /* work out an optimum size */ + window_size++; + + /* work out the slide constants */ + precompute_slide_window(ctx, window_size, bi); +#else /* just one constant */ + ctx->g = (bigint **)malloc(sizeof(bigint *)); + ctx->g[0] = bi_clone(ctx, bi); + ctx->window = 1; + bi_permanent(ctx->g[0]); +#endif + + /* if sliding-window is off, then only one bit will be done at a time and + * will reduce to standard left-to-right exponentiation */ + do + { + if (exp_bit_is_one(biexp, i)) + { + int l = i-window_size+1; + int part_exp = 0; + + if (l < 0) /* LSB of exponent will always be 1 */ + l = 0; + else + { + while (exp_bit_is_one(biexp, l) == 0) + l++; /* go back up */ + } + + /* build up the section of the exponent */ + for (j = i; j >= l; j--) + { + biR = bi_residue(ctx, bi_square(ctx, biR)); + if (exp_bit_is_one(biexp, j)) + part_exp++; + + if (j != l) + part_exp <<= 1; + } + + part_exp = (part_exp-1)/2; /* adjust for array */ + biR = bi_residue(ctx, bi_multiply(ctx, biR, ctx->g[part_exp])); + i = l-1; + } + else /* square it */ + { + biR = bi_residue(ctx, bi_square(ctx, biR)); + i--; + } + } while (i >= 0); + + /* cleanup */ + for (i = 0; i < ctx->window; i++) + { + bi_depermanent(ctx->g[i]); + bi_free(ctx, ctx->g[i]); + } + + free(ctx->g); + bi_free(ctx, bi); + bi_free(ctx, biexp); +#if defined CONFIG_BIGINT_MONTGOMERY + return ctx->use_classical ? biR : bi_mont(ctx, biR); /* convert back */ +#else /* CONFIG_BIGINT_CLASSICAL or CONFIG_BIGINT_BARRETT */ + return biR; +#endif +} + +#ifdef CONFIG_SSL_CERT_VERIFICATION +/** + * @brief Perform a modular exponentiation using a temporary modulus. + * + * We need this function to check the signatures of certificates. The modulus + * of this function is temporary as it's just used for authentication. + * @param ctx [in] The bigint session context. + * @param bi [in] The bigint to perform the exp/mod. + * @param bim [in] The temporary modulus. + * @param biexp [in] The bigint exponent. + * @return The result of the mod exponentiation operation + * @see bi_set_mod(). + */ +bigint *bi_mod_power2(BI_CTX *ctx, bigint *bi, bigint *bim, bigint *biexp) +{ + bigint *biR, *tmp_biR; + + /* Set up a temporary bigint context and transfer what we need between + * them. We need to do this since we want to keep the original modulus + * which is already in this context. This operation is only called when + * doing peer verification, and so is not expensive :-) */ + BI_CTX *tmp_ctx = bi_initialize(); + bi_set_mod(tmp_ctx, bi_clone(tmp_ctx, bim), BIGINT_M_OFFSET); + tmp_biR = bi_mod_power(tmp_ctx, + bi_clone(tmp_ctx, bi), + bi_clone(tmp_ctx, biexp)); + biR = bi_clone(ctx, tmp_biR); + bi_free(tmp_ctx, tmp_biR); + bi_free_mod(tmp_ctx, BIGINT_M_OFFSET); + bi_terminate(tmp_ctx); + + bi_free(ctx, bi); + bi_free(ctx, bim); + bi_free(ctx, biexp); + return biR; +} +#endif + +#ifdef CONFIG_BIGINT_CRT +/** + * @brief Use the Chinese Remainder Theorem to quickly perform RSA decrypts. + * + * @param ctx [in] The bigint session context. + * @param bi [in] The bigint to perform the exp/mod. + * @param dP [in] CRT's dP bigint + * @param dQ [in] CRT's dQ bigint + * @param p [in] CRT's p bigint + * @param q [in] CRT's q bigint + * @param qInv [in] CRT's qInv bigint + * @return The result of the CRT operation + */ +bigint *bi_crt(BI_CTX *ctx, bigint *bi, + bigint *dP, bigint *dQ, + bigint *p, bigint *q, bigint *qInv) +{ + bigint *m1, *m2, *h; + + /* Montgomery has a condition the 0 < x, y < m and these products violate + * that condition. So disable Montgomery when using CRT */ +#if defined(CONFIG_BIGINT_MONTGOMERY) + ctx->use_classical = 1; +#endif + ctx->mod_offset = BIGINT_P_OFFSET; + m1 = bi_mod_power(ctx, bi_copy(bi), dP); + + ctx->mod_offset = BIGINT_Q_OFFSET; + m2 = bi_mod_power(ctx, bi, dQ); + + h = bi_subtract(ctx, bi_add(ctx, m1, p), bi_copy(m2), NULL); + h = bi_multiply(ctx, h, qInv); + ctx->mod_offset = BIGINT_P_OFFSET; + h = bi_residue(ctx, h); +#if defined(CONFIG_BIGINT_MONTGOMERY) + ctx->use_classical = 0; /* reset for any further operation */ +#endif + return bi_add(ctx, m2, bi_multiply(ctx, q, h)); +} +#endif +/** @} */ diff --git a/src/extensions/crypt/bigint/bigint.h b/src/extensions/crypt/bigint/bigint.h new file mode 100644 index 0000000000..e58f81dc01 --- /dev/null +++ b/src/extensions/crypt/bigint/bigint.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BIGINT_HEADER +#define BIGINT_HEADER + +#include "bigint_config.h" +#include "bigint_impl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +BI_CTX *bi_initialize(void); +void bi_terminate(BI_CTX *ctx); +void bi_permanent(bigint *bi); +void bi_depermanent(bigint *bi); +void bi_clear_cache(BI_CTX *ctx); +void bi_free(BI_CTX *ctx, bigint *bi); +bigint *bi_copy(bigint *bi); +bigint *bi_clone(BI_CTX *ctx, const bigint *bi); +void bi_export(BI_CTX *ctx, bigint *bi, uint8_t *data, int size); +bigint *bi_import(BI_CTX *ctx, const uint8_t *data, int len); +bigint *int_to_bi(BI_CTX *ctx, comp i); + +/* the functions that actually do something interesting */ +bigint *bi_add(BI_CTX *ctx, bigint *bia, bigint *bib); +bigint *bi_subtract(BI_CTX *ctx, bigint *bia, + bigint *bib, int *is_negative); +bigint *bi_divide(BI_CTX *ctx, bigint *bia, bigint *bim, int is_mod); +bigint *bi_multiply(BI_CTX *ctx, bigint *bia, bigint *bib); +bigint *bi_mod_power(BI_CTX *ctx, bigint *bi, bigint *biexp); +bigint *bi_mod_power2(BI_CTX *ctx, bigint *bi, bigint *bim, bigint *biexp); +int bi_compare(bigint *bia, bigint *bib); +void bi_set_mod(BI_CTX *ctx, bigint *bim, int mod_offset); +void bi_free_mod(BI_CTX *ctx, int mod_offset); + +#ifdef CONFIG_BIGINT_DEBUG +void bi_print(const char *label, bigint *bi); +bigint *bi_str_import(BI_CTX *ctx, const char *data); +#endif + +/** + * @def bi_mod + * Find the residue of B. bi_set_mod() must be called before hand. + */ +#define bi_mod(A, B) bi_divide(A, B, ctx->bi_mod[ctx->mod_offset], 1) + +/** + * bi_residue() is technically the same as bi_mod(), but it uses the + * appropriate reduction technique (which is bi_mod() when doing classical + * reduction). + */ +#if defined(CONFIG_BIGINT_MONTGOMERY) +#define bi_residue(A, B) bi_mont(A, B) +bigint *bi_mont(BI_CTX *ctx, bigint *bixy); +#elif defined(CONFIG_BIGINT_BARRETT) +#define bi_residue(A, B) bi_barrett(A, B) +bigint *bi_barrett(BI_CTX *ctx, bigint *bi); +#else /* if defined(CONFIG_BIGINT_CLASSICAL) */ +#define bi_residue(A, B) bi_mod(A, B) +#endif + +#ifdef CONFIG_BIGINT_SQUARE +bigint *bi_square(BI_CTX *ctx, bigint *bi); +#else +#define bi_square(A, B) bi_multiply(A, bi_copy(B), B) +#endif + +#ifdef CONFIG_BIGINT_CRT +bigint *bi_crt(BI_CTX *ctx, bigint *bi, + bigint *dP, bigint *dQ, + bigint *p, bigint *q, + bigint *qInv); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/extensions/crypt/bigint/bigint_config.h b/src/extensions/crypt/bigint/bigint_config.h new file mode 100644 index 0000000000..81861ac859 --- /dev/null +++ b/src/extensions/crypt/bigint/bigint_config.h @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * BigInt Options + */ + +/* + CONFIG_BIGINT_DEBUG + Enable diagnostics. Most of the extra size in this mode is + due to the storage of various strings that are used. +*/ +#undef CONFIG_BIGINT_DEBUG + +/* + CONFIG_BIGINT_CLASSICAL + Classical uses standard division. It has no limitations and is + theoretically the slowest due to the divisions used. For this particular + implementation it is surprisingly quite fast. +*/ +#undef CONFIG_BIGINT_CLASSICAL + +/* + CONFIG_BIGINT_MONTGOMERY + Montgomery uses simple addition and multiplication to achieve its + performance. It has the limitation that 0 <= x, y < m, and so is not + used when CRT is active. +*/ +#undef CONFIG_BIGINT_MONTGOMERY + +/* + CONFIG_BIGINT_BARRETT + Barrett performs expensive precomputation before reduction and partial + multiplies for computational speed. + + It is about 40% faster than Classical/Montgomery with the expense of + about 2kB, and so this option is normally selected. +*/ +#define CONFIG_BIGINT_BARRETT 1 + +/* + CONFIG_BIGINT_CRT + Uses a number of extra coefficients from the private key to improve the + performance of a decryption. This feature is one of the most + significant performance improvements (it reduces a decryption time by + over 3 times). + This option should be selected. +*/ +#define CONFIG_BIGINT_CRT 1 + +/* + CONFIG_BIGINT_KARATSUBA + Uses 3 multiplications (plus a number of additions/subtractions) + instead of 4. Multiplications are O(N^2) but addition/subtraction + is O(N) hence for large numbers is beneficial. For this project, the + effect was only useful for 4096 bit keys (for 32 bit processors). For + 8 bit processors this option might be a possibility. + It costs about 2kB to enable it. +*/ +#undef CONFIG_BIGINT_KARATSUBA + +/* + MUL_KARATSUBA_THRESH + The minimum number of components needed before Karasuba muliplication + is used. + + This is very dependent on the speed/implementation of bi_add()/ + bi_subtract(). There is a bit of trial and error here and will be + at a different point for different architectures. +*/ +#define MUL_KARATSUBA_THRESH + +/* + SQU_KARATSUBA_THRESH + The minimum number of components needed before Karatsuba squaring + is used. + + This is very dependent on the speed/implementation of bi_add()/ + bi_subtract(). There is a bit of trial and error here and will be + at a different point for different architectures. +*/ +#define SQU_KARATSUBA_THRESH + +/* + CONFIG_BIGINT_SLIDING_WINDOW + Allow Sliding-Window Exponentiation to be used. + Potentially processes more than 1 bit at a time when doing + exponentiation. The sliding-window technique reduces the number of + precomputations compared to other precomputed techniques. + It results in a considerable performance improvement with it enabled + (it halves the decryption time) and so should be selected. +*/ +//NOTE: the sliding window optimization doesn't work with Diffie-Hellman from some reason. Needs to be checked why --Cyphre +//#define CONFIG_BIGINT_SLIDING_WINDOW 1 +#undef CONFIG_BIGINT_SLIDING_WINDOW + +/* + CONFIG_BIGINT_SQUARE + Allow squaring to be used instead of a multiplication. It uses + 1/2 of the standard multiplies to obtain its performance. + It gives a 20% speed improvement overall and so should be selected. +*/ +#define CONFIG_BIGINT_SQUARE 1 + +/* + CONFIG_BIGINT_CHECK_ON + This is used when developing bigint algorithms. It performs a sanity + check on all operations at the expense of speed. + This option is only selected when developing and should normally be + turned off. +*/ +#undef CONFIG_BIGINT_CHECK_ON + +/* + CONFIG_INTEGER_32BIT + The native integer size is 32 bits or higher. +*/ +#define CONFIG_INTEGER_32BIT 1 + +/* + CONFIG_INTEGER_16BIT + The native integer size is 16 bits. +*/ +#undef CONFIG_INTEGER_16BIT + +/* + CONFIG_INTEGER_8BIT + The native integer size is 8 bits. +*/ +#undef CONFIG_INTEGER_8BIT diff --git a/src/extensions/crypt/bigint/bigint_impl.h b/src/extensions/crypt/bigint/bigint_impl.h new file mode 100644 index 0000000000..f65d9dffcd --- /dev/null +++ b/src/extensions/crypt/bigint/bigint_impl.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#ifndef BIGINT_IMPL_HEADER +#define BIGINT_IMPL_HEADER + +/* Maintain a number of precomputed variables when doing reduction */ +#define BIGINT_M_OFFSET 0 /**< Normal modulo offset. */ +#ifdef CONFIG_BIGINT_CRT +#define BIGINT_P_OFFSET 1 /**< p modulo offset. */ +#define BIGINT_Q_OFFSET 2 /**< q module offset. */ +#define BIGINT_NUM_MODS 3 /**< The number of modulus constants used. */ +#else +#define BIGINT_NUM_MODS 1 +#endif + +/* Architecture specific functions for big ints */ +#if defined(CONFIG_INTEGER_8BIT) +#define COMP_RADIX 256U /**< Max component + 1 */ +#define COMP_MAX 0xFFFFU/**< (Max dbl comp -1) */ +#define COMP_BIT_SIZE 8 /**< Number of bits in a component. */ +#define COMP_BYTE_SIZE 1 /**< Number of bytes in a component. */ +#define COMP_NUM_NIBBLES 2 /**< Used For diagnostics only. */ +typedef uint8_t comp; /**< A single precision component. */ +typedef uint16_t long_comp; /**< A double precision component. */ +typedef int16_t slong_comp; /**< A signed double precision component. */ +#elif defined(CONFIG_INTEGER_16BIT) +#define COMP_RADIX 65536U /**< Max component + 1 */ +#define COMP_MAX 0xFFFFFFFFU/**< (Max dbl comp -1) */ +#define COMP_BIT_SIZE 16 /**< Number of bits in a component. */ +#define COMP_BYTE_SIZE 2 /**< Number of bytes in a component. */ +#define COMP_NUM_NIBBLES 4 /**< Used For diagnostics only. */ +typedef uint16_t comp; /**< A single precision component. */ +typedef uint32_t long_comp; /**< A double precision component. */ +typedef int32_t slong_comp; /**< A signed double precision component. */ +#else /* regular 32 bit */ +#ifdef WIN32 +#define COMP_RADIX 4294967296ULL +#define COMP_MAX 0xFFFFFFFFFFFFFFFFULL +#else +#define COMP_RADIX 4294967296ULL /**< Max component + 1 */ +#define COMP_MAX 0xFFFFFFFFFFFFFFFFULL/**< (Max dbl comp -1) */ +#endif +#define COMP_BIT_SIZE 32 /**< Number of bits in a component. */ +#define COMP_BYTE_SIZE 4 /**< Number of bytes in a component. */ +#define COMP_NUM_NIBBLES 8 /**< Used For diagnostics only. */ +typedef uint32_t comp; /**< A single precision component. */ +typedef uint64_t long_comp; /**< A double precision component. */ +typedef int64_t slong_comp; /**< A signed double precision component. */ +#endif + +/** + * @struct _bigint + * @brief A big integer basic object + */ +struct _bigint +{ + struct _bigint* next; /**< The next bigint in the cache. */ + short size; /**< The number of components in this bigint. */ + short max_comps; /**< The heapsize allocated for this bigint */ + int refs; /**< An internal reference count. */ + comp* comps; /**< A ptr to the actual component data */ +}; + +typedef struct _bigint bigint; /**< An alias for _bigint */ + +/** + * Maintains the state of the cache, and a number of variables used in + * reduction. + */ +typedef struct /**< A big integer "session" context. */ +{ + bigint *active_list; /**< Bigints currently used. */ + bigint *free_list; /**< Bigints not used. */ + bigint *bi_radix; /**< The radix used. */ + bigint *bi_mod[BIGINT_NUM_MODS]; /**< modulus */ + +#if defined(CONFIG_BIGINT_MONTGOMERY) + bigint *bi_RR_mod_m[BIGINT_NUM_MODS]; /**< R^2 mod m */ + bigint *bi_R_mod_m[BIGINT_NUM_MODS]; /**< R mod m */ + comp N0_dash[BIGINT_NUM_MODS]; +#elif defined(CONFIG_BIGINT_BARRETT) + bigint *bi_mu[BIGINT_NUM_MODS]; /**< Storage for mu */ +#endif + bigint *bi_normalised_mod[BIGINT_NUM_MODS]; /**< Normalised mod storage. */ + bigint **g; /**< Used by sliding-window. */ + int window; /**< The size of the sliding window */ + int active_count; /**< Number of active bigints. */ + int free_count; /**< Number of free bigints. */ + +#ifdef CONFIG_BIGINT_MONTGOMERY + uint8_t use_classical; /**< Use classical reduction. */ +#endif + uint8_t mod_offset; /**< The mod offset we are using */ +} BI_CTX; + +//#ifndef WIN32 +#ifndef max + #define max(a,b) ((a)>(b)?(a):(b)) /**< Find the maximum of 2 numbers. */ +#endif +#ifndef min +#define min(a,b) ((a)<(b)?(a):(b)) /**< Find the minimum of 2 numbers. */ +#endif +//#endif + +#define PERMANENT 0x7FFF55AA /**< A magic number for permanents. */ + +#endif diff --git a/src/extensions/crypt/dh/dh.c b/src/extensions/crypt/dh/dh.c new file mode 100644 index 0000000000..ffc87ee1f4 --- /dev/null +++ b/src/extensions/crypt/dh/dh.c @@ -0,0 +1,73 @@ +/* +Simple implementation of Diffie-Hellman algorithm (c) 2013 Richard Smolak +The code uses Bigint implementation Copyright (c) 2007, Cameron Rich +*/ + +#include "dh.h" +#include "../rsa/rsa.h" + +void DH_generate_key(DH_CTX *dh_ctx) +{ + BI_CTX *bi_ctx = bi_initialize(); + int len = dh_ctx->len; + bigint *p = bi_import(bi_ctx, dh_ctx->p, len); //p modulus + bigint *g = bi_import(bi_ctx, dh_ctx->g, dh_ctx->glen); //generator + bigint *x, *gx; + + bi_permanent(g); + + //generate private key X + get_random_NZ(len, dh_ctx->x); + x = bi_import(bi_ctx, dh_ctx->x, len); + bi_permanent(x); + + //calculate public key gx = g^x mod p + bi_set_mod(bi_ctx, p, BIGINT_M_OFFSET); + bi_ctx->mod_offset = BIGINT_M_OFFSET; + gx = bi_mod_power(bi_ctx, g, x); + bi_permanent(gx); + + bi_export(bi_ctx, x, dh_ctx->x, len); + bi_export(bi_ctx, gx, dh_ctx->gx, len); + + bi_depermanent(g); + bi_depermanent(x); + bi_depermanent(gx); + bi_free(bi_ctx, g); + bi_free(bi_ctx, x); + bi_free(bi_ctx, gx); + + bi_free_mod(bi_ctx, BIGINT_M_OFFSET); + bi_terminate(bi_ctx); +} + +void DH_compute_key(DH_CTX *dh_ctx) +{ + BI_CTX *bi_ctx = bi_initialize(); + int len = dh_ctx->len; + bigint *p = bi_import(bi_ctx, dh_ctx->p, len); //p modulus + bigint *x = bi_import(bi_ctx, dh_ctx->x, len); //private key + bigint *gy = bi_import(bi_ctx, dh_ctx->gy, len); //public key(peer) + bigint *k; //negotiated(session) key + + bi_permanent(x); + bi_permanent(gy); + + //calculate session key k = gy^x mod p + bi_set_mod(bi_ctx, p, BIGINT_M_OFFSET); + bi_ctx->mod_offset = BIGINT_M_OFFSET; + k = bi_mod_power(bi_ctx, gy, x); + bi_permanent(k); + + bi_export(bi_ctx, k, dh_ctx->k, len); + + bi_depermanent(x); + bi_depermanent(gy); + bi_depermanent(k); + bi_free(bi_ctx, x); + bi_free(bi_ctx, gy); + bi_free(bi_ctx, k); + + bi_free_mod(bi_ctx, BIGINT_M_OFFSET); + bi_terminate(bi_ctx); +} diff --git a/src/extensions/crypt/dh/dh.h b/src/extensions/crypt/dh/dh.h new file mode 100644 index 0000000000..f5a2334388 --- /dev/null +++ b/src/extensions/crypt/dh/dh.h @@ -0,0 +1,30 @@ +/* +Simple implementation of Diffie-Hellman algorithm (c) 2013 Richard Smolak +The code uses Bigint implementation Copyright (c) 2007, Cameron Rich +*/ + +#include +#include "../bigint/bigint.h" + +typedef struct +{ + int len; //length of keys in bytes + int glen; //length of generator in bytes + uint8_t *p; // prime modulus + uint8_t *g; // generator + uint8_t *x; // private key + uint8_t *gx; // public key(self) + uint8_t *gy; // public key(peer) + uint8_t *k; // negotiated key +} +DH_CTX; + + +void DH_generate_key( + DH_CTX *dh_ctx +); + +void DH_compute_key( + DH_CTX *dh_ctx +); + diff --git a/src/extensions/crypt/ext-crypt-init.reb b/src/extensions/crypt/ext-crypt-init.reb new file mode 100644 index 0000000000..520dcb25a2 --- /dev/null +++ b/src/extensions/crypt/ext-crypt-init.reb @@ -0,0 +1,56 @@ +REBOL [ + Title: "Crypt Extension" + name: 'Crypt + type: 'Extension + version: 1.0.0 + license: {Apache 2.0} +] +hmac-sha256: function [{computes the hmac-sha256 for message m using key k} + k [binary!] m [binary!]][ + key: copy k + message: copy m + blocksize: 64 + if (length key) > blocksize [ + key: sha256 key + ] + if (length key) < blocksize [ + insert/dup tail key #{00} (blocksize - length key) + ] + insert/dup opad: copy #{} #{5C} blocksize + insert/dup ipad: copy #{} #{36} blocksize + o_key_pad: XOR~ opad key + i_key_pad: XOR~ ipad key + sha256 join-of o_key_pad sha256 join-of i_key_pad message +] + +append lib compose [rsa-make-key: (func [ + {Creates a key object for RSA algorithm.} +][ + has [ + n: ;modulus + e: ;public exponent + d: ;private exponent + p: ;prime num 1 + q: ;prime num 2 + dp: ;CRT exponent 1 + dq: ;CRT exponent 2 + qinv: ;CRT coefficient + _ + ] +])] + +append lib compose [dh-make-key: (func [ + {Creates a key object for Diffie-Hellman algorithm.} +;NOT YET IMPLEMENTED +; /generate +; size [integer!] \"Key length\" +; generator [integer!] \"Generator number\" +][ + has [ + priv-key: ;private key + pub-key: ;public key + g: ;generator + p: ;prime modulus + _ + ] +])] diff --git a/src/extensions/crypt/ext-crypt.c b/src/extensions/crypt/ext-crypt.c new file mode 100644 index 0000000000..c29a27220e --- /dev/null +++ b/src/extensions/crypt/ext-crypt.c @@ -0,0 +1,56 @@ +// +// File: %ext-crypt.c +// Summary: "Crypt functions" +// Section: Extension +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ================================================================== +// +#include "sys-core.h" +#include "sys-ext.h" + +#include "tmp-ext-crypt-init.inc" + +void Init_Crypto(void); +void Shutdown_Crypto(void); + +#define MODULE_INCLUDE_DECLARATION_ONLY +#include "tmp-mod-crypt-last.h" + +DEFINE_EXT_INIT_COMPRESSED(Crypt, //name of the extension + script_bytes, // REBOL script for the extension in the source form + { + // init all modules in this extension + Init_Crypto(); + int init = CALL_MODULE_INIT(Crypt); + if (init < 0) return init; + } +) + +DEFINE_EXT_QUIT(Crypt, +{ + Shutdown_Crypto(); + return CALL_MODULE_QUIT(Crypt); +} +) diff --git a/src/extensions/crypt/mod-crypt.c b/src/extensions/crypt/mod-crypt.c new file mode 100644 index 0000000000..c27cdc6c14 --- /dev/null +++ b/src/extensions/crypt/mod-crypt.c @@ -0,0 +1,834 @@ +// +// File: %mod-crypt.c +// Summary: "Native Functions for cryptography" +// Section: Extension +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The original cryptography additions to Rebol were done by Saphirion, at +// a time prior to Rebol's open sourcing. They had to go through a brittle, +// incomplete, and difficult to read API for extending the interpreter with +// C code. +// +// This contains a simplification of %host-core.c, written directly to the +// native API. It also includes the longstanding (but not standard, and not +// particularly secure) ENCLOAK and DECLOAK operations from R3-Alpha. +// + +#include "rc4/rc4.h" +#include "rsa/rsa.h" // defines gCryptProv and rng_fd (used in Init/Shutdown) +#include "dh/dh.h" +#include "aes/aes.h" + +#ifdef IS_ERROR +#undef IS_ERROR //winerror.h defines this, so undef it to avoid the warning +#endif +#include "sys-core.h" +#include "sys-ext.h" + +#include "sha256/sha256.h" // depends on Reb-C for REBCNT, REBYTE + +#include "tmp-mod-crypt-first.h" + +// +// Init_Crypto: C +// +void Init_Crypto(void) +{ +#ifdef TO_WINDOWS + if (!CryptAcquireContextW( + &gCryptProv, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT + )) { + // !!! There is no good way to return failure here as the + // routine is designed, and it appears that in some cases + // a zero initialization worked in the past. Assert in the + // debug build but continue silently otherwise. + assert(FALSE); + gCryptProv = 0; + } +#else + rng_fd = open("/dev/urandom", O_RDONLY); + if (rng_fd == -1) { + // We don't crash the release client now, but we will later + // if they try to generate random numbers + assert(FALSE); + } +#endif +} + + +// +// Shutdown_Crypto: C +// +void Shutdown_Crypto(void) +{ +#ifdef TO_WINDOWS + if (gCryptProv != 0) + CryptReleaseContext(gCryptProv, 0); +#else + if (rng_fd != -1) + close(rng_fd); +#endif +} + + +static void cleanup_rc4_ctx(const REBVAL *v) +{ + RC4_CTX *rc4_ctx = VAL_HANDLE_POINTER(RC4_CTX, v); + FREE(RC4_CTX, rc4_ctx); +} + + +// +// rc4: native/export [ +// +// "Encrypt/decrypt data (modifies) using RC4 algorithm." +// +// return: [handle!] +// "Returns stream cipher context handle." +// /key +// "Provided only for the first time to get stream HANDLE!" +// crypt-key [binary!] +// "Crypt key." +// /stream +// ctx [handle!] +// "Stream cipher context." +// data [binary!] +// "Data to encrypt/decrypt." +// ] +// new-errors: [ +// key-or-stream-required: {Refinement /key or /stream has to be present} +// invalid-rc4-context: [{Not a RC4 context:} :arg1] +// ] +// +static REBNATIVE(rc4) +{ + INCLUDE_PARAMS_OF_RC4; + + if (REF(stream)) { + REBVAL *data = ARG(data); + + if (VAL_HANDLE_CLEANER(ARG(ctx)) != cleanup_rc4_ctx) + fail (Error(RE_EXT_CRYPT_INVALID_RC4_CONTEXT, ARG(ctx))); + + RC4_CTX *rc4_ctx = VAL_HANDLE_POINTER(RC4_CTX, ARG(ctx)); + + RC4_crypt( + rc4_ctx, + VAL_BIN_AT(data), // input "message" + VAL_BIN_AT(data), // output (same, since it modifies) + VAL_LEN_AT(data) + ); + + // In %host-core.c this used to fall through to return the first arg, + // a refinement, which was true in this case. :-/ + // + return R_TRUE; + } + + if (IS_BINARY(ARG(crypt_key))) { // Key defined - setup new context + RC4_CTX *rc4_ctx = ALLOC_ZEROFILL(RC4_CTX); + + RC4_setup(rc4_ctx, VAL_BIN_AT(ARG(key)), VAL_LEN_AT(ARG(key))); + + Init_Handle_Managed(D_OUT, rc4_ctx, 0, &cleanup_rc4_ctx); + return R_OUT; + } + + fail (Error(RE_EXT_CRYPT_KEY_OR_STREAM_REQUIRED)); +} + + +// +// rsa: native/export [ +// +// "Encrypt/decrypt data using the RSA algorithm." +// +// data [binary!] +// key-object [object!] +// /decrypt +// "Decrypts the data (default is to encrypt)" +// /private +// "Uses an RSA private key (default is a public key)" +// /padding +// "Selects the type of padding to use" +// padding-type [word! blank!] +// "Type of padding. Available values: PKCS1 or NONE" +// ] +// new-words: [n e d p q dp dq qinv pkcs1] +// new-errors: [ +// invalid-key-field: [{Unrecognized field in the key object:} :arg1] +// invalid-key-data: [{Invalid data in the key object:} :arg1 {for} :arg2] +// invalid-key: [{No valid key in the object:} :obj] +// decryption-failure: [{Failed to decrypt:} :arg1] +// encryption-failure: [{Failed to encrypt:} :arg1] +// ] +// +static REBNATIVE(rsa) +{ + INCLUDE_PARAMS_OF_RSA; + + REBOOL padding; + if (REF(padding)) + padding = NOT(IS_BLANK(ARG(padding_type))); + else + padding = TRUE; // PKCS1 is on by default + + REBYTE *n = NULL; + REBYTE *e = NULL; + REBYTE *d = NULL; + REBYTE *p = NULL; + REBYTE *q = NULL; + REBYTE *dp = NULL; + REBYTE *dq = NULL; + REBYTE *qinv = NULL; + + REBINT n_len = 0; + REBINT e_len = 0; + REBINT d_len = 0; + REBINT p_len = 0; + REBINT q_len = 0; + REBINT dp_len = 0; + REBINT dq_len = 0; + REBINT qinv_len = 0; + + REBCTX *obj = VAL_CONTEXT(ARG(key_object)); + + REBVAL *key = CTX_KEYS_HEAD(obj); + REBVAL *var = CTX_VARS_HEAD(obj); + + for (; NOT_END(key); ++key, ++var) { + if (VAL_KEY_SYM(key) == SYM_SELF //object may have a 'self key that referring to itself + || IS_BLANK(var) //some fields are initialized to blank + ) + continue; + + if (!IS_BINARY(var)) + fail (Error(RE_EXT_CRYPT_INVALID_KEY_DATA, var, key)); + + REBSTR* word = VAL_KEY_CANON(key); + if (word == CRYPT_WORD_N) { + n = VAL_BIN_AT(var); + n_len = VAL_LEN_AT(var); + } + else if (word == CRYPT_WORD_E) { + e = VAL_BIN_AT(var); + e_len = VAL_LEN_AT(var); + } + else if (word == CRYPT_WORD_D) { + d = VAL_BIN_AT(var); + d_len = VAL_LEN_AT(var); + } + else if (word == CRYPT_WORD_P) { + p = VAL_BIN_AT(var); + p_len = VAL_LEN_AT(var); + } + else if (word == CRYPT_WORD_Q) { + q = VAL_BIN_AT(var); + q_len = VAL_LEN_AT(var); + break; + } + else if (word == CRYPT_WORD_DP) { + dp = VAL_BIN_AT(var); + dp_len = VAL_LEN_AT(var); + } + else if (word == CRYPT_WORD_DQ) { + dq = VAL_BIN_AT(var); + dq_len = VAL_LEN_AT(var); + } + else if (word == CRYPT_WORD_QINV) { + qinv = VAL_BIN_AT(var); + qinv_len = VAL_LEN_AT(var); + } + else { + fail (Error(RE_EXT_CRYPT_INVALID_KEY_FIELD, key)); + } + } + + if (!n || !e) + fail (Error(RE_EXT_CRYPT_INVALID_KEY, ARG(key_object))); + + RSA_CTX *rsa_ctx = NULL; + + REBINT binary_len; + if (REF(private)) { + if (!d) + return R_BLANK; + + RSA_priv_key_new( + &rsa_ctx, n, n_len, e, e_len, d, d_len, + p, p_len, q, q_len, dp, dp_len, dq, dq_len, qinv, qinv_len + ); + binary_len = d_len; + } + else { + RSA_pub_key_new(&rsa_ctx, n, n_len, e, e_len); + binary_len = n_len; + } + + REBYTE *dataBuffer = VAL_BIN_AT(ARG(data)); + REBINT data_len = VAL_LEN_AT(ARG(data)); + + BI_CTX *bi_ctx = rsa_ctx->bi_ctx; + bigint *data_bi = bi_import(bi_ctx, dataBuffer, data_len); + + REBSER *binary = Make_Binary(binary_len); + + if (REF(decrypt)) { + binary_len = RSA_decrypt( + rsa_ctx, + dataBuffer, + BIN_HEAD(binary), + REF(private) ? 1 : 0, + padding ? 1 : 0 + ); + + if (binary_len == -1) { + bi_free(rsa_ctx->bi_ctx, data_bi); + RSA_free(rsa_ctx); + + Free_Series(binary); + fail (Error(RE_EXT_CRYPT_DECRYPTION_FAILURE, ARG(data))); + } + } + else { + if ( + -1 == RSA_encrypt( + rsa_ctx, + dataBuffer, + data_len, + BIN_HEAD(binary), + REF(private) ? 1 : 0, + padding ? 1 : 0 + ) + ){ + bi_free(rsa_ctx->bi_ctx, data_bi); + RSA_free(rsa_ctx); + + Free_Series(binary); + fail (Error(RE_EXT_CRYPT_ENCRYPTION_FAILURE, ARG(data))); + } + } + + SET_SERIES_LEN(binary, binary_len); + + bi_free(rsa_ctx->bi_ctx, data_bi); + RSA_free(rsa_ctx); + + Init_Binary(D_OUT, binary); + return R_OUT; +} + + +// +// dh-generate-key: native/export [ +// +// "Generates a new DH private/public key pair." +// +// return: [] +// obj [object!] +// "The Diffie-Hellman key object, with generator(g) and modulus(p)" +// ] +// new-words: [priv-key pub-key p g] +// +static REBNATIVE(dh_generate_key) +{ + INCLUDE_PARAMS_OF_DH_GENERATE_KEY; + + DH_CTX dh_ctx; + memset(&dh_ctx, 0, sizeof(dh_ctx)); + + REBCTX *obj = VAL_CONTEXT(ARG(obj)); + + REBVAL *key = CTX_KEYS_HEAD(obj); + REBVAL *var = CTX_VARS_HEAD(obj); + + for (; NOT_END(key); ++key, ++var) { + if (VAL_KEY_SYM(key) == SYM_SELF //object may have a 'self key that referring to itself + || IS_BLANK(var) //some fields are initialized to blank + ) + continue; + + if (!IS_BINARY(var)) + fail (Error(RE_EXT_CRYPT_INVALID_KEY_DATA, var, key)); + + REBSTR* word = VAL_KEY_CANON(key); + if (word == CRYPT_WORD_P) { + dh_ctx.p = VAL_BIN_AT(var); + dh_ctx.len = VAL_LEN_AT(var); + break; + } + else if (word == CRYPT_WORD_G) { + dh_ctx.g = VAL_BIN_AT(var); + dh_ctx.glen = VAL_LEN_AT(var); + } + else { + fail (Error(RE_EXT_CRYPT_INVALID_KEY_FIELD, key)); + } + } + + if (!dh_ctx.p || !dh_ctx.g) + fail (Error(RE_EXT_CRYPT_INVALID_KEY, ARG(obj))); + + // allocate new BINARY! for private key + // + REBSER *priv_bin = Make_Binary(dh_ctx.len); + dh_ctx.x = BIN_HEAD(priv_bin); + memset(dh_ctx.x, 0, dh_ctx.len); + SET_SERIES_LEN(priv_bin, dh_ctx.len); + + // allocate new BINARY! for public key + // + REBSER *pub_bin = Make_Binary(dh_ctx.len); + dh_ctx.gx = BIN_HEAD(pub_bin); + memset(dh_ctx.gx, 0, dh_ctx.len); + SET_SERIES_LEN(pub_bin, dh_ctx.len); + + DH_generate_key(&dh_ctx); + + // set the object fields + + REBCNT priv_index = Find_Canon_In_Context(obj, CRYPT_WORD_PRIV_KEY, FALSE); + if (priv_index == 0) + fail ("Cannot find PRIV-KEY in crypto object"); + Init_Binary(CTX_VAR(obj, priv_index), priv_bin); + + REBCNT pub_index = Find_Canon_In_Context(obj, CRYPT_WORD_PUB_KEY, FALSE); + if (pub_index == 0) + fail ("Cannot find PUB-KEY in crypto object"); + Init_Binary(CTX_VAR(obj, pub_index), pub_bin); + + return R_VOID; +} + + +// +// dh-compute-key: native/export [ +// +// "Computes key from a private/public key pair and the peer's public key." +// +// return: [binary!] +// "Negotiated key" +// obj [object!] +// "The Diffie-Hellman key object" +// public-key [binary!] +// "Peer's public key" +// ] +// +static REBNATIVE(dh_compute_key) +{ + INCLUDE_PARAMS_OF_DH_COMPUTE_KEY; + + DH_CTX dh_ctx; + memset(&dh_ctx, 0, sizeof(dh_ctx)); + + REBCTX *obj = VAL_CONTEXT(ARG(obj)); + + REBVAL *key = CTX_KEYS_HEAD(obj); + REBVAL *var = CTX_VARS_HEAD(obj); + + for (; NOT_END(key); ++key, ++var) { + REBSTR* canon = VAL_KEY_CANON(key); + + if (canon == Canon(SYM_SELF)) { + NOOP; + } + else if (canon == CRYPT_WORD_P) { + if (NOT(IS_BINARY(var))) + fail (Error(RE_EXT_CRYPT_INVALID_KEY, var)); + + dh_ctx.p = VAL_BIN_AT(var); + dh_ctx.len = VAL_LEN_AT(var); + } + else if (canon == CRYPT_WORD_PRIV_KEY) { + if (NOT(IS_BINARY(var))) + fail (Error(RE_EXT_CRYPT_INVALID_KEY, var)); + + dh_ctx.x = VAL_BIN_AT(var); + } + else if (canon == CRYPT_WORD_PUB_KEY) { + NOOP; + } + else if (canon == CRYPT_WORD_G) { + NOOP; + } + else + fail (Error(RE_EXT_CRYPT_INVALID_KEY_FIELD, key)); + } + + dh_ctx.gy = VAL_BIN_AT(ARG(public_key)); + + if (!dh_ctx.p || !dh_ctx.x || !dh_ctx.gy) + fail (Error(RE_EXT_CRYPT_INVALID_KEY, ARG(obj))); + + REBSER *binary = Make_Binary(dh_ctx.len); + memset(BIN_HEAD(binary), 0, dh_ctx.len); + SET_SERIES_LEN(binary, dh_ctx.len); + + dh_ctx.k = BIN_HEAD(binary); + + DH_compute_key(&dh_ctx); + + Init_Binary(D_OUT, binary); + return R_OUT; +} + + +static void cleanup_aes_ctx(const REBVAL *v) +{ + AES_CTX *aes_ctx = VAL_HANDLE_POINTER(AES_CTX, v); + FREE(AES_CTX, aes_ctx); +} + + +// +// aes: native/export [ +// +// "Encrypt/decrypt data using AES algorithm." +// +// return: [handle! binary! logic!] +// "Stream cipher context handle or encrypted/decrypted data." +// /key +// "Provided only for the first time to get stream HANDLE!" +// crypt-key [binary!] +// "Crypt key." +// iv [binary! blank!] +// "Optional initialization vector." +// /stream +// ctx [handle!] +// "Stream cipher context." +// data [binary!] +// "Data to encrypt/decrypt." +// /decrypt +// "Use the crypt-key for decryption (default is to encrypt)" +// ] +// new-errors: [ +// invalid-aes-context: [{Not a AES context:} :arg1] +// invalid-aes-key-length: [{AES key length has to be 16 or 32:} :arg1] +// ] +// +static REBNATIVE(aes) +{ + INCLUDE_PARAMS_OF_AES; + + if (REF(stream)) { + if (VAL_HANDLE_CLEANER(ARG(ctx)) != cleanup_aes_ctx) + fail (Error(RE_EXT_CRYPT_INVALID_AES_CONTEXT, ARG(ctx))); + + AES_CTX *aes_ctx = VAL_HANDLE_POINTER(AES_CTX, ARG(ctx)); + + REBYTE *dataBuffer = VAL_BIN_AT(ARG(data)); + REBINT len = VAL_LEN_AT(ARG(data)); + + if (len == 0) + return R_BLANK; + + REBINT pad_len = (((len - 1) >> 4) << 4) + AES_BLOCKSIZE; + + REBYTE *pad_data; + if (len < pad_len) { + // + // make new data input with zero-padding + // + pad_data = ALLOC_N(REBYTE, pad_len); + memset(pad_data, 0, pad_len); + memcpy(pad_data, dataBuffer, len); + dataBuffer = pad_data; + } + else + pad_data = NULL; + + REBSER *binaryOut = Make_Binary(pad_len); + memset(BIN_HEAD(binaryOut), 0, pad_len); + + if (aes_ctx->key_mode == AES_MODE_DECRYPT) + AES_cbc_decrypt( + aes_ctx, + cast(const uint8_t*, dataBuffer), + BIN_HEAD(binaryOut), + pad_len + ); + else + AES_cbc_encrypt( + aes_ctx, + cast(const uint8_t*, dataBuffer), + BIN_HEAD(binaryOut), + pad_len + ); + + if (pad_data) + FREE_N(REBYTE, pad_len, pad_data); + + SET_SERIES_LEN(binaryOut, pad_len); + Init_Binary(D_OUT, binaryOut); + return R_OUT; + } + + if (REF(key)) { + uint8_t iv[AES_IV_SIZE]; + + if (IS_BINARY(ARG(iv))) { + if (VAL_LEN_AT(ARG(iv)) < AES_IV_SIZE) + return R_BLANK; + + memcpy(iv, VAL_BIN_AT(ARG(iv)), AES_IV_SIZE); + } + else { + assert(IS_BLANK(ARG(iv))); + memset(iv, 0, AES_IV_SIZE); + } + + //key defined - setup new context + + REBINT len = VAL_LEN_AT(ARG(crypt_key)) << 3; + if (len != 128 && len != 256) { + DECLARE_LOCAL (i); + Init_Integer(i, len); + fail (Error(RE_EXT_CRYPT_INVALID_AES_KEY_LENGTH, i)); + } + + AES_CTX *aes_ctx = ALLOC_ZEROFILL(AES_CTX); + + AES_set_key( + aes_ctx, + cast(const uint8_t *, VAL_BIN_AT(ARG(crypt_key))), + cast(const uint8_t *, iv), + (len == 128) ? AES_MODE_128 : AES_MODE_256 + ); + + if (REF(decrypt)) + AES_convert_key(aes_ctx); + + Init_Handle_Managed(D_OUT, aes_ctx, 0, &cleanup_aes_ctx); + return R_OUT; + } + + fail (Error(RE_EXT_CRYPT_KEY_OR_STREAM_REQUIRED)); +} + + +// +// sha256: native/export [ +// +// {Calculate a SHA256 hash value from binary data.} +// +// return: [binary!] +// {32-byte binary hash} +// data [binary! string!] +// {Data to hash, STRING! will be converted to UTF-8} +// ] +// +REBNATIVE(sha256) +{ + INCLUDE_PARAMS_OF_SHA256; + + REBCNT index; + REBCNT len; + REBSER *series; + if (NOT(VAL_BYTE_SIZE(ARG(data)))) { // wide string + series = Temp_Bin_Str_Managed(ARG(data), &index, &len); + } + else { + series = VAL_SERIES(ARG(data)); + index = VAL_INDEX(ARG(data)); + len = VAL_LEN_AT(ARG(data)); + } + + REBYTE *data = BIN_AT(series, index); + + SHA256_CTX ctx; + + sha256_init(&ctx); + sha256_update(&ctx, data, len); + + REBSER *buf = Make_Binary(SHA256_BLOCK_SIZE); + sha256_final(&ctx, BIN_HEAD(buf)); + TERM_BIN_LEN(buf, SHA256_BLOCK_SIZE); + + Init_Binary(D_OUT, buf); + return R_OUT; +} + + +/* +#define SEED_LEN 10 +static REBYTE seed_str[SEED_LEN] = { + 249, 52, 217, 38, 207, 59, 216, 52, 222, 61 // xor "Sassenrath" #{AA55..} +}; +// kp = seed_str; // Any seed constant. +// klen = SEED_LEN; +*/ + +// +// Cloak: C +// +// Simple data scrambler. Quality depends on the key length. +// Result is made in place (data string). +// +// The key (kp) is passed as a REBVAL or REBYTE (when klen is !0). +// +static REBOOL Cloak( + REBOOL decode, + REBYTE *cp, + REBCNT dlen, + REBYTE *kp, + REBCNT klen, + REBOOL as_is +) { + REBYTE src[20]; + REBYTE dst[20]; + + if (dlen == 0) + return TRUE; + + REBCNT i; + + // Decode KEY as VALUE field (binary, string, or integer) + if (klen == 0) { + REBVAL *val = (REBVAL*)kp; + REBSER *ser; + + switch (VAL_TYPE(val)) { + case REB_BINARY: + kp = VAL_BIN_AT(val); + klen = VAL_LEN_AT(val); + break; + + case REB_STRING: + ser = Temp_Bin_Str_Managed(val, &i, &klen); + kp = BIN_AT(ser, i); + break; + + case REB_INTEGER: + INT_TO_STR(VAL_INT64(val), dst); + klen = LEN_BYTES(dst); + as_is = FALSE; + break; + + default: + assert(FALSE); + } + + if (klen == 0) + return FALSE; + } + + if (!as_is) { + for (i = 0; i < 20; i++) + src[i] = kp[i % klen]; + + SHA1(src, 20, dst); + klen = 20; + kp = dst; + } + + if (decode) { + for (i = dlen - 1; i > 0; i--) + cp[i] ^= cp[i - 1] ^ kp[i % klen]; + } + + // Change starting byte based all other bytes. + + REBCNT n = 0xa5; + for (i = 1; i < dlen; i++) + n += cp[i]; + + cp[0] ^= cast(REBYTE, n); + + if (!decode) { + for (i = 1; i < dlen; i++) + cp[i] ^= cp[i - 1] ^ kp[i % klen]; + } + + return TRUE; +} + + +// +// decloak: native/export [ +// +// {Decodes a binary string scrambled previously by encloak.} +// +// data [binary!] +// "Binary series to descramble (modified)" +// key [string! binary! integer!] +// "Encryption key or pass phrase" +// /with +// "Use a string! key as-is (do not generate hash)" +// ] +// +static REBNATIVE(decloak) +{ + INCLUDE_PARAMS_OF_DECLOAK; + + if (NOT(Cloak( + TRUE, + VAL_BIN_AT(ARG(data)), + VAL_LEN_AT(ARG(data)), + cast(REBYTE*, ARG(key)), + 0, + REF(with) + ))){ + fail (ARG(key)); + } + + Move_Value(D_OUT, ARG(data)); + return R_OUT; +} + + +// +// encloak: native/export [ +// +// "Scrambles a binary string based on a key." +// +// data [binary!] +// "Binary series to scramble (modified)" +// key [string! binary! integer!] +// "Encryption key or pass phrase" +// /with +// "Use a string! key as-is (do not generate hash)" +// ] +// +static REBNATIVE(encloak) +{ + INCLUDE_PARAMS_OF_ENCLOAK; + + if (NOT(Cloak( + FALSE, + VAL_BIN_AT(ARG(data)), + VAL_LEN_AT(ARG(data)), + cast(REBYTE*, ARG(key)), + 0, + REF(with)) + )){ + fail (ARG(key)); + } + + Move_Value(D_OUT, ARG(data)); + return R_OUT; +} + +#include "tmp-mod-crypt-last.h" diff --git a/src/extensions/crypt/rc4/rc4.c b/src/extensions/crypt/rc4/rc4.c new file mode 100644 index 0000000000..5d2dfc36bb --- /dev/null +++ b/src/extensions/crypt/rc4/rc4.c @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "rc4.h" + +/** + * Get ready for an encrypt/decrypt operation + */ +void RC4_setup(RC4_CTX *ctx, const uint8_t *key, int length) +{ + int i, j = 0, k = 0, a; + uint8_t *m; + + ctx->x = 0; + ctx->y = 0; + m = ctx->m; + + for (i = 0; i < 256; i++) + m[i] = i; + + for (i = 0; i < 256; i++) + { + a = m[i]; + j = (uint8_t)(j + a + key[k]); + m[i] = m[j]; + m[j] = a; + + if (++k >= length) + k = 0; + } +} + +/** + * Perform the encrypt/decrypt operation (can use it for either since + * this is a stream cipher). + * NOTE: *msg and *out must be the same pointer (performance tweak) + */ +void RC4_crypt(RC4_CTX *ctx, const uint8_t *msg, uint8_t *out, int length) +{ + (void)msg; // not used since msg and out are the same + + int i; + uint8_t *m, x, y, a, b; + + x = ctx->x; + y = ctx->y; + m = ctx->m; + + for (i = 0; i < length; i++) + { + a = m[++x]; + y += a; + m[x] = b = m[y]; + m[y] = a; + out[i] ^= m[(uint8_t)(a + b)]; + } + + ctx->x = x; + ctx->y = y; +} diff --git a/src/extensions/crypt/rc4/rc4.h b/src/extensions/crypt/rc4/rc4.h new file mode 100644 index 0000000000..1f23bb58f0 --- /dev/null +++ b/src/extensions/crypt/rc4/rc4.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +/************************************************************************** + * RC4 declarations + **************************************************************************/ + +typedef struct +{ + uint8_t x, y, m[256]; +} RC4_CTX; + +void RC4_setup(RC4_CTX *s, const uint8_t *key, int length); +void RC4_crypt(RC4_CTX *s, const uint8_t *msg, uint8_t *data, int length); diff --git a/src/extensions/crypt/rsa/rsa.c b/src/extensions/crypt/rsa/rsa.c new file mode 100644 index 0000000000..480b702312 --- /dev/null +++ b/src/extensions/crypt/rsa/rsa.c @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Implements the RSA public encryption algorithm. Uses the bigint library to + * perform its calculations. + */ +// #include // !!! No in Ren-C release builds +#include +#include +#include + +#include + +#if defined(TO_OSX_X86) || defined(TO_OSX_PPC) + #include +#elif defined(TO_WINDOWS) + #include +#endif + +#include "rsa.h" + + +// Initialized by the CRYPT extension entry point, shut down by the exit code +// +#ifdef TO_WINDOWS + HCRYPTPROV gCryptProv = 0; +#else + int rng_fd = -1; +#endif + +/** + * Set a series of bytes with a random number. Individual bytes can be 0 + */ +void get_random(int num_rand_bytes, uint8_t *rand_data) +{ +#ifdef TO_WINDOWS + if (CryptGenRandom(gCryptProv, num_rand_bytes, rand_data) != 0) + return; // success +#else + if (rng_fd != -1 && read(rng_fd, rand_data, num_rand_bytes) != -1) + return; // success +#endif + + // !!! If this routine cannot generate random numbers, it is a serious + // error which cannot continue. The organization of this code doesn't + // currently include Rebol's failure tools, so for now we assert and + // force an exit should this happen. + + assert(0); + exit(EXIT_FAILURE); +} + +/** + * Set a series of bytes with a random number. Individual bytes are not zero. + */ +void get_random_NZ(int num_rand_bytes, uint8_t *rand_data) +{ + int i; + get_random(num_rand_bytes, rand_data); + + for (i = 0; i < num_rand_bytes; i++) + { + while (rand_data[i] == 0) /* can't be 0 */ + rand_data[i] = (uint8_t)(rand()); + } +} + +void RSA_priv_key_new(RSA_CTX **ctx, + const uint8_t *modulus, int mod_len, + const uint8_t *pub_exp, int pub_len, + const uint8_t *priv_exp, int priv_len +#if CONFIG_BIGINT_CRT + , const uint8_t *p, int p_len, + const uint8_t *q, int q_len, + const uint8_t *dP, int dP_len, + const uint8_t *dQ, int dQ_len, + const uint8_t *qInv, int qInv_len +#endif + ) +{ + RSA_CTX *rsa_ctx; + BI_CTX *bi_ctx; + RSA_pub_key_new(ctx, modulus, mod_len, pub_exp, pub_len); + rsa_ctx = *ctx; + bi_ctx = rsa_ctx->bi_ctx; + rsa_ctx->d = bi_import(bi_ctx, priv_exp, priv_len); + bi_permanent(rsa_ctx->d); + +#ifdef CONFIG_BIGINT_CRT + if (dP && dQ && p && q && qInv) + { + rsa_ctx->p = bi_import(bi_ctx, p, p_len); + rsa_ctx->q = bi_import(bi_ctx, q, q_len); + rsa_ctx->dP = bi_import(bi_ctx, dP, dP_len); + rsa_ctx->dQ = bi_import(bi_ctx, dQ, dQ_len); + rsa_ctx->qInv = bi_import(bi_ctx, qInv, qInv_len); + bi_permanent(rsa_ctx->dP); + bi_permanent(rsa_ctx->dQ); + bi_permanent(rsa_ctx->qInv); + bi_set_mod(bi_ctx, rsa_ctx->p, BIGINT_P_OFFSET); + bi_set_mod(bi_ctx, rsa_ctx->q, BIGINT_Q_OFFSET); + } +#endif +} + +void RSA_pub_key_new(RSA_CTX **ctx, + const uint8_t *modulus, int mod_len, + const uint8_t *pub_exp, int pub_len) +{ + RSA_CTX *rsa_ctx; + BI_CTX *bi_ctx; + + if (*ctx) /* if we load multiple certs, dump the old one */ + RSA_free(*ctx); + + bi_ctx = bi_initialize(); + *ctx = (RSA_CTX *)calloc(1, sizeof(RSA_CTX)); + rsa_ctx = *ctx; + rsa_ctx->bi_ctx = bi_ctx; + rsa_ctx->num_octets = mod_len; + rsa_ctx->m = bi_import(bi_ctx, modulus, mod_len); + bi_set_mod(bi_ctx, rsa_ctx->m, BIGINT_M_OFFSET); + rsa_ctx->e = bi_import(bi_ctx, pub_exp, pub_len); + bi_permanent(rsa_ctx->e); +} + +/** + * Free up any RSA context resources. + */ +void RSA_free(RSA_CTX *rsa_ctx) +{ + BI_CTX *bi_ctx; + if (rsa_ctx == NULL) /* deal with ptrs that are null */ + return; + + bi_ctx = rsa_ctx->bi_ctx; + + bi_depermanent(rsa_ctx->e); + bi_free(bi_ctx, rsa_ctx->e); + bi_free_mod(rsa_ctx->bi_ctx, BIGINT_M_OFFSET); + + if (rsa_ctx->d) + { + bi_depermanent(rsa_ctx->d); + bi_free(bi_ctx, rsa_ctx->d); +#ifdef CONFIG_BIGINT_CRT + if (rsa_ctx->dP) //it is enough to check only one value - complete check is in already in RSA_priv_key_new() + { + bi_depermanent(rsa_ctx->dP); + bi_depermanent(rsa_ctx->dQ); + bi_depermanent(rsa_ctx->qInv); + bi_free(bi_ctx, rsa_ctx->dP); + bi_free(bi_ctx, rsa_ctx->dQ); + bi_free(bi_ctx, rsa_ctx->qInv); + bi_free_mod(rsa_ctx->bi_ctx, BIGINT_P_OFFSET); + bi_free_mod(rsa_ctx->bi_ctx, BIGINT_Q_OFFSET); + } +#endif + } + + bi_terminate(bi_ctx); + free(rsa_ctx); +} + +/** + * @brief Use PKCS1.5 for decryption/verification. + * @param ctx [in] The context + * @param in_data [in] The data to encrypt (must be < modulus size-11) + * @param out_data [out] The encrypted data. + * @param is_decryption [in] Decryption or verify operation. + * @return The number of bytes that were originally encrypted. -1 on error. + * @see http://www.rsasecurity.com/rsalabs/node.asp?id=2125 + */ +int RSA_decrypt(const RSA_CTX *ctx, const uint8_t *in_data, + uint8_t *out_data, int is_decryption, int padding) +{ + const int byte_size = ctx->num_octets; + int i, size; + bigint *decrypted_bi, *dat_bi; + uint8_t *block = (uint8_t *)alloca(byte_size); + + memset(out_data, 0, byte_size); /* initialise */ + + /* decrypt */ + dat_bi = bi_import(ctx->bi_ctx, in_data, byte_size); +#ifdef CONFIG_SSL_CERT_VERIFICATION + decrypted_bi = is_decryption ? /* decrypt or verify? */ + RSA_private(ctx, dat_bi) : RSA_public(ctx, dat_bi); +#else /* always a decryption */ + decrypted_bi = RSA_private(ctx, dat_bi); +#endif + + /* convert to a normal block */ + bi_export(ctx->bi_ctx, decrypted_bi, block, byte_size); + + if (padding) + { + i = 0; + } + else + { + + i = 10; /* start at the first possible non-padded byte */ + +#ifdef CONFIG_SSL_CERT_VERIFICATION + if (is_decryption == 0) /* PKCS1.5 signing pads with "0xff"s */ + { + while (block[i++] == 0xff && i < byte_size); + + if (block[i-2] != 0xff) + i = byte_size; /*ensure size is 0 */ + } + else /* PKCS1.5 encryption padding is random */ +#endif + { + while (block[i++] && i < byte_size); + } + } + size = byte_size - i; + + /* get only the bit we want */ + if (size > 0) + memcpy(out_data, &block[i], size); + + return size ? size : -1; +} + +/** + * Performs m = c^d mod n + */ +bigint *RSA_private(const RSA_CTX *c, bigint *bi_msg) +{ +#ifdef CONFIG_BIGINT_CRT + if (c->dP) //it is enough to check only one value - complete check is in already in RSA_priv_key_new() + return bi_crt(c->bi_ctx, bi_msg, c->dP, c->dQ, c->p, c->q, c->qInv); + else { + BI_CTX *ctx = c->bi_ctx; + ctx->mod_offset = BIGINT_M_OFFSET; + return bi_mod_power(ctx, bi_msg, c->d); + } +#else + BI_CTX *ctx = c->bi_ctx; + ctx->mod_offset = BIGINT_M_OFFSET; + return bi_mod_power(ctx, bi_msg, c->d); +#endif +} + +#ifdef CONFIG_SSL_DEBUG +/** + * Used for diagnostics. + */ +void RSA_print(const RSA_CTX *rsa_ctx) +{ + if (rsa_ctx == NULL) + return; + + printf("----------------- RSA DEBUG ----------------\n"); + printf("Size:\t%d\n", rsa_ctx->num_octets); +#ifdef CONFIG_BIGINT_DEBUG + bi_print("Modulus", rsa_ctx->m); + bi_print("Public Key", rsa_ctx->e); + bi_print("Private Key", rsa_ctx->d); +#endif +} +#endif + +#if defined(CONFIG_SSL_CERT_VERIFICATION) || defined(CONFIG_SSL_GENERATE_X509_CERT) +/** + * Performs c = m^e mod n + */ +bigint *RSA_public(const RSA_CTX * c, bigint *bi_msg) +{ + c->bi_ctx->mod_offset = BIGINT_M_OFFSET; + return bi_mod_power(c->bi_ctx, bi_msg, c->e); +} + +/** + * Use PKCS1.5 for encryption/signing. + * see http://www.rsasecurity.com/rsalabs/node.asp?id=2125 + */ +int RSA_encrypt(const RSA_CTX *ctx, const uint8_t *in_data, uint16_t in_len, + uint8_t *out_data, int is_signing, int padding) +{ + int byte_size = ctx->num_octets; + bigint *dat_bi, *encrypt_bi; + + if (padding) + { + int num_pads_needed = byte_size-in_len-3; + + //input won't fit pkcs output + if (num_pads_needed < 0) return -1; + + /* note: in_len+11 must be > byte_size */ + out_data[0] = 0; /* ensure encryption block is < modulus */ + + if (is_signing) + { + out_data[1] = 1; /* PKCS1.5 signing pads with "0xff"'s */ + memset(&out_data[2], 0xff, num_pads_needed); + } + else /* randomize the encryption padding with non-zero bytes */ + { + out_data[1] = 2; + get_random_NZ(num_pads_needed, &out_data[2]); + } + + out_data[2+num_pads_needed] = 0; + memcpy(&out_data[3+num_pads_needed], in_data, in_len); + } + else + { + memcpy(out_data, in_data, in_len); + } + + /* now encrypt it */ + dat_bi = bi_import(ctx->bi_ctx, out_data, byte_size); + encrypt_bi = is_signing ? RSA_private(ctx, dat_bi) : + RSA_public(ctx, dat_bi); + bi_export(ctx->bi_ctx, encrypt_bi, out_data, byte_size); + + /* save a few bytes of memory */ + bi_clear_cache(ctx->bi_ctx); + return byte_size; +} + +#endif /* CONFIG_SSL_CERT_VERIFICATION */ diff --git a/src/extensions/crypt/rsa/rsa.h b/src/extensions/crypt/rsa/rsa.h new file mode 100644 index 0000000000..35bdaf946d --- /dev/null +++ b/src/extensions/crypt/rsa/rsa.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2007, Cameron Rich + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the axTLS project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef HEADER_RSA_H +#define HEADER_RSA_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef TO_WINDOWS + #include + #include + + extern HCRYPTPROV gCryptProv; // encryption provider handle +#else + #include + #include + + extern int rng_fd; // file descriptor for random number generator +#endif + + +/* + * RSA Options + */ + +/* + CONFIG_SSL_DEBUG + Enable diagnostics. Most of the extra size in this mode is + due to the storage of various strings that are used. +*/ +#undef CONFIG_SSL_DEBUG + +#define CONFIG_SSL_CERT_VERIFICATION +#undef CONFIG_SSL_GENERATE_X509_CERT + +#include +#include "../bigint/bigint.h" + +/* To use this exported function of dll, include this header + * in your project. + */ + +/************************************************************************** + * RSA declarations + **************************************************************************/ + +typedef struct +{ + bigint *m; /* modulus */ + bigint *e; /* public exponent */ + bigint *d; /* private exponent */ +#ifdef CONFIG_BIGINT_CRT + bigint *p; /* p as in m = pq */ + bigint *q; /* q as in m = pq */ + bigint *dP; /* d mod (p-1) */ + bigint *dQ; /* d mod (q-1) */ + bigint *qInv; /* q^-1 mod p */ +#endif + int num_octets; + BI_CTX *bi_ctx; +} RSA_CTX; + +void RSA_priv_key_new(RSA_CTX **rsa_ctx, + const uint8_t *modulus, int mod_len, + const uint8_t *pub_exp, int pub_len, + const uint8_t *priv_exp, int priv_len +#ifdef CONFIG_BIGINT_CRT + , const uint8_t *p, int p_len, + const uint8_t *q, int q_len, + const uint8_t *dP, int dP_len, + const uint8_t *dQ, int dQ_len, + const uint8_t *qInv, int qInv_len +#endif + ); +void RSA_pub_key_new(RSA_CTX **rsa_ctx, + const uint8_t *modulus, int mod_len, + const uint8_t *pub_exp, int pub_len); +void RSA_free(RSA_CTX *ctx); +int RSA_decrypt(const RSA_CTX *ctx, const uint8_t *in_data, uint8_t *out_data, + int is_decryption, int padding); +bigint *RSA_private(const RSA_CTX *c, bigint *bi_msg); +#if defined(CONFIG_SSL_CERT_VERIFICATION) || defined(CONFIG_SSL_GENERATE_X509_CERT) +bigint *RSA_sign_verify(BI_CTX *ctx, const uint8_t *sig, int sig_len, + bigint *modulus, bigint *pub_exp); +bigint *RSA_public(const RSA_CTX * c, bigint *bi_msg); +int RSA_encrypt(const RSA_CTX *ctx, const uint8_t *in_data, uint16_t in_len, + uint8_t *out_data, int is_signing, int padding); +void RSA_print(const RSA_CTX *ctx); +#endif + +/************************************************************************** + * RNG declarations + **************************************************************************/ +void get_random(int num_rand_bytes, uint8_t *rand_data); +void get_random_NZ(int num_rand_bytes, uint8_t *rand_data); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/extensions/crypt/sha256/sha256.c b/src/extensions/crypt/sha256/sha256.c new file mode 100644 index 0000000000..98da9c6438 --- /dev/null +++ b/src/extensions/crypt/sha256/sha256.c @@ -0,0 +1,159 @@ +/********************************************************************* +* Filename: sha256.c +* Author: Brad Conte (brad AT bradconte.com) +* Copyright: +* Disclaimer: This code is presented "as is" without any guarantees. +* Details: Implementation of the SHA-256 hashing algorithm. + SHA-256 is one of the three algorithms in the SHA2 + specification. The others, SHA-384 and SHA-512, are not + offered in this implementation. + Algorithm specification can be found here: + * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf + This implementation uses little endian byte order. +*********************************************************************/ + +/*************************** HEADER FILES ***************************/ +#include +#include +#include "reb-c.h" // needed for REBYTE, REBCNT, REBU64 +#include "sha256.h" + +/****************************** MACROS ******************************/ +#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b)))) +#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b)))) + +#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z))) +#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22)) +#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25)) +#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3)) +#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10)) + +/**************************** VARIABLES *****************************/ +static const REBCNT k[64] = { + 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, + 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, + 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, + 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, + 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, + 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, + 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, + 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2 +}; + +/*********************** FUNCTION DEFINITIONS ***********************/ +void sha256_transform(SHA256_CTX *ctx, const REBYTE data[]) +{ + REBCNT a, b, c, d, e, f, g, h, i, j, t1, t2, m[64]; + + for (i = 0, j = 0; i < 16; ++i, j += 4) + m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]); + for ( ; i < 64; ++i) + m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16]; + + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + e = ctx->state[4]; + f = ctx->state[5]; + g = ctx->state[6]; + h = ctx->state[7]; + + for (i = 0; i < 64; ++i) { + t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i]; + t2 = EP0(a) + MAJ(a,b,c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; + ctx->state[4] += e; + ctx->state[5] += f; + ctx->state[6] += g; + ctx->state[7] += h; +} + +void sha256_init(SHA256_CTX *ctx) +{ + ctx->datalen = 0; + ctx->bitlen = 0; + ctx->state[0] = 0x6a09e667; + ctx->state[1] = 0xbb67ae85; + ctx->state[2] = 0x3c6ef372; + ctx->state[3] = 0xa54ff53a; + ctx->state[4] = 0x510e527f; + ctx->state[5] = 0x9b05688c; + ctx->state[6] = 0x1f83d9ab; + ctx->state[7] = 0x5be0cd19; +} + +void sha256_update(SHA256_CTX *ctx, const REBYTE data[], size_t len) +{ + REBCNT i; + + for (i = 0; i < len; ++i) { + ctx->data[ctx->datalen] = data[i]; + ctx->datalen++; + if (ctx->datalen == 64) { + sha256_transform(ctx, ctx->data); + ctx->bitlen += 512; + ctx->datalen = 0; + } + } +} + +void sha256_final(SHA256_CTX *ctx, REBYTE hash[]) +{ + REBCNT i; + + i = ctx->datalen; + + // Pad whatever data is left in the buffer. + if (ctx->datalen < 56) { + ctx->data[i++] = 0x80; + while (i < 56) + ctx->data[i++] = 0x00; + } + else { + ctx->data[i++] = 0x80; + while (i < 64) + ctx->data[i++] = 0x00; + sha256_transform(ctx, ctx->data); + memset(ctx->data, 0, 56); + } + + // Append to the padding the total message's length in bits and transform. + ctx->bitlen += ctx->datalen * 8; + ctx->data[63] = ctx->bitlen; + ctx->data[62] = ctx->bitlen >> 8; + ctx->data[61] = ctx->bitlen >> 16; + ctx->data[60] = ctx->bitlen >> 24; + ctx->data[59] = ctx->bitlen >> 32; + ctx->data[58] = ctx->bitlen >> 40; + ctx->data[57] = ctx->bitlen >> 48; + ctx->data[56] = ctx->bitlen >> 56; + sha256_transform(ctx, ctx->data); + + // Since this implementation uses little endian byte ordering and SHA uses big endian, + // reverse all the bytes when copying the final state to the output hash. + for (i = 0; i < 4; ++i) { + hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff; + hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff; + hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff; + hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff; + hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff; + hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff; + hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff; + hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff; + } +} diff --git a/src/extensions/crypt/sha256/sha256.h b/src/extensions/crypt/sha256/sha256.h new file mode 100644 index 0000000000..8454794604 --- /dev/null +++ b/src/extensions/crypt/sha256/sha256.h @@ -0,0 +1,33 @@ +/********************************************************************* +* Filename: sha256.h +* Author: Brad Conte (brad AT bradconte.com) +* Copyright: +* Disclaimer: This code is presented "as is" without any guarantees. +* Details: Defines the API for the corresponding SHA1 implementation. +*********************************************************************/ + +#ifndef SHA256_H +#define SHA256_H + +/*************************** HEADER FILES ***************************/ +#include + +/****************************** MACROS ******************************/ +#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest + +// Note: Original defined WORD as a 32-bit entity and BYTE. This conflicts +// with definitions in Windows. Modified to use REBYTE and REBCNT + +typedef struct { + REBYTE data[64]; + REBCNT datalen; + REBU64 bitlen; + REBCNT state[8]; +} SHA256_CTX; + +/*********************** FUNCTION DECLARATIONS **********************/ +void sha256_init(SHA256_CTX *ctx); +void sha256_update(SHA256_CTX *ctx, const REBYTE data[], size_t len); +void sha256_final(SHA256_CTX *ctx, REBYTE hash[]); + +#endif // SHA256_H diff --git a/src/extensions/gif/ext-gif.c b/src/extensions/gif/ext-gif.c new file mode 100644 index 0000000000..e014b5f94b --- /dev/null +++ b/src/extensions/gif/ext-gif.c @@ -0,0 +1,64 @@ +// +// File: %ext-gif.c +// Summary: "GIF codec" +// Section: Extension +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ================================================================== +// +#include "sys-core.h" +#include "sys-ext.h" + +static const REBYTE script_bytes[] = +"REBOL [" + "Title: \"GIF Codec Extension\"\n" + "name: 'GIF\n" + "type: 'Extension\n" + "version: 1.0.0\n" + "license: {Apache 2.0}\n" +"]\n" +"sys/register-codec* 'gif %.gif\n" + "get in import 'gif 'identify-gif?\n" + "get in import 'gif 'decode-gif\n" + "_" // currently no GIF encoder +; + +#define MODULE_INCLUDE_DECLARATION_ONLY +#include "tmp-mod-gif-last.h" + +DEFINE_EXT_INIT(GIF, //name of the extension + script_bytes, // REBOL script for the extension in the source form + { + // init all modules in this extension + int init = CALL_MODULE_INIT(GIF); + if (init < 0) return init; + } +) + +DEFINE_EXT_QUIT(GIF, +{ + return CALL_MODULE_QUIT(GIF); +} +) + diff --git a/src/extensions/gif/mod-gif.c b/src/extensions/gif/mod-gif.c new file mode 100644 index 0000000000..db3cee6023 --- /dev/null +++ b/src/extensions/gif/mod-gif.c @@ -0,0 +1,374 @@ +// +// File: %mod-gif.c +// Summary: "GIF image format conversion" +// Section: Extension +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This is an optional part of R3. This file can be replaced by +// library function calls into an updated implementation. +// + +#include "sys-core.h" +#include "sys-ext.h" +#include "tmp-mod-gif-first.h" + + +#define MAX_STACK_SIZE 4096 +#define NULL_CODE (-1) +#define BitSet(byte,bit) (((byte) & (bit)) == (bit)) +#define LSBFirstOrder(x,y) (((y) << 8) | (x)) + +static REBINT interlace_rate[4] = { 8, 8, 4, 2 }, + interlace_start[4] = { 0, 4, 2, 1 }; + + +#ifdef COMP_IMAGES +// Because graphics.c is not included, we must have a copy here. +void Chrom_Key_Alpha(REBVAL *v,REBCNT col,REBINT blitmode) { + REBOOL found=FALSE; + int i; + REBCNT *p; + + p=(REBCNT *)VAL_IMAGE_HEAD(v); + i=VAL_IMAGE_WIDTH(v)*VAL_IMAGE_HEIGHT(v); + switch(blitmode) { + case BLIT_MODE_COLOR: + for(;i>0;i--,p++) { + if(*p==col) { + found=TRUE; + *p=col|0xff000000; + } + } + case BLIT_MODE_LUMA: + for(;i>0;i--,p++) { + if(BRIGHT(((REBRGB *)p))<=col) { + found=TRUE; + *p|=0xff000000; + } + } + break; + } + if(found) + VAL_IMAGE_TRANSP(v)=VITT_ALPHA; +} +#endif + +// +// Decode_LZW: C +// +// Perform LZW decompression. +// +void Decode_LZW(REBCNT *data, REBYTE **cpp, REBYTE *colortab, REBINT w, REBINT h, REBOOL interlaced) +{ + REBYTE *cp = *cpp; + REBYTE *rp; + REBINT available, clear, code_mask, code_size, end_of_info, in_code; + REBINT old_code, bits, code, count, x, y, data_size, row, i; + REBCNT *dp, datum; + short *prefix; + REBYTE first, *pixel_stack, *suffix, *top_stack; + + suffix = ALLOC_N(REBYTE, + MAX_STACK_SIZE * (sizeof(REBYTE) + sizeof(REBYTE) + sizeof(short)) + ); + pixel_stack = suffix + MAX_STACK_SIZE; + prefix = (short *)(pixel_stack + MAX_STACK_SIZE); + + data_size = *cp++; + clear = 1 << data_size; + end_of_info = clear + 1; + available = clear + 2; + old_code = NULL_CODE; + code_size = data_size + 1; + code_mask = (1 << code_size) - 1; + + for (code=0; code>= code_size; + bits -= code_size; + + // sanity check + if (code > available || code == end_of_info) + break; + // time to reset the tables + if (code == clear) { + code_size = data_size + 1; + code_mask = (1 << code_size) - 1; + available = clear + 2; + old_code = NULL_CODE; + continue; + } + // if we are the first code, just stack it + if (old_code == NULL_CODE) { + *top_stack++ = suffix[code]; + old_code = code; + first = code; + continue; + } + in_code = code; + if (code == available) { + *top_stack++ = first; + code = old_code; + } + while (code > clear) { + *top_stack++ = suffix[code]; + code = prefix[code]; + } + first = suffix[code]; + + // add a new string to the table + if (available >= MAX_STACK_SIZE) + break; + *top_stack++ = first; + prefix[available] = old_code; + suffix[available++] = first; + if ((available & code_mask) == 0 && available < MAX_STACK_SIZE) { + code_size++; + code_mask += available; + } + old_code = in_code; + } + top_stack--; + rp = colortab + 3 * *top_stack; + *dp++ = TO_PIXEL_COLOR(rp[0], rp[1], rp[2], 0xff); + x++; + } + if (interlaced) { + row += interlace_rate[i]; + if (row >= h) { + row = interlace_start[++i]; + } + dp = data + row * w; + } + } + *cpp = cp + count + 1; + + FREE_N(REBYTE, + MAX_STACK_SIZE * (sizeof(REBYTE) + sizeof(REBYTE) + sizeof(short)), + suffix + ); +} + + +static REBOOL Has_Valid_GIF_Header(REBYTE *data, REBCNT len) { + if (len < 5) + return FALSE; + + if (strncmp(cast(char*, data), "GIF87", 5) == 0) + return TRUE; + + if (strncmp(cast(char*, data), "GIF89", 5) == 0) + return TRUE; + + return FALSE; +} + + +// +// identify-gif?: native [ +// +// {Codec for identifying BINARY! data for a GIF} +// +// return: [logic!] +// data [binary!] +// ] +// +REBNATIVE(identify_gif_q) +{ + INCLUDE_PARAMS_OF_IDENTIFY_GIF_Q; + + REBYTE *data = VAL_BIN_AT(ARG(data)); + REBCNT len = VAL_LEN_AT(ARG(data)); + + // Assume signature matching is good enough (will get a fail() on + // decode if it's a false positive). + // + return R_FROM_BOOL(Has_Valid_GIF_Header(data, len)); +} + + +// +// decode-gif: native [ +// +// {Codec for decoding BINARY! data for a GIF} +// +// return: [image! block!] +// {Single image or BLOCK! of images if multiple frames (animated)} +// data [binary!] +// ] +// +REBNATIVE(decode_gif) +{ + INCLUDE_PARAMS_OF_DECODE_GIF; + + REBYTE *data = VAL_BIN_AT(ARG(data)); + REBCNT len = VAL_LEN_AT(ARG(data)); + + if (NOT(Has_Valid_GIF_Header(data, len))) + fail (Error_Bad_Media_Raw()); + + REBINT w, h; + REBINT transparency_index; + REBYTE c, *global_colormap, *colormap; + REBCNT global_colors, local_colormap; + REBCNT colors; + REBOOL interlaced; + + REBYTE *cp = data; + REBYTE *end = data + len; + + global_colors = 0; + global_colormap = (unsigned char *) NULL; + if (cp[10] & 0x80) { + // Read global colormap. + global_colors = 1 << ((cp[10] & 0x07) + 1); + global_colormap = cp + 13; + cp += global_colors * 3; + } + cp += 13; + transparency_index = -1; + + REBDSP dsp_orig = DSP; // push each image frame found in the GIF file + + for (;;) { + if (cp >= end) break; + c = *cp++; + + if (c == ';') + break; // terminator + + if (c == '!') { + // GIF Extension block. + c = *cp++; + switch (c) { + case 0xf9: + // Transparency extension block. + while (cp[0] != 0 && cp[5] != 0) + cp += 5; + if ((cp[1] & 0x01) == 1) + transparency_index = cp[4]; + cp += cp[0] + 1 + 1; + break; + + default: + while (cp[0] != 0) + cp += cp[0] + 1; + cp++; + break; + } + } + + if (c != ',') continue; + + interlaced = LOGICAL(cp[8] & 0x40); + local_colormap = cp[8] & 0x80; + + w = LSBFirstOrder(cp[4],cp[5]); + h = LSBFirstOrder(cp[6],cp[7]); + // if(w * h * 4 > VAL_SERIES_LEN(img)) + // h = 4 * VAL_SERIES_LEN(img) / w; + + // Initialize colormap. + if (local_colormap) { + colors = 1 << ((cp[8] & 0x07) + 1); + colormap = cp + 9; + cp += 3 * colors; + } + else { + colors = global_colors; + colormap = global_colormap; + } + cp += 9; + + REBSER *ser = Make_Image(w, h, TRUE); + + REBCNT *dp = cast(REBCNT*, IMG_DATA(ser)); + + Decode_LZW(dp, &cp, colormap, w, h, interlaced); + + if(transparency_index >= 0) { + REBYTE *p=colormap+3*transparency_index; + UNUSED(p); + ///Chroma_Key_Alpha(Temp_Value, (REBCNT)(p[2]|(p[1]<<8)|(p[0]<<16)), BLIT_MODE_COLOR); + } + + DS_PUSH_TRASH; + Init_Image(DS_TOP, ser); + } + + if (dsp_orig + 1 == DSP) { + // + // If 1 image, return as a single value + // + // !!! Should formats that can act as containers always be a block? + // + assert(IS_IMAGE(DS_TOP)); + Move_Value(D_OUT, DS_TOP); + DS_DROP; + } + else { + // If 0 or more than one image, return a BLOCK!. + // + Init_Block(D_OUT, Pop_Stack_Values(dsp_orig)); + } + + return R_OUT; +} + + +#include "tmp-mod-gif-last.h" diff --git a/src/extensions/jpg/ext-jpg.c b/src/extensions/jpg/ext-jpg.c new file mode 100644 index 0000000000..0115cdbf28 --- /dev/null +++ b/src/extensions/jpg/ext-jpg.c @@ -0,0 +1,64 @@ +// +// File: %ext-jpg.c +// Summary: "JPG codec" +// Section: Extension +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ================================================================== +// +#include "sys-core.h" +#include "sys-ext.h" + +static const REBYTE script_bytes[] = +"REBOL [" + "Title: \"JPG Codec Extension\"\n" + "name: 'JPG\n" + "type: 'Extension\n" + "version: 1.0.0\n" + "license: {Apache 2.0}\n" +"]\n" +"sys/register-codec* 'jpeg [%.jpg %jpeg]\n" + "get in import 'jpg 'identify-jpeg?\n" + "get in import 'jpg 'decode-jpeg\n" + "_" // currently no JPG encoder +; + +#define MODULE_INCLUDE_DECLARATION_ONLY +#include "tmp-mod-jpg-last.h" + +DEFINE_EXT_INIT(JPG, //name of the extension + script_bytes, // REBOL script for the extension in the source form + { + // init all modules in this extension + int init = CALL_MODULE_INIT(JPG); + if (init < 0) return init; + } +) + +DEFINE_EXT_QUIT(JPG, +{ + return CALL_MODULE_QUIT(JPG); +} +) + diff --git a/src/extensions/jpg/mod-jpg.c b/src/extensions/jpg/mod-jpg.c new file mode 100644 index 0000000000..369cab2b3a --- /dev/null +++ b/src/extensions/jpg/mod-jpg.c @@ -0,0 +1,105 @@ +// +// File: %mod-jpg.c +// Summary: "JPEG codec natives (dependent on %sys-core.h)" +// Section: Extension +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The original JPEG encoder and decoder did not include sys-core.h. But +// after getting rid of the REBCDI-based interface and converting codecs to +// be natives, it's necessary to include the core. +// + +#include "sys-core.h" +#include "sys-ext.h" +#include "tmp-mod-jpg-first.h" + +// These routines live in %u-jpg.c, which doesn't depend on %sys-core.h, but +// has a minor dependency on %reb-c.h + +extern jmp_buf jpeg_state; +extern void jpeg_info(char *buffer, int nbytes, int *w, int *h); +extern void jpeg_load(char *buffer, int nbytes, char *output); + + +// +// identify-jpeg?: native [ +// +// {Codec for identifying BINARY! data for a JPEG} +// +// return: [logic!] +// data [binary!] +// ] +// +REBNATIVE(identify_jpeg_q) +{ + INCLUDE_PARAMS_OF_IDENTIFY_JPEG_Q; + + // Handle JPEG error throw: + if (setjmp(jpeg_state)) { + return R_FALSE; + } + + REBYTE *data = VAL_BIN_AT(ARG(data)); + REBCNT len = VAL_LEN_AT(ARG(data)); + + int w, h; + jpeg_info(s_cast(data), len, &w, &h); // may longjmp above + return R_TRUE; +} + + +// +// decode-jpeg: native [ +// +// {Codec for decoding BINARY! data for a JPEG} +// +// return: [image!] +// data [binary!] +// ] +// +REBNATIVE(decode_jpeg) +{ + INCLUDE_PARAMS_OF_DECODE_JPEG; + + // Handle JPEG error throw: + if (setjmp(jpeg_state)) { + fail (Error_Bad_Media_Raw()); // generic + } + + REBYTE *data = VAL_BIN_AT(ARG(data)); + REBCNT len = VAL_LEN_AT(ARG(data)); + + int w, h; + jpeg_info(s_cast(data), len, &w, &h); // may longjmp above + + REBSER *ser = Make_Image(w, h, TRUE); + jpeg_load(s_cast(data), len, cast(char*, IMG_DATA(ser))); + + Init_Image(D_OUT, ser); + return R_OUT; +} + +#include "tmp-mod-jpg-last.h" diff --git a/src/include/sys-jpg.h b/src/extensions/jpg/sys-jpg.h similarity index 71% rename from src/include/sys-jpg.h rename to src/extensions/jpg/sys-jpg.h index 00cf55cf5a..64360879ce 100644 --- a/src/include/sys-jpg.h +++ b/src/extensions/jpg/sys-jpg.h @@ -23,16 +23,16 @@ #ifdef JPEG_CJPEG_DJPEG -#define BMP_SUPPORTED /* BMP image file format */ -#define GIF_SUPPORTED /* GIF image file format */ -#define PPM_SUPPORTED /* PBMPLUS PPM/PGM image file format */ -#undef RLE_SUPPORTED /* Utah RLE image file format */ -#define TARGA_SUPPORTED /* Targa image file format */ - -#undef TWO_FILE_COMMANDLINE /* You may need this on non-Unix systems */ -#undef NEED_SIGNAL_CATCHER /* Define this if you use jmemname.c */ +#define BMP_SUPPORTED /* BMP image file format */ +#define GIF_SUPPORTED /* GIF image file format */ +#define PPM_SUPPORTED /* PBMPLUS PPM/PGM image file format */ +#undef RLE_SUPPORTED /* Utah RLE image file format */ +#define TARGA_SUPPORTED /* Targa image file format */ + +#undef TWO_FILE_COMMANDLINE /* You may need this on non-Unix systems */ +#undef NEED_SIGNAL_CATCHER /* Define this if you use jmemname.c */ #undef DONT_USE_B_MODE -/* #define PROGRESS_REPORT */ /* optional */ +/* #define PROGRESS_REPORT */ /* optional */ #endif /* JPEG_CJPEG_DJPEG */ /* @@ -57,7 +57,7 @@ * We do not support run-time selection of data precision, sorry. */ -#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */ +#define BITS_IN_JSAMPLE 8 /* use 8 or 12 */ /* @@ -69,7 +69,7 @@ * bytes of storage, whether actually used in an image or not.) */ -#define MAX_COMPONENTS 10 /* maximum number of image components */ +#define MAX_COMPONENTS 10 /* maximum number of image components */ /* @@ -107,8 +107,8 @@ typedef char JSAMPLE; #endif /* HAVE_UNSIGNED_CHAR */ -#define MAXJSAMPLE 255 -#define CENTERJSAMPLE 128 +#define MAXJSAMPLE 255 +#define CENTERJSAMPLE 128 #endif /* BITS_IN_JSAMPLE == 8 */ @@ -121,8 +121,8 @@ typedef char JSAMPLE; typedef short JSAMPLE; #define GETJSAMPLE(value) ((int) (value)) -#define MAXJSAMPLE 4095 -#define CENTERJSAMPLE 2048 +#define MAXJSAMPLE 4095 +#define CENTERJSAMPLE 2048 #endif /* BITS_IN_JSAMPLE == 12 */ @@ -188,13 +188,13 @@ typedef unsigned int UINT16; /* INT16 must hold at least the values -32768..32767. */ -#ifndef XMD_H /* X11/xmd.h correctly defines INT16 */ +#ifndef XMD_H /* X11/xmd.h correctly defines INT16 */ typedef short INT16; #endif /* INT32 must hold at least signed 32-bit values. */ -#ifndef XMD_H /* X11/xmd.h correctly defines INT32 */ +#ifndef XMD_H /* X11/xmd.h correctly defines INT32 */ typedef long INT32; #endif @@ -218,13 +218,13 @@ typedef unsigned int JDIMENSION; */ /* a function called through method pointers: */ -#define METHODDEF(type) static type +#define METHODDEF(type) static type /* a function used only in its module: */ -#define LOCAL(type) static type +#define LOCAL(type) static type /* a function referenced thru EXTERNs: */ -#define GLOBAL(type) type +#define GLOBAL(type) type /* a reference to a GLOBAL function: */ -#define EXTERN(type) extern type +#define EXTERN(type) extern type /* This macro is used to declare a "method", that is, a function pointer. @@ -263,11 +263,11 @@ typedef unsigned int JDIMENSION; #ifndef HAVE_BOOLEAN typedef int boolean; #endif -#ifndef FALSE /* in case these macros already exist */ -#define FALSE 0 /* values of boolean */ +#ifndef FALSE /* in case these macros already exist */ +#define FALSE 0 /* values of boolean */ #endif #ifndef TRUE -#define TRUE 1 +#define TRUE 1 #endif @@ -298,22 +298,22 @@ typedef int boolean; /* Capability options common to encoder and decoder: */ -#define DCT_ISLOW_SUPPORTED /* slow but accurate integer algorithm */ -#define DCT_IFAST_SUPPORTED /* faster, less accurate integer method */ -#define DCT_FLOAT_SUPPORTED /* floating-point: accurate, fast on fast HW */ +#define DCT_ISLOW_SUPPORTED /* slow but accurate integer algorithm */ +#define DCT_IFAST_SUPPORTED /* faster, less accurate integer method */ +#define DCT_FLOAT_SUPPORTED /* floating-point: accurate, fast on fast HW */ ///* Decoder capability options: */ // #undef D_ARITH_CODING_SUPPORTED /* Arithmetic coding back end? */ #define D_MULTISCAN_FILES_SUPPORTED /* Multiple-scan JPEG files? */ -#define D_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ -//#define SAVE_MARKERS_SUPPORTED /* jpeg_save_markers() needed? */ +#define D_PROGRESSIVE_SUPPORTED /* Progressive JPEG? (Requires MULTISCAN)*/ +//#define SAVE_MARKERS_SUPPORTED /* jpeg_save_markers() needed? */ //#define BLOCK_SMOOTHING_SUPPORTED /* Block smoothing? (Progressive only) */ -//#define IDCT_SCALING_SUPPORTED /* Output rescaling via IDCT? */ +//#define IDCT_SCALING_SUPPORTED /* Output rescaling via IDCT? */ //#undef UPSAMPLE_SCALING_SUPPORTED /* Output rescaling at upsample stage? */ //#define UPSAMPLE_MERGING_SUPPORTED /* Fast path for sloppy upsampling? */ -#define QUANT_1PASS_SUPPORTED /* 1-pass color quantization? */ -//#define QUANT_2PASS_SUPPORTED /* 2-pass color quantization? */ +#define QUANT_1PASS_SUPPORTED /* 1-pass color quantization? */ +//#define QUANT_2PASS_SUPPORTED /* 2-pass color quantization? */ /* more capability options later, no doubt */ @@ -333,10 +333,10 @@ typedef int boolean; * can't use color quantization if you change that value. */ -#define RGB_RED 0 /* Offset of Red in an RGB scanline element */ -#define RGB_GREEN 1 /* Offset of Green */ -#define RGB_BLUE 2 /* Offset of Blue */ -#define RGB_PIXELSIZE 3 /* JSAMPLEs per RGB scanline element */ +#define RGB_RED 0 /* Offset of Red in an RGB scanline element */ +#define RGB_GREEN 1 /* Offset of Green */ +#define RGB_BLUE 2 /* Offset of Blue */ +#define RGB_PIXELSIZE 3 /* JSAMPLEs per RGB scanline element */ /* Definitions for speed-related optimizations. */ @@ -347,11 +347,11 @@ typedef int boolean; */ #ifndef INLINE -#ifdef __GNUC__ /* for instance, GNU C knows about inline */ +#ifdef __GNUC__ /* for instance, GNU C knows about inline */ #define INLINE __inline__ #endif #ifndef INLINE -#define INLINE /* default is to define it as empty */ +#define INLINE /* default is to define it as empty */ #endif #endif @@ -362,7 +362,7 @@ typedef int boolean; */ #ifndef MULTIPLIER -#define MULTIPLIER int /* type for fastest integer multiply */ +#define MULTIPLIER int /* type for fastest integer multiply */ #endif @@ -402,8 +402,8 @@ typedef int boolean; /* Include auto-config file to find out which system include files we need. */ -//#include "jconfig.h" /* auto configuration options */ -#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ +//#include "jconfig.h" /* auto configuration options */ +#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */ /* * We need the NULL macro and size_t typedef. @@ -428,7 +428,7 @@ typedef int boolean; #include #endif -#include +// #include // !!! No in Ren-C release builds /* * We need memory copying and zeroing functions, plus strncpy(). @@ -443,14 +443,14 @@ typedef int boolean; #ifdef NEED_BSD_STRINGS #include -#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) -#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) +#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size)) +#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size)) #else /* not BSD, assume ANSI/SysV string lib */ #include -#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) -#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) +#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size)) +#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size)) #endif @@ -462,7 +462,7 @@ typedef int boolean; * we always use this SIZEOF() macro in place of using sizeof() directly. */ -#define SIZEOF(object) ((size_t) sizeof(object)) +#define SIZEOF(object) ((size_t) sizeof(object)) /* * The modules that use fread() and fwrite() always invoke them through @@ -508,7 +508,7 @@ typedef int boolean; typedef enum { -#define JMESSAGE(code,string) code , +#define JMESSAGE(code,string) code , #endif /* JMAKE_ENUM_LIST */ @@ -516,7 +516,7 @@ JMESSAGE(JMSG_NOMESSAGE, "Bogus message code %d") /* Must be first entry! */ /* For maintenance convenience, list is alphabetical by message code name */ JMESSAGE(JERR_ARITH_NOTIMPL, - "Sorry, there are legal restrictions on arithmetic coding") + "Sorry, there are legal restrictions on arithmetic coding") JMESSAGE(JERR_BAD_ALIGN_TYPE, "ALIGN_TYPE is wrong, please fix") JMESSAGE(JERR_BAD_ALLOC_CHUNK, "MAX_ALLOC_CHUNK is wrong, please fix") JMESSAGE(JERR_BAD_BUFFER_MODE, "Bogus buffer control mode") @@ -528,19 +528,19 @@ JMESSAGE(JERR_BAD_IN_COLORSPACE, "Bogus input colorspace") JMESSAGE(JERR_BAD_J_COLORSPACE, "Bogus JPEG colorspace") JMESSAGE(JERR_BAD_LENGTH, "Bogus marker length") JMESSAGE(JERR_BAD_LIB_VERSION, - "Wrong JPEG library version: library is %d, caller expects %d") + "Wrong JPEG library version: library is %d, caller expects %d") JMESSAGE(JERR_BAD_MCU_SIZE, "Sampling factors too large for interleaved scan") JMESSAGE(JERR_BAD_POOL_ID, "Invalid memory pool code %d") JMESSAGE(JERR_BAD_PRECISION, "Unsupported JPEG data precision %d") JMESSAGE(JERR_BAD_PROGRESSION, - "Invalid progressive parameters Ss=%d Se=%d Ah=%d Al=%d") + "Invalid progressive parameters Ss=%d Se=%d Ah=%d Al=%d") JMESSAGE(JERR_BAD_PROG_SCRIPT, - "Invalid progressive parameters at scan script entry %d") + "Invalid progressive parameters at scan script entry %d") JMESSAGE(JERR_BAD_SAMPLING, "Bogus sampling factors") JMESSAGE(JERR_BAD_SCAN_SCRIPT, "Invalid scan script at entry %d") JMESSAGE(JERR_BAD_STATE, "Improper call to JPEG library in state %d") JMESSAGE(JERR_BAD_STRUCT_SIZE, - "JPEG parameter struct mismatch: library thinks size is %u, caller expects %u") + "JPEG parameter struct mismatch: library thinks size is %u, caller expects %u") JMESSAGE(JERR_BAD_VIRTUAL_ACCESS, "Bogus virtual array access") JMESSAGE(JERR_BUFFER_SIZE, "Buffer passed to JPEG library is too small") JMESSAGE(JERR_CANT_SUSPEND, "Suspension not allowed here") @@ -564,7 +564,7 @@ JMESSAGE(JERR_IMAGE_TOO_BIG, "Maximum supported image dimension is %u pixels") JMESSAGE(JERR_INPUT_EMPTY, "Empty input file") JMESSAGE(JERR_INPUT_EOF, "Premature end of input file") JMESSAGE(JERR_MISMATCHED_QUANT_TABLE, - "Cannot transcode due to multiple use of quantization table %d") + "Cannot transcode due to multiple use of quantization table %d") JMESSAGE(JERR_MISSING_DATA, "Scan script does not transmit all data") JMESSAGE(JERR_MODE_CHANGE, "Invalid color quantization mode change") JMESSAGE(JERR_NOTIMPL, "Not implemented yet") @@ -576,7 +576,7 @@ JMESSAGE(JERR_NO_QUANT_TABLE, "Quantization table 0x%02x was not defined") JMESSAGE(JERR_NO_SOI, "Not a JPEG file: starts with 0x%02x 0x%02x") JMESSAGE(JERR_OUT_OF_MEMORY, "Insufficient memory (case %d)") JMESSAGE(JERR_QUANT_COMPONENTS, - "Cannot quantize more than %d color components") + "Cannot quantize more than %d color components") JMESSAGE(JERR_QUANT_FEW_COLORS, "Cannot quantize to fewer than %d colors") JMESSAGE(JERR_QUANT_MANY_COLORS, "Cannot quantize to more than %d colors") JMESSAGE(JERR_SOF_DUPLICATE, "Invalid JPEG file structure: two SOF markers") @@ -588,7 +588,7 @@ JMESSAGE(JERR_TFILE_CREATE, "Failed to create temporary file %s") JMESSAGE(JERR_TFILE_READ, "Read failed on temporary file") JMESSAGE(JERR_TFILE_SEEK, "Seek failed on temporary file") JMESSAGE(JERR_TFILE_WRITE, - "Write failed on temporary file --- out of disk space?") + "Write failed on temporary file --- out of disk space?") JMESSAGE(JERR_TOO_LITTLE_DATA, "Application transferred too few scanlines") JMESSAGE(JERR_UNKNOWN_MARKER, "Unsupported marker type 0x%02x") JMESSAGE(JERR_VIRTUAL_BUG, "Virtual array controller messed up") @@ -598,9 +598,9 @@ JMESSAGE(JERR_XMS_WRITE, "Write to XMS failed") JMESSAGE(JMSG_COPYRIGHT, JCOPYRIGHT) JMESSAGE(JMSG_VERSION, JVERSION) JMESSAGE(JTRC_16BIT_TABLES, - "Caution: quantization tables are too coarse for baseline JPEG") + "Caution: quantization tables are too coarse for baseline JPEG") JMESSAGE(JTRC_ADOBE, - "Adobe APP14 marker: version %d, flags 0x%04x 0x%04x, transform %d") + "Adobe APP14 marker: version %d, flags 0x%04x 0x%04x, transform %d") JMESSAGE(JTRC_APP0, "Unknown APP0 marker (not JFIF), length %u") JMESSAGE(JTRC_APP14, "Unknown APP14 marker (not Adobe), length %u") JMESSAGE(JTRC_DAC, "Define Arithmetic Table 0x%02x: 0x%02x") @@ -613,9 +613,9 @@ JMESSAGE(JTRC_EOI, "End Of Image") JMESSAGE(JTRC_HUFFBITS, " %3d %3d %3d %3d %3d %3d %3d %3d") JMESSAGE(JTRC_JFIF, "JFIF APP0 marker: version %d.%02d, density %dx%d %d") JMESSAGE(JTRC_JFIF_BADTHUMBNAILSIZE, - "Warning: thumbnail image size does not match data length %u") + "Warning: thumbnail image size does not match data length %u") JMESSAGE(JTRC_JFIF_EXTENSION, - "JFIF extension marker: type 0x%02x, length %u") + "JFIF extension marker: type 0x%02x, length %u") JMESSAGE(JTRC_JFIF_THUMBNAIL, " with %d x %d thumbnail image") JMESSAGE(JTRC_MISC_MARKER, "Miscellaneous marker 0x%02x, length %u") JMESSAGE(JTRC_PARMLESS_MARKER, "Unexpected marker 0x%02x") @@ -626,7 +626,7 @@ JMESSAGE(JTRC_QUANT_SELECTED, "Selected %d colors for quantization") JMESSAGE(JTRC_RECOVERY_ACTION, "At marker 0x%02x, recovery action %d") JMESSAGE(JTRC_RST, "RST%d") JMESSAGE(JTRC_SMOOTH_NOTIMPL, - "Smoothing not supported with nonstandard sampling ratios") + "Smoothing not supported with nonstandard sampling ratios") JMESSAGE(JTRC_SOF, "Start Of Frame 0x%02x: width=%u, height=%u, components=%d") JMESSAGE(JTRC_SOF_COMPONENT, " Component %d: %dhx%dv q=%d") JMESSAGE(JTRC_SOI, "Start of Image") @@ -636,26 +636,26 @@ JMESSAGE(JTRC_SOS_PARAMS, " Ss=%d, Se=%d, Ah=%d, Al=%d") JMESSAGE(JTRC_TFILE_CLOSE, "Closed temporary file %s") JMESSAGE(JTRC_TFILE_OPEN, "Opened temporary file %s") JMESSAGE(JTRC_THUMB_JPEG, - "JFIF extension marker: JPEG-compressed thumbnail image, length %u") + "JFIF extension marker: JPEG-compressed thumbnail image, length %u") JMESSAGE(JTRC_THUMB_PALETTE, - "JFIF extension marker: palette thumbnail image, length %u") + "JFIF extension marker: palette thumbnail image, length %u") JMESSAGE(JTRC_THUMB_RGB, - "JFIF extension marker: RGB thumbnail image, length %u") + "JFIF extension marker: RGB thumbnail image, length %u") JMESSAGE(JTRC_UNKNOWN_IDS, - "Unrecognized component IDs %d %d %d, assuming YCbCr") + "Unrecognized component IDs %d %d %d, assuming YCbCr") JMESSAGE(JTRC_XMS_CLOSE, "Freed XMS handle %u") JMESSAGE(JTRC_XMS_OPEN, "Obtained XMS handle %u") JMESSAGE(JWRN_ADOBE_XFORM, "Unknown Adobe color transform code %d") JMESSAGE(JWRN_BOGUS_PROGRESSION, - "Inconsistent progression sequence for component %d coefficient %d") + "Inconsistent progression sequence for component %d coefficient %d") JMESSAGE(JWRN_EXTRANEOUS_DATA, - "Corrupt JPEG data: %u extraneous bytes before marker 0x%02x") + "Corrupt JPEG data: %u extraneous bytes before marker 0x%02x") JMESSAGE(JWRN_HIT_MARKER, "Corrupt JPEG data: premature end of data segment") JMESSAGE(JWRN_HUFF_BAD_CODE, "Corrupt JPEG data: bad Huffman code") JMESSAGE(JWRN_JFIF_MAJOR, "Warning: unknown JFIF revision number %d.%02d") JMESSAGE(JWRN_JPEG_EOF, "Premature end of JPEG file") JMESSAGE(JWRN_MUST_RESYNC, - "Corrupt JPEG data: found marker 0x%02x instead of RST%d") + "Corrupt JPEG data: found marker 0x%02x instead of RST%d") JMESSAGE(JWRN_NOT_SEQUENTIAL, "Invalid SOS parameters for sequential JPEG") JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") @@ -708,7 +708,7 @@ JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo))) -#define MAKESTMT(stuff) do { stuff } while (0) +#define MAKESTMT(stuff) do { stuff } while (0) /* Nonfatal errors (we can keep going, but the data is probably corrupt) */ #define WARNMS(cinfo,code) \ @@ -739,26 +739,26 @@ JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl))) #define TRACEMS3(cinfo,lvl,code,p1,p2,p3) \ MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ - _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); \ - (cinfo)->err->msg_code = (code); \ - (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) #define TRACEMS4(cinfo,lvl,code,p1,p2,p3,p4) \ MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ - _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ - (cinfo)->err->msg_code = (code); \ - (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) #define TRACEMS5(cinfo,lvl,code,p1,p2,p3,p4,p5) \ MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ - _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ - _mp[4] = (p5); \ - (cinfo)->err->msg_code = (code); \ - (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) #define TRACEMS8(cinfo,lvl,code,p1,p2,p3,p4,p5,p6,p7,p8) \ MAKESTMT(int * _mp = (cinfo)->err->msg_parm.i; \ - _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ - _mp[4] = (p5); _mp[5] = (p6); _mp[6] = (p7); _mp[7] = (p8); \ - (cinfo)->err->msg_code = (code); \ - (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) + _mp[0] = (p1); _mp[1] = (p2); _mp[2] = (p3); _mp[3] = (p4); \ + _mp[4] = (p5); _mp[5] = (p6); _mp[6] = (p7); _mp[7] = (p8); \ + (cinfo)->err->msg_code = (code); \ + (*(cinfo)->err->emit_message) ((j_common_ptr) (cinfo), (lvl)); ) #define TRACEMSS(cinfo,lvl,code,str) \ ((cinfo)->err->msg_code = (code), \ strncpy((cinfo)->err->msg_parm.s, (str), JMSG_STR_PARM_MAX), \ @@ -788,17 +788,17 @@ JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") * manual configuration options that most people need not worry about. */ -#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */ -//#include "jconfig.h" /* widely used configuration options */ +#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */ +//#include "jconfig.h" /* widely used configuration options */ #endif -//#include "jmorecfg.h" /* seldom changed options */ +//#include "jmorecfg.h" /* seldom changed options */ /* Version ID for the JPEG library. * Might be useful for tests like "#if JPEG_LIB_VERSION >= 60". */ -#define JPEG_LIB_VERSION 62 /* Version 6b */ +#define JPEG_LIB_VERSION 62 /* Version 6b */ /* Various constants determining the sizes of things. @@ -806,13 +806,13 @@ JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") * if you want to be compatible. */ -#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ -#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ -#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ -#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ -#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ -#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ -#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ +#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */ +#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */ +#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */ +#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */ +#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */ +#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */ +#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */ /* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; * the PostScript DCT filter can emit files with many more than 10 blocks/MCU. * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU @@ -831,16 +831,16 @@ JMESSAGE(JWRN_TOO_MUCH_DATA, "Application transferred too many scanlines") * but the pointer arrays can fit in near memory. */ -typedef JSAMPLE FAR *JSAMPROW; /* ptr to one image row of pixel samples. */ -typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ -typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ +typedef JSAMPLE FAR *JSAMPROW; /* ptr to one image row of pixel samples. */ +typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */ +typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */ -typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ -typedef JBLOCK FAR *JBLOCKROW; /* pointer to one row of coefficient blocks */ -typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ -typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ +typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */ +typedef JBLOCK FAR *JBLOCKROW; /* pointer to one row of coefficient blocks */ +typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */ +typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */ -typedef JCOEF FAR *JCOEFPTR; /* useful in a couple of places */ +typedef JCOEF FAR *JCOEFPTR; /* useful in a couple of places */ /* Types for JPEG compression parameters and working tables. */ @@ -853,13 +853,13 @@ typedef struct { * (not the zigzag order in which they are stored in a JPEG DQT marker). * CAUTION: IJG versions prior to v6a kept this array in zigzag order. */ - UINT16 quantval[DCTSIZE2]; /* quantization step for each coefficient */ + UINT16 quantval[DCTSIZE2]; /* quantization step for each coefficient */ /* This field is used only during compression. It's initialized FALSE when * the table is created, and set TRUE when it's been output to the file. * You could suppress output of a table by setting this to TRUE. * (See jpeg_suppress_tables for an example.) */ - boolean sent_table; /* TRUE when table has been output */ + boolean sent_table; /* TRUE when table has been output */ } JQUANT_TBL; @@ -867,15 +867,15 @@ typedef struct { typedef struct { /* These two fields directly represent the contents of a JPEG DHT marker */ - UINT8 bits[17]; /* bits[k] = # of symbols with codes of */ - /* length k bits; bits[0] is unused */ - UINT8 huffval[256]; /* The symbols, in order of incr code length */ + UINT8 bits[17]; /* bits[k] = # of symbols with codes of */ + /* length k bits; bits[0] is unused */ + UINT8 huffval[256]; /* The symbols, in order of incr code length */ /* This field is used only during compression. It's initialized FALSE when * the table is created, and set TRUE when it's been output to the file. * You could suppress output of a table by setting this to TRUE. * (See jpeg_suppress_tables for an example.) */ - boolean sent_table; /* TRUE when table has been output */ + boolean sent_table; /* TRUE when table has been output */ } JHUFF_TBL; @@ -885,20 +885,20 @@ typedef struct { /* These values are fixed over the whole image. */ /* For compression, they must be supplied by parameter setup; */ /* for decompression, they are read from the SOF marker. */ - int component_id; /* identifier for this component (0..255) */ - int component_index; /* its index in SOF or cinfo->comp_info[] */ - int h_samp_factor; /* horizontal sampling factor (1..4) */ - int v_samp_factor; /* vertical sampling factor (1..4) */ - int quant_tbl_no; /* quantization table selector (0..3) */ + int component_id; /* identifier for this component (0..255) */ + int component_index; /* its index in SOF or cinfo->comp_info[] */ + int h_samp_factor; /* horizontal sampling factor (1..4) */ + int v_samp_factor; /* vertical sampling factor (1..4) */ + int quant_tbl_no; /* quantization table selector (0..3) */ /* These values may vary between scans. */ /* For compression, they must be supplied by parameter setup; */ /* for decompression, they are read from the SOS marker. */ /* The decompressor output side may not use these variables. */ - int dc_tbl_no; /* DC entropy table selector (0..3) */ - int ac_tbl_no; /* AC entropy table selector (0..3) */ - + int dc_tbl_no; /* DC entropy table selector (0..3) */ + int ac_tbl_no; /* AC entropy table selector (0..3) */ + /* Remaining fields should be treated as private by applications. */ - + /* These values are computed during compression or decompression startup: */ /* Component's size in DCT blocks. * Any dummy blocks added to complete an MCU are not counted; therefore @@ -919,22 +919,22 @@ typedef struct { * and similarly for height. For decompression, IDCT scaling is included, so * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE) */ - JDIMENSION downsampled_width; /* actual width in samples */ + JDIMENSION downsampled_width; /* actual width in samples */ JDIMENSION downsampled_height; /* actual height in samples */ /* This flag is used only for decompression. In cases where some of the * components will be ignored (eg grayscale output from YCbCr image), * we can skip most computations for the unused components. */ - boolean component_needed; /* do we need the value of this component? */ + boolean component_needed; /* do we need the value of this component? */ /* These values are computed before starting a scan of the component. */ /* The decompressor output side may not use these variables. */ - int MCU_width; /* number of blocks per MCU, horizontally */ - int MCU_height; /* number of blocks per MCU, vertically */ - int MCU_blocks; /* MCU_width * MCU_height */ - int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */ - int last_col_width; /* # of non-dummy blocks across in last MCU */ - int last_row_height; /* # of non-dummy blocks down in last MCU */ + int MCU_width; /* number of blocks per MCU, horizontally */ + int MCU_height; /* number of blocks per MCU, vertically */ + int MCU_blocks; /* MCU_width * MCU_height */ + int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */ + int last_col_width; /* # of non-dummy blocks across in last MCU */ + int last_row_height; /* # of non-dummy blocks down in last MCU */ /* Saved quantization table for component; NULL if none yet saved. * See jdinput.c comments about the need for this information. @@ -950,10 +950,10 @@ typedef struct { /* The script for encoding a multiple-scan file is an array of these: */ typedef struct { - int comps_in_scan; /* number of components encoded in this scan */ + int comps_in_scan; /* number of components encoded in this scan */ int component_index[MAX_COMPS_IN_SCAN]; /* their SOF/comp_info[] indexes */ - int Ss, Se; /* progressive JPEG spectral selection parms */ - int Ah, Al; /* progressive JPEG successive approx. parms */ + int Ss, Se; /* progressive JPEG spectral selection parms */ + int Ah, Al; /* progressive JPEG successive approx. parms */ } jpeg_scan_info; /* The decompressor can save APPn and COM markers in a list of these: */ @@ -961,65 +961,65 @@ typedef struct { typedef struct jpeg_marker_struct FAR * jpeg_saved_marker_ptr; struct jpeg_marker_struct { - jpeg_saved_marker_ptr next; /* next in list, or NULL */ - UINT8 marker; /* marker code: JPEG_COM, or JPEG_APP0+n */ - unsigned int original_length; /* # bytes of data in the file */ - unsigned int data_length; /* # bytes of data saved at data[] */ - JOCTET FAR * data; /* the data contained in the marker */ + jpeg_saved_marker_ptr next; /* next in list, or NULL */ + UINT8 marker; /* marker code: JPEG_COM, or JPEG_APP0+n */ + unsigned int original_length; /* # bytes of data in the file */ + unsigned int data_length; /* # bytes of data saved at data[] */ + JOCTET FAR * data; /* the data contained in the marker */ /* the marker length word is not counted in data_length or original_length */ }; /* Known color spaces. */ typedef enum { - JCS_UNKNOWN, /* error/unspecified */ - JCS_GRAYSCALE, /* monochrome */ - JCS_RGB, /* red/green/blue */ - JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */ - JCS_CMYK, /* C/M/Y/K */ - JCS_YCCK /* Y/Cb/Cr/K */ + JCS_UNKNOWN, /* error/unspecified */ + JCS_GRAYSCALE, /* monochrome */ + JCS_RGB, /* red/green/blue */ + JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */ + JCS_CMYK, /* C/M/Y/K */ + JCS_YCCK /* Y/Cb/Cr/K */ } J_COLOR_SPACE; /* DCT/IDCT algorithm options. */ typedef enum { - JDCT_ISLOW, /* slow but accurate integer algorithm */ - JDCT_IFAST, /* faster, less accurate integer method */ - JDCT_FLOAT /* floating-point: accurate, fast on fast HW */ + JDCT_ISLOW, /* slow but accurate integer algorithm */ + JDCT_IFAST, /* faster, less accurate integer method */ + JDCT_FLOAT /* floating-point: accurate, fast on fast HW */ } J_DCT_METHOD; -#ifndef JDCT_DEFAULT /* may be overridden in jconfig.h */ +#ifndef JDCT_DEFAULT /* may be overridden in jconfig.h */ #define JDCT_DEFAULT JDCT_ISLOW #endif -#ifndef JDCT_FASTEST /* may be overridden in jconfig.h */ +#ifndef JDCT_FASTEST /* may be overridden in jconfig.h */ #define JDCT_FASTEST JDCT_IFAST #endif /* Dithering options for decompression. */ typedef enum { - JDITHER_NONE, /* no dithering */ - JDITHER_ORDERED, /* simple ordered dither */ - JDITHER_FS /* Floyd-Steinberg error diffusion dither */ + JDITHER_BLANK, /* no dithering */ + JDITHER_ORDERED, /* simple ordered dither */ + JDITHER_FS /* Floyd-Steinberg error diffusion dither */ } J_DITHER_MODE; /* Common fields between JPEG compression and decompression master structs. */ #define jpeg_common_fields \ - struct jpeg_error_mgr * err; /* Error handler module */\ - struct jpeg_memory_mgr * mem; /* Memory manager module */\ + struct jpeg_error_mgr * err; /* Error handler module */\ + struct jpeg_memory_mgr * mem; /* Memory manager module */\ struct jpeg_progress_mgr * progress; /* Progress monitor, or NULL if none */\ - void * client_data; /* Available for use by application */\ - boolean is_decompressor; /* So common code can tell which is which */\ - int global_state /* For checking call sequence validity */ + void * client_data; /* Available for use by application */\ + boolean is_decompressor; /* So common code can tell which is which */\ + int global_state /* For checking call sequence validity */ /* Routines that are to be used by both halves of the library are declared * to receive a pointer to this structure. There are no actual instances of * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct. */ struct jpeg_common_struct { - jpeg_common_fields; /* Fields common to both master struct types */ + jpeg_common_fields; /* Fields common to both master struct types */ /* Additional fields follow in an actual jpeg_compress_struct or * jpeg_decompress_struct. All three structs must agree on these * initial fields! (This would be a lot cleaner in C++.) @@ -1034,7 +1034,7 @@ typedef struct jpeg_decompress_struct * j_decompress_ptr; /* Master record for a compression instance */ struct jpeg_compress_struct { - jpeg_common_fields; /* Fields shared with jpeg_decompress_struct */ + jpeg_common_fields; /* Fields shared with jpeg_decompress_struct */ /* Destination for compressed data */ struct jpeg_destination_mgr * dest; @@ -1044,12 +1044,12 @@ struct jpeg_compress_struct { * be correct before you can even call jpeg_set_defaults(). */ - JDIMENSION image_width; /* input image width */ - JDIMENSION image_height; /* input image height */ - int input_components; /* # of color components in input image */ - J_COLOR_SPACE in_color_space; /* colorspace of input image */ + JDIMENSION image_width; /* input image width */ + JDIMENSION image_height; /* input image height */ + int input_components; /* # of color components in input image */ + J_COLOR_SPACE in_color_space; /* colorspace of input image */ - double input_gamma; /* image gamma of input image */ + double input_gamma; /* image gamma of input image */ /* Compression parameters --- these fields must be set before calling * jpeg_start_compress(). We recommend calling jpeg_set_defaults() to @@ -1059,38 +1059,38 @@ struct jpeg_compress_struct { * helper routines to simplify changing parameters. */ - int data_precision; /* bits of precision in image data */ + int data_precision; /* bits of precision in image data */ - int num_components; /* # of color components in JPEG image */ + int num_components; /* # of color components in JPEG image */ J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ jpeg_component_info * comp_info; /* comp_info[i] describes component that appears i'th in SOF */ - + JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS]; /* ptrs to coefficient quantization tables, or NULL if not defined */ - + JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; /* ptrs to Huffman coding tables, or NULL if not defined */ - + UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */ - int num_scans; /* # of entries in scan_info array */ + int num_scans; /* # of entries in scan_info array */ const jpeg_scan_info * scan_info; /* script for multi-scan file, or NULL */ /* The default value of scan_info is NULL, which causes a single-scan * sequential JPEG file to be emitted. To create a multi-scan file, * set num_scans and scan_info to point to an array of scan definitions. */ - boolean raw_data_in; /* TRUE=caller supplies downsampled data */ - boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ - boolean optimize_coding; /* TRUE=optimize entropy encoding parms */ - boolean CCIR601_sampling; /* TRUE=first samples are cosited */ - int smoothing_factor; /* 1..100, or 0 for no input smoothing */ - J_DCT_METHOD dct_method; /* DCT algorithm selector */ + boolean raw_data_in; /* TRUE=caller supplies downsampled data */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + boolean optimize_coding; /* TRUE=optimize entropy encoding parms */ + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + int smoothing_factor; /* 1..100, or 0 for no input smoothing */ + J_DCT_METHOD dct_method; /* DCT algorithm selector */ /* The restart interval can be specified in absolute MCUs by setting * restart_interval, or in MCU rows by setting restart_in_rows @@ -1098,28 +1098,28 @@ struct jpeg_compress_struct { * for each scan). */ unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */ - int restart_in_rows; /* if > 0, MCU rows per restart interval */ + int restart_in_rows; /* if > 0, MCU rows per restart interval */ /* Parameters controlling emission of special markers. */ - boolean write_JFIF_header; /* should a JFIF marker be written? */ - UINT8 JFIF_major_version; /* What to write for the JFIF version number */ + boolean write_JFIF_header; /* should a JFIF marker be written? */ + UINT8 JFIF_major_version; /* What to write for the JFIF version number */ UINT8 JFIF_minor_version; /* These three values are not used by the JPEG code, merely copied */ /* into the JFIF APP0 marker. density_unit can be 0 for unknown, */ /* 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect */ /* ratio is defined by X_density/Y_density even when density_unit=0. */ - UINT8 density_unit; /* JFIF code for pixel size units */ - UINT16 X_density; /* Horizontal pixel density */ - UINT16 Y_density; /* Vertical pixel density */ - boolean write_Adobe_marker; /* should an Adobe marker be written? */ - + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean write_Adobe_marker; /* should an Adobe marker be written? */ + /* State variable: index of next scanline to be written to * jpeg_write_scanlines(). Application may use this to control its * processing loop, e.g., "while (next_scanline < image_height)". */ - JDIMENSION next_scanline; /* 0 .. image_height-1 */ + JDIMENSION next_scanline; /* 0 .. image_height-1 */ /* Remaining fields are known throughout compressor, but generally * should not be touched by a surrounding application. @@ -1128,40 +1128,40 @@ struct jpeg_compress_struct { /* * These fields are computed during compression startup */ - boolean progressive_mode; /* TRUE if scan script uses progressive mode */ - int max_h_samp_factor; /* largest h_samp_factor */ - int max_v_samp_factor; /* largest v_samp_factor */ + boolean progressive_mode; /* TRUE if scan script uses progressive mode */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ - JDIMENSION total_iMCU_rows; /* # of iMCU rows to be input to coef ctlr */ + JDIMENSION total_iMCU_rows; /* # of iMCU rows to be input to coef ctlr */ /* The coefficient controller receives data in units of MCU rows as defined * for fully interleaved scans (whether the JPEG file is interleaved or not). * There are v_samp_factor * DCTSIZE sample rows of each component in an * "iMCU" (interleaved MCU) row. */ - + /* * These fields are valid during any one scan. * They describe the components and MCUs actually appearing in the scan. */ - int comps_in_scan; /* # of JPEG components in this scan */ + int comps_in_scan; /* # of JPEG components in this scan */ jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; /* *cur_comp_info[i] describes component that appears i'th in SOS */ - - JDIMENSION MCUs_per_row; /* # of MCUs across the image */ - JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ - - int blocks_in_MCU; /* # of DCT blocks per MCU */ + + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + + int blocks_in_MCU; /* # of DCT blocks per MCU */ int MCU_membership[C_MAX_BLOCKS_IN_MCU]; /* MCU_membership[i] is index in cur_comp_info of component owning */ /* i'th block in an MCU */ - int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ /* * Links to compression subobjects (methods and private variables of modules) */ struct jpeg_comp_master * master; - struct jpeg_c_main_controller * main; + struct jpeg_c_main_controller * main_ptr; struct jpeg_c_prep_controller * prep; struct jpeg_c_coef_controller * coef; struct jpeg_marker_writer * marker; @@ -1177,7 +1177,7 @@ struct jpeg_compress_struct { /* Master record for a decompression instance */ struct jpeg_decompress_struct { - jpeg_common_fields; /* Fields shared with jpeg_compress_struct */ + jpeg_common_fields; /* Fields shared with jpeg_compress_struct */ /* Source of compressed data */ struct jpeg_source_mgr * src; @@ -1185,9 +1185,9 @@ struct jpeg_decompress_struct { /* Basic description of image --- filled in by jpeg_read_header(). */ /* Application may inspect these values to decide how to process image. */ - JDIMENSION image_width; /* nominal image width (from SOF marker) */ - JDIMENSION image_height; /* nominal image height */ - int num_components; /* # of color components in JPEG image */ + JDIMENSION image_width; /* nominal image width (from SOF marker) */ + JDIMENSION image_height; /* nominal image height */ + int num_components; /* # of color components in JPEG image */ J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */ /* Decompression processing parameters --- these fields must be set before @@ -1199,24 +1199,24 @@ struct jpeg_decompress_struct { unsigned int scale_num, scale_denom; /* fraction by which to scale image */ - double output_gamma; /* image gamma wanted in output */ + double output_gamma; /* image gamma wanted in output */ - boolean buffered_image; /* TRUE=multiple output passes */ - boolean raw_data_out; /* TRUE=downsampled data wanted */ + boolean buffered_image; /* TRUE=multiple output passes */ + boolean raw_data_out; /* TRUE=downsampled data wanted */ - J_DCT_METHOD dct_method; /* IDCT algorithm selector */ - boolean do_fancy_upsampling; /* TRUE=apply fancy upsampling */ - boolean do_block_smoothing; /* TRUE=apply interblock smoothing */ + J_DCT_METHOD dct_method; /* IDCT algorithm selector */ + boolean do_fancy_upsampling; /* TRUE=apply fancy upsampling */ + boolean do_block_smoothing; /* TRUE=apply interblock smoothing */ - boolean quantize_colors; /* TRUE=colormapped output wanted */ + boolean quantize_colors; /* TRUE=colormapped output wanted */ /* the following are ignored if not quantize_colors: */ - J_DITHER_MODE dither_mode; /* type of color dithering to use */ - boolean two_pass_quantize; /* TRUE=use two-pass color quantization */ - int desired_number_of_colors; /* max # colors to use in created colormap */ + J_DITHER_MODE dither_mode; /* type of color dithering to use */ + boolean two_pass_quantize; /* TRUE=use two-pass color quantization */ + int desired_number_of_colors; /* max # colors to use in created colormap */ /* these are significant only in buffered-image mode: */ - boolean enable_1pass_quant; /* enable future use of 1-pass quantizer */ + boolean enable_1pass_quant; /* enable future use of 1-pass quantizer */ boolean enable_external_quant;/* enable future use of external colormap */ - boolean enable_2pass_quant; /* enable future use of 2-pass quantizer */ + boolean enable_2pass_quant; /* enable future use of 2-pass quantizer */ /* Description of actual output image that will be returned to application. * These fields are computed by jpeg_start_decompress(). @@ -1224,14 +1224,14 @@ struct jpeg_decompress_struct { * in advance of calling jpeg_start_decompress(). */ - JDIMENSION output_width; /* scaled image width */ - JDIMENSION output_height; /* scaled image height */ - int out_color_components; /* # of color components in out_color_space */ - int output_components; /* # of color components returned */ + JDIMENSION output_width; /* scaled image width */ + JDIMENSION output_height; /* scaled image height */ + int out_color_components; /* # of color components in out_color_space */ + int output_components; /* # of color components returned */ /* output_components is 1 (a colormap index) when quantizing colors; * otherwise it equals out_color_components. */ - int rec_outbuf_height; /* min recommended height of scanline buffer */ + int rec_outbuf_height; /* min recommended height of scanline buffer */ /* If the buffer passed to jpeg_read_scanlines() is less than this many rows * high, space and time will be wasted due to unnecessary data copying. * Usually rec_outbuf_height will be 1 or 2, at most 4. @@ -1243,8 +1243,8 @@ struct jpeg_decompress_struct { * jpeg_start_decompress or jpeg_start_output. * The map has out_color_components rows and actual_number_of_colors columns. */ - int actual_number_of_colors; /* number of entries in use */ - JSAMPARRAY colormap; /* The color map as a 2-D pixel array */ + int actual_number_of_colors; /* number of entries in use */ + JSAMPARRAY colormap; /* The color map as a 2-D pixel array */ /* State variables: these variables indicate the progress of decompression. * The application may examine these but must not modify them. @@ -1254,20 +1254,20 @@ struct jpeg_decompress_struct { * Application may use this to control its processing loop, e.g., * "while (output_scanline < output_height)". */ - JDIMENSION output_scanline; /* 0 .. output_height-1 */ + JDIMENSION output_scanline; /* 0 .. output_height-1 */ /* Current input scan number and number of iMCU rows completed in scan. * These indicate the progress of the decompressor input side. */ - int input_scan_number; /* Number of SOS markers seen so far */ - JDIMENSION input_iMCU_row; /* Number of iMCU rows completed */ + int input_scan_number; /* Number of SOS markers seen so far */ + JDIMENSION input_iMCU_row; /* Number of iMCU rows completed */ /* The "output scan number" is the notional scan being displayed by the * output side. The decompressor will not allow output scan/row number * to get ahead of input scan/row, but it can fall arbitrarily far behind. */ - int output_scan_number; /* Nominal scan number being displayed */ - JDIMENSION output_iMCU_row; /* Number of iMCU rows read */ + int output_scan_number; /* Nominal scan number being displayed */ + JDIMENSION output_iMCU_row; /* Number of iMCU rows read */ /* Current progression status. coef_bits[c][i] indicates the precision * with which component c's DCT coefficient i (in zigzag order) is known. @@ -1276,7 +1276,7 @@ struct jpeg_decompress_struct { * (thus, 0 at completion of the progression). * This pointer is NULL when reading a non-progressive file. */ - int (*coef_bits)[DCTSIZE2]; /* -1 or current Al value for each coef */ + int (*coef_bits)[DCTSIZE2]; /* -1 or current Al value for each coef */ /* Internal JPEG parameters --- the application usually need not look at * these fields. Note that the decompressor output side may not use @@ -1298,13 +1298,13 @@ struct jpeg_decompress_struct { * are given in SOF/SOS markers or defined to be reset by SOI. */ - int data_precision; /* bits of precision in image data */ + int data_precision; /* bits of precision in image data */ jpeg_component_info * comp_info; /* comp_info[i] describes component that appears i'th in SOF */ - boolean progressive_mode; /* TRUE if SOFn specifies progressive mode */ - boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ + boolean progressive_mode; /* TRUE if SOFn specifies progressive mode */ + boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */ UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */ @@ -1315,17 +1315,17 @@ struct jpeg_decompress_struct { /* These fields record data obtained from optional markers recognized by * the JPEG library. */ - boolean saw_JFIF_marker; /* TRUE iff a JFIF APP0 marker was found */ + boolean saw_JFIF_marker; /* TRUE iff a JFIF APP0 marker was found */ /* Data copied from JFIF marker; only valid if saw_JFIF_marker is TRUE: */ - UINT8 JFIF_major_version; /* JFIF version number */ + UINT8 JFIF_major_version; /* JFIF version number */ UINT8 JFIF_minor_version; - UINT8 density_unit; /* JFIF code for pixel size units */ - UINT16 X_density; /* Horizontal pixel density */ - UINT16 Y_density; /* Vertical pixel density */ - boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */ - UINT8 Adobe_transform; /* Color transform code from Adobe marker */ + UINT8 density_unit; /* JFIF code for pixel size units */ + UINT16 X_density; /* Horizontal pixel density */ + UINT16 Y_density; /* Vertical pixel density */ + boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */ + UINT8 Adobe_transform; /* Color transform code from Adobe marker */ - boolean CCIR601_sampling; /* TRUE=first samples are cosited */ + boolean CCIR601_sampling; /* TRUE=first samples are cosited */ /* Aside from the specific data retained from APPn markers known to the * library, the uninterpreted contents of any or all APPn and COM markers @@ -1340,12 +1340,12 @@ struct jpeg_decompress_struct { /* * These fields are computed during decompression startup */ - int max_h_samp_factor; /* largest h_samp_factor */ - int max_v_samp_factor; /* largest v_samp_factor */ + int max_h_samp_factor; /* largest h_samp_factor */ + int max_v_samp_factor; /* largest v_samp_factor */ - int min_DCT_scaled_size; /* smallest DCT_scaled_size of any component */ + int min_DCT_scaled_size; /* smallest DCT_scaled_size of any component */ - JDIMENSION total_iMCU_rows; /* # of iMCU rows in image */ + JDIMENSION total_iMCU_rows; /* # of iMCU rows in image */ /* The coefficient controller's input and output progress is measured in * units of "iMCU" (interleaved MCU) rows. These are the same as MCU rows * in fully interleaved JPEG scans, but are used whether the scan is @@ -1361,19 +1361,19 @@ struct jpeg_decompress_struct { * They describe the components and MCUs actually appearing in the scan. * Note that the decompressor output side must not use these fields. */ - int comps_in_scan; /* # of JPEG components in this scan */ + int comps_in_scan; /* # of JPEG components in this scan */ jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN]; /* *cur_comp_info[i] describes component that appears i'th in SOS */ - JDIMENSION MCUs_per_row; /* # of MCUs across the image */ - JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ + JDIMENSION MCUs_per_row; /* # of MCUs across the image */ + JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */ - int blocks_in_MCU; /* # of DCT blocks per MCU */ + int blocks_in_MCU; /* # of DCT blocks per MCU */ int MCU_membership[D_MAX_BLOCKS_IN_MCU]; /* MCU_membership[i] is index in cur_comp_info of component owning */ /* i'th block in an MCU */ - int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ + int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */ /* This field is shared between entropy decoder and marker parser. * It is either zero or the code of a JPEG marker that has been @@ -1385,7 +1385,7 @@ struct jpeg_decompress_struct { * Links to decompression subobjects (methods, private variables of modules) */ struct jpeg_decomp_master * master; - struct jpeg_d_main_controller * main; + struct jpeg_d_main_controller * main_ptr; struct jpeg_d_coef_controller * coef; struct jpeg_d_post_controller * post; struct jpeg_input_controller * inputctl; @@ -1417,10 +1417,10 @@ struct jpeg_error_mgr { JMETHOD(void, output_message, (j_common_ptr cinfo)); /* Format a message string for the most recent JPEG error or message */ JMETHOD(void, format_message, (j_common_ptr cinfo, char * buffer)); -#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */ +#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */ /* Reset error state variables at start of a new image */ JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo)); - + /* The message ID code and any parameters are saved here. * A message can have one string parameter or up to 8 int parameters. */ @@ -1430,18 +1430,18 @@ struct jpeg_error_mgr { int i[8]; char s[JMSG_STR_PARM_MAX]; } msg_parm; - + /* Standard state variables for error facility */ - - int trace_level; /* max msg_level that will be displayed */ - + + int trace_level; /* max msg_level that will be displayed */ + /* For recoverable corrupt-data errors, we emit a warning message, * but keep going unless emit_message chooses to abort. emit_message * should count warnings in num_warnings. The surrounding application * can check for bad data by seeing if num_warnings is nonzero at the * end of processing. */ - long num_warnings; /* number of corrupt-data warnings */ + long num_warnings; /* number of corrupt-data warnings */ /* These fields point to the table(s) of error message strings. * An application can change the table pointer to switch to a different @@ -1459,8 +1459,8 @@ struct jpeg_error_mgr { * It contains strings numbered first_addon_message..last_addon_message. */ const char * const * addon_message_table; /* Non-library errors */ - int first_addon_message; /* code for first string in addon table */ - int last_addon_message; /* code for last string in addon table */ + int first_addon_message; /* code for first string in addon table */ + int last_addon_message; /* code for last string in addon table */ }; @@ -1469,18 +1469,18 @@ struct jpeg_error_mgr { struct jpeg_progress_mgr { JMETHOD(void, progress_monitor, (j_common_ptr cinfo)); - long pass_counter; /* work units completed in this pass */ - long pass_limit; /* total number of work units in this pass */ - int completed_passes; /* passes completed so far */ - int total_passes; /* total number of passes expected */ + long pass_counter; /* work units completed in this pass */ + long pass_limit; /* total number of work units in this pass */ + int completed_passes; /* passes completed so far */ + int total_passes; /* total number of passes expected */ }; /* Data destination object for compression */ struct jpeg_destination_mgr { - JOCTET * next_output_byte; /* => next byte to write in buffer */ - size_t free_in_buffer; /* # of byte spaces remaining in buffer */ + JOCTET * next_output_byte; /* => next byte to write in buffer */ + size_t free_in_buffer; /* # of byte spaces remaining in buffer */ JMETHOD(void, init_destination, (j_compress_ptr cinfo)); JMETHOD(boolean, empty_output_buffer, (j_compress_ptr cinfo)); @@ -1492,7 +1492,7 @@ struct jpeg_destination_mgr { struct jpeg_source_mgr { const JOCTET * next_input_byte; /* => next byte to read from buffer */ - size_t bytes_in_buffer; /* # of bytes remaining in buffer */ + size_t bytes_in_buffer; /* # of bytes remaining in buffer */ JMETHOD(void, init_source, (j_decompress_ptr cinfo)); JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo)); @@ -1513,9 +1513,9 @@ struct jpeg_source_mgr { * successful. */ -#define JPOOL_PERMANENT 0 /* lasts until master record is destroyed */ -#define JPOOL_IMAGE 1 /* lasts until done with image/datastream */ -#define JPOOL_NUMPOOLS 2 +#define JPOOL_PERMANENT 0 /* lasts until master record is destroyed */ +#define JPOOL_IMAGE 1 /* lasts until done with image/datastream */ +#define JPOOL_NUMPOOLS 2 typedef struct jvirt_sarray_control * jvirt_sarray_ptr; typedef struct jvirt_barray_control * jvirt_barray_ptr; @@ -1524,38 +1524,38 @@ typedef struct jvirt_barray_control * jvirt_barray_ptr; struct jpeg_memory_mgr { /* Method pointers */ JMETHOD(void *, alloc_small, (j_common_ptr cinfo, int pool_id, - size_t sizeofobject)); + size_t sizeofobject)); JMETHOD(void FAR *, alloc_large, (j_common_ptr cinfo, int pool_id, - size_t sizeofobject)); + size_t sizeofobject)); JMETHOD(JSAMPARRAY, alloc_sarray, (j_common_ptr cinfo, int pool_id, - JDIMENSION samplesperrow, - JDIMENSION numrows)); + JDIMENSION samplesperrow, + JDIMENSION numrows)); JMETHOD(JBLOCKARRAY, alloc_barray, (j_common_ptr cinfo, int pool_id, - JDIMENSION blocksperrow, - JDIMENSION numrows)); + JDIMENSION blocksperrow, + JDIMENSION numrows)); JMETHOD(jvirt_sarray_ptr, request_virt_sarray, (j_common_ptr cinfo, - int pool_id, - boolean pre_zero, - JDIMENSION samplesperrow, - JDIMENSION numrows, - JDIMENSION maxaccess)); + int pool_id, + boolean pre_zero, + JDIMENSION samplesperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); JMETHOD(jvirt_barray_ptr, request_virt_barray, (j_common_ptr cinfo, - int pool_id, - boolean pre_zero, - JDIMENSION blocksperrow, - JDIMENSION numrows, - JDIMENSION maxaccess)); + int pool_id, + boolean pre_zero, + JDIMENSION blocksperrow, + JDIMENSION numrows, + JDIMENSION maxaccess)); JMETHOD(void, realize_virt_arrays, (j_common_ptr cinfo)); JMETHOD(JSAMPARRAY, access_virt_sarray, (j_common_ptr cinfo, - jvirt_sarray_ptr ptr, - JDIMENSION start_row, - JDIMENSION num_rows, - boolean writable)); + jvirt_sarray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); JMETHOD(JBLOCKARRAY, access_virt_barray, (j_common_ptr cinfo, - jvirt_barray_ptr ptr, - JDIMENSION start_row, - JDIMENSION num_rows, - boolean writable)); + jvirt_barray_ptr ptr, + JDIMENSION start_row, + JDIMENSION num_rows, + boolean writable)); JMETHOD(void, free_pool, (j_common_ptr cinfo, int pool_id)); JMETHOD(void, self_destruct, (j_common_ptr cinfo)); @@ -1583,74 +1583,74 @@ typedef JMETHOD(boolean, jpeg_marker_parser_method, (j_decompress_ptr cinfo)); */ #ifdef HAVE_PROTOTYPES -#define JPP(arglist) arglist +#define JPP(arglist) arglist #else -#define JPP(arglist) () +#define JPP(arglist) () #endif /* Short forms of external names for systems with brain-damaged linkers. * We shorten external names to be unique in the first six letters, which * is good enough for all known systems. - * (If your compiler itself needs names to be unique in less than 15 + * (If your compiler itself needs names to be unique in less than 15 * characters, you are out of luck. Get a better compiler.) */ #ifdef NEED_SHORT_EXTERNAL_NAMES -#define jpeg_std_error jStdError -#define jpeg_CreateCompress jCreaCompress -#define jpeg_CreateDecompress jCreaDecompress -#define jpeg_destroy_compress jDestCompress -#define jpeg_destroy_decompress jDestDecompress -#define jpeg_stdio_dest jStdDest -#define jpeg_stdio_src jStdSrc -#define jpeg_set_defaults jSetDefaults -#define jpeg_set_colorspace jSetColorspace -#define jpeg_default_colorspace jDefColorspace -#define jpeg_set_quality jSetQuality -#define jpeg_set_linear_quality jSetLQuality -#define jpeg_add_quant_table jAddQuantTable -#define jpeg_quality_scaling jQualityScaling -#define jpeg_simple_progression jSimProgress -#define jpeg_suppress_tables jSuppressTables -#define jpeg_alloc_quant_table jAlcQTable -#define jpeg_alloc_huff_table jAlcHTable -#define jpeg_start_compress jStrtCompress -#define jpeg_write_scanlines jWrtScanlines -#define jpeg_finish_compress jFinCompress -#define jpeg_write_raw_data jWrtRawData -#define jpeg_write_marker jWrtMarker -#define jpeg_write_m_header jWrtMHeader -#define jpeg_write_m_byte jWrtMByte -#define jpeg_write_tables jWrtTables -#define jpeg_read_header jReadHeader -#define jpeg_start_decompress jStrtDecompress -#define jpeg_read_scanlines jReadScanlines -#define jpeg_finish_decompress jFinDecompress -#define jpeg_read_raw_data jReadRawData -#define jpeg_has_multiple_scans jHasMultScn -#define jpeg_start_output jStrtOutput -#define jpeg_finish_output jFinOutput -#define jpeg_input_complete jInComplete -#define jpeg_new_colormap jNewCMap -#define jpeg_consume_input jConsumeInput -#define jpeg_calc_output_dimensions jCalcDimensions -#define jpeg_save_markers jSaveMarkers -#define jpeg_set_marker_processor jSetMarker -#define jpeg_read_coefficients jReadCoefs -#define jpeg_write_coefficients jWrtCoefs -#define jpeg_copy_critical_parameters jCopyCrit -#define jpeg_abort_compress jAbrtCompress -#define jpeg_abort_decompress jAbrtDecompress -#define jpeg_abort jAbort -#define jpeg_destroy jDestroy -#define jpeg_resync_to_restart jResyncRestart +#define jpeg_std_error jStdError +#define jpeg_CreateCompress jCreaCompress +#define jpeg_CreateDecompress jCreaDecompress +#define jpeg_destroy_compress jDestCompress +#define jpeg_destroy_decompress jDestDecompress +#define jpeg_stdio_dest jStdDest +#define jpeg_stdio_src jStdSrc +#define jpeg_set_defaults jSetDefaults +#define jpeg_set_colorspace jSetColorspace +#define jpeg_default_colorspace jDefColorspace +#define jpeg_set_quality jSetQuality +#define jpeg_set_linear_quality jSetLQuality +#define jpeg_add_quant_table jAddQuantTable +#define jpeg_quality_scaling jQualityScaling +#define jpeg_simple_progression jSimProgress +#define jpeg_suppress_tables jSuppressTables +#define jpeg_alloc_quant_table jAlcQTable +#define jpeg_alloc_huff_table jAlcHTable +#define jpeg_start_compress jStrtCompress +#define jpeg_write_scanlines jWrtScanlines +#define jpeg_finish_compress jFinCompress +#define jpeg_write_raw_data jWrtRawData +#define jpeg_write_marker jWrtMarker +#define jpeg_write_m_header jWrtMHeader +#define jpeg_write_m_byte jWrtMByte +#define jpeg_write_tables jWrtTables +#define jpeg_read_header jReadHeader +#define jpeg_start_decompress jStrtDecompress +#define jpeg_read_scanlines jReadScanlines +#define jpeg_finish_decompress jFinDecompress +#define jpeg_read_raw_data jReadRawData +#define jpeg_has_multiple_scans jHasMultScn +#define jpeg_start_output jStrtOutput +#define jpeg_finish_output jFinOutput +#define jpeg_input_complete jInComplete +#define jpeg_new_colormap jNewCMap +#define jpeg_consume_input jConsumeInput +#define jpeg_calc_output_dimensions jCalcDimensions +#define jpeg_save_markers jSaveMarkers +#define jpeg_set_marker_processor jSetMarker +#define jpeg_read_coefficients jReadCoefs +#define jpeg_write_coefficients jWrtCoefs +#define jpeg_copy_critical_parameters jCopyCrit +#define jpeg_abort_compress jAbrtCompress +#define jpeg_abort_decompress jAbrtDecompress +#define jpeg_abort jAbort +#define jpeg_destroy jDestroy +#define jpeg_resync_to_restart jResyncRestart #endif /* NEED_SHORT_EXTERNAL_NAMES */ /* Default error-management setup */ EXTERN(struct jpeg_error_mgr *) jpeg_std_error - JPP((struct jpeg_error_mgr * err)); + JPP((struct jpeg_error_mgr * err)); /* Initialization of JPEG compression objects. * jpeg_create_compress() and jpeg_create_decompress() are the exported @@ -1661,78 +1661,79 @@ EXTERN(struct jpeg_error_mgr *) jpeg_std_error */ #define jpeg_create_compress(cinfo) \ jpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, \ - (size_t) sizeof(struct jpeg_compress_struct)) + (size_t) sizeof(struct jpeg_compress_struct)) #define jpeg_create_decompress(cinfo) \ jpeg_CreateDecompress((cinfo), JPEG_LIB_VERSION, \ - (size_t) sizeof(struct jpeg_decompress_struct)) + (size_t) sizeof(struct jpeg_decompress_struct)) EXTERN(void) jpeg_CreateCompress JPP((j_compress_ptr cinfo, - int version, size_t structsize)); + int version, size_t structsize)); EXTERN(void) jpeg_CreateDecompress JPP((j_decompress_ptr cinfo, - int version, size_t structsize)); + int version, size_t structsize)); /* Destruction of JPEG compression objects */ EXTERN(void) jpeg_destroy_compress JPP((j_compress_ptr cinfo)); EXTERN(void) jpeg_destroy_decompress JPP((j_decompress_ptr cinfo)); /* Standard data source and destination managers: stdio streams. */ /* Caller is responsible for opening the file before and closing after. */ -EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile)); -EXTERN(void) jpeg_stdio_src JPP((j_decompress_ptr cinfo, FILE * infile)); +// !!! No in Ren-C release builds +//EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile)); +//EXTERN(void) jpeg_stdio_src JPP((j_decompress_ptr cinfo, FILE * infile)); /* Default parameter setup for compression */ EXTERN(void) jpeg_set_defaults JPP((j_compress_ptr cinfo)); /* Compression parameter setup aids */ EXTERN(void) jpeg_set_colorspace JPP((j_compress_ptr cinfo, - J_COLOR_SPACE colorspace)); + J_COLOR_SPACE colorspace)); EXTERN(void) jpeg_default_colorspace JPP((j_compress_ptr cinfo)); EXTERN(void) jpeg_set_quality JPP((j_compress_ptr cinfo, int quality, - boolean force_baseline)); + boolean force_baseline)); EXTERN(void) jpeg_set_linear_quality JPP((j_compress_ptr cinfo, - int scale_factor, - boolean force_baseline)); + int scale_factor, + boolean force_baseline)); EXTERN(void) jpeg_add_quant_table JPP((j_compress_ptr cinfo, int which_tbl, - const unsigned int *basic_table, - int scale_factor, - boolean force_baseline)); + const unsigned int *basic_table, + int scale_factor, + boolean force_baseline)); EXTERN(int) jpeg_quality_scaling JPP((int quality)); EXTERN(void) jpeg_simple_progression JPP((j_compress_ptr cinfo)); EXTERN(void) jpeg_suppress_tables JPP((j_compress_ptr cinfo, - boolean suppress)); + boolean suppress)); EXTERN(JQUANT_TBL *) jpeg_alloc_quant_table JPP((j_common_ptr cinfo)); EXTERN(JHUFF_TBL *) jpeg_alloc_huff_table JPP((j_common_ptr cinfo)); /* Main entry points for compression */ EXTERN(void) jpeg_start_compress JPP((j_compress_ptr cinfo, - boolean write_all_tables)); + boolean write_all_tables)); EXTERN(JDIMENSION) jpeg_write_scanlines JPP((j_compress_ptr cinfo, - JSAMPARRAY scanlines, - JDIMENSION num_lines)); + JSAMPARRAY scanlines, + JDIMENSION num_lines)); EXTERN(void) jpeg_finish_compress JPP((j_compress_ptr cinfo)); /* Replaces jpeg_write_scanlines when writing raw downsampled data. */ EXTERN(JDIMENSION) jpeg_write_raw_data JPP((j_compress_ptr cinfo, - JSAMPIMAGE data, - JDIMENSION num_lines)); + JSAMPIMAGE data, + JDIMENSION num_lines)); /* Write a special marker. See libjpeg.doc concerning safe usage. */ EXTERN(void) jpeg_write_marker - JPP((j_compress_ptr cinfo, int marker, - const JOCTET * dataptr, unsigned int datalen)); + JPP((j_compress_ptr cinfo, int marker, + const JOCTET * dataptr, unsigned int datalen)); /* Same, but piecemeal. */ EXTERN(void) jpeg_write_m_header - JPP((j_compress_ptr cinfo, int marker, unsigned int datalen)); + JPP((j_compress_ptr cinfo, int marker, unsigned int datalen)); EXTERN(void) jpeg_write_m_byte - JPP((j_compress_ptr cinfo, int val)); + JPP((j_compress_ptr cinfo, int val)); /* Alternate compression function: just write an abbreviated table file */ EXTERN(void) jpeg_write_tables JPP((j_compress_ptr cinfo)); /* Decompression startup: read start of JPEG datastream to see what's there */ EXTERN(int) jpeg_read_header JPP((j_decompress_ptr cinfo, - boolean require_image)); + boolean require_image)); /* Return value is one of: */ -#define JPEG_SUSPENDED 0 /* Suspended due to lack of input data */ -#define JPEG_HEADER_OK 1 /* Found valid image datastream */ -#define JPEG_HEADER_TABLES_ONLY 2 /* Found valid table-specs-only datastream */ +#define JPEG_SUSPENDED 0 /* Suspended due to lack of input data */ +#define JPEG_HEADER_OK 1 /* Found valid image datastream */ +#define JPEG_HEADER_TABLES_ONLY 2 /* Found valid table-specs-only datastream */ /* If you pass require_image = TRUE (normal case), you need not check for * a TABLES_ONLY return code; an abbreviated file will cause an error exit. * JPEG_SUSPENDED is only possible if you use a data source module that can @@ -1742,49 +1743,49 @@ EXTERN(int) jpeg_read_header JPP((j_decompress_ptr cinfo, /* Main entry points for decompression */ EXTERN(boolean) jpeg_start_decompress JPP((j_decompress_ptr cinfo)); EXTERN(JDIMENSION) jpeg_read_scanlines JPP((j_decompress_ptr cinfo, - JSAMPARRAY scanlines, - JDIMENSION max_lines)); + JSAMPARRAY scanlines, + JDIMENSION max_lines)); EXTERN(boolean) jpeg_finish_decompress JPP((j_decompress_ptr cinfo)); /* Replaces jpeg_read_scanlines when reading raw downsampled data. */ EXTERN(JDIMENSION) jpeg_read_raw_data JPP((j_decompress_ptr cinfo, - JSAMPIMAGE data, - JDIMENSION max_lines)); + JSAMPIMAGE data, + JDIMENSION max_lines)); /* Additional entry points for buffered-image mode. */ EXTERN(boolean) jpeg_has_multiple_scans JPP((j_decompress_ptr cinfo)); EXTERN(boolean) jpeg_start_output JPP((j_decompress_ptr cinfo, - int scan_number)); + int scan_number)); EXTERN(boolean) jpeg_finish_output JPP((j_decompress_ptr cinfo)); EXTERN(boolean) jpeg_input_complete JPP((j_decompress_ptr cinfo)); EXTERN(void) jpeg_new_colormap JPP((j_decompress_ptr cinfo)); EXTERN(int) jpeg_consume_input JPP((j_decompress_ptr cinfo)); /* Return value is one of: */ -/* #define JPEG_SUSPENDED 0 Suspended due to lack of input data */ -#define JPEG_REACHED_SOS 1 /* Reached start of new scan */ -#define JPEG_REACHED_EOI 2 /* Reached end of image */ -#define JPEG_ROW_COMPLETED 3 /* Completed one iMCU row */ -#define JPEG_SCAN_COMPLETED 4 /* Completed last iMCU row of a scan */ +/* #define JPEG_SUSPENDED 0 Suspended due to lack of input data */ +#define JPEG_REACHED_SOS 1 /* Reached start of new scan */ +#define JPEG_REACHED_EOI 2 /* Reached end of image */ +#define JPEG_ROW_COMPLETED 3 /* Completed one iMCU row */ +#define JPEG_SCAN_COMPLETED 4 /* Completed last iMCU row of a scan */ /* Precalculate output dimensions for current decompression parameters. */ EXTERN(void) jpeg_calc_output_dimensions JPP((j_decompress_ptr cinfo)); /* Control saving of COM and APPn markers into marker_list. */ EXTERN(void) jpeg_save_markers - JPP((j_decompress_ptr cinfo, int marker_code, - unsigned int length_limit)); + JPP((j_decompress_ptr cinfo, int marker_code, + unsigned int length_limit)); /* Install a special processing method for COM or APPn markers. */ EXTERN(void) jpeg_set_marker_processor - JPP((j_decompress_ptr cinfo, int marker_code, - jpeg_marker_parser_method routine)); + JPP((j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine)); /* Read or write raw DCT coefficients --- useful for lossless transcoding. */ EXTERN(jvirt_barray_ptr *) jpeg_read_coefficients JPP((j_decompress_ptr cinfo)); EXTERN(void) jpeg_write_coefficients JPP((j_compress_ptr cinfo, - jvirt_barray_ptr * coef_arrays)); + jvirt_barray_ptr * coef_arrays)); EXTERN(void) jpeg_copy_critical_parameters JPP((j_decompress_ptr srcinfo, - j_compress_ptr dstinfo)); + j_compress_ptr dstinfo)); /* If you choose to abort compression or decompression before completing * jpeg_finish_(de)compress, then you need to clean up to release memory, @@ -1803,17 +1804,17 @@ EXTERN(void) jpeg_destroy JPP((j_common_ptr cinfo)); /* Default restart-marker-resync procedure for use by data source modules */ EXTERN(boolean) jpeg_resync_to_restart JPP((j_decompress_ptr cinfo, - int desired)); + int desired)); /* These marker codes are exported since applications and data source modules * are likely to want to use them. */ -#define JPEG_RST0 0xD0 /* RST0 marker code */ -#define JPEG_EOI 0xD9 /* EOI marker code */ -#define JPEG_APP0 0xE0 /* APP0 marker code */ -#define JPEG_COM 0xFE /* COM marker code */ +#define JPEG_RST0 0xD0 /* RST0 marker code */ +#define JPEG_EOI 0xD9 /* EOI marker code */ +#define JPEG_APP0 0xE0 /* APP0 marker code */ +#define JPEG_COM 0xFE /* COM marker code */ /* If we have a brain-damaged compiler that emits warnings (or worse, errors) @@ -1822,7 +1823,7 @@ EXTERN(boolean) jpeg_resync_to_restart JPP((j_decompress_ptr cinfo, */ #ifdef INCOMPLETE_TYPES_BROKEN -#ifndef JPEG_INTERNALS /* will be defined in jpegint.h */ +#ifndef JPEG_INTERNALS /* will be defined in jpegint.h */ struct jvirt_sarray_control { long dummy; }; struct jvirt_barray_control { long dummy; }; struct jpeg_comp_master { long dummy; }; @@ -1873,30 +1874,30 @@ struct jpeg_color_quantizer { long dummy; }; /* Declarations for both compression & decompression */ -typedef enum { /* Operating modes for buffer controllers */ - JBUF_PASS_THRU, /* Plain stripwise operation */ - /* Remaining modes require a full-image buffer to have been created */ - JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ - JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ - JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ +typedef enum { /* Operating modes for buffer controllers */ + JBUF_PASS_THRU, /* Plain stripwise operation */ + /* Remaining modes require a full-image buffer to have been created */ + JBUF_SAVE_SOURCE, /* Run source subobject only, save output */ + JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */ + JBUF_SAVE_AND_PASS /* Run both subobjects, save output */ } J_BUF_MODE; /* Values of global_state field (jdapi.c has some dependencies on ordering!) */ -#define CSTATE_START 100 /* after create_compress */ -#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ -#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ -#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ -#define DSTATE_START 200 /* after create_decompress */ -#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ -#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ -#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ -#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ -#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ -#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ -#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ -#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ -#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ -#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ +#define CSTATE_START 100 /* after create_compress */ +#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */ +#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */ +#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */ +#define DSTATE_START 200 /* after create_decompress */ +#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */ +#define DSTATE_READY 202 /* found SOS, ready for start_decompress */ +#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/ +#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */ +#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */ +#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */ +#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */ +#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */ +#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */ +#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */ /* Declarations for compression modules */ @@ -1908,54 +1909,54 @@ struct jpeg_comp_master { JMETHOD(void, finish_pass, (j_compress_ptr cinfo)); /* State variables made visible to other modules */ - boolean call_pass_startup; /* True if pass_startup must be called */ - boolean is_last_pass; /* True during last pass */ + boolean call_pass_startup; /* True if pass_startup must be called */ + boolean is_last_pass; /* True during last pass */ }; /* Main buffer control (downsampled-data buffer) */ struct jpeg_c_main_controller { JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); JMETHOD(void, process_data, (j_compress_ptr cinfo, - JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, - JDIMENSION in_rows_avail)); + JSAMPARRAY input_buf, JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail)); }; /* Compression preprocessing (downsampling input buffer control) */ struct jpeg_c_prep_controller { JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); JMETHOD(void, pre_process_data, (j_compress_ptr cinfo, - JSAMPARRAY input_buf, - JDIMENSION *in_row_ctr, - JDIMENSION in_rows_avail, - JSAMPIMAGE output_buf, - JDIMENSION *out_row_group_ctr, - JDIMENSION out_row_groups_avail)); + JSAMPARRAY input_buf, + JDIMENSION *in_row_ctr, + JDIMENSION in_rows_avail, + JSAMPIMAGE output_buf, + JDIMENSION *out_row_group_ctr, + JDIMENSION out_row_groups_avail)); }; /* Coefficient buffer control */ struct jpeg_c_coef_controller { JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode)); JMETHOD(boolean, compress_data, (j_compress_ptr cinfo, - JSAMPIMAGE input_buf)); + JSAMPIMAGE input_buf)); }; /* Colorspace conversion */ struct jpeg_color_converter { JMETHOD(void, start_pass, (j_compress_ptr cinfo)); JMETHOD(void, color_convert, (j_compress_ptr cinfo, - JSAMPARRAY input_buf, JSAMPIMAGE output_buf, - JDIMENSION output_row, int num_rows)); + JSAMPARRAY input_buf, JSAMPIMAGE output_buf, + JDIMENSION output_row, int num_rows)); }; /* Downsampling */ struct jpeg_downsampler { JMETHOD(void, start_pass, (j_compress_ptr cinfo)); JMETHOD(void, downsample, (j_compress_ptr cinfo, - JSAMPIMAGE input_buf, JDIMENSION in_row_index, - JSAMPIMAGE output_buf, - JDIMENSION out_row_group_index)); + JSAMPIMAGE input_buf, JDIMENSION in_row_index, + JSAMPIMAGE output_buf, + JDIMENSION out_row_group_index)); - boolean need_context_rows; /* TRUE if need rows above & below */ + boolean need_context_rows; /* TRUE if need rows above & below */ }; /* Forward DCT (also controls coefficient quantization) */ @@ -1963,10 +1964,10 @@ struct jpeg_forward_dct { JMETHOD(void, start_pass, (j_compress_ptr cinfo)); /* perhaps this should be an array??? */ JMETHOD(void, forward_DCT, (j_compress_ptr cinfo, - jpeg_component_info * compptr, - JSAMPARRAY sample_data, JBLOCKROW coef_blocks, - JDIMENSION start_row, JDIMENSION start_col, - JDIMENSION num_blocks)); + jpeg_component_info * compptr, + JSAMPARRAY sample_data, JBLOCKROW coef_blocks, + JDIMENSION start_row, JDIMENSION start_col, + JDIMENSION num_blocks)); }; /* Entropy encoding */ @@ -1986,7 +1987,7 @@ struct jpeg_marker_writer { /* These routines are exported to allow insertion of extra markers */ /* Probably only COM and APPn markers should be written this way */ JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker, - unsigned int datalen)); + unsigned int datalen)); JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val)); }; @@ -1999,7 +2000,7 @@ struct jpeg_decomp_master { JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo)); /* State variables made visible to other modules */ - boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ + boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */ }; /* Input control module */ @@ -2010,16 +2011,16 @@ struct jpeg_input_controller { JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo)); /* State variables made visible to other modules */ - boolean has_multiple_scans; /* True if file has multiple scans */ - boolean eoi_reached; /* True when EOI has been consumed */ + boolean has_multiple_scans; /* True if file has multiple scans */ + boolean eoi_reached; /* True when EOI has been consumed */ }; /* Main buffer control (downsampled-data buffer) */ struct jpeg_d_main_controller { JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); JMETHOD(void, process_data, (j_decompress_ptr cinfo, - JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, - JDIMENSION out_rows_avail)); + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); }; /* Coefficient buffer control */ @@ -2028,7 +2029,7 @@ struct jpeg_d_coef_controller { JMETHOD(int, consume_data, (j_decompress_ptr cinfo)); JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo)); JMETHOD(int, decompress_data, (j_decompress_ptr cinfo, - JSAMPIMAGE output_buf)); + JSAMPIMAGE output_buf)); /* Pointer to array of coefficient virtual arrays, or NULL if none */ jvirt_barray_ptr *coef_arrays; }; @@ -2037,12 +2038,12 @@ struct jpeg_d_coef_controller { struct jpeg_d_post_controller { JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode)); JMETHOD(void, post_process_data, (j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, - JDIMENSION *in_row_group_ctr, - JDIMENSION in_row_groups_avail, - JSAMPARRAY output_buf, - JDIMENSION *out_row_ctr, - JDIMENSION out_rows_avail)); + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); }; /* Marker reading & parsing */ @@ -2059,28 +2060,28 @@ struct jpeg_marker_reader { /* State of marker reader --- nominally internal, but applications * supplying COM or APPn handlers might like to know the state. */ - boolean saw_SOI; /* found SOI? */ - boolean saw_SOF; /* found SOF? */ - int next_restart_num; /* next restart number expected (0-7) */ - unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ + boolean saw_SOI; /* found SOI? */ + boolean saw_SOF; /* found SOF? */ + int next_restart_num; /* next restart number expected (0-7) */ + unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */ }; /* Entropy decoding */ struct jpeg_entropy_decoder { JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo, - JBLOCKROW *MCU_data)); + JBLOCKROW *MCU_data)); /* This is here to share code between baseline and progressive decoders; */ /* other modules probably should not use it */ - boolean insufficient_data; /* set TRUE after emitting warning */ + boolean insufficient_data; /* set TRUE after emitting warning */ }; /* Inverse DCT (also performs dequantization) */ typedef JMETHOD(void, inverse_DCT_method_ptr, - (j_decompress_ptr cinfo, jpeg_component_info * compptr, - JCOEFPTR coef_block, - JSAMPARRAY output_buf, JDIMENSION output_col)); + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col)); struct jpeg_inverse_dct { JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); @@ -2092,30 +2093,30 @@ struct jpeg_inverse_dct { struct jpeg_upsampler { JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); JMETHOD(void, upsample, (j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, - JDIMENSION *in_row_group_ctr, - JDIMENSION in_row_groups_avail, - JSAMPARRAY output_buf, - JDIMENSION *out_row_ctr, - JDIMENSION out_rows_avail)); - - boolean need_context_rows; /* TRUE if need rows above & below */ + JSAMPIMAGE input_buf, + JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); + + boolean need_context_rows; /* TRUE if need rows above & below */ }; /* Colorspace conversion */ struct jpeg_color_deconverter { JMETHOD(void, start_pass, (j_decompress_ptr cinfo)); JMETHOD(void, color_convert, (j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, JDIMENSION input_row, - JSAMPARRAY output_buf, int num_rows)); + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows)); }; /* Color quantization or color precision reduction */ struct jpeg_color_quantizer { JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan)); JMETHOD(void, color_quantize, (j_decompress_ptr cinfo, - JSAMPARRAY input_buf, JSAMPARRAY output_buf, - int num_rows)); + JSAMPARRAY input_buf, JSAMPARRAY output_buf, + int num_rows)); JMETHOD(void, finish_pass, (j_decompress_ptr cinfo)); JMETHOD(void, new_color_map, (j_decompress_ptr cinfo)); }; @@ -2124,9 +2125,9 @@ struct jpeg_color_quantizer { /* Miscellaneous useful macros */ #undef MAX -#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#define MAX(a,b) ((a) > (b) ? (a) : (b)) #undef MIN -#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#define MIN(a,b) ((a) < (b) ? (a) : (b)) /* We assume that right shift corresponds to signed division by 2 with @@ -2140,66 +2141,66 @@ struct jpeg_color_quantizer { */ #ifdef RIGHT_SHIFT_IS_UNSIGNED -#define SHIFT_TEMPS INT32 shift_temp; +#define SHIFT_TEMPS INT32 shift_temp; #define RIGHT_SHIFT(x,shft) \ - ((shift_temp = (x)) < 0 ? \ - (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ - (shift_temp >> (shft))) + ((shift_temp = (x)) < 0 ? \ + (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \ + (shift_temp >> (shft))) #else #define SHIFT_TEMPS -#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) +#define RIGHT_SHIFT(x,shft) ((x) >> (shft)) #endif /* Short forms of external names for systems with brain-damaged linkers. */ #ifdef NEED_SHORT_EXTERNAL_NAMES -#define jinit_compress_master jICompress -#define jinit_c_master_control jICMaster -#define jinit_c_main_controller jICMainC -#define jinit_c_prep_controller jICPrepC -#define jinit_c_coef_controller jICCoefC -#define jinit_color_converter jICColor -#define jinit_downsampler jIDownsampler -#define jinit_forward_dct jIFDCT -#define jinit_huff_encoder jIHEncoder -#define jinit_phuff_encoder jIPHEncoder -#define jinit_marker_writer jIMWriter -#define jinit_master_decompress jIDMaster -#define jinit_d_main_controller jIDMainC -#define jinit_d_coef_controller jIDCoefC -#define jinit_d_post_controller jIDPostC -#define jinit_input_controller jIInCtlr -#define jinit_marker_reader jIMReader -#define jinit_huff_decoder jIHDecoder -#define jinit_phuff_decoder jIPHDecoder -#define jinit_inverse_dct jIIDCT -#define jinit_upsampler jIUpsampler -#define jinit_color_deconverter jIDColor -#define jinit_1pass_quantizer jI1Quant -#define jinit_2pass_quantizer jI2Quant -#define jinit_merged_upsampler jIMUpsampler -#define jinit_memory_mgr jIMemMgr -#define jdiv_round_up jDivRound -#define jround_up jRound -#define jcopy_sample_rows jCopySamples -#define jcopy_block_row jCopyBlocks -#define jzero_far jZeroFar -#define jpeg_zigzag_order jZIGTable -#define jpeg_natural_order jZAGTable +#define jinit_compress_master jICompress +#define jinit_c_master_control jICMaster +#define jinit_c_main_controller jICMainC +#define jinit_c_prep_controller jICPrepC +#define jinit_c_coef_controller jICCoefC +#define jinit_color_converter jICColor +#define jinit_downsampler jIDownsampler +#define jinit_forward_dct jIFDCT +#define jinit_huff_encoder jIHEncoder +#define jinit_phuff_encoder jIPHEncoder +#define jinit_marker_writer jIMWriter +#define jinit_master_decompress jIDMaster +#define jinit_d_main_controller jIDMainC +#define jinit_d_coef_controller jIDCoefC +#define jinit_d_post_controller jIDPostC +#define jinit_input_controller jIInCtlr +#define jinit_marker_reader jIMReader +#define jinit_huff_decoder jIHDecoder +#define jinit_phuff_decoder jIPHDecoder +#define jinit_inverse_dct jIIDCT +#define jinit_upsampler jIUpsampler +#define jinit_color_deconverter jIDColor +#define jinit_1pass_quantizer jI1Quant +#define jinit_2pass_quantizer jI2Quant +#define jinit_merged_upsampler jIMUpsampler +#define jinit_memory_mgr jIMemMgr +#define jdiv_round_up jDivRound +#define jround_up jRound +#define jcopy_sample_rows jCopySamples +#define jcopy_block_row jCopyBlocks +#define jzero_far jZeroFar +#define jpeg_zigzag_order jZIGTable +#define jpeg_natural_order jZAGTable #endif /* NEED_SHORT_EXTERNAL_NAMES */ /* Compression module initialization routines */ EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo)); EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo, - boolean transcode_only)); + boolean transcode_only)); EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo, - boolean need_full_buffer)); + boolean need_full_buffer)); EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo, - boolean need_full_buffer)); + boolean need_full_buffer)); EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo, - boolean need_full_buffer)); + boolean need_full_buffer)); EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo)); EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo)); EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo)); @@ -2209,11 +2210,11 @@ EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo)); /* Decompression module initialization routines */ EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo)); EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo, - boolean need_full_buffer)); + boolean need_full_buffer)); EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo, - boolean need_full_buffer)); + boolean need_full_buffer)); EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo, - boolean need_full_buffer)); + boolean need_full_buffer)); EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo)); EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo)); EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo)); @@ -2231,13 +2232,13 @@ EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo)); EXTERN(long) jdiv_round_up JPP((long a, long b)); EXTERN(long) jround_up JPP((long a, long b)); EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row, - JSAMPARRAY output_array, int dest_row, - int num_rows, JDIMENSION num_cols)); + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols)); EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row, - JDIMENSION num_blocks)); + JDIMENSION num_blocks)); EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero)); /* Constant tables in jutils.c */ -#if 0 /* This table is not actually needed in v6a */ +#if 0 /* This table is not actually needed in v6a */ extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */ #endif extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ @@ -2245,14 +2246,14 @@ extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */ /* Suppress undefined-structure complaints if necessary. */ #ifdef INCOMPLETE_TYPES_BROKEN -#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ +#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */ struct jvirt_sarray_control { long dummy; }; struct jvirt_barray_control { long dummy; }; #endif #endif /* INCOMPLETE_TYPES_BROKEN */ -//#include "jpegint.h" /* fetch private declarations */ -//#include "jerror.h" /* fetch error codes too */ +//#include "jpegint.h" /* fetch private declarations */ +//#include "jerror.h" /* fetch error codes too */ #endif #endif /* JPEGLIB_H */ @@ -2272,21 +2273,21 @@ struct jvirt_barray_control { long dummy; }; /* Short forms of external names for systems with brain-damaged linkers. */ #ifdef NEED_SHORT_EXTERNAL_NAMES -#define jpeg_make_d_derived_tbl jMkDDerived -#define jpeg_fill_bit_buffer jFilBitBuf -#define jpeg_huff_decode jHufDecode +#define jpeg_make_d_derived_tbl jMkDDerived +#define jpeg_fill_bit_buffer jFilBitBuf +#define jpeg_huff_decode jHufDecode #endif /* NEED_SHORT_EXTERNAL_NAMES */ /* Derived data constructed for each Huffman table */ -#define HUFF_LOOKAHEAD 8 /* # of bits of lookahead */ +#define HUFF_LOOKAHEAD 8 /* # of bits of lookahead */ typedef struct { /* Basic tables: (element [0] of each array is unused) */ - INT32 maxcode[18]; /* largest code of length k (-1 if none) */ + INT32 maxcode[18]; /* largest code of length k (-1 if none) */ /* (maxcode[17] is a sentinel to ensure jpeg_huff_decode terminates) */ - INT32 valoffset[17]; /* huffval[] offset for codes of length k */ + INT32 valoffset[17]; /* huffval[] offset for codes of length k */ /* valoffset[k] = huffval[] index of 1st symbol of code length k, less * the smallest code of length k; so given a code of length k, the * corresponding symbol is huffval[code + valoffset[k]] @@ -2306,8 +2307,8 @@ typedef struct { /* Expand a Huffman table definition into the derived format */ EXTERN(void) jpeg_make_d_derived_tbl - JPP((j_decompress_ptr cinfo, boolean isDC, int tblno, - d_derived_tbl ** pdtbl)); + JPP((j_decompress_ptr cinfo, boolean isDC, int tblno, + d_derived_tbl ** pdtbl)); /* @@ -2328,8 +2329,8 @@ EXTERN(void) jpeg_make_d_derived_tbl * necessary. */ -typedef INT32 bit_buf_type; /* type of bit-extraction buffer */ -#define BIT_BUF_SIZE 32 /* size of buffer in bits */ +typedef INT32 bit_buf_type; /* type of bit-extraction buffer */ +#define BIT_BUF_SIZE 32 /* size of buffer in bits */ /* If long is > 32 bits on your machine, and shifting/masking longs is * reasonably fast, making bit_buf_type be long and setting BIT_BUF_SIZE @@ -2338,43 +2339,43 @@ typedef INT32 bit_buf_type; /* type of bit-extraction buffer */ * because not all machines measure sizeof in 8-bit bytes. */ -typedef struct { /* Bitreading state saved across MCUs */ - bit_buf_type get_buffer; /* current bit-extraction buffer */ - int bits_left; /* # of unused bits in it */ +typedef struct { /* Bitreading state saved across MCUs */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ } bitread_perm_state; -typedef struct { /* Bitreading working state within an MCU */ +typedef struct { /* Bitreading working state within an MCU */ /* Current data source location */ /* We need a copy, rather than munging the original, in case of suspension */ const JOCTET * next_input_byte; /* => next byte to read from source */ - size_t bytes_in_buffer; /* # of bytes remaining in source buffer */ - /* Bit input buffer --- note these values are kept in register variables, + size_t bytes_in_buffer; /* # of bytes remaining in source buffer */ + /* Bit input buffer --- note these values are kept in variables, * not in this struct, inside the inner loops. */ - bit_buf_type get_buffer; /* current bit-extraction buffer */ - int bits_left; /* # of unused bits in it */ + bit_buf_type get_buffer; /* current bit-extraction buffer */ + int bits_left; /* # of unused bits in it */ /* Pointer needed by jpeg_fill_bit_buffer. */ - j_decompress_ptr cinfo; /* back link to decompress master record */ + j_decompress_ptr cinfo; /* back link to decompress master record */ } bitread_working_state; /* Macros to declare and load/save bitread local variables. */ #define BITREAD_STATE_VARS \ - register bit_buf_type get_buffer; \ - register int bits_left; \ - bitread_working_state br_state + bit_buf_type get_buffer; \ + int bits_left; \ + bitread_working_state br_state #define BITREAD_LOAD_STATE(cinfop,permstate) \ - br_state.cinfo = cinfop; \ - br_state.next_input_byte = cinfop->src->next_input_byte; \ - br_state.bytes_in_buffer = cinfop->src->bytes_in_buffer; \ - get_buffer = permstate.get_buffer; \ - bits_left = permstate.bits_left; + br_state.cinfo = cinfop; \ + br_state.next_input_byte = cinfop->src->next_input_byte; \ + br_state.bytes_in_buffer = cinfop->src->bytes_in_buffer; \ + get_buffer = permstate.get_buffer; \ + bits_left = permstate.bits_left; #define BITREAD_SAVE_STATE(cinfop,permstate) \ - cinfop->src->next_input_byte = br_state.next_input_byte; \ - cinfop->src->bytes_in_buffer = br_state.bytes_in_buffer; \ - permstate.get_buffer = get_buffer; \ - permstate.bits_left = bits_left + cinfop->src->next_input_byte = br_state.next_input_byte; \ + cinfop->src->bytes_in_buffer = br_state.bytes_in_buffer; \ + permstate.get_buffer = get_buffer; \ + permstate.bits_left = bits_left /* * These macros provide the in-line portion of bit fetching. @@ -2382,37 +2383,37 @@ typedef struct { /* Bitreading working state within an MCU */ * before using GET_BITS, PEEK_BITS, or DROP_BITS. * The variables get_buffer and bits_left are assumed to be locals, * but the state struct might not be (jpeg_huff_decode needs this). - * CHECK_BIT_BUFFER(state,n,action); - * Ensure there are N bits in get_buffer; if suspend, take action. + * CHECK_BIT_BUFFER(state,n,action); + * Ensure there are N bits in get_buffer; if suspend, take action. * val = GET_BITS(n); - * Fetch next N bits. + * Fetch next N bits. * val = PEEK_BITS(n); - * Fetch next N bits without removing them from the buffer. - * DROP_BITS(n); - * Discard next N bits. + * Fetch next N bits without removing them from the buffer. + * DROP_BITS(n); + * Discard next N bits. * The value N should be a simple variable, not an expression, because it * is evaluated multiple times. */ #define CHECK_BIT_BUFFER(state,nbits,action) \ - { if (bits_left < (nbits)) { \ - if (! jpeg_fill_bit_buffer(&(state),get_buffer,bits_left,nbits)) \ - { action; } \ - get_buffer = (state).get_buffer; bits_left = (state).bits_left; } } + { if (bits_left < (nbits)) { \ + if (! jpeg_fill_bit_buffer(&(state),get_buffer,bits_left,nbits)) \ + { action; } \ + get_buffer = (state).get_buffer; bits_left = (state).bits_left; } } #define GET_BITS(nbits) \ - (((int) (get_buffer >> (bits_left -= (nbits)))) & ((1<<(nbits))-1)) + (((int) (get_buffer >> (bits_left -= (nbits)))) & ((1<<(nbits))-1)) #define PEEK_BITS(nbits) \ - (((int) (get_buffer >> (bits_left - (nbits)))) & ((1<<(nbits))-1)) + (((int) (get_buffer >> (bits_left - (nbits)))) & ((1<<(nbits))-1)) #define DROP_BITS(nbits) \ - (bits_left -= (nbits)) + (bits_left -= (nbits)) /* Load up the bit buffer to a depth of at least nbits */ EXTERN(boolean) jpeg_fill_bit_buffer - JPP((bitread_working_state * state, register bit_buf_type get_buffer, - register int bits_left, int nbits)); + JPP((bitread_working_state * state, bit_buf_type get_buffer, + int bits_left, int nbits)); /* @@ -2433,7 +2434,7 @@ EXTERN(boolean) jpeg_fill_bit_buffer */ #define HUFF_DECODE(result,state,htbl,failaction,slowlabel) \ -{ register int nb, look; \ +{ int nb, look; \ if (bits_left < HUFF_LOOKAHEAD) { \ if (! jpeg_fill_bit_buffer(&state,get_buffer,bits_left, 0)) {failaction;} \ get_buffer = state.get_buffer; bits_left = state.bits_left; \ @@ -2449,15 +2450,15 @@ EXTERN(boolean) jpeg_fill_bit_buffer nb = HUFF_LOOKAHEAD+1; \ slowlabel: \ if ((result=jpeg_huff_decode(&state,get_buffer,bits_left,htbl,nb)) < 0) \ - { failaction; } \ + { failaction; } \ get_buffer = state.get_buffer; bits_left = state.bits_left; \ } \ } /* Out-of-line case for Huffman code fetching */ EXTERN(int) jpeg_huff_decode - JPP((bitread_working_state * state, register bit_buf_type get_buffer, - register int bits_left, d_derived_tbl * htbl, int min_bits)); + JPP((bitread_working_state * state, bit_buf_type get_buffer, + int bits_left, d_derived_tbl * htbl, int min_bits)); /* * jdct.h @@ -2469,7 +2470,7 @@ EXTERN(int) jpeg_huff_decode * This include file contains common declarations for the forward and * inverse DCT modules. These declarations are private to the DCT managers * (jcdctmgr.c, jddctmgr.c) and the individual DCT algorithms. - * The individual DCT algorithms are kept in separate files to ease + * The individual DCT algorithms are kept in separate files to ease * machine-dependent tuning (e.g., assembly coding). */ @@ -2488,9 +2489,9 @@ EXTERN(int) jpeg_huff_decode */ #if BITS_IN_JSAMPLE == 8 -typedef int DCTELEM; /* 16 or 32 bits is fine */ +typedef int DCTELEM; /* 16 or 32 bits is fine */ #else -typedef INT32 DCTELEM; /* must have 32 bits */ +typedef INT32 DCTELEM; /* must have 32 bits */ #endif typedef JMETHOD(void, forward_DCT_method_ptr, (DCTELEM * data)); @@ -2517,10 +2518,10 @@ typedef JMETHOD(void, float_DCT_method_ptr, (FAST_FLOAT * data)); typedef MULTIPLIER ISLOW_MULT_TYPE; /* short or int, whichever is faster */ #if BITS_IN_JSAMPLE == 8 typedef MULTIPLIER IFAST_MULT_TYPE; /* 16 bits is OK, use short if faster */ -#define IFAST_SCALE_BITS 2 /* fractional bits in scale factors */ +#define IFAST_SCALE_BITS 2 /* fractional bits in scale factors */ #else -typedef INT32 IFAST_MULT_TYPE; /* need 32 bits for scaled quantizers */ -#define IFAST_SCALE_BITS 13 /* fractional bits in scale factors */ +typedef INT32 IFAST_MULT_TYPE; /* need 32 bits for scaled quantizers */ +#define IFAST_SCALE_BITS 13 /* fractional bits in scale factors */ #endif typedef FAST_FLOAT FLOAT_MULT_TYPE; /* preferred floating type */ @@ -2542,15 +2543,15 @@ typedef FAST_FLOAT FLOAT_MULT_TYPE; /* preferred floating type */ /* Short forms of external names for systems with brain-damaged linkers. */ #ifdef NEED_SHORT_EXTERNAL_NAMES -#define jpeg_fdct_islow jFDislow -#define jpeg_fdct_ifast jFDifast -#define jpeg_fdct_float jFDfloat -#define jpeg_idct_islow jRDislow -#define jpeg_idct_ifast jRDifast -#define jpeg_idct_float jRDfloat -#define jpeg_idct_4x4 jRD4x4 -#define jpeg_idct_2x2 jRD2x2 -#define jpeg_idct_1x1 jRD1x1 +#define jpeg_fdct_islow jFDislow +#define jpeg_fdct_ifast jFDifast +#define jpeg_fdct_float jFDfloat +#define jpeg_idct_islow jRDislow +#define jpeg_idct_ifast jRDifast +#define jpeg_idct_float jRDfloat +#define jpeg_idct_4x4 jRD4x4 +#define jpeg_idct_2x2 jRD2x2 +#define jpeg_idct_1x1 jRD1x1 #endif /* NEED_SHORT_EXTERNAL_NAMES */ /* Extern declarations for the forward and inverse DCT routines. */ @@ -2561,22 +2562,22 @@ EXTERN(void) jpeg_fdct_float JPP((FAST_FLOAT * data)); EXTERN(void) jpeg_idct_islow JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, - JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); EXTERN(void) jpeg_idct_ifast JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, - JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); EXTERN(void) jpeg_idct_float JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, - JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); EXTERN(void) jpeg_idct_4x4 JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, - JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); EXTERN(void) jpeg_idct_2x2 JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, - JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); EXTERN(void) jpeg_idct_1x1 JPP((j_decompress_ptr cinfo, jpeg_component_info * compptr, - JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); + JCOEFPTR coef_block, JSAMPARRAY output_buf, JDIMENSION output_col)); /* @@ -2589,7 +2590,7 @@ EXTERN(void) jpeg_idct_1x1 * and may differ from one module to the next. */ -#define ONE ((INT32) 1) +#define ONE ((INT32) 1) #define CONST_SCALE (ONE << CONST_BITS) /* Convert a positive real constant to an integer scaled by CONST_SCALE. @@ -2597,7 +2598,7 @@ EXTERN(void) jpeg_idct_1x1 * thus causing a lot of useless floating-point operations at run time. */ -#define FIX(x) ((INT32) ((x) * CONST_SCALE + 0.5)) +#define FIX(x) ((INT32) ((x) * CONST_SCALE + 0.5)) /* Descale and correctly round an INT32 value that's scaled by N bits. * We assume RIGHT_SHIFT rounds towards minus infinity, so adding @@ -2616,24 +2617,24 @@ EXTERN(void) jpeg_idct_1x1 * correct combination of casts. */ -#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ #define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT16) (const))) #endif -#ifdef SHORTxLCONST_32 /* known to work with Microsoft C 6.0 */ +#ifdef SHORTxLCONST_32 /* known to work with Microsoft C 6.0 */ #define MULTIPLY16C16(var,const) (((INT16) (var)) * ((INT32) (const))) #endif -#ifndef MULTIPLY16C16 /* default definition */ +#ifndef MULTIPLY16C16 /* default definition */ #define MULTIPLY16C16(var,const) ((var) * (const)) #endif /* Same except both inputs are variables. */ -#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ +#ifdef SHORTxSHORT_32 /* may work if 'int' is 32 bits */ #define MULTIPLY16V16(var1,var2) (((INT16) (var1)) * ((INT16) (var2))) #endif -#ifndef MULTIPLY16V16 /* default definition */ +#ifndef MULTIPLY16V16 /* default definition */ #define MULTIPLY16V16(var1,var2) ((var1) * (var2)) #endif @@ -2648,9 +2649,9 @@ EXTERN(void) jpeg_idct_1x1 */ -#define JVERSION "6b 27-Mar-1998" +#define JVERSION "6b 27-Mar-1998" -#define JCOPYRIGHT "Copyright (C) 1998, Thomas G. Lane" +#define JCOPYRIGHT "Copyright (C) 1998, Thomas G. Lane" /* * jmemsys.h @@ -2676,14 +2677,14 @@ EXTERN(void) jpeg_idct_1x1 /* Short forms of external names for systems with brain-damaged linkers. */ #ifdef NEED_SHORT_EXTERNAL_NAMES -#define jpeg_get_small jGetSmall -#define jpeg_free_small jFreeSmall -#define jpeg_get_large jGetLarge -#define jpeg_free_large jFreeLarge -#define jpeg_mem_available jMemAvail -#define jpeg_open_backing_store jOpenBackStore -#define jpeg_mem_init jMemInit -#define jpeg_mem_term jMemTerm +#define jpeg_get_small jGetSmall +#define jpeg_free_small jFreeSmall +#define jpeg_get_large jGetLarge +#define jpeg_free_large jFreeLarge +#define jpeg_mem_available jMemAvail +#define jpeg_open_backing_store jOpenBackStore +#define jpeg_mem_init jMemInit +#define jpeg_mem_term jMemTerm #endif /* NEED_SHORT_EXTERNAL_NAMES */ @@ -2700,7 +2701,7 @@ EXTERN(void) jpeg_idct_1x1 EXTERN(void *) jpeg_get_small JPP((j_common_ptr cinfo, size_t sizeofobject)); EXTERN(void) jpeg_free_small JPP((j_common_ptr cinfo, void * object, - size_t sizeofobject)); + size_t sizeofobject)); /* * These two functions are used to allocate and release large chunks of @@ -2712,9 +2713,9 @@ EXTERN(void) jpeg_free_small JPP((j_common_ptr cinfo, void * object, */ EXTERN(void FAR *) jpeg_get_large JPP((j_common_ptr cinfo, - size_t sizeofobject)); + size_t sizeofobject)); EXTERN(void) jpeg_free_large JPP((j_common_ptr cinfo, void FAR * object, - size_t sizeofobject)); + size_t sizeofobject)); /* * The macro MAX_ALLOC_CHUNK designates the maximum number of bytes that may @@ -2728,7 +2729,7 @@ EXTERN(void) jpeg_free_large JPP((j_common_ptr cinfo, void FAR * object, * size_t and will be a multiple of sizeof(align_type). */ -#ifndef MAX_ALLOC_CHUNK /* may be overridden in jconfig.h */ +#ifndef MAX_ALLOC_CHUNK /* may be overridden in jconfig.h */ #define MAX_ALLOC_CHUNK 1000000000L #endif @@ -2755,9 +2756,9 @@ EXTERN(void) jpeg_free_large JPP((j_common_ptr cinfo, void FAR * object, */ EXTERN(long) jpeg_mem_available JPP((j_common_ptr cinfo, - long min_bytes_needed, - long max_bytes_needed, - long already_allocated)); + long min_bytes_needed, + long max_bytes_needed, + long already_allocated)); /* @@ -2767,23 +2768,23 @@ EXTERN(long) jpeg_mem_available JPP((j_common_ptr cinfo, * are private to the system-dependent backing store routines. */ -#define TEMP_NAME_LENGTH 64 /* max length of a temporary file's name */ +#define TEMP_NAME_LENGTH 64 /* max length of a temporary file's name */ -#ifdef USE_MSDOS_MEMMGR /* DOS-specific junk */ +#ifdef USE_MSDOS_MEMMGR /* DOS-specific junk */ -typedef unsigned short XMSH; /* type of extended-memory handles */ -typedef unsigned short EMSH; /* type of expanded-memory handles */ +typedef unsigned short XMSH; /* type of extended-memory handles */ +typedef unsigned short EMSH; /* type of expanded-memory handles */ typedef union { - short file_handle; /* DOS file handle if it's a temp file */ - XMSH xms_handle; /* handle if it's a chunk of XMS */ - EMSH ems_handle; /* handle if it's a chunk of EMS */ + short file_handle; /* DOS file handle if it's a temp file */ + XMSH xms_handle; /* handle if it's a chunk of XMS */ + EMSH ems_handle; /* handle if it's a chunk of EMS */ } handle_union; #endif /* USE_MSDOS_MEMMGR */ -#ifdef USE_MAC_MEMMGR /* Mac-specific junk */ +#ifdef USE_MAC_MEMMGR /* Mac-specific junk */ #include //ENDINCLUDE #endif /* USE_MAC_MEMMGR */ @@ -2794,30 +2795,35 @@ typedef struct backing_store_struct * backing_store_ptr; typedef struct backing_store_struct { /* Methods for reading/writing/closing this backing-store object */ JMETHOD(void, read_backing_store, (j_common_ptr cinfo, - backing_store_ptr info, - void FAR * buffer_address, - long file_offset, long byte_count)); + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count)); JMETHOD(void, write_backing_store, (j_common_ptr cinfo, - backing_store_ptr info, - void FAR * buffer_address, - long file_offset, long byte_count)); + backing_store_ptr info, + void FAR * buffer_address, + long file_offset, long byte_count)); JMETHOD(void, close_backing_store, (j_common_ptr cinfo, - backing_store_ptr info)); + backing_store_ptr info)); /* Private fields for system-dependent backing-store management */ #ifdef USE_MSDOS_MEMMGR /* For the MS-DOS manager (jmemdos.c), we need: */ - handle_union handle; /* reference to backing-store storage object */ + handle_union handle; /* reference to backing-store storage object */ char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */ #else #ifdef USE_MAC_MEMMGR /* For the Mac manager (jmemmac.c), we need: */ - short temp_file; /* file reference number to temp file */ - FSSpec tempSpec; /* the FSSpec for the temp file */ + short temp_file; /* file reference number to temp file */ + FSSpec tempSpec; /* the FSSpec for the temp file */ char temp_name[TEMP_NAME_LENGTH]; /* name if it's a file */ #else /* For a typical implementation with temp files, we need: */ - FILE * temp_file; /* stdio reference to temp file */ + // + // !!! No in Ren-C release builds. This is actually okay here + // because this backing store is never used...it just does normal + // memory allocation and assumes virtual memory will handle it + // + //FILE * temp_file; /* stdio reference to temp file */ char temp_name[TEMP_NAME_LENGTH]; /* name of temp file */ #endif #endif @@ -2832,9 +2838,13 @@ typedef struct backing_store_struct { * just take an error exit.) */ +// !!! Though this looks like it depends on backing_store_info and hence +// might introduce a core dependency on , it actually just errors +// if it gets called--which it never should (apparently). +// EXTERN(void) jpeg_open_backing_store JPP((j_common_ptr cinfo, - backing_store_ptr info, - long total_bytes_needed)); + backing_store_ptr info, + long total_bytes_needed)); /* diff --git a/src/core/u-jpg.c b/src/extensions/jpg/u-jpg.c similarity index 80% rename from src/core/u-jpg.c rename to src/extensions/jpg/u-jpg.c index 48dccb4eca..b215f4a3f0 100644 --- a/src/core/u-jpg.c +++ b/src/extensions/jpg/u-jpg.c @@ -1,10 +1,44 @@ +// %sys-jpg.h appears to be a mostly unmodified third-party file. The defines +// are used to configure it. +// #define JPEG_INTERNALS #define NO_GETENV -#include "reb-config.h" -#include "reb-c.h" -#include #include "sys-jpg.h" +// setjmp and longjmp are used as the error reporting hook. It's not clear +// if this choice was made by Rebol or if the original file used it. +// +#include + +extern jmp_buf jpeg_state; +extern void jpeg_info(char *buffer, int nbytes, int *w, int *h); +extern void jpeg_load(char *buffer, int nbytes, char *output); + + +// !!! In R3-Alpha, it was possible to write a codec that was not dependent on +// the RL_API in %reb-host.h (or the "internal API", which didn't exist +// formally but was effectively what you got by including %sys-core.h). +// Instead the interface to the codec was abstract to the byte-level, which +// meant Rebol types had to be proxied into "pure" C types, the codec would +// run and then proxy back into Rebol types again. +// +// Ren-C does not force all extension code to Rebol to include everything +// (such as the definition of the full Reb_Value struct and all its +// dependencies) but extensions or plugins are expected to be linked to +// the RL_API. This covers many things, such as transferring managed platform +// independent strings back and forth, etc. +// +// These routines seem to be of somewhat old origin in the 90s. Although +// the main code has not been retrofitted to use REBINTs or REBOOLs or things +// from %reb-c.h, it does depend on TO_PIXEL_COLOR to get the pixel bits +// right across platforms, as well as needing a stable 32-bit integer type +// from u32. If the code were sync'd, then it may be updated by someone +// else to not need these dependencies...and %reb-host.h inclusion could +// be moved to the bottom of the file. +// +#include "reb-c.h" + + /* * jdatasrc.c * @@ -26,20 +60,16 @@ //#include "jpeglib.h" //#include "jerror.h" -#ifdef LONG_SIZE_64 -typedef unsigned int uinteger32; -#else -typedef unsigned long uinteger32; -#endif +typedef u32 uinteger32; /* Expanded data source object for stdio input */ typedef struct { - struct jpeg_source_mgr pub; /* public fields */ + struct jpeg_source_mgr pub; /* public fields */ - JOCTET * buffer; /* start of buffer */ + JOCTET * buffer; /* start of buffer */ size_t nbytes; - boolean start_of_file; /* have we gotten any data yet? */ + boolean start_of_file; /* have we gotten any data yet? */ } my_source_mgr; typedef my_source_mgr * my_src_ptr; @@ -102,18 +132,18 @@ fill_input_buffer (j_decompress_ptr cinfo) static JOCTET buffer[ 2 ]; if (src->nbytes <= 0) { - if (src->start_of_file) /* Treat empty input file as fatal error */ + if (src->start_of_file) /* Treat empty input file as fatal error */ ERREXIT(cinfo, JERR_INPUT_EMPTY); WARNMS(cinfo, JWRN_JPEG_EOF); /* Insert a fake EOI marker */ buffer[0] = (JOCTET) 0xFF; buffer[1] = (JOCTET) JPEG_EOI; - src->pub.next_input_byte = buffer; + src->pub.next_input_byte = buffer; src->pub.bytes_in_buffer = 2; } else { - src->pub.next_input_byte = src->buffer; - src->pub.bytes_in_buffer = src->nbytes; + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = src->nbytes; } src->start_of_file = FALSE; @@ -193,10 +223,10 @@ jpeg_series_src (j_decompress_ptr cinfo, JOCTET *buffer, size_t nbytes ) * This makes it unsafe to use this manager and a different source * manager serially with the same JPEG object. Caveat programmer. */ - if (cinfo->src == NULL) { /* first time for this JPEG object? */ + if (cinfo->src == NULL) { /* first time for this JPEG object? */ cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, - SIZEOF(my_source_mgr)); + SIZEOF(my_source_mgr)); src = (my_src_ptr) cinfo->src; } @@ -236,8 +266,8 @@ void jpeg_load( char *buffer, int nbytes, char *output ) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; - JSAMPROW array[ 4 ]; - unsigned int i, j; + JSAMPROW array[ 4 ]; + unsigned int i, j; /* Initialize the JPEG decompression object with default error handling. */ cinfo.err = jpeg_std_error(&jerr); @@ -254,41 +284,41 @@ void jpeg_load( char *buffer, int nbytes, char *output ) /* Process data */ while (cinfo.output_scanline < cinfo.output_height) { - array[ 0 ] = (JSAMPROW)(output + cinfo.output_scanline * cinfo.image_width * 4); - array[ 1 ] = array[ 0 ] + cinfo.image_width * 4; - array[ 2 ] = array[ 1 ] + cinfo.image_width * 4; - array[ 3 ] = array[ 2 ] + cinfo.image_width * 4; + array[ 0 ] = (JSAMPROW)(output + cinfo.output_scanline * cinfo.image_width * 4); + array[ 1 ] = array[ 0 ] + cinfo.image_width * 4; + array[ 2 ] = array[ 1 ] + cinfo.image_width * 4; + array[ 3 ] = array[ 2 ] + cinfo.image_width * 4; jpeg_read_scanlines(&cinfo, array, 4 ); } if (cinfo.out_color_space != JCS_GRAYSCALE) // convert 3 byte values into four byte ones for ( i=0; imem = NULL; /* so jpeg_destroy knows mem mgr not called */ + cinfo->mem = NULL; /* so jpeg_destroy knows mem mgr not called */ if (version != JPEG_LIB_VERSION) ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version); if (structsize != SIZEOF(struct jpeg_decompress_struct)) - ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, - (int) SIZEOF(struct jpeg_decompress_struct), (int) structsize); + ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, + (int) SIZEOF(struct jpeg_decompress_struct), (int) structsize); /* For debugging purposes, we zero the whole master structure. * But the application has already set the err pointer, and may have set @@ -421,22 +451,22 @@ default_decompress_parms (j_decompress_ptr cinfo) cinfo->jpeg_color_space = JCS_GRAYSCALE; cinfo->out_color_space = JCS_GRAYSCALE; break; - + case 3: if (cinfo->saw_JFIF_marker) { cinfo->jpeg_color_space = JCS_YCbCr; /* JFIF implies YCbCr */ } else if (cinfo->saw_Adobe_marker) { switch (cinfo->Adobe_transform) { case 0: - cinfo->jpeg_color_space = JCS_RGB; - break; + cinfo->jpeg_color_space = JCS_RGB; + break; case 1: - cinfo->jpeg_color_space = JCS_YCbCr; - break; + cinfo->jpeg_color_space = JCS_YCbCr; + break; default: - WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); - cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ - break; + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + break; } } else { /* Saw no special markers, try to guess from the component IDs */ @@ -445,31 +475,31 @@ default_decompress_parms (j_decompress_ptr cinfo) int cid2 = cinfo->comp_info[2].component_id; if (cid0 == 1 && cid1 == 2 && cid2 == 3) - cinfo->jpeg_color_space = JCS_YCbCr; /* assume JFIF w/out marker */ + cinfo->jpeg_color_space = JCS_YCbCr; /* assume JFIF w/out marker */ else if (cid0 == 82 && cid1 == 71 && cid2 == 66) - cinfo->jpeg_color_space = JCS_RGB; /* ASCII 'R', 'G', 'B' */ + cinfo->jpeg_color_space = JCS_RGB; /* ASCII 'R', 'G', 'B' */ else { - TRACEMS3(cinfo, 1, JTRC_UNKNOWN_IDS, cid0, cid1, cid2); - cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ + TRACEMS3(cinfo, 1, JTRC_UNKNOWN_IDS, cid0, cid1, cid2); + cinfo->jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */ } } /* Always guess RGB is proper output colorspace. */ cinfo->out_color_space = JCS_RGB; break; - + case 4: if (cinfo->saw_Adobe_marker) { switch (cinfo->Adobe_transform) { case 0: - cinfo->jpeg_color_space = JCS_CMYK; - break; + cinfo->jpeg_color_space = JCS_CMYK; + break; case 2: - cinfo->jpeg_color_space = JCS_YCCK; - break; + cinfo->jpeg_color_space = JCS_YCCK; + break; default: - WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); - cinfo->jpeg_color_space = JCS_YCCK; /* assume it's YCCK */ - break; + WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo->Adobe_transform); + cinfo->jpeg_color_space = JCS_YCCK; /* assume it's YCCK */ + break; } } else { /* No special markers, assume straight CMYK. */ @@ -477,7 +507,7 @@ default_decompress_parms (j_decompress_ptr cinfo) } cinfo->out_color_space = JCS_CMYK; break; - + default: cinfo->jpeg_color_space = JCS_UNKNOWN; cinfo->out_color_space = JCS_UNKNOWN; @@ -485,7 +515,7 @@ default_decompress_parms (j_decompress_ptr cinfo) } /* Set defaults for other decompression parameters. */ - cinfo->scale_num = 1; /* 1:1 scaling */ + cinfo->scale_num = 1; /* 1:1 scaling */ cinfo->scale_denom = 1; cinfo->output_gamma = 1.0; cinfo->buffered_image = FALSE; @@ -553,7 +583,7 @@ jpeg_read_header (j_decompress_ptr cinfo, boolean require_image) retcode = JPEG_HEADER_OK; break; case JPEG_REACHED_EOI: - if (require_image) /* Complain if application wanted an image */ + if (require_image) /* Complain if application wanted an image */ ERREXIT(cinfo, JERR_NO_IMAGE); /* Reset to start state; it would be safer to require the application to * call jpeg_abort, but we can't change it now for compatibility reasons. @@ -685,7 +715,7 @@ jpeg_finish_decompress (j_decompress_ptr cinfo) /* Read until EOI */ while (! cinfo->inputctl->eoi_reached) { if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) - return FALSE; /* Suspend, come back later */ + return FALSE; /* Suspend, come back later */ } /* Do final cleanup */ (*cinfo->src->term_source) (cinfo); @@ -747,24 +777,24 @@ jpeg_start_decompress (j_decompress_ptr cinfo) if (cinfo->inputctl->has_multiple_scans) { #ifdef D_MULTISCAN_FILES_SUPPORTED for (;;) { - int retcode; - /* Call progress monitor hook if present */ - if (cinfo->progress != NULL) - (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); - /* Absorb some more input */ - retcode = (*cinfo->inputctl->consume_input) (cinfo); - if (retcode == JPEG_SUSPENDED) - return FALSE; - if (retcode == JPEG_REACHED_EOI) - break; - /* Advance progress counter if appropriate */ - if (cinfo->progress != NULL && - (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { - if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { - /* jdmaster underestimated number of scans; ratchet up one scan */ - cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; - } - } + int retcode; + /* Call progress monitor hook if present */ + if (cinfo->progress != NULL) + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + /* Absorb some more input */ + retcode = (*cinfo->inputctl->consume_input) (cinfo); + if (retcode == JPEG_SUSPENDED) + return FALSE; + if (retcode == JPEG_REACHED_EOI) + break; + /* Advance progress counter if appropriate */ + if (cinfo->progress != NULL && + (retcode == JPEG_ROW_COMPLETED || retcode == JPEG_REACHED_SOS)) { + if (++cinfo->progress->pass_counter >= cinfo->progress->pass_limit) { + /* jdmaster underestimated number of scans; ratchet up one scan */ + cinfo->progress->pass_limit += (long) cinfo->total_iMCU_rows; + } + } } #else ERREXIT(cinfo, JERR_NOT_COMPILED); @@ -803,16 +833,16 @@ output_pass_setup (j_decompress_ptr cinfo) JDIMENSION last_scanline; /* Call progress monitor hook if present */ if (cinfo->progress != NULL) { - cinfo->progress->pass_counter = (long) cinfo->output_scanline; - cinfo->progress->pass_limit = (long) cinfo->output_height; - (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); + cinfo->progress->pass_counter = (long) cinfo->output_scanline; + cinfo->progress->pass_limit = (long) cinfo->output_height; + (*cinfo->progress->progress_monitor) ((j_common_ptr) cinfo); } /* Process some data */ last_scanline = cinfo->output_scanline; - (*cinfo->main->process_data) (cinfo, (JSAMPARRAY) NULL, - &cinfo->output_scanline, (JDIMENSION) 0); + (*cinfo->main_ptr->process_data) (cinfo, (JSAMPARRAY) NULL, + &cinfo->output_scanline, (JDIMENSION) 0); if (cinfo->output_scanline == last_scanline) - return FALSE; /* No progress made, must suspend */ + return FALSE; /* No progress made, must suspend */ } /* Finish up dummy pass, and set up for another one */ (*cinfo->master->finish_output_pass) (cinfo); @@ -845,7 +875,7 @@ output_pass_setup (j_decompress_ptr cinfo) GLOBAL(JDIMENSION) jpeg_read_scanlines (j_decompress_ptr cinfo, JSAMPARRAY scanlines, - JDIMENSION max_lines) + JDIMENSION max_lines) { JDIMENSION row_ctr; @@ -865,7 +895,7 @@ jpeg_read_scanlines (j_decompress_ptr cinfo, JSAMPARRAY scanlines, /* Process some data */ row_ctr = 0; - (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, max_lines); + (*cinfo->main_ptr->process_data) (cinfo, scanlines, &row_ctr, max_lines); cinfo->output_scanline += row_ctr; return row_ctr; } @@ -878,7 +908,7 @@ jpeg_read_scanlines (j_decompress_ptr cinfo, JSAMPARRAY scanlines, GLOBAL(JDIMENSION) jpeg_read_raw_data (j_decompress_ptr cinfo, JSAMPIMAGE data, - JDIMENSION max_lines) + JDIMENSION max_lines) { JDIMENSION lines_per_iMCU_row; @@ -903,7 +933,7 @@ jpeg_read_raw_data (j_decompress_ptr cinfo, JSAMPIMAGE data, /* Decompress directly into user's buffer. */ if (! (*cinfo->coef->decompress_data) (cinfo, data)) - return 0; /* suspension forced, can do nothing more */ + return 0; /* suspension forced, can do nothing more */ /* OK, we processed one iMCU row. */ cinfo->output_scanline += lines_per_iMCU_row; @@ -959,9 +989,9 @@ jpeg_finish_output (j_decompress_ptr cinfo) } /* Read markers looking for SOS or EOI */ while (cinfo->input_scan_number <= cinfo->output_scan_number && - ! cinfo->inputctl->eoi_reached) { + ! cinfo->inputctl->eoi_reached) { if ((*cinfo->inputctl->consume_input) (cinfo) == JPEG_SUSPENDED) - return FALSE; /* Suspend, come back later */ + return FALSE; /* Suspend, come back later */ } cinfo->global_state = DSTATE_BUFIMAGE; return TRUE; @@ -991,7 +1021,7 @@ jpeg_finish_output (j_decompress_ptr cinfo) typedef struct { struct jpeg_decomp_master pub; /* public fields */ - int pass_number; /* # of passes completed */ + int pass_number; /* # of passes completed */ boolean using_merged_upsample; /* TRUE if using merged upsample/cconvert */ @@ -1036,7 +1066,7 @@ use_merged_upsample (j_decompress_ptr cinfo) cinfo->comp_info[2].DCT_scaled_size != cinfo->min_DCT_scaled_size) return FALSE; /* ??? also need to test for upsample-time rescaling, when & if supported */ - return TRUE; /* by golly, it'll work... */ + return TRUE; /* by golly, it'll work... */ #else return FALSE; #endif @@ -1102,10 +1132,10 @@ jpeg_calc_output_dimensions (j_decompress_ptr cinfo) ci++, compptr++) { int ssize = cinfo->min_DCT_scaled_size; while (ssize < DCTSIZE && - (compptr->h_samp_factor * ssize * 2 <= - cinfo->max_h_samp_factor * cinfo->min_DCT_scaled_size) && - (compptr->v_samp_factor * ssize * 2 <= - cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size)) { + (compptr->h_samp_factor * ssize * 2 <= + cinfo->max_h_samp_factor * cinfo->min_DCT_scaled_size) && + (compptr->v_samp_factor * ssize * 2 <= + cinfo->max_v_samp_factor * cinfo->min_DCT_scaled_size)) { ssize = ssize * 2; } compptr->DCT_scaled_size = ssize; @@ -1119,12 +1149,12 @@ jpeg_calc_output_dimensions (j_decompress_ptr cinfo) /* Size in samples, after IDCT scaling */ compptr->downsampled_width = (JDIMENSION) jdiv_round_up((long) cinfo->image_width * - (long) (compptr->h_samp_factor * compptr->DCT_scaled_size), - (long) (cinfo->max_h_samp_factor * DCTSIZE)); + (long) (compptr->h_samp_factor * compptr->DCT_scaled_size), + (long) (cinfo->max_h_samp_factor * DCTSIZE)); compptr->downsampled_height = (JDIMENSION) jdiv_round_up((long) cinfo->image_height * - (long) (compptr->v_samp_factor * compptr->DCT_scaled_size), - (long) (cinfo->max_v_samp_factor * DCTSIZE)); + (long) (compptr->v_samp_factor * compptr->DCT_scaled_size), + (long) (cinfo->max_v_samp_factor * DCTSIZE)); } #else /* !IDCT_SCALING_SUPPORTED */ @@ -1156,12 +1186,12 @@ jpeg_calc_output_dimensions (j_decompress_ptr cinfo) case JCS_YCCK: cinfo->out_color_components = 4; break; - default: /* else must be same colorspace as in file */ + default: /* else must be same colorspace as in file */ cinfo->out_color_components = cinfo->num_components; break; } cinfo->output_components = (cinfo->quantize_colors ? 1 : - cinfo->out_color_components); + cinfo->out_color_components); /* See if upsampler will want to emit more than one row at a time */ if (use_merged_upsample(cinfo)) @@ -1178,20 +1208,20 @@ jpeg_calc_output_dimensions (j_decompress_ptr cinfo) * processes are inner loops and need to be as fast as possible. On most * machines, particularly CPUs with pipelines or instruction prefetch, * a (subscript-check-less) C table lookup - * x = sample_range_limit[x]; + * x = sample_range_limit[x]; * is faster than explicit tests - * if (x < 0) x = 0; - * else if (x > MAXJSAMPLE) x = MAXJSAMPLE; + * if (x < 0) x = 0; + * else if (x > MAXJSAMPLE) x = MAXJSAMPLE; * These processes all use a common table prepared by the routine below. * * For most steps we can mathematically guarantee that the initial value * of x is within MAXJSAMPLE+1 of the legal range, so a table running from * -(MAXJSAMPLE+1) to 2*MAXJSAMPLE+1 is sufficient. But for the initial - * limiting step (just after the IDCT), a wildly out-of-range value is + * limiting step (just after the IDCT), a wildly out-of-range value is * possible if the input data is corrupt. To avoid any chance of indexing * off the end of memory and getting a bad-pointer trap, we perform the * post-IDCT limiting thus: - * x = range_limit[x & MASK]; + * x = range_limit[x & MASK]; * where MASK is 2 bits wider than legal sample data, ie 10 bits for 8-bit * samples. Under normal circumstances this is more than enough range and * a correct output will be generated; with bogus input data the mask will @@ -1223,23 +1253,23 @@ prepare_range_limit_table (j_decompress_ptr cinfo) table = (JSAMPLE *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - (5 * (MAXJSAMPLE+1) + CENTERJSAMPLE) * SIZEOF(JSAMPLE)); - table += (MAXJSAMPLE+1); /* allow negative subscripts of simple table */ + (5 * (MAXJSAMPLE+1) + CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + table += (MAXJSAMPLE+1); /* allow negative subscripts of simple table */ cinfo->sample_range_limit = table; /* First segment of "simple" table: limit[x] = 0 for x < 0 */ MEMZERO(table - (MAXJSAMPLE+1), (MAXJSAMPLE+1) * SIZEOF(JSAMPLE)); /* Main part of "simple" table: limit[x] = x */ for (i = 0; i <= MAXJSAMPLE; i++) table[i] = (JSAMPLE) i; - table += CENTERJSAMPLE; /* Point to where post-IDCT table starts */ + table += CENTERJSAMPLE; /* Point to where post-IDCT table starts */ /* End of simple table, rest of first half of post-IDCT table */ for (i = CENTERJSAMPLE; i < 2*(MAXJSAMPLE+1); i++) table[i] = MAXJSAMPLE; /* Second half of post-IDCT table */ MEMZERO(table + (2 * (MAXJSAMPLE+1)), - (2 * (MAXJSAMPLE+1) - CENTERJSAMPLE) * SIZEOF(JSAMPLE)); + (2 * (MAXJSAMPLE+1) - CENTERJSAMPLE) * SIZEOF(JSAMPLE)); MEMCOPY(table + (4 * (MAXJSAMPLE+1) - CENTERJSAMPLE), - cinfo->sample_range_limit, CENTERJSAMPLE * SIZEOF(JSAMPLE)); + cinfo->sample_range_limit, CENTERJSAMPLE * SIZEOF(JSAMPLE)); } @@ -1415,7 +1445,7 @@ prepare_for_output_pass (j_decompress_ptr cinfo) master->pub.is_dummy_pass = FALSE; (*cinfo->cquantize->start_pass) (cinfo, FALSE); (*cinfo->post->start_pass) (cinfo, JBUF_CRANK_DEST); - (*cinfo->main->start_pass) (cinfo, JBUF_CRANK_DEST); + (*cinfo->main_ptr->start_pass) (cinfo, JBUF_CRANK_DEST); #else ERREXIT(cinfo, JERR_NOT_COMPILED); #endif /* QUANT_2PASS_SUPPORTED */ @@ -1423,25 +1453,25 @@ prepare_for_output_pass (j_decompress_ptr cinfo) if (cinfo->quantize_colors && cinfo->colormap == NULL) { /* Select new quantization method */ if (cinfo->two_pass_quantize && cinfo->enable_2pass_quant) { - cinfo->cquantize = master->quantizer_2pass; - master->pub.is_dummy_pass = TRUE; + cinfo->cquantize = master->quantizer_2pass; + master->pub.is_dummy_pass = TRUE; } else if (cinfo->enable_1pass_quant) { - cinfo->cquantize = master->quantizer_1pass; + cinfo->cquantize = master->quantizer_1pass; } else { - ERREXIT(cinfo, JERR_MODE_CHANGE); + ERREXIT(cinfo, JERR_MODE_CHANGE); } } (*cinfo->idct->start_pass) (cinfo); (*cinfo->coef->start_output_pass) (cinfo); if (! cinfo->raw_data_out) { if (! master->using_merged_upsample) - (*cinfo->cconvert->start_pass) (cinfo); + (*cinfo->cconvert->start_pass) (cinfo); (*cinfo->upsample->start_pass) (cinfo); if (cinfo->quantize_colors) - (*cinfo->cquantize->start_pass) (cinfo, master->pub.is_dummy_pass); + (*cinfo->cquantize->start_pass) (cinfo, master->pub.is_dummy_pass); (*cinfo->post->start_pass) (cinfo, - (master->pub.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU)); - (*cinfo->main->start_pass) (cinfo, JBUF_PASS_THRU); + (master->pub.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU)); + (*cinfo->main_ptr->start_pass) (cinfo, JBUF_PASS_THRU); } } @@ -1449,7 +1479,7 @@ prepare_for_output_pass (j_decompress_ptr cinfo) if (cinfo->progress != NULL) { cinfo->progress->completed_passes = master->pass_number; cinfo->progress->total_passes = master->pass_number + - (master->pub.is_dummy_pass ? 2 : 1); + (master->pub.is_dummy_pass ? 2 : 1); /* In buffered-image mode, we assume one more output pass if EOI not * yet reached, but no more passes if EOI has been reached. */ @@ -1516,7 +1546,7 @@ jinit_master_decompress (j_decompress_ptr cinfo) master = (my_master_ptr) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(my_decomp_master)); + SIZEOF(my_decomp_master)); cinfo->master = (struct jpeg_decomp_master *) master; master->pub.prepare_for_output_pass = prepare_for_output_pass; master->pub.finish_output_pass = finish_output_pass; @@ -1548,7 +1578,7 @@ jinit_master_decompress (j_decompress_ptr cinfo) typedef struct { struct jpeg_input_controller pub; /* public fields */ - boolean inheaders; /* TRUE until first SOS is reached */ + boolean inheaders; /* TRUE until first SOS is reached */ } my_input_controller; typedef my_input_controller * my_inputctl_ptr; @@ -1581,7 +1611,7 @@ initial_setup (j_decompress_ptr cinfo) /* Check that number of components won't exceed internal array sizes */ if (cinfo->num_components > MAX_COMPONENTS) ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->num_components, - MAX_COMPONENTS); + MAX_COMPONENTS); /* Compute maximum sampling factors; check factor validity */ cinfo->max_h_samp_factor = 1; @@ -1589,12 +1619,12 @@ initial_setup (j_decompress_ptr cinfo) for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; ci++, compptr++) { if (compptr->h_samp_factor<=0 || compptr->h_samp_factor>MAX_SAMP_FACTOR || - compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR) + compptr->v_samp_factor<=0 || compptr->v_samp_factor>MAX_SAMP_FACTOR) ERREXIT(cinfo, JERR_BAD_SAMPLING); cinfo->max_h_samp_factor = MAX(cinfo->max_h_samp_factor, - compptr->h_samp_factor); + compptr->h_samp_factor); cinfo->max_v_samp_factor = MAX(cinfo->max_v_samp_factor, - compptr->v_samp_factor); + compptr->v_samp_factor); } /* We initialize DCT_scaled_size and min_DCT_scaled_size to DCTSIZE. @@ -1610,10 +1640,10 @@ initial_setup (j_decompress_ptr cinfo) /* Size in DCT blocks */ compptr->width_in_blocks = (JDIMENSION) jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, - (long) (cinfo->max_h_samp_factor * DCTSIZE)); + (long) (cinfo->max_h_samp_factor * DCTSIZE)); compptr->height_in_blocks = (JDIMENSION) jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, - (long) (cinfo->max_v_samp_factor * DCTSIZE)); + (long) (cinfo->max_v_samp_factor * DCTSIZE)); /* downsampled_width and downsampled_height will also be overridden by * jdmaster.c if we are doing full decompression. The transcoder library * doesn't use these values, but the calling application might. @@ -1621,10 +1651,10 @@ initial_setup (j_decompress_ptr cinfo) /* Size in samples */ compptr->downsampled_width = (JDIMENSION) jdiv_round_up((long) cinfo->image_width * (long) compptr->h_samp_factor, - (long) cinfo->max_h_samp_factor); + (long) cinfo->max_h_samp_factor); compptr->downsampled_height = (JDIMENSION) jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor, - (long) cinfo->max_v_samp_factor); + (long) cinfo->max_v_samp_factor); /* Mark component needed, until color conversion says otherwise */ compptr->component_needed = TRUE; /* Mark no quantization table yet saved for component */ @@ -1634,7 +1664,7 @@ initial_setup (j_decompress_ptr cinfo) /* Compute number of fully interleaved MCU rows. */ cinfo->total_iMCU_rows = (JDIMENSION) jdiv_round_up((long) cinfo->image_height, - (long) (cinfo->max_v_samp_factor*DCTSIZE)); + (long) (cinfo->max_v_samp_factor*DCTSIZE)); /* Decide whether file contains multiple scans */ if (cinfo->comps_in_scan < cinfo->num_components || cinfo->progressive_mode) @@ -1651,16 +1681,16 @@ per_scan_setup (j_decompress_ptr cinfo) { int ci, mcublks, tmp; jpeg_component_info *compptr; - + if (cinfo->comps_in_scan == 1) { - + /* Noninterleaved (single-component) scan */ compptr = cinfo->cur_comp_info[0]; - + /* Overall image size in MCUs */ cinfo->MCUs_per_row = compptr->width_in_blocks; cinfo->MCU_rows_in_scan = compptr->height_in_blocks; - + /* For noninterleaved scan, always one block per MCU */ compptr->MCU_width = 1; compptr->MCU_height = 1; @@ -1673,28 +1703,28 @@ per_scan_setup (j_decompress_ptr cinfo) tmp = (int) (compptr->height_in_blocks % compptr->v_samp_factor); if (tmp == 0) tmp = compptr->v_samp_factor; compptr->last_row_height = tmp; - + /* Prepare array describing MCU composition */ cinfo->blocks_in_MCU = 1; cinfo->MCU_membership[0] = 0; - + } else { - + /* Interleaved (multi-component) scan */ if (cinfo->comps_in_scan <= 0 || cinfo->comps_in_scan > MAX_COMPS_IN_SCAN) ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo->comps_in_scan, - MAX_COMPS_IN_SCAN); - + MAX_COMPS_IN_SCAN); + /* Overall image size in MCUs */ cinfo->MCUs_per_row = (JDIMENSION) jdiv_round_up((long) cinfo->image_width, - (long) (cinfo->max_h_samp_factor*DCTSIZE)); + (long) (cinfo->max_h_samp_factor*DCTSIZE)); cinfo->MCU_rows_in_scan = (JDIMENSION) jdiv_round_up((long) cinfo->image_height, - (long) (cinfo->max_v_samp_factor*DCTSIZE)); - + (long) (cinfo->max_v_samp_factor*DCTSIZE)); + cinfo->blocks_in_MCU = 0; - + for (ci = 0; ci < cinfo->comps_in_scan; ci++) { compptr = cinfo->cur_comp_info[ci]; /* Sampling factors give # of blocks of component in each MCU */ @@ -1712,12 +1742,12 @@ per_scan_setup (j_decompress_ptr cinfo) /* Prepare array describing MCU composition */ mcublks = compptr->MCU_blocks; if (cinfo->blocks_in_MCU + mcublks > D_MAX_BLOCKS_IN_MCU) - ERREXIT(cinfo, JERR_BAD_MCU_SIZE); + ERREXIT(cinfo, JERR_BAD_MCU_SIZE); while (mcublks-- > 0) { - cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = ci; } } - + } } @@ -1758,12 +1788,12 @@ latch_quant_tables (j_decompress_ptr cinfo) /* Make sure specified quantization table is present */ qtblno = compptr->quant_tbl_no; if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || - cinfo->quant_tbl_ptrs[qtblno] == NULL) + cinfo->quant_tbl_ptrs[qtblno] == NULL) ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno); /* OK, save away the quantization table */ qtbl = (JQUANT_TBL *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(JQUANT_TBL)); + SIZEOF(JQUANT_TBL)); MEMCOPY(qtbl, cinfo->quant_tbl_ptrs[qtblno], SIZEOF(JQUANT_TBL)); compptr->quant_table = qtbl; } @@ -1823,31 +1853,31 @@ consume_markers (j_decompress_ptr cinfo) val = (*cinfo->marker->read_markers) (cinfo); switch (val) { - case JPEG_REACHED_SOS: /* Found SOS */ - if (inputctl->inheaders) { /* 1st SOS */ + case JPEG_REACHED_SOS: /* Found SOS */ + if (inputctl->inheaders) { /* 1st SOS */ initial_setup(cinfo); inputctl->inheaders = FALSE; /* Note: start_input_pass must be called by jdmaster.c * before any more input can be consumed. jdapimin.c is * responsible for enforcing this sequencing. */ - } else { /* 2nd or later SOS marker */ + } else { /* 2nd or later SOS marker */ if (! inputctl->pub.has_multiple_scans) - ERREXIT(cinfo, JERR_EOI_EXPECTED); /* Oops, I wasn't expecting this! */ + ERREXIT(cinfo, JERR_EOI_EXPECTED); /* Oops, I wasn't expecting this! */ start_input_pass(cinfo); } break; - case JPEG_REACHED_EOI: /* Found EOI */ + case JPEG_REACHED_EOI: /* Found EOI */ inputctl->pub.eoi_reached = TRUE; - if (inputctl->inheaders) { /* Tables-only datastream, apparently */ + if (inputctl->inheaders) { /* Tables-only datastream, apparently */ if (cinfo->marker->saw_SOF) - ERREXIT(cinfo, JERR_SOF_NO_SOS); + ERREXIT(cinfo, JERR_SOF_NO_SOS); } else { /* Prevent infinite loop in coef ctlr's decompress_data routine * if user set output_scan_number larger than number of scans. */ if (cinfo->output_scan_number > cinfo->input_scan_number) - cinfo->output_scan_number = cinfo->input_scan_number; + cinfo->output_scan_number = cinfo->input_scan_number; } break; case JPEG_SUSPENDED: @@ -1892,7 +1922,7 @@ jinit_input_controller (j_decompress_ptr cinfo) /* Create subobject in permanent pool */ inputctl = (my_inputctl_ptr) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, - SIZEOF(my_input_controller)); + SIZEOF(my_input_controller)); cinfo->inputctl = (struct jpeg_input_controller *) inputctl; /* Initialize method pointers */ inputctl->pub.consume_input = consume_markers; @@ -1925,29 +1955,29 @@ jinit_input_controller (j_decompress_ptr cinfo) //#include "jpeglib.h" -typedef enum { /* JPEG marker codes */ +typedef enum { /* JPEG marker codes */ M_SOF0 = 0xc0, M_SOF1 = 0xc1, M_SOF2 = 0xc2, M_SOF3 = 0xc3, - + M_SOF5 = 0xc5, M_SOF6 = 0xc6, M_SOF7 = 0xc7, - + M_JPG = 0xc8, M_SOF9 = 0xc9, M_SOF10 = 0xca, M_SOF11 = 0xcb, - + M_SOF13 = 0xcd, M_SOF14 = 0xce, M_SOF15 = 0xcf, - + M_DHT = 0xc4, - + M_DAC = 0xcc, - + M_RST0 = 0xd0, M_RST1 = 0xd1, M_RST2 = 0xd2, @@ -1956,7 +1986,7 @@ typedef enum { /* JPEG marker codes */ M_RST5 = 0xd5, M_RST6 = 0xd6, M_RST7 = 0xd7, - + M_SOI = 0xd8, M_EOI = 0xd9, M_SOS = 0xda, @@ -1965,7 +1995,7 @@ typedef enum { /* JPEG marker codes */ M_DRI = 0xdd, M_DHP = 0xde, M_EXP = 0xdf, - + M_APP0 = 0xe0, M_APP1 = 0xe1, M_APP2 = 0xe2, @@ -1982,13 +2012,13 @@ typedef enum { /* JPEG marker codes */ M_APP13 = 0xed, M_APP14 = 0xee, M_APP15 = 0xef, - + M_JPG0 = 0xf0, M_JPG13 = 0xfd, M_COM = 0xfe, - + M_TEM = 0x01, - + M_ERROR = 0x100 } JPEG_MARKER; @@ -2007,8 +2037,8 @@ typedef struct { unsigned int length_limit_APPn[16]; /* Status of COM/APPn marker saving */ - jpeg_saved_marker_ptr cur_marker; /* NULL if not processing a marker */ - unsigned int bytes_read; /* data bytes read so far in marker */ + jpeg_saved_marker_ptr cur_marker; /* NULL if not processing a marker */ + unsigned int bytes_read; /* data bytes read so far in marker */ /* Note: cur_marker is not linked into marker_list until it's all read. */ } my_marker_reader; @@ -2025,49 +2055,49 @@ typedef my_marker_reader * my_marker_ptr; /* Declare and initialize local copies of input pointer/count */ #define jdar_INPUT_VARS(cinfo) \ - struct jpeg_source_mgr * datasrc = (cinfo)->src; \ - const JOCTET * next_input_byte = datasrc->next_input_byte; \ - size_t bytes_in_buffer = datasrc->bytes_in_buffer + struct jpeg_source_mgr * datasrc = (cinfo)->src; \ + const JOCTET * next_input_byte = datasrc->next_input_byte; \ + size_t bytes_in_buffer = datasrc->bytes_in_buffer /* Unload the local copies --- do this only at a restart boundary */ #define jdar_INPUT_SYNC(cinfo) \ - ( datasrc->next_input_byte = next_input_byte, \ - datasrc->bytes_in_buffer = bytes_in_buffer ) + ( datasrc->next_input_byte = next_input_byte, \ + datasrc->bytes_in_buffer = bytes_in_buffer ) /* Reload the local copies --- used only in MAKE_BYTE_AVAIL */ #define jdar_INPUT_RELOAD(cinfo) \ - ( next_input_byte = datasrc->next_input_byte, \ - bytes_in_buffer = datasrc->bytes_in_buffer ) + ( next_input_byte = datasrc->next_input_byte, \ + bytes_in_buffer = datasrc->bytes_in_buffer ) /* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available. * Note we do *not* do INPUT_SYNC before calling fill_input_buffer, * but we must reload the local copies after a successful fill. */ #define jdar_MAKE_BYTE_AVAIL(cinfo,action) \ - if (bytes_in_buffer == 0) { \ - if (! (*datasrc->fill_input_buffer) (cinfo)) \ - { action; } \ - jdar_INPUT_RELOAD(cinfo); \ - } + if (bytes_in_buffer == 0) { \ + if (! (*datasrc->fill_input_buffer) (cinfo)) \ + { action; } \ + jdar_INPUT_RELOAD(cinfo); \ + } /* Read a byte into variable V. * If must suspend, take the specified action (typically "return FALSE"). */ #define jdar_INPUT_BYTE(cinfo,V,action) \ - MAKESTMT( jdar_MAKE_BYTE_AVAIL(cinfo,action); \ - bytes_in_buffer--; \ - V = GETJOCTET(*next_input_byte++); ) + MAKESTMT( jdar_MAKE_BYTE_AVAIL(cinfo,action); \ + bytes_in_buffer--; \ + V = GETJOCTET(*next_input_byte++); ) /* As above, but read two bytes interpreted as an unsigned 16-bit integer. * V should be declared unsigned int or perhaps INT32. */ #define jdar_INPUT_2BYTES(cinfo,V,action) \ - MAKESTMT( jdar_MAKE_BYTE_AVAIL(cinfo,action); \ - bytes_in_buffer--; \ - V = ((unsigned int) GETJOCTET(*next_input_byte++)) << 8; \ - jdar_MAKE_BYTE_AVAIL(cinfo,action); \ - bytes_in_buffer--; \ - V += GETJOCTET(*next_input_byte++); ) + MAKESTMT( jdar_MAKE_BYTE_AVAIL(cinfo,action); \ + bytes_in_buffer--; \ + V = ((unsigned int) GETJOCTET(*next_input_byte++)) << 8; \ + jdar_MAKE_BYTE_AVAIL(cinfo,action); \ + bytes_in_buffer--; \ + V += GETJOCTET(*next_input_byte++); ) /* @@ -2106,7 +2136,7 @@ get_soi (j_decompress_ptr cinfo) /* Process an SOI marker */ { int i; - + TRACEMS(cinfo, 1, JTRC_SOI); if (cinfo->marker->saw_SOI) @@ -2163,8 +2193,8 @@ get_sof (j_decompress_ptr cinfo, boolean is_prog, boolean is_arith) length -= 8; TRACEMS4(cinfo, 1, JTRC_SOF, cinfo->unread_marker, - (int) cinfo->image_width, (int) cinfo->image_height, - cinfo->num_components); + (int) cinfo->image_width, (int) cinfo->image_height, + cinfo->num_components); if (cinfo->marker->saw_SOF) ERREXIT(cinfo, JERR_SOF_DUPLICATE); @@ -2179,11 +2209,11 @@ get_sof (j_decompress_ptr cinfo, boolean is_prog, boolean is_arith) if (length != (cinfo->num_components * 3)) ERREXIT(cinfo, JERR_BAD_LENGTH); - if (cinfo->comp_info == NULL) /* do only once, even if suspend */ + if (cinfo->comp_info == NULL) /* do only once, even if suspend */ cinfo->comp_info = (jpeg_component_info *) (*cinfo->mem->alloc_small) - ((j_common_ptr) cinfo, JPOOL_IMAGE, - cinfo->num_components * SIZEOF(jpeg_component_info)); - + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->num_components * SIZEOF(jpeg_component_info)); + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; ci++, compptr++) { compptr->component_index = ci; @@ -2194,8 +2224,8 @@ get_sof (j_decompress_ptr cinfo, boolean is_prog, boolean is_arith) jdar_INPUT_BYTE(cinfo, compptr->quant_tbl_no, return FALSE); TRACEMS4(cinfo, 1, JTRC_SOF_COMPONENT, - compptr->component_id, compptr->h_samp_factor, - compptr->v_samp_factor, compptr->quant_tbl_no); + compptr->component_id, compptr->h_samp_factor, + compptr->v_samp_factor, compptr->quant_tbl_no); } cinfo->marker->saw_SOF = TRUE; @@ -2233,11 +2263,11 @@ get_sos (j_decompress_ptr cinfo) for (i = 0; i < n; i++) { jdar_INPUT_BYTE(cinfo, cc, return FALSE); jdar_INPUT_BYTE(cinfo, c, return FALSE); - + for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; - ci++, compptr++) { + ci++, compptr++) { if (cc == compptr->component_id) - goto id_found; + goto id_found; } ERREXIT1(cinfo, JERR_BAD_COMPONENT_ID, cc); @@ -2247,9 +2277,9 @@ get_sos (j_decompress_ptr cinfo) cinfo->cur_comp_info[i] = compptr; compptr->dc_tbl_no = (c >> 4) & 15; compptr->ac_tbl_no = (c ) & 15; - + TRACEMS3(cinfo, 1, JTRC_SOS_COMPONENT, cc, - compptr->dc_tbl_no, compptr->ac_tbl_no); + compptr->dc_tbl_no, compptr->ac_tbl_no); } /* Collect the additional scan parameters Ss, Se, Ah/Al. */ @@ -2262,7 +2292,7 @@ get_sos (j_decompress_ptr cinfo) cinfo->Al = (c ) & 15; TRACEMS4(cinfo, 1, JTRC_SOS_PARAMS, cinfo->Ss, cinfo->Se, - cinfo->Ah, cinfo->Al); + cinfo->Ah, cinfo->Al); /* Prepare to scan data & restart markers */ cinfo->marker->next_restart_num = 0; @@ -2287,7 +2317,7 @@ get_dac (j_decompress_ptr cinfo) jdar_INPUT_2BYTES(cinfo, length, return FALSE); length -= 2; - + while (length > 0) { jdar_INPUT_BYTE(cinfo, index, return FALSE); jdar_INPUT_BYTE(cinfo, val, return FALSE); @@ -2301,11 +2331,11 @@ get_dac (j_decompress_ptr cinfo) if (index >= NUM_ARITH_TBLS) { /* define AC table */ cinfo->arith_ac_K[index-NUM_ARITH_TBLS] = (UINT8) val; - } else { /* define DC table */ + } else { /* define DC table */ cinfo->arith_dc_L[index] = (UINT8) (val & 0x0F); cinfo->arith_dc_U[index] = (UINT8) (val >> 4); if (cinfo->arith_dc_L[index] > cinfo->arith_dc_U[index]) - ERREXIT1(cinfo, JERR_DAC_VALUE, val); + ERREXIT1(cinfo, JERR_DAC_VALUE, val); } } @@ -2336,12 +2366,12 @@ get_dht (j_decompress_ptr cinfo) jdar_INPUT_2BYTES(cinfo, length, return FALSE); length -= 2; - + while (length > 16) { jdar_INPUT_BYTE(cinfo, index, return FALSE); TRACEMS1(cinfo, 1, JTRC_DHT, index); - + bits[0] = 0; count = 0; for (i = 1; i <= 16; i++) { @@ -2352,11 +2382,11 @@ get_dht (j_decompress_ptr cinfo) length -= 1 + 16; TRACEMS8(cinfo, 2, JTRC_HUFFBITS, - bits[1], bits[2], bits[3], bits[4], - bits[5], bits[6], bits[7], bits[8]); + bits[1], bits[2], bits[3], bits[4], + bits[5], bits[6], bits[7], bits[8]); TRACEMS8(cinfo, 2, JTRC_HUFFBITS, - bits[9], bits[10], bits[11], bits[12], - bits[13], bits[14], bits[15], bits[16]); + bits[9], bits[10], bits[11], bits[12], + bits[13], bits[14], bits[15], bits[16]); /* Here we just do minimal validation of the counts to avoid walking * off the end of our table space. jdhuff.c will check more carefully. @@ -2369,19 +2399,18 @@ get_dht (j_decompress_ptr cinfo) length -= count; - if (index & 0x10) { /* AC table definition */ + if (index & 0x10) { /* AC table definition */ index -= 0x10; htblptr = &cinfo->ac_huff_tbl_ptrs[index]; - } else { /* DC table definition */ + } else { /* DC table definition */ + if (index < 0 || index >= NUM_HUFF_TBLS) + ERREXIT1(cinfo, JERR_DHT_INDEX, index); htblptr = &cinfo->dc_huff_tbl_ptrs[index]; } - if (index < 0 || index >= NUM_HUFF_TBLS) - ERREXIT1(cinfo, JERR_DHT_INDEX, index); - if (*htblptr == NULL) *htblptr = jpeg_alloc_huff_table((j_common_ptr) cinfo); - + MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits)); MEMCOPY((*htblptr)->huffval, huffval, SIZEOF((*htblptr)->huffval)); } @@ -2416,27 +2445,27 @@ get_dqt (j_decompress_ptr cinfo) if (n >= NUM_QUANT_TBLS) ERREXIT1(cinfo, JERR_DQT_INDEX, n); - + if (cinfo->quant_tbl_ptrs[n] == NULL) cinfo->quant_tbl_ptrs[n] = jpeg_alloc_quant_table((j_common_ptr) cinfo); quant_ptr = cinfo->quant_tbl_ptrs[n]; for (i = 0; i < DCTSIZE2; i++) { if (prec) - jdar_INPUT_2BYTES(cinfo, tmp, return FALSE); + jdar_INPUT_2BYTES(cinfo, tmp, return FALSE); else - jdar_INPUT_BYTE(cinfo, tmp, return FALSE); + jdar_INPUT_BYTE(cinfo, tmp, return FALSE); /* We convert the zigzag-order table to natural array order. */ quant_ptr->quantval[jpeg_natural_order[i]] = (UINT16) tmp; } if (cinfo->err->trace_level >= 2) { for (i = 0; i < DCTSIZE2; i += 8) { - TRACEMS8(cinfo, 2, JTRC_QUANTVALS, - quant_ptr->quantval[i], quant_ptr->quantval[i+1], - quant_ptr->quantval[i+2], quant_ptr->quantval[i+3], - quant_ptr->quantval[i+4], quant_ptr->quantval[i+5], - quant_ptr->quantval[i+6], quant_ptr->quantval[i+7]); + TRACEMS8(cinfo, 2, JTRC_QUANTVALS, + quant_ptr->quantval[i], quant_ptr->quantval[i+1], + quant_ptr->quantval[i+2], quant_ptr->quantval[i+3], + quant_ptr->quantval[i+4], quant_ptr->quantval[i+5], + quant_ptr->quantval[i+6], quant_ptr->quantval[i+7]); } } @@ -2461,7 +2490,7 @@ get_dri (j_decompress_ptr cinfo) jdar_INPUT_VARS(cinfo); jdar_INPUT_2BYTES(cinfo, length, return FALSE); - + if (length != 4) ERREXIT(cinfo, JERR_BAD_LENGTH); @@ -2483,14 +2512,14 @@ get_dri (j_decompress_ptr cinfo) * JFIF and Adobe markers, respectively. */ -#define APP0_DATA_LEN 14 /* Length of interesting data in APP0 */ -#define APP14_DATA_LEN 12 /* Length of interesting data in APP14 */ -#define APPN_DATA_LEN 14 /* Must be the largest of the above!! */ +#define APP0_DATA_LEN 14 /* Length of interesting data in APP0 */ +#define APP14_DATA_LEN 12 /* Length of interesting data in APP14 */ +#define APPN_DATA_LEN 14 /* Must be the largest of the above!! */ LOCAL(void) examine_app0 (j_decompress_ptr cinfo, JOCTET FAR * data, - unsigned int datalen, INT32 remaining) + unsigned int datalen, INT32 remaining) /* Examine first few bytes from an APP0. * Take appropriate action if it is a JFIF marker. * datalen is # of bytes at data[], remaining is length of rest of marker data. @@ -2519,18 +2548,18 @@ examine_app0 (j_decompress_ptr cinfo, JOCTET FAR * data, */ if (cinfo->JFIF_major_version != 1) WARNMS2(cinfo, JWRN_JFIF_MAJOR, - cinfo->JFIF_major_version, cinfo->JFIF_minor_version); + cinfo->JFIF_major_version, cinfo->JFIF_minor_version); /* Generate trace messages */ TRACEMS5(cinfo, 1, JTRC_JFIF, - cinfo->JFIF_major_version, cinfo->JFIF_minor_version, - cinfo->X_density, cinfo->Y_density, cinfo->density_unit); + cinfo->JFIF_major_version, cinfo->JFIF_minor_version, + cinfo->X_density, cinfo->Y_density, cinfo->density_unit); /* Validate thumbnail dimensions and issue appropriate messages */ if (GETJOCTET(data[12]) | GETJOCTET(data[13])) TRACEMS2(cinfo, 1, JTRC_JFIF_THUMBNAIL, - GETJOCTET(data[12]), GETJOCTET(data[13])); + GETJOCTET(data[12]), GETJOCTET(data[13])); totallen -= APP0_DATA_LEN; if (totallen != - ((INT32)GETJOCTET(data[12]) * (INT32)GETJOCTET(data[13]) * (INT32) 3)) + ((INT32)GETJOCTET(data[12]) * (INT32)GETJOCTET(data[13]) * (INT32) 3)) TRACEMS1(cinfo, 1, JTRC_JFIF_BADTHUMBNAILSIZE, (int) totallen); } else if (datalen >= 6 && GETJOCTET(data[0]) == 0x4A && @@ -2554,7 +2583,7 @@ examine_app0 (j_decompress_ptr cinfo, JOCTET FAR * data, break; default: TRACEMS2(cinfo, 1, JTRC_JFIF_EXTENSION, - GETJOCTET(data[5]), (int) totallen); + GETJOCTET(data[5]), (int) totallen); break; } } else { @@ -2566,7 +2595,7 @@ examine_app0 (j_decompress_ptr cinfo, JOCTET FAR * data, LOCAL(void) examine_app14 (j_decompress_ptr cinfo, JOCTET FAR * data, - unsigned int datalen, INT32 remaining) + unsigned int datalen, INT32 remaining) /* Examine first few bytes from an APP14. * Take appropriate action if it is an Adobe marker. * datalen is # of bytes at data[], remaining is length of rest of marker data. @@ -2658,19 +2687,19 @@ save_marker (j_decompress_ptr cinfo) /* begin reading a marker */ jdar_INPUT_2BYTES(cinfo, length, return FALSE); length -= 2; - if (length >= 0) { /* watch out for bogus length word */ + if (length >= 0) { /* watch out for bogus length word */ /* figure out how much we want to save */ unsigned int limit; if (cinfo->unread_marker == (int) M_COM) - limit = marker->length_limit_COM; + limit = marker->length_limit_COM; else - limit = marker->length_limit_APPn[cinfo->unread_marker - (int) M_APP0]; + limit = marker->length_limit_APPn[cinfo->unread_marker - (int) M_APP0]; if ((unsigned int) length < limit) - limit = (unsigned int) length; + limit = (unsigned int) length; /* allocate and initialize the marker item */ cur_marker = (jpeg_saved_marker_ptr) - (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(struct jpeg_marker_struct) + limit); + (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, + SIZEOF(struct jpeg_marker_struct) + limit); cur_marker->next = NULL; cur_marker->marker = (UINT8) cinfo->unread_marker; cur_marker->original_length = (unsigned int) length; @@ -2694,7 +2723,7 @@ save_marker (j_decompress_ptr cinfo) } while (bytes_read < data_length) { - jdar_INPUT_SYNC(cinfo); /* move the restart point to here */ + jdar_INPUT_SYNC(cinfo); /* move the restart point to here */ marker->bytes_read = bytes_read; /* If there's not at least one byte in buffer, suspend */ jdar_MAKE_BYTE_AVAIL(cinfo, return FALSE); @@ -2707,14 +2736,14 @@ save_marker (j_decompress_ptr cinfo) } /* Done reading what we want to read */ - if (cur_marker != NULL) { /* will be NULL if bogus length word */ + if (cur_marker != NULL) { /* will be NULL if bogus length word */ /* Add new marker to end of list */ if (cinfo->marker_list == NULL) { cinfo->marker_list = cur_marker; } else { jpeg_saved_marker_ptr prev = cinfo->marker_list; while (prev->next != NULL) - prev = prev->next; + prev = prev->next; prev->next = cur_marker; } /* Reset pointer & calc remaining data length */ @@ -2734,12 +2763,12 @@ save_marker (j_decompress_ptr cinfo) break; default: TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, - (int) (data_length + length)); + (int) (data_length + length)); break; } /* skip any remaining data -- could be lots */ - jdar_INPUT_SYNC(cinfo); /* do before skip_input_data */ + jdar_INPUT_SYNC(cinfo); /* do before skip_input_data */ if (length > 0) (*cinfo->src->skip_input_data) (cinfo, (long) length); @@ -2758,10 +2787,10 @@ skip_variable (j_decompress_ptr cinfo) jdar_INPUT_2BYTES(cinfo, length, return FALSE); length -= 2; - + TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo->unread_marker, (int) length); - jdar_INPUT_SYNC(cinfo); /* do before skip_input_data */ + jdar_INPUT_SYNC(cinfo); /* do before skip_input_data */ if (length > 0) (*cinfo->src->skip_input_data) (cinfo, (long) length); @@ -2805,7 +2834,7 @@ next_marker (j_decompress_ptr cinfo) jdar_INPUT_BYTE(cinfo, c, return FALSE); } while (c == 0xFF); if (c != 0) - break; /* found a valid marker, exit loop */ + break; /* found a valid marker, exit loop */ /* Reach here if we found a stuffed-zero data sequence (FF/00). * Discard it and loop back to try again. */ @@ -2865,11 +2894,11 @@ read_markers (j_decompress_ptr cinfo) /* NB: first_marker() enforces the requirement that SOI appear first. */ if (cinfo->unread_marker == 0) { if (! cinfo->marker->saw_SOI) { - if (! first_marker(cinfo)) - return JPEG_SUSPENDED; + if (! first_marker(cinfo)) + return JPEG_SUSPENDED; } else { - if (! next_marker(cinfo)) - return JPEG_SUSPENDED; + if (! next_marker(cinfo)) + return JPEG_SUSPENDED; } } /* At this point cinfo->unread_marker contains the marker code and the @@ -2879,74 +2908,74 @@ read_markers (j_decompress_ptr cinfo) switch (cinfo->unread_marker) { case M_SOI: if (! get_soi(cinfo)) - return JPEG_SUSPENDED; + return JPEG_SUSPENDED; break; - case M_SOF0: /* Baseline */ - case M_SOF1: /* Extended sequential, Huffman */ + case M_SOF0: /* Baseline */ + case M_SOF1: /* Extended sequential, Huffman */ if (! get_sof(cinfo, FALSE, FALSE)) - return JPEG_SUSPENDED; + return JPEG_SUSPENDED; break; - case M_SOF2: /* Progressive, Huffman */ + case M_SOF2: /* Progressive, Huffman */ if (! get_sof(cinfo, TRUE, FALSE)) - return JPEG_SUSPENDED; + return JPEG_SUSPENDED; break; - case M_SOF9: /* Extended sequential, arithmetic */ + case M_SOF9: /* Extended sequential, arithmetic */ if (! get_sof(cinfo, FALSE, TRUE)) - return JPEG_SUSPENDED; + return JPEG_SUSPENDED; break; - case M_SOF10: /* Progressive, arithmetic */ + case M_SOF10: /* Progressive, arithmetic */ if (! get_sof(cinfo, TRUE, TRUE)) - return JPEG_SUSPENDED; + return JPEG_SUSPENDED; break; /* Currently unsupported SOFn types */ - case M_SOF3: /* Lossless, Huffman */ - case M_SOF5: /* Differential sequential, Huffman */ - case M_SOF6: /* Differential progressive, Huffman */ - case M_SOF7: /* Differential lossless, Huffman */ - case M_JPG: /* Reserved for JPEG extensions */ - case M_SOF11: /* Lossless, arithmetic */ - case M_SOF13: /* Differential sequential, arithmetic */ - case M_SOF14: /* Differential progressive, arithmetic */ - case M_SOF15: /* Differential lossless, arithmetic */ + case M_SOF3: /* Lossless, Huffman */ + case M_SOF5: /* Differential sequential, Huffman */ + case M_SOF6: /* Differential progressive, Huffman */ + case M_SOF7: /* Differential lossless, Huffman */ + case M_JPG: /* Reserved for JPEG extensions */ + case M_SOF11: /* Lossless, arithmetic */ + case M_SOF13: /* Differential sequential, arithmetic */ + case M_SOF14: /* Differential progressive, arithmetic */ + case M_SOF15: /* Differential lossless, arithmetic */ ERREXIT1(cinfo, JERR_SOF_UNSUPPORTED, cinfo->unread_marker); break; case M_SOS: if (! get_sos(cinfo)) - return JPEG_SUSPENDED; - cinfo->unread_marker = 0; /* processed the marker */ + return JPEG_SUSPENDED; + cinfo->unread_marker = 0; /* processed the marker */ return JPEG_REACHED_SOS; - + case M_EOI: TRACEMS(cinfo, 1, JTRC_EOI); - cinfo->unread_marker = 0; /* processed the marker */ + cinfo->unread_marker = 0; /* processed the marker */ return JPEG_REACHED_EOI; - + case M_DAC: if (! jdar_get_dac(cinfo)) - return JPEG_SUSPENDED; + return JPEG_SUSPENDED; break; - + case M_DHT: if (! get_dht(cinfo)) - return JPEG_SUSPENDED; + return JPEG_SUSPENDED; break; - + case M_DQT: if (! get_dqt(cinfo)) - return JPEG_SUSPENDED; + return JPEG_SUSPENDED; break; - + case M_DRI: if (! get_dri(cinfo)) - return JPEG_SUSPENDED; + return JPEG_SUSPENDED; break; - + case M_APP0: case M_APP1: case M_APP2: @@ -2964,16 +2993,16 @@ read_markers (j_decompress_ptr cinfo) case M_APP14: case M_APP15: if (! (*((my_marker_ptr) cinfo->marker)->process_APPn[ - cinfo->unread_marker - (int) M_APP0]) (cinfo)) - return JPEG_SUSPENDED; + cinfo->unread_marker - (int) M_APP0]) (cinfo)) + return JPEG_SUSPENDED; break; - + case M_COM: if (! (*((my_marker_ptr) cinfo->marker)->process_COM) (cinfo)) - return JPEG_SUSPENDED; + return JPEG_SUSPENDED; break; - case M_RST0: /* these are all parameterless */ + case M_RST0: /* these are all parameterless */ case M_RST1: case M_RST2: case M_RST3: @@ -2985,12 +3014,12 @@ read_markers (j_decompress_ptr cinfo) TRACEMS1(cinfo, 1, JTRC_PARMLESS_MARKER, cinfo->unread_marker); break; - case M_DNL: /* Ignore DNL ... perhaps the wrong thing */ + case M_DNL: /* Ignore DNL ... perhaps the wrong thing */ if (! skip_variable(cinfo)) - return JPEG_SUSPENDED; + return JPEG_SUSPENDED; break; - default: /* must be DHP, EXP, JPGn, or RESn */ + default: /* must be DHP, EXP, JPGn, or RESn */ /* For now, we treat the reserved markers as fatal errors since they are * likely to be used to signal incompatible JPEG Part 3 extensions. * Once the JPEG 3 version-number marker is well defined, this code @@ -3036,7 +3065,7 @@ read_restart_marker (j_decompress_ptr cinfo) /* Uh-oh, the restart markers have been messed up. */ /* Let the data source manager determine how to resync. */ if (! (*cinfo->src->resync_to_restart) (cinfo, - cinfo->marker->next_restart_num)) + cinfo->marker->next_restart_num)) return FALSE; } @@ -3101,25 +3130,25 @@ jpeg_resync_to_restart (j_decompress_ptr cinfo, int desired) { int marker = cinfo->unread_marker; int action = 1; - + /* Always put up a warning. */ WARNMS2(cinfo, JWRN_MUST_RESYNC, marker, desired); - + /* Outer loop handles repeated decision after scanning forward. */ for (;;) { if (marker < (int) M_SOF0) - action = 2; /* invalid marker */ + action = 2; /* invalid marker */ else if (marker < (int) M_RST0 || marker > (int) M_RST7) - action = 3; /* valid non-restart marker */ + action = 3; /* valid non-restart marker */ else { if (marker == ((int) M_RST0 + ((desired+1) & 7)) || - marker == ((int) M_RST0 + ((desired+2) & 7))) - action = 3; /* one of the next two expected restarts */ + marker == ((int) M_RST0 + ((desired+2) & 7))) + action = 3; /* one of the next two expected restarts */ else if (marker == ((int) M_RST0 + ((desired-1) & 7)) || - marker == ((int) M_RST0 + ((desired-2) & 7))) - action = 2; /* a prior restart, so advance */ + marker == ((int) M_RST0 + ((desired-2) & 7))) + action = 2; /* a prior restart, so advance */ else - action = 1; /* desired restart or too far away */ + action = 1; /* desired restart or too far away */ } TRACEMS2(cinfo, 4, JTRC_RECOVERY_ACTION, marker, action); switch (action) { @@ -3130,7 +3159,7 @@ jpeg_resync_to_restart (j_decompress_ptr cinfo, int desired) case 2: /* Scan to the next marker, and repeat the decision loop. */ if (! next_marker(cinfo)) - return FALSE; + return FALSE; marker = cinfo->unread_marker; break; case 3: @@ -3151,10 +3180,10 @@ reset_marker_reader (j_decompress_ptr cinfo) { my_marker_ptr marker = (my_marker_ptr) cinfo->marker; - cinfo->comp_info = NULL; /* until allocated by get_sof */ - cinfo->input_scan_number = 0; /* no SOS seen yet */ - cinfo->unread_marker = 0; /* no pending marker */ - marker->pub.saw_SOI = FALSE; /* set internal state too */ + cinfo->comp_info = NULL; /* until allocated by get_sof */ + cinfo->input_scan_number = 0; /* no SOS seen yet */ + cinfo->unread_marker = 0; /* no pending marker */ + marker->pub.saw_SOI = FALSE; /* set internal state too */ marker->pub.saw_SOF = FALSE; marker->pub.discarded_bytes = 0; marker->cur_marker = NULL; @@ -3175,7 +3204,7 @@ jinit_marker_reader (j_decompress_ptr cinfo) /* Create subobject in permanent pool */ marker = (my_marker_ptr) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, - SIZEOF(my_marker_reader)); + SIZEOF(my_marker_reader)); cinfo->marker = (struct jpeg_marker_reader *) marker; /* Initialize public method pointers */ marker->pub.reset_marker_reader = reset_marker_reader; @@ -3206,7 +3235,7 @@ jinit_marker_reader (j_decompress_ptr cinfo) GLOBAL(void) jpeg_save_markers (j_decompress_ptr cinfo, int marker_code, - unsigned int length_limit) + unsigned int length_limit) { my_marker_ptr marker = (my_marker_ptr) cinfo->marker; long maxlength; @@ -3255,7 +3284,7 @@ jpeg_save_markers (j_decompress_ptr cinfo, int marker_code, GLOBAL(void) jpeg_set_marker_processor (j_decompress_ptr cinfo, int marker_code, - jpeg_marker_parser_method routine) + jpeg_marker_parser_method routine) { my_marker_ptr marker = (my_marker_ptr) cinfo->marker; @@ -3285,7 +3314,7 @@ jpeg_set_marker_processor (j_decompress_ptr cinfo, int marker_code, #define JPEG_INTERNALS //#include "jinclude.h" //#include "jpeglib.h" -//#include "jdhuff.h" /* Declarations shared with jdphuff.c */ +//#include "jdhuff.h" /* Declarations shared with jdphuff.c */ /* @@ -3309,10 +3338,10 @@ typedef struct { #else #if MAX_COMPS_IN_SCAN == 4 #define jdu_jdu_ASSIGN_STATE(dest,src) \ - ((dest).last_dc_val[0] = (src).last_dc_val[0], \ - (dest).last_dc_val[1] = (src).last_dc_val[1], \ - (dest).last_dc_val[2] = (src).last_dc_val[2], \ - (dest).last_dc_val[3] = (src).last_dc_val[3]) + ((dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) #endif #endif @@ -3323,11 +3352,11 @@ typedef struct { /* These fields are loaded into local variables at start of each MCU. * In case of suspension, we exit WITHOUT updating them. */ - bitread_perm_state bitstate; /* Bit buffer at start of MCU */ - savable_state saved; /* Other state at start of MCU */ + bitread_perm_state bitstate; /* Bit buffer at start of MCU */ + savable_state saved; /* Other state at start of MCU */ /* These fields are NOT loaded into local working state. */ - unsigned int restarts_to_go; /* MCUs left in this restart interval */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ /* Pointers to derived tables (these workspaces have image lifespan) */ d_derived_tbl * dc_derived_tbls[NUM_HUFF_TBLS]; @@ -3372,9 +3401,9 @@ start_pass_huff_decoder (j_decompress_ptr cinfo) /* Compute derived values for Huffman tables */ /* We may do this more than once for a table, but it's not expensive */ jpeg_make_d_derived_tbl(cinfo, TRUE, dctbl, - & entropy->dc_derived_tbls[dctbl]); + & entropy->dc_derived_tbls[dctbl]); jpeg_make_d_derived_tbl(cinfo, FALSE, actbl, - & entropy->ac_derived_tbls[actbl]); + & entropy->ac_derived_tbls[actbl]); /* Initialize DC predictions to 0 */ entropy->saved.last_dc_val[ci] = 0; } @@ -3415,7 +3444,7 @@ start_pass_huff_decoder (j_decompress_ptr cinfo) GLOBAL(void) jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, boolean isDC, int tblno, - d_derived_tbl ** pdtbl) + d_derived_tbl ** pdtbl) { JHUFF_TBL *htbl; d_derived_tbl *dtbl; @@ -3441,26 +3470,26 @@ jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, boolean isDC, int tblno, if (*pdtbl == NULL) *pdtbl = (d_derived_tbl *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(d_derived_tbl)); + SIZEOF(d_derived_tbl)); dtbl = *pdtbl; - dtbl->pub = htbl; /* fill in back link */ - + dtbl->pub = htbl; /* fill in back link */ + /* Figure C.1: make table of Huffman code length for each symbol */ p = 0; for (l = 1; l <= 16; l++) { i = (int) htbl->bits[l]; - if (i < 0 || p + i > 256) /* protect against table overrun */ + if (i < 0 || p + i > 256) /* protect against table overrun */ ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); while (i--) huffsize[p++] = (char) l; } huffsize[p] = 0; numsymbols = p; - + /* Figure C.2: generate the codes themselves */ /* We also validate that the counts represent a legal Huffman code tree. */ - + code = 0; si = huffsize[0]; p = 0; @@ -3490,7 +3519,7 @@ jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, boolean isDC, int tblno, p += htbl->bits[l]; dtbl->maxcode[l] = huffcode[p-1]; /* maximum code of length l */ } else { - dtbl->maxcode[l] = -1; /* -1 if no codes of this length */ + dtbl->maxcode[l] = -1; /* -1 if no codes of this length */ } } dtbl->maxcode[17] = 0xFFFFFL; /* ensures jpeg_huff_decode terminates */ @@ -3511,9 +3540,9 @@ jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, boolean isDC, int tblno, /* Generate left-justified code followed by all possible bit sequences */ lookbits = huffcode[p] << (HUFF_LOOKAHEAD-l); for (ctr = 1 << (HUFF_LOOKAHEAD-l); ctr > 0; ctr--) { - dtbl->look_nbits[lookbits] = l; - dtbl->look_sym[lookbits] = htbl->huffval[p]; - lookbits++; + dtbl->look_nbits[lookbits] = l; + dtbl->look_sym[lookbits] = htbl->huffval[p]; + lookbits++; } } } @@ -3528,7 +3557,7 @@ jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, boolean isDC, int tblno, for (i = 0; i < numsymbols; i++) { int sym = htbl->huffval[i]; if (sym < 0 || sym > 15) - ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); } } } @@ -3550,7 +3579,7 @@ jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, boolean isDC, int tblno, */ #ifdef SLOW_SHIFT_32 -#define MIN_GET_BITS 15 /* minimum allowable value */ +#define MIN_GET_BITS 15 /* minimum allowable value */ #else #define MIN_GET_BITS (BIT_BUF_SIZE-7) #endif @@ -3558,67 +3587,67 @@ jpeg_make_d_derived_tbl (j_decompress_ptr cinfo, boolean isDC, int tblno, GLOBAL(boolean) jpeg_fill_bit_buffer (bitread_working_state * state, - register bit_buf_type get_buffer, register int bits_left, - int nbits) + bit_buf_type get_buffer, int bits_left, + int nbits) /* Load up the bit buffer to a depth of at least nbits */ { /* Copy heavily used state fields into locals (hopefully registers) */ - register const JOCTET * next_input_byte = state->next_input_byte; - register size_t bytes_in_buffer = state->bytes_in_buffer; + const JOCTET * next_input_byte = state->next_input_byte; + size_t bytes_in_buffer = state->bytes_in_buffer; j_decompress_ptr cinfo = state->cinfo; /* Attempt to load at least MIN_GET_BITS bits into get_buffer. */ /* (It is assumed that no request will be for more than that many bits.) */ /* We fail to do so only if we hit a marker or are forced to suspend. */ - if (cinfo->unread_marker == 0) { /* cannot advance past a marker */ + if (cinfo->unread_marker == 0) { /* cannot advance past a marker */ while (bits_left < MIN_GET_BITS) { - register int c; + int c; /* Attempt to read a byte */ if (bytes_in_buffer == 0) { - if (! (*cinfo->src->fill_input_buffer) (cinfo)) - return FALSE; - next_input_byte = cinfo->src->next_input_byte; - bytes_in_buffer = cinfo->src->bytes_in_buffer; + if (! (*cinfo->src->fill_input_buffer) (cinfo)) + return FALSE; + next_input_byte = cinfo->src->next_input_byte; + bytes_in_buffer = cinfo->src->bytes_in_buffer; } bytes_in_buffer--; c = GETJOCTET(*next_input_byte++); /* If it's 0xFF, check and discard stuffed zero byte */ if (c == 0xFF) { - /* Loop here to discard any padding FF's on terminating marker, - * so that we can save a valid unread_marker value. NOTE: we will - * accept multiple FF's followed by a 0 as meaning a single FF data - * byte. This data pattern is not valid according to the standard. - */ - do { - if (bytes_in_buffer == 0) { - if (! (*cinfo->src->fill_input_buffer) (cinfo)) - return FALSE; - next_input_byte = cinfo->src->next_input_byte; - bytes_in_buffer = cinfo->src->bytes_in_buffer; - } - bytes_in_buffer--; - c = GETJOCTET(*next_input_byte++); - } while (c == 0xFF); - - if (c == 0) { - /* Found FF/00, which represents an FF data byte */ - c = 0xFF; - } else { - /* Oops, it's actually a marker indicating end of compressed data. - * Save the marker code for later use. - * Fine point: it might appear that we should save the marker into - * bitread working state, not straight into permanent state. But - * once we have hit a marker, we cannot need to suspend within the - * current MCU, because we will read no more bytes from the data - * source. So it is OK to update permanent state right away. - */ - cinfo->unread_marker = c; - /* See if we need to insert some fake zero bits. */ - goto no_more_bytes; - } + /* Loop here to discard any padding FF's on terminating marker, + * so that we can save a valid unread_marker value. NOTE: we will + * accept multiple FF's followed by a 0 as meaning a single FF data + * byte. This data pattern is not valid according to the standard. + */ + do { + if (bytes_in_buffer == 0) { + if (! (*cinfo->src->fill_input_buffer) (cinfo)) + return FALSE; + next_input_byte = cinfo->src->next_input_byte; + bytes_in_buffer = cinfo->src->bytes_in_buffer; + } + bytes_in_buffer--; + c = GETJOCTET(*next_input_byte++); + } while (c == 0xFF); + + if (c == 0) { + /* Found FF/00, which represents an FF data byte */ + c = 0xFF; + } else { + /* Oops, it's actually a marker indicating end of compressed data. + * Save the marker code for later use. + * Fine point: it might appear that we should save the marker into + * bitread working state, not straight into permanent state. But + * once we have hit a marker, we cannot need to suspend within the + * current MCU, because we will read no more bytes from the data + * source. So it is OK to update permanent state right away. + */ + cinfo->unread_marker = c; + /* See if we need to insert some fake zero bits. */ + goto no_more_bytes; + } } /* OK, load c into get_buffer */ @@ -3638,8 +3667,8 @@ jpeg_fill_bit_buffer (bitread_working_state * state, * appears per data segment. */ if (! cinfo->entropy->insufficient_data) { - WARNMS(cinfo, JWRN_HIT_MARKER); - cinfo->entropy->insufficient_data = TRUE; + WARNMS(cinfo, JWRN_HIT_MARKER); + cinfo->entropy->insufficient_data = TRUE; } /* Fill the buffer with zero bits */ get_buffer <<= MIN_GET_BITS - bits_left; @@ -3664,11 +3693,11 @@ jpeg_fill_bit_buffer (bitread_working_state * state, GLOBAL(int) jpeg_huff_decode (bitread_working_state * state, - register bit_buf_type get_buffer, register int bits_left, - d_derived_tbl * htbl, int min_bits) + bit_buf_type get_buffer, int bits_left, + d_derived_tbl * htbl, int min_bits) { - register int l = min_bits; - register INT32 code; + int l = min_bits; + INT32 code; /* HUFF_DECODE has determined that the code is at least min_bits */ /* bits long, so fetch that many bits in one swoop. */ @@ -3694,7 +3723,7 @@ jpeg_huff_decode (bitread_working_state * state, if (l > 16) { WARNMS(state->cinfo, JWRN_HUFF_BAD_CODE); - return 0; /* fake a zero as the safest result */ + return 0; /* fake a zero as the safest result */ } return htbl->pub->huffval[ (int) (code + htbl->valoffset[l]) ]; @@ -3719,10 +3748,10 @@ static const int extend_test[16] = /* entry n is 2**(n-1) */ 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; static const int extend_offset[16] = /* entry n is (-1 << n) + 1 */ - { 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1, - ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1, - ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1, - ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 }; + { 0, -(1<<1) + 1, -(1<<2) + 1, -(1<<3) + 1, -(1<<4) + 1, + -(1<<5) + 1, -(1<<6) + 1, -(1<<7) + 1, -(1<<8) + 1, + -(1<<9) + 1, -(1<<10) + 1, -(1<<11) + 1, -(1<<12) + 1, + -(1<<13) + 1, -(1<<14) + 1, -(1<<15) + 1 }; #endif /* AVOID_TABLES */ @@ -3793,7 +3822,7 @@ decode_mcu (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) if (cinfo->restart_interval) { if (entropy->restarts_to_go == 0) if (! process_restart(cinfo)) - return FALSE; + return FALSE; } /* If we've run out of data, just leave the MCU set to zeroes. @@ -3811,74 +3840,74 @@ decode_mcu (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) JBLOCKROW block = MCU_data[blkn]; d_derived_tbl * dctbl = entropy->dc_cur_tbls[blkn]; d_derived_tbl * actbl = entropy->ac_cur_tbls[blkn]; - register int s, k, r; + int s, k, r; /* Decode a single block's worth of coefficients */ /* Section F.2.2.1: decode the DC coefficient difference */ HUFF_DECODE(s, br_state, dctbl, return FALSE, label1); if (s) { - CHECK_BIT_BUFFER(br_state, s, return FALSE); - r = GET_BITS(s); - s = jdu_jdu_HUFF_EXTEND(r, s); + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = jdu_jdu_HUFF_EXTEND(r, s); } if (entropy->dc_needed[blkn]) { - /* Convert DC difference to actual value, update last_dc_val */ - int ci = cinfo->MCU_membership[blkn]; - s += state.last_dc_val[ci]; - state.last_dc_val[ci] = s; - /* Output the DC coefficient (assumes jpeg_natural_order[0] = 0) */ - (*block)[0] = (JCOEF) s; + /* Convert DC difference to actual value, update last_dc_val */ + int ci = cinfo->MCU_membership[blkn]; + s += state.last_dc_val[ci]; + state.last_dc_val[ci] = s; + /* Output the DC coefficient (assumes jpeg_natural_order[0] = 0) */ + (*block)[0] = (JCOEF) s; } if (entropy->ac_needed[blkn]) { - /* Section F.2.2.2: decode the AC coefficients */ - /* Since zeroes are skipped, output area must be cleared beforehand */ - for (k = 1; k < DCTSIZE2; k++) { - HUFF_DECODE(s, br_state, actbl, return FALSE, label2); - - r = s >> 4; - s &= 15; - - if (s) { - k += r; - CHECK_BIT_BUFFER(br_state, s, return FALSE); - r = GET_BITS(s); - s = jdu_jdu_HUFF_EXTEND(r, s); - /* Output coefficient in natural (dezigzagged) order. - * Note: the extra entries in jpeg_natural_order[] will save us - * if k >= DCTSIZE2, which could happen if the data is corrupted. - */ - (*block)[jpeg_natural_order[k]] = (JCOEF) s; - } else { - if (r != 15) - break; - k += 15; - } - } + /* Section F.2.2.2: decode the AC coefficients */ + /* Since zeroes are skipped, output area must be cleared beforehand */ + for (k = 1; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, actbl, return FALSE, label2); + + r = s >> 4; + s &= 15; + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = jdu_jdu_HUFF_EXTEND(r, s); + /* Output coefficient in natural (dezigzagged) order. + * Note: the extra entries in jpeg_natural_order[] will save us + * if k >= DCTSIZE2, which could happen if the data is corrupted. + */ + (*block)[jpeg_natural_order[k]] = (JCOEF) s; } else { + if (r != 15) + break; + k += 15; + } + } - /* Section F.2.2.2: decode the AC coefficients */ - /* In this path we just discard the values */ - for (k = 1; k < DCTSIZE2; k++) { - HUFF_DECODE(s, br_state, actbl, return FALSE, label3); - - r = s >> 4; - s &= 15; - - if (s) { - k += r; - CHECK_BIT_BUFFER(br_state, s, return FALSE); - DROP_BITS(s); - } else { - if (r != 15) - break; - k += 15; - } - } + } else { + + /* Section F.2.2.2: decode the AC coefficients */ + /* In this path we just discard the values */ + for (k = 1; k < DCTSIZE2; k++) { + HUFF_DECODE(s, br_state, actbl, return FALSE, label3); + + r = s >> 4; + s &= 15; + + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + DROP_BITS(s); + } else { + if (r != 15) + break; + k += 15; + } + } } } @@ -3907,7 +3936,7 @@ jinit_huff_decoder (j_decompress_ptr cinfo) entropy = (huff_entropy_ptr) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(huff_entropy_decoder)); + SIZEOF(huff_entropy_decoder)); cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; entropy->pub.start_pass = start_pass_huff_decoder; entropy->pub.decode_mcu = decode_mcu; @@ -4036,39 +4065,39 @@ typedef struct { /* Pointer to allocated workspace (M or M+2 row groups). */ JSAMPARRAY buffer[MAX_COMPONENTS]; - boolean buffer_full; /* Have we gotten an iMCU row from decoder? */ - JDIMENSION rowgroup_ctr; /* counts row groups output to postprocessor */ + boolean buffer_full; /* Have we gotten an iMCU row from decoder? */ + JDIMENSION rowgroup_ctr; /* counts row groups output to postprocessor */ /* Remaining fields are only used in the context case. */ /* These are the master pointers to the funny-order pointer lists. */ - JSAMPIMAGE xbuffer[2]; /* pointers to weird pointer lists */ + JSAMPIMAGE xbuffer[2]; /* pointers to weird pointer lists */ - int whichptr; /* indicates which pointer set is now in use */ - int context_state; /* process_data state machine status */ - JDIMENSION rowgroups_avail; /* row groups available to postprocessor */ - JDIMENSION iMCU_row_ctr; /* counts iMCU rows to detect image top/bot */ + int whichptr; /* indicates which pointer set is now in use */ + int context_state; /* process_data state machine status */ + JDIMENSION rowgroups_avail; /* row groups available to postprocessor */ + JDIMENSION iMCU_row_ctr; /* counts iMCU rows to detect image top/bot */ } my_main_controller; typedef my_main_controller * my_main_ptr; /* context_state values: */ -#define CTX_PREPARE_FOR_IMCU 0 /* need to prepare for MCU row */ -#define CTX_PROCESS_IMCU 1 /* feeding iMCU to postprocessor */ -#define CTX_POSTPONED_ROW 2 /* feeding postponed row group */ +#define CTX_PREPARE_FOR_IMCU 0 /* need to prepare for MCU row */ +#define CTX_PROCESS_IMCU 1 /* feeding iMCU to postprocessor */ +#define CTX_POSTPONED_ROW 2 /* feeding postponed row group */ /* Forward declarations */ METHODDEF(void) process_data_simple_main - JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, - JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); METHODDEF(void) process_data_context_main - JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, - JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); #ifdef QUANT_2PASS_SUPPORTED METHODDEF(void) process_data_crank_post - JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, - JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); + JPP((j_decompress_ptr cinfo, JSAMPARRAY output_buf, + JDIMENSION *out_row_ctr, JDIMENSION out_rows_avail)); #endif @@ -4078,7 +4107,7 @@ alloc_funny_pointers (j_decompress_ptr cinfo) * This is done only once, not once per pass. */ { - my_main_ptr main = (my_main_ptr) cinfo->main; + my_main_ptr main_ptr = (my_main_ptr) cinfo->main_ptr; int ci, rgroup; int M = cinfo->min_DCT_scaled_size; jpeg_component_info *compptr; @@ -4087,10 +4116,10 @@ alloc_funny_pointers (j_decompress_ptr cinfo) /* Get top-level space for component array pointers. * We alloc both arrays with one call to save a few cycles. */ - main->xbuffer[0] = (JSAMPIMAGE) + main_ptr->xbuffer[0] = (JSAMPIMAGE) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - cinfo->num_components * 2 * SIZEOF(JSAMPARRAY)); - main->xbuffer[1] = main->xbuffer[0] + cinfo->num_components; + cinfo->num_components * 2 * SIZEOF(JSAMPARRAY)); + main_ptr->xbuffer[1] = main_ptr->xbuffer[0] + cinfo->num_components; for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; ci++, compptr++) { @@ -4101,11 +4130,11 @@ alloc_funny_pointers (j_decompress_ptr cinfo) */ xbuf = (JSAMPARRAY) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - 2 * (rgroup * (M + 4)) * SIZEOF(JSAMPROW)); - xbuf += rgroup; /* want one row group at negative offsets */ - main->xbuffer[0][ci] = xbuf; + 2 * (rgroup * (M + 4)) * SIZEOF(JSAMPROW)); + xbuf += rgroup; /* want one row group at negative offsets */ + main_ptr->xbuffer[0][ci] = xbuf; xbuf += rgroup * (M + 4); - main->xbuffer[1][ci] = xbuf; + main_ptr->xbuffer[1][ci] = xbuf; } } @@ -4113,13 +4142,13 @@ alloc_funny_pointers (j_decompress_ptr cinfo) LOCAL(void) make_funny_pointers (j_decompress_ptr cinfo) /* Create the funny pointer lists discussed in the comments above. - * The actual workspace is already allocated (in main->buffer), + * The actual workspace is already allocated (in main_ptr->buffer), * and the space for the pointer lists is allocated too. * This routine just fills in the curiously ordered lists. * This will be repeated at the beginning of each pass. */ { - my_main_ptr main = (my_main_ptr) cinfo->main; + my_main_ptr main_ptr = (my_main_ptr) cinfo->main_ptr; int ci, i, rgroup; int M = cinfo->min_DCT_scaled_size; jpeg_component_info *compptr; @@ -4129,10 +4158,10 @@ make_funny_pointers (j_decompress_ptr cinfo) ci++, compptr++) { rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / cinfo->min_DCT_scaled_size; /* height of a row group of component */ - xbuf0 = main->xbuffer[0][ci]; - xbuf1 = main->xbuffer[1][ci]; + xbuf0 = main_ptr->xbuffer[0][ci]; + xbuf1 = main_ptr->xbuffer[1][ci]; /* First copy the workspace pointers as-is */ - buf = main->buffer[ci]; + buf = main_ptr->buffer[ci]; for (i = 0; i < rgroup * (M + 2); i++) { xbuf0[i] = xbuf1[i] = buf[i]; } @@ -4159,7 +4188,7 @@ set_wraparound_pointers (j_decompress_ptr cinfo) * This changes the pointer list state from top-of-image to the normal state. */ { - my_main_ptr main = (my_main_ptr) cinfo->main; + my_main_ptr main_ptr = (my_main_ptr) cinfo->main_ptr; int ci, i, rgroup; int M = cinfo->min_DCT_scaled_size; jpeg_component_info *compptr; @@ -4169,8 +4198,8 @@ set_wraparound_pointers (j_decompress_ptr cinfo) ci++, compptr++) { rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / cinfo->min_DCT_scaled_size; /* height of a row group of component */ - xbuf0 = main->xbuffer[0][ci]; - xbuf1 = main->xbuffer[1][ci]; + xbuf0 = main_ptr->xbuffer[0][ci]; + xbuf1 = main_ptr->xbuffer[1][ci]; for (i = 0; i < rgroup; i++) { xbuf0[i - rgroup] = xbuf0[rgroup*(M+1) + i]; xbuf1[i - rgroup] = xbuf1[rgroup*(M+1) + i]; @@ -4188,7 +4217,7 @@ set_bottom_pointers (j_decompress_ptr cinfo) * Also sets rowgroups_avail to indicate number of nondummy row groups in row. */ { - my_main_ptr main = (my_main_ptr) cinfo->main; + my_main_ptr main_ptr = (my_main_ptr) cinfo->main_ptr; int ci, i, rgroup, iMCUheight, rows_left; jpeg_component_info *compptr; JSAMPARRAY xbuf; @@ -4205,12 +4234,12 @@ set_bottom_pointers (j_decompress_ptr cinfo) * so we need only do it once. */ if (ci == 0) { - main->rowgroups_avail = (JDIMENSION) ((rows_left-1) / rgroup + 1); + main_ptr->rowgroups_avail = (JDIMENSION) ((rows_left-1) / rgroup + 1); } /* Duplicate the last real sample row rgroup*2 times; this pads out the * last partial rowgroup and ensures at least one full rowgroup of context. */ - xbuf = main->xbuffer[main->whichptr][ci]; + xbuf = main_ptr->xbuffer[main_ptr->whichptr][ci]; for (i = 0; i < rgroup * 2; i++) { xbuf[rows_left + i] = xbuf[rows_left-1]; } @@ -4225,27 +4254,27 @@ set_bottom_pointers (j_decompress_ptr cinfo) METHODDEF(void) start_pass_main (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) { - my_main_ptr main = (my_main_ptr) cinfo->main; + my_main_ptr main_ptr = (my_main_ptr) cinfo->main_ptr; switch (pass_mode) { case JBUF_PASS_THRU: if (cinfo->upsample->need_context_rows) { - main->pub.process_data = process_data_context_main; + main_ptr->pub.process_data = process_data_context_main; make_funny_pointers(cinfo); /* Create the xbuffer[] lists */ - main->whichptr = 0; /* Read first iMCU row into xbuffer[0] */ - main->context_state = CTX_PREPARE_FOR_IMCU; - main->iMCU_row_ctr = 0; + main_ptr->whichptr = 0; /* Read first iMCU row into xbuffer[0] */ + main_ptr->context_state = CTX_PREPARE_FOR_IMCU; + main_ptr->iMCU_row_ctr = 0; } else { /* Simple case with no context needed */ - main->pub.process_data = process_data_simple_main; + main_ptr->pub.process_data = process_data_simple_main; } - main->buffer_full = FALSE; /* Mark buffer empty */ - main->rowgroup_ctr = 0; + main_ptr->buffer_full = FALSE; /* Mark buffer empty */ + main_ptr->rowgroup_ctr = 0; break; #ifdef QUANT_2PASS_SUPPORTED case JBUF_CRANK_DEST: /* For last pass of 2-pass quantization, just crank the postprocessor */ - main->pub.process_data = process_data_crank_post; + main_ptr->pub.process_data = process_data_crank_post; break; #endif default: @@ -4262,17 +4291,17 @@ start_pass_main (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) METHODDEF(void) process_data_simple_main (j_decompress_ptr cinfo, - JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, - JDIMENSION out_rows_avail) + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) { - my_main_ptr main = (my_main_ptr) cinfo->main; + my_main_ptr main_ptr = (my_main_ptr) cinfo->main_ptr; JDIMENSION rowgroups_avail; /* Read input data if we haven't filled the main buffer yet */ - if (! main->buffer_full) { - if (! (*cinfo->coef->decompress_data) (cinfo, main->buffer)) - return; /* suspension forced, can do nothing more */ - main->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + if (! main_ptr->buffer_full) { + if (! (*cinfo->coef->decompress_data) (cinfo, main_ptr->buffer)) + return; /* suspension forced, can do nothing more */ + main_ptr->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ } /* There are always min_DCT_scaled_size row groups in an iMCU row. */ @@ -4283,14 +4312,14 @@ process_data_simple_main (j_decompress_ptr cinfo, */ /* Feed the postprocessor */ - (*cinfo->post->post_process_data) (cinfo, main->buffer, - &main->rowgroup_ctr, rowgroups_avail, - output_buf, out_row_ctr, out_rows_avail); + (*cinfo->post->post_process_data) (cinfo, main_ptr->buffer, + &main_ptr->rowgroup_ctr, rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); /* Has postprocessor consumed all the data yet? If so, mark buffer empty */ - if (main->rowgroup_ctr >= rowgroups_avail) { - main->buffer_full = FALSE; - main->rowgroup_ctr = 0; + if (main_ptr->rowgroup_ctr >= rowgroups_avail) { + main_ptr->buffer_full = FALSE; + main_ptr->rowgroup_ctr = 0; } } @@ -4302,18 +4331,18 @@ process_data_simple_main (j_decompress_ptr cinfo, METHODDEF(void) process_data_context_main (j_decompress_ptr cinfo, - JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, - JDIMENSION out_rows_avail) + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) { - my_main_ptr main = (my_main_ptr) cinfo->main; + my_main_ptr main_ptr = (my_main_ptr) cinfo->main_ptr; /* Read input data if we haven't filled the main buffer yet */ - if (! main->buffer_full) { + if (! main_ptr->buffer_full) { if (! (*cinfo->coef->decompress_data) (cinfo, - main->xbuffer[main->whichptr])) - return; /* suspension forced, can do nothing more */ - main->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ - main->iMCU_row_ctr++; /* count rows received */ + main_ptr->xbuffer[main_ptr->whichptr])) + return; /* suspension forced, can do nothing more */ + main_ptr->buffer_full = TRUE; /* OK, we have an iMCU row to work with */ + main_ptr->iMCU_row_ctr++; /* count rows received */ } /* Postprocessor typically will not swallow all the input data it is handed @@ -4321,47 +4350,47 @@ process_data_context_main (j_decompress_ptr cinfo, * to exit and restart. This switch lets us keep track of how far we got. * Note that each case falls through to the next on successful completion. */ - switch (main->context_state) { + switch (main_ptr->context_state) { case CTX_POSTPONED_ROW: /* Call postprocessor using previously set pointers for postponed row */ - (*cinfo->post->post_process_data) (cinfo, main->xbuffer[main->whichptr], - &main->rowgroup_ctr, main->rowgroups_avail, - output_buf, out_row_ctr, out_rows_avail); - if (main->rowgroup_ctr < main->rowgroups_avail) - return; /* Need to suspend */ - main->context_state = CTX_PREPARE_FOR_IMCU; + (*cinfo->post->post_process_data) (cinfo, main_ptr->xbuffer[main_ptr->whichptr], + &main_ptr->rowgroup_ctr, main_ptr->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + if (main_ptr->rowgroup_ctr < main_ptr->rowgroups_avail) + return; /* Need to suspend */ + main_ptr->context_state = CTX_PREPARE_FOR_IMCU; if (*out_row_ctr >= out_rows_avail) - return; /* Postprocessor exactly filled output buf */ + return; /* Postprocessor exactly filled output buf */ /*FALLTHROUGH*/ case CTX_PREPARE_FOR_IMCU: /* Prepare to process first M-1 row groups of this iMCU row */ - main->rowgroup_ctr = 0; - main->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size - 1); + main_ptr->rowgroup_ctr = 0; + main_ptr->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size - 1); /* Check for bottom of image: if so, tweak pointers to "duplicate" * the last sample row, and adjust rowgroups_avail to ignore padding rows. */ - if (main->iMCU_row_ctr == cinfo->total_iMCU_rows) + if (main_ptr->iMCU_row_ctr == cinfo->total_iMCU_rows) set_bottom_pointers(cinfo); - main->context_state = CTX_PROCESS_IMCU; + main_ptr->context_state = CTX_PROCESS_IMCU; /*FALLTHROUGH*/ case CTX_PROCESS_IMCU: /* Call postprocessor using previously set pointers */ - (*cinfo->post->post_process_data) (cinfo, main->xbuffer[main->whichptr], - &main->rowgroup_ctr, main->rowgroups_avail, - output_buf, out_row_ctr, out_rows_avail); - if (main->rowgroup_ctr < main->rowgroups_avail) - return; /* Need to suspend */ + (*cinfo->post->post_process_data) (cinfo, main_ptr->xbuffer[main_ptr->whichptr], + &main_ptr->rowgroup_ctr, main_ptr->rowgroups_avail, + output_buf, out_row_ctr, out_rows_avail); + if (main_ptr->rowgroup_ctr < main_ptr->rowgroups_avail) + return; /* Need to suspend */ /* After the first iMCU, change wraparound pointers to normal state */ - if (main->iMCU_row_ctr == 1) + if (main_ptr->iMCU_row_ctr == 1) set_wraparound_pointers(cinfo); /* Prepare to load new iMCU row using other xbuffer list */ - main->whichptr ^= 1; /* 0=>1 or 1=>0 */ - main->buffer_full = FALSE; + main_ptr->whichptr ^= 1; /* 0=>1 or 1=>0 */ + main_ptr->buffer_full = FALSE; /* Still need to process last row group of this iMCU row, */ /* which is saved at index M+1 of the other xbuffer */ - main->rowgroup_ctr = (JDIMENSION) (cinfo->min_DCT_scaled_size + 1); - main->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size + 2); - main->context_state = CTX_POSTPONED_ROW; + main_ptr->rowgroup_ctr = (JDIMENSION) (cinfo->min_DCT_scaled_size + 1); + main_ptr->rowgroups_avail = (JDIMENSION) (cinfo->min_DCT_scaled_size + 2); + main_ptr->context_state = CTX_POSTPONED_ROW; } } @@ -4376,12 +4405,12 @@ process_data_context_main (j_decompress_ptr cinfo, METHODDEF(void) process_data_crank_post (j_decompress_ptr cinfo, - JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, - JDIMENSION out_rows_avail) + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) { (*cinfo->post->post_process_data) (cinfo, (JSAMPIMAGE) NULL, - (JDIMENSION *) NULL, (JDIMENSION) 0, - output_buf, out_row_ctr, out_rows_avail); + (JDIMENSION *) NULL, (JDIMENSION) 0, + output_buf, out_row_ctr, out_rows_avail); } #endif /* QUANT_2PASS_SUPPORTED */ @@ -4394,17 +4423,17 @@ process_data_crank_post (j_decompress_ptr cinfo, GLOBAL(void) jinit_d_main_controller (j_decompress_ptr cinfo, boolean need_full_buffer) { - my_main_ptr main; + my_main_ptr main_ptr; int ci, rgroup, ngroups; jpeg_component_info *compptr; - main = (my_main_ptr) + main_ptr = (my_main_ptr) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(my_main_controller)); - cinfo->main = (struct jpeg_d_main_controller *) main; - main->pub.start_pass = start_pass_main; + SIZEOF(my_main_controller)); + cinfo->main_ptr = (struct jpeg_d_main_controller *) main_ptr; + main_ptr->pub.start_pass = start_pass_main; - if (need_full_buffer) /* shouldn't happen */ + if (need_full_buffer) /* shouldn't happen */ ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); /* Allocate the workspace. @@ -4423,10 +4452,10 @@ jinit_d_main_controller (j_decompress_ptr cinfo, boolean need_full_buffer) ci++, compptr++) { rgroup = (compptr->v_samp_factor * compptr->DCT_scaled_size) / cinfo->min_DCT_scaled_size; /* height of a row group of component */ - main->buffer[ci] = (*cinfo->mem->alloc_sarray) - ((j_common_ptr) cinfo, JPOOL_IMAGE, - compptr->width_in_blocks * compptr->DCT_scaled_size, - (JDIMENSION) (rgroup * ngroups)); + main_ptr->buffer[ci] = (*cinfo->mem->alloc_sarray) + ((j_common_ptr) cinfo, JPOOL_IMAGE, + compptr->width_in_blocks * compptr->DCT_scaled_size, + (JDIMENSION) (rgroup * ngroups)); } } /* @@ -4461,9 +4490,9 @@ typedef struct { /* These variables keep track of the current location of the input side. */ /* cinfo->input_iMCU_row is also used for this. */ - JDIMENSION MCU_ctr; /* counts MCUs processed in current row */ - int MCU_vert_offset; /* counts MCU rows within iMCU row */ - int MCU_rows_per_iMCU_row; /* number of such rows needed */ + JDIMENSION MCU_ctr; /* counts MCUs processed in current row */ + int MCU_vert_offset; /* counts MCU rows within iMCU row */ + int MCU_rows_per_iMCU_row; /* number of such rows needed */ /* The output side's location is represented by cinfo->output_iMCU_row. */ @@ -4486,7 +4515,7 @@ typedef struct { #ifdef BLOCK_SMOOTHING_SUPPORTED /* When doing block smoothing, we latch coefficient Al values here */ int * coef_bits_latch; -#define SAVED_COEFS 6 /* we save coef_bits[0..5] */ +#define SAVED_COEFS 6 /* we save coef_bits[0..5] */ #endif } my_coef_controller; @@ -4494,15 +4523,15 @@ typedef my_coef_controller * my_coef_ptr; /* Forward declarations */ METHODDEF(int) decompress_onepass - JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); #ifdef D_MULTISCAN_FILES_SUPPORTED METHODDEF(int) decompress_data - JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); #endif #ifdef BLOCK_SMOOTHING_SUPPORTED LOCAL(boolean) smoothing_ok JPP((j_decompress_ptr cinfo)); METHODDEF(int) decompress_smooth_data - JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); + JPP((j_decompress_ptr cinfo, JSAMPIMAGE output_buf)); #endif @@ -4578,7 +4607,7 @@ METHODDEF(int) decompress_onepass (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) { my_coef_ptr coef = (my_coef_ptr) cinfo->coef; - JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION MCU_col_num; /* index of current MCU within row */ JDIMENSION last_MCU_col = cinfo->MCUs_per_row - 1; JDIMENSION last_iMCU_row = cinfo->total_iMCU_rows - 1; int blkn, ci, xindex, yindex, yoffset, useful_width; @@ -4591,49 +4620,49 @@ decompress_onepass (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; yoffset++) { for (MCU_col_num = coef->MCU_ctr; MCU_col_num <= last_MCU_col; - MCU_col_num++) { + MCU_col_num++) { /* Try to fetch an MCU. Entropy decoder expects buffer to be zeroed. */ jzero_far((void FAR *) coef->MCU_buffer[0], - (size_t) (cinfo->blocks_in_MCU * SIZEOF(JBLOCK))); + (size_t) (cinfo->blocks_in_MCU * SIZEOF(JBLOCK))); if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { - /* Suspension forced; update state counters and exit */ - coef->MCU_vert_offset = yoffset; - coef->MCU_ctr = MCU_col_num; - return JPEG_SUSPENDED; + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; } /* Determine where data should go in output_buf and do the IDCT thing. * We skip dummy blocks at the right and bottom edges (but blkn gets * incremented past them!). Note the inner loop relies on having * allocated the MCU_buffer[] blocks sequentially. */ - blkn = 0; /* index of current DCT block within MCU */ + blkn = 0; /* index of current DCT block within MCU */ for (ci = 0; ci < cinfo->comps_in_scan; ci++) { - compptr = cinfo->cur_comp_info[ci]; - /* Don't bother to IDCT an uninteresting component. */ - if (! compptr->component_needed) { - blkn += compptr->MCU_blocks; - continue; - } - inverse_DCT = cinfo->idct->inverse_DCT[compptr->component_index]; - useful_width = (MCU_col_num < last_MCU_col) ? compptr->MCU_width - : compptr->last_col_width; - output_ptr = output_buf[compptr->component_index] + - yoffset * compptr->DCT_scaled_size; - start_col = MCU_col_num * compptr->MCU_sample_width; - for (yindex = 0; yindex < compptr->MCU_height; yindex++) { - if (cinfo->input_iMCU_row < last_iMCU_row || - yoffset+yindex < compptr->last_row_height) { - output_col = start_col; - for (xindex = 0; xindex < useful_width; xindex++) { - (*inverse_DCT) (cinfo, compptr, - (JCOEFPTR) coef->MCU_buffer[blkn+xindex], - output_ptr, output_col); - output_col += compptr->DCT_scaled_size; - } - } - blkn += compptr->MCU_width; - output_ptr += compptr->DCT_scaled_size; - } + compptr = cinfo->cur_comp_info[ci]; + /* Don't bother to IDCT an uninteresting component. */ + if (! compptr->component_needed) { + blkn += compptr->MCU_blocks; + continue; + } + inverse_DCT = cinfo->idct->inverse_DCT[compptr->component_index]; + useful_width = (MCU_col_num < last_MCU_col) ? compptr->MCU_width + : compptr->last_col_width; + output_ptr = output_buf[compptr->component_index] + + yoffset * compptr->DCT_scaled_size; + start_col = MCU_col_num * compptr->MCU_sample_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + if (cinfo->input_iMCU_row < last_iMCU_row || + yoffset+yindex < compptr->last_row_height) { + output_col = start_col; + for (xindex = 0; xindex < useful_width; xindex++) { + (*inverse_DCT) (cinfo, compptr, + (JCOEFPTR) coef->MCU_buffer[blkn+xindex], + output_ptr, output_col); + output_col += compptr->DCT_scaled_size; + } + } + blkn += compptr->MCU_width; + output_ptr += compptr->DCT_scaled_size; + } } } /* Completed an MCU row, but perhaps not an iMCU row */ @@ -4658,7 +4687,7 @@ decompress_onepass (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) METHODDEF(int) dummy_consume_data (j_decompress_ptr cinfo) { - return JPEG_SUSPENDED; /* Always indicate nothing was done */ + return JPEG_SUSPENDED; /* Always indicate nothing was done */ } @@ -4675,7 +4704,7 @@ METHODDEF(int) consume_data (j_decompress_ptr cinfo) { my_coef_ptr coef = (my_coef_ptr) cinfo->coef; - JDIMENSION MCU_col_num; /* index of current MCU within row */ + JDIMENSION MCU_col_num; /* index of current MCU within row */ int blkn, ci, xindex, yindex, yoffset; JDIMENSION start_col; JBLOCKARRAY buffer[MAX_COMPS_IN_SCAN]; @@ -4699,25 +4728,25 @@ consume_data (j_decompress_ptr cinfo) for (yoffset = coef->MCU_vert_offset; yoffset < coef->MCU_rows_per_iMCU_row; yoffset++) { for (MCU_col_num = coef->MCU_ctr; MCU_col_num < cinfo->MCUs_per_row; - MCU_col_num++) { + MCU_col_num++) { /* Construct list of pointers to DCT blocks belonging to this MCU */ - blkn = 0; /* index of current DCT block within MCU */ + blkn = 0; /* index of current DCT block within MCU */ for (ci = 0; ci < cinfo->comps_in_scan; ci++) { - compptr = cinfo->cur_comp_info[ci]; - start_col = MCU_col_num * compptr->MCU_width; - for (yindex = 0; yindex < compptr->MCU_height; yindex++) { - buffer_ptr = buffer[ci][yindex+yoffset] + start_col; - for (xindex = 0; xindex < compptr->MCU_width; xindex++) { - coef->MCU_buffer[blkn++] = buffer_ptr++; - } - } + compptr = cinfo->cur_comp_info[ci]; + start_col = MCU_col_num * compptr->MCU_width; + for (yindex = 0; yindex < compptr->MCU_height; yindex++) { + buffer_ptr = buffer[ci][yindex+yoffset] + start_col; + for (xindex = 0; xindex < compptr->MCU_width; xindex++) { + coef->MCU_buffer[blkn++] = buffer_ptr++; + } + } } /* Try to fetch the MCU. */ if (! (*cinfo->entropy->decode_mcu) (cinfo, coef->MCU_buffer)) { - /* Suspension forced; update state counters and exit */ - coef->MCU_vert_offset = yoffset; - coef->MCU_ctr = MCU_col_num; - return JPEG_SUSPENDED; + /* Suspension forced; update state counters and exit */ + coef->MCU_vert_offset = yoffset; + coef->MCU_ctr = MCU_col_num; + return JPEG_SUSPENDED; } } /* Completed an MCU row, but perhaps not an iMCU row */ @@ -4758,8 +4787,8 @@ decompress_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) /* Force some input to be done if we are getting ahead of the input. */ while (cinfo->input_scan_number < cinfo->output_scan_number || - (cinfo->input_scan_number == cinfo->output_scan_number && - cinfo->input_iMCU_row <= cinfo->output_iMCU_row)) { + (cinfo->input_scan_number == cinfo->output_scan_number && + cinfo->input_iMCU_row <= cinfo->output_iMCU_row)) { if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) return JPEG_SUSPENDED; } @@ -4790,10 +4819,10 @@ decompress_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) buffer_ptr = buffer[block_row]; output_col = 0; for (block_num = 0; block_num < compptr->width_in_blocks; block_num++) { - (*inverse_DCT) (cinfo, compptr, (JCOEFPTR) buffer_ptr, - output_ptr, output_col); - buffer_ptr++; - output_col += compptr->DCT_scaled_size; + (*inverse_DCT) (cinfo, compptr, (JCOEFPTR) buffer_ptr, + output_ptr, output_col); + buffer_ptr++; + output_col += compptr->DCT_scaled_size; } output_ptr += compptr->DCT_scaled_size; } @@ -4850,8 +4879,8 @@ smoothing_ok (j_decompress_ptr cinfo) if (coef->coef_bits_latch == NULL) coef->coef_bits_latch = (int *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - cinfo->num_components * - (SAVED_COEFS * SIZEOF(int))); + cinfo->num_components * + (SAVED_COEFS * SIZEOF(int))); coef_bits_latch = coef->coef_bits_latch; for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; @@ -4861,11 +4890,11 @@ smoothing_ok (j_decompress_ptr cinfo) return FALSE; /* Verify DC & first 5 AC quantizers are nonzero to avoid zero-divide. */ if (qtable->quantval[0] == 0 || - qtable->quantval[Q01_POS] == 0 || - qtable->quantval[Q10_POS] == 0 || - qtable->quantval[Q20_POS] == 0 || - qtable->quantval[Q11_POS] == 0 || - qtable->quantval[Q02_POS] == 0) + qtable->quantval[Q01_POS] == 0 || + qtable->quantval[Q10_POS] == 0 || + qtable->quantval[Q20_POS] == 0 || + qtable->quantval[Q11_POS] == 0 || + qtable->quantval[Q02_POS] == 0) return FALSE; /* DC values must be at least partly known for all components. */ coef_bits = cinfo->coef_bits[ci]; @@ -4875,7 +4904,7 @@ smoothing_ok (j_decompress_ptr cinfo) for (coefi = 1; coefi <= 5; coefi++) { coef_bits_latch[coefi] = coef_bits[coefi]; if (coef_bits[coefi] != 0) - smoothing_useful = TRUE; + smoothing_useful = TRUE; } coef_bits_latch += SAVED_COEFS; } @@ -4911,7 +4940,7 @@ decompress_smooth_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) /* Force some input to be done if we are getting ahead of the input. */ while (cinfo->input_scan_number <= cinfo->output_scan_number && - ! cinfo->inputctl->eoi_reached) { + ! cinfo->inputctl->eoi_reached) { if (cinfo->input_scan_number == cinfo->output_scan_number) { /* If input is working on current scan, we ordinarily want it to * have completed the current row. But if input scan is DC, @@ -4920,7 +4949,7 @@ decompress_smooth_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) */ JDIMENSION delta = (cinfo->Ss == 0) ? 1 : 0; if (cinfo->input_iMCU_row > cinfo->output_iMCU_row+delta) - break; + break; } if ((*cinfo->inputctl->consume_input)(cinfo) == JPEG_SUSPENDED) return JPEG_SUSPENDED; @@ -4948,15 +4977,15 @@ decompress_smooth_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) if (cinfo->output_iMCU_row > 0) { access_rows += compptr->v_samp_factor; /* prior iMCU row too */ buffer = (*cinfo->mem->access_virt_barray) - ((j_common_ptr) cinfo, coef->whole_image[ci], - (cinfo->output_iMCU_row - 1) * compptr->v_samp_factor, - (JDIMENSION) access_rows, FALSE); - buffer += compptr->v_samp_factor; /* point to current iMCU row */ + ((j_common_ptr) cinfo, coef->whole_image[ci], + (cinfo->output_iMCU_row - 1) * compptr->v_samp_factor, + (JDIMENSION) access_rows, FALSE); + buffer += compptr->v_samp_factor; /* point to current iMCU row */ first_row = FALSE; } else { buffer = (*cinfo->mem->access_virt_barray) - ((j_common_ptr) cinfo, coef->whole_image[ci], - (JDIMENSION) 0, (JDIMENSION) access_rows, FALSE); + ((j_common_ptr) cinfo, coef->whole_image[ci], + (JDIMENSION) 0, (JDIMENSION) access_rows, FALSE); first_row = TRUE; } /* Fetch component-dependent info */ @@ -4974,13 +5003,13 @@ decompress_smooth_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) for (block_row = 0; block_row < block_rows; block_row++) { buffer_ptr = buffer[block_row]; if (first_row && block_row == 0) - prev_block_row = buffer_ptr; + prev_block_row = buffer_ptr; else - prev_block_row = buffer[block_row-1]; + prev_block_row = buffer[block_row-1]; if (last_row && block_row == block_rows-1) - next_block_row = buffer_ptr; + next_block_row = buffer_ptr; else - next_block_row = buffer[block_row+1]; + next_block_row = buffer[block_row+1]; /* We fetch the surrounding DC values using a sliding-register approach. * Initialize all nine here so as to do the right thing on narrow pics. */ @@ -4990,102 +5019,102 @@ decompress_smooth_data (j_decompress_ptr cinfo, JSAMPIMAGE output_buf) output_col = 0; last_block_column = compptr->width_in_blocks - 1; for (block_num = 0; block_num <= last_block_column; block_num++) { - /* Fetch current DCT block into workspace so we can modify it. */ - jcopy_block_row(buffer_ptr, (JBLOCKROW) workspace, (JDIMENSION) 1); - /* Update DC values */ - if (block_num < last_block_column) { - DC3 = (int) prev_block_row[1][0]; - DC6 = (int) buffer_ptr[1][0]; - DC9 = (int) next_block_row[1][0]; - } - /* Compute coefficient estimates per K.8. - * An estimate is applied only if coefficient is still zero, - * and is not known to be fully accurate. - */ - /* AC01 */ - if ((Al=coef_bits[1]) != 0 && workspace[1] == 0) { - num = 36 * Q00 * (DC4 - DC6); - if (num >= 0) { - pred = (int) (((Q01<<7) + num) / (Q01<<8)); - if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { - pred = (int) (((Q10<<7) + num) / (Q10<<8)); - if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { - pred = (int) (((Q20<<7) + num) / (Q20<<8)); - if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { - pred = (int) (((Q11<<7) + num) / (Q11<<8)); - if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { - pred = (int) (((Q02<<7) + num) / (Q02<<8)); - if (Al > 0 && pred >= (1< 0 && pred >= (1<DCT_scaled_size; + /* Fetch current DCT block into workspace so we can modify it. */ + jcopy_block_row(buffer_ptr, (JBLOCKROW) workspace, (JDIMENSION) 1); + /* Update DC values */ + if (block_num < last_block_column) { + DC3 = (int) prev_block_row[1][0]; + DC6 = (int) buffer_ptr[1][0]; + DC9 = (int) next_block_row[1][0]; + } + /* Compute coefficient estimates per K.8. + * An estimate is applied only if coefficient is still zero, + * and is not known to be fully accurate. + */ + /* AC01 */ + if ((Al=coef_bits[1]) != 0 && workspace[1] == 0) { + num = 36 * Q00 * (DC4 - DC6); + if (num >= 0) { + pred = (int) (((Q01<<7) + num) / (Q01<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q10<<7) + num) / (Q10<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q20<<7) + num) / (Q20<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q11<<7) + num) / (Q11<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (int) (((Q02<<7) + num) / (Q02<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<DCT_scaled_size; } output_ptr += compptr->DCT_scaled_size; } @@ -5110,7 +5139,7 @@ jinit_d_coef_controller (j_decompress_ptr cinfo, boolean need_full_buffer) coef = (my_coef_ptr) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(my_coef_controller)); + SIZEOF(my_coef_controller)); cinfo->coef = (struct jpeg_d_coef_controller *) coef; coef->pub.start_input_pass = co_start_input_pass; coef->pub.start_output_pass = start_output_pass; @@ -5128,20 +5157,20 @@ jinit_d_coef_controller (j_decompress_ptr cinfo, boolean need_full_buffer) jpeg_component_info *compptr; for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; - ci++, compptr++) { + ci++, compptr++) { access_rows = compptr->v_samp_factor; #ifdef BLOCK_SMOOTHING_SUPPORTED /* If block smoothing could be used, need a bigger window */ if (cinfo->progressive_mode) - access_rows *= 3; + access_rows *= 3; #endif coef->whole_image[ci] = (*cinfo->mem->request_virt_barray) - ((j_common_ptr) cinfo, JPOOL_IMAGE, TRUE, - (JDIMENSION) jround_up((long) compptr->width_in_blocks, - (long) compptr->h_samp_factor), - (JDIMENSION) jround_up((long) compptr->height_in_blocks, - (long) compptr->v_samp_factor), - (JDIMENSION) access_rows); + ((j_common_ptr) cinfo, JPOOL_IMAGE, TRUE, + (JDIMENSION) jround_up((long) compptr->width_in_blocks, + (long) compptr->h_samp_factor), + (JDIMENSION) jround_up((long) compptr->height_in_blocks, + (long) compptr->v_samp_factor), + (JDIMENSION) access_rows); } coef->pub.consume_data = consume_data; coef->pub.decompress_data = decompress_data; @@ -5156,7 +5185,7 @@ jinit_d_coef_controller (j_decompress_ptr cinfo, boolean need_full_buffer) buffer = (JBLOCKROW) (*cinfo->mem->alloc_large) ((j_common_ptr) cinfo, JPOOL_IMAGE, - D_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); + D_MAX_BLOCKS_IN_MCU * SIZEOF(JBLOCK)); for (i = 0; i < D_MAX_BLOCKS_IN_MCU; i++) { coef->MCU_buffer[i] = buffer + i; } @@ -5198,12 +5227,12 @@ typedef struct { * For two-pass color quantization, we need a full-image buffer; * for one-pass operation, a strip buffer is sufficient. */ - jvirt_sarray_ptr whole_image; /* virtual array, or NULL if one-pass */ - JSAMPARRAY buffer; /* strip buffer, or current strip of virtual */ - JDIMENSION strip_height; /* buffer size in rows */ + jvirt_sarray_ptr whole_image; /* virtual array, or NULL if one-pass */ + JSAMPARRAY buffer; /* strip buffer, or current strip of virtual */ + JDIMENSION strip_height; /* buffer size in rows */ /* for two-pass mode only: */ - JDIMENSION starting_row; /* row # of first row in current strip */ - JDIMENSION next_row; /* index of next row to fill/empty in strip */ + JDIMENSION starting_row; /* row # of first row in current strip */ + JDIMENSION next_row; /* index of next row to fill/empty in strip */ } my_post_controller; typedef my_post_controller * my_post_ptr; @@ -5211,24 +5240,24 @@ typedef my_post_controller * my_post_ptr; /* Forward declarations */ METHODDEF(void) post_process_1pass - JPP((j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, - JDIMENSION in_row_groups_avail, - JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, - JDIMENSION out_rows_avail)); + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); #ifdef QUANT_2PASS_SUPPORTED METHODDEF(void) post_process_prepass - JPP((j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, - JDIMENSION in_row_groups_avail, - JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, - JDIMENSION out_rows_avail)); + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); METHODDEF(void) post_process_2pass - JPP((j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, - JDIMENSION in_row_groups_avail, - JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, - JDIMENSION out_rows_avail)); + JPP((j_decompress_ptr cinfo, + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail)); #endif @@ -5251,9 +5280,9 @@ start_pass_dpost (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) * allocate a strip buffer. Use the virtual-array buffer as workspace. */ if (post->buffer == NULL) { - post->buffer = (*cinfo->mem->access_virt_sarray) - ((j_common_ptr) cinfo, post->whole_image, - (JDIMENSION) 0, post->strip_height, TRUE); + post->buffer = (*cinfo->mem->access_virt_sarray) + ((j_common_ptr) cinfo, post->whole_image, + (JDIMENSION) 0, post->strip_height, TRUE); } } else { /* For single-pass processing without color quantization, @@ -5291,10 +5320,10 @@ start_pass_dpost (j_decompress_ptr cinfo, J_BUF_MODE pass_mode) METHODDEF(void) post_process_1pass (j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, - JDIMENSION in_row_groups_avail, - JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, - JDIMENSION out_rows_avail) + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) { my_post_ptr post = (my_post_ptr) cinfo->post; JDIMENSION num_rows, max_rows; @@ -5306,11 +5335,11 @@ post_process_1pass (j_decompress_ptr cinfo, max_rows = post->strip_height; num_rows = 0; (*cinfo->upsample->upsample) (cinfo, - input_buf, in_row_group_ctr, in_row_groups_avail, - post->buffer, &num_rows, max_rows); + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &num_rows, max_rows); /* Quantize and emit data. */ (*cinfo->cquantize->color_quantize) (cinfo, - post->buffer, output_buf + *out_row_ctr, (int) num_rows); + post->buffer, output_buf + *out_row_ctr, (int) num_rows); *out_row_ctr += num_rows; } @@ -5323,10 +5352,10 @@ post_process_1pass (j_decompress_ptr cinfo, METHODDEF(void) post_process_prepass (j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, - JDIMENSION in_row_groups_avail, - JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, - JDIMENSION out_rows_avail) + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) { my_post_ptr post = (my_post_ptr) cinfo->post; JDIMENSION old_next_row, num_rows; @@ -5334,22 +5363,22 @@ post_process_prepass (j_decompress_ptr cinfo, /* Reposition virtual buffer if at start of strip. */ if (post->next_row == 0) { post->buffer = (*cinfo->mem->access_virt_sarray) - ((j_common_ptr) cinfo, post->whole_image, - post->starting_row, post->strip_height, TRUE); + ((j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, TRUE); } /* Upsample some data (up to a strip height's worth). */ old_next_row = post->next_row; (*cinfo->upsample->upsample) (cinfo, - input_buf, in_row_group_ctr, in_row_groups_avail, - post->buffer, &post->next_row, post->strip_height); + input_buf, in_row_group_ctr, in_row_groups_avail, + post->buffer, &post->next_row, post->strip_height); /* Allow quantizer to scan new data. No data is emitted, */ /* but we advance out_row_ctr so outer loop can tell when we're done. */ if (post->next_row > old_next_row) { num_rows = post->next_row - old_next_row; (*cinfo->cquantize->color_quantize) (cinfo, post->buffer + old_next_row, - (JSAMPARRAY) NULL, (int) num_rows); + (JSAMPARRAY) NULL, (int) num_rows); *out_row_ctr += num_rows; } @@ -5367,10 +5396,10 @@ post_process_prepass (j_decompress_ptr cinfo, METHODDEF(void) post_process_2pass (j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, - JDIMENSION in_row_groups_avail, - JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, - JDIMENSION out_rows_avail) + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) { my_post_ptr post = (my_post_ptr) cinfo->post; JDIMENSION num_rows, max_rows; @@ -5378,8 +5407,8 @@ post_process_2pass (j_decompress_ptr cinfo, /* Reposition virtual buffer if at start of strip. */ if (post->next_row == 0) { post->buffer = (*cinfo->mem->access_virt_sarray) - ((j_common_ptr) cinfo, post->whole_image, - post->starting_row, post->strip_height, FALSE); + ((j_common_ptr) cinfo, post->whole_image, + post->starting_row, post->strip_height, FALSE); } /* Determine number of rows to emit. */ @@ -5394,8 +5423,8 @@ post_process_2pass (j_decompress_ptr cinfo, /* Quantize and emit data. */ (*cinfo->cquantize->color_quantize) (cinfo, - post->buffer + post->next_row, output_buf + *out_row_ctr, - (int) num_rows); + post->buffer + post->next_row, output_buf + *out_row_ctr, + (int) num_rows); *out_row_ctr += num_rows; /* Advance if we filled the strip. */ @@ -5420,11 +5449,11 @@ jinit_d_post_controller (j_decompress_ptr cinfo, boolean need_full_buffer) post = (my_post_ptr) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(my_post_controller)); + SIZEOF(my_post_controller)); cinfo->post = (struct jpeg_d_post_controller *) post; post->pub.start_pass = start_pass_dpost; - post->whole_image = NULL; /* flag for no virtual arrays */ - post->buffer = NULL; /* flag for no strip buffer */ + post->whole_image = NULL; /* flag for no virtual arrays */ + post->buffer = NULL; /* flag for no strip buffer */ /* Create the quantization buffer, if needed */ if (cinfo->quantize_colors) { @@ -5438,20 +5467,20 @@ jinit_d_post_controller (j_decompress_ptr cinfo, boolean need_full_buffer) /* We round up the number of rows to a multiple of the strip height. */ #ifdef QUANT_2PASS_SUPPORTED post->whole_image = (*cinfo->mem->request_virt_sarray) - ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, - cinfo->output_width * cinfo->out_color_components, - (JDIMENSION) jround_up((long) cinfo->output_height, - (long) post->strip_height), - post->strip_height); + ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE, + cinfo->output_width * cinfo->out_color_components, + (JDIMENSION) jround_up((long) cinfo->output_height, + (long) post->strip_height), + post->strip_height); #else ERREXIT(cinfo, JERR_BAD_BUFFER_MODE); #endif /* QUANT_2PASS_SUPPORTED */ } else { /* One-pass color quantization: just make a strip buffer. */ post->buffer = (*cinfo->mem->alloc_sarray) - ((j_common_ptr) cinfo, JPOOL_IMAGE, - cinfo->output_width * cinfo->out_color_components, - post->strip_height); + ((j_common_ptr) cinfo, JPOOL_IMAGE, + cinfo->output_width * cinfo->out_color_components, + post->strip_height); } } } @@ -5475,7 +5504,7 @@ jinit_d_post_controller (j_decompress_ptr cinfo, boolean need_full_buffer) #define JPEG_INTERNALS //#include "jinclude.h" //#include "jpeglib.h" -//#include "jdct.h" /* Private declarations for DCT subsystem */ +//#include "jdct.h" /* Private declarations for DCT subsystem */ /* @@ -5498,7 +5527,7 @@ jinit_d_post_controller (j_decompress_ptr cinfo, boolean need_full_buffer) /* Private subobject for this module */ typedef struct { - struct jpeg_inverse_dct pub; /* public fields */ + struct jpeg_inverse_dct pub; /* public fields */ /* This array contains the IDCT method code that each multiplier table * is currently set up for, or -1 if it's not yet set up. @@ -5559,40 +5588,40 @@ start_pass (j_decompress_ptr cinfo) #ifdef IDCT_SCALING_SUPPORTED case 1: method_ptr = jpeg_idct_1x1; - method = JDCT_ISLOW; /* jidctred uses islow-style table */ + method = JDCT_ISLOW; /* jidctred uses islow-style table */ break; case 2: method_ptr = jpeg_idct_2x2; - method = JDCT_ISLOW; /* jidctred uses islow-style table */ + method = JDCT_ISLOW; /* jidctred uses islow-style table */ break; case 4: method_ptr = jpeg_idct_4x4; - method = JDCT_ISLOW; /* jidctred uses islow-style table */ + method = JDCT_ISLOW; /* jidctred uses islow-style table */ break; #endif case DCTSIZE: switch (cinfo->dct_method) { #ifdef DCT_ISLOW_SUPPORTED case JDCT_ISLOW: - method_ptr = jpeg_idct_islow; - method = JDCT_ISLOW; - break; + method_ptr = jpeg_idct_islow; + method = JDCT_ISLOW; + break; #endif #ifdef DCT_IFAST_SUPPORTED case JDCT_IFAST: - method_ptr = jpeg_idct_ifast; - method = JDCT_IFAST; - break; + method_ptr = jpeg_idct_ifast; + method = JDCT_IFAST; + break; #endif #ifdef DCT_FLOAT_SUPPORTED case JDCT_FLOAT: - method_ptr = jpeg_idct_float; - method = JDCT_FLOAT; - break; + method_ptr = jpeg_idct_float; + method = JDCT_FLOAT; + break; #endif default: - ERREXIT(cinfo, JERR_NOT_COMPILED); - break; + ERREXIT(cinfo, JERR_NOT_COMPILED); + break; } break; default: @@ -5610,81 +5639,81 @@ start_pass (j_decompress_ptr cinfo) if (! compptr->component_needed || idct->cur_method[ci] == method) continue; qtbl = compptr->quant_table; - if (qtbl == NULL) /* happens if no data yet for component */ + if (qtbl == NULL) /* happens if no data yet for component */ continue; idct->cur_method[ci] = method; switch (method) { #ifdef PROVIDE_ISLOW_TABLES case JDCT_ISLOW: { - /* For LL&M IDCT method, multipliers are equal to raw quantization - * coefficients, but are stored as ints to ensure access efficiency. - */ - ISLOW_MULT_TYPE * ismtbl = (ISLOW_MULT_TYPE *) compptr->dct_table; - for (i = 0; i < DCTSIZE2; i++) { - ismtbl[i] = (ISLOW_MULT_TYPE) qtbl->quantval[i]; - } + /* For LL&M IDCT method, multipliers are equal to raw quantization + * coefficients, but are stored as ints to ensure access efficiency. + */ + ISLOW_MULT_TYPE * ismtbl = (ISLOW_MULT_TYPE *) compptr->dct_table; + for (i = 0; i < DCTSIZE2; i++) { + ismtbl[i] = (ISLOW_MULT_TYPE) qtbl->quantval[i]; + } } break; #endif #ifdef DCT_IFAST_SUPPORTED case JDCT_IFAST: { - /* For AA&N IDCT method, multipliers are equal to quantization - * coefficients scaled by scalefactor[row]*scalefactor[col], where - * scalefactor[0] = 1 - * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 - * For integer operation, the multiplier table is to be scaled by - * IFAST_SCALE_BITS. - */ - IFAST_MULT_TYPE * ifmtbl = (IFAST_MULT_TYPE *) compptr->dct_table; + /* For AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + * For integer operation, the multiplier table is to be scaled by + * IFAST_SCALE_BITS. + */ + IFAST_MULT_TYPE * ifmtbl = (IFAST_MULT_TYPE *) compptr->dct_table; #define CONST_BITS 14 - static const INT16 aanscales[DCTSIZE2] = { - /* precomputed values scaled up by 14 bits */ - 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, - 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, - 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, - 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, - 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, - 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, - 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, - 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 - }; - SHIFT_TEMPS - - for (i = 0; i < DCTSIZE2; i++) { - ifmtbl[i] = (IFAST_MULT_TYPE) - DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[i], - (INT32) aanscales[i]), - CONST_BITS-IFAST_SCALE_BITS); - } + static const INT16 aanscales[DCTSIZE2] = { + /* precomputed values scaled up by 14 bits */ + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 + }; + SHIFT_TEMPS + + for (i = 0; i < DCTSIZE2; i++) { + ifmtbl[i] = (IFAST_MULT_TYPE) + DESCALE(MULTIPLY16V16((INT32) qtbl->quantval[i], + (INT32) aanscales[i]), + CONST_BITS-IFAST_SCALE_BITS); + } } break; #endif #ifdef DCT_FLOAT_SUPPORTED case JDCT_FLOAT: { - /* For float AA&N IDCT method, multipliers are equal to quantization - * coefficients scaled by scalefactor[row]*scalefactor[col], where - * scalefactor[0] = 1 - * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 - */ - FLOAT_MULT_TYPE * fmtbl = (FLOAT_MULT_TYPE *) compptr->dct_table; - int row, col; - static const double aanscalefactor[DCTSIZE] = { - 1.0, 1.387039845, 1.306562965, 1.175875602, - 1.0, 0.785694958, 0.541196100, 0.275899379 - }; - - i = 0; - for (row = 0; row < DCTSIZE; row++) { - for (col = 0; col < DCTSIZE; col++) { - fmtbl[i] = (FLOAT_MULT_TYPE) - ((double) qtbl->quantval[i] * - aanscalefactor[row] * aanscalefactor[col]); - i++; - } - } + /* For float AA&N IDCT method, multipliers are equal to quantization + * coefficients scaled by scalefactor[row]*scalefactor[col], where + * scalefactor[0] = 1 + * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 + */ + FLOAT_MULT_TYPE * fmtbl = (FLOAT_MULT_TYPE *) compptr->dct_table; + int row, col; + static const double aanscalefactor[DCTSIZE] = { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + i = 0; + for (row = 0; row < DCTSIZE; row++) { + for (col = 0; col < DCTSIZE; col++) { + fmtbl[i] = (FLOAT_MULT_TYPE) + ((double) qtbl->quantval[i] * + aanscalefactor[row] * aanscalefactor[col]); + i++; + } + } } break; #endif @@ -5709,7 +5738,7 @@ jinit_inverse_dct (j_decompress_ptr cinfo) idct = (my_idct_ptr) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(my_idct_controller)); + SIZEOF(my_idct_controller)); cinfo->idct = (struct jpeg_inverse_dct *) idct; idct->pub.start_pass = start_pass; @@ -5718,7 +5747,7 @@ jinit_inverse_dct (j_decompress_ptr cinfo) /* Allocate and pre-zero a multiplier table for each component */ compptr->dct_table = (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(multiplier_table)); + SIZEOF(multiplier_table)); MEMZERO(compptr->dct_table, SIZEOF(multiplier_table)); /* Mark multiplier table not yet set up for any method */ idct->cur_method[ci] = -1; @@ -5761,7 +5790,7 @@ jinit_inverse_dct (j_decompress_ptr cinfo) #define JPEG_INTERNALS //#include "jinclude.h" //#include "jpeglib.h" -//#include "jdct.h" /* Private declarations for DCT subsystem */ +//#include "jdct.h" /* Private declarations for DCT subsystem */ #ifdef DCT_IFAST_SUPPORTED @@ -5805,7 +5834,7 @@ jinit_inverse_dct (j_decompress_ptr cinfo) #define PASS1_BITS 2 #else #define CONST_BITS 8 -#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ #endif /* Some C compilers fail to reduce "FIX(constant)" at compile time, thus @@ -5816,10 +5845,10 @@ jinit_inverse_dct (j_decompress_ptr cinfo) */ #if CONST_BITS == 8 -#define FIX_1_082392200 ((INT32) 277) /* FIX(1.082392200) */ -#define FIX_1_414213562 ((INT32) 362) /* FIX(1.414213562) */ -#define FIX_1_847759065 ((INT32) 473) /* FIX(1.847759065) */ -#define FIX_2_613125930 ((INT32) 669) /* FIX(2.613125930) */ +#define FIX_1_082392200 ((INT32) 277) /* FIX(1.082392200) */ +#define FIX_1_414213562 ((INT32) 362) /* FIX(1.414213562) */ +#define FIX_1_847759065 ((INT32) 473) /* FIX(1.847759065) */ +#define FIX_2_613125930 ((INT32) 669) /* FIX(2.613125930) */ #else #define FIX_1_082392200 FIX(1.082392200) #define FIX_1_414213562 FIX(1.414213562) @@ -5856,7 +5885,7 @@ jinit_inverse_dct (j_decompress_ptr cinfo) #define jic_jic_DEQUANTIZE(coef,quantval) (((IFAST_MULT_TYPE) (coef)) * (quantval)) #else #define jic_jic_DEQUANTIZE(coef,quantval) \ - jic_DESCALE((coef)*(quantval), IFAST_SCALE_BITS-PASS1_BITS) + jic_DESCALE((coef)*(quantval), IFAST_SCALE_BITS-PASS1_BITS) #endif @@ -5865,11 +5894,11 @@ jinit_inverse_dct (j_decompress_ptr cinfo) */ #ifdef RIGHT_SHIFT_IS_UNSIGNED -#define ISHIFT_TEMPS DCTELEM ishift_temp; +#define ISHIFT_TEMPS DCTELEM ishift_temp; #if BITS_IN_JSAMPLE == 8 -#define DCTELEMBITS 16 /* DCTELEM may be 16 or 32 bits */ +#define DCTELEMBITS 16 /* DCTELEM may be 16 or 32 bits */ #else -#define DCTELEMBITS 32 /* DCTELEM must be 32 bits */ +#define DCTELEMBITS 32 /* DCTELEM must be 32 bits */ #endif #define jic_jic_IRIGHT_SHIFT(x,shft) \ ((ishift_temp = (x)) < 0 ? \ @@ -5877,7 +5906,7 @@ jinit_inverse_dct (j_decompress_ptr cinfo) (ishift_temp >> (shft))) #else #define ISHIFT_TEMPS -#define jic_jic_IRIGHT_SHIFT(x,shft) ((x) >> (shft)) +#define jic_jic_IRIGHT_SHIFT(x,shft) ((x) >> (shft)) #endif #ifdef USE_ACCURATE_ROUNDING @@ -5893,8 +5922,8 @@ jinit_inverse_dct (j_decompress_ptr cinfo) GLOBAL(void) jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, - JCOEFPTR coef_block, - JSAMPARRAY output_buf, JDIMENSION output_col) + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) { DCTELEM tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; DCTELEM tmp10, tmp11, tmp12, tmp13; @@ -5905,9 +5934,9 @@ jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, JSAMPROW outptr; JSAMPLE *range_limit = IDCT_range_limit(cinfo); int ctr; - int workspace[DCTSIZE2]; /* buffers data between passes */ - SHIFT_TEMPS /* for DESCALE */ - ISHIFT_TEMPS /* for IDESCALE */ + int workspace[DCTSIZE2]; /* buffers data between passes */ + SHIFT_TEMPS /* for DESCALE */ + ISHIFT_TEMPS /* for IDESCALE */ /* Pass 1: process columns from input, store into work array. */ @@ -5923,11 +5952,11 @@ jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, * With typical images and quantization tables, half or more of the * column DCT calculations can be simplified this way. */ - + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && - inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && - inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && - inptr[DCTSIZE*7] == 0) { + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && + inptr[DCTSIZE*7] == 0) { /* AC terms all zero */ int dcval = (int) jic_jic_DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); @@ -5939,13 +5968,13 @@ jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, wsptr[DCTSIZE*5] = dcval; wsptr[DCTSIZE*6] = dcval; wsptr[DCTSIZE*7] = dcval; - - inptr++; /* advance pointers to next column */ + + inptr++; /* advance pointers to next column */ quantptr++; wsptr++; continue; } - + /* Even part */ tmp0 = jic_jic_DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); @@ -5953,17 +5982,17 @@ jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, tmp2 = jic_jic_DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); tmp3 = jic_jic_DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); - tmp10 = tmp0 + tmp2; /* phase 3 */ + tmp10 = tmp0 + tmp2; /* phase 3 */ tmp11 = tmp0 - tmp2; - tmp13 = tmp1 + tmp3; /* phases 5-3 */ + tmp13 = tmp1 + tmp3; /* phases 5-3 */ tmp12 = jic_MULTIPLY(tmp1 - tmp3, FIX_1_414213562) - tmp13; /* 2*c4 */ - tmp0 = tmp10 + tmp13; /* phase 2 */ + tmp0 = tmp10 + tmp13; /* phase 2 */ tmp3 = tmp10 - tmp13; tmp1 = tmp11 + tmp12; tmp2 = tmp11 - tmp12; - + /* Odd part */ tmp4 = jic_jic_DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); @@ -5971,19 +6000,19 @@ jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, tmp6 = jic_jic_DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); tmp7 = jic_jic_DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); - z13 = tmp6 + tmp5; /* phase 6 */ + z13 = tmp6 + tmp5; /* phase 6 */ z10 = tmp6 - tmp5; z11 = tmp4 + tmp7; z12 = tmp4 - tmp7; - tmp7 = z11 + z13; /* phase 5 */ + tmp7 = z11 + z13; /* phase 5 */ tmp11 = jic_MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */ z5 = jic_MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */ tmp10 = jic_MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */ tmp12 = jic_MULTIPLY(z10, - FIX_2_613125930) + z5; /* -2*(c2+c6) */ - tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp6 = tmp12 - tmp7; /* phase 2 */ tmp5 = tmp11 - tmp6; tmp4 = tmp10 + tmp5; @@ -5996,11 +6025,11 @@ jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, wsptr[DCTSIZE*4] = (int) (tmp3 + tmp4); wsptr[DCTSIZE*3] = (int) (tmp3 - tmp4); - inptr++; /* advance pointers to next column */ + inptr++; /* advance pointers to next column */ quantptr++; wsptr++; } - + /* Pass 2: process rows from work array, store into output array. */ /* Note that we must descale the results by a factor of 8 == 2**3, */ /* and also undo the PASS1_BITS scaling. */ @@ -6015,14 +6044,14 @@ jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, * test takes more time than it's worth. In that case this section * may be commented out. */ - + #ifndef NO_ZERO_ROW_TEST if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 && wsptr[4] == 0 && - wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) { + wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) { /* AC terms all zero */ JSAMPLE dcval = range_limit[Ijic_DESCALE(wsptr[0], PASS1_BITS+3) - & RANGE_MASK]; - + & RANGE_MASK]; + outptr[0] = dcval; outptr[1] = dcval; outptr[2] = dcval; @@ -6032,11 +6061,11 @@ jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, outptr[6] = dcval; outptr[7] = dcval; - wsptr += DCTSIZE; /* advance pointer to next row */ + wsptr += DCTSIZE; /* advance pointer to next row */ continue; } #endif - + /* Even part */ tmp10 = ((DCTELEM) wsptr[0] + (DCTELEM) wsptr[4]); @@ -6044,7 +6073,7 @@ jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, tmp13 = ((DCTELEM) wsptr[2] + (DCTELEM) wsptr[6]); tmp12 = jic_MULTIPLY((DCTELEM) wsptr[2] - (DCTELEM) wsptr[6], FIX_1_414213562) - - tmp13; + - tmp13; tmp0 = tmp10 + tmp13; tmp3 = tmp10 - tmp13; @@ -6058,37 +6087,37 @@ jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, z11 = (DCTELEM) wsptr[1] + (DCTELEM) wsptr[7]; z12 = (DCTELEM) wsptr[1] - (DCTELEM) wsptr[7]; - tmp7 = z11 + z13; /* phase 5 */ + tmp7 = z11 + z13; /* phase 5 */ tmp11 = jic_MULTIPLY(z11 - z13, FIX_1_414213562); /* 2*c4 */ z5 = jic_MULTIPLY(z10 + z12, FIX_1_847759065); /* 2*c2 */ tmp10 = jic_MULTIPLY(z12, FIX_1_082392200) - z5; /* 2*(c2-c6) */ tmp12 = jic_MULTIPLY(z10, - FIX_2_613125930) + z5; /* -2*(c2+c6) */ - tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp6 = tmp12 - tmp7; /* phase 2 */ tmp5 = tmp11 - tmp6; tmp4 = tmp10 + tmp5; /* Final output stage: scale down by a factor of 8 and range-limit */ outptr[0] = range_limit[Ijic_DESCALE(tmp0 + tmp7, PASS1_BITS+3) - & RANGE_MASK]; + & RANGE_MASK]; outptr[7] = range_limit[Ijic_DESCALE(tmp0 - tmp7, PASS1_BITS+3) - & RANGE_MASK]; + & RANGE_MASK]; outptr[1] = range_limit[Ijic_DESCALE(tmp1 + tmp6, PASS1_BITS+3) - & RANGE_MASK]; + & RANGE_MASK]; outptr[6] = range_limit[Ijic_DESCALE(tmp1 - tmp6, PASS1_BITS+3) - & RANGE_MASK]; + & RANGE_MASK]; outptr[2] = range_limit[Ijic_DESCALE(tmp2 + tmp5, PASS1_BITS+3) - & RANGE_MASK]; + & RANGE_MASK]; outptr[5] = range_limit[Ijic_DESCALE(tmp2 - tmp5, PASS1_BITS+3) - & RANGE_MASK]; + & RANGE_MASK]; outptr[4] = range_limit[Ijic_DESCALE(tmp3 + tmp4, PASS1_BITS+3) - & RANGE_MASK]; + & RANGE_MASK]; outptr[3] = range_limit[Ijic_DESCALE(tmp3 - tmp4, PASS1_BITS+3) - & RANGE_MASK]; + & RANGE_MASK]; - wsptr += DCTSIZE; /* advance pointer to next row */ + wsptr += DCTSIZE; /* advance pointer to next row */ } } @@ -6134,7 +6163,7 @@ jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, #define JPEG_INTERNALS //#include "jinclude.h" //#include "jpeglib.h" -//#include "jdct.h" /* Private declarations for DCT subsystem */ +//#include "jdct.h" /* Private declarations for DCT subsystem */ #ifdef DCT_FLOAT_SUPPORTED @@ -6161,8 +6190,8 @@ jpeg_idct_ifast (j_decompress_ptr cinfo, jpeg_component_info * compptr, GLOBAL(void) jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, - JCOEFPTR coef_block, - JSAMPARRAY output_buf, JDIMENSION output_col) + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) { FAST_FLOAT tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; FAST_FLOAT tmp10, tmp11, tmp12, tmp13; @@ -6190,14 +6219,14 @@ jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, * With typical images and quantization tables, half or more of the * column DCT calculations can be simplified this way. */ - + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && - inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && - inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && - inptr[DCTSIZE*7] == 0) { + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && + inptr[DCTSIZE*7] == 0) { /* AC terms all zero */ FAST_FLOAT dcval = jict_DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); - + wsptr[DCTSIZE*0] = dcval; wsptr[DCTSIZE*1] = dcval; wsptr[DCTSIZE*2] = dcval; @@ -6206,13 +6235,13 @@ jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, wsptr[DCTSIZE*5] = dcval; wsptr[DCTSIZE*6] = dcval; wsptr[DCTSIZE*7] = dcval; - - inptr++; /* advance pointers to next column */ + + inptr++; /* advance pointers to next column */ quantptr++; wsptr++; continue; } - + /* Even part */ tmp0 = jict_DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); @@ -6220,17 +6249,17 @@ jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, tmp2 = jict_DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); tmp3 = jict_DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); - tmp10 = tmp0 + tmp2; /* phase 3 */ + tmp10 = tmp0 + tmp2; /* phase 3 */ tmp11 = tmp0 - tmp2; - tmp13 = tmp1 + tmp3; /* phases 5-3 */ + tmp13 = tmp1 + tmp3; /* phases 5-3 */ tmp12 = (tmp1 - tmp3) * ((FAST_FLOAT) 1.414213562) - tmp13; /* 2*c4 */ - tmp0 = tmp10 + tmp13; /* phase 2 */ + tmp0 = tmp10 + tmp13; /* phase 2 */ tmp3 = tmp10 - tmp13; tmp1 = tmp11 + tmp12; tmp2 = tmp11 - tmp12; - + /* Odd part */ tmp4 = jict_DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); @@ -6238,19 +6267,19 @@ jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, tmp6 = jict_DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); tmp7 = jict_DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); - z13 = tmp6 + tmp5; /* phase 6 */ + z13 = tmp6 + tmp5; /* phase 6 */ z10 = tmp6 - tmp5; z11 = tmp4 + tmp7; z12 = tmp4 - tmp7; - tmp7 = z11 + z13; /* phase 5 */ + tmp7 = z11 + z13; /* phase 5 */ tmp11 = (z11 - z13) * ((FAST_FLOAT) 1.414213562); /* 2*c4 */ z5 = (z10 + z12) * ((FAST_FLOAT) 1.847759065); /* 2*c2 */ tmp10 = ((FAST_FLOAT) 1.082392200) * z12 - z5; /* 2*(c2-c6) */ tmp12 = ((FAST_FLOAT) -2.613125930) * z10 + z5; /* -2*(c2+c6) */ - tmp6 = tmp12 - tmp7; /* phase 2 */ + tmp6 = tmp12 - tmp7; /* phase 2 */ tmp5 = tmp11 - tmp6; tmp4 = tmp10 + tmp5; @@ -6263,11 +6292,11 @@ jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, wsptr[DCTSIZE*4] = tmp3 + tmp4; wsptr[DCTSIZE*3] = tmp3 - tmp4; - inptr++; /* advance pointers to next column */ + inptr++; /* advance pointers to next column */ quantptr++; wsptr++; } - + /* Pass 2: process rows from work array, store into output array. */ /* Note that we must descale the results by a factor of 8 == 2**3. */ @@ -6279,7 +6308,7 @@ jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, * the simplification applies less often (typically 5% to 10% of the time). * And testing floats for zero is relatively expensive, so we don't bother. */ - + /* Even part */ tmp10 = wsptr[0] + wsptr[4]; @@ -6314,23 +6343,23 @@ jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, /* Final output stage: scale down by a factor of 8 and range-limit */ outptr[0] = range_limit[(int) DESCALE((INT32) (tmp0 + tmp7), 3) - & RANGE_MASK]; + & RANGE_MASK]; outptr[7] = range_limit[(int) DESCALE((INT32) (tmp0 - tmp7), 3) - & RANGE_MASK]; + & RANGE_MASK]; outptr[1] = range_limit[(int) DESCALE((INT32) (tmp1 + tmp6), 3) - & RANGE_MASK]; + & RANGE_MASK]; outptr[6] = range_limit[(int) DESCALE((INT32) (tmp1 - tmp6), 3) - & RANGE_MASK]; + & RANGE_MASK]; outptr[2] = range_limit[(int) DESCALE((INT32) (tmp2 + tmp5), 3) - & RANGE_MASK]; + & RANGE_MASK]; outptr[5] = range_limit[(int) DESCALE((INT32) (tmp2 - tmp5), 3) - & RANGE_MASK]; + & RANGE_MASK]; outptr[4] = range_limit[(int) DESCALE((INT32) (tmp3 + tmp4), 3) - & RANGE_MASK]; + & RANGE_MASK]; outptr[3] = range_limit[(int) DESCALE((INT32) (tmp3 - tmp4), 3) - & RANGE_MASK]; - - wsptr += DCTSIZE; /* advance pointer to next row */ + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ } } @@ -6365,7 +6394,7 @@ jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, #define JPEG_INTERNALS //#include "jinclude.h" //#include "jpeglib.h" -//#include "jdct.h" /* Private declarations for DCT subsystem */ +//#include "jdct.h" /* Private declarations for DCT subsystem */ #ifdef DCT_ISLOW_SUPPORTED @@ -6417,7 +6446,7 @@ jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, #define PASS1_BITS 2 #else #define CONST_BITS 13 -#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ +#define PASS1_BITS 1 /* lose a little precision to avoid overflow */ #endif /* Some C compilers fail to reduce "FIX(constant)" at compile time, thus @@ -6429,18 +6458,18 @@ jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, #undef FIX_1_847759065 #if CONST_BITS == 13 -#define FIX_0_298631336 ((INT32) 2446) /* FIX(0.298631336) */ -#define FIX_0_390180644 ((INT32) 3196) /* FIX(0.390180644) */ -#define FIX_0_541196100 ((INT32) 4433) /* FIX(0.541196100) */ -#define FIX_0_765366865 ((INT32) 6270) /* FIX(0.765366865) */ -#define FIX_0_899976223 ((INT32) 7373) /* FIX(0.899976223) */ -#define FIX_1_175875602 ((INT32) 9633) /* FIX(1.175875602) */ -#define FIX_1_501321110 ((INT32) 12299) /* FIX(1.501321110) */ -#define FIX_1_847759065 ((INT32) 15137) /* FIX(1.847759065) */ -#define FIX_1_961570560 ((INT32) 16069) /* FIX(1.961570560) */ -#define FIX_2_053119869 ((INT32) 16819) /* FIX(2.053119869) */ -#define FIX_2_562915447 ((INT32) 20995) /* FIX(2.562915447) */ -#define FIX_3_072711026 ((INT32) 25172) /* FIX(3.072711026) */ +#define FIX_0_298631336 ((INT32) 2446) /* FIX(0.298631336) */ +#define FIX_0_390180644 ((INT32) 3196) /* FIX(0.390180644) */ +#define FIX_0_541196100 ((INT32) 4433) /* FIX(0.541196100) */ +#define FIX_0_765366865 ((INT32) 6270) /* FIX(0.765366865) */ +#define FIX_0_899976223 ((INT32) 7373) /* FIX(0.899976223) */ +#define FIX_1_175875602 ((INT32) 9633) /* FIX(1.175875602) */ +#define FIX_1_501321110 ((INT32) 12299) /* FIX(1.501321110) */ +#define FIX_1_847759065 ((INT32) 15137) /* FIX(1.847759065) */ +#define FIX_1_961570560 ((INT32) 16069) /* FIX(1.961570560) */ +#define FIX_2_053119869 ((INT32) 16819) /* FIX(2.053119869) */ +#define FIX_2_562915447 ((INT32) 20995) /* FIX(2.562915447) */ +#define FIX_3_072711026 ((INT32) 25172) /* FIX(3.072711026) */ #else #define FIX_0_298631336 FIX(0.298631336) #define FIX_0_390180644 FIX(0.390180644) @@ -6485,8 +6514,8 @@ jpeg_idct_float (j_decompress_ptr cinfo, jpeg_component_info * compptr, GLOBAL(void) jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr, - JCOEFPTR coef_block, - JSAMPARRAY output_buf, JDIMENSION output_col) + JCOEFPTR coef_block, + JSAMPARRAY output_buf, JDIMENSION output_col) { INT32 tmp0, tmp1, tmp2, tmp3; INT32 tmp10, tmp11, tmp12, tmp13; @@ -6497,7 +6526,7 @@ jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr, JSAMPROW outptr; JSAMPLE *range_limit = IDCT_range_limit(cinfo); int ctr; - int workspace[DCTSIZE2]; /* buffers data between passes */ + int workspace[DCTSIZE2]; /* buffers data between passes */ SHIFT_TEMPS /* Pass 1: process columns from input, store into work array. */ @@ -6516,14 +6545,14 @@ jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr, * With typical images and quantization tables, half or more of the * column DCT calculations can be simplified this way. */ - + if (inptr[DCTSIZE*1] == 0 && inptr[DCTSIZE*2] == 0 && - inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && - inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && - inptr[DCTSIZE*7] == 0) { + inptr[DCTSIZE*3] == 0 && inptr[DCTSIZE*4] == 0 && + inptr[DCTSIZE*5] == 0 && inptr[DCTSIZE*6] == 0 && + inptr[DCTSIZE*7] == 0) { /* AC terms all zero */ int dcval = jicti_DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]) << PASS1_BITS; - + wsptr[DCTSIZE*0] = dcval; wsptr[DCTSIZE*1] = dcval; wsptr[DCTSIZE*2] = dcval; @@ -6532,49 +6561,49 @@ jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr, wsptr[DCTSIZE*5] = dcval; wsptr[DCTSIZE*6] = dcval; wsptr[DCTSIZE*7] = dcval; - - inptr++; /* advance pointers to next column */ + + inptr++; /* advance pointers to next column */ quantptr++; wsptr++; continue; } - + /* Even part: reverse the even part of the forward DCT. */ /* The rotator is sqrt(2)*c(-6). */ - + z2 = jicti_DEQUANTIZE(inptr[DCTSIZE*2], quantptr[DCTSIZE*2]); z3 = jicti_DEQUANTIZE(inptr[DCTSIZE*6], quantptr[DCTSIZE*6]); - + z1 = jicti_jicti_MULTIPLY(z2 + z3, FIX_0_541196100); tmp2 = z1 + jicti_jicti_MULTIPLY(z3, - FIX_1_847759065); tmp3 = z1 + jicti_jicti_MULTIPLY(z2, FIX_0_765366865); - + z2 = jicti_DEQUANTIZE(inptr[DCTSIZE*0], quantptr[DCTSIZE*0]); z3 = jicti_DEQUANTIZE(inptr[DCTSIZE*4], quantptr[DCTSIZE*4]); tmp0 = (z2 + z3) << CONST_BITS; tmp1 = (z2 - z3) << CONST_BITS; - + tmp10 = tmp0 + tmp3; tmp13 = tmp0 - tmp3; tmp11 = tmp1 + tmp2; tmp12 = tmp1 - tmp2; - + /* Odd part per figure 8; the matrix is unitary and hence its * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. */ - + tmp0 = jicti_DEQUANTIZE(inptr[DCTSIZE*7], quantptr[DCTSIZE*7]); tmp1 = jicti_DEQUANTIZE(inptr[DCTSIZE*5], quantptr[DCTSIZE*5]); tmp2 = jicti_DEQUANTIZE(inptr[DCTSIZE*3], quantptr[DCTSIZE*3]); tmp3 = jicti_DEQUANTIZE(inptr[DCTSIZE*1], quantptr[DCTSIZE*1]); - + z1 = tmp0 + tmp3; z2 = tmp1 + tmp2; z3 = tmp0 + tmp2; z4 = tmp1 + tmp3; z5 = jicti_jicti_MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */ - + tmp0 = jicti_jicti_MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ tmp1 = jicti_jicti_MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ tmp2 = jicti_jicti_MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ @@ -6583,17 +6612,17 @@ jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr, z2 = jicti_jicti_MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ z3 = jicti_jicti_MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ z4 = jicti_jicti_MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ - + z3 += z5; z4 += z5; - + tmp0 += z1 + z3; tmp1 += z2 + z4; tmp2 += z2 + z3; tmp3 += z1 + z4; - + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ - + wsptr[DCTSIZE*0] = (int) DESCALE(tmp10 + tmp3, CONST_BITS-PASS1_BITS); wsptr[DCTSIZE*7] = (int) DESCALE(tmp10 - tmp3, CONST_BITS-PASS1_BITS); wsptr[DCTSIZE*1] = (int) DESCALE(tmp11 + tmp2, CONST_BITS-PASS1_BITS); @@ -6602,12 +6631,12 @@ jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr, wsptr[DCTSIZE*5] = (int) DESCALE(tmp12 - tmp1, CONST_BITS-PASS1_BITS); wsptr[DCTSIZE*3] = (int) DESCALE(tmp13 + tmp0, CONST_BITS-PASS1_BITS); wsptr[DCTSIZE*4] = (int) DESCALE(tmp13 - tmp0, CONST_BITS-PASS1_BITS); - - inptr++; /* advance pointers to next column */ + + inptr++; /* advance pointers to next column */ quantptr++; wsptr++; } - + /* Pass 2: process rows from work array, store into output array. */ /* Note that we must descale the results by a factor of 8 == 2**3, */ /* and also undo the PASS1_BITS scaling. */ @@ -6622,14 +6651,14 @@ jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr, * test takes more time than it's worth. In that case this section * may be commented out. */ - + #ifndef NO_ZERO_ROW_TEST if (wsptr[1] == 0 && wsptr[2] == 0 && wsptr[3] == 0 && wsptr[4] == 0 && - wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) { + wsptr[5] == 0 && wsptr[6] == 0 && wsptr[7] == 0) { /* AC terms all zero */ JSAMPLE dcval = range_limit[(int) DESCALE((INT32) wsptr[0], PASS1_BITS+3) - & RANGE_MASK]; - + & RANGE_MASK]; + outptr[0] = dcval; outptr[1] = dcval; outptr[2] = dcval; @@ -6639,44 +6668,44 @@ jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr, outptr[6] = dcval; outptr[7] = dcval; - wsptr += DCTSIZE; /* advance pointer to next row */ + wsptr += DCTSIZE; /* advance pointer to next row */ continue; } #endif - + /* Even part: reverse the even part of the forward DCT. */ /* The rotator is sqrt(2)*c(-6). */ - + z2 = (INT32) wsptr[2]; z3 = (INT32) wsptr[6]; - + z1 = jicti_jicti_MULTIPLY(z2 + z3, FIX_0_541196100); tmp2 = z1 + jicti_jicti_MULTIPLY(z3, - FIX_1_847759065); tmp3 = z1 + jicti_jicti_MULTIPLY(z2, FIX_0_765366865); - + tmp0 = ((INT32) wsptr[0] + (INT32) wsptr[4]) << CONST_BITS; tmp1 = ((INT32) wsptr[0] - (INT32) wsptr[4]) << CONST_BITS; - + tmp10 = tmp0 + tmp3; tmp13 = tmp0 - tmp3; tmp11 = tmp1 + tmp2; tmp12 = tmp1 - tmp2; - + /* Odd part per figure 8; the matrix is unitary and hence its * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively. */ - + tmp0 = (INT32) wsptr[7]; tmp1 = (INT32) wsptr[5]; tmp2 = (INT32) wsptr[3]; tmp3 = (INT32) wsptr[1]; - + z1 = tmp0 + tmp3; z2 = tmp1 + tmp2; z3 = tmp0 + tmp2; z4 = tmp1 + tmp3; z5 = jicti_jicti_MULTIPLY(z3 + z4, FIX_1_175875602); /* sqrt(2) * c3 */ - + tmp0 = jicti_jicti_MULTIPLY(tmp0, FIX_0_298631336); /* sqrt(2) * (-c1+c3+c5-c7) */ tmp1 = jicti_jicti_MULTIPLY(tmp1, FIX_2_053119869); /* sqrt(2) * ( c1+c3-c5+c7) */ tmp2 = jicti_jicti_MULTIPLY(tmp2, FIX_3_072711026); /* sqrt(2) * ( c1+c3+c5-c7) */ @@ -6685,43 +6714,43 @@ jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr, z2 = jicti_jicti_MULTIPLY(z2, - FIX_2_562915447); /* sqrt(2) * (-c1-c3) */ z3 = jicti_jicti_MULTIPLY(z3, - FIX_1_961570560); /* sqrt(2) * (-c3-c5) */ z4 = jicti_jicti_MULTIPLY(z4, - FIX_0_390180644); /* sqrt(2) * (c5-c3) */ - + z3 += z5; z4 += z5; - + tmp0 += z1 + z3; tmp1 += z2 + z4; tmp2 += z2 + z3; tmp3 += z1 + z4; - + /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */ - + outptr[0] = range_limit[(int) DESCALE(tmp10 + tmp3, - CONST_BITS+PASS1_BITS+3) - & RANGE_MASK]; + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; outptr[7] = range_limit[(int) DESCALE(tmp10 - tmp3, - CONST_BITS+PASS1_BITS+3) - & RANGE_MASK]; + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; outptr[1] = range_limit[(int) DESCALE(tmp11 + tmp2, - CONST_BITS+PASS1_BITS+3) - & RANGE_MASK]; + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; outptr[6] = range_limit[(int) DESCALE(tmp11 - tmp2, - CONST_BITS+PASS1_BITS+3) - & RANGE_MASK]; + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; outptr[2] = range_limit[(int) DESCALE(tmp12 + tmp1, - CONST_BITS+PASS1_BITS+3) - & RANGE_MASK]; + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; outptr[5] = range_limit[(int) DESCALE(tmp12 - tmp1, - CONST_BITS+PASS1_BITS+3) - & RANGE_MASK]; + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; outptr[3] = range_limit[(int) DESCALE(tmp13 + tmp0, - CONST_BITS+PASS1_BITS+3) - & RANGE_MASK]; + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; outptr[4] = range_limit[(int) DESCALE(tmp13 - tmp0, - CONST_BITS+PASS1_BITS+3) - & RANGE_MASK]; - - wsptr += DCTSIZE; /* advance pointer to next row */ + CONST_BITS+PASS1_BITS+3) + & RANGE_MASK]; + + wsptr += DCTSIZE; /* advance pointer to next row */ } } @@ -6753,13 +6782,13 @@ jpeg_idct_islow (j_decompress_ptr cinfo, jpeg_component_info * compptr, /* Pointer to routine to upsample a single component */ typedef JMETHOD(void, upsample1_ptr, - (j_decompress_ptr cinfo, jpeg_component_info * compptr, - JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)); + (j_decompress_ptr cinfo, jpeg_component_info * compptr, + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr)); /* Private subobject */ typedef struct { - struct jpeg_upsampler pub; /* public fields */ + struct jpeg_upsampler pub; /* public fields */ /* Color conversion buffer. When using separate upsampling and color * conversion steps, this buffer holds one upsampled row group until it @@ -6773,8 +6802,8 @@ typedef struct { /* Per-component upsampling method pointers */ upsample1_ptr methods[MAX_COMPONENTS]; - int next_row_out; /* counts rows emitted from color_buf */ - JDIMENSION rows_to_go; /* counts rows remaining in image */ + int next_row_out; /* counts rows emitted from color_buf */ + JDIMENSION rows_to_go; /* counts rows remaining in image */ /* Height of an input row group for each component. */ int rowgroup_height[MAX_COMPONENTS]; @@ -6815,10 +6844,10 @@ start_pass_upsample (j_decompress_ptr cinfo) METHODDEF(void) sep_upsample (j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, - JDIMENSION in_row_groups_avail, - JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, - JDIMENSION out_rows_avail) + JSAMPIMAGE input_buf, JDIMENSION *in_row_group_ctr, + JDIMENSION in_row_groups_avail, + JSAMPARRAY output_buf, JDIMENSION *out_row_ctr, + JDIMENSION out_rows_avail) { my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; int ci; @@ -6828,13 +6857,13 @@ sep_upsample (j_decompress_ptr cinfo, /* Fill the conversion buffer, if it's empty */ if (upsample->next_row_out >= cinfo->max_v_samp_factor) { for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components; - ci++, compptr++) { + ci++, compptr++) { /* Invoke per-component upsample method. Notice we pass a POINTER * to color_buf[ci], so that fullsize_upsample can change it. */ (*upsample->methods[ci]) (cinfo, compptr, - input_buf[ci] + (*in_row_group_ctr * upsample->rowgroup_height[ci]), - upsample->color_buf + ci); + input_buf[ci] + (*in_row_group_ctr * upsample->rowgroup_height[ci]), + upsample->color_buf + ci); } upsample->next_row_out = 0; } @@ -6846,7 +6875,7 @@ sep_upsample (j_decompress_ptr cinfo, /* Not more than the distance to the end of the image. Need this test * in case the image height is not a multiple of max_v_samp_factor: */ - if (num_rows > upsample->rows_to_go) + if (num_rows > upsample->rows_to_go) num_rows = upsample->rows_to_go; /* And not more than what the client can accept: */ out_rows_avail -= *out_row_ctr; @@ -6854,9 +6883,9 @@ sep_upsample (j_decompress_ptr cinfo, num_rows = out_rows_avail; (*cinfo->cconvert->color_convert) (cinfo, upsample->color_buf, - (JDIMENSION) upsample->next_row_out, - output_buf + *out_row_ctr, - (int) num_rows); + (JDIMENSION) upsample->next_row_out, + output_buf + *out_row_ctr, + (int) num_rows); /* Adjust counts */ *out_row_ctr += num_rows; @@ -6883,7 +6912,7 @@ sep_upsample (j_decompress_ptr cinfo, METHODDEF(void) fullsize_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, - JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) { *output_data_ptr = input_data; } @@ -6896,9 +6925,9 @@ fullsize_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, METHODDEF(void) noop_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, - JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) { - *output_data_ptr = NULL; /* safety check */ + *output_data_ptr = NULL; /* safety check */ } @@ -6915,13 +6944,13 @@ noop_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, METHODDEF(void) int_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, - JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) { my_upsample_ptr upsample = (my_upsample_ptr) cinfo->upsample; JSAMPARRAY output_data = *output_data_ptr; - register JSAMPROW inptr, outptr; - register JSAMPLE invalue; - register int h; + JSAMPROW inptr, outptr; + JSAMPLE invalue; + int h; JSAMPROW outend; int h_expand, v_expand; int inrow, outrow; @@ -6936,15 +6965,15 @@ int_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, outptr = output_data[outrow]; outend = outptr + cinfo->output_width; while (outptr < outend) { - invalue = *inptr++; /* don't need GETJSAMPLE() here */ + invalue = *inptr++; /* don't need GETJSAMPLE() here */ for (h = h_expand; h > 0; h--) { - *outptr++ = invalue; + *outptr++ = invalue; } } /* Generate any additional output rows by duplicating the first one */ if (v_expand > 1) { jcopy_sample_rows(output_data, outrow, output_data, outrow+1, - v_expand-1, cinfo->output_width); + v_expand-1, cinfo->output_width); } inrow++; outrow += v_expand; @@ -6959,11 +6988,11 @@ int_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, METHODDEF(void) h2v1_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, - JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) { JSAMPARRAY output_data = *output_data_ptr; - register JSAMPROW inptr, outptr; - register JSAMPLE invalue; + JSAMPROW inptr, outptr; + JSAMPLE invalue; JSAMPROW outend; int inrow; @@ -6972,7 +7001,7 @@ h2v1_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, outptr = output_data[inrow]; outend = outptr + cinfo->output_width; while (outptr < outend) { - invalue = *inptr++; /* don't need GETJSAMPLE() here */ + invalue = *inptr++; /* don't need GETJSAMPLE() here */ *outptr++ = invalue; *outptr++ = invalue; } @@ -6987,11 +7016,11 @@ h2v1_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, METHODDEF(void) h2v2_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, - JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) { JSAMPARRAY output_data = *output_data_ptr; - register JSAMPROW inptr, outptr; - register JSAMPLE invalue; + JSAMPROW inptr, outptr; + JSAMPLE invalue; JSAMPROW outend; int inrow, outrow; @@ -7001,12 +7030,12 @@ h2v2_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, outptr = output_data[outrow]; outend = outptr + cinfo->output_width; while (outptr < outend) { - invalue = *inptr++; /* don't need GETJSAMPLE() here */ + invalue = *inptr++; /* don't need GETJSAMPLE() here */ *outptr++ = invalue; *outptr++ = invalue; } jcopy_sample_rows(output_data, outrow, output_data, outrow+1, - 1, cinfo->output_width); + 1, cinfo->output_width); inrow++; outrow += 2; } @@ -7030,12 +7059,12 @@ h2v2_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, METHODDEF(void) h2v1_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, - JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) { JSAMPARRAY output_data = *output_data_ptr; - register JSAMPROW inptr, outptr; - register int invalue; - register JDIMENSION colctr; + JSAMPROW inptr, outptr; + int invalue; + JDIMENSION colctr; int inrow; for (inrow = 0; inrow < cinfo->max_v_samp_factor; inrow++) { @@ -7071,16 +7100,16 @@ h2v1_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, METHODDEF(void) h2v2_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, - JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) + JSAMPARRAY input_data, JSAMPARRAY * output_data_ptr) { JSAMPARRAY output_data = *output_data_ptr; - register JSAMPROW inptr0, inptr1, outptr; + JSAMPROW inptr0, inptr1, outptr; #if BITS_IN_JSAMPLE == 8 - register int thiscolsum, lastcolsum, nextcolsum; + int thiscolsum, lastcolsum, nextcolsum; #else - register INT32 thiscolsum, lastcolsum, nextcolsum; + INT32 thiscolsum, lastcolsum, nextcolsum; #endif - register JDIMENSION colctr; + JDIMENSION colctr; int inrow, outrow, v; inrow = outrow = 0; @@ -7088,10 +7117,10 @@ h2v2_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, for (v = 0; v < 2; v++) { /* inptr0 points to nearest input row, inptr1 points to next nearest */ inptr0 = input_data[inrow]; - if (v == 0) /* next nearest is row above */ - inptr1 = input_data[inrow-1]; - else /* next nearest is row below */ - inptr1 = input_data[inrow+1]; + if (v == 0) /* next nearest is row above */ + inptr1 = input_data[inrow-1]; + else /* next nearest is row below */ + inptr1 = input_data[inrow+1]; outptr = output_data[outrow++]; /* Special case for first column */ @@ -7102,12 +7131,12 @@ h2v2_fancy_upsample (j_decompress_ptr cinfo, jpeg_component_info * compptr, lastcolsum = thiscolsum; thiscolsum = nextcolsum; for (colctr = compptr->downsampled_width - 2; colctr > 0; colctr--) { - /* General case: 3/4 * nearer pixel + 1/4 * further pixel in each */ - /* dimension, thus 9/16, 3/16, 3/16, 1/16 overall */ - nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); - *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4); - *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4); - lastcolsum = thiscolsum; thiscolsum = nextcolsum; + /* General case: 3/4 * nearer pixel + 1/4 * further pixel in each */ + /* dimension, thus 9/16, 3/16, 3/16, 1/16 overall */ + nextcolsum = GETJSAMPLE(*inptr0++) * 3 + GETJSAMPLE(*inptr1++); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + lastcolsum + 8) >> 4); + *outptr++ = (JSAMPLE) ((thiscolsum * 3 + nextcolsum + 7) >> 4); + lastcolsum = thiscolsum; thiscolsum = nextcolsum; } /* Special case for last column */ @@ -7134,13 +7163,13 @@ jinit_upsampler (j_decompress_ptr cinfo) upsample = (my_upsample_ptr) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(my_upsampler)); + SIZEOF(my_upsampler)); cinfo->upsample = (struct jpeg_upsampler *) upsample; upsample->pub.start_pass = start_pass_upsample; upsample->pub.upsample = sep_upsample; upsample->pub.need_context_rows = FALSE; /* until we find out differently */ - if (cinfo->CCIR601_sampling) /* this isn't supported */ + if (cinfo->CCIR601_sampling) /* this isn't supported */ ERREXIT(cinfo, JERR_CCIR601_NOTIMPL); /* jdmainct.c doesn't support context rows when min_DCT_scaled_size = 1, @@ -7157,9 +7186,9 @@ jinit_upsampler (j_decompress_ptr cinfo) * are to be converted to max_h_samp_factor * max_v_samp_factor pixels. */ h_in_group = (compptr->h_samp_factor * compptr->DCT_scaled_size) / - cinfo->min_DCT_scaled_size; + cinfo->min_DCT_scaled_size; v_in_group = (compptr->v_samp_factor * compptr->DCT_scaled_size) / - cinfo->min_DCT_scaled_size; + cinfo->min_DCT_scaled_size; h_out_group = cinfo->max_h_samp_factor; v_out_group = cinfo->max_v_samp_factor; upsample->rowgroup_height[ci] = v_in_group; /* save for use later */ @@ -7173,22 +7202,22 @@ jinit_upsampler (j_decompress_ptr cinfo) upsample->methods[ci] = fullsize_upsample; need_buffer = FALSE; } else if (h_in_group * 2 == h_out_group && - v_in_group == v_out_group) { + v_in_group == v_out_group) { /* Special cases for 2h1v upsampling */ if (do_fancy && compptr->downsampled_width > 2) - upsample->methods[ci] = h2v1_fancy_upsample; + upsample->methods[ci] = h2v1_fancy_upsample; else - upsample->methods[ci] = h2v1_upsample; + upsample->methods[ci] = h2v1_upsample; } else if (h_in_group * 2 == h_out_group && - v_in_group * 2 == v_out_group) { + v_in_group * 2 == v_out_group) { /* Special cases for 2h2v upsampling */ if (do_fancy && compptr->downsampled_width > 2) { - upsample->methods[ci] = h2v2_fancy_upsample; - upsample->pub.need_context_rows = TRUE; + upsample->methods[ci] = h2v2_fancy_upsample; + upsample->pub.need_context_rows = TRUE; } else - upsample->methods[ci] = h2v2_upsample; + upsample->methods[ci] = h2v2_upsample; } else if ((h_out_group % h_in_group) == 0 && - (v_out_group % v_in_group) == 0) { + (v_out_group % v_in_group) == 0) { /* Generic integral-factors upsampling method */ upsample->methods[ci] = int_upsample; upsample->h_expand[ci] = (UINT8) (h_out_group / h_in_group); @@ -7197,10 +7226,10 @@ jinit_upsampler (j_decompress_ptr cinfo) ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL); if (need_buffer) { upsample->color_buf[ci] = (*cinfo->mem->alloc_sarray) - ((j_common_ptr) cinfo, JPOOL_IMAGE, - (JDIMENSION) jround_up((long) cinfo->output_width, - (long) cinfo->max_h_samp_factor), - (JDIMENSION) cinfo->max_v_samp_factor); + ((j_common_ptr) cinfo, JPOOL_IMAGE, + (JDIMENSION) jround_up((long) cinfo->output_width, + (long) cinfo->max_h_samp_factor), + (JDIMENSION) cinfo->max_v_samp_factor); } } } @@ -7225,10 +7254,10 @@ typedef struct { struct jpeg_color_deconverter pub; /* public fields */ /* Private state for YCC->RGB conversion */ - int * Cr_r_tab; /* => table for Cr to R conversion */ - int * Cb_b_tab; /* => table for Cb to B conversion */ - INT32 * Cr_g_tab; /* => table for Cr to G conversion */ - INT32 * Cb_g_tab; /* => table for Cb to G conversion */ + int * Cr_r_tab; /* => table for Cr to R conversion */ + int * Cb_b_tab; /* => table for Cb to B conversion */ + INT32 * Cr_g_tab; /* => table for Cr to G conversion */ + INT32 * Cb_g_tab; /* => table for Cb to G conversion */ } my_color_deconverter; typedef my_color_deconverter * my_cconvert_ptr; @@ -7240,9 +7269,9 @@ typedef my_color_deconverter * my_cconvert_ptr; * YCbCr is defined per CCIR 601-1, except that Cb and Cr are * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5. * The conversion equations to be implemented are therefore - * R = Y + 1.40200 * Cr - * G = Y - 0.34414 * Cb - 0.71414 * Cr - * B = Y + 1.77200 * Cb + * R = Y + 1.40200 * Cr + * G = Y - 0.34414 * Cb - 0.71414 * Cr + * B = Y + 1.77200 * Cb * where Cb and Cr represent the incoming values less CENTERJSAMPLE. * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.) * @@ -7263,9 +7292,9 @@ typedef my_color_deconverter * my_cconvert_ptr; * together before rounding. */ -#define SCALEBITS 16 /* speediest right-shift on some machines */ -#define ONE_HALF ((INT32) 1 << (SCALEBITS-1)) -#define jdol_FIX(x) ((INT32) ((x) * (1L<Cr_r_tab = (int *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - (MAXJSAMPLE+1) * SIZEOF(int)); + (MAXJSAMPLE+1) * SIZEOF(int)); cconvert->Cb_b_tab = (int *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - (MAXJSAMPLE+1) * SIZEOF(int)); + (MAXJSAMPLE+1) * SIZEOF(int)); cconvert->Cr_g_tab = (INT32 *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - (MAXJSAMPLE+1) * SIZEOF(INT32)); + (MAXJSAMPLE+1) * SIZEOF(INT32)); cconvert->Cb_g_tab = (INT32 *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - (MAXJSAMPLE+1) * SIZEOF(INT32)); + (MAXJSAMPLE+1) * SIZEOF(INT32)); for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) { /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ /* Cr=>R value is nearest int to 1.40200 * x */ cconvert->Cr_r_tab[i] = (int) - RIGHT_SHIFT(jdol_FIX(1.40200) * x + ONE_HALF, SCALEBITS); + RIGHT_SHIFT(jdol_FIX(1.40200) * x + ONE_HALF, SCALEBITS); /* Cb=>B value is nearest int to 1.77200 * x */ cconvert->Cb_b_tab[i] = (int) - RIGHT_SHIFT(jdol_FIX(1.77200) * x + ONE_HALF, SCALEBITS); + RIGHT_SHIFT(jdol_FIX(1.77200) * x + ONE_HALF, SCALEBITS); /* Cr=>G value is scaled-up -0.71414 * x */ cconvert->Cr_g_tab[i] = (- jdol_FIX(0.71414)) * x; /* Cb=>G value is scaled-up -0.34414 * x */ @@ -7324,21 +7353,21 @@ build_ycc_rgb_table (j_decompress_ptr cinfo) METHODDEF(void) ycc_rgb_convert (j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, JDIMENSION input_row, - JSAMPARRAY output_buf, int num_rows) + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) { my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; - register int y, cb, cr; - register JSAMPROW outptr; - register JSAMPROW inptr0, inptr1, inptr2; - register JDIMENSION col; + int y, cb, cr; + JSAMPROW outptr; + JSAMPROW inptr0, inptr1, inptr2; + JDIMENSION col; JDIMENSION num_cols = cinfo->output_width; /* copy these pointers into registers if possible */ - register JSAMPLE * range_limit = cinfo->sample_range_limit; - register int * Crrtab = cconvert->Cr_r_tab; - register int * Cbbtab = cconvert->Cb_b_tab; - register INT32 * Crgtab = cconvert->Cr_g_tab; - register INT32 * Cbgtab = cconvert->Cb_g_tab; + JSAMPLE * range_limit = cinfo->sample_range_limit; + int * Crrtab = cconvert->Cr_r_tab; + int * Cbbtab = cconvert->Cb_b_tab; + INT32 * Crgtab = cconvert->Cr_g_tab; + INT32 * Cbgtab = cconvert->Cb_g_tab; SHIFT_TEMPS while (--num_rows >= 0) { @@ -7354,8 +7383,8 @@ ycc_rgb_convert (j_decompress_ptr cinfo, /* Range-limiting is essential due to noise introduced by DCT losses. */ outptr[RGB_RED] = range_limit[y + Crrtab[cr]]; outptr[RGB_GREEN] = range_limit[y + - ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], - SCALEBITS))]; + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + SCALEBITS))]; outptr[RGB_BLUE] = range_limit[y + Cbbtab[cb]]; outptr += RGB_PIXELSIZE; } @@ -7373,12 +7402,12 @@ ycc_rgb_convert (j_decompress_ptr cinfo, METHODDEF(void) null_convert (j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, JDIMENSION input_row, - JSAMPARRAY output_buf, int num_rows) + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) { - register JSAMPROW inptr, outptr; - register JDIMENSION count; - register int num_components = cinfo->num_components; + JSAMPROW inptr, outptr; + JDIMENSION count; + int num_components = cinfo->num_components; JDIMENSION num_cols = cinfo->output_width; int ci; @@ -7387,8 +7416,8 @@ null_convert (j_decompress_ptr cinfo, inptr = input_buf[ci][input_row]; outptr = output_buf[0] + ci; for (count = num_cols; count > 0; count--) { - *outptr = *inptr++; /* needn't bother with GETJSAMPLE() here */ - outptr += num_components; + *outptr = *inptr++; /* needn't bother with GETJSAMPLE() here */ + outptr += num_components; } } input_row++; @@ -7405,11 +7434,11 @@ null_convert (j_decompress_ptr cinfo, METHODDEF(void) grayscale_convert (j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, JDIMENSION input_row, - JSAMPARRAY output_buf, int num_rows) + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) { jcopy_sample_rows(input_buf[0], (int) input_row, output_buf, 0, - num_rows, cinfo->output_width); + num_rows, cinfo->output_width); } @@ -7421,11 +7450,11 @@ grayscale_convert (j_decompress_ptr cinfo, METHODDEF(void) gray_rgb_convert (j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, JDIMENSION input_row, - JSAMPARRAY output_buf, int num_rows) + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) { - register JSAMPROW inptr, outptr; - register JDIMENSION col; + JSAMPROW inptr, outptr; + JDIMENSION col; JDIMENSION num_cols = cinfo->output_width; while (--num_rows >= 0) { @@ -7449,21 +7478,21 @@ gray_rgb_convert (j_decompress_ptr cinfo, METHODDEF(void) ycck_cmyk_convert (j_decompress_ptr cinfo, - JSAMPIMAGE input_buf, JDIMENSION input_row, - JSAMPARRAY output_buf, int num_rows) + JSAMPIMAGE input_buf, JDIMENSION input_row, + JSAMPARRAY output_buf, int num_rows) { my_cconvert_ptr cconvert = (my_cconvert_ptr) cinfo->cconvert; - register int y, cb, cr; - register JSAMPROW outptr; - register JSAMPROW inptr0, inptr1, inptr2, inptr3; - register JDIMENSION col; + int y, cb, cr; + JSAMPROW outptr; + JSAMPROW inptr0, inptr1, inptr2, inptr3; + JDIMENSION col; JDIMENSION num_cols = cinfo->output_width; /* copy these pointers into registers if possible */ - register JSAMPLE * range_limit = cinfo->sample_range_limit; - register int * Crrtab = cconvert->Cr_r_tab; - register int * Cbbtab = cconvert->Cb_b_tab; - register INT32 * Crgtab = cconvert->Cr_g_tab; - register INT32 * Cbgtab = cconvert->Cb_g_tab; + JSAMPLE * range_limit = cinfo->sample_range_limit; + int * Crrtab = cconvert->Cr_r_tab; + int * Cbbtab = cconvert->Cb_b_tab; + INT32 * Crgtab = cconvert->Cr_g_tab; + INT32 * Cbgtab = cconvert->Cb_g_tab; SHIFT_TEMPS while (--num_rows >= 0) { @@ -7478,13 +7507,13 @@ ycck_cmyk_convert (j_decompress_ptr cinfo, cb = GETJSAMPLE(inptr1[col]); cr = GETJSAMPLE(inptr2[col]); /* Range-limiting is essential due to noise introduced by DCT losses. */ - outptr[0] = range_limit[MAXJSAMPLE - (y + Crrtab[cr])]; /* red */ - outptr[1] = range_limit[MAXJSAMPLE - (y + /* green */ - ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], - SCALEBITS)))]; - outptr[2] = range_limit[MAXJSAMPLE - (y + Cbbtab[cb])]; /* blue */ + outptr[0] = range_limit[MAXJSAMPLE - (y + Crrtab[cr])]; /* red */ + outptr[1] = range_limit[MAXJSAMPLE - (y + /* green */ + ((int) RIGHT_SHIFT(Cbgtab[cb] + Crgtab[cr], + SCALEBITS)))]; + outptr[2] = range_limit[MAXJSAMPLE - (y + Cbbtab[cb])]; /* blue */ /* K passes through unchanged */ - outptr[3] = inptr3[col]; /* don't need GETJSAMPLE here */ + outptr[3] = inptr3[col]; /* don't need GETJSAMPLE here */ outptr += 4; } } @@ -7514,7 +7543,7 @@ jinit_color_deconverter (j_decompress_ptr cinfo) cconvert = (my_cconvert_ptr) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(my_color_deconverter)); + SIZEOF(my_color_deconverter)); cinfo->cconvert = (struct jpeg_color_deconverter *) cconvert; cconvert->pub.start_pass = start_pass_dcolor; @@ -7537,7 +7566,7 @@ jinit_color_deconverter (j_decompress_ptr cinfo) ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); break; - default: /* JCS_UNKNOWN can be anything */ + default: /* JCS_UNKNOWN can be anything */ if (cinfo->num_components < 1) ERREXIT(cinfo, JERR_BAD_J_COLORSPACE); break; @@ -7552,11 +7581,11 @@ jinit_color_deconverter (j_decompress_ptr cinfo) case JCS_GRAYSCALE: cinfo->out_color_components = 1; if (cinfo->jpeg_color_space == JCS_GRAYSCALE || - cinfo->jpeg_color_space == JCS_YCbCr) { + cinfo->jpeg_color_space == JCS_YCbCr) { cconvert->pub.color_convert = grayscale_convert; /* For color->grayscale conversion, only the Y (0) component is needed */ for (ci = 1; ci < cinfo->num_components; ci++) - cinfo->comp_info[ci].component_needed = FALSE; + cinfo->comp_info[ci].component_needed = FALSE; } else ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); break; @@ -7590,7 +7619,7 @@ jinit_color_deconverter (j_decompress_ptr cinfo) if (cinfo->out_color_space == cinfo->jpeg_color_space) { cinfo->out_color_components = cinfo->num_components; cconvert->pub.color_convert = null_convert; - } else /* unsupported non-null conversion */ + } else /* unsupported non-null conversion */ ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL); break; } @@ -7674,8 +7703,8 @@ jpeg_destroy (j_common_ptr cinfo) /* NB: mem pointer is NULL if memory mgr failed to initialize. */ if (cinfo->mem != NULL) (*cinfo->mem->self_destruct) (cinfo); - cinfo->mem = NULL; /* be safe if jpeg_destroy is called twice */ - cinfo->global_state = 0; /* mark it destroyed */ + cinfo->mem = NULL; /* be safe if jpeg_destroy is called twice */ + cinfo->global_state = 0; /* mark it destroyed */ } @@ -7691,7 +7720,7 @@ jpeg_alloc_quant_table (j_common_ptr cinfo) tbl = (JQUANT_TBL *) (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JQUANT_TBL)); - tbl->sent_table = FALSE; /* make sure this is false in any new table */ + tbl->sent_table = FALSE; /* make sure this is false in any new table */ return tbl; } @@ -7703,7 +7732,7 @@ jpeg_alloc_huff_table (j_common_ptr cinfo) tbl = (JHUFF_TBL *) (*cinfo->mem->alloc_small) (cinfo, JPOOL_PERMANENT, SIZEOF(JHUFF_TBL)); - tbl->sent_table = FALSE; /* make sure this is false in any new table */ + tbl->sent_table = FALSE; /* make sure this is false in any new table */ return tbl; } /* @@ -7729,7 +7758,7 @@ jpeg_alloc_huff_table (j_common_ptr cinfo) * of a DCT block read in natural order (left to right, top to bottom). */ -#if 0 /* This table is not actually needed in v6a */ +#if 0 /* This table is not actually needed in v6a */ const int jpeg_zigzag_order[DCTSIZE2] = { 0, 1, 5, 6, 14, 15, 27, 28, @@ -7804,34 +7833,34 @@ jround_up (long a, long b) * is not all that great, because these routines aren't very heavily used.) */ -#ifndef NEED_FAR_POINTERS /* normal case, same as regular macros */ -#define jui_jui_FMEMCOPY(dest,src,size) MEMCOPY(dest,src,size) -#define jui_jui_FMEMZERO(target,size) MEMZERO(target,size) -#else /* 80x86 case, define if we can */ +#ifndef NEED_FAR_POINTERS /* normal case, same as regular macros */ +#define jui_jui_FMEMCOPY(dest,src,size) MEMCOPY(dest,src,size) +#define jui_jui_FMEMZERO(target,size) MEMZERO(target,size) +#else /* 80x86 case, define if we can */ #ifdef USE_FMEM -#define jui_jui_FMEMCOPY(dest,src,size) _fmemcpy((void FAR *)(dest), (const void FAR *)(src), (size_t)(size)) -#define jui_jui_FMEMZERO(target,size) _fmemset((void FAR *)(target), 0, (size_t)(size)) +#define jui_jui_FMEMCOPY(dest,src,size) _fmemcpy((void FAR *)(dest), (const void FAR *)(src), (size_t)(size)) +#define jui_jui_FMEMZERO(target,size) _fmemset((void FAR *)(target), 0, (size_t)(size)) #endif #endif GLOBAL(void) jcopy_sample_rows (JSAMPARRAY input_array, int source_row, - JSAMPARRAY output_array, int dest_row, - int num_rows, JDIMENSION num_cols) + JSAMPARRAY output_array, int dest_row, + int num_rows, JDIMENSION num_cols) /* Copy some rows of samples from one place to another. * num_rows rows are copied from input_array[source_row++] * to output_array[dest_row++]; these areas may overlap for duplication. * The source and destination arrays must be at least as wide as num_cols. */ { - register JSAMPROW inptr, outptr; + JSAMPROW inptr, outptr; #ifdef FMEMCOPY - register size_t count = (size_t) (num_cols * SIZEOF(JSAMPLE)); + size_t count = (size_t) (num_cols * SIZEOF(JSAMPLE)); #else - register JDIMENSION count; + JDIMENSION count; #endif - register int row; + int row; input_array += source_row; output_array += dest_row; @@ -7843,7 +7872,7 @@ jcopy_sample_rows (JSAMPARRAY input_array, int source_row, jui_jui_FMEMCOPY(outptr, inptr, count); #else for (count = num_cols; count > 0; count--) - *outptr++ = *inptr++; /* needn't bother with GETJSAMPLE() here */ + *outptr++ = *inptr++; /* needn't bother with GETJSAMPLE() here */ #endif } } @@ -7851,14 +7880,14 @@ jcopy_sample_rows (JSAMPARRAY input_array, int source_row, GLOBAL(void) jcopy_block_row (JBLOCKROW input_row, JBLOCKROW output_row, - JDIMENSION num_blocks) + JDIMENSION num_blocks) /* Copy a row of coefficient blocks from one place to another. */ { #ifdef FMEMCOPY jui_jui_FMEMCOPY(output_row, input_row, num_blocks * (DCTSIZE2 * SIZEOF(JCOEF))); #else - register JCOEFPTR inptr, outptr; - register long count; + JCOEFPTR inptr, outptr; + long count; inptr = (JCOEFPTR) input_row; outptr = (JCOEFPTR) output_row; @@ -7877,8 +7906,8 @@ jzero_far (void FAR * target, size_t bytestozero) #ifdef FMEMZERO jui_jui_FMEMZERO(target, bytestozero); #else - register char FAR * ptr = (char FAR *) target; - register size_t count; + char FAR * ptr = (char FAR *) target; + size_t count; for (count = bytestozero; count > 0; count--) { *ptr++ = 0; @@ -7923,7 +7952,9 @@ jmp_buf jpeg_state; METHODDEF(void) error_exit (j_common_ptr cinfo) { - longjmp(jpeg_state, 1); + jpeg_destroy(cinfo); /* don't just abort, actually destroy the object */ + + longjmp(jpeg_state, 1); } @@ -7991,17 +8022,17 @@ reset_error_mgr (j_common_ptr cinfo) { cinfo->err->num_warnings = 0; /* trace_level is not reset since it is an application-supplied parameter */ - cinfo->err->msg_code = 0; /* may be useful as a flag for "no error" */ + cinfo->err->msg_code = 0; /* may be useful as a flag for "no error" */ } /* * Fill in the standard error-handling methods in a jpeg_error_mgr object. * Typical call is: - * struct jpeg_compress_struct cinfo; - * struct jpeg_error_mgr err; + * struct jpeg_compress_struct cinfo; + * struct jpeg_error_mgr err; * - * cinfo.err = jpeg_std_error(&err); + * cinfo.err = jpeg_std_error(&err); * after which the application may override some of the methods. */ @@ -8014,9 +8045,9 @@ jpeg_std_error (struct jpeg_error_mgr * err) err->format_message = format_message; err->reset_error_mgr = reset_error_mgr; - err->trace_level = 0; /* default = no tracing */ - err->num_warnings = 0; /* no warnings emitted yet */ - err->msg_code = 0; /* may be useful as a flag for "no error" */ + err->trace_level = 0; /* default = no tracing */ + err->num_warnings = 0; /* no warnings emitted yet */ + err->msg_code = 0; /* may be useful as a flag for "no error" */ return err; } @@ -8047,13 +8078,13 @@ jpeg_std_error (struct jpeg_error_mgr * err) */ #define JPEG_INTERNALS -#define AM_MEMORY_MANAGER /* we define jvirt_Xarray_control structs */ +#define AM_MEMORY_MANAGER /* we define jvirt_Xarray_control structs */ //#include "jinclude.h" //#include "jpeglib.h" -//#include "jmemsys.h" /* import the system-dependent declarations */ +//#include "jmemsys.h" /* import the system-dependent declarations */ #ifndef NO_GETENV -#ifndef HAVE_STDLIB_H /* should declare getenv() */ +#ifndef HAVE_STDLIB_H /* should declare getenv() */ extern char * getenv JPP((const char * name)); #endif #endif @@ -8091,7 +8122,7 @@ extern char * getenv JPP((const char * name)); * such a compiler. */ -#ifndef ALIGN_TYPE /* so can override from jconfig.h */ +#ifndef ALIGN_TYPE /* so can override from jconfig.h */ #define ALIGN_TYPE double #endif @@ -8112,22 +8143,22 @@ typedef union small_pool_struct * small_pool_ptr; typedef union small_pool_struct { struct { - small_pool_ptr next; /* next in list of pools */ - size_t bytes_used; /* how many bytes already used within pool */ - size_t bytes_left; /* bytes still available in this pool */ + small_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ } hdr; - ALIGN_TYPE dummy; /* included in union to ensure alignment */ + ALIGN_TYPE dummy; /* included in union to ensure alignment */ } small_pool_hdr; typedef union large_pool_struct FAR * large_pool_ptr; typedef union large_pool_struct { struct { - large_pool_ptr next; /* next in list of pools */ - size_t bytes_used; /* how many bytes already used within pool */ - size_t bytes_left; /* bytes still available in this pool */ + large_pool_ptr next; /* next in list of pools */ + size_t bytes_used; /* how many bytes already used within pool */ + size_t bytes_left; /* bytes still available in this pool */ } hdr; - ALIGN_TYPE dummy; /* included in union to ensure alignment */ + ALIGN_TYPE dummy; /* included in union to ensure alignment */ } large_pool_hdr; @@ -8136,7 +8167,7 @@ typedef union large_pool_struct { */ typedef struct { - struct jpeg_memory_mgr pub; /* public fields */ + struct jpeg_memory_mgr pub; /* public fields */ /* Each pool identifier (lifetime class) names a linked list of pools. */ small_pool_ptr small_list[JPOOL_NUMPOOLS]; @@ -8156,7 +8187,7 @@ typedef struct { /* alloc_sarray and alloc_barray set this value for use by virtual * array routines. */ - JDIMENSION last_rowsperchunk; /* from most recent alloc_sarray/barray */ + JDIMENSION last_rowsperchunk; /* from most recent alloc_sarray/barray */ } my_memory_mgr; typedef my_memory_mgr * my_mem_ptr; @@ -8170,39 +8201,39 @@ typedef my_memory_mgr * my_mem_ptr; */ struct jvirt_sarray_control { - JSAMPARRAY mem_buffer; /* => the in-memory buffer */ - JDIMENSION rows_in_array; /* total virtual array height */ - JDIMENSION samplesperrow; /* width of array (and of memory buffer) */ - JDIMENSION maxaccess; /* max rows accessed by access_virt_sarray */ - JDIMENSION rows_in_mem; /* height of memory buffer */ - JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ - JDIMENSION cur_start_row; /* first logical row # in the buffer */ - JDIMENSION first_undef_row; /* row # of first uninitialized row */ - boolean pre_zero; /* pre-zero mode requested? */ - boolean dirty; /* do current buffer contents need written? */ - boolean b_s_open; /* is backing-store data valid? */ - jvirt_sarray_ptr next; /* link to next virtual sarray control block */ - backing_store_info b_s_info; /* System-dependent control info */ + JSAMPARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION samplesperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_sarray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_sarray_ptr next; /* link to next virtual sarray control block */ + backing_store_info b_s_info; /* System-dependent control info */ }; struct jvirt_barray_control { - JBLOCKARRAY mem_buffer; /* => the in-memory buffer */ - JDIMENSION rows_in_array; /* total virtual array height */ - JDIMENSION blocksperrow; /* width of array (and of memory buffer) */ - JDIMENSION maxaccess; /* max rows accessed by access_virt_barray */ - JDIMENSION rows_in_mem; /* height of memory buffer */ - JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ - JDIMENSION cur_start_row; /* first logical row # in the buffer */ - JDIMENSION first_undef_row; /* row # of first uninitialized row */ - boolean pre_zero; /* pre-zero mode requested? */ - boolean dirty; /* do current buffer contents need written? */ - boolean b_s_open; /* is backing-store data valid? */ - jvirt_barray_ptr next; /* link to next virtual barray control block */ - backing_store_info b_s_info; /* System-dependent control info */ + JBLOCKARRAY mem_buffer; /* => the in-memory buffer */ + JDIMENSION rows_in_array; /* total virtual array height */ + JDIMENSION blocksperrow; /* width of array (and of memory buffer) */ + JDIMENSION maxaccess; /* max rows accessed by access_virt_barray */ + JDIMENSION rows_in_mem; /* height of memory buffer */ + JDIMENSION rowsperchunk; /* allocation chunk size in mem_buffer */ + JDIMENSION cur_start_row; /* first logical row # in the buffer */ + JDIMENSION first_undef_row; /* row # of first uninitialized row */ + boolean pre_zero; /* pre-zero mode requested? */ + boolean dirty; /* do current buffer contents need written? */ + boolean b_s_open; /* is backing-store data valid? */ + jvirt_barray_ptr next; /* link to next virtual barray control block */ + backing_store_info b_s_info; /* System-dependent control info */ }; -#ifdef MEM_STATS /* optional extra stuff for statistics */ +#ifdef MEM_STATS /* optional extra stuff for statistics */ LOCAL(void) print_mem_stats (j_common_ptr cinfo, int pool_id) @@ -8216,32 +8247,32 @@ print_mem_stats (j_common_ptr cinfo, int pool_id) * This is helpful because message parm array can't handle longs. */ fprintf(stderr, "Freeing pool %d, total space = %ld\n", - pool_id, mem->total_space_allocated); + pool_id, mem->total_space_allocated); for (lhdr_ptr = mem->large_list[pool_id]; lhdr_ptr != NULL; lhdr_ptr = lhdr_ptr->hdr.next) { fprintf(stderr, " Large chunk used %ld\n", - (long) lhdr_ptr->hdr.bytes_used); + (long) lhdr_ptr->hdr.bytes_used); } for (shdr_ptr = mem->small_list[pool_id]; shdr_ptr != NULL; shdr_ptr = shdr_ptr->hdr.next) { fprintf(stderr, " Small chunk used %ld free %ld\n", - (long) shdr_ptr->hdr.bytes_used, - (long) shdr_ptr->hdr.bytes_left); + (long) shdr_ptr->hdr.bytes_used, + (long) shdr_ptr->hdr.bytes_left); } } #endif /* MEM_STATS */ - +/* coverity[+kill] */ LOCAL(void) out_of_memory (j_common_ptr cinfo, int which) /* Report an out-of-memory error and stop execution */ /* If we compiled MEM_STATS support, report alloc requests before dying */ { #ifdef MEM_STATS - cinfo->err->trace_level = 2; /* force self_destruct to report stats */ + cinfo->err->trace_level = 2; /* force self_destruct to report stats */ #endif ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, which); } @@ -8260,19 +8291,19 @@ out_of_memory (j_common_ptr cinfo, int which) * machines, but may be too small if longs are 64 bits or more. */ -static const size_t first_pool_slop[JPOOL_NUMPOOLS] = +static const size_t first_pool_slop[JPOOL_NUMPOOLS] = { - 1600, /* first PERMANENT pool */ - 16000 /* first IMAGE pool */ + 1600, /* first PERMANENT pool */ + 16000 /* first IMAGE pool */ }; -static const size_t extra_pool_slop[JPOOL_NUMPOOLS] = +static const size_t extra_pool_slop[JPOOL_NUMPOOLS] = { - 0, /* additional PERMANENT pools */ - 5000 /* additional IMAGE pools */ + 0, /* additional PERMANENT pools */ + 5000 /* additional IMAGE pools */ }; -#define MIN_SLOP 50 /* greater than 0 to avoid futile looping */ +#define MIN_SLOP 50 /* greater than 0 to avoid futile looping */ METHODDEF(void *) @@ -8286,7 +8317,7 @@ alloc_small (j_common_ptr cinfo, int pool_id, size_t sizeofobject) /* Check for unsatisfiable request (do now to ensure no overflow below) */ if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(small_pool_hdr))) - out_of_memory(cinfo, 1); /* request exceeds malloc's ability */ + out_of_memory(cinfo, 1); /* request exceeds malloc's ability */ /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); @@ -8295,12 +8326,12 @@ alloc_small (j_common_ptr cinfo, int pool_id, size_t sizeofobject) /* See if space is available in any existing pool */ if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) - ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ prev_hdr_ptr = NULL; hdr_ptr = mem->small_list[pool_id]; while (hdr_ptr != NULL) { if (hdr_ptr->hdr.bytes_left >= sizeofobject) - break; /* found pool with enough space */ + break; /* found pool with enough space */ prev_hdr_ptr = hdr_ptr; hdr_ptr = hdr_ptr->hdr.next; } @@ -8309,7 +8340,7 @@ alloc_small (j_common_ptr cinfo, int pool_id, size_t sizeofobject) if (hdr_ptr == NULL) { /* min_request is what we need now, slop is what will be leftover */ min_request = sizeofobject + SIZEOF(small_pool_hdr); - if (prev_hdr_ptr == NULL) /* first pool in class? */ + if (prev_hdr_ptr == NULL) /* first pool in class? */ slop = first_pool_slop[pool_id]; else slop = extra_pool_slop[pool_id]; @@ -8320,17 +8351,17 @@ alloc_small (j_common_ptr cinfo, int pool_id, size_t sizeofobject) for (;;) { hdr_ptr = (small_pool_ptr) jpeg_get_small(cinfo, min_request + slop); if (hdr_ptr != NULL) - break; + break; slop /= 2; - if (slop < MIN_SLOP) /* give up when it gets real small */ - out_of_memory(cinfo, 2); /* jpeg_get_small failed */ + if (slop < MIN_SLOP) /* give up when it gets real small */ + out_of_memory(cinfo, 2); /* jpeg_get_small failed */ } mem->total_space_allocated += min_request + slop; /* Success, initialize the new pool header and add to end of list */ hdr_ptr->hdr.next = NULL; hdr_ptr->hdr.bytes_used = 0; hdr_ptr->hdr.bytes_left = sizeofobject + slop; - if (prev_hdr_ptr == NULL) /* first pool in class? */ + if (prev_hdr_ptr == NULL) /* first pool in class? */ mem->small_list[pool_id] = hdr_ptr; else prev_hdr_ptr->hdr.next = hdr_ptr; @@ -8370,7 +8401,7 @@ alloc_large (j_common_ptr cinfo, int pool_id, size_t sizeofobject) /* Check for unsatisfiable request (do now to ensure no overflow below) */ if (sizeofobject > (size_t) (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr))) - out_of_memory(cinfo, 3); /* request exceeds malloc's ability */ + out_of_memory(cinfo, 3); /* request exceeds malloc's ability */ /* Round up the requested size to a multiple of SIZEOF(ALIGN_TYPE) */ odd_bytes = sizeofobject % SIZEOF(ALIGN_TYPE); @@ -8379,12 +8410,12 @@ alloc_large (j_common_ptr cinfo, int pool_id, size_t sizeofobject) /* Always make a new pool */ if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) - ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ hdr_ptr = (large_pool_ptr) jpeg_get_large(cinfo, sizeofobject + - SIZEOF(large_pool_hdr)); + SIZEOF(large_pool_hdr)); if (hdr_ptr == NULL) - out_of_memory(cinfo, 4); /* jpeg_get_large failed */ + out_of_memory(cinfo, 4); /* jpeg_get_large failed */ mem->total_space_allocated += sizeofobject + SIZEOF(large_pool_hdr); /* Success, initialize the new pool header and add to list */ @@ -8415,7 +8446,7 @@ alloc_large (j_common_ptr cinfo, int pool_id, size_t sizeofobject) METHODDEF(JSAMPARRAY) alloc_sarray (j_common_ptr cinfo, int pool_id, - JDIMENSION samplesperrow, JDIMENSION numrows) + JDIMENSION samplesperrow, JDIMENSION numrows) /* Allocate a 2-D sample array */ { my_mem_ptr mem = (my_mem_ptr) cinfo->mem; @@ -8426,7 +8457,7 @@ alloc_sarray (j_common_ptr cinfo, int pool_id, /* Calculate max # of rows allowed in one allocation chunk */ ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / - ((long) samplesperrow * SIZEOF(JSAMPLE)); + ((long) samplesperrow * SIZEOF(JSAMPLE)); if (ltemp <= 0) ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); if (ltemp < (long) numrows) @@ -8437,15 +8468,15 @@ alloc_sarray (j_common_ptr cinfo, int pool_id, /* Get space for row pointers (small object) */ result = (JSAMPARRAY) alloc_small(cinfo, pool_id, - (size_t) (numrows * SIZEOF(JSAMPROW))); + (size_t) (numrows * SIZEOF(JSAMPROW))); /* Get the rows themselves (large objects) */ currow = 0; while (currow < numrows) { rowsperchunk = MIN(rowsperchunk, numrows - currow); workspace = (JSAMPROW) alloc_large(cinfo, pool_id, - (size_t) ((size_t) rowsperchunk * (size_t) samplesperrow - * SIZEOF(JSAMPLE))); + (size_t) ((size_t) rowsperchunk * (size_t) samplesperrow + * SIZEOF(JSAMPLE))); for (i = rowsperchunk; i > 0; i--) { result[currow++] = workspace; workspace += samplesperrow; @@ -8463,7 +8494,7 @@ alloc_sarray (j_common_ptr cinfo, int pool_id, METHODDEF(JBLOCKARRAY) alloc_barray (j_common_ptr cinfo, int pool_id, - JDIMENSION blocksperrow, JDIMENSION numrows) + JDIMENSION blocksperrow, JDIMENSION numrows) /* Allocate a 2-D coefficient-block array */ { my_mem_ptr mem = (my_mem_ptr) cinfo->mem; @@ -8474,7 +8505,7 @@ alloc_barray (j_common_ptr cinfo, int pool_id, /* Calculate max # of rows allowed in one allocation chunk */ ltemp = (MAX_ALLOC_CHUNK-SIZEOF(large_pool_hdr)) / - ((long) blocksperrow * SIZEOF(JBLOCK)); + ((long) blocksperrow * SIZEOF(JBLOCK)); if (ltemp <= 0) ERREXIT(cinfo, JERR_WIDTH_OVERFLOW); if (ltemp < (long) numrows) @@ -8485,15 +8516,15 @@ alloc_barray (j_common_ptr cinfo, int pool_id, /* Get space for row pointers (small object) */ result = (JBLOCKARRAY) alloc_small(cinfo, pool_id, - (size_t) (numrows * SIZEOF(JBLOCKROW))); + (size_t) (numrows * SIZEOF(JBLOCKROW))); /* Get the rows themselves (large objects) */ currow = 0; while (currow < numrows) { rowsperchunk = MIN(rowsperchunk, numrows - currow); workspace = (JBLOCKROW) alloc_large(cinfo, pool_id, - (size_t) ((size_t) rowsperchunk * (size_t) blocksperrow - * SIZEOF(JBLOCK))); + (size_t) ((size_t) rowsperchunk * (size_t) blocksperrow + * SIZEOF(JBLOCK))); for (i = rowsperchunk; i > 0; i--) { result[currow++] = workspace; workspace += blocksperrow; @@ -8543,8 +8574,8 @@ alloc_barray (j_common_ptr cinfo, int pool_id, METHODDEF(jvirt_sarray_ptr) request_virt_sarray (j_common_ptr cinfo, int pool_id, boolean pre_zero, - JDIMENSION samplesperrow, JDIMENSION numrows, - JDIMENSION maxaccess) + JDIMENSION samplesperrow, JDIMENSION numrows, + JDIMENSION maxaccess) /* Request a virtual 2-D sample array */ { my_mem_ptr mem = (my_mem_ptr) cinfo->mem; @@ -8552,18 +8583,18 @@ request_virt_sarray (j_common_ptr cinfo, int pool_id, boolean pre_zero, /* Only IMAGE-lifetime virtual arrays are currently supported */ if (pool_id != JPOOL_IMAGE) - ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ /* get control block */ result = (jvirt_sarray_ptr) alloc_small(cinfo, pool_id, - SIZEOF(struct jvirt_sarray_control)); + SIZEOF(struct jvirt_sarray_control)); - result->mem_buffer = NULL; /* marks array not yet realized */ + result->mem_buffer = NULL; /* marks array not yet realized */ result->rows_in_array = numrows; result->samplesperrow = samplesperrow; result->maxaccess = maxaccess; result->pre_zero = pre_zero; - result->b_s_open = FALSE; /* no associated backing-store object */ + result->b_s_open = FALSE; /* no associated backing-store object */ result->next = mem->virt_sarray_list; /* add to list of virtual arrays */ mem->virt_sarray_list = result; @@ -8573,8 +8604,8 @@ request_virt_sarray (j_common_ptr cinfo, int pool_id, boolean pre_zero, METHODDEF(jvirt_barray_ptr) request_virt_barray (j_common_ptr cinfo, int pool_id, boolean pre_zero, - JDIMENSION blocksperrow, JDIMENSION numrows, - JDIMENSION maxaccess) + JDIMENSION blocksperrow, JDIMENSION numrows, + JDIMENSION maxaccess) /* Request a virtual 2-D coefficient-block array */ { my_mem_ptr mem = (my_mem_ptr) cinfo->mem; @@ -8582,18 +8613,18 @@ request_virt_barray (j_common_ptr cinfo, int pool_id, boolean pre_zero, /* Only IMAGE-lifetime virtual arrays are currently supported */ if (pool_id != JPOOL_IMAGE) - ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ /* get control block */ result = (jvirt_barray_ptr) alloc_small(cinfo, pool_id, - SIZEOF(struct jvirt_barray_control)); + SIZEOF(struct jvirt_barray_control)); - result->mem_buffer = NULL; /* marks array not yet realized */ + result->mem_buffer = NULL; /* marks array not yet realized */ result->rows_in_array = numrows; result->blocksperrow = blocksperrow; result->maxaccess = maxaccess; result->pre_zero = pre_zero; - result->b_s_open = FALSE; /* no associated backing-store object */ + result->b_s_open = FALSE; /* no associated backing-store object */ result->next = mem->virt_barray_list; /* add to list of virtual arrays */ mem->virt_barray_list = result; @@ -8620,26 +8651,26 @@ realize_virt_arrays (j_common_ptr cinfo) for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { if (sptr->mem_buffer == NULL) { /* if not realized yet */ space_per_minheight += (long) sptr->maxaccess * - (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); maximum_space += (long) sptr->rows_in_array * - (long) sptr->samplesperrow * SIZEOF(JSAMPLE); + (long) sptr->samplesperrow * SIZEOF(JSAMPLE); } } for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { if (bptr->mem_buffer == NULL) { /* if not realized yet */ space_per_minheight += (long) bptr->maxaccess * - (long) bptr->blocksperrow * SIZEOF(JBLOCK); + (long) bptr->blocksperrow * SIZEOF(JBLOCK); maximum_space += (long) bptr->rows_in_array * - (long) bptr->blocksperrow * SIZEOF(JBLOCK); + (long) bptr->blocksperrow * SIZEOF(JBLOCK); } } if (space_per_minheight <= 0) - return; /* no unrealized arrays, no work */ + return; /* no unrealized arrays, no work */ /* Determine amount of memory to actually use; this is system-dependent. */ avail_mem = jpeg_mem_available(cinfo, space_per_minheight, maximum_space, - mem->total_space_allocated); + mem->total_space_allocated); /* If the maximum space needed is available, make all the buffers full * height; otherwise parcel it out with the same number of minheights @@ -8662,19 +8693,19 @@ realize_virt_arrays (j_common_ptr cinfo) if (sptr->mem_buffer == NULL) { /* if not realized yet */ minheights = ((long) sptr->rows_in_array - 1L) / sptr->maxaccess + 1L; if (minheights <= max_minheights) { - /* This buffer fits in memory */ - sptr->rows_in_mem = sptr->rows_in_array; + /* This buffer fits in memory */ + sptr->rows_in_mem = sptr->rows_in_array; } else { - /* It doesn't fit in memory, create backing store. */ - sptr->rows_in_mem = (JDIMENSION) (max_minheights * sptr->maxaccess); - jpeg_open_backing_store(cinfo, & sptr->b_s_info, - (long) sptr->rows_in_array * - (long) sptr->samplesperrow * - (long) SIZEOF(JSAMPLE)); - sptr->b_s_open = TRUE; + /* It doesn't fit in memory, create backing store. */ + sptr->rows_in_mem = (JDIMENSION) (max_minheights * sptr->maxaccess); + jpeg_open_backing_store(cinfo, & sptr->b_s_info, + (long) sptr->rows_in_array * + (long) sptr->samplesperrow * + (long) SIZEOF(JSAMPLE)); + sptr->b_s_open = TRUE; } sptr->mem_buffer = alloc_sarray(cinfo, JPOOL_IMAGE, - sptr->samplesperrow, sptr->rows_in_mem); + sptr->samplesperrow, sptr->rows_in_mem); sptr->rowsperchunk = mem->last_rowsperchunk; sptr->cur_start_row = 0; sptr->first_undef_row = 0; @@ -8686,19 +8717,19 @@ realize_virt_arrays (j_common_ptr cinfo) if (bptr->mem_buffer == NULL) { /* if not realized yet */ minheights = ((long) bptr->rows_in_array - 1L) / bptr->maxaccess + 1L; if (minheights <= max_minheights) { - /* This buffer fits in memory */ - bptr->rows_in_mem = bptr->rows_in_array; + /* This buffer fits in memory */ + bptr->rows_in_mem = bptr->rows_in_array; } else { - /* It doesn't fit in memory, create backing store. */ - bptr->rows_in_mem = (JDIMENSION) (max_minheights * bptr->maxaccess); - jpeg_open_backing_store(cinfo, & bptr->b_s_info, - (long) bptr->rows_in_array * - (long) bptr->blocksperrow * - (long) SIZEOF(JBLOCK)); - bptr->b_s_open = TRUE; + /* It doesn't fit in memory, create backing store. */ + bptr->rows_in_mem = (JDIMENSION) (max_minheights * bptr->maxaccess); + jpeg_open_backing_store(cinfo, & bptr->b_s_info, + (long) bptr->rows_in_array * + (long) bptr->blocksperrow * + (long) SIZEOF(JBLOCK)); + bptr->b_s_open = TRUE; } bptr->mem_buffer = alloc_barray(cinfo, JPOOL_IMAGE, - bptr->blocksperrow, bptr->rows_in_mem); + bptr->blocksperrow, bptr->rows_in_mem); bptr->rowsperchunk = mem->last_rowsperchunk; bptr->cur_start_row = 0; bptr->first_undef_row = 0; @@ -8725,17 +8756,17 @@ do_sarray_io (j_common_ptr cinfo, jvirt_sarray_ptr ptr, boolean writing) rows = MIN(rows, (long) ptr->first_undef_row - thisrow); /* Transfer no more than fits in file */ rows = MIN(rows, (long) ptr->rows_in_array - thisrow); - if (rows <= 0) /* this chunk might be past end of file! */ + if (rows <= 0) /* this chunk might be past end of file! */ break; byte_count = rows * bytesperrow; if (writing) (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, - (void FAR *) ptr->mem_buffer[i], - file_offset, byte_count); + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); else (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, - (void FAR *) ptr->mem_buffer[i], - file_offset, byte_count); + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); file_offset += byte_count; } } @@ -8758,17 +8789,17 @@ do_barray_io (j_common_ptr cinfo, jvirt_barray_ptr ptr, boolean writing) rows = MIN(rows, (long) ptr->first_undef_row - thisrow); /* Transfer no more than fits in file */ rows = MIN(rows, (long) ptr->rows_in_array - thisrow); - if (rows <= 0) /* this chunk might be past end of file! */ + if (rows <= 0) /* this chunk might be past end of file! */ break; byte_count = rows * bytesperrow; if (writing) (*ptr->b_s_info.write_backing_store) (cinfo, & ptr->b_s_info, - (void FAR *) ptr->mem_buffer[i], - file_offset, byte_count); + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); else (*ptr->b_s_info.read_backing_store) (cinfo, & ptr->b_s_info, - (void FAR *) ptr->mem_buffer[i], - file_offset, byte_count); + (void FAR *) ptr->mem_buffer[i], + file_offset, byte_count); file_offset += byte_count; } } @@ -8776,8 +8807,8 @@ do_barray_io (j_common_ptr cinfo, jvirt_barray_ptr ptr, boolean writing) METHODDEF(JSAMPARRAY) access_virt_sarray (j_common_ptr cinfo, jvirt_sarray_ptr ptr, - JDIMENSION start_row, JDIMENSION num_rows, - boolean writable) + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable) /* Access the part of a virtual sample array starting at start_row */ /* and extending for num_rows rows. writable is true if */ /* caller intends to modify the accessed area. */ @@ -8815,7 +8846,7 @@ access_virt_sarray (j_common_ptr cinfo, jvirt_sarray_ptr ptr, ltemp = (long) end_row - (long) ptr->rows_in_mem; if (ltemp < 0) - ltemp = 0; /* don't fall off front end of file */ + ltemp = 0; /* don't fall off front end of file */ ptr->cur_start_row = (JDIMENSION) ltemp; } /* Read in the selected part of the array. @@ -8830,9 +8861,9 @@ access_virt_sarray (j_common_ptr cinfo, jvirt_sarray_ptr ptr, */ if (ptr->first_undef_row < end_row) { if (ptr->first_undef_row < start_row) { - if (writable) /* writer skipped over a section of array */ - ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); - undef_row = start_row; /* but reader is allowed to read ahead */ + if (writable) /* writer skipped over a section of array */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + undef_row = start_row; /* but reader is allowed to read ahead */ } else { undef_row = ptr->first_undef_row; } @@ -8843,12 +8874,12 @@ access_virt_sarray (j_common_ptr cinfo, jvirt_sarray_ptr ptr, undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ end_row -= ptr->cur_start_row; while (undef_row < end_row) { - jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); - undef_row++; + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + undef_row++; } } else { - if (! writable) /* reader looking at undefined data */ - ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + if (! writable) /* reader looking at undefined data */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); } } /* Flag the buffer dirty if caller will write in it */ @@ -8861,8 +8892,8 @@ access_virt_sarray (j_common_ptr cinfo, jvirt_sarray_ptr ptr, METHODDEF(JBLOCKARRAY) access_virt_barray (j_common_ptr cinfo, jvirt_barray_ptr ptr, - JDIMENSION start_row, JDIMENSION num_rows, - boolean writable) + JDIMENSION start_row, JDIMENSION num_rows, + boolean writable) /* Access the part of a virtual block array starting at start_row */ /* and extending for num_rows rows. writable is true if */ /* caller intends to modify the accessed area. */ @@ -8900,7 +8931,7 @@ access_virt_barray (j_common_ptr cinfo, jvirt_barray_ptr ptr, ltemp = (long) end_row - (long) ptr->rows_in_mem; if (ltemp < 0) - ltemp = 0; /* don't fall off front end of file */ + ltemp = 0; /* don't fall off front end of file */ ptr->cur_start_row = (JDIMENSION) ltemp; } /* Read in the selected part of the array. @@ -8915,9 +8946,9 @@ access_virt_barray (j_common_ptr cinfo, jvirt_barray_ptr ptr, */ if (ptr->first_undef_row < end_row) { if (ptr->first_undef_row < start_row) { - if (writable) /* writer skipped over a section of array */ - ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); - undef_row = start_row; /* but reader is allowed to read ahead */ + if (writable) /* writer skipped over a section of array */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + undef_row = start_row; /* but reader is allowed to read ahead */ } else { undef_row = ptr->first_undef_row; } @@ -8928,12 +8959,12 @@ access_virt_barray (j_common_ptr cinfo, jvirt_barray_ptr ptr, undef_row -= ptr->cur_start_row; /* make indexes relative to buffer */ end_row -= ptr->cur_start_row; while (undef_row < end_row) { - jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); - undef_row++; + jzero_far((void FAR *) ptr->mem_buffer[undef_row], bytesperrow); + undef_row++; } } else { - if (! writable) /* reader looking at undefined data */ - ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); + if (! writable) /* reader looking at undefined data */ + ERREXIT(cinfo, JERR_BAD_VIRTUAL_ACCESS); } } /* Flag the buffer dirty if caller will write in it */ @@ -8957,7 +8988,7 @@ free_pool (j_common_ptr cinfo, int pool_id) size_t space_freed; if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) - ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ + ERREXIT1(cinfo, JERR_BAD_POOL_ID, pool_id); /* safety check */ #ifdef MEM_STATS if (cinfo->err->trace_level > 1) @@ -8970,16 +9001,16 @@ free_pool (j_common_ptr cinfo, int pool_id) jvirt_barray_ptr bptr; for (sptr = mem->virt_sarray_list; sptr != NULL; sptr = sptr->next) { - if (sptr->b_s_open) { /* there may be no backing store */ - sptr->b_s_open = FALSE; /* prevent recursive close if error */ - (*sptr->b_s_info.close_backing_store) (cinfo, & sptr->b_s_info); + if (sptr->b_s_open) { /* there may be no backing store */ + sptr->b_s_open = FALSE; /* prevent recursive close if error */ + (*sptr->b_s_info.close_backing_store) (cinfo, & sptr->b_s_info); } } mem->virt_sarray_list = NULL; for (bptr = mem->virt_barray_list; bptr != NULL; bptr = bptr->next) { - if (bptr->b_s_open) { /* there may be no backing store */ - bptr->b_s_open = FALSE; /* prevent recursive close if error */ - (*bptr->b_s_info.close_backing_store) (cinfo, & bptr->b_s_info); + if (bptr->b_s_open) { /* there may be no backing store */ + bptr->b_s_open = FALSE; /* prevent recursive close if error */ + (*bptr->b_s_info.close_backing_store) (cinfo, & bptr->b_s_info); } } mem->virt_barray_list = NULL; @@ -8992,8 +9023,8 @@ free_pool (j_common_ptr cinfo, int pool_id) while (lhdr_ptr != NULL) { large_pool_ptr next_lhdr_ptr = lhdr_ptr->hdr.next; space_freed = lhdr_ptr->hdr.bytes_used + - lhdr_ptr->hdr.bytes_left + - SIZEOF(large_pool_hdr); + lhdr_ptr->hdr.bytes_left + + SIZEOF(large_pool_hdr); jpeg_free_large(cinfo, (void FAR *) lhdr_ptr, space_freed); mem->total_space_allocated -= space_freed; lhdr_ptr = next_lhdr_ptr; @@ -9006,8 +9037,8 @@ free_pool (j_common_ptr cinfo, int pool_id) while (shdr_ptr != NULL) { small_pool_ptr next_shdr_ptr = shdr_ptr->hdr.next; space_freed = shdr_ptr->hdr.bytes_used + - shdr_ptr->hdr.bytes_left + - SIZEOF(small_pool_hdr); + shdr_ptr->hdr.bytes_left + + SIZEOF(small_pool_hdr); jpeg_free_small(cinfo, (void *) shdr_ptr, space_freed); mem->total_space_allocated -= space_freed; shdr_ptr = next_shdr_ptr; @@ -9035,9 +9066,9 @@ self_destruct (j_common_ptr cinfo) /* Release the memory manager control block too. */ jpeg_free_small(cinfo, (void *) cinfo->mem, SIZEOF(my_memory_mgr)); - cinfo->mem = NULL; /* ensures I will be called only once */ + cinfo->mem = NULL; /* ensures I will be called only once */ - jpeg_mem_term(cinfo); /* system-dependent cleanup */ + jpeg_mem_term(cinfo); /* system-dependent cleanup */ } @@ -9054,7 +9085,7 @@ jinit_memory_mgr (j_common_ptr cinfo) int pool; size_t test_mac; - cinfo->mem = NULL; /* for safety if init fails */ + cinfo->mem = NULL; /* for safety if init fails */ /* Check for configuration errors. * SIZEOF(ALIGN_TYPE) should be a power of 2; otherwise, it probably @@ -9081,7 +9112,7 @@ jinit_memory_mgr (j_common_ptr cinfo) mem = (my_mem_ptr) jpeg_get_small(cinfo, SIZEOF(my_memory_mgr)); if (mem == NULL) { - jpeg_mem_term(cinfo); /* system-dependent cleanup */ + jpeg_mem_term(cinfo); /* system-dependent cleanup */ ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 0); } @@ -9129,9 +9160,9 @@ jinit_memory_mgr (j_common_ptr cinfo) char ch = 'x'; if (sscanf(memenv, "%ld%c", &max_to_use, &ch) > 0) { - if (ch == 'm' || ch == 'M') - max_to_use *= 1000L; - mem->pub.max_memory_to_use = max_to_use * 1000L; + if (ch == 'm' || ch == 'M') + max_to_use *= 1000L; + mem->pub.max_memory_to_use = max_to_use * 1000L; } } } @@ -9158,9 +9189,9 @@ jinit_memory_mgr (j_common_ptr cinfo) #define JPEG_INTERNALS //#include "jinclude.h" //#include "jpeglib.h" -//#include "jmemsys.h" /* import the system-dependent declarations */ +//#include "jmemsys.h" /* import the system-dependent declarations */ -#ifndef HAVE_STDLIB_H /* should declare malloc(),free() */ +#ifndef HAVE_STDLIB_H /* should declare malloc(),free() */ extern void * malloc JPP((size_t size)); extern void free JPP((void *ptr)); #endif @@ -9211,7 +9242,7 @@ jpeg_free_large (j_common_ptr cinfo, void FAR * object, size_t sizeofobject) GLOBAL(long) jpeg_mem_available (j_common_ptr cinfo, long min_bytes_needed, - long max_bytes_needed, long already_allocated) + long max_bytes_needed, long already_allocated) { return max_bytes_needed; } @@ -9225,7 +9256,7 @@ jpeg_mem_available (j_common_ptr cinfo, long min_bytes_needed, GLOBAL(void) jpeg_open_backing_store (j_common_ptr cinfo, backing_store_ptr info, - long total_bytes_needed) + long total_bytes_needed) { ERREXIT(cinfo, JERR_NO_BACKING_STORE); } @@ -9239,7 +9270,7 @@ jpeg_open_backing_store (j_common_ptr cinfo, backing_store_ptr info, GLOBAL(long) jpeg_mem_init (j_common_ptr cinfo) { - return 0; /* just set max_memory_to_use to 0 */ + return 0; /* just set max_memory_to_use to 0 */ } GLOBAL(void) @@ -9318,9 +9349,9 @@ jpeg_mem_term (j_common_ptr cinfo) * table in both directions. */ -#define ODITHER_SIZE 16 /* dimension of dither matrix */ +#define ODITHER_SIZE 16 /* dimension of dither matrix */ /* NB: if ODITHER_SIZE is not a power of 2, ODITHER_MASK uses will break */ -#define ODITHER_CELLS (ODITHER_SIZE*ODITHER_SIZE) /* # cells in matrix */ +#define ODITHER_CELLS (ODITHER_SIZE*ODITHER_SIZE) /* # cells in matrix */ #define ODITHER_MASK (ODITHER_SIZE-1) /* mask for wrapping around counters */ typedef int ODITHER_MATRIX[ODITHER_SIZE][ODITHER_SIZE]; @@ -9355,8 +9386,8 @@ static const UINT8 base_dither_matrix[ODITHER_SIZE][ODITHER_SIZE] = { * Errors are accumulated into the array fserrors[], at a resolution of * 1/16th of a pixel count. The error at a given pixel is propagated * to its not-yet-processed neighbors using the standard F-S fractions, - * ... (here) 7/16 - * 3/16 5/16 1/16 + * ... (here) 7/16 + * 3/16 5/16 1/16 * We work left-to-right on even rows, right-to-left on odd rows. * * We can get away with a single array (holding one row's worth of errors) @@ -9375,43 +9406,43 @@ static const UINT8 base_dither_matrix[ODITHER_SIZE][ODITHER_SIZE] = { */ #if BITS_IN_JSAMPLE == 8 -typedef INT16 FSERROR; /* 16 bits should be enough */ -typedef int LOCFSERROR; /* use 'int' for calculation temps */ +typedef INT16 FSERROR; /* 16 bits should be enough */ +typedef int LOCFSERROR; /* use 'int' for calculation temps */ #else -typedef INT32 FSERROR; /* may need more than 16 bits */ -typedef INT32 LOCFSERROR; /* be sure calculation temps are big enough */ +typedef INT32 FSERROR; /* may need more than 16 bits */ +typedef INT32 LOCFSERROR; /* be sure calculation temps are big enough */ #endif -typedef FSERROR FAR *FSERRPTR; /* pointer to error array (in FAR storage!) */ +typedef FSERROR FAR *FSERRPTR; /* pointer to error array (in FAR storage!) */ /* Private subobject */ -#define MAX_Q_COMPS 4 /* max components I can handle */ +#define MAX_Q_COMPS 4 /* max components I can handle */ typedef struct { struct jpeg_color_quantizer pub; /* public fields */ /* Initially allocated colormap is saved here */ - JSAMPARRAY sv_colormap; /* The color map as a 2-D pixel array */ - int sv_actual; /* number of entries in use */ + JSAMPARRAY sv_colormap; /* The color map as a 2-D pixel array */ + int sv_actual; /* number of entries in use */ - JSAMPARRAY colorindex; /* Precomputed mapping for speed */ + JSAMPARRAY colorindex; /* Precomputed mapping for speed */ /* colorindex[i][j] = index of color closest to pixel value j in component i, * premultiplied as described above. Since colormap indexes must fit into * JSAMPLEs, the entries of this array will too. */ - boolean is_padded; /* is the colorindex padded for odither? */ + boolean is_padded; /* is the colorindex padded for odither? */ - int Ncolors[MAX_Q_COMPS]; /* # of values alloced to each component */ + int Ncolors[MAX_Q_COMPS]; /* # of values alloced to each component */ /* Variables for ordered dithering */ - int row_index; /* cur row's vertical index in dither matrix */ + int row_index; /* cur row's vertical index in dither matrix */ ODITHER_MATRIX_PTR odither[MAX_Q_COMPS]; /* one dither array per component */ /* Variables for Floyd-Steinberg dithering */ FSERRPTR fserrors[MAX_Q_COMPS]; /* accumulated errors */ - boolean on_odd_row; /* flag to remember which row we are on */ + boolean on_odd_row; /* flag to remember which row we are on */ } my_cquantizer; typedef my_cquantizer * my_cquantize_ptr; @@ -9450,11 +9481,11 @@ select_ncolors (j_decompress_ptr cinfo, int Ncolors[]) iroot = 1; do { iroot++; - temp = iroot; /* set temp = iroot ** nc */ + temp = iroot; /* set temp = iroot ** nc */ for (i = 1; i < nc; i++) temp *= iroot; } while (temp <= (long) max_colors); /* repeat till iroot exceeds root */ - iroot--; /* now iroot = floor(root) */ + iroot--; /* now iroot = floor(root) */ /* Must have at least 2 color values per component */ if (iroot < 2) @@ -9478,10 +9509,10 @@ select_ncolors (j_decompress_ptr cinfo, int Ncolors[]) j = (cinfo->out_color_space == JCS_RGB ? RGB_order[i] : i); /* calculate new total_colors if Ncolors[j] is incremented */ temp = total_colors / Ncolors[j]; - temp *= Ncolors[j]+1; /* done in long arith to avoid oflo */ + temp *= Ncolors[j]+1; /* done in long arith to avoid oflo */ if (temp > (long) max_colors) - break; /* won't fit, done with this pass */ - Ncolors[j]++; /* OK, apply the increment */ + break; /* won't fit, done with this pass */ + Ncolors[j]++; /* OK, apply the increment */ total_colors = (int) temp; changed = TRUE; } @@ -9523,8 +9554,8 @@ LOCAL(void) create_colormap (j_decompress_ptr cinfo) { my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; - JSAMPARRAY colormap; /* Created colormap */ - int total_colors; /* Number of distinct output colors */ + JSAMPARRAY colormap; /* Created colormap */ + int total_colors; /* Number of distinct output colors */ int i,j,k, nci, blksize, blkdist, ptr, val; /* Select number of colors for each component */ @@ -9533,8 +9564,8 @@ create_colormap (j_decompress_ptr cinfo) /* Report selected color counts */ if (cinfo->out_color_components == 3) TRACEMS4(cinfo, 1, JTRC_QUANT_3_NCOLORS, - total_colors, cquantize->Ncolors[0], - cquantize->Ncolors[1], cquantize->Ncolors[2]); + total_colors, cquantize->Ncolors[0], + cquantize->Ncolors[1], cquantize->Ncolors[2]); else TRACEMS1(cinfo, 1, JTRC_QUANT_NCOLORS, total_colors); @@ -9559,12 +9590,12 @@ create_colormap (j_decompress_ptr cinfo) val = output_value(cinfo, i, j, nci-1); /* Fill in all colormap entries that have this value of this component */ for (ptr = j * blksize; ptr < total_colors; ptr += blkdist) { - /* fill in blksize entries beginning at ptr */ - for (k = 0; k < blksize; k++) - colormap[i][ptr+k] = (JSAMPLE) val; + /* fill in blksize entries beginning at ptr */ + for (k = 0; k < blksize; k++) + colormap[i][ptr+k] = (JSAMPLE) val; } } - blkdist = blksize; /* blksize of this color is blkdist of next */ + blkdist = blksize; /* blksize of this color is blkdist of next */ } /* Save the colormap in private storage, @@ -9622,16 +9653,16 @@ create_colorindex (j_decompress_ptr cinfo) val = 0; k = largest_input_value(cinfo, i, 0, nci-1); for (j = 0; j <= MAXJSAMPLE; j++) { - while (j > k) /* advance val if past boundary */ - k = largest_input_value(cinfo, i, ++val, nci-1); + while (j > k) /* advance val if past boundary */ + k = largest_input_value(cinfo, i, ++val, nci-1); /* premultiply so that no multiplication needed in main processing */ indexptr[j] = (JSAMPLE) (val * blksize); } /* Pad at both ends if necessary */ if (pad) for (j = 1; j <= MAXJSAMPLE; j++) { - indexptr[-j] = indexptr[0]; - indexptr[MAXJSAMPLE+j] = indexptr[MAXJSAMPLE]; + indexptr[-j] = indexptr[0]; + indexptr[MAXJSAMPLE+j] = indexptr[MAXJSAMPLE]; } } } @@ -9651,7 +9682,7 @@ make_odither_array (j_decompress_ptr cinfo, int ncolors) odither = (ODITHER_MATRIX_PTR) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(ODITHER_MATRIX)); + SIZEOF(ODITHER_MATRIX)); /* The inter-value distance for this color is MAXJSAMPLE/(ncolors-1). * Hence the dither value for the matrix cell with fill order f * (f=0..N-1) should be (N-1-2*f)/(2*N) * MAXJSAMPLE/(ncolors-1). @@ -9661,7 +9692,7 @@ make_odither_array (j_decompress_ptr cinfo, int ncolors) for (j = 0; j < ODITHER_SIZE; j++) { for (k = 0; k < ODITHER_SIZE; k++) { num = ((INT32) (ODITHER_CELLS-1 - 2*((int)base_dither_matrix[j][k]))) - * MAXJSAMPLE; + * MAXJSAMPLE; /* Ensure round towards zero despite C's lack of consistency * about rounding negative values in integer division... */ @@ -9674,7 +9705,7 @@ make_odither_array (j_decompress_ptr cinfo, int ncolors) /* * Create the ordered-dither tables. - * Components having the same number of representative colors may + * Components having the same number of representative colors may * share a dither table. */ @@ -9687,14 +9718,14 @@ create_odither_tables (j_decompress_ptr cinfo) for (i = 0; i < cinfo->out_color_components; i++) { nci = cquantize->Ncolors[i]; /* # of distinct values for this color */ - odither = NULL; /* search for matching prior component */ + odither = NULL; /* search for matching prior component */ for (j = 0; j < i; j++) { if (nci == cquantize->Ncolors[j]) { - odither = cquantize->odither[j]; - break; + odither = cquantize->odither[j]; + break; } } - if (odither == NULL) /* need a new table? */ + if (odither == NULL) /* need a new table? */ odither = make_odither_array(cinfo, nci); cquantize->odither[i] = odither; } @@ -9707,17 +9738,17 @@ create_odither_tables (j_decompress_ptr cinfo) METHODDEF(void) color_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf, - JSAMPARRAY output_buf, int num_rows) + JSAMPARRAY output_buf, int num_rows) /* General case, no dithering */ { my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; JSAMPARRAY colorindex = cquantize->colorindex; - register int pixcode, ci; - register JSAMPROW ptrin, ptrout; + int pixcode, ci; + JSAMPROW ptrin, ptrout; int row; JDIMENSION col; JDIMENSION width = cinfo->output_width; - register int nc = cinfo->out_color_components; + int nc = cinfo->out_color_components; for (row = 0; row < num_rows; row++) { ptrin = input_buf[row]; @@ -9725,7 +9756,7 @@ color_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf, for (col = width; col > 0; col--) { pixcode = 0; for (ci = 0; ci < nc; ci++) { - pixcode += GETJSAMPLE(colorindex[ci][GETJSAMPLE(*ptrin++)]); + pixcode += GETJSAMPLE(colorindex[ci][GETJSAMPLE(*ptrin++)]); } *ptrout++ = (JSAMPLE) pixcode; } @@ -9735,12 +9766,12 @@ color_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf, METHODDEF(void) color_quantize3 (j_decompress_ptr cinfo, JSAMPARRAY input_buf, - JSAMPARRAY output_buf, int num_rows) + JSAMPARRAY output_buf, int num_rows) /* Fast path for out_color_components==3, no dithering */ { my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; - register int pixcode; - register JSAMPROW ptrin, ptrout; + int pixcode; + JSAMPROW ptrin, ptrout; JSAMPROW colorindex0 = cquantize->colorindex[0]; JSAMPROW colorindex1 = cquantize->colorindex[1]; JSAMPROW colorindex2 = cquantize->colorindex[2]; @@ -9763,15 +9794,15 @@ color_quantize3 (j_decompress_ptr cinfo, JSAMPARRAY input_buf, METHODDEF(void) quantize_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, - JSAMPARRAY output_buf, int num_rows) + JSAMPARRAY output_buf, int num_rows) /* General case, with ordered dithering */ { my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; - register JSAMPROW input_ptr; - register JSAMPROW output_ptr; + JSAMPROW input_ptr; + JSAMPROW output_ptr; JSAMPROW colorindex_ci; - int * dither; /* points to active row of dither matrix */ - int row_index, col_index; /* current indexes into dither matrix */ + int * dither; /* points to active row of dither matrix */ + int row_index, col_index; /* current indexes into dither matrix */ int nc = cinfo->out_color_components; int ci; int row; @@ -9781,7 +9812,7 @@ quantize_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, for (row = 0; row < num_rows; row++) { /* Initialize output values to 0 so can process components separately */ jzero_far((void FAR *) output_buf[row], - (size_t) (width * SIZEOF(JSAMPLE))); + (size_t) (width * SIZEOF(JSAMPLE))); row_index = cquantize->row_index; for (ci = 0; ci < nc; ci++) { input_ptr = input_buf[row] + ci; @@ -9791,17 +9822,17 @@ quantize_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, col_index = 0; for (col = width; col > 0; col--) { - /* Form pixel value + dither, range-limit to 0..MAXJSAMPLE, - * select output value, accumulate into output code for this pixel. - * Range-limiting need not be done explicitly, as we have extended - * the colorindex table to produce the right answers for out-of-range - * inputs. The maximum dither is +- MAXJSAMPLE; this sets the - * required amount of padding. - */ - *output_ptr += colorindex_ci[GETJSAMPLE(*input_ptr)+dither[col_index]]; - input_ptr += nc; - output_ptr++; - col_index = (col_index + 1) & ODITHER_MASK; + /* Form pixel value + dither, range-limit to 0..MAXJSAMPLE, + * select output value, accumulate into output code for this pixel. + * Range-limiting need not be done explicitly, as we have extended + * the colorindex table to produce the right answers for out-of-range + * inputs. The maximum dither is +- MAXJSAMPLE; this sets the + * required amount of padding. + */ + *output_ptr += colorindex_ci[GETJSAMPLE(*input_ptr)+dither[col_index]]; + input_ptr += nc; + output_ptr++; + col_index = (col_index + 1) & ODITHER_MASK; } } /* Advance row index for next row */ @@ -9813,20 +9844,20 @@ quantize_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, METHODDEF(void) quantize3_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, - JSAMPARRAY output_buf, int num_rows) + JSAMPARRAY output_buf, int num_rows) /* Fast path for out_color_components==3, with ordered dithering */ { my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; - register int pixcode; - register JSAMPROW input_ptr; - register JSAMPROW output_ptr; + int pixcode; + JSAMPROW input_ptr; + JSAMPROW output_ptr; JSAMPROW colorindex0 = cquantize->colorindex[0]; JSAMPROW colorindex1 = cquantize->colorindex[1]; JSAMPROW colorindex2 = cquantize->colorindex[2]; - int * dither0; /* points to active row of dither matrix */ + int * dither0; /* points to active row of dither matrix */ int * dither1; int * dither2; - int row_index, col_index; /* current indexes into dither matrix */ + int row_index, col_index; /* current indexes into dither matrix */ int row; JDIMENSION col; JDIMENSION width = cinfo->output_width; @@ -9842,11 +9873,11 @@ quantize3_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, for (col = width; col > 0; col--) { pixcode = GETJSAMPLE(colorindex0[GETJSAMPLE(*input_ptr++) + - dither0[col_index]]); + dither0[col_index]]); pixcode += GETJSAMPLE(colorindex1[GETJSAMPLE(*input_ptr++) + - dither1[col_index]]); + dither1[col_index]]); pixcode += GETJSAMPLE(colorindex2[GETJSAMPLE(*input_ptr++) + - dither2[col_index]]); + dither2[col_index]]); *output_ptr++ = (JSAMPLE) pixcode; col_index = (col_index + 1) & ODITHER_MASK; } @@ -9858,24 +9889,24 @@ quantize3_ord_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, METHODDEF(void) quantize_fs_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, - JSAMPARRAY output_buf, int num_rows) + JSAMPARRAY output_buf, int num_rows) /* General case, with Floyd-Steinberg dithering */ { my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize; - register LOCFSERROR cur; /* current error or pixel value */ - LOCFSERROR belowerr; /* error for pixel below cur */ - LOCFSERROR bpreverr; /* error for below/prev col */ - LOCFSERROR bnexterr; /* error for below/next col */ + LOCFSERROR cur; /* current error or pixel value */ + LOCFSERROR belowerr; /* error for pixel below cur */ + LOCFSERROR bpreverr; /* error for below/prev col */ + LOCFSERROR bnexterr; /* error for below/next col */ LOCFSERROR delta; - register FSERRPTR errorptr; /* => fserrors[] at column before current */ - register JSAMPROW input_ptr; - register JSAMPROW output_ptr; + FSERRPTR errorptr; /* => fserrors[] at column before current */ + JSAMPROW input_ptr; + JSAMPROW output_ptr; JSAMPROW colorindex_ci; JSAMPROW colormap_ci; int pixcode; int nc = cinfo->out_color_components; - int dir; /* 1 for left-to-right, -1 for right-to-left */ - int dirnc; /* dir * nc */ + int dir; /* 1 for left-to-right, -1 for right-to-left */ + int dirnc; /* dir * nc */ int ci; int row; JDIMENSION col; @@ -9886,22 +9917,22 @@ quantize_fs_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, for (row = 0; row < num_rows; row++) { /* Initialize output values to 0 so can process components separately */ jzero_far((void FAR *) output_buf[row], - (size_t) (width * SIZEOF(JSAMPLE))); + (size_t) (width * SIZEOF(JSAMPLE))); for (ci = 0; ci < nc; ci++) { input_ptr = input_buf[row] + ci; output_ptr = output_buf[row]; if (cquantize->on_odd_row) { - /* work right to left in this row */ - input_ptr += (width-1) * nc; /* so point to rightmost pixel */ - output_ptr += width-1; - dir = -1; - dirnc = -nc; - errorptr = cquantize->fserrors[ci] + (width+1); /* => entry after last column */ + /* work right to left in this row */ + input_ptr += (width-1) * nc; /* so point to rightmost pixel */ + output_ptr += width-1; + dir = -1; + dirnc = -nc; + errorptr = cquantize->fserrors[ci] + (width+1); /* => entry after last column */ } else { - /* work left to right in this row */ - dir = 1; - dirnc = nc; - errorptr = cquantize->fserrors[ci]; /* => entry before first column */ + /* work left to right in this row */ + dir = 1; + dirnc = nc; + errorptr = cquantize->fserrors[ci]; /* => entry before first column */ } colorindex_ci = cquantize->colorindex[ci]; colormap_ci = cquantize->sv_colormap[ci]; @@ -9911,47 +9942,47 @@ quantize_fs_dither (j_decompress_ptr cinfo, JSAMPARRAY input_buf, belowerr = bpreverr = 0; for (col = width; col > 0; col--) { - /* cur holds the error propagated from the previous pixel on the - * current line. Add the error propagated from the previous line - * to form the complete error correction term for this pixel, and - * round the error term (which is expressed * 16) to an integer. - * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct - * for either sign of the error value. - * Note: errorptr points to *previous* column's array entry. - */ - cur = RIGHT_SHIFT(cur + errorptr[dir] + 8, 4); - /* Form pixel value + error, and range-limit to 0..MAXJSAMPLE. - * The maximum error is +- MAXJSAMPLE; this sets the required size - * of the range_limit array. - */ - cur += GETJSAMPLE(*input_ptr); - cur = GETJSAMPLE(range_limit[cur]); - /* Select output value, accumulate into output code for this pixel */ - pixcode = GETJSAMPLE(colorindex_ci[cur]); - *output_ptr += (JSAMPLE) pixcode; - /* Compute actual representation error at this pixel */ - /* Note: we can do this even though we don't have the final */ - /* pixel code, because the colormap is orthogonal. */ - cur -= GETJSAMPLE(colormap_ci[pixcode]); - /* Compute error fractions to be propagated to adjacent pixels. - * Add these into the running sums, and simultaneously shift the - * next-line error sums left by 1 column. - */ - bnexterr = cur; - delta = cur * 2; - cur += delta; /* form error * 3 */ - errorptr[0] = (FSERROR) (bpreverr + cur); - cur += delta; /* form error * 5 */ - bpreverr = belowerr + cur; - belowerr = bnexterr; - cur += delta; /* form error * 7 */ - /* At this point cur contains the 7/16 error value to be propagated - * to the next pixel on the current line, and all the errors for the - * next line have been shifted over. We are therefore ready to move on. - */ - input_ptr += dirnc; /* advance input ptr to next column */ - output_ptr += dir; /* advance output ptr to next column */ - errorptr += dir; /* advance errorptr to current column */ + /* cur holds the error propagated from the previous pixel on the + * current line. Add the error propagated from the previous line + * to form the complete error correction term for this pixel, and + * round the error term (which is expressed * 16) to an integer. + * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct + * for either sign of the error value. + * Note: errorptr points to *previous* column's array entry. + */ + cur = RIGHT_SHIFT(cur + errorptr[dir] + 8, 4); + /* Form pixel value + error, and range-limit to 0..MAXJSAMPLE. + * The maximum error is +- MAXJSAMPLE; this sets the required size + * of the range_limit array. + */ + cur += GETJSAMPLE(*input_ptr); + cur = GETJSAMPLE(range_limit[cur]); + /* Select output value, accumulate into output code for this pixel */ + pixcode = GETJSAMPLE(colorindex_ci[cur]); + *output_ptr += (JSAMPLE) pixcode; + /* Compute actual representation error at this pixel */ + /* Note: we can do this even though we don't have the final */ + /* pixel code, because the colormap is orthogonal. */ + cur -= GETJSAMPLE(colormap_ci[pixcode]); + /* Compute error fractions to be propagated to adjacent pixels. + * Add these into the running sums, and simultaneously shift the + * next-line error sums left by 1 column. + */ + bnexterr = cur; + delta = cur * 2; + cur += delta; /* form error * 3 */ + errorptr[0] = (FSERROR) (bpreverr + cur); + cur += delta; /* form error * 5 */ + bpreverr = belowerr + cur; + belowerr = bnexterr; + cur += delta; /* form error * 7 */ + /* At this point cur contains the 7/16 error value to be propagated + * to the next pixel on the current line, and all the errors for the + * next line have been shifted over. We are therefore ready to move on. + */ + input_ptr += dirnc; /* advance input ptr to next column */ + output_ptr += dir; /* advance output ptr to next column */ + errorptr += dir; /* advance errorptr to current column */ } /* Post-loop cleanup: we must unload the final error value into the * final fserrors[] entry. Note we need not unload belowerr because @@ -10000,7 +10031,7 @@ start_pass_1_quant (j_decompress_ptr cinfo, boolean is_pre_scan) /* Initialize for desired dithering mode. */ switch (cinfo->dither_mode) { - case JDITHER_NONE: + case JDITHER_BLANK: if (cinfo->out_color_components == 3) cquantize->pub.color_quantize = color_quantize3; else @@ -10011,7 +10042,7 @@ start_pass_1_quant (j_decompress_ptr cinfo, boolean is_pre_scan) cquantize->pub.color_quantize = quantize3_ord_dither; else cquantize->pub.color_quantize = quantize_ord_dither; - cquantize->row_index = 0; /* initialize state for ordered dither */ + cquantize->row_index = 0; /* initialize state for ordered dither */ /* If user changed to ordered dither from another mode, * we must recreate the color index table with padding. * This will cost extra space, but probably isn't very likely. @@ -10074,13 +10105,13 @@ jinit_1pass_quantizer (j_decompress_ptr cinfo) cquantize = (my_cquantize_ptr) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(my_cquantizer)); + SIZEOF(my_cquantizer)); cinfo->cquantize = (struct jpeg_color_quantizer *) cquantize; cquantize->pub.start_pass = start_pass_1_quant; cquantize->pub.finish_pass = finish_pass_1_quant; cquantize->pub.new_color_map = new_color_map_1_quant; cquantize->fserrors[0] = NULL; /* Flag FS workspace not allocated */ - cquantize->odither[0] = NULL; /* Also flag odither arrays not allocated */ + cquantize->odither[0] = NULL; /* Also flag odither arrays not allocated */ /* Make sure my internal arrays won't overflow */ if (cinfo->out_color_components > MAX_Q_COMPS) @@ -10124,7 +10155,7 @@ jinit_1pass_quantizer (j_decompress_ptr cinfo) #define JPEG_INTERNALS //#include "jinclude.h" //#include "jpeglib.h" -//#include "jdhuff.h" /* Declarations shared with jdhuff.c */ +//#include "jdhuff.h" /* Declarations shared with jdhuff.c */ #ifdef D_PROGRESSIVE_SUPPORTED @@ -10137,8 +10168,8 @@ jinit_1pass_quantizer (j_decompress_ptr cinfo) */ typedef struct { - unsigned int EOBRUN; /* remaining EOBs in EOBRUN */ - int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ + unsigned int EOBRUN; /* remaining EOBs in EOBRUN */ + int last_dc_val[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */ } p_savable_state; /* This macro is to work around compilers with missing or broken @@ -10151,11 +10182,11 @@ typedef struct { #else #if MAX_COMPS_IN_SCAN == 4 #define ASSIGN_STATE(dest,src) \ - ((dest).EOBRUN = (src).EOBRUN, \ - (dest).last_dc_val[0] = (src).last_dc_val[0], \ - (dest).last_dc_val[1] = (src).last_dc_val[1], \ - (dest).last_dc_val[2] = (src).last_dc_val[2], \ - (dest).last_dc_val[3] = (src).last_dc_val[3]) + ((dest).EOBRUN = (src).EOBRUN, \ + (dest).last_dc_val[0] = (src).last_dc_val[0], \ + (dest).last_dc_val[1] = (src).last_dc_val[1], \ + (dest).last_dc_val[2] = (src).last_dc_val[2], \ + (dest).last_dc_val[3] = (src).last_dc_val[3]) #endif #endif @@ -10166,11 +10197,11 @@ typedef struct { /* These fields are loaded into local variables at start of each MCU. * In case of suspension, we exit WITHOUT updating them. */ - bitread_perm_state bitstate; /* Bit buffer at start of MCU */ - p_savable_state saved; /* Other state at start of MCU */ + bitread_perm_state bitstate; /* Bit buffer at start of MCU */ + p_savable_state saved; /* Other state at start of MCU */ /* These fields are NOT loaded into local working state. */ - unsigned int restarts_to_go; /* MCUs left in this restart interval */ + unsigned int restarts_to_go; /* MCUs left in this restart interval */ /* Pointers to derived tables (these workspaces have image lifespan) */ d_derived_tbl * derived_tbls[NUM_HUFF_TBLS]; @@ -10182,13 +10213,13 @@ typedef phuff_entropy_decoder * phuff_entropy_ptr; /* Forward declarations */ METHODDEF(boolean) decode_mcu_DC_first JPP((j_decompress_ptr cinfo, - JBLOCKROW *MCU_data)); + JBLOCKROW *MCU_data)); METHODDEF(boolean) decode_mcu_AC_first JPP((j_decompress_ptr cinfo, - JBLOCKROW *MCU_data)); + JBLOCKROW *MCU_data)); METHODDEF(boolean) decode_mcu_DC_refine JPP((j_decompress_ptr cinfo, - JBLOCKROW *MCU_data)); + JBLOCKROW *MCU_data)); METHODDEF(boolean) decode_mcu_AC_refine JPP((j_decompress_ptr cinfo, - JBLOCKROW *MCU_data)); + JBLOCKROW *MCU_data)); /* @@ -10224,7 +10255,7 @@ start_pass_phuff_decoder (j_decompress_ptr cinfo) if (cinfo->Al != cinfo->Ah-1) bad = TRUE; } - if (cinfo->Al > 13) /* need not check for < 0 */ + if (cinfo->Al > 13) /* need not check for < 0 */ bad = TRUE; /* Arguably the maximum Al value should be less than 13 for 8-bit precision, * but the spec doesn't say so, and we try to be liberal about what we @@ -10234,7 +10265,7 @@ start_pass_phuff_decoder (j_decompress_ptr cinfo) */ if (bad) ERREXIT4(cinfo, JERR_BAD_PROGRESSION, - cinfo->Ss, cinfo->Se, cinfo->Ah, cinfo->Al); + cinfo->Ss, cinfo->Se, cinfo->Ah, cinfo->Al); /* Update progression status, and verify that scan order is legal. * Note that inter-scan inconsistencies are treated as warnings * not fatal errors ... not clear if this is right way to behave. @@ -10247,7 +10278,7 @@ start_pass_phuff_decoder (j_decompress_ptr cinfo) for (coefi = cinfo->Ss; coefi <= cinfo->Se; coefi++) { int expected = (coef_bit_ptr[coefi] < 0) ? 0 : coef_bit_ptr[coefi]; if (cinfo->Ah != expected) - WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, coefi); + WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, coefi); coef_bit_ptr[coefi] = cinfo->Al; } } @@ -10271,15 +10302,15 @@ start_pass_phuff_decoder (j_decompress_ptr cinfo) * We may build same derived table more than once, but it's not expensive. */ if (is_DC_band) { - if (cinfo->Ah == 0) { /* DC refinement needs no table */ - tbl = compptr->dc_tbl_no; - jpeg_make_d_derived_tbl(cinfo, TRUE, tbl, - & entropy->derived_tbls[tbl]); + if (cinfo->Ah == 0) { /* DC refinement needs no table */ + tbl = compptr->dc_tbl_no; + jpeg_make_d_derived_tbl(cinfo, TRUE, tbl, + & entropy->derived_tbls[tbl]); } } else { tbl = compptr->ac_tbl_no; jpeg_make_d_derived_tbl(cinfo, FALSE, tbl, - & entropy->derived_tbls[tbl]); + & entropy->derived_tbls[tbl]); /* remember the single active table */ entropy->ac_derived_tbl = entropy->derived_tbls[tbl]; } @@ -10318,10 +10349,10 @@ static const int p_extend_test[16] = /* entry n is 2**(n-1) */ 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000 }; static const int p_extend_offset[16] = /* entry n is (-1 << n) + 1 */ - { 0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1, - ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1, - ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1, - ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1 }; + { 0, -(1<<1) + 1, -(1<<2) + 1, -(1<<3) + 1, -(1<<4) + 1, + -(1<<5) + 1, -(1<<6) + 1, -(1<<7) + 1, -(1<<8) + 1, + -(1<<9) + 1, -(1<<10) + 1, -(1<<11) + 1, -(1<<12) + 1, + -(1<<13) + 1, -(1<<14) + 1, -(1<<15) + 1 }; #endif /* AVOID_TABLES */ @@ -10370,7 +10401,7 @@ p_process_restart (j_decompress_ptr cinfo) /* * Huffman MCU decoding. * Each of these routines decodes and returns one MCU's worth of - * Huffman-compressed coefficients. + * Huffman-compressed coefficients. * The coefficients are reordered from zigzag order into natural array order, * but are not dequantized. * @@ -10391,10 +10422,10 @@ p_process_restart (j_decompress_ptr cinfo) METHODDEF(boolean) decode_mcu_DC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) -{ +{ phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; int Al = cinfo->Al; - register int s, r; + int s, r; int blkn, ci; JBLOCKROW block; BITREAD_STATE_VARS; @@ -10406,7 +10437,7 @@ decode_mcu_DC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) if (cinfo->restart_interval) { if (entropy->restarts_to_go == 0) if (! p_process_restart(cinfo)) - return FALSE; + return FALSE; } /* If we've run out of data, just leave the MCU set to zeroes. @@ -10431,9 +10462,9 @@ decode_mcu_DC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) /* Section F.2.2.1: decode the DC coefficient difference */ HUFF_DECODE(s, br_state, tbl, return FALSE, label1); if (s) { - CHECK_BIT_BUFFER(br_state, s, return FALSE); - r = GET_BITS(s); - s = HUFF_EXTEND(r, s); + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); } /* Convert DC difference to actual value, update last_dc_val */ @@ -10462,11 +10493,11 @@ decode_mcu_DC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) METHODDEF(boolean) decode_mcu_AC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) -{ +{ phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; int Se = cinfo->Se; int Al = cinfo->Al; - register int s, k, r; + int s, k, r; unsigned int EOBRUN; JBLOCKROW block; BITREAD_STATE_VARS; @@ -10476,7 +10507,7 @@ decode_mcu_AC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) if (cinfo->restart_interval) { if (entropy->restarts_to_go == 0) if (! p_process_restart(cinfo)) - return FALSE; + return FALSE; } /* If we've run out of data, just leave the MCU set to zeroes. @@ -10487,49 +10518,49 @@ decode_mcu_AC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) /* Load up working state. * We can avoid loading/saving bitread state if in an EOB run. */ - EOBRUN = entropy->saved.EOBRUN; /* only part of saved state we need */ + EOBRUN = entropy->saved.EOBRUN; /* only part of saved state we need */ /* There is always only one block per MCU */ - if (EOBRUN > 0) /* if it's a band of zeroes... */ - EOBRUN--; /* ...process it now (we do nothing) */ + if (EOBRUN > 0) /* if it's a band of zeroes... */ + EOBRUN--; /* ...process it now (we do nothing) */ else { BITREAD_LOAD_STATE(cinfo,entropy->bitstate); block = MCU_data[0]; tbl = entropy->ac_derived_tbl; for (k = cinfo->Ss; k <= Se; k++) { - HUFF_DECODE(s, br_state, tbl, return FALSE, label2); - r = s >> 4; - s &= 15; - if (s) { - k += r; - CHECK_BIT_BUFFER(br_state, s, return FALSE); - r = GET_BITS(s); - s = HUFF_EXTEND(r, s); - /* Scale and output coefficient in natural (dezigzagged) order */ - (*block)[jpeg_natural_order[k]] = (JCOEF) (s << Al); - } else { - if (r == 15) { /* ZRL */ - k += 15; /* skip 15 zeroes in band */ - } else { /* EOBr, run length is 2^r + appended bits */ - EOBRUN = 1 << r; - if (r) { /* EOBr, r > 0 */ - CHECK_BIT_BUFFER(br_state, r, return FALSE); - r = GET_BITS(r); - EOBRUN += r; - } - EOBRUN--; /* this band is processed at this moment */ - break; /* force end-of-band */ - } - } + HUFF_DECODE(s, br_state, tbl, return FALSE, label2); + r = s >> 4; + s &= 15; + if (s) { + k += r; + CHECK_BIT_BUFFER(br_state, s, return FALSE); + r = GET_BITS(s); + s = HUFF_EXTEND(r, s); + /* Scale and output coefficient in natural (dezigzagged) order */ + (*block)[jpeg_natural_order[k]] = (JCOEF) (s << Al); + } else { + if (r == 15) { /* ZRL */ + k += 15; /* skip 15 zeroes in band */ + } else { /* EOBr, run length is 2^r + appended bits */ + EOBRUN = 1 << r; + if (r) { /* EOBr, r > 0 */ + CHECK_BIT_BUFFER(br_state, r, return FALSE); + r = GET_BITS(r); + EOBRUN += r; + } + EOBRUN--; /* this band is processed at this moment */ + break; /* force end-of-band */ + } + } } BITREAD_SAVE_STATE(cinfo,entropy->bitstate); } /* Completed MCU, so update state */ - entropy->saved.EOBRUN = EOBRUN; /* only part of saved state we need */ + entropy->saved.EOBRUN = EOBRUN; /* only part of saved state we need */ } /* Account for restart interval (no-op if not using restarts) */ @@ -10547,9 +10578,9 @@ decode_mcu_AC_first (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) METHODDEF(boolean) decode_mcu_DC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) -{ +{ phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; - int p1 = 1 << cinfo->Al; /* 1 in the bit position being coded */ + int p1 = 1 << cinfo->Al; /* 1 in the bit position being coded */ int blkn; JBLOCKROW block; BITREAD_STATE_VARS; @@ -10558,7 +10589,7 @@ decode_mcu_DC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) if (cinfo->restart_interval) { if (entropy->restarts_to_go == 0) if (! p_process_restart(cinfo)) - return FALSE; + return FALSE; } /* Not worth the cycles to check insufficient_data here, @@ -10596,12 +10627,12 @@ decode_mcu_DC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) METHODDEF(boolean) decode_mcu_AC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) -{ +{ phuff_entropy_ptr entropy = (phuff_entropy_ptr) cinfo->entropy; int Se = cinfo->Se; - int p1 = 1 << cinfo->Al; /* 1 in the bit position being coded */ - int m1 = (-1) << cinfo->Al; /* -1 in the bit position being coded */ - register int s, k, r; + int p1 = 1 << cinfo->Al; /* 1 in the bit position being coded */ + int m1 = (-1) << cinfo->Al; /* -1 in the bit position being coded */ + int s, k, r; unsigned int EOBRUN; JBLOCKROW block; JCOEFPTR thiscoef; @@ -10614,7 +10645,7 @@ decode_mcu_AC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) if (cinfo->restart_interval) { if (entropy->restarts_to_go == 0) if (! p_process_restart(cinfo)) - return FALSE; + return FALSE; } /* If we've run out of data, don't modify the MCU. @@ -10642,58 +10673,58 @@ decode_mcu_AC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) if (EOBRUN == 0) { for (; k <= Se; k++) { - HUFF_DECODE(s, br_state, tbl, goto undoit, label3); - r = s >> 4; - s &= 15; - if (s) { - if (s != 1) /* size of new coef should always be 1 */ - WARNMS(cinfo, JWRN_HUFF_BAD_CODE); - CHECK_BIT_BUFFER(br_state, 1, goto undoit); - if (GET_BITS(1)) - s = p1; /* newly nonzero coef is positive */ - else - s = m1; /* newly nonzero coef is negative */ - } else { - if (r != 15) { - EOBRUN = 1 << r; /* EOBr, run length is 2^r + appended bits */ - if (r) { - CHECK_BIT_BUFFER(br_state, r, goto undoit); - r = GET_BITS(r); - EOBRUN += r; - } - break; /* rest of block is handled by EOB logic */ - } - /* note s = 0 for processing ZRL */ - } - /* Advance over already-nonzero coefs and r still-zero coefs, - * appending correction bits to the nonzeroes. A correction bit is 1 - * if the absolute value of the coefficient must be increased. - */ - do { - thiscoef = *block + jpeg_natural_order[k]; - if (*thiscoef != 0) { - CHECK_BIT_BUFFER(br_state, 1, goto undoit); - if (GET_BITS(1)) { - if ((*thiscoef & p1) == 0) { /* do nothing if already set it */ - if (*thiscoef >= 0) - *thiscoef += p1; - else - *thiscoef += m1; - } - } - } else { - if (--r < 0) - break; /* reached target zero coefficient */ - } - k++; - } while (k <= Se); - if (s) { - int pos = jpeg_natural_order[k]; - /* Output newly nonzero coefficient */ - (*block)[pos] = (JCOEF) s; - /* Remember its position in case we have to suspend */ - newnz_pos[num_newnz++] = pos; - } + HUFF_DECODE(s, br_state, tbl, goto undoit, label3); + r = s >> 4; + s &= 15; + if (s) { + if (s != 1) /* size of new coef should always be 1 */ + WARNMS(cinfo, JWRN_HUFF_BAD_CODE); + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) + s = p1; /* newly nonzero coef is positive */ + else + s = m1; /* newly nonzero coef is negative */ + } else { + if (r != 15) { + EOBRUN = 1 << r; /* EOBr, run length is 2^r + appended bits */ + if (r) { + CHECK_BIT_BUFFER(br_state, r, goto undoit); + r = GET_BITS(r); + EOBRUN += r; + } + break; /* rest of block is handled by EOB logic */ + } + /* note s = 0 for processing ZRL */ + } + /* Advance over already-nonzero coefs and r still-zero coefs, + * appending correction bits to the nonzeroes. A correction bit is 1 + * if the absolute value of the coefficient must be increased. + */ + do { + thiscoef = *block + jpeg_natural_order[k]; + if (*thiscoef != 0) { + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) { + if ((*thiscoef & p1) == 0) { /* do nothing if already set it */ + if (*thiscoef >= 0) + *thiscoef += p1; + else + *thiscoef += m1; + } + } + } else { + if (--r < 0) + break; /* reached target zero coefficient */ + } + k++; + } while (k <= Se); + if (s) { + int pos = jpeg_natural_order[k]; + /* Output newly nonzero coefficient */ + (*block)[pos] = (JCOEF) s; + /* Remember its position in case we have to suspend */ + newnz_pos[num_newnz++] = pos; + } } } @@ -10704,18 +10735,18 @@ decode_mcu_AC_refine (j_decompress_ptr cinfo, JBLOCKROW *MCU_data) * if the absolute value of the coefficient must be increased. */ for (; k <= Se; k++) { - thiscoef = *block + jpeg_natural_order[k]; - if (*thiscoef != 0) { - CHECK_BIT_BUFFER(br_state, 1, goto undoit); - if (GET_BITS(1)) { - if ((*thiscoef & p1) == 0) { /* do nothing if already changed it */ - if (*thiscoef >= 0) - *thiscoef += p1; - else - *thiscoef += m1; - } - } - } + thiscoef = *block + jpeg_natural_order[k]; + if (*thiscoef != 0) { + CHECK_BIT_BUFFER(br_state, 1, goto undoit); + if (GET_BITS(1)) { + if ((*thiscoef & p1) == 0) { /* do nothing if already changed it */ + if (*thiscoef >= 0) + *thiscoef += p1; + else + *thiscoef += m1; + } + } + } } /* Count one block completed in EOB run */ EOBRUN--; @@ -10753,7 +10784,7 @@ jinit_phuff_decoder (j_decompress_ptr cinfo) entropy = (phuff_entropy_ptr) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - SIZEOF(phuff_entropy_decoder)); + SIZEOF(phuff_entropy_decoder)); cinfo->entropy = (struct jpeg_entropy_decoder *) entropy; entropy->pub.start_pass = start_pass_phuff_decoder; @@ -10765,69 +10796,11 @@ jinit_phuff_decoder (j_decompress_ptr cinfo) /* Create progression status table */ cinfo->coef_bits = (int (*)[DCTSIZE2]) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, - cinfo->num_components*DCTSIZE2*SIZEOF(int)); + cinfo->num_components*DCTSIZE2*SIZEOF(int)); coef_bit_ptr = & cinfo->coef_bits[0][0]; - for (ci = 0; ci < cinfo->num_components; ci++) + for (ci = 0; ci < cinfo->num_components; ci++) for (i = 0; i < DCTSIZE2; i++) *coef_bit_ptr++ = -1; } #endif /* D_PROGRESSIVE_SUPPORTED */ - - -/*********************************************************************** -** -** REBOL Interface (keep it minimal) -** -***********************************************************************/ - -#ifndef CODI_DEFINED -#include "reb-codec.h" -extern long* Make_Mem(size_t size); -extern void Register_Codec(char *name, codo dispatcher); -#endif - -/*********************************************************************** -** -*/ REBINT Codec_JPEG_Image(REBCDI *codi) -/* -***********************************************************************/ -{ - codi->error = 0; - - // Handle JPEG error throw: - if (setjmp(jpeg_state)) { - codi->error = CODI_ERR_BAD_DATA; // generic - if (codi->action == CODI_IDENTIFY) return CODI_CHECK; - return CODI_ERROR; - } - - if (codi->action == CODI_IDENTIFY) { - int w, h; - jpeg_info(codi->data, codi->len, &w, &h); // will throw errors - return CODI_CHECK; - } - - if (codi->action == CODI_DECODE) { - int w, h; - jpeg_info(codi->data, codi->len, &w, &h); - codi->bits = (u32 *)Make_Mem(w * h * 4); - jpeg_load(codi->data, codi->len, (char *)codi->bits); - codi->w = w; - codi->h = h; - return CODI_IMAGE; - } - - codi->error = CODI_ERR_NA; - return CODI_ERROR; -} - - -/*********************************************************************** -** -*/ void Init_JPEG_Codec(void) -/* -***********************************************************************/ -{ - Register_Codec("jpeg", Codec_JPEG_Image); -} diff --git a/src/extensions/locale/ISO-639-2_utf-8.txt b/src/extensions/locale/ISO-639-2_utf-8.txt new file mode 100644 index 0000000000..4597d9822f --- /dev/null +++ b/src/extensions/locale/ISO-639-2_utf-8.txt @@ -0,0 +1,487 @@ +aar||aa|Afar|afar +abk||ab|Abkhazian|abkhaze +ace|||Achinese|aceh +ach|||Acoli|acoli +ada|||Adangme|adangme +ady|||Adyghe; Adygei|adyghé +afa|||Afro-Asiatic languages|afro-asiatiques, langues +afh|||Afrihili|afrihili +afr||af|Afrikaans|afrikaans +ain|||Ainu|aïnou +aka||ak|Akan|akan +akk|||Akkadian|akkadien +alb|sqi|sq|Albanian|albanais +ale|||Aleut|aléoute +alg|||Algonquian languages|algonquines, langues +alt|||Southern Altai|altai du Sud +amh||am|Amharic|amharique +ang|||English, Old (ca.450-1100)|anglo-saxon (ca.450-1100) +anp|||Angika|angika +apa|||Apache languages|apaches, langues +ara||ar|Arabic|arabe +arc|||Official Aramaic (700-300 BCE); Imperial Aramaic (700-300 BCE)|araméen d'empire (700-300 BCE) +arg||an|Aragonese|aragonais +arm|hye|hy|Armenian|arménien +arn|||Mapudungun; Mapuche|mapudungun; mapuche; mapuce +arp|||Arapaho|arapaho +art|||Artificial languages|artificielles, langues +arw|||Arawak|arawak +asm||as|Assamese|assamais +ast|||Asturian; Bable; Leonese; Asturleonese|asturien; bable; léonais; asturoléonais +ath|||Athapascan languages|athapascanes, langues +aus|||Australian languages|australiennes, langues +ava||av|Avaric|avar +ave||ae|Avestan|avestique +awa|||Awadhi|awadhi +aym||ay|Aymara|aymara +aze||az|Azerbaijani|azéri +bad|||Banda languages|banda, langues +bai|||Bamileke languages|bamiléké, langues +bak||ba|Bashkir|bachkir +bal|||Baluchi|baloutchi +bam||bm|Bambara|bambara +ban|||Balinese|balinais +baq|eus|eu|Basque|basque +bas|||Basa|basa +bat|||Baltic languages|baltes, langues +bej|||Beja; Bedawiyet|bedja +bel||be|Belarusian|biélorusse +bem|||Bemba|bemba +ben||bn|Bengali|bengali +ber|||Berber languages|berbères, langues +bho|||Bhojpuri|bhojpuri +bih||bh|Bihari languages|langues biharis +bik|||Bikol|bikol +bin|||Bini; Edo|bini; edo +bis||bi|Bislama|bichlamar +bla|||Siksika|blackfoot +bnt|||Bantu (Other)|bantoues, autres langues +bos||bs|Bosnian|bosniaque +bra|||Braj|braj +bre||br|Breton|breton +btk|||Batak languages|batak, langues +bua|||Buriat|bouriate +bug|||Buginese|bugi +bul||bg|Bulgarian|bulgare +bur|mya|my|Burmese|birman +byn|||Blin; Bilin|blin; bilen +cad|||Caddo|caddo +cai|||Central American Indian languages|amérindiennes de L'Amérique centrale, langues +car|||Galibi Carib|karib; galibi; carib +cat||ca|Catalan; Valencian|catalan; valencien +cau|||Caucasian languages|caucasiennes, langues +ceb|||Cebuano|cebuano +cel|||Celtic languages|celtiques, langues; celtes, langues +cha||ch|Chamorro|chamorro +chb|||Chibcha|chibcha +che||ce|Chechen|tchétchène +chg|||Chagatai|djaghataï +chi|zho|zh|Chinese|chinois +chk|||Chuukese|chuuk +chm|||Mari|mari +chn|||Chinook jargon|chinook, jargon +cho|||Choctaw|choctaw +chp|||Chipewyan; Dene Suline|chipewyan +chr|||Cherokee|cherokee +chu||cu|Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic|slavon d'église; vieux slave; slavon liturgique; vieux bulgare +chv||cv|Chuvash|tchouvache +chy|||Cheyenne|cheyenne +cmc|||Chamic languages|chames, langues +cop|||Coptic|copte +cor||kw|Cornish|cornique +cos||co|Corsican|corse +cpe|||Creoles and pidgins, English based|créoles et pidgins basés sur l'anglais +cpf|||Creoles and pidgins, French-based |créoles et pidgins basés sur le français +cpp|||Creoles and pidgins, Portuguese-based |créoles et pidgins basés sur le portugais +cre||cr|Cree|cree +crh|||Crimean Tatar; Crimean Turkish|tatar de Crimé +crp|||Creoles and pidgins |créoles et pidgins +csb|||Kashubian|kachoube +cus|||Cushitic languages|couchitiques, langues +cze|ces|cs|Czech|tchèque +dak|||Dakota|dakota +dan||da|Danish|danois +dar|||Dargwa|dargwa +day|||Land Dayak languages|dayak, langues +del|||Delaware|delaware +den|||Slave (Athapascan)|esclave (athapascan) +dgr|||Dogrib|dogrib +din|||Dinka|dinka +div||dv|Divehi; Dhivehi; Maldivian|maldivien +doi|||Dogri|dogri +dra|||Dravidian languages|dravidiennes, langues +dsb|||Lower Sorbian|bas-sorabe +dua|||Duala|douala +dum|||Dutch, Middle (ca.1050-1350)|néerlandais moyen (ca. 1050-1350) +dut|nld|nl|Dutch; Flemish|néerlandais; flamand +dyu|||Dyula|dioula +dzo||dz|Dzongkha|dzongkha +efi|||Efik|efik +egy|||Egyptian (Ancient)|égyptien +eka|||Ekajuk|ekajuk +elx|||Elamite|élamite +eng||en|English|anglais +enm|||English, Middle (1100-1500)|anglais moyen (1100-1500) +epo||eo|Esperanto|espéranto +est||et|Estonian|estonien +ewe||ee|Ewe|éwé +ewo|||Ewondo|éwondo +fan|||Fang|fang +fao||fo|Faroese|féroïen +fat|||Fanti|fanti +fij||fj|Fijian|fidjien +fil|||Filipino; Pilipino|filipino; pilipino +fin||fi|Finnish|finnois +fiu|||Finno-Ugrian languages|finno-ougriennes, langues +fon|||Fon|fon +fre|fra|fr|French|français +frm|||French, Middle (ca.1400-1600)|français moyen (1400-1600) +fro|||French, Old (842-ca.1400)|français ancien (842-ca.1400) +frr|||Northern Frisian|frison septentrional +frs|||Eastern Frisian|frison oriental +fry||fy|Western Frisian|frison occidental +ful||ff|Fulah|peul +fur|||Friulian|frioulan +gaa|||Ga|ga +gay|||Gayo|gayo +gba|||Gbaya|gbaya +gem|||Germanic languages|germaniques, langues +geo|kat|ka|Georgian|géorgien +ger|deu|de|German|allemand +gez|||Geez|guèze +gil|||Gilbertese|kiribati +gla||gd|Gaelic; Scottish Gaelic|gaélique; gaélique écossais +gle||ga|Irish|irlandais +glg||gl|Galician|galicien +glv||gv|Manx|manx; mannois +gmh|||German, Middle High (ca.1050-1500)|allemand, moyen haut (ca. 1050-1500) +goh|||German, Old High (ca.750-1050)|allemand, vieux haut (ca. 750-1050) +gon|||Gondi|gond +gor|||Gorontalo|gorontalo +got|||Gothic|gothique +grb|||Grebo|grebo +grc|||Greek, Ancient (to 1453)|grec ancien (jusqu'à 1453) +gre|ell|el|Greek, Modern (1453-)|grec moderne (après 1453) +grn||gn|Guarani|guarani +gsw|||Swiss German; Alemannic; Alsatian|suisse alémanique; alémanique; alsacien +guj||gu|Gujarati|goudjrati +gwi|||Gwich'in|gwich'in +hai|||Haida|haida +hat||ht|Haitian; Haitian Creole|haïtien; créole haïtien +hau||ha|Hausa|haoussa +haw|||Hawaiian|hawaïen +heb||he|Hebrew|hébreu +her||hz|Herero|herero +hil|||Hiligaynon|hiligaynon +him|||Himachali languages; Western Pahari languages|langues himachalis; langues paharis occidentales +hin||hi|Hindi|hindi +hit|||Hittite|hittite +hmn|||Hmong; Mong|hmong +hmo||ho|Hiri Motu|hiri motu +hrv||hr|Croatian|croate +hsb|||Upper Sorbian|haut-sorabe +hun||hu|Hungarian|hongrois +hup|||Hupa|hupa +iba|||Iban|iban +ibo||ig|Igbo|igbo +ice|isl|is|Icelandic|islandais +ido||io|Ido|ido +iii||ii|Sichuan Yi; Nuosu|yi de Sichuan +ijo|||Ijo languages|ijo, langues +iku||iu|Inuktitut|inuktitut +ile||ie|Interlingue; Occidental|interlingue +ilo|||Iloko|ilocano +ina||ia|Interlingua (International Auxiliary Language Association)|interlingua (langue auxiliaire internationale) +inc|||Indic languages|indo-aryennes, langues +ind||id|Indonesian|indonésien +ine|||Indo-European languages|indo-européennes, langues +inh|||Ingush|ingouche +ipk||ik|Inupiaq|inupiaq +ira|||Iranian languages|iraniennes, langues +iro|||Iroquoian languages|iroquoises, langues +ita||it|Italian|italien +jav||jv|Javanese|javanais +jbo|||Lojban|lojban +jpn||ja|Japanese|japonais +jpr|||Judeo-Persian|judéo-persan +jrb|||Judeo-Arabic|judéo-arabe +kaa|||Kara-Kalpak|karakalpak +kab|||Kabyle|kabyle +kac|||Kachin; Jingpho|kachin; jingpho +kal||kl|Kalaallisut; Greenlandic|groenlandais +kam|||Kamba|kamba +kan||kn|Kannada|kannada +kar|||Karen languages|karen, langues +kas||ks|Kashmiri|kashmiri +kau||kr|Kanuri|kanouri +kaw|||Kawi|kawi +kaz||kk|Kazakh|kazakh +kbd|||Kabardian|kabardien +kha|||Khasi|khasi +khi|||Khoisan languages|khoïsan, langues +khm||km|Central Khmer|khmer central +kho|||Khotanese; Sakan|khotanais; sakan +kik||ki|Kikuyu; Gikuyu|kikuyu +kin||rw|Kinyarwanda|rwanda +kir||ky|Kirghiz; Kyrgyz|kirghiz +kmb|||Kimbundu|kimbundu +kok|||Konkani|konkani +kom||kv|Komi|kom +kon||kg|Kongo|kongo +kor||ko|Korean|coréen +kos|||Kosraean|kosrae +kpe|||Kpelle|kpellé +krc|||Karachay-Balkar|karatchai balkar +krl|||Karelian|carélien +kro|||Kru languages|krou, langues +kru|||Kurukh|kurukh +kua||kj|Kuanyama; Kwanyama|kuanyama; kwanyama +kum|||Kumyk|koumyk +kur||ku|Kurdish|kurde +kut|||Kutenai|kutenai +lad|||Ladino|judéo-espagnol +lah|||Lahnda|lahnda +lam|||Lamba|lamba +lao||lo|Lao|lao +lat||la|Latin|latin +lav||lv|Latvian|letton +lez|||Lezghian|lezghien +lim||li|Limburgan; Limburger; Limburgish|limbourgeois +lin||ln|Lingala|lingala +lit||lt|Lithuanian|lituanien +lol|||Mongo|mongo +loz|||Lozi|lozi +ltz||lb|Luxembourgish; Letzeburgesch|luxembourgeois +lua|||Luba-Lulua|luba-lulua +lub||lu|Luba-Katanga|luba-katanga +lug||lg|Ganda|ganda +lui|||Luiseno|luiseno +lun|||Lunda|lunda +luo|||Luo (Kenya and Tanzania)|luo (Kenya et Tanzanie) +lus|||Lushai|lushai +mac|mkd|mk|Macedonian|macédonien +mad|||Madurese|madourais +mag|||Magahi|magahi +mah||mh|Marshallese|marshall +mai|||Maithili|maithili +mak|||Makasar|makassar +mal||ml|Malayalam|malayalam +man|||Mandingo|mandingue +mao|mri|mi|Maori|maori +map|||Austronesian languages|austronésiennes, langues +mar||mr|Marathi|marathe +mas|||Masai|massaï +may|msa|ms|Malay|malais +mdf|||Moksha|moksa +mdr|||Mandar|mandar +men|||Mende|mendé +mga|||Irish, Middle (900-1200)|irlandais moyen (900-1200) +mic|||Mi'kmaq; Micmac|mi'kmaq; micmac +min|||Minangkabau|minangkabau +mis|||Uncoded languages|langues non codées +mkh|||Mon-Khmer languages|môn-khmer, langues +mlg||mg|Malagasy|malgache +mlt||mt|Maltese|maltais +mnc|||Manchu|mandchou +mni|||Manipuri|manipuri +mno|||Manobo languages|manobo, langues +moh|||Mohawk|mohawk +mon||mn|Mongolian|mongol +mos|||Mossi|moré +mul|||Multiple languages|multilingue +mun|||Munda languages|mounda, langues +mus|||Creek|muskogee +mwl|||Mirandese|mirandais +mwr|||Marwari|marvari +myn|||Mayan languages|maya, langues +myv|||Erzya|erza +nah|||Nahuatl languages|nahuatl, langues +nai|||North American Indian languages|nord-amérindiennes, langues +nap|||Neapolitan|napolitain +nau||na|Nauru|nauruan +nav||nv|Navajo; Navaho|navaho +nbl||nr|Ndebele, South; South Ndebele|ndébélé du Sud +nde||nd|Ndebele, North; North Ndebele|ndébélé du Nord +ndo||ng|Ndonga|ndonga +nds|||Low German; Low Saxon; German, Low; Saxon, Low|bas allemand; bas saxon; allemand, bas; saxon, bas +nep||ne|Nepali|népalais +new|||Nepal Bhasa; Newari|nepal bhasa; newari +nia|||Nias|nias +nic|||Niger-Kordofanian languages|nigéro-kordofaniennes, langues +niu|||Niuean|niué +nno||nn|Norwegian Nynorsk; Nynorsk, Norwegian|norvégien nynorsk; nynorsk, norvégien +nob||nb|Bokmål, Norwegian; Norwegian Bokmål|norvégien bokmål +nog|||Nogai|nogaï; nogay +non|||Norse, Old|norrois, vieux +nor||no|Norwegian|norvégien +nqo|||N'Ko|n'ko +nso|||Pedi; Sepedi; Northern Sotho|pedi; sepedi; sotho du Nord +nub|||Nubian languages|nubiennes, langues +nwc|||Classical Newari; Old Newari; Classical Nepal Bhasa|newari classique +nya||ny|Chichewa; Chewa; Nyanja|chichewa; chewa; nyanja +nym|||Nyamwezi|nyamwezi +nyn|||Nyankole|nyankolé +nyo|||Nyoro|nyoro +nzi|||Nzima|nzema +oci||oc|Occitan (post 1500); Provençal|occitan (après 1500); provençal +oji||oj|Ojibwa|ojibwa +ori||or|Oriya|oriya +orm||om|Oromo|galla +osa|||Osage|osage +oss||os|Ossetian; Ossetic|ossète +ota|||Turkish, Ottoman (1500-1928)|turc ottoman (1500-1928) +oto|||Otomian languages|otomi, langues +paa|||Papuan languages|papoues, langues +pag|||Pangasinan|pangasinan +pal|||Pahlavi|pahlavi +pam|||Pampanga; Kapampangan|pampangan +pan||pa|Panjabi; Punjabi|pendjabi +pap|||Papiamento|papiamento +pau|||Palauan|palau +peo|||Persian, Old (ca.600-400 B.C.)|perse, vieux (ca. 600-400 av. J.-C.) +per|fas|fa|Persian|persan +phi|||Philippine languages|philippines, langues +phn|||Phoenician|phénicien +pli||pi|Pali|pali +pol||pl|Polish|polonais +pon|||Pohnpeian|pohnpei +por||pt|Portuguese|portugais +pra|||Prakrit languages|prâkrit, langues +pro|||Provençal, Old (to 1500)|provençal ancien (jusqu'à 1500) +pus||ps|Pushto; Pashto|pachto +qaa-qtz|||Reserved for local use|réservée à l'usage local +que||qu|Quechua|quechua +raj|||Rajasthani|rajasthani +rap|||Rapanui|rapanui +rar|||Rarotongan; Cook Islands Maori|rarotonga; maori des îles Cook +roa|||Romance languages|romanes, langues +roh||rm|Romansh|romanche +rom|||Romany|tsigane +rum|ron|ro|Romanian; Moldavian; Moldovan|roumain; moldave +run||rn|Rundi|rundi +rup|||Aromanian; Arumanian; Macedo-Romanian|aroumain; macédo-roumain +rus||ru|Russian|russe +sad|||Sandawe|sandawe +sag||sg|Sango|sango +sah|||Yakut|iakoute +sai|||South American Indian (Other)|indiennes d'Amérique du Sud, autres langues +sal|||Salishan languages|salishennes, langues +sam|||Samaritan Aramaic|samaritain +san||sa|Sanskrit|sanskrit +sas|||Sasak|sasak +sat|||Santali|santal +scn|||Sicilian|sicilien +sco|||Scots|écossais +sel|||Selkup|selkoupe +sem|||Semitic languages|sémitiques, langues +sga|||Irish, Old (to 900)|irlandais ancien (jusqu'à 900) +sgn|||Sign Languages|langues des signes +shn|||Shan|chan +sid|||Sidamo|sidamo +sin||si|Sinhala; Sinhalese|singhalais +sio|||Siouan languages|sioux, langues +sit|||Sino-Tibetan languages|sino-tibétaines, langues +sla|||Slavic languages|slaves, langues +slo|slk|sk|Slovak|slovaque +slv||sl|Slovenian|slovène +sma|||Southern Sami|sami du Sud +sme||se|Northern Sami|sami du Nord +smi|||Sami languages|sames, langues +smj|||Lule Sami|sami de Lule +smn|||Inari Sami|sami d'Inari +smo||sm|Samoan|samoan +sms|||Skolt Sami|sami skolt +sna||sn|Shona|shona +snd||sd|Sindhi|sindhi +snk|||Soninke|soninké +sog|||Sogdian|sogdien +som||so|Somali|somali +son|||Songhai languages|songhai, langues +sot||st|Sotho, Southern|sotho du Sud +spa||es|Spanish; Castilian|espagnol; castillan +srd||sc|Sardinian|sarde +srn|||Sranan Tongo|sranan tongo +srp||sr|Serbian|serbe +srr|||Serer|sérère +ssa|||Nilo-Saharan languages|nilo-sahariennes, langues +ssw||ss|Swati|swati +suk|||Sukuma|sukuma +sun||su|Sundanese|soundanais +sus|||Susu|soussou +sux|||Sumerian|sumérien +swa||sw|Swahili|swahili +swe||sv|Swedish|suédois +syc|||Classical Syriac|syriaque classique +syr|||Syriac|syriaque +tah||ty|Tahitian|tahitien +tai|||Tai languages|tai, langues +tam||ta|Tamil|tamoul +tat||tt|Tatar|tatar +tel||te|Telugu|télougou +tem|||Timne|temne +ter|||Tereno|tereno +tet|||Tetum|tetum +tgk||tg|Tajik|tadjik +tgl||tl|Tagalog|tagalog +tha||th|Thai|thaï +tib|bod|bo|Tibetan|tibétain +tig|||Tigre|tigré +tir||ti|Tigrinya|tigrigna +tiv|||Tiv|tiv +tkl|||Tokelau|tokelau +tlh|||Klingon; tlhIngan-Hol|klingon +tli|||Tlingit|tlingit +tmh|||Tamashek|tamacheq +tog|||Tonga (Nyasa)|tonga (Nyasa) +ton||to|Tonga (Tonga Islands)|tongan (Îles Tonga) +tpi|||Tok Pisin|tok pisin +tsi|||Tsimshian|tsimshian +tsn||tn|Tswana|tswana +tso||ts|Tsonga|tsonga +tuk||tk|Turkmen|turkmène +tum|||Tumbuka|tumbuka +tup|||Tupi languages|tupi, langues +tur||tr|Turkish|turc +tut|||Altaic languages|altaïques, langues +tvl|||Tuvalu|tuvalu +twi||tw|Twi|twi +tyv|||Tuvinian|touva +udm|||Udmurt|oudmourte +uga|||Ugaritic|ougaritique +uig||ug|Uighur; Uyghur|ouïgour +ukr||uk|Ukrainian|ukrainien +umb|||Umbundu|umbundu +und|||Undetermined|indéterminée +urd||ur|Urdu|ourdou +uzb||uz|Uzbek|ouszbek +vai|||Vai|vaï +ven||ve|Venda|venda +vie||vi|Vietnamese|vietnamien +vol||vo|Volapük|volapük +vot|||Votic|vote +wak|||Wakashan languages|wakashanes, langues +wal|||Walamo|walamo +war|||Waray|waray +was|||Washo|washo +wel|cym|cy|Welsh|gallois +wen|||Sorbian languages|sorabes, langues +wln||wa|Walloon|wallon +wol||wo|Wolof|wolof +xal|||Kalmyk; Oirat|kalmouk; oïrat +xho||xh|Xhosa|xhosa +yao|||Yao|yao +yap|||Yapese|yapois +yid||yi|Yiddish|yiddish +yor||yo|Yoruba|yoruba +ypk|||Yupik languages|yupik, langues +zap|||Zapotec|zapotèque +zbl|||Blissymbols; Blissymbolics; Bliss|symboles Bliss; Bliss +zen|||Zenaga|zenaga +zgh|||Standard Moroccan Tamazight|amazighe standard marocain +zha||za|Zhuang; Chuang|zhuang; chuang +znd|||Zande languages|zandé, langues +zul||zu|Zulu|zoulou +zun|||Zuni|zuni +zxx|||No linguistic content; Not applicable|pas de contenu linguistique; non applicable +zza|||Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki|zaza; dimili; dimli; kirdki; kirmanjki; zazaki + diff --git a/src/extensions/locale/ext-locale-init.reb b/src/extensions/locale/ext-locale-init.reb new file mode 100644 index 0000000000..3bc22a8935 --- /dev/null +++ b/src/extensions/locale/ext-locale-init.reb @@ -0,0 +1,496 @@ +REBOL [ + Title: "Locale Extension" + name: 'Locale + type: 'Extension + version: 1.0.0 + license: {Apache 2.0} +] + +unless 'Windows = first system/platform [ + ; Windows has locale implemented as a native + + ;DO NOT EDIT this table + ;It's updated by iso3166.r + iso-3166-table: make map! lock [ + "AF" "Afghanistan" + "AX" "Åland Islands" + "AL" "Albania" + "DZ" "Algeria" + "AS" "American Samoa" + "AD" "Andorra" + "AO" "Angola" + "AI" "Anguilla" + "AQ" "Antarctica" + "AG" "Antigua And Barbuda" + "AR" "Argentina" + "AM" "Armenia" + "AW" "Aruba" + "AU" "Australia" + "AT" "Austria" + "AZ" "Azerbaijan" + "BS" "Bahamas" + "BH" "Bahrain" + "BD" "Bangladesh" + "BB" "Barbados" + "BY" "Belarus" + "BE" "Belgium" + "BZ" "Belize" + "BJ" "Benin" + "BM" "Bermuda" + "BT" "Bhutan" + "BO" "Bolivia, Plurinational State of" + "BQ" "Bonaire, Sint Eustatius And Saba" + "BA" "Bosnia And Herzegovina" + "BW" "Botswana" + "BV" "Bouvet Island" + "BR" "Brazil" + "IO" "British Indian Ocean Territory" + "BN" "Brunei Darussalam" + "BG" "Bulgaria" + "BF" "Burkina Faso" + "BI" "Burundi" + "KH" "Cambodia" + "CM" "Cameroon" + "CA" "Canada" + "CV" "Cape Verde" + "KY" "Cayman Islands" + "CF" "Central African Republic" + "TD" "Chad" + "CL" "Chile" + "CN" "China" + "CX" "Christmas Island" + "CC" "Cocos (keeling) Islands" + "CO" "Colombia" + "KM" "Comoros" + "CG" "Congo" + "CD" "Congo, The Democratic Republic of The" + "CK" "Cook Islands" + "CR" "Costa Rica" + "CI" "Côte D'ivoire" + "HR" "Croatia" + "CU" "Cuba" + "CW" "Curaçao" + "CY" "Cyprus" + "CZ" "Czech Republic" + "DK" "Denmark" + "DJ" "Djibouti" + "DM" "Dominica" + "DO" "Dominican Republic" + "EC" "Ecuador" + "EG" "Egypt" + "SV" "El Salvador" + "GQ" "Equatorial Guinea" + "ER" "Eritrea" + "EE" "Estonia" + "ET" "Ethiopia" + "FK" "Falkland Islands (malvinas)" + "FO" "Faroe Islands" + "FJ" "Fiji" + "FI" "Finland" + "FR" "France" + "GF" "French Guiana" + "PF" "French Polynesia" + "TF" "French Southern Territories" + "GA" "Gabon" + "GM" "Gambia" + "GE" "Georgia" + "DE" "Germany" + "GH" "Ghana" + "GI" "Gibraltar" + "GR" "Greece" + "GL" "Greenland" + "GD" "Grenada" + "GP" "Guadeloupe" + "GU" "Guam" + "GT" "Guatemala" + "GG" "Guernsey" + "GN" "Guinea" + "GW" "Guinea-bissau" + "GY" "Guyana" + "HT" "Haiti" + "HM" "Heard Island And Mcdonald Islands" + "VA" "Holy See (vatican City State)" + "HN" "Honduras" + "HK" "Hong Kong" + "HU" "Hungary" + "IS" "Iceland" + "IN" "India" + "ID" "Indonesia" + "IR" "Iran, Islamic Republic of" + "IQ" "Iraq" + "IE" "Ireland" + "IM" "Isle of Man" + "IL" "Israel" + "IT" "Italy" + "JM" "Jamaica" + "JP" "Japan" + "JE" "Jersey" + "JO" "Jordan" + "KZ" "Kazakhstan" + "KE" "Kenya" + "KI" "Kiribati" + "KP" "Korea, Democratic People's Republic of" + "KR" "Korea, Republic of" + "KW" "Kuwait" + "KG" "Kyrgyzstan" + "LA" "Lao People's Democratic Republic" + "LV" "Latvia" + "LB" "Lebanon" + "LS" "Lesotho" + "LR" "Liberia" + "LY" "Libya" + "LI" "Liechtenstein" + "LT" "Lithuania" + "LU" "Luxembourg" + "MO" "Macao" + "MK" "Macedonia, The Former Yugoslav Republic of" + "MG" "Madagascar" + "MW" "Malawi" + "MY" "Malaysia" + "MV" "Maldives" + "ML" "Mali" + "MT" "Malta" + "MH" "Marshall Islands" + "MQ" "Martinique" + "MR" "Mauritania" + "MU" "Mauritius" + "YT" "Mayotte" + "MX" "Mexico" + "FM" "Micronesia, Federated States of" + "MD" "Moldova, Republic of" + "MC" "Monaco" + "MN" "Mongolia" + "ME" "Montenegro" + "MS" "Montserrat" + "MA" "Morocco" + "MZ" "Mozambique" + "MM" "Myanmar" + "NA" "Namibia" + "NR" "Nauru" + "NP" "Nepal" + "NL" "Netherlands" + "NC" "New Caledonia" + "NZ" "New Zealand" + "NI" "Nicaragua" + "NE" "Niger" + "NG" "Nigeria" + "NU" "Niue" + "NF" "Norfolk Island" + "MP" "Northern Mariana Islands" + "NO" "Norway" + "OM" "Oman" + "PK" "Pakistan" + "PW" "Palau" + "PS" "Palestine, State of" + "PA" "Panama" + "PG" "Papua New Guinea" + "PY" "Paraguay" + "PE" "Peru" + "PH" "Philippines" + "PN" "Pitcairn" + "PL" "Poland" + "PT" "Portugal" + "PR" "Puerto Rico" + "QA" "Qatar" + "RE" "Réunion" + "RO" "Romania" + "RU" "Russian Federation" + "RW" "Rwanda" + "BL" "Saint Barthélemy" + "SH" "Saint Helena, Ascension And Tristan Da Cunha" + "KN" "Saint Kitts And Nevis" + "LC" "Saint Lucia" + "MF" "Saint Martin (french Part)" + "PM" "Saint Pierre And Miquelon" + "VC" "Saint Vincent And The Grenadines" + "WS" "Samoa" + "SM" "San Marino" + "ST" "Sao Tome And Principe" + "SA" "Saudi Arabia" + "SN" "Senegal" + "RS" "Serbia" + "SC" "Seychelles" + "SL" "Sierra Leone" + "SG" "Singapore" + "SX" "Sint Maarten (dutch Part)" + "SK" "Slovakia" + "SI" "Slovenia" + "SB" "Solomon Islands" + "SO" "Somalia" + "ZA" "South Africa" + "GS" "South Georgia And The South Sandwich Islands" + "SS" "South Sudan" + "ES" "Spain" + "LK" "Sri Lanka" + "SD" "Sudan" + "SR" "Suriname" + "SJ" "Svalbard And Jan Mayen" + "SZ" "Swaziland" + "SE" "Sweden" + "CH" "Switzerland" + "SY" "Syrian Arab Republic" + "TW" "Taiwan, Province of China" + "TJ" "Tajikistan" + "TZ" "Tanzania, United Republic of" + "TH" "Thailand" + "TL" "Timor-leste" + "TG" "Togo" + "TK" "Tokelau" + "TO" "Tonga" + "TT" "Trinidad And Tobago" + "TN" "Tunisia" + "TR" "Turkey" + "TM" "Turkmenistan" + "TC" "Turks And Caicos Islands" + "TV" "Tuvalu" + "UG" "Uganda" + "UA" "Ukraine" + "AE" "United Arab Emirates" + "GB" "United Kingdom" + "US" "United States" + "UM" "United States Minor Outlying Islands" + "UY" "Uruguay" + "UZ" "Uzbekistan" + "VU" "Vanuatu" + "VE" "Venezuela, Bolivarian Republic of" + "VN" "Viet Nam" + "VG" "Virgin Islands, British" + "VI" "Virgin Islands, U.S." + "WF" "Wallis And Futuna" + "EH" "Western Sahara" + "YE" "Yemen" + "ZM" "Zambia" + "ZW" "Zimbabwe" +] + + ;DO NOT EDIT this table + ;It's updated by iso639.r + iso-639-table: make map! lock [ + "aa" "Afar" + "ab" "Abkhazian" + "af" "Afrikaans" + "ak" "Akan" + "sq" "Albanian" + "am" "Amharic" + "ar" "Arabic" + "an" "Aragonese" + "hy" "Armenian" + "as" "Assamese" + "av" "Avaric" + "ae" "Avestan" + "ay" "Aymara" + "az" "Azerbaijani" + "ba" "Bashkir" + "bm" "Bambara" + "eu" "Basque" + "be" "Belarusian" + "bn" "Bengali" + "bh" "Bihari languages" + "bi" "Bislama" + "bs" "Bosnian" + "br" "Breton" + "bg" "Bulgarian" + "my" "Burmese" + "ca" "Catalan; Valencian" + "ch" "Chamorro" + "ce" "Chechen" + "zh" "Chinese" + "cu" {Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic} + "cv" "Chuvash" + "kw" "Cornish" + "co" "Corsican" + "cr" "Cree" + "cs" "Czech" + "da" "Danish" + "dv" "Divehi; Dhivehi; Maldivian" + "nl" "Dutch; Flemish" + "dz" "Dzongkha" + "en" "English" + "eo" "Esperanto" + "et" "Estonian" + "ee" "Ewe" + "fo" "Faroese" + "fj" "Fijian" + "fi" "Finnish" + "fr" "French" + "fy" "Western Frisian" + "ff" "Fulah" + "ka" "Georgian" + "de" "German" + "gd" "Gaelic; Scottish Gaelic" + "ga" "Irish" + "gl" "Galician" + "gv" "Manx" + "el" "Greek, Modern (1453-)" + "gn" "Guarani" + "gu" "Gujarati" + "ht" "Haitian; Haitian Creole" + "ha" "Hausa" + "he" "Hebrew" + "hz" "Herero" + "hi" "Hindi" + "ho" "Hiri Motu" + "hr" "Croatian" + "hu" "Hungarian" + "ig" "Igbo" + "is" "Icelandic" + "io" "Ido" + "ii" "Sichuan Yi; Nuosu" + "iu" "Inuktitut" + "ie" "Interlingue; Occidental" + "ia" {Interlingua (International Auxiliary Language Association)} + "id" "Indonesian" + "ik" "Inupiaq" + "it" "Italian" + "jv" "Javanese" + "ja" "Japanese" + "kl" "Kalaallisut; Greenlandic" + "kn" "Kannada" + "ks" "Kashmiri" + "kr" "Kanuri" + "kk" "Kazakh" + "km" "Central Khmer" + "ki" "Kikuyu; Gikuyu" + "rw" "Kinyarwanda" + "ky" "Kirghiz; Kyrgyz" + "kv" "Komi" + "kg" "Kongo" + "ko" "Korean" + "kj" "Kuanyama; Kwanyama" + "ku" "Kurdish" + "lo" "Lao" + "la" "Latin" + "lv" "Latvian" + "li" "Limburgan; Limburger; Limburgish" + "ln" "Lingala" + "lt" "Lithuanian" + "lb" "Luxembourgish; Letzeburgesch" + "lu" "Luba-Katanga" + "lg" "Ganda" + "mk" "Macedonian" + "mh" "Marshallese" + "ml" "Malayalam" + "mi" "Maori" + "mr" "Marathi" + "ms" "Malay" + "mg" "Malagasy" + "mt" "Maltese" + "mn" "Mongolian" + "na" "Nauru" + "nv" "Navajo; Navaho" + "nr" "Ndebele, South; South Ndebele" + "nd" "Ndebele, North; North Ndebele" + "ng" "Ndonga" + "ne" "Nepali" + "nn" "Norwegian Nynorsk; Nynorsk, Norwegian" + "nb" "Bokmål, Norwegian; Norwegian Bokmål" + "no" "Norwegian" + "ny" "Chichewa; Chewa; Nyanja" + "oc" "Occitan (post 1500); Provençal" + "oj" "Ojibwa" + "or" "Oriya" + "om" "Oromo" + "os" "Ossetian; Ossetic" + "pa" "Panjabi; Punjabi" + "fa" "Persian" + "pi" "Pali" + "pl" "Polish" + "pt" "Portuguese" + "ps" "Pushto; Pashto" + "qu" "Quechua" + "rm" "Romansh" + "ro" "Romanian; Moldavian; Moldovan" + "rn" "Rundi" + "ru" "Russian" + "sg" "Sango" + "sa" "Sanskrit" + "si" "Sinhala; Sinhalese" + "sk" "Slovak" + "sl" "Slovenian" + "se" "Northern Sami" + "sm" "Samoan" + "sn" "Shona" + "sd" "Sindhi" + "so" "Somali" + "st" "Sotho, Southern" + "es" "Spanish; Castilian" + "sc" "Sardinian" + "sr" "Serbian" + "ss" "Swati" + "su" "Sundanese" + "sw" "Swahili" + "sv" "Swedish" + "ty" "Tahitian" + "ta" "Tamil" + "tt" "Tatar" + "te" "Telugu" + "tg" "Tajik" + "tl" "Tagalog" + "th" "Thai" + "bo" "Tibetan" + "ti" "Tigrinya" + "to" "Tonga (Tonga Islands)" + "tn" "Tswana" + "ts" "Tsonga" + "tk" "Turkmen" + "tr" "Turkish" + "tw" "Twi" + "ug" "Uighur; Uyghur" + "uk" "Ukrainian" + "ur" "Urdu" + "uz" "Uzbek" + "ve" "Venda" + "vi" "Vietnamese" + "vo" "Volapük" + "cy" "Welsh" + "wa" "Walloon" + "wo" "Wolof" + "xh" "Xhosa" + "yi" "Yiddish" + "yo" "Yoruba" + "za" "Zhuang; Chuang" + "zu" "Zulu" +] + + hijack 'locale function [ + type [word!] + + iso-639 (iso-639-table) + iso-3166 (iso-3166-table) + ][ + env-lang: get-env "LANG" + unless env-lang [ + return _ + ] + + letter: charset [#"a" - #"z" #"A" - #"Z"] + unless parse env-lang [ + copy lang: [some letter] + #"_" copy territory: [some letter] + to end + ][ + fail spaced ["Malformated env LANG:" env-lang] + ] + + case [ + find? [language language*] type [ + select iso-639 lang + ] + find? [territory territory*] type [ + select iso-3166 territory + ] + true [ + fail spaced ["Invalid locale type:" type] + ] + ] + ] + + unset 'iso-3166-table + unset 'iso-639-table +] + +; initialize system/locale +system/locale/language: locale 'language +system/locale/language*: locale 'language* +system/locale/locale: locale 'territory +system/locale/locale*: locale 'territory* diff --git a/src/extensions/locale/ext-locale.c b/src/extensions/locale/ext-locale.c new file mode 100644 index 0000000000..173ba76a23 --- /dev/null +++ b/src/extensions/locale/ext-locale.c @@ -0,0 +1,53 @@ +// +// File: %ext-locale.c +// Summary: "Locale functions" +// Section: Extension +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ================================================================== +// +#include "sys-core.h" +#include "sys-ext.h" + + +#include "tmp-ext-locale-init.inc" + + +#define MODULE_INCLUDE_DECLARATION_ONLY +#include "tmp-mod-locale-last.h" + +DEFINE_EXT_INIT_COMPRESSED(Locale, //name of the extension + script_bytes, // REBOL script for the extension in the source form + { + // init all modules in this extension + int init = CALL_MODULE_INIT(Locale); + if (init < 0) return init; + } +) + +DEFINE_EXT_QUIT(Locale, +{ + return CALL_MODULE_QUIT(Locale); +} +) diff --git a/src/extensions/locale/iso3166.r b/src/extensions/locale/iso3166.r new file mode 100644 index 0000000000..2e7a56bdab --- /dev/null +++ b/src/extensions/locale/iso3166.r @@ -0,0 +1,63 @@ +REBOL [] + +inp: %iso3166.txt +init: %ext-locale-init.reb +cnt: read inp +if #{EFBBBF} = to binary! copy/part cnt 3 [ ;UTF8 BOM + cnt: skip cnt 3 +] + +lower: charset [#"a" - #"z"] +letter: charset [#"a" - #"z" #"A" - #"Z"] + +capitalize: func [ + n +][ + ret: copy "" + words: split to string! n " " + spaced [ + map-each w words [ + case [ + w = "OF" [ + "of" + ] + w = "U.S." [ + "U.S." + ] + ] else [ + unspaced [ + uppercase first w + lowercase next w + ] + ] + ] + ] +] + +iso-3166-table: make map! 512 +parse cnt [ + some [ + copy name to ";" + ";" copy code-2 to "^/" + (append iso-3166-table pair: reduce [lock to string! code-2 to string! capitalize name] + ) + + "^/" + ] +] + +init-code: to string! read init +space: charset " ^-^M^/" +iso-3166-table-cnt: find mold iso-3166-table #"[" +unless parse init-code [ + thru "iso-3166-table:" + to #"[" + change [ + #"[" thru #"]" + ] iso-3166-table-cnt + to end +][ + fail "Failed to update iso-3166-table" +] + +write init init-code diff --git a/src/extensions/locale/iso3166.txt b/src/extensions/locale/iso3166.txt new file mode 100644 index 0000000000..02d0157792 --- /dev/null +++ b/src/extensions/locale/iso3166.txt @@ -0,0 +1,250 @@ +AFGHANISTAN;AF +ÅLAND ISLANDS;AX +ALBANIA;AL +ALGERIA;DZ +AMERICAN SAMOA;AS +ANDORRA;AD +ANGOLA;AO +ANGUILLA;AI +ANTARCTICA;AQ +ANTIGUA AND BARBUDA;AG +ARGENTINA;AR +ARMENIA;AM +ARUBA;AW +AUSTRALIA;AU +AUSTRIA;AT +AZERBAIJAN;AZ +BAHAMAS;BS +BAHRAIN;BH +BANGLADESH;BD +BARBADOS;BB +BELARUS;BY +BELGIUM;BE +BELIZE;BZ +BENIN;BJ +BERMUDA;BM +BHUTAN;BT +BOLIVIA, PLURINATIONAL STATE OF;BO +BONAIRE, SINT EUSTATIUS AND SABA;BQ +BOSNIA AND HERZEGOVINA;BA +BOTSWANA;BW +BOUVET ISLAND;BV +BRAZIL;BR +BRITISH INDIAN OCEAN TERRITORY;IO +BRUNEI DARUSSALAM;BN +BULGARIA;BG +BURKINA FASO;BF +BURUNDI;BI +CAMBODIA;KH +CAMEROON;CM +CANADA;CA +CAPE VERDE;CV +CAYMAN ISLANDS;KY +CENTRAL AFRICAN REPUBLIC;CF +CHAD;TD +CHILE;CL +CHINA;CN +CHRISTMAS ISLAND;CX +COCOS (KEELING) ISLANDS;CC +COLOMBIA;CO +COMOROS;KM +CONGO;CG +CONGO, THE DEMOCRATIC REPUBLIC OF THE;CD +COOK ISLANDS;CK +COSTA RICA;CR +CÔTE D'IVOIRE;CI +CROATIA;HR +CUBA;CU +CURAÇAO;CW +CYPRUS;CY +CZECH REPUBLIC;CZ +DENMARK;DK +DJIBOUTI;DJ +DOMINICA;DM +DOMINICAN REPUBLIC;DO +ECUADOR;EC +EGYPT;EG +EL SALVADOR;SV +EQUATORIAL GUINEA;GQ +ERITREA;ER +ESTONIA;EE +ETHIOPIA;ET +FALKLAND ISLANDS (MALVINAS);FK +FAROE ISLANDS;FO +FIJI;FJ +FINLAND;FI +FRANCE;FR +FRENCH GUIANA;GF +FRENCH POLYNESIA;PF +FRENCH SOUTHERN TERRITORIES;TF +GABON;GA +GAMBIA;GM +GEORGIA;GE +GERMANY;DE +GHANA;GH +GIBRALTAR;GI +GREECE;GR +GREENLAND;GL +GRENADA;GD +GUADELOUPE;GP +GUAM;GU +GUATEMALA;GT +GUERNSEY;GG +GUINEA;GN +GUINEA-BISSAU;GW +GUYANA;GY +HAITI;HT +HEARD ISLAND AND MCDONALD ISLANDS;HM +HOLY SEE (VATICAN CITY STATE);VA +HONDURAS;HN +HONG KONG;HK +HUNGARY;HU +ICELAND;IS +INDIA;IN +INDONESIA;ID +IRAN, ISLAMIC REPUBLIC OF;IR +IRAQ;IQ +IRELAND;IE +ISLE OF MAN;IM +ISRAEL;IL +ITALY;IT +JAMAICA;JM +JAPAN;JP +JERSEY;JE +JORDAN;JO +KAZAKHSTAN;KZ +KENYA;KE +KIRIBATI;KI +KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF;KP +KOREA, REPUBLIC OF;KR +KUWAIT;KW +KYRGYZSTAN;KG +LAO PEOPLE'S DEMOCRATIC REPUBLIC;LA +LATVIA;LV +LEBANON;LB +LESOTHO;LS +LIBERIA;LR +LIBYA;LY +LIECHTENSTEIN;LI +LITHUANIA;LT +LUXEMBOURG;LU +MACAO;MO +MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF;MK +MADAGASCAR;MG +MALAWI;MW +MALAYSIA;MY +MALDIVES;MV +MALI;ML +MALTA;MT +MARSHALL ISLANDS;MH +MARTINIQUE;MQ +MAURITANIA;MR +MAURITIUS;MU +MAYOTTE;YT +MEXICO;MX +MICRONESIA, FEDERATED STATES OF;FM +MOLDOVA, REPUBLIC OF;MD +MONACO;MC +MONGOLIA;MN +MONTENEGRO;ME +MONTSERRAT;MS +MOROCCO;MA +MOZAMBIQUE;MZ +MYANMAR;MM +NAMIBIA;NA +NAURU;NR +NEPAL;NP +NETHERLANDS;NL +NEW CALEDONIA;NC +NEW ZEALAND;NZ +NICARAGUA;NI +NIGER;NE +NIGERIA;NG +NIUE;NU +NORFOLK ISLAND;NF +NORTHERN MARIANA ISLANDS;MP +NORWAY;NO +OMAN;OM +PAKISTAN;PK +PALAU;PW +PALESTINE, STATE OF;PS +PANAMA;PA +PAPUA NEW GUINEA;PG +PARAGUAY;PY +PERU;PE +PHILIPPINES;PH +PITCAIRN;PN +POLAND;PL +PORTUGAL;PT +PUERTO RICO;PR +QATAR;QA +RÉUNION;RE +ROMANIA;RO +RUSSIAN FEDERATION;RU +RWANDA;RW +SAINT BARTHÉLEMY;BL +SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA;SH +SAINT KITTS AND NEVIS;KN +SAINT LUCIA;LC +SAINT MARTIN (FRENCH PART);MF +SAINT PIERRE AND MIQUELON;PM +SAINT VINCENT AND THE GRENADINES;VC +SAMOA;WS +SAN MARINO;SM +SAO TOME AND PRINCIPE;ST +SAUDI ARABIA;SA +SENEGAL;SN +SERBIA;RS +SEYCHELLES;SC +SIERRA LEONE;SL +SINGAPORE;SG +SINT MAARTEN (DUTCH PART);SX +SLOVAKIA;SK +SLOVENIA;SI +SOLOMON ISLANDS;SB +SOMALIA;SO +SOUTH AFRICA;ZA +SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS;GS +SOUTH SUDAN;SS +SPAIN;ES +SRI LANKA;LK +SUDAN;SD +SURINAME;SR +SVALBARD AND JAN MAYEN;SJ +SWAZILAND;SZ +SWEDEN;SE +SWITZERLAND;CH +SYRIAN ARAB REPUBLIC;SY +TAIWAN, PROVINCE OF CHINA;TW +TAJIKISTAN;TJ +TANZANIA, UNITED REPUBLIC OF;TZ +THAILAND;TH +TIMOR-LESTE;TL +TOGO;TG +TOKELAU;TK +TONGA;TO +TRINIDAD AND TOBAGO;TT +TUNISIA;TN +TURKEY;TR +TURKMENISTAN;TM +TURKS AND CAICOS ISLANDS;TC +TUVALU;TV +UGANDA;UG +UKRAINE;UA +UNITED ARAB EMIRATES;AE +UNITED KINGDOM;GB +UNITED STATES;US +UNITED STATES MINOR OUTLYING ISLANDS;UM +URUGUAY;UY +UZBEKISTAN;UZ +VANUATU;VU +VENEZUELA, BOLIVARIAN REPUBLIC OF;VE +VIET NAM;VN +VIRGIN ISLANDS, BRITISH;VG +VIRGIN ISLANDS, U.S.;VI +WALLIS AND FUTUNA;WF +WESTERN SAHARA;EH +YEMEN;YE +ZAMBIA;ZM +ZIMBABWE;ZW + diff --git a/src/extensions/locale/iso639.r b/src/extensions/locale/iso639.r new file mode 100644 index 0000000000..7c2ebd80a6 --- /dev/null +++ b/src/extensions/locale/iso639.r @@ -0,0 +1,68 @@ +REBOL [] + +init: %ext-locale-init.reb +inp: %ISO-639-2_utf-8.txt +cnt: read inp +if #{EFBBBF} = to binary! copy/part cnt 3 [ ;UTF8 BOM + cnt: skip cnt 3 +] + +;cnt: to string! cnt +;print ["string cnt BOM:" mold copy/part cnt 3] + +iso-639-table: make map! 1024 + +lower: charset [#"a" - #"z"] +letter: charset [#"a" - #"z" #"A" - #"Z"] + +parse cnt [ + some [ + ;initialization + (code-2: name: _) + + ; 3-letter code + ; + to "|" + + ; "terminological code" + ; https://en.wikipedia.org/wiki/ISO_639-2#B_and_T_codes + ; + "|" opt [3 lower] + + ; 2-letter code + ; + "|" opt [ + copy code-2 2 lower + ] + + ; Language name in English + ; + "|" copy name to "|" ( + if code-2 [ + append iso-639-table reduce [lock to string! code-2 to string! name] + ] + ) + + ; Language name in French + ; + "|" to "^/" + + ["^/" | "^M"] + ] +] + +init-code: to string! read init +space: charset " ^-^M^/" +iso-639-table-cnt: find mold iso-639-table #"[" +unless parse init-code [ + thru "iso-639-table:" + to #"[" + change [ + #"[" thru #"]" + ] iso-639-table-cnt + to end +][ + fail "Failed to update iso-639-table" +] + +write init init-code diff --git a/src/extensions/locale/mod-locale.c b/src/extensions/locale/mod-locale.c new file mode 100644 index 0000000000..51267752aa --- /dev/null +++ b/src/extensions/locale/mod-locale.c @@ -0,0 +1,93 @@ +// +// File: %mod-locale.c +// Summary: "Native Functions for spawning and controlling processes" +// Section: Extension +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + +#ifdef TO_WINDOWS + #include +#endif + +// IS_ERROR might be defined in winerror.h and reb-types.h +#ifdef IS_ERROR +#undef IS_ERROR +#endif + +#include "sys-core.h" +#include "sys-ext.h" + +#include "tmp-mod-locale-first.h" + + +// +// locale: native/export [ +// "Get locale specific information" +// category [word!] +// {Language: English name of the language, +// Territory: English name of the country/region, +// Language*: Full localized primary name of the language +// Territory*: Full localized name of the country/region} +// ] +// new-words: [Language Language* Territory Territory*] +// new-errors: [ +// invalid-category: [{Invalid locale category:} :arg1] +// ] +// +REBNATIVE(locale) +// +{ +#ifdef TO_WINDOWS + INCLUDE_PARAMS_OF_LOCALE; + REBSTR *cat = VAL_WORD_CANON(ARG(category)); + LCTYPE type; + if (cat == LOCALE_WORD_LANGUAGE) { + type = LOCALE_SENGLANGUAGE; + } else if (cat == LOCALE_WORD_LANGUAGE_P) { + type = LOCALE_SNATIVELANGNAME; + } else if (cat == LOCALE_WORD_TERRITORY) { + type = LOCALE_SENGCOUNTRY; + } else if (cat == LOCALE_WORD_TERRITORY_P) { + type = LOCALE_SCOUNTRY; + } else { + fail (Error(RE_EXT_LOCALE_INVALID_CATEGORY, ARG(category), END)); + } + int len = GetLocaleInfo(0, type, 0, 0); + REBSER *data = Make_Unicode(len); + assert(sizeof(REBUNI) == sizeof(wchar_t)); + len = GetLocaleInfo(0, type, cast(wchar_t*, UNI_HEAD(data)), len); + SET_UNI_LEN(data, len - 1); + + Init_String(D_OUT, data); + + return R_OUT; +#else + UNUSED(frame_); + fail ("Locale not implemented for non-windows"); +#endif + +} + +#include "tmp-mod-locale-last.h" diff --git a/src/extensions/png/ext-png.c b/src/extensions/png/ext-png.c new file mode 100644 index 0000000000..d1e51e23ea --- /dev/null +++ b/src/extensions/png/ext-png.c @@ -0,0 +1,75 @@ +// +// File: %ext-png.c +// Summary: "PNG codec" +// Section: Extension +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ================================================================== +// +#include "sys-core.h" +#include "sys-ext.h" + +static const REBYTE script_bytes[] = +"REBOL [" + "Title: \"PNG Codec Extension\"\n" + "name: 'PNG\n" + "type: 'Extension\n" + "version: 1.0.0\n" + "license: {Apache 2.0}\n" +"]\n" +"sys/register-codec* 'png %.png\n" + "get in import 'upng 'identify-png?\n" + "get in import 'upng 'decode-png\n" + "get in import 'lodepng 'encode-png-lodepng\n" +; + +#define MODULE_INCLUDE_DECLARATION_ONLY +#include "tmp-mod-lodepng-last.h" +#include "tmp-mod-upng-last.h" + +DEFINE_EXT_INIT(PNG, //name of the extension + script_bytes, // REBOL script for the extension in the source form + { + // init all modules in this extension + int init = CALL_MODULE_INIT(LodePNG); + if (init < 0) return init; + init = CALL_MODULE_INIT(uPNG); + if (init < 0) return init; + } +) + +DEFINE_EXT_QUIT(PNG, +{ + int ret = 0; + + int r = CALL_MODULE_QUIT(uPNG); + if (r != 0) ret = r; + + r = CALL_MODULE_QUIT(LodePNG); + if (r != 0) ret = r; + + return ret; +} +) + diff --git a/src/extensions/png/lodepng.c b/src/extensions/png/lodepng.c new file mode 100644 index 0000000000..2c478e4d37 --- /dev/null +++ b/src/extensions/png/lodepng.c @@ -0,0 +1,6291 @@ +/* +LodePNG version 20130311 + +Copyright (c) 2005-2013 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* +The manual and changelog are in the header file "lodepng.h" +Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. +*/ + +#include "lodepng.h" + +#include +#include + +#ifdef LODEPNG_COMPILE_CPP +#include +#endif /*LODEPNG_COMPILE_CPP*/ + +#include "reb-config.h" +#include "reb-c.h" + +#define VERSION_STRING "20130311" + +/* +This source file is built up in the following large parts. The code sections +with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. +-Tools for C and common code for PNG and Zlib +-C Code for Zlib (huffman, deflate, ...) +-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) +-The C++ wrapper around all of the above +*/ + +/*The malloc, realloc and free functions defined here with "lodepng_" in front +of the name, so that you can easily change them to others related to your +platform if needed. Everything else in the code calls these. Pass +-DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out +#define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and +define them in your own project's source files without needing to change +lodepng source code. Don't forget to remove "static" if you copypaste them +from here.*/ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +static void* lodepng_malloc(size_t size) +{ + return malloc(size); +} + +static void* lodepng_realloc(void* ptr, size_t new_size) +{ + return realloc(ptr, new_size); +} + +static void lodepng_free(void* ptr) +{ + free(ptr); +} +#else /*LODEPNG_COMPILE_ALLOCATORS*/ +void* lodepng_malloc(size_t size); +void* lodepng_realloc(void* ptr, size_t new_size); +void lodepng_free(void* ptr); +#endif /*LODEPNG_COMPILE_ALLOCATORS*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // Tools for C, and common code for PNG and Zlib. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Often in case of an error a value is assigned to a variable and then it breaks +out of a loop (to go to the cleanup phase of a function). This macro does that. +It makes the error handling code shorter and more readable. + +Example: if(!uivector_resizev(&frequencies_ll, 286, 0)) ERROR_BREAK(83); +*/ +#define CERROR_BREAK(errorvar, code)\ +{\ + errorvar = code;\ + break;\ +} + +/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ +#define ERROR_BREAK(code) CERROR_BREAK(error, code) + +/*Set error var to the error code, and return it.*/ +#define CERROR_RETURN_ERROR(errorvar, code)\ +{\ + errorvar = code;\ + return code;\ +} + +/*Try the code, if it returns error, also return the error.*/ +#define CERROR_TRY_RETURN(call)\ +{\ + unsigned error = call;\ + if(error) return error;\ +} + +/* +About uivector, ucvector and string: +-All of them wrap dynamic arrays or text strings in a similar way. +-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. +-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. +-They're not used in the interface, only internally in this file as static functions. +-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. +*/ + +#ifdef LODEPNG_COMPILE_ZLIB +/*dynamic vector of unsigned ints*/ +typedef struct uivector +{ + unsigned* data; + size_t size; /*size in number of unsigned longs*/ + size_t allocsize; /*allocated size in bytes*/ +} uivector; + +static void uivector_cleanup(void* p) +{ + ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; + lodepng_free(((uivector*)p)->data); + ((uivector*)p)->data = NULL; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_resize(uivector* p, size_t size) +{ + if(size * sizeof(unsigned) > p->allocsize) + { + size_t newsize = size * sizeof(unsigned) * 2; + void* data = lodepng_realloc(p->data, newsize); + if(data) + { + p->allocsize = newsize; + p->data = (unsigned*)data; + p->size = size; + } + else return 0; + } + else p->size = size; + return 1; +} + +/*resize and give all new elements the value*/ +static unsigned uivector_resizev(uivector* p, size_t size, unsigned value) +{ + size_t oldsize = p->size, i; + if(!uivector_resize(p, size)) return 0; + for(i = oldsize; i < size; i++) p->data[i] = value; + return 1; +} + +static void uivector_init(uivector* p) +{ + p->data = NULL; + p->size = p->allocsize = 0; +} + +#ifdef LODEPNG_COMPILE_ENCODER +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_push_back(uivector* p, unsigned c) +{ + if(!uivector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} + +/*copy q to p, returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_copy(uivector* p, const uivector* q) +{ + size_t i; + if(!uivector_resize(p, q->size)) return 0; + for(i = 0; i < q->size; i++) p->data[i] = q->data[i]; + return 1; +} + +static void uivector_swap(uivector* p, uivector* q) +{ + size_t tmp; + unsigned* tmpp; + tmp = p->size; p->size = q->size; q->size = tmp; + tmp = p->allocsize; p->allocsize = q->allocsize; q->allocsize = tmp; + tmpp = p->data; p->data = q->data; q->data = tmpp; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +/*dynamic vector of unsigned chars*/ +typedef struct ucvector +{ + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +} ucvector; + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_resize(ucvector* p, size_t size) +{ + if(size * sizeof(unsigned char) > p->allocsize) + { + size_t newsize = size * sizeof(unsigned char) * 2; + void* data = lodepng_realloc(p->data, newsize); + if(data) + { + p->allocsize = newsize; + p->data = (unsigned char*)data; + p->size = size; + } + else return 0; /*error: not enough memory*/ + } + else p->size = size; + return 1; +} + +#ifdef LODEPNG_COMPILE_PNG + +static void ucvector_cleanup(void* p) +{ + ((ucvector*)p)->size = ((ucvector*)p)->allocsize = 0; + lodepng_free(((ucvector*)p)->data); + ((ucvector*)p)->data = NULL; +} + +static void ucvector_init(ucvector* p) +{ + p->data = NULL; + p->size = p->allocsize = 0; +} + +#ifdef LODEPNG_COMPILE_DECODER +/*resize and give all new elements the value*/ +static unsigned ucvector_resizev(ucvector* p, size_t size, unsigned char value) +{ + size_t oldsize = p->size, i; + if(!ucvector_resize(p, size)) return 0; + for(i = oldsize; i < size; i++) p->data[i] = value; + return 1; +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ZLIB +/*you can both convert from vector to buffer&size and vica versa. If you use +init_buffer to take over a buffer and size, it is not needed to use cleanup*/ +static void ucvector_init_buffer(ucvector* p, unsigned char* buffer, size_t size) +{ + p->data = buffer; + p->allocsize = p->size = size; +} +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#if (defined(LODEPNG_COMPILE_PNG) && defined(LODEPNG_COMPILE_ANCILLARY_CHUNKS)) || defined(LODEPNG_COMPILE_ENCODER) +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_push_back(ucvector* p, unsigned char c) +{ + if(!ucvector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned string_resize(char** out, size_t size) +{ + char* data = (char*)lodepng_realloc(*out, size + 1); + if(data) + { + data[size] = 0; /*null termination char*/ + *out = data; + } + return data != 0; +} + +/*init a {char*, size_t} pair for use as string*/ +static void string_init(char** out) +{ + *out = NULL; + string_resize(out, 0); +} + +/*free the above pair again*/ +static void string_cleanup(char** out) +{ + lodepng_free(*out); + *out = NULL; +} + +static void string_set(char** out, const char* in) +{ + size_t insize = strlen(in), i = 0; + if(string_resize(out, insize)) + { + for(i = 0; i < insize; i++) + { + (*out)[i] = in[i]; + } + } +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_read32bitInt(const unsigned char* buffer) +{ + return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; +} + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) +/*buffer must have at least 4 allocated bytes available*/ +static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) +{ + buffer[0] = (unsigned char)((value >> 24) & 0xff); + buffer[1] = (unsigned char)((value >> 16) & 0xff); + buffer[2] = (unsigned char)((value >> 8) & 0xff); + buffer[3] = (unsigned char)((value ) & 0xff); +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + +#ifdef LODEPNG_COMPILE_ENCODER +static void lodepng_add32bitInt(ucvector* buffer, unsigned value) +{ + ucvector_resize(buffer, buffer->size + 4); /*todo: give error if resize failed*/ + lodepng_set32bitInt(&buffer->data[buffer->size - 4], value); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / File IO / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) +{ + FILE* file; + long size; + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + + file = fopen(filename, "rb"); + if(!file) return 78; + + /*get filesize:*/ + if (fseek(file , 0 , SEEK_END) < 0) { + fclose(file); + return 90; + } + size = ftell(file); + if (size <= 0) { + /* file size can't be zero */ + fclose(file); + return 91; + } + rewind(file); + + /*read contents of the file into the vector*/ + *outsize = 0; + *out = (unsigned char*)lodepng_malloc((size_t)size); + if(size && (*out)) (*outsize) = fread(*out, 1, (size_t)size, file); + + fclose(file); + if(!(*out) && size) return 83; /*the above malloc failed*/ + return 0; +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) +{ + FILE* file; + file = fopen(filename, "wb" ); + if(!file) return 79; + // Ren/C: fix cast away of const + fwrite((const char*)buffer , 1 , buffersize, file); + fclose(file); + return 0; +} + +#endif /*LODEPNG_COMPILE_DISK*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of common code and tools. Begin of Zlib related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER +/*TODO: this ignores potential out of memory errors*/ +static void addBitToStream(size_t* bitpointer, ucvector* bitstream, unsigned char bit) +{ + /*add a new byte at the end*/ + if((*bitpointer) % 8 == 0) ucvector_push_back(bitstream, (unsigned char)0); + /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/ + (bitstream->data[bitstream->size - 1]) |= (bit << ((*bitpointer) & 0x7)); + (*bitpointer)++; +} + +static void addBitsToStream(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) +{ + size_t i; + for(i = 0; i < nbits; i++) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> i) & 1)); +} + +static void addBitsToStreamReversed(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) +{ + size_t i; + for(i = 0; i < nbits; i++) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> (nbits - 1 - i)) & 1)); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +#define READBIT(bitpointer, bitstream) ((bitstream[bitpointer >> 3] >> (bitpointer & 0x7)) & (unsigned char)1) + +static unsigned char readBitFromStream(size_t* bitpointer, const unsigned char* bitstream) +{ + unsigned char result = (unsigned char)(READBIT(*bitpointer, bitstream)); + (*bitpointer)++; + return result; +} + +static unsigned readBitsFromStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) +{ + unsigned result = 0, i; + for(i = 0; i < nbits; i++) + { + result += ((unsigned)READBIT(*bitpointer, bitstream)) << i; + (*bitpointer)++; + } + return result; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +/*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_DISTANCE_SYMBOLS 32 +/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ +#define NUM_CODE_LENGTH_CODES 19 + +/*the base lengths represented by codes 257-285*/ +static const unsigned LENGTHBASE[29] + = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258}; + +/*the extra bits used by codes 257-285 (added to base length)*/ +static const unsigned LENGTHEXTRA[29] + = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0}; + +/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ +static const unsigned DISTANCEBASE[30] + = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; + +/*the extra bits of backwards distances (added to base)*/ +static const unsigned DISTANCEEXTRA[30] + = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + +/*the order in which "code length alphabet code lengths" are stored, out of this +the huffman tree of the dynamic huffman tree lengths is generated*/ +static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Huffman tree struct, containing multiple representations of the tree +*/ +typedef struct HuffmanTree +{ + unsigned* tree2d; + unsigned* tree1d; + unsigned* lengths; /*the lengths of the codes of the 1d-tree*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ +} HuffmanTree; + +/*function used for debug purposes to draw the tree in ascii art with C++*/ +/*#include +static void HuffmanTree_draw(HuffmanTree* tree) +{ + std::cout << "tree. length: " << tree->numcodes << " maxbitlen: " << tree->maxbitlen << std::endl; + for(size_t i = 0; i < tree->tree1d.size; i++) + { + if(tree->lengths.data[i]) + std::cout << i << " " << tree->tree1d.data[i] << " " << tree->lengths.data[i] << std::endl; + } + std::cout << std::endl; +}*/ + +static void HuffmanTree_init(HuffmanTree* tree) +{ + tree->tree2d = 0; + tree->tree1d = 0; + tree->lengths = 0; +} + +static void HuffmanTree_cleanup(HuffmanTree* tree) +{ + lodepng_free(tree->tree2d); + lodepng_free(tree->tree1d); + lodepng_free(tree->lengths); +} + +/*the tree representation used by the decoder. return value is error*/ +static unsigned HuffmanTree_make2DTree(HuffmanTree* tree) +{ + unsigned nodefilled = 0; /*up to which node it is filled*/ + unsigned treepos = 0; /*position in the tree (1 of the numcodes columns)*/ + unsigned n, i; + + tree->tree2d = (unsigned*)lodepng_malloc(tree->numcodes * 2 * sizeof(unsigned)); + if(!tree->tree2d) return 83; /*alloc fail*/ + + /* + convert tree1d[] to tree2d[][]. In the 2D array, a value of 32767 means + uninited, a value >= numcodes is an address to another bit, a value < numcodes + is a code. The 2 rows are the 2 possible bit values (0 or 1), there are as + many columns as codes - 1. + A good huffmann tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. + Here, the internal nodes are stored (what their 0 and 1 option point to). + There is only memory for such good tree currently, if there are more nodes + (due to too long length codes), error 55 will happen + */ + for(n = 0; n < tree->numcodes * 2; n++) + { + tree->tree2d[n] = 32767; /*32767 here means the tree2d isn't filled there yet*/ + } + + for(n = 0; n < tree->numcodes; n++) /*the codes*/ + { + for(i = 0; i < tree->lengths[n]; i++) /*the bits for this code*/ + { + unsigned char bit = (unsigned char)((tree->tree1d[n] >> (tree->lengths[n] - i - 1)) & 1); + if(treepos > tree->numcodes - 2) return 55; /*oversubscribed, see comment in lodepng_error_text*/ + if(tree->tree2d[2 * treepos + bit] == 32767) /*not yet filled in*/ + { + if(i + 1 == tree->lengths[n]) /*last bit*/ + { + tree->tree2d[2 * treepos + bit] = n; /*put the current code in it*/ + treepos = 0; + } + else + { + /*put address of the next step in here, first that address has to be found of course + (it's just nodefilled + 1)...*/ + nodefilled++; + /*addresses encoded with numcodes added to it*/ + tree->tree2d[2 * treepos + bit] = nodefilled + tree->numcodes; + treepos = nodefilled; + } + } + else treepos = tree->tree2d[2 * treepos + bit] - tree->numcodes; + } + } + + for(n = 0; n < tree->numcodes * 2; n++) + { + if(tree->tree2d[n] == 32767) tree->tree2d[n] = 0; /*remove possible remaining 32767's*/ + } + + return 0; +} + +/* +Second step for the ...makeFromLengths and ...makeFromFrequencies functions. +numcodes, lengths and maxbitlen must already be filled in correctly. return +value is error. +*/ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) +{ + uivector blcount; + uivector nextcode; + unsigned bits, n, error = 0; + + uivector_init(&blcount); + uivector_init(&nextcode); + + tree->tree1d = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned)); + if(!tree->tree1d) error = 83; /*alloc fail*/ + + if(!uivector_resizev(&blcount, tree->maxbitlen + 1, 0) + || !uivector_resizev(&nextcode, tree->maxbitlen + 1, 0)) + error = 83; /*alloc fail*/ + + if(!error) + { + /*step 1: count number of instances of each code length*/ + for(bits = 0; bits < tree->numcodes; bits++) blcount.data[tree->lengths[bits]]++; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; bits++) + { + nextcode.data[bits] = (nextcode.data[bits - 1] + blcount.data[bits - 1]) << 1; + } + /*step 3: generate all the codes*/ + for(n = 0; n < tree->numcodes; n++) + { + if(tree->lengths[n] != 0) tree->tree1d[n] = nextcode.data[tree->lengths[n]]++; + } + } + + uivector_cleanup(&blcount); + uivector_cleanup(&nextcode); + + if(!error) return HuffmanTree_make2DTree(tree); + else return error; +} + +/* +given the code lengths (as stored in the PNG file), generate the tree as defined +by Deflate. maxbitlen is the maximum bits that a code in the tree can have. +return value is error. +*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, + size_t numcodes, unsigned maxbitlen) +{ + unsigned i; + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + for(i = 0; i < numcodes; i++) tree->lengths[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + +#ifdef LODEPNG_COMPILE_ENCODER + +/* +A coin, this is the terminology used for the package-merge algorithm and the +coin collector's problem. This is used to generate the huffman tree. +A coin can be multiple coins (when they're merged) +*/ +typedef struct Coin +{ + uivector symbols; + float weight; /*the sum of all weights in this coin*/ +} Coin; + +static void coin_init(Coin* c) +{ + uivector_init(&c->symbols); +} + +/*argument c is void* so that this dtor can be given as function pointer to the vector resize function*/ +static void coin_cleanup(void* c) +{ + uivector_cleanup(&((Coin*)c)->symbols); +} + +static void coin_copy(Coin* c1, const Coin* c2) +{ + c1->weight = c2->weight; + uivector_copy(&c1->symbols, &c2->symbols); +} + +static void add_coins(Coin* c1, const Coin* c2) +{ + size_t i; + for(i = 0; i < c2->symbols.size; i++) uivector_push_back(&c1->symbols, c2->symbols.data[i]); + c1->weight += c2->weight; +} + +static void init_coins(Coin* coins, size_t num) +{ + size_t i; + for(i = 0; i < num; i++) coin_init(&coins[i]); +} + +static void cleanup_coins(Coin* coins, size_t num) +{ + size_t i; + for(i = 0; i < num; i++) coin_cleanup(&coins[i]); +} + +/* +This uses a simple combsort to sort the data. This function is not critical for +overall encoding speed and the data amount isn't that large. +*/ +static void sort_coins(Coin* data, size_t amount) +{ + size_t gap = amount; + unsigned char swapped = 0; + while((gap > 1) || swapped) + { + size_t i; + gap = (gap * 10) / 13; /*shrink factor 1.3*/ + if(gap == 9 || gap == 10) gap = 11; /*combsort11*/ + if(gap < 1) gap = 1; + swapped = 0; + for(i = 0; i < amount - gap; i++) + { + size_t j = i + gap; + if(data[j].weight < data[i].weight) + { + float temp = data[j].weight; data[j].weight = data[i].weight; data[i].weight = temp; + uivector_swap(&data[i].symbols, &data[j].symbols); + swapped = 1; + } + } + } +} + +static unsigned append_symbol_coins(Coin* coins, const unsigned* frequencies, unsigned numcodes, size_t sum) +{ + unsigned i; + unsigned j = 0; /*index of present symbols*/ + for(i = 0; i < numcodes; i++) + { + if(frequencies[i] != 0) /*only include symbols that are present*/ + { + coins[j].weight = frequencies[i] / (float)sum; + uivector_push_back(&coins[j].symbols, i); + j++; + } + } + return 0; +} + +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen) +{ + unsigned i, j; + size_t sum = 0, numpresent = 0; + unsigned error = 0; + Coin* coins; /*the coins of the currently calculated row*/ + Coin* prev_row; /*the previous row of coins*/ + unsigned numcoins; + unsigned coinmem; + + if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ + + for(i = 0; i < numcodes; i++) + { + if(frequencies[i] > 0) + { + numpresent++; + sum += frequencies[i]; + } + } + + for(i = 0; i < numcodes; i++) lengths[i] = 0; + + /*ensure at least two present symbols. There should be at least one symbol + according to RFC 1951 section 3.2.7. To decoders incorrectly require two. To + make these work as well ensure there are at least two symbols. The + Package-Merge code below also doesn't work correctly if there's only one + symbol, it'd give it the theoritical 0 bits but in practice zlib wants 1 bit*/ + if(numpresent == 0) + { + lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ + } + else if(numpresent == 1) + { + for(i = 0; i < numcodes; i++) + { + if(frequencies[i]) + { + lengths[i] = 1; + lengths[i == 0 ? 1 : 0] = 1; + break; + } + } + } + else + { + /*Package-Merge algorithm represented by coin collector's problem + For every symbol, maxbitlen coins will be created*/ + + coinmem = numpresent * 2; /*max amount of coins needed with the current algo*/ + coins = (Coin*)lodepng_malloc(sizeof(Coin) * coinmem); + prev_row = (Coin*)lodepng_malloc(sizeof(Coin) * coinmem); + if(!coins || !prev_row) return 83; /*alloc fail*/ + init_coins(coins, coinmem); + init_coins(prev_row, coinmem); + + /*first row, lowest denominator*/ + error = append_symbol_coins(coins, frequencies, numcodes, sum); + numcoins = numpresent; + sort_coins(coins, numcoins); + if(!error) + { + unsigned numprev = 0; + for(j = 1; j <= maxbitlen && !error; j++) /*each of the remaining rows*/ + { + unsigned tempnum; + Coin* tempcoins; + /*swap prev_row and coins, and their amounts*/ + tempcoins = prev_row; prev_row = coins; coins = tempcoins; + tempnum = numprev; numprev = numcoins; numcoins = tempnum; + + cleanup_coins(coins, numcoins); + init_coins(coins, numcoins); + + numcoins = 0; + + /*fill in the merged coins of the previous row*/ + for(i = 0; i + 1 < numprev; i += 2) + { + /*merge prev_row[i] and prev_row[i + 1] into new coin*/ + Coin* coin = &coins[numcoins++]; + coin_copy(coin, &prev_row[i]); + add_coins(coin, &prev_row[i + 1]); + } + /*fill in all the original symbols again*/ + if(j < maxbitlen) + { + error = append_symbol_coins(coins + numcoins, frequencies, numcodes, sum); + numcoins += numpresent; + } + sort_coins(coins, numcoins); + } + } + + if(!error) + { + /*calculate the lenghts of each symbol, as the amount of times a coin of each symbol is used*/ + for(i = 0; i < numpresent - 1; i++) + { + Coin* coin = &coins[i]; + for(j = 0; j < coin->symbols.size; j++) lengths[coin->symbols.data[j]]++; + } + } + + cleanup_coins(coins, coinmem); + lodepng_free(coins); + cleanup_coins(prev_row, coinmem); + lodepng_free(prev_row); + } + + return error; +} + +/*Create the Huffman tree given the symbol frequencies*/ +static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, + size_t mincodes, size_t numcodes, unsigned maxbitlen) +{ + unsigned error = 0; + while(!frequencies[numcodes - 1] && numcodes > mincodes) numcodes--; /*trim zeroes*/ + tree->maxbitlen = maxbitlen; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->lengths = (unsigned*)lodepng_realloc(tree->lengths, numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + /*initialize all lengths to 0*/ + memset(tree->lengths, 0, numcodes * sizeof(unsigned)); + + error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen); + if(!error) error = HuffmanTree_makeFromLengths2(tree); + return error; +} + +static unsigned HuffmanTree_getCode(const HuffmanTree* tree, unsigned index) +{ + return tree->tree1d[index]; +} + +static unsigned HuffmanTree_getLength(const HuffmanTree* tree, unsigned index) +{ + return tree->lengths[index]; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) +{ + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for(i = 0; i <= 143; i++) bitlen[i] = 8; + for(i = 144; i <= 255; i++) bitlen[i] = 9; + for(i = 256; i <= 279; i++) bitlen[i] = 7; + for(i = 280; i <= 287; i++) bitlen[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) +{ + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*there are 32 distance codes, but 30-31 are unused*/ + for(i = 0; i < NUM_DISTANCE_SYMBOLS; i++) bitlen[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* +returns the code, or (unsigned)(-1) if error happened +inbitlength is the length of the complete buffer, in bits (so its byte length times 8) +*/ +static unsigned huffmanDecodeSymbol(const unsigned char* in, size_t* bp, + const HuffmanTree* codetree, size_t inbitlength) +{ + unsigned treepos = 0, ct; + for(;;) + { + if(*bp >= inbitlength) return (unsigned)(-1); /*error: end of input memory reached without endcode*/ + /* + decode the symbol from the tree. The "readBitFromStream" code is inlined in + the expression below because this is the biggest bottleneck while decoding + */ + ct = codetree->tree2d[(treepos << 1) + READBIT(*bp, in)]; + (*bp)++; + if(ct < codetree->numcodes) return ct; /*the symbol is decoded, return it*/ + else treepos = ct - codetree->numcodes; /*symbol not yet decoded, instead move tree position*/ + + if(treepos >= codetree->numcodes) return (unsigned)(-1); /*error: it appeared outside the codetree*/ + } +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator (Decompressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static void getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) +{ + /*TODO: check for out of memory errors*/ + generateFixedLitLenTree(tree_ll); + generateFixedDistanceTree(tree_d); +} + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, + const unsigned char* in, size_t* bp, size_t inlength) +{ + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + size_t inbitlength = inlength * 8; + + /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ + unsigned* bitlen_ll = 0; /*lit,len code lengths*/ + unsigned* bitlen_d = 0; /*dist code lengths*/ + /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ + unsigned* bitlen_cl = 0; + HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ + + if((*bp) >> 3 >= inlength - 2) return 49; /*error: the bit pointer is or will go past the memory*/ + + /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HLIT = readBitsFromStream(bp, in, 5) + 257; + /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HDIST = readBitsFromStream(bp, in, 5) + 1; + /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + HCLEN = readBitsFromStream(bp, in, 4) + 4; + + HuffmanTree_init(&tree_cl); + + while(!error) + { + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + + bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); + if(!bitlen_cl) ERROR_BREAK(83 /*alloc fail*/); + + for(i = 0; i < NUM_CODE_LENGTH_CODES; i++) + { + if(i < HCLEN) bitlen_cl[CLCL_ORDER[i]] = readBitsFromStream(bp, in, 3); + else bitlen_cl[CLCL_ORDER[i]] = 0; /*if not, it must stay 0*/ + } + + error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); + for(i = 0; i < NUM_DEFLATE_CODE_SYMBOLS; i++) bitlen_ll[i] = 0; + for(i = 0; i < NUM_DISTANCE_SYMBOLS; i++) bitlen_d[i] = 0; + + /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ + i = 0; + while(i < HLIT + HDIST) + { + unsigned code = huffmanDecodeSymbol(in, bp, &tree_cl, inbitlength); + if(code <= 15) /*a length code*/ + { + if(i < HLIT) bitlen_ll[i] = code; + else bitlen_d[i - HLIT] = code; + i++; + } + else if(code == 16) /*repeat previous*/ + { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if(*bp >= inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + if (i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ + + replength += readBitsFromStream(bp, in, 2); + + if(i < HLIT + 1) value = bitlen_ll[i - 1]; + else value = bitlen_d[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; n++) + { + if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen_ll[i] = value; + else bitlen_d[i - HLIT] = value; + i++; + } + } + else if(code == 17) /*repeat "0" 3-10 times*/ + { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + if(*bp >= inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + + replength += readBitsFromStream(bp, in, 3); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; n++) + { + if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + i++; + } + } + else if(code == 18) /*repeat "0" 11-138 times*/ + { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + if(*bp >= inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + + replength += readBitsFromStream(bp, in, 7); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; n++) + { + if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + i++; + } + } + else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + if(code == (unsigned)(-1)) + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = (*bp) > inbitlength ? 10 : 11; + } + else error = 16; /*unexisting code, this can never happen*/ + break; + } + } + if(error) break; + + if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); + if(error) break; + error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); + + break; /*end of error-while*/ + } + + lodepng_free(bitlen_cl); + lodepng_free(bitlen_ll); + lodepng_free(bitlen_d); + HuffmanTree_cleanup(&tree_cl); + + return error; +} + +/*inflate a block with dynamic of fixed Huffman tree*/ +static unsigned inflateHuffmanBlock(ucvector* out, const unsigned char* in, size_t* bp, + size_t* pos, size_t inlength, unsigned btype) +{ + unsigned error = 0; + HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ + HuffmanTree tree_d; /*the huffman tree for distance codes*/ + size_t inbitlength = inlength * 8; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + if(btype == 1) getTreeInflateFixed(&tree_ll, &tree_d); + else if(btype == 2) error = getTreeInflateDynamic(&tree_ll, &tree_d, in, bp, inlength); + + while(!error) /*decode all symbols until end reached, breaks at end code*/ + { + /*code_ll is literal, length or end code*/ + unsigned code_ll = huffmanDecodeSymbol(in, bp, &tree_ll, inbitlength); + if(code_ll <= 255) /*literal symbol*/ + { + if((*pos) >= out->size) + { + /*reserve more room at once*/ + if(!ucvector_resize(out, ((*pos) + 1) * 2)) ERROR_BREAK(83 /*alloc fail*/); + } + out->data[(*pos)] = (unsigned char)(code_ll); + (*pos)++; + } + else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ + { + unsigned code_d, distance; + unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ + size_t start, forward, backward, length; + + /*part 1: get length base*/ + length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; + if(*bp >= inbitlength) ERROR_BREAK(51); /*error, bit pointer will jump past memory*/ + length += readBitsFromStream(bp, in, numextrabits_l); + + /*part 3: get distance code*/ + code_d = huffmanDecodeSymbol(in, bp, &tree_d, inbitlength); + if(code_d > 29) + { + if(code_ll == (unsigned)(-1)) /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = (*bp) > inlength * 8 ? 10 : 11; + } + else error = 18; /*error: invalid distance code (30-31 are never used)*/ + break; + } + distance = DISTANCEBASE[code_d]; + + /*part 4: get extra bits from distance*/ + numextrabits_d = DISTANCEEXTRA[code_d]; + if(*bp >= inbitlength) ERROR_BREAK(51); /*error, bit pointer will jump past memory*/ + + distance += readBitsFromStream(bp, in, numextrabits_d); + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = (*pos); + if(distance > start) ERROR_BREAK(52); /*too long backward distance*/ + backward = start - distance; + if((*pos) + length >= out->size) + { + /*reserve more room at once*/ + if(!ucvector_resize(out, ((*pos) + length) * 2)) ERROR_BREAK(83 /*alloc fail*/); + } + + for(forward = 0; forward < length; forward++) + { + out->data[(*pos)] = out->data[backward]; + (*pos)++; + backward++; + if(backward >= start) backward = start - distance; + } + } + else if(code_ll == 256) + { + break; /*end code, break the loop*/ + } + else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ + { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + error = (*bp) > inlength * 8 ? 10 : 11; + break; + } + } + + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned inflateNoCompression(ucvector* out, const unsigned char* in, size_t* bp, size_t* pos, size_t inlength) +{ + /*go to first boundary of byte*/ + size_t p; + unsigned LEN, NLEN, n, error = 0; + while(((*bp) & 0x7) != 0) (*bp)++; + p = (*bp) / 8; /*byte position*/ + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if(p >= inlength - 4) return 52; /*error, bit pointer will jump past memory*/ + LEN = in[p] + 256 * in[p + 1]; p += 2; + NLEN = in[p] + 256 * in[p + 1]; p += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if(LEN + NLEN != 65535) return 21; /*error: NLEN is not one's complement of LEN*/ + + if((*pos) + LEN >= out->size) + { + if(!ucvector_resize(out, (*pos) + LEN)) return 83; /*alloc fail*/ + } + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if(p + LEN > inlength) return 23; /*error: reading outside of in buffer*/ + for(n = 0; n < LEN; n++) out->data[(*pos)++] = in[p++]; + + (*bp) = p * 8; + + return error; +} + +static unsigned lodepng_inflatev(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) +{ + /*bit pointer in the "in" data, current byte is bp >> 3, current bit is bp & 0x7 (from lsb to msb of the byte)*/ + size_t bp = 0; + unsigned BFINAL = 0; + size_t pos = 0; /*byte position in the out buffer*/ + + unsigned error = 0; + + (void)settings; + + while(!BFINAL) + { + unsigned BTYPE; + if(bp + 2 >= insize * 8) return 52; /*error, bit pointer will jump past memory*/ + BFINAL = readBitFromStream(&bp, in); + BTYPE = 1 * readBitFromStream(&bp, in); + BTYPE += 2 * readBitFromStream(&bp, in); + + if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if(BTYPE == 0) error = inflateNoCompression(out, in, &bp, &pos, insize); /*no compression*/ + else error = inflateHuffmanBlock(out, in, &bp, &pos, insize, BTYPE); /*compression, BTYPE 01 or 10*/ + + if(error) return error; + } + + /*Only now we know the true size of out, resize it to that*/ + if(!ucvector_resize(out, pos)) error = 83; /*alloc fail*/ + + return error; +} + +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) +{ + unsigned error; + ucvector v; + ucvector_init_buffer(&v, *out, *outsize); + error = lodepng_inflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) +{ + if(settings->custom_inflate) + { + return settings->custom_inflate(out, outsize, in, insize, settings); + } + else + { + return lodepng_inflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflator (Compressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; + +/*bitlen is the size in bits of the code*/ +static void addHuffmanSymbol(size_t* bp, ucvector* compressed, unsigned code, unsigned bitlen) +{ + addBitsToStreamReversed(bp, compressed, code, bitlen); +} + +/*search the index in the array, that has the largest value smaller than or equal to the given value, +given array must be sorted (if no value is smaller, it returns the size of the given array)*/ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) +{ + /*linear search implementation*/ + /*for(size_t i = 1; i < array_size; i++) if(array[i] > value) return i - 1; + return array_size - 1;*/ + + /*binary search implementation (not that much faster) (precondition: array_size > 0)*/ + size_t left = 1; + size_t right = array_size - 1; + while(left <= right) + { + size_t mid = (left + right) / 2; + if(array[mid] <= value) left = mid + 1; /*the value to find is more to the right*/ + else if(array[mid - 1] > value) right = mid - 1; /*the value to find is more to the left*/ + else return mid - 1; + } + return array_size - 1; +} + +static void addLengthDistance(uivector* values, size_t length, size_t distance) +{ + /*values in encoded vector are those used by deflate: + 0-255: literal bytes + 256: end + 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) + 286-287: invalid*/ + + unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); + unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); + unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); + unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); + + uivector_push_back(values, length_code + FIRST_LENGTH_CODE_INDEX); + uivector_push_back(values, extra_length); + uivector_push_back(values, dist_code); + uivector_push_back(values, extra_distance); +} + +static const unsigned HASH_NUM_VALUES = 65536; +static const unsigned HASH_NUM_CHARACTERS = 3; +static const unsigned HASH_SHIFT = 2; +/* +The HASH_NUM_CHARACTERS value is used to make encoding faster by using longer +sequences to generate a hash value from the stream bytes. Setting it to 3 +gives exactly the same compression as the brute force method, since deflate's +run length encoding starts with lengths of 3. Setting it to higher values, +like 6, can make the encoding faster (not always though!), but will cause the +encoding to miss any length between 3 and this value, so that the compression +may be worse (but this can vary too depending on the image, sometimes it is +even a bit better instead). +The HASH_NUM_VALUES is the amount of unique possible hash values that +combinations of bytes can give, the higher it is the more memory is needed, but +if it's too low the advantage of hashing is gone. +*/ + +typedef struct Hash +{ + int* head; /*hash value to head circular pos*/ + int* val; /*circular pos to hash value*/ + /*circular pos to prev circular pos*/ + unsigned short* chain; + unsigned short* zeros; +} Hash; + +static unsigned hash_init(Hash* hash, unsigned windowsize) +{ + unsigned i; + hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); + hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); + hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + if(!hash->head || !hash->val || !hash->chain || !hash->zeros) return 83; /*alloc fail*/ + + /*initialize hash table*/ + for(i = 0; i < HASH_NUM_VALUES; i++) hash->head[i] = -1; + for(i = 0; i < windowsize; i++) hash->val[i] = -1; + for(i = 0; i < windowsize; i++) hash->chain[i] = i; /*same value as index indicates uninitialized*/ + + return 0; +} + +static void hash_cleanup(Hash* hash) +{ + lodepng_free(hash->head); + lodepng_free(hash->val); + lodepng_free(hash->chain); + lodepng_free(hash->zeros); +} + +static unsigned getHash(const unsigned char* data, size_t size, size_t pos) +{ + unsigned result = 0; + size_t amount, i; + if(pos >= size) return 0; + amount = HASH_NUM_CHARACTERS; + if(pos + amount >= size) amount = size - pos; + for(i = 0; i < amount; i++) result ^= (data[pos + i] << (i * HASH_SHIFT)); + return result % HASH_NUM_VALUES; +} + +static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) +{ + const unsigned char* start = data + pos; + const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; + if(end > data + size) end = data + size; + data = start; + while (data != end && *data == 0) data++; + /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/ + return (unsigned)(data - start); +} + +static void updateHashChain(Hash* hash, size_t pos, int hashval, unsigned windowsize) +{ + unsigned wpos = pos % windowsize; + hash->val[wpos] = hashval; + if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; + hash->head[hashval] = wpos; +} + +/* +LZ77-encode the data. Return value is error code. The input are raw bytes, the output +is in the form of unsigned integers with codes representing for example literal bytes, or +length/distance pairs. +It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a +sliding window (of windowsize) is used, and all past bytes in that window can be used as +the "dictionary". A brute force search through all possible distances would be slow, and +this hash technique is one out of several ways to speed this up. +*/ +static unsigned encodeLZ77(uivector* out, Hash* hash, + const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, + unsigned minmatch, unsigned nicematch, unsigned lazymatching) +{ + unsigned short numzeros = 0; + int usezeros = windowsize >= 8192; /*for small window size, the 'max chain length' optimization does a better job*/ + unsigned pos, i, error = 0; + /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ + unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8; + unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64; + + if(!error) + { + unsigned offset; /*the offset represents the distance in LZ77 terminology*/ + unsigned length; + unsigned lazy = 0; + unsigned lazylength = 0, lazyoffset = 0; + unsigned hashval; + unsigned current_offset, current_length; + const unsigned char *lastptr, *foreptr, *backptr; + unsigned short hashpos, prevpos; + + for(pos = inpos; pos < insize; pos++) + { + size_t wpos = pos % windowsize; /*position for in 'circular' hash buffers*/ + + hashval = getHash(in, insize, pos); + updateHashChain(hash, pos, hashval, windowsize); + + if(usezeros && hashval == 0) + { + numzeros = countZeros(in, insize, pos); + hash->zeros[wpos] = numzeros; + } + + /*the length and offset found for the current position*/ + length = 0; + offset = 0; + + prevpos = hash->head[hashval]; + hashpos = hash->chain[prevpos]; + + lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; + + /*search for the longest string*/ + if(hash->val[wpos] == (int)hashval) + { + unsigned chainlength = 0; + for(;;) + { + /*stop when went completely around the circular buffer*/ + if(prevpos < wpos && hashpos > prevpos && hashpos <= wpos) break; + if(prevpos > wpos && (hashpos <= wpos || hashpos > prevpos)) break; + if(chainlength++ >= maxchainlength) break; + + current_offset = hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize; + if(current_offset > 0) + { + /*test the next characters*/ + foreptr = &in[pos]; + backptr = &in[pos - current_offset]; + + /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ + if(usezeros && hashval == 0 && hash->val[hashpos] == 0 /*hashval[hashpos] may be out of date*/) + { + unsigned short skip = hash->zeros[hashpos]; + if(skip > numzeros) skip = numzeros; + backptr += skip; + foreptr += skip; + } + + /* multiple checks at once per array bounds check */ + while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ + { + ++backptr; + ++foreptr; + } + current_length = (unsigned)(foreptr - &in[pos]); + + if(current_length > length) + { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + /*jump out once a length of max length is found (speed gain)*/ + if(current_length >= nicematch || current_length == MAX_SUPPORTED_DEFLATE_LENGTH) break; + } + } + + if(hashpos == hash->chain[hashpos]) break; + + prevpos = hashpos; + hashpos = hash->chain[hashpos]; + } + } + + if(lazymatching) + { + if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) + { + lazy = 1; + lazylength = length; + lazyoffset = offset; + continue; /*try the next byte*/ + } + if(lazy) + { + lazy = 0; + if(pos == 0) ERROR_BREAK(81); + if(length > lazylength + 1) + { + /*push the previous character as literal*/ + if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); + } + else + { + length = lazylength; + offset = lazyoffset; + hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ + pos--; + } + } + } + if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); + + /**encode it as length/distance pair or literal value**/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ + { + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } + else if(length < minmatch || (length == 3 && offset > 4096)) + { + /*compensate for the fact that longer offsets have more extra bits, a + length of only 3 may be not worth it then*/ + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } + else + { + addLengthDistance(out, length, offset); + for(i = 1; i < length; i++) + { + pos++; + hashval = getHash(in, insize, pos); + updateHashChain(hash, pos, hashval, windowsize); + if(usezeros && hashval == 0) + { + hash->zeros[pos % windowsize] = countZeros(in, insize, pos); + } + } + } + + } /*end of the loop through each character of input*/ + } /*end of "if(!error)"*/ + + return error; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) +{ + /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, + 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ + + size_t i, j, numdeflateblocks = (datasize + 65534) / 65535; + unsigned datapos = 0; + for(i = 0; i < numdeflateblocks; i++) + { + unsigned BFINAL, BTYPE, LEN, NLEN; + unsigned char firstbyte; + + BFINAL = (i == numdeflateblocks - 1); + BTYPE = 0; + + firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1) << 1) + ((BTYPE & 2) << 1)); + ucvector_push_back(out, firstbyte); + + LEN = 65535; + if(datasize - datapos < 65535) LEN = (unsigned)datasize - datapos; + NLEN = 65535 - LEN; + + ucvector_push_back(out, (unsigned char)(LEN % 256)); + ucvector_push_back(out, (unsigned char)(LEN / 256)); + ucvector_push_back(out, (unsigned char)(NLEN % 256)); + ucvector_push_back(out, (unsigned char)(NLEN / 256)); + + /*Decompressed data*/ + for(j = 0; j < 65535 && datapos < datasize; j++) + { + ucvector_push_back(out, data[datapos++]); + } + } + + return 0; +} + +/* +write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. +tree_ll: the tree for lit and len codes. +tree_d: the tree for distance codes. +*/ +static void writeLZ77data(size_t* bp, ucvector* out, const uivector* lz77_encoded, + const HuffmanTree* tree_ll, const HuffmanTree* tree_d) +{ + size_t i = 0; + for(i = 0; i < lz77_encoded->size; i++) + { + unsigned val = lz77_encoded->data[i]; + addHuffmanSymbol(bp, out, HuffmanTree_getCode(tree_ll, val), HuffmanTree_getLength(tree_ll, val)); + if(val > 256) /*for a length code, 3 more things have to be added*/ + { + unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; + unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; + unsigned length_extra_bits = lz77_encoded->data[++i]; + + unsigned distance_code = lz77_encoded->data[++i]; + + unsigned distance_index = distance_code; + unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; + unsigned distance_extra_bits = lz77_encoded->data[++i]; + + addBitsToStream(bp, out, length_extra_bits, n_length_extra_bits); + addHuffmanSymbol(bp, out, HuffmanTree_getCode(tree_d, distance_code), + HuffmanTree_getLength(tree_d, distance_code)); + addBitsToStream(bp, out, distance_extra_bits, n_distance_extra_bits); + } + } +} + +/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ +static unsigned deflateDynamic(ucvector* out, size_t* bp, Hash* hash, + const unsigned char* data, size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, int final) +{ + unsigned error = 0; + + /* + A block is compressed as follows: The PNG data is lz77 encoded, resulting in + literal bytes and length/distance pairs. This is then huffman compressed with + two huffman trees. One huffman tree is used for the lit and len values ("ll"), + another huffman tree is used for the dist values ("d"). These two trees are + stored using their code lengths, and to compress even more these code lengths + are also run-length encoded and huffman compressed. This gives a huffman tree + of code lengths "cl". The code lenghts used to describe this third tree are + the code length code lengths ("clcl"). + */ + + /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ + uivector lz77_encoded; + HuffmanTree tree_ll; /*tree for lit,len values*/ + HuffmanTree tree_d; /*tree for distance codes*/ + HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ + uivector frequencies_ll; /*frequency of lit,len codes*/ + uivector frequencies_d; /*frequency of dist codes*/ + uivector frequencies_cl; /*frequency of code length codes*/ + uivector bitlen_lld; /*lit,len,dist code lenghts (int bits), literally (without repeat codes).*/ + uivector bitlen_lld_e; /*bitlen_lld encoded with repeat codes (this is a rudemtary run length compression)*/ + /*bitlen_cl is the code length code lengths ("clcl"). The bit lengths of codes to represent tree_cl + (these are written as is in the file, it would be crazy to compress these using yet another huffman + tree that needs to be represented by yet another set of code lengths)*/ + uivector bitlen_cl; + size_t datasize = dataend - datapos; + + /* + Due to the huffman compression of huffman tree representations ("two levels"), there are some anologies: + bitlen_lld is to tree_cl what data is to tree_ll and tree_d. + bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. + bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. + */ + + unsigned BFINAL = final; + size_t numcodes_ll, numcodes_d, i; + unsigned HLIT, HDIST, HCLEN; + + uivector_init(&lz77_encoded); + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + HuffmanTree_init(&tree_cl); + uivector_init(&frequencies_ll); + uivector_init(&frequencies_d); + uivector_init(&frequencies_cl); + uivector_init(&bitlen_lld); + uivector_init(&bitlen_lld_e); + uivector_init(&bitlen_cl); + + /*This while loop never loops due to a break at the end, it is here to + allow breaking out of it to the cleanup phase on error conditions.*/ + while(!error) + { + if(settings->use_lz77) + { + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(error) break; + } + else + { + if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); + for(i = datapos; i < dataend; i++) lz77_encoded.data[i] = data[i]; /*no LZ77, but still will be Huffman compressed*/ + } + + if(!uivector_resizev(&frequencies_ll, 286, 0)) ERROR_BREAK(83 /*alloc fail*/); + if(!uivector_resizev(&frequencies_d, 30, 0)) ERROR_BREAK(83 /*alloc fail*/); + + /*Count the frequencies of lit, len and dist codes*/ + for(i = 0; i < lz77_encoded.size; i++) + { + unsigned symbol = lz77_encoded.data[i]; + frequencies_ll.data[symbol]++; + if(symbol > 256) + { + unsigned dist = lz77_encoded.data[i + 2]; + frequencies_d.data[dist]++; + i += 3; + } + } + frequencies_ll.data[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ + + /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ + error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll.data, 257, frequencies_ll.size, 15); + if(error) break; + /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/ + error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d.data, 2, frequencies_d.size, 15); + if(error) break; + + numcodes_ll = tree_ll.numcodes; if(numcodes_ll > 286) numcodes_ll = 286; + numcodes_d = tree_d.numcodes; if(numcodes_d > 30) numcodes_d = 30; + /*store the code lengths of both generated trees in bitlen_lld*/ + for(i = 0; i < numcodes_ll; i++) uivector_push_back(&bitlen_lld, HuffmanTree_getLength(&tree_ll, (unsigned)i)); + for(i = 0; i < numcodes_d; i++) uivector_push_back(&bitlen_lld, HuffmanTree_getLength(&tree_d, (unsigned)i)); + + /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), + 17 (3-10 zeroes), 18 (11-138 zeroes)*/ + for(i = 0; i < (unsigned)bitlen_lld.size; i++) + { + unsigned j = 0; /*amount of repititions*/ + while(i + j + 1 < (unsigned)bitlen_lld.size && bitlen_lld.data[i + j + 1] == bitlen_lld.data[i]) j++; + + if(bitlen_lld.data[i] == 0 && j >= 2) /*repeat code for zeroes*/ + { + j++; /*include the first zero*/ + if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ + { + uivector_push_back(&bitlen_lld_e, 17); + uivector_push_back(&bitlen_lld_e, j - 3); + } + else /*repeat code 18 supports max 138 zeroes*/ + { + if(j > 138) j = 138; + uivector_push_back(&bitlen_lld_e, 18); + uivector_push_back(&bitlen_lld_e, j - 11); + } + i += (j - 1); + } + else if(j >= 3) /*repeat code for value other than zero*/ + { + size_t k; + unsigned num = j / 6, rest = j % 6; + uivector_push_back(&bitlen_lld_e, bitlen_lld.data[i]); + for(k = 0; k < num; k++) + { + uivector_push_back(&bitlen_lld_e, 16); + uivector_push_back(&bitlen_lld_e, 6 - 3); + } + if(rest >= 3) + { + uivector_push_back(&bitlen_lld_e, 16); + uivector_push_back(&bitlen_lld_e, rest - 3); + } + else j -= rest; + i += j; + } + else /*too short to benefit from repeat code*/ + { + uivector_push_back(&bitlen_lld_e, bitlen_lld.data[i]); + } + } + + /*generate tree_cl, the huffmantree of huffmantrees*/ + + if(!uivector_resizev(&frequencies_cl, NUM_CODE_LENGTH_CODES, 0)) ERROR_BREAK(83 /*alloc fail*/); + for(i = 0; i < bitlen_lld_e.size; i++) + { + frequencies_cl.data[bitlen_lld_e.data[i]]++; + /*after a repeat code come the bits that specify the number of repetitions, + those don't need to be in the frequencies_cl calculation*/ + if(bitlen_lld_e.data[i] >= 16) i++; + } + + error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl.data, + frequencies_cl.size, frequencies_cl.size, 7); + if(error) break; + + if(!uivector_resize(&bitlen_cl, tree_cl.numcodes)) ERROR_BREAK(83 /*alloc fail*/); + for(i = 0; i < tree_cl.numcodes; i++) + { + /*lenghts of code length tree is in the order as specified by deflate*/ + bitlen_cl.data[i] = HuffmanTree_getLength(&tree_cl, CLCL_ORDER[i]); + } + while(bitlen_cl.data[bitlen_cl.size - 1] == 0 && bitlen_cl.size > 4) + { + /*remove zeros at the end, but minimum size must be 4*/ + if(!uivector_resize(&bitlen_cl, bitlen_cl.size - 1)) ERROR_BREAK(83 /*alloc fail*/); + } + if(error) break; + + /* + Write everything into the output + + After the BFINAL and BTYPE, the dynamic block consists out of the following: + - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN + - (HCLEN+4)*3 bits code lengths of code length alphabet + - HLIT + 257 code lenghts of lit/length alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - HDIST + 1 code lengths of distance alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - compressed data + - 256 (end code) + */ + + /*Write block type*/ + addBitToStream(bp, out, BFINAL); + addBitToStream(bp, out, 0); /*first bit of BTYPE "dynamic"*/ + addBitToStream(bp, out, 1); /*second bit of BTYPE "dynamic"*/ + + /*write the HLIT, HDIST and HCLEN values*/ + HLIT = (unsigned)(numcodes_ll - 257); + HDIST = (unsigned)(numcodes_d - 1); + HCLEN = (unsigned)bitlen_cl.size - 4; + /*trim zeroes for HCLEN. HLIT and HDIST were already trimmed at tree creation*/ + while(!bitlen_cl.data[HCLEN + 4 - 1] && HCLEN > 0) HCLEN--; + addBitsToStream(bp, out, HLIT, 5); + addBitsToStream(bp, out, HDIST, 5); + addBitsToStream(bp, out, HCLEN, 4); + + /*write the code lenghts of the code length alphabet*/ + for(i = 0; i < HCLEN + 4; i++) addBitsToStream(bp, out, bitlen_cl.data[i], 3); + + /*write the lenghts of the lit/len AND the dist alphabet*/ + for(i = 0; i < bitlen_lld_e.size; i++) + { + addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_cl, bitlen_lld_e.data[i]), + HuffmanTree_getLength(&tree_cl, bitlen_lld_e.data[i])); + /*extra bits of repeat codes*/ + if(bitlen_lld_e.data[i] == 16) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 2); + else if(bitlen_lld_e.data[i] == 17) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 3); + else if(bitlen_lld_e.data[i] == 18) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 7); + } + + /*write the compressed data symbols*/ + writeLZ77data(bp, out, &lz77_encoded, &tree_ll, &tree_d); + /*error: the length of the end code 256 must be larger than 0*/ + if(HuffmanTree_getLength(&tree_ll, 256) == 0) ERROR_BREAK(64); + + /*write the end code*/ + addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, 256), HuffmanTree_getLength(&tree_ll, 256)); + + break; /*end of error-while*/ + } + + /*cleanup*/ + uivector_cleanup(&lz77_encoded); + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + HuffmanTree_cleanup(&tree_cl); + uivector_cleanup(&frequencies_ll); + uivector_cleanup(&frequencies_d); + uivector_cleanup(&frequencies_cl); + uivector_cleanup(&bitlen_lld_e); + uivector_cleanup(&bitlen_lld); + uivector_cleanup(&bitlen_cl); + + return error; +} + +static unsigned deflateFixed(ucvector* out, size_t* bp, Hash* hash, + const unsigned char* data, + size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, int final) +{ + HuffmanTree tree_ll; /*tree for literal values and length codes*/ + HuffmanTree tree_d; /*tree for distance codes*/ + + unsigned BFINAL = final; + unsigned error = 0; + size_t i; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + generateFixedLitLenTree(&tree_ll); + generateFixedDistanceTree(&tree_d); + + addBitToStream(bp, out, BFINAL); + addBitToStream(bp, out, 1); /*first bit of BTYPE*/ + addBitToStream(bp, out, 0); /*second bit of BTYPE*/ + + if(settings->use_lz77) /*LZ77 encoded*/ + { + uivector lz77_encoded; + uivector_init(&lz77_encoded); + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(!error) writeLZ77data(bp, out, &lz77_encoded, &tree_ll, &tree_d); + uivector_cleanup(&lz77_encoded); + } + else /*no LZ77, but still will be Huffman compressed*/ + { + for(i = datapos; i < dataend; i++) + { + addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, data[i]), HuffmanTree_getLength(&tree_ll, data[i])); + } + } + /*add END code*/ + if(!error) addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, 256), HuffmanTree_getLength(&tree_ll, 256)); + + /*cleanup*/ + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) +{ + unsigned error = 0; + size_t i, blocksize, numdeflateblocks; + size_t bp = 0; /*the bit pointer*/ + Hash hash; + + if(settings->btype > 2) return 61; + else if(settings->btype == 0) return deflateNoCompression(out, in, insize); + else if(settings->btype == 1) blocksize = insize; + else /*if(settings->btype == 2)*/ + { + blocksize = insize / 8 + 8; + if(blocksize < 65535) blocksize = 65535; + } + + numdeflateblocks = (insize + blocksize - 1) / blocksize; + if(numdeflateblocks == 0) numdeflateblocks = 1; + + error = hash_init(&hash, settings->windowsize); + if(error) return error; + + for(i = 0; i < numdeflateblocks && !error; i++) + { + int final = i == numdeflateblocks - 1; + size_t start = i * blocksize; + size_t end = start + blocksize; + if(end > insize) end = insize; + + if(settings->btype == 1) error = deflateFixed(out, &bp, &hash, in, start, end, settings, final); + else if(settings->btype == 2) error = deflateDynamic(out, &bp, &hash, in, start, end, settings, final); + } + + hash_cleanup(&hash); + + return error; +} + +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) +{ + unsigned error; + ucvector v; + ucvector_init_buffer(&v, *out, *outsize); + error = lodepng_deflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) +{ + if(settings->custom_deflate) + { + return settings->custom_deflate(out, outsize, in, insize, settings); + } + else + { + return lodepng_deflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) +{ + unsigned s1 = adler & 0xffff; + unsigned s2 = (adler >> 16) & 0xffff; + + while(len > 0) + { + /*at least 5550 sums can be done before the sums overflow, saving a lot of module divisions*/ + unsigned amount = len > 5550 ? 5550 : len; + len -= amount; + while(amount > 0) + { + s1 += (*data++); + s2 += s1; + amount--; + } + s1 %= 65521; + s2 %= 65521; + } + + return (s2 << 16) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) +{ + return update_adler32(1L, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) +{ + unsigned error = 0; + unsigned CM, CINFO, FDICT; + + if(insize < 2) return 53; /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if((in[0] * 256 + in[1]) % 31 != 0) + { + /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + return 24; + } + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ + + if(CM != 8 || CINFO > 7) + { + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + return 25; + } + if(FDICT != 0) + { + /*error: the specification of PNG says about the zlib stream: + "The additional flags shall not specify a preset dictionary."*/ + return 26; + } + + error = inflate(out, outsize, in + 2, insize - 2, settings); + if(error) return error; + + if(!settings->ignore_adler32) + { + unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(*out, (unsigned)(*outsize)); + if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ + } + + return 0; /*no error*/ +} + +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) +{ + if(settings->custom_zlib) + return settings->custom_zlib(out, outsize, in, insize, settings); + else + return lodepng_zlib_decompress(out, outsize, in, insize, settings); +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) +{ + /*initially, *out must be NULL and outsize 0, if you just give some random *out + that's pointing to a non allocated buffer, this'll crash*/ + ucvector outv; + size_t i; + unsigned error; + unsigned char* deflatedata = 0; + size_t deflatesize = 0; + + unsigned ADLER32; + /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ + unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ + unsigned FLEVEL = 0; + unsigned FDICT = 0; + unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; + unsigned FCHECK = 31 - CMFFLG % 31; + CMFFLG += FCHECK; + + /*ucvector-controlled version of the output buffer, for dynamic array*/ + ucvector_init_buffer(&outv, *out, *outsize); + + ucvector_push_back(&outv, (unsigned char)(CMFFLG / 256)); + ucvector_push_back(&outv, (unsigned char)(CMFFLG % 256)); + + error = deflate(&deflatedata, &deflatesize, in, insize, settings); + + if(!error) + { + ADLER32 = adler32(in, (unsigned)insize); + for(i = 0; i < deflatesize; i++) ucvector_push_back(&outv, deflatedata[i]); + lodepng_free(deflatedata); + lodepng_add32bitInt(&outv, ADLER32); + } + + *out = outv.data; + *outsize = outv.size; + + return error; +} + +/* compress using the default or custom zlib function */ +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) +{ + if(settings->custom_zlib) + { + return settings->custom_zlib(out, outsize, in, insize, settings); + } + else + { + return lodepng_zlib_compress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#else /*no LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) +{ + if (!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) +{ + if (!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/*this is a good tradeoff between speed and compression ratio*/ +#define DEFAULT_WINDOWSIZE 2048 + +void lodepng_compress_settings_init(LodePNGCompressSettings* settings) +{ + /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ + settings->btype = 2; + settings->use_lz77 = 1; + settings->windowsize = DEFAULT_WINDOWSIZE; + settings->minmatch = 3; + settings->nicematch = 128; + settings->lazymatching = 1; + + settings->custom_zlib = 0; + settings->custom_deflate = 0; + settings->custom_context = 0; +} + +const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0}; + + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) +{ + settings->ignore_adler32 = 0; + + settings->custom_zlib = 0; + settings->custom_inflate = 0; + settings->custom_context = 0; +} + +const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0}; + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code. Begin of PNG related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / CRC32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned Crc32_crc_table_computed = 0; +static unsigned Crc32_crc_table[256]; + +/*Make the table for a fast CRC.*/ +static void Crc32_make_crc_table(void) +{ + unsigned c, k, n; + for(n = 0; n < 256; n++) + { + c = n; + for(k = 0; k < 8; k++) + { + if(c & 1) c = 0xedb88320L ^ (c >> 1); + else c = c >> 1; + } + Crc32_crc_table[n] = c; + } + Crc32_crc_table_computed = 1; +} + +/*Update a running CRC with the bytes buf[0..len-1]--the CRC should be +initialized to all 1's, and the transmitted value is the 1's complement of the +final running CRC (see the crc() routine below).*/ +static unsigned Crc32_update_crc(const unsigned char* buf, unsigned crc, size_t len) +{ + unsigned c = crc; + size_t n; + + if(!Crc32_crc_table_computed) Crc32_make_crc_table(); + for(n = 0; n < len; n++) + { + c = Crc32_crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8); + } + return c; +} + +/*Return the CRC of the bytes buf[0..len-1].*/ +unsigned lodepng_crc32(const unsigned char* buf, size_t len) +{ + return Crc32_update_crc(buf, 0xffffffffL, len) ^ 0xffffffffL; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing single bits and bytes from/to stream for LodePNG / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) +{ + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); + (*bitpointer)++; + return result; +} + +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) +{ + unsigned result = 0; + size_t i; + for(i = nbits - 1; i < nbits; i--) + { + result += (unsigned)readBitFromReversedStream(bitpointer, bitstream) << i; + } + return result; +} + +#ifdef LODEPNG_COMPILE_DECODER +static void setBitOfReversedStream0(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) +{ + /*the current bit in bitstream must be 0 for this to work*/ + if(bit) + { + /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/ + bitstream[(*bitpointer) >> 3] |= (bit << (7 - ((*bitpointer) & 0x7))); + } + (*bitpointer)++; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) +{ + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if(bit == 0) bitstream[(*bitpointer) >> 3] &= (unsigned char)(~(1 << (7 - ((*bitpointer) & 0x7)))); + else bitstream[(*bitpointer) >> 3] |= (1 << (7 - ((*bitpointer) & 0x7))); + (*bitpointer)++; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_chunk_length(const unsigned char* chunk) +{ + return lodepng_read32bitInt(&chunk[0]); +} + +void lodepng_chunk_type(char type[5], const unsigned char* chunk) +{ + unsigned i; + for(i = 0; i < 4; i++) type[i] = chunk[4 + i]; + type[4] = 0; /*null termination char*/ +} + +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) +{ + if(strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) +{ + return((chunk[4] & 32) != 0); +} + +unsigned char lodepng_chunk_private(const unsigned char* chunk) +{ + return((chunk[6] & 32) != 0); +} + +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) +{ + return((chunk[7] & 32) != 0); +} + +unsigned char* lodepng_chunk_data(unsigned char* chunk) +{ + return &chunk[8]; +} + +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) +{ + return &chunk[8]; +} + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk) +{ + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); + /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + unsigned checksum = lodepng_crc32(&chunk[4], length + 4); + if(CRC != checksum) return 1; + else return 0; +} + +void lodepng_chunk_generate_crc(unsigned char* chunk) +{ + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_crc32(&chunk[4], length + 4); + lodepng_set32bitInt(chunk + 8 + length, CRC); +} + +unsigned char* lodepng_chunk_next(unsigned char* chunk) +{ + unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; + return &chunk[total_chunk_length]; +} + +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk) +{ + unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; + return &chunk[total_chunk_length]; +} + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk) +{ + unsigned i; + unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; + unsigned char *chunk_start, *new_buffer; + size_t new_length = (*outlength) + total_chunk_length; + if(new_length < total_chunk_length || new_length < (*outlength)) return 77; /*integer overflow happened*/ + + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outlength) = new_length; + chunk_start = &(*out)[new_length - total_chunk_length]; + + for(i = 0; i < total_chunk_length; i++) chunk_start[i] = chunk[i]; + + return 0; +} + +unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data) +{ + unsigned i; + unsigned char *chunk, *new_buffer; + size_t new_length = (*outlength) + length + 12; + if(new_length < length + 12 || new_length < (*outlength)) return 77; /*integer overflow happened*/ + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outlength) = new_length; + chunk = &(*out)[(*outlength) - length - 12]; + + /*1: length*/ + lodepng_set32bitInt(chunk, (unsigned)length); + + /*2: chunk name (4 letters)*/ + chunk[4] = type[0]; + chunk[5] = type[1]; + chunk[6] = type[2]; + chunk[7] = type[3]; + + /*3: the data*/ + for(i = 0; i < length; i++) chunk[8 + i] = data[i]; + + /*4: CRC (of the chunkname characters and the data)*/ + lodepng_chunk_generate_crc(chunk); + + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types and such / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*return type is a LodePNG error code*/ +static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) /*bd = bitdepth*/ +{ + switch(colortype) + { + case 0: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; /*grey*/ + case 2: if(!( bd == 8 || bd == 16)) return 37; break; /*RGB*/ + case 3: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; /*palette*/ + case 4: if(!( bd == 8 || bd == 16)) return 37; break; /*grey + alpha*/ + case 6: if(!( bd == 8 || bd == 16)) return 37; break; /*RGBA*/ + default: return 31; + } + return 0; /*allowed color type / bits combination*/ +} + +static unsigned getNumColorChannels(LodePNGColorType colortype) +{ + switch(colortype) + { + case 0: return 1; /*grey*/ + case 2: return 3; /*RGB*/ + case 3: return 1; /*palette*/ + case 4: return 2; /*grey + alpha*/ + case 6: return 4; /*RGBA*/ + } + return 0; /*unexisting color type*/ +} + +static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) +{ + /*bits per pixel is amount of channels * bits per channel*/ + return getNumColorChannels(colortype) * bitdepth; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +void lodepng_color_mode_init(LodePNGColorMode* info) +{ + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colortype = LCT_RGBA; + info->bitdepth = 8; + info->palette = 0; + info->palettesize = 0; +} + +void lodepng_color_mode_cleanup(LodePNGColorMode* info) +{ + lodepng_palette_clear(info); +} + +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) +{ + size_t i; + lodepng_color_mode_cleanup(dest); + *dest = *source; + if(source->palette) + { + dest->palette = (unsigned char*)lodepng_malloc(source->palettesize * 4); + if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ + for(i = 0; i < source->palettesize * 4; i++) dest->palette[i] = source->palette[i]; + } + return 0; +} + +static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) +{ + size_t i; + if(a->colortype != b->colortype) return 0; + if(a->bitdepth != b->bitdepth) return 0; + if(a->key_defined != b->key_defined) return 0; + if(a->key_defined) + { + if(a->key_r != b->key_r) return 0; + if(a->key_g != b->key_g) return 0; + if(a->key_b != b->key_b) return 0; + } + if(a->palettesize != b->palettesize) return 0; + for(i = 0; i < a->palettesize * 4; i++) + { + if(a->palette[i] != b->palette[i]) return 0; + } + return 1; +} + +void lodepng_palette_clear(LodePNGColorMode* info) +{ + if(info->palette) lodepng_free(info->palette); + info->palettesize = 0; +} + +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + unsigned char* data; + /*the same resize technique as C++ std::vectors is used, and here it's made so that for a palette with + the max of 256 colors, it'll have the exact alloc size*/ + if(!(info->palettesize & (info->palettesize - 1))) /*if palettesize is 0 or a power of two*/ + { + /*allocated data must be at least 4* palettesize (for 4 color bytes)*/ + size_t alloc_size = info->palettesize == 0 ? 4 : info->palettesize * 4 * 2; + data = (unsigned char*)lodepng_realloc(info->palette, alloc_size); + if(!data) return 83; /*alloc fail*/ + else info->palette = data; + } + info->palette[4 * info->palettesize + 0] = r; + info->palette[4 * info->palettesize + 1] = g; + info->palette[4 * info->palettesize + 2] = b; + info->palette[4 * info->palettesize + 3] = a; + info->palettesize++; + return 0; +} + +unsigned lodepng_get_bpp(const LodePNGColorMode* info) +{ + /*calculate bits per pixel out of colortype and bitdepth*/ + return lodepng_get_bpp_lct(info->colortype, info->bitdepth); +} + +unsigned lodepng_get_channels(const LodePNGColorMode* info) +{ + return getNumColorChannels(info->colortype); +} + +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) +{ + return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; +} + +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) +{ + return (info->colortype & 4) != 0; /*4 or 6*/ +} + +unsigned lodepng_is_palette_type(const LodePNGColorMode* info) +{ + return info->colortype == LCT_PALETTE; +} + +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) +{ + size_t i; + for(i = 0; i < info->palettesize; i++) + { + if(info->palette[i * 4 + 3] < 255) return 1; + } + return 0; +} + +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) +{ + return info->key_defined + || lodepng_is_alpha_type(info) + || lodepng_has_palette_alpha(info); +} + +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) +{ + return (w * h * lodepng_get_bpp(color) + 7) / 8; +} + +size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) +{ + return (w * h * lodepng_get_bpp_lct(colortype, bitdepth) + 7) / 8; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static void LodePNGUnknownChunks_init(LodePNGInfo* info) +{ + unsigned i; + for(i = 0; i < 3; i++) info->unknown_chunks_data[i] = 0; + for(i = 0; i < 3; i++) info->unknown_chunks_size[i] = 0; +} + +static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) +{ + unsigned i; + for(i = 0; i < 3; i++) lodepng_free(info->unknown_chunks_data[i]); +} + +static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) +{ + unsigned i; + + LodePNGUnknownChunks_cleanup(dest); + + for(i = 0; i < 3; i++) + { + size_t j; + dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; + dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); + if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ + for(j = 0; j < src->unknown_chunks_size[i]; j++) + { + dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; + } + } + + return 0; +} + +/******************************************************************************/ + +static void LodePNGText_init(LodePNGInfo* info) +{ + info->text_num = 0; + info->text_keys = NULL; + info->text_strings = NULL; +} + +static void LodePNGText_cleanup(LodePNGInfo* info) +{ + size_t i; + for(i = 0; i < info->text_num; i++) + { + string_cleanup(&info->text_keys[i]); + string_cleanup(&info->text_strings[i]); + } + lodepng_free(info->text_keys); + lodepng_free(info->text_strings); +} + +static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) +{ + size_t i = 0; + dest->text_keys = 0; + dest->text_strings = 0; + dest->text_num = 0; + for(i = 0; i < source->text_num; i++) + { + CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); + } + return 0; +} + +void lodepng_clear_text(LodePNGInfo* info) +{ + LodePNGText_cleanup(info); +} + +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) +{ + char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); + if(!new_keys || !new_strings) + { + lodepng_free(new_keys); + lodepng_free(new_strings); + return 83; /*alloc fail*/ + } + + info->text_num++; + info->text_keys = new_keys; + info->text_strings = new_strings; + + string_init(&info->text_keys[info->text_num - 1]); + string_set(&info->text_keys[info->text_num - 1], key); + + string_init(&info->text_strings[info->text_num - 1]); + string_set(&info->text_strings[info->text_num - 1], str); + + return 0; +} + +/******************************************************************************/ + +static void LodePNGIText_init(LodePNGInfo* info) +{ + info->itext_num = 0; + info->itext_keys = NULL; + info->itext_langtags = NULL; + info->itext_transkeys = NULL; + info->itext_strings = NULL; +} + +static void LodePNGIText_cleanup(LodePNGInfo* info) +{ + size_t i; + for(i = 0; i < info->itext_num; i++) + { + string_cleanup(&info->itext_keys[i]); + string_cleanup(&info->itext_langtags[i]); + string_cleanup(&info->itext_transkeys[i]); + string_cleanup(&info->itext_strings[i]); + } + lodepng_free(info->itext_keys); + lodepng_free(info->itext_langtags); + lodepng_free(info->itext_transkeys); + lodepng_free(info->itext_strings); +} + +static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) +{ + size_t i = 0; + dest->itext_keys = 0; + dest->itext_langtags = 0; + dest->itext_transkeys = 0; + dest->itext_strings = 0; + dest->itext_num = 0; + for(i = 0; i < source->itext_num; i++) + { + CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], + source->itext_transkeys[i], source->itext_strings[i])); + } + return 0; +} + +void lodepng_clear_itext(LodePNGInfo* info) +{ + LodePNGIText_cleanup(info); +} + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str) +{ + char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); + char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); + char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) + { + lodepng_free(new_keys); + lodepng_free(new_langtags); + lodepng_free(new_transkeys); + lodepng_free(new_strings); + return 83; /*alloc fail*/ + } + + info->itext_num++; + info->itext_keys = new_keys; + info->itext_langtags = new_langtags; + info->itext_transkeys = new_transkeys; + info->itext_strings = new_strings; + + string_init(&info->itext_keys[info->itext_num - 1]); + string_set(&info->itext_keys[info->itext_num - 1], key); + + string_init(&info->itext_langtags[info->itext_num - 1]); + string_set(&info->itext_langtags[info->itext_num - 1], langtag); + + string_init(&info->itext_transkeys[info->itext_num - 1]); + string_set(&info->itext_transkeys[info->itext_num - 1], transkey); + + string_init(&info->itext_strings[info->itext_num - 1]); + string_set(&info->itext_strings[info->itext_num - 1], str); + + return 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +void lodepng_info_init(LodePNGInfo* info) +{ + lodepng_color_mode_init(&info->color); + info->interlace_method = 0; + info->compression_method = 0; + info->filter_method = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + info->background_defined = 0; + info->background_r = info->background_g = info->background_b = 0; + + LodePNGText_init(info); + LodePNGIText_init(info); + + info->time_defined = 0; + info->phys_defined = 0; + + LodePNGUnknownChunks_init(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +void lodepng_info_cleanup(LodePNGInfo* info) +{ + lodepng_color_mode_cleanup(&info->color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + LodePNGText_cleanup(info); + LodePNGIText_cleanup(info); + + LodePNGUnknownChunks_cleanup(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) +{ + lodepng_info_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->color); + CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color)); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); + CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); + + LodePNGUnknownChunks_init(dest); + CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source)); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + return 0; +} + +void lodepng_info_swap(LodePNGInfo* a, LodePNGInfo* b) +{ + LodePNGInfo temp = *a; + *a = *b; + *b = temp; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +/*index: bitgroup index, bits: bitgroup size(1, 2 or 4, in: bitgroup value, out: octet array to add bits to*/ +static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) +{ + /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ + unsigned p = index % (8 / bits); + in &= (1 << bits) - 1; /*filter out any other bits of the input value*/ + in = in << (bits * (8 / bits - p - 1)); + if(p == 0) out[index * bits / 8] = in; + else out[index * bits / 8] |= in; +} + +typedef struct ColorTree ColorTree; + +/* +One node of a color tree +This is the data structure used to count the number of unique colors and to get a palette +index for a color. It's like an octree, but because the alpha channel is used too, each +node has 16 instead of 8 children. +*/ +struct ColorTree +{ + ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ + int index; /*the payload. Only has a meaningful value if this is in the last level*/ +}; + +static void color_tree_init(ColorTree* tree) +{ + int i; + for(i = 0; i < 16; i++) tree->children[i] = 0; + tree->index = -1; +} + +static void color_tree_cleanup(ColorTree* tree) +{ + int i; + for(i = 0; i < 16; i++) + { + if(tree->children[i]) + { + color_tree_cleanup(tree->children[i]); + lodepng_free(tree->children[i]); + } + } +} + +/*returns -1 if color not present, its index otherwise*/ +static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + int bit = 0; + for(bit = 0; bit < 8; bit++) + { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) return -1; + else tree = tree->children[i]; + } + return tree ? tree->index : -1; +} + +#ifdef LODEPNG_COMPILE_ENCODER +static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return color_tree_get(tree, r, g, b, a) >= 0; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*color is not allowed to already exist. +Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist")*/ +static void color_tree_add(ColorTree* tree, + unsigned char r, unsigned char g, unsigned char b, unsigned char a, int index) +{ + int bit; + for(bit = 0; bit < 8; bit++) + { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) + { + tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); + color_tree_init(tree->children[i]); + } + tree = tree->children[i]; + } + tree->index = index; +} + +/*put a pixel, given its RGBA color, into image of any color type*/ +static unsigned rgba8ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + if(mode->colortype == LCT_GREY) + { + unsigned char grey = r; /*((unsigned short)r + g + b) / 3*/; + if(mode->bitdepth == 8) out[i] = grey; + else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = grey; + else + { + /*take the most significant bits of grey*/ + grey = (grey >> (8 - mode->bitdepth)) & ((1 << mode->bitdepth) - 1); + addColorBits(out, i, mode->bitdepth, grey); + } + } + else if(mode->colortype == LCT_RGB) + { + if(mode->bitdepth == 8) + { + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } + else + { + out[i * 6 + 0] = out[i * 6 + 1] = r; + out[i * 6 + 2] = out[i * 6 + 3] = g; + out[i * 6 + 4] = out[i * 6 + 5] = b; + } + } + else if(mode->colortype == LCT_PALETTE) + { + int index = color_tree_get(tree, r, g, b, a); + if(index < 0) return 82; /*color not in palette*/ + if(mode->bitdepth == 8) out[i] = index; + else addColorBits(out, i, mode->bitdepth, index); + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + unsigned char grey = r; /*((unsigned short)r + g + b) / 3*/; + if(mode->bitdepth == 8) + { + out[i * 2 + 0] = grey; + out[i * 2 + 1] = a; + } + else if(mode->bitdepth == 16) + { + out[i * 4 + 0] = out[i * 4 + 1] = grey; + out[i * 4 + 2] = out[i * 4 + 3] = a; + } + } + else if(mode->colortype == LCT_RGBA) + { + if(mode->bitdepth == 8) + { + out[i * 4 + 0] = r; + out[i * 4 + 1] = g; + out[i * 4 + 2] = b; + out[i * 4 + 3] = a; + } + else + { + out[i * 8 + 0] = out[i * 8 + 1] = r; + out[i * 8 + 2] = out[i * 8 + 3] = g; + out[i * 8 + 4] = out[i * 8 + 5] = b; + out[i * 8 + 6] = out[i * 8 + 7] = a; + } + } + + return 0; /*no error*/ +} + +/*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ +static unsigned rgba16ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, + unsigned short r, unsigned short g, unsigned short b, unsigned short a) +{ + if(mode->bitdepth != 16) return 85; /*must be 16 for this function*/ + if(mode->colortype == LCT_GREY) + { + unsigned short grey = r; /*((unsigned)r + g + b) / 3*/; + out[i * 2 + 0] = (grey >> 8) & 255; + out[i * 2 + 1] = grey & 255; + } + else if(mode->colortype == LCT_RGB) + { + out[i * 6 + 0] = (r >> 8) & 255; + out[i * 6 + 1] = r & 255; + out[i * 6 + 2] = (g >> 8) & 255; + out[i * 6 + 3] = g & 255; + out[i * 6 + 4] = (b >> 8) & 255; + out[i * 6 + 5] = b & 255; + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + unsigned short grey = r; /*((unsigned)r + g + b) / 3*/; + out[i * 4 + 0] = (grey >> 8) & 255; + out[i * 4 + 1] = grey & 255; + out[i * 4 + 2] = (a >> 8) & 255; + out[i * 4 + 3] = a & 255; + } + else if(mode->colortype == LCT_RGBA) + { + out[i * 8 + 0] = (r >> 8) & 255; + out[i * 8 + 1] = r & 255; + out[i * 8 + 2] = (g >> 8) & 255; + out[i * 8 + 3] = g & 255; + out[i * 8 + 4] = (b >> 8) & 255; + out[i * 8 + 5] = b & 255; + out[i * 8 + 6] = (a >> 8) & 255; + out[i * 8 + 7] = a & 255; + } + + return 0; /*no error*/ +} + +/*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/ +static unsigned getPixelColorRGBA8(unsigned char* r, unsigned char* g, + unsigned char* b, unsigned char* a, + const unsigned char* in, size_t i, + const LodePNGColorMode* mode) +{ + if(mode->colortype == LCT_GREY) + { + if(mode->bitdepth == 8) + { + *r = *g = *b = in[i]; + if(mode->key_defined && *r == mode->key_r) *a = 0; + else *a = 255; + } + else if(mode->bitdepth == 16) + { + *r = *g = *b = in[i * 2 + 0]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 255; + } + else + { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = i * mode->bitdepth; + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + *r = *g = *b = (value * 255) / highest; + if(mode->key_defined && value == mode->key_r) *a = 0; + else *a = 255; + } + } + else if(mode->colortype == LCT_RGB) + { + if(mode->bitdepth == 8) + { + *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; + if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; + else *a = 255; + } + else + { + *r = in[i * 6 + 0]; + *g = in[i * 6 + 2]; + *b = in[i * 6 + 4]; + if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 255; + } + } + else if(mode->colortype == LCT_PALETTE) + { + unsigned index; + if(mode->bitdepth == 8) index = in[i]; + else + { + size_t j = i * mode->bitdepth; + index = readBitsFromReversedStream(&j, in, mode->bitdepth); + } + if(index >= mode->palettesize) return 47; /*index out of palette*/ + *r = mode->palette[index * 4 + 0]; + *g = mode->palette[index * 4 + 1]; + *b = mode->palette[index * 4 + 2]; + *a = mode->palette[index * 4 + 3]; + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + if(mode->bitdepth == 8) + { + *r = *g = *b = in[i * 2 + 0]; + *a = in[i * 2 + 1]; + } + else + { + *r = *g = *b = in[i * 4 + 0]; + *a = in[i * 4 + 2]; + } + } + else if(mode->colortype == LCT_RGBA) + { + if(mode->bitdepth == 8) + { + *r = in[i * 4 + 0]; + *g = in[i * 4 + 1]; + *b = in[i * 4 + 2]; + *a = in[i * 4 + 3]; + } + else + { + *r = in[i * 8 + 0]; + *g = in[i * 8 + 2]; + *b = in[i * 8 + 4]; + *a = in[i * 8 + 6]; + } + } + + return 0; /*no error*/ +} + +/*Similar to getPixelColorRGBA8, but with all the for loops inside of the color +mode test cases, optimized to convert the colors much faster, when converting +to RGBA or RGB with 8 bit per cannel. buffer must be RGBA or RGB output with +enough memory, if has_alpha is true the output is RGBA. mode has the color mode +of the input buffer.*/ +static unsigned getPixelColorsRGBA8(unsigned char* buffer, size_t numpixels, + unsigned has_alpha, const unsigned char* in, + const LodePNGColorMode* mode) +{ + unsigned num_channels = has_alpha ? 4 : 3; + size_t i; + if(mode->colortype == LCT_GREY) + { + if(mode->bitdepth == 8) + { + for(i = 0; i < numpixels; i++, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i]; + if(has_alpha) buffer[3] = mode->key_defined && in[i] == mode->key_r ? 0 : 255; + } + } + else if(mode->bitdepth == 16) + { + for(i = 0; i < numpixels; i++, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + if(has_alpha) buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; + } + } + else + { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i < numpixels; i++, buffer += num_channels) + { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + if(has_alpha) buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; + } + } + } + else if(mode->colortype == LCT_RGB) + { + if(mode->bitdepth == 8) + { + for(i = 0; i < numpixels; i++, buffer += num_channels) + { + buffer[0] = in[i * 3 + 0]; + buffer[1] = in[i * 3 + 1]; + buffer[2] = in[i * 3 + 2]; + if(has_alpha) buffer[3] = mode->key_defined && buffer[0] == mode->key_r + && buffer[1]== mode->key_g && buffer[2] == mode->key_b ? 0 : 255; + } + } + else + { + for(i = 0; i < numpixels; i++, buffer += num_channels) + { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + if(has_alpha) buffer[3] = mode->key_defined + && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; + } + } + } + else if(mode->colortype == LCT_PALETTE) + { + unsigned index; + size_t j = 0; + for(i = 0; i < numpixels; i++, buffer += num_channels) + { + if(mode->bitdepth == 8) index = in[i]; + else index = readBitsFromReversedStream(&j, in, mode->bitdepth); + if(index >= mode->palettesize) return 47; /*index out of palette*/ + buffer[0] = mode->palette[index * 4 + 0]; + buffer[1] = mode->palette[index * 4 + 1]; + buffer[2] = mode->palette[index * 4 + 2]; + if(has_alpha) buffer[3] = mode->palette[index * 4 + 3]; + } + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + if(mode->bitdepth == 8) + { + for(i = 0; i < numpixels; i++, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + if(has_alpha) buffer[3] = in[i * 2 + 1]; + } + } + else + { + for(i = 0; i < numpixels; i++, buffer += num_channels) + { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + if(has_alpha) buffer[3] = in[i * 4 + 2]; + } + } + } + else if(mode->colortype == LCT_RGBA) + { + if(mode->bitdepth == 8) + { + for(i = 0; i < numpixels; i++, buffer += num_channels) + { + buffer[0] = in[i * 4 + 0]; + buffer[1] = in[i * 4 + 1]; + buffer[2] = in[i * 4 + 2]; + if(has_alpha) buffer[3] = in[i * 4 + 3]; + } + } + else + { + for(i = 0; i < numpixels; i++, buffer += num_channels) + { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + if(has_alpha) buffer[3] = in[i * 8 + 6]; + } + } + } + + return 0; /*no error*/ +} + +/*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with +given color type, but the given color type must be 16-bit itself.*/ +static unsigned getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, + const unsigned char* in, size_t i, const LodePNGColorMode* mode) +{ + if(mode->bitdepth != 16) return 85; /*error: this function only supports 16-bit input*/ + + if(mode->colortype == LCT_GREY) + { + *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 65535; + } + else if(mode->colortype == LCT_RGB) + { + *r = 256 * in[i * 6 + 0] + in[i * 6 + 1]; + *g = 256 * in[i * 6 + 2] + in[i * 6 + 3]; + *b = 256 * in[i * 6 + 4] + in[i * 6 + 5]; + if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 65535; + } + else if(mode->colortype == LCT_GREY_ALPHA) + { + *r = *g = *b = 256 * in[i * 4 + 0] + in[i * 4 + 1]; + *a = 256 * in[i * 4 + 2] + in[i * 4 + 3]; + } + else if(mode->colortype == LCT_RGBA) + { + *r = 256 * in[i * 8 + 0] + in[i * 8 + 1]; + *g = 256 * in[i * 8 + 2] + in[i * 8 + 3]; + *b = 256 * in[i * 8 + 4] + in[i * 8 + 5]; + *a = 256 * in[i * 8 + 6] + in[i * 8 + 7]; + } + else return 85; /*error: this function only supports 16-bit input, not palettes*/ + + return 0; /*no error*/ +} + +/* +converts from any color type to 24-bit or 32-bit (later maybe more supported). return value = LodePNG error code +the out buffer must have (w * h * bpp + 7) / 8 bytes, where bpp is the bits per pixel of the output color type +(lodepng_get_bpp) for < 8 bpp images, there may _not_ be padding bits at the end of scanlines. +*/ +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + LodePNGColorMode* mode_out, LodePNGColorMode* mode_in, + unsigned w, unsigned h) +{ + unsigned error = 0; + size_t i; + ColorTree tree; + size_t numpixels = w * h; + + if(lodepng_color_mode_equal(mode_out, mode_in)) + { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + for(i = 0; i < numbytes; i++) out[i] = in[i]; + return error; + } + + if(mode_out->colortype == LCT_PALETTE) + { + // !!! Ren-C modification...add cast for bit shift, avoid warning + size_t palsize = ((size_t)1) << mode_out->bitdepth; + if(mode_out->palettesize < palsize) palsize = mode_out->palettesize; + color_tree_init(&tree); + for(i = 0; i < palsize; i++) + { + unsigned char* p = &mode_out->palette[i * 4]; + color_tree_add(&tree, p[0], p[1], p[2], p[3], i); + } + } + + if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) + { + for(i = 0; i < numpixels; i++) + { + unsigned short r = 0, g = 0, b = 0, a = 0; + error = getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if(error) break; + error = rgba16ToPixel(out, i, mode_out, r, g, b, a); + if(error) break; + } + } + else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) + { + error = getPixelColorsRGBA8(out, numpixels, 1, in, mode_in); + } + else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) + { + error = getPixelColorsRGBA8(out, numpixels, 0, in, mode_in); + } + else + { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i < numpixels; i++) + { + error = getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + if(error) break; + error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); + if(error) break; + } + } + + if(mode_out->colortype == LCT_PALETTE) + { + color_tree_cleanup(&tree); + } + + return error; +} + +#ifdef LODEPNG_COMPILE_ENCODER + +typedef struct ColorProfile +{ + unsigned char sixteenbit; /*needs more than 8 bits per channel*/ + unsigned char sixteenbit_done; + + + unsigned char colored; /*not greyscale*/ + unsigned char colored_done; + + unsigned char key; /*a color key is required, or more*/ + unsigned short key_r; /*these values are always in 16-bit bitdepth in the profile*/ + unsigned short key_g; + unsigned short key_b; + unsigned char alpha; /*alpha channel, or alpha palette, required*/ + unsigned char alpha_done; + + unsigned numcolors; + ColorTree tree; /*for listing the counted colors, up to 256*/ + unsigned char* palette; /*size 1024. Remember up to the first 256 RGBA colors*/ + unsigned maxnumcolors; /*if more than that amount counted*/ + unsigned char numcolors_done; + + unsigned greybits; /*amount of bits required for greyscale (1, 2, 4, 8). Does not take 16 bit into account.*/ + unsigned char greybits_done; + +} ColorProfile; + +static void color_profile_init(ColorProfile* profile, LodePNGColorMode* mode) +{ + profile->sixteenbit = 0; + profile->sixteenbit_done = mode->bitdepth == 16 ? 0 : 1; + + profile->colored = 0; + profile->colored_done = lodepng_is_greyscale_type(mode) ? 1 : 0; + + profile->key = 0; + profile->alpha = 0; + profile->alpha_done = lodepng_can_have_alpha(mode) ? 0 : 1; + + profile->numcolors = 0; + color_tree_init(&profile->tree); + profile->palette = (unsigned char*)lodepng_malloc(1024); + profile->maxnumcolors = 257; + if(lodepng_get_bpp(mode) <= 8) + { + int bpp = lodepng_get_bpp(mode); + profile->maxnumcolors = bpp == 1 ? 2 : (bpp == 2 ? 4 : (bpp == 4 ? 16 : 256)); + } + profile->numcolors_done = 0; + + profile->greybits = 1; + profile->greybits_done = lodepng_get_bpp(mode) == 1 ? 1 : 0; +} + +static void color_profile_cleanup(ColorProfile* profile) +{ + color_tree_cleanup(&profile->tree); + lodepng_free(profile->palette); +} + +/*function used for debug purposes with C++*/ +/*void printColorProfile(ColorProfile* p) +{ + std::cout << "sixteenbit: " << (int)p->sixteenbit << std::endl; + std::cout << "sixteenbit_done: " << (int)p->sixteenbit_done << std::endl; + std::cout << "colored: " << (int)p->colored << std::endl; + std::cout << "colored_done: " << (int)p->colored_done << std::endl; + std::cout << "key: " << (int)p->key << std::endl; + std::cout << "key_r: " << (int)p->key_r << std::endl; + std::cout << "key_g: " << (int)p->key_g << std::endl; + std::cout << "key_b: " << (int)p->key_b << std::endl; + std::cout << "alpha: " << (int)p->alpha << std::endl; + std::cout << "alpha_done: " << (int)p->alpha_done << std::endl; + std::cout << "numcolors: " << (int)p->numcolors << std::endl; + std::cout << "maxnumcolors: " << (int)p->maxnumcolors << std::endl; + std::cout << "numcolors_done: " << (int)p->numcolors_done << std::endl; + std::cout << "greybits: " << (int)p->greybits << std::endl; + std::cout << "greybits_done: " << (int)p->greybits_done << std::endl; +}*/ + +/*Returns how many bits needed to represent given value (max 8 bit)*/ +unsigned getValueRequiredBits(unsigned short value) +{ + if(value == 0 || value == 255) return 1; + /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ + if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; + return 8; +} + +/*profile must already have been inited with mode. +It's ok to set some parameters of profile to done already.*/ +static unsigned get_color_profile(ColorProfile* profile, + const unsigned char* in, size_t numpixels, + LodePNGColorMode* mode) +{ + unsigned error = 0; + size_t i; + + if(mode->bitdepth == 16) + { + for(i = 0; i < numpixels; i++) + { + unsigned short r, g, b, a; + error = getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode); + if(error) break; + + /*a color is considered good for 8-bit if the first byte and the second byte are equal, + (so if it's divisible through 257), NOT necessarily if the second byte is 0*/ + if(!profile->sixteenbit_done + && (((r & 255) != ((r >> 8) & 255)) + || ((g & 255) != ((g >> 8) & 255)) + || ((b & 255) != ((b >> 8) & 255)))) + { + profile->sixteenbit = 1; + profile->sixteenbit_done = 1; + profile->greybits_done = 1; /*greybits is not applicable anymore at 16-bit*/ + profile->numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/ + } + + if(!profile->colored_done && (r != g || r != b)) + { + profile->colored = 1; + profile->colored_done = 1; + profile->greybits_done = 1; /*greybits is not applicable anymore*/ + } + + if(!profile->alpha_done && a != 65535) + { + if(a == 0 && !(profile->key && (r != profile->key_r || g != profile->key_g || b != profile->key_b))) + { + if(!profile->key) + { + profile->key = 1; + profile->key_r = r; + profile->key_g = g; + profile->key_b = b; + } + } + else + { + profile->alpha = 1; + profile->alpha_done = 1; + profile->greybits_done = 1; /*greybits is not applicable anymore*/ + } + } + + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + if(!profile->alpha_done && a == 65535 && profile->key + && r == profile->key_r && g == profile->key_g && b == profile->key_b) + { + profile->alpha = 1; + profile->alpha_done = 1; + profile->greybits_done = 1; /*greybits is not applicable anymore*/ + } + + if(!profile->greybits_done) + { + /*assuming 8-bit r, this test does not care about 16-bit*/ + unsigned bits = getValueRequiredBits(r); + if(bits > profile->greybits) profile->greybits = bits; + if(profile->greybits >= 8) profile->greybits_done = 1; + } + + if(!profile->numcolors_done) + { + /*assuming 8-bit rgba, this test does not care about 16-bit*/ + if(!color_tree_has(&profile->tree, (unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a)) + { + color_tree_add(&profile->tree, (unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a, + profile->numcolors); + if(profile->numcolors < 256) + { + unsigned char* p = profile->palette; + unsigned i = profile->numcolors; + p[i * 4 + 0] = (unsigned char)r; + p[i * 4 + 1] = (unsigned char)g; + p[i * 4 + 2] = (unsigned char)b; + p[i * 4 + 3] = (unsigned char)a; + } + profile->numcolors++; + if(profile->numcolors >= profile->maxnumcolors) profile->numcolors_done = 1; + } + } + + if(profile->alpha_done && profile->numcolors_done + && profile->colored_done && profile->sixteenbit_done && profile->greybits_done) + { + break; + } + }; + } + else /* < 16-bit */ + { + for(i = 0; i < numpixels; i++) + { + unsigned char r = 0, g = 0, b = 0, a = 0; + error = getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode); + if(error) break; + + if(!profile->colored_done && (r != g || r != b)) + { + profile->colored = 1; + profile->colored_done = 1; + profile->greybits_done = 1; /*greybits is not applicable anymore*/ + } + + if(!profile->alpha_done && a != 255) + { + if(a == 0 && !(profile->key && (r != profile->key_r || g != profile->key_g || b != profile->key_b))) + { + if(!profile->key) + { + profile->key = 1; + profile->key_r = r; + profile->key_g = g; + profile->key_b = b; + } + } + else + { + profile->alpha = 1; + profile->alpha_done = 1; + profile->greybits_done = 1; /*greybits is not applicable anymore*/ + } + } + + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + if(!profile->alpha_done && a == 255 && profile->key + && r == profile->key_r && g == profile->key_g && b == profile->key_b) + { + profile->alpha = 1; + profile->alpha_done = 1; + profile->greybits_done = 1; /*greybits is not applicable anymore*/ + } + + if(!profile->greybits_done) + { + unsigned bits = getValueRequiredBits(r); + if(bits > profile->greybits) profile->greybits = bits; + if(profile->greybits >= 8) profile->greybits_done = 1; + } + + if(!profile->numcolors_done) + { + if(!color_tree_has(&profile->tree, r, g, b, a)) + { + + color_tree_add(&profile->tree, r, g, b, a, profile->numcolors); + if(profile->numcolors < 256) + { + unsigned char* p = profile->palette; + unsigned i = profile->numcolors; + p[i * 4 + 0] = r; + p[i * 4 + 1] = g; + p[i * 4 + 2] = b; + p[i * 4 + 3] = a; + } + profile->numcolors++; + if(profile->numcolors >= profile->maxnumcolors) profile->numcolors_done = 1; + } + } + + if(profile->alpha_done && profile->numcolors_done && profile->colored_done && profile->greybits_done) + { + break; + } + }; + } + + /*make the profile's key always 16-bit for consistency*/ + if(mode->bitdepth < 16) + { + /*repeat each byte twice*/ + profile->key_r *= 257; + profile->key_g *= 257; + profile->key_b *= 257; + } + + return error; +} + +/*updates values of mode with a potentially smaller color model. mode_out should +contain the user chosen color model, but will be overwritten with the new chosen one.*/ +static unsigned doAutoChooseColor(LodePNGColorMode* mode_out, + const unsigned char* image, unsigned w, unsigned h, LodePNGColorMode* mode_in, + LodePNGAutoConvert auto_convert) +{ + ColorProfile profile; + unsigned error = 0; + int no_nibbles = auto_convert == LAC_AUTO_NO_NIBBLES || auto_convert == LAC_AUTO_NO_NIBBLES_NO_PALETTE; + int no_palette = auto_convert == LAC_AUTO_NO_PALETTE || auto_convert == LAC_AUTO_NO_NIBBLES_NO_PALETTE; + + if(auto_convert == LAC_ALPHA) + { + if(mode_out->colortype != LCT_RGBA && mode_out->colortype != LCT_GREY_ALPHA) return 0; + } + + color_profile_init(&profile, mode_in); + if(auto_convert == LAC_ALPHA) + { + profile.colored_done = 1; + profile.greybits_done = 1; + profile.numcolors_done = 1; + profile.sixteenbit_done = 1; + } + error = get_color_profile(&profile, image, w * h, mode_in); + + if(!error && auto_convert == LAC_ALPHA) + { + if(!profile.alpha) + { + mode_out->colortype = (mode_out->colortype == LCT_RGBA ? LCT_RGB : LCT_GREY); + } + } + else if(!error && auto_convert != LAC_ALPHA) + { + mode_out->key_defined = 0; + + if(profile.sixteenbit) + { + mode_out->bitdepth = 16; + if(profile.alpha) + { + mode_out->colortype = profile.colored ? LCT_RGBA : LCT_GREY_ALPHA; + } + else + { + mode_out->colortype = profile.colored ? LCT_RGB : LCT_GREY; + if(profile.key) + { + mode_out->key_defined = 1; + mode_out->key_r = profile.key_r; + mode_out->key_g = profile.key_g; + mode_out->key_b = profile.key_b; + } + } + } + else /*less than 16 bits per channel*/ + { + /*don't add palette overhead if image hasn't got a lot of pixels*/ + unsigned n = profile.numcolors; + int palette_ok = !no_palette && n <= 256 && (n * 2 < w * h); + unsigned palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); + int grey_ok = !profile.colored && !profile.alpha; /*grey without alpha, with potentially low bits*/ + if(palette_ok || grey_ok) + { + if(!palette_ok || (grey_ok && profile.greybits <= palettebits)) + { + mode_out->colortype = LCT_GREY; + mode_out->bitdepth = profile.greybits; + if(profile.key) + { + unsigned keyval = profile.key_r; + keyval &= (profile.greybits - 1); /*same subgroup of bits repeated, so taking right bits is fine*/ + mode_out->key_defined = 1; + mode_out->key_r = keyval; + mode_out->key_g = keyval; + mode_out->key_b = keyval; + } + } + else + { + /*fill in the palette*/ + unsigned i; + unsigned char* p = profile.palette; + for(i = 0; i < profile.numcolors; i++) + { + error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); + if(error) break; + } + + mode_out->colortype = LCT_PALETTE; + mode_out->bitdepth = palettebits; + } + } + else /*8-bit per channel*/ + { + mode_out->bitdepth = 8; + if(profile.alpha) + { + mode_out->colortype = profile.colored ? LCT_RGBA : LCT_GREY_ALPHA; + } + else + { + mode_out->colortype = profile.colored ? LCT_RGB : LCT_GREY /*LCT_GREY normally won't occur, already done earlier*/; + if(profile.key) + { + mode_out->key_defined = 1; + mode_out->key_r = profile.key_r % 256; + mode_out->key_g = profile.key_g % 256; + mode_out->key_b = profile.key_b % 256; + } + } + } + } + } + + color_profile_cleanup(&profile); + + if(mode_out->colortype == LCT_PALETTE && mode_in->palettesize == mode_out->palettesize) + { + /*In this case keep the palette order of the input, so that the user can choose an optimal one*/ + size_t i; + for(i = 0; i < mode_in->palettesize * 4; i++) + { + mode_out->palette[i] = mode_in->palette[i]; + } + } + + if(no_nibbles && mode_out->bitdepth < 8) + { + /*palette can keep its small amount of colors, as long as no indices use it*/ + mode_out->bitdepth = 8; + } + + return error; +} + +#endif /* #ifdef LODEPNG_COMPILE_ENCODER */ + +/* +Paeth predicter, used by PNG filter type 4 +The parameters are of type short, but should come from unsigned chars, the shorts +are only needed to make the paeth calculation correct. +*/ +static unsigned char paethPredictor(short a, short b, short c) +{ + short pa = abs(b - c); + short pb = abs(a - c); + short pc = abs(a + b - c - c); + + if(pc < pa && pc < pb) return (unsigned char)c; + else if(pb < pa) return (unsigned char)b; + else return (unsigned char)a; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +/* +Outputs various dimensions and positions in the image related to the Adam7 reduced images. +passw: output containing the width of the 7 passes +passh: output containing the height of the 7 passes +filter_passstart: output containing the index of the start and end of each + reduced image with filter bytes +padded_passstart output containing the index of the start and end of each + reduced image when without filter bytes but with padded scanlines +passstart: output containing the index of the start and end of each reduced + image without padding between scanlines, but still padding between the images +w, h: width and height of non-interlaced image +bpp: bits per pixel +"padded" is only relevant if bpp is less than 8 and a scanline or image does not + end at a full byte +*/ +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], + size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) +{ + /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ + unsigned i; + + /*calculate width and height in pixels of each pass*/ + for(i = 0; i < 7; i++) + { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for(i = 0; i < 7; i++) + { + /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ + filter_passstart[i + 1] = filter_passstart[i] + + ((passw[i] && passh[i]) ? passh[i] * (1 + (passw[i] * bpp + 7) / 8) : 0); + /*bits padded if needed to fill full byte at end of each scanline*/ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7) / 8); + /*only padded at end of reduced image*/ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7) / 8; + } +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*read the information from the header and store it in the LodePNGInfo. return value is error*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, + const unsigned char* in, size_t insize) +{ + LodePNGInfo* info = &state->info_png; + if(insize == 0 || in == 0) + { + CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ + } + if(insize < 29) + { + CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ + } + + /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ + lodepng_info_cleanup(info); + lodepng_info_init(info); + + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 + || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) + { + CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ + } + if(in[12] != 'I' || in[13] != 'H' || in[14] != 'D' || in[15] != 'R') + { + CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ + } + + /*read the values given in the header*/ + *w = lodepng_read32bitInt(&in[16]); + *h = lodepng_read32bitInt(&in[20]); + info->color.bitdepth = in[24]; + info->color.colortype = (LodePNGColorType)in[25]; + info->compression_method = in[26]; + info->filter_method = in[27]; + info->interlace_method = in[28]; + + if(!state->decoder.ignore_crc) + { + unsigned CRC = lodepng_read32bitInt(&in[29]); + unsigned checksum = lodepng_crc32(&in[12], 17); + if(CRC != checksum) + { + CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ + } + } + + /*error: only compression method 0 is allowed in the specification*/ + if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); + /*error: only filter method 0 is allowed in the specification*/ + if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); + /*error: only interlace methods 0 and 1 exist in the specification*/ + if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); + + state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); + return state->error; +} + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, + size_t bytewidth, unsigned char filterType, size_t length) +{ + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, + the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + size_t i; + switch(filterType) + { + case 0: + for(i = 0; i < length; i++) recon[i] = scanline[i]; + break; + case 1: + for(i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for(i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth]; + break; + case 2: + if(precon) + { + for(i = 0; i < length; i++) recon[i] = scanline[i] + precon[i]; + } + else + { + for(i = 0; i < length; i++) recon[i] = scanline[i]; + } + break; + case 3: + if(precon) + { + for(i = 0; i < bytewidth; i++) recon[i] = scanline[i] + precon[i] / 2; + for(i = bytewidth; i < length; i++) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) / 2); + } + else + { + for(i = 0; i < bytewidth; i++) recon[i] = scanline[i]; + for(i = bytewidth; i < length; i++) recon[i] = scanline[i] + recon[i - bytewidth] / 2; + } + break; + case 4: + if(precon) + { + for(i = 0; i < bytewidth; i++) + { + recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ + } + for(i = bytewidth; i < length; i++) + { + recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth])); + } + } + else + { + for(i = 0; i < bytewidth; i++) + { + recon[i] = scanline[i]; + } + for(i = bytewidth; i < length; i++) + { + /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ + recon[i] = (scanline[i] + recon[i - bytewidth]); + } + } + break; + default: return 36; /*error: unexisting filter type given*/ + } + return 0; +} + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) + */ + + unsigned y; + unsigned char* prevline = 0; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7) / 8; + size_t linebytes = (w * bpp + 7) / 8; + + for(y = 0; y < h; y++) + { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + unsigned char filterType = in[inindex]; + + CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); + + prevline = &out[outindex]; + } + + return 0; +} + +/* +in: Adam7 interlaced image, with no padding bits between scanlines, but between + reduced images so that each reduced image starts at a byte. +out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h +bpp: bits per pixel +out has the following size in bits: w * h * bpp. +in is possibly bigger due to padding bits between reduced images. +out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation +(because that's likely a little bit faster) +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) + { + for(i = 0; i < 7; i++) + { + unsigned x, y, b; + size_t bytewidth = bpp / 8; + for(y = 0; y < passh[i]; y++) + for(x = 0; x < passw[i]; x++) + { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + for(b = 0; b < bytewidth; b++) + { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } + else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ + { + for(i = 0; i < 7; i++) + { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; y++) + for(x = 0; x < passw[i]; x++) + { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + for(b = 0; b < bpp; b++) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + /*note that this function assumes the out buffer is completely 0, use setBitOfReversedStream otherwise*/ + setBitOfReversedStream0(&obp, out, bit); + } + } + } + } +} + +static void removePaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) +{ + /* + After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need + to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers + for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t ibp = 0, obp = 0; /*input and output bit pointers*/ + for(y = 0; y < h; y++) + { + size_t x; + for(x = 0; x < olinebits; x++) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from +the IDAT chunks (with filter index bytes and possible padding bits) +return value is error*/ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, + unsigned w, unsigned h, const LodePNGInfo* info_png) +{ + /* + This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. + Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= posible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + if(bpp == 0) return 31; /*error: invalid colortype*/ + + if(info_png->interlace_method == 0) + { + if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) + { + CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); + removePaddingBits(out, in, w * bpp, ((w * bpp + 7) / 8) * 8, h); + } + /*we can immediatly filter into the out buffer, no other steps needed*/ + else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); + } + else /*interlace_method is 1 (Adam7)*/ + { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for(i = 0; i < 7; i++) + { + CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); + /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, + move bytes instead of bits or move not at all*/ + if(bpp < 8) + { + /*remove padding bits in scanlines; after this there still may be padding + bits between the different reduced images: each reduced image still starts nicely at a byte*/ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, + ((passw[i] * bpp + 7) / 8) * 8, passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return 0; +} + +static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) +{ + unsigned pos = 0, i; + if(color->palette) lodepng_free(color->palette); + color->palettesize = chunkLength / 3; + color->palette = (unsigned char*)lodepng_malloc(4 * color->palettesize); + if(!color->palette && color->palettesize) + { + color->palettesize = 0; + return 83; /*alloc fail*/ + } + if(color->palettesize > 256) return 38; /*error: palette too big*/ + + for(i = 0; i < color->palettesize; i++) + { + color->palette[4 * i + 0] = data[pos++]; /*R*/ + color->palette[4 * i + 1] = data[pos++]; /*G*/ + color->palette[4 * i + 2] = data[pos++]; /*B*/ + color->palette[4 * i + 3] = 255; /*alpha*/ + } + + return 0; /* OK */ +} + +static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) +{ + unsigned i; + if(color->colortype == LCT_PALETTE) + { + /*error: more alpha values given than there are palette entries*/ + if(chunkLength > color->palettesize) return 38; + + for(i = 0; i < chunkLength; i++) color->palette[4 * i + 3] = data[i]; + } + else if(color->colortype == LCT_GREY) + { + /*error: this chunk must be 2 bytes for greyscale image*/ + if(chunkLength != 2) return 30; + + color->key_defined = 1; + color->key_r = color->key_g = color->key_b = 256 * data[0] + data[1]; + } + else if(color->colortype == LCT_RGB) + { + /*error: this chunk must be 6 bytes for RGB image*/ + if(chunkLength != 6) return 41; + + color->key_defined = 1; + color->key_r = 256 * data[0] + data[1]; + color->key_g = 256 * data[2] + data[3]; + color->key_b = 256 * data[4] + data[5]; + } + else return 42; /*error: tRNS chunk not allowed for other color models*/ + + return 0; /* OK */ +} + + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*background color chunk (bKGD)*/ +static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + if(info->color.colortype == LCT_PALETTE) + { + /*error: this chunk must be 1 byte for indexed color image*/ + if(chunkLength != 1) return 43; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = data[0]; + } + else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) + { + /*error: this chunk must be 2 bytes for greyscale image*/ + if(chunkLength != 2) return 44; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b + = 256 * data[0] + data[1]; + } + else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) + { + /*error: this chunk must be 6 bytes for greyscale image*/ + if(chunkLength != 6) return 45; + + info->background_defined = 1; + info->background_r = 256 * data[0] + data[1]; + info->background_g = 256 * data[2] + data[3]; + info->background_b = 256 * data[4] + data[5]; + } + + return 0; /* OK */ +} + +/*text chunk (tEXt)*/ +static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + unsigned error = 0; + char *key = 0, *str = 0; + unsigned i; + + while(!error) /*not really a while loop, only used to break on error*/ + { + unsigned length, string2_begin; + + length = 0; + while(length < chunkLength && data[length] != 0) length++; + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i < length; i++) key[i] = data[i]; + + string2_begin = length + 1; /*skip keyword null terminator*/ + + length = chunkLength < string2_begin ? 0 : chunkLength - string2_begin; + str = (char*)lodepng_malloc(length + 1); + if(!str) CERROR_BREAK(error, 83); /*alloc fail*/ + + str[length] = 0; + for(i = 0; i < length; i++) str[i] = data[string2_begin + i]; + + error = lodepng_add_text(info, key, str); + + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*compressed text chunk (zTXt)*/ +static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, + const unsigned char* data, size_t chunkLength) +{ + unsigned error = 0; + unsigned i; + + unsigned length, string2_begin; + char *key = 0; + ucvector decoded; + + ucvector_init(&decoded); + + while(!error) /*not really a while loop, only used to break on error*/ + { + for(length = 0; length < chunkLength && data[length] != 0; length++) ; + if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i < length; i++) key[i] = data[i]; + + if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + + length = chunkLength - string2_begin; + /*will fail if zlib error, e.g. if length is too small*/ + // Ren/C: Fix cast away of const + error = zlib_decompress(&decoded.data, &decoded.size, + (const unsigned char*)(&data[string2_begin]), + length, zlibsettings); + if(error) break; + ucvector_push_back(&decoded, 0); + + error = lodepng_add_text(info, key, (char*)decoded.data); + + break; + } + + lodepng_free(key); + ucvector_cleanup(&decoded); + + return error; +} + +/*international text chunk (iTXt)*/ +static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, + const unsigned char* data, size_t chunkLength) +{ + unsigned error = 0; + unsigned i; + + unsigned length, begin, compressed; + char *key = 0, *langtag = 0, *transkey = 0; + ucvector decoded; + ucvector_init(&decoded); + + while(!error) /*not really a while loop, only used to break on error*/ + { + /*Quick check if the chunk length isn't too small. Even without check + it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ + if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ + + /*read the key*/ + for(length = 0; length < chunkLength && data[length] != 0; length++) ; + if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + key[length] = 0; + for(i = 0; i < length; i++) key[i] = data[i]; + + /*read the compression method*/ + compressed = data[length + 1]; + if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty for the next 3 texts*/ + + /*read the langtag*/ + begin = length + 3; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; i++) length++; + + langtag = (char*)lodepng_malloc(length + 1); + if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/ + + langtag[length] = 0; + for(i = 0; i < length; i++) langtag[i] = data[begin + i]; + + /*read the transkey*/ + begin += length + 1; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; i++) length++; + + transkey = (char*)lodepng_malloc(length + 1); + if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/ + + transkey[length] = 0; + for(i = 0; i < length; i++) transkey[i] = data[begin + i]; + + /*read the actual text*/ + begin += length + 1; + + length = chunkLength < begin ? 0 : chunkLength - begin; + + if(compressed) + { + /*will fail if zlib error, e.g. if length is too small*/ + // Ren/C fix cast away of const + error = zlib_decompress(&decoded.data, &decoded.size, + (const unsigned char*)(&data[begin]), + length, zlibsettings); + if(error) break; + if(decoded.allocsize < decoded.size) decoded.allocsize = decoded.size; + ucvector_push_back(&decoded, 0); + } + else + { + if(!ucvector_resize(&decoded, length + 1)) CERROR_BREAK(error, 83 /*alloc fail*/); + + decoded.data[length] = 0; + for(i = 0; i < length; i++) decoded.data[i] = data[begin + i]; + } + + error = lodepng_add_itext(info, key, langtag, transkey, (char*)decoded.data); + + break; + } + + lodepng_free(key); + lodepng_free(langtag); + lodepng_free(transkey); + ucvector_cleanup(&decoded); + + return error; +} + +static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ + + info->time_defined = 1; + info->time.year = 256 * data[0] + data[+ 1]; + info->time.month = data[2]; + info->time.day = data[3]; + info->time.hour = data[4]; + info->time.minute = data[5]; + info->time.second = data[6]; + + return 0; /* OK */ +} + +static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) +{ + if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ + + info->phys_defined = 1; + info->phys_x = 16777216 * data[0] + 65536 * data[1] + 256 * data[2] + data[3]; + info->phys_y = 16777216 * data[4] + 65536 * data[5] + 256 * data[6] + data[7]; + info->phys_unit = data[8]; + + return 0; /* OK */ +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) +{ + unsigned char IEND = 0; + const unsigned char* chunk; + size_t i; + ucvector idat; /*the data from idat chunks*/ + + /*for unknown chunk order*/ + unsigned unknown = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + + /*provide some proper output values if error will happen*/ + *out = 0; + + state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ + if(state->error) return; + + ucvector_init(&idat); + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. + IDAT data is put at the start of the in buffer*/ + while(!IEND && !state->error) + { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + + /*error: size of the in buffer too small to contain next chunk*/ + if((size_t)((chunk - in) + 12) > insize || chunk < in) CERROR_BREAK(state->error, 30); + + /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ + chunkLength = lodepng_chunk_length(chunk); + /*error: chunk length larger than the max PNG chunk size*/ + if(chunkLength > 2147483647) CERROR_BREAK(state->error, 63); + + if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) + { + CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/ + } + + data = lodepng_chunk_data_const(chunk); + + /*IDAT chunk, containing compressed image data*/ + if(lodepng_chunk_type_equals(chunk, "IDAT")) + { + size_t oldsize = idat.size; + if(!ucvector_resize(&idat, oldsize + chunkLength)) CERROR_BREAK(state->error, 83 /*alloc fail*/); + for(i = 0; i < chunkLength; i++) idat.data[oldsize + i] = data[i]; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 3; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + /*IEND chunk*/ + else if(lodepng_chunk_type_equals(chunk, "IEND")) + { + IEND = 1; + } + /*palette chunk (PLTE)*/ + else if(lodepng_chunk_type_equals(chunk, "PLTE")) + { + state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 2; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + /*palette transparency chunk (tRNS)*/ + else if(lodepng_chunk_type_equals(chunk, "tRNS")) + { + state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); + if(state->error) break; + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*background color chunk (bKGD)*/ + else if(lodepng_chunk_type_equals(chunk, "bKGD")) + { + state->error = readChunk_bKGD(&state->info_png, data, chunkLength); + if(state->error) break; + } + /*text chunk (tEXt)*/ + else if(lodepng_chunk_type_equals(chunk, "tEXt")) + { + if(state->decoder.read_text_chunks) + { + state->error = readChunk_tEXt(&state->info_png, data, chunkLength); + if(state->error) break; + } + } + /*compressed text chunk (zTXt)*/ + else if(lodepng_chunk_type_equals(chunk, "zTXt")) + { + if(state->decoder.read_text_chunks) + { + state->error = readChunk_zTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + if(state->error) break; + } + } + /*international text chunk (iTXt)*/ + else if(lodepng_chunk_type_equals(chunk, "iTXt")) + { + if(state->decoder.read_text_chunks) + { + state->error = readChunk_iTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); + if(state->error) break; + } + } + else if(lodepng_chunk_type_equals(chunk, "tIME")) + { + state->error = readChunk_tIME(&state->info_png, data, chunkLength); + if(state->error) break; + } + else if(lodepng_chunk_type_equals(chunk, "pHYs")) + { + state->error = readChunk_pHYs(&state->info_png, data, chunkLength); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + else /*it's not an implemented chunk type, so ignore it: skip over the data*/ + { + /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + if(!lodepng_chunk_ancillary(chunk)) CERROR_BREAK(state->error, 69); + + unknown = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(state->decoder.remember_unknown_chunks) + { + state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], + &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + + if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ + { + if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ + } + + if(!IEND) chunk = lodepng_chunk_next_const(chunk); + } + + if(!state->error) + { + ucvector scanlines; + ucvector_init(&scanlines); + + /*maximum final image length is already reserved in the vector's length - this is not really necessary*/ + if(!ucvector_resize(&scanlines, lodepng_get_raw_size(*w, *h, &state->info_png.color) + *h)) + { + state->error = 83; /*alloc fail*/ + } + if(!state->error) + { + /*decompress with the Zlib decompressor*/ + state->error = zlib_decompress(&scanlines.data, &scanlines.size, idat.data, + idat.size, &state->decoder.zlibsettings); + } + + if(!state->error) + { + ucvector outv; + ucvector_init(&outv); + if(!ucvector_resizev(&outv, + lodepng_get_raw_size(*w, *h, &state->info_png.color), 0)) state->error = 83; /*alloc fail*/ + if(!state->error) state->error = postProcessScanlines(outv.data, scanlines.data, *w, *h, &state->info_png); + *out = outv.data; + } + ucvector_cleanup(&scanlines); + } + + ucvector_cleanup(&idat); +} + +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) +{ + *out = 0; + decodeGeneric(out, w, h, state, in, insize); + if(state->error) return state->error; + if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) + { + /*same color type, no copying or converting of data needed*/ + /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype + the raw image has to the end user*/ + if(!state->decoder.color_convert) + { + state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); + if(state->error) return state->error; + } + } + else + { + /*color conversion needed; sort of copy of the data*/ + unsigned char* data = *out; + size_t outsize; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert + from greyscale input color type, to 8-bit greyscale or greyscale with alpha"*/ + if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) + && !(state->info_raw.bitdepth == 8)) + { + return 56; /*unsupported color mode conversion*/ + } + + outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!(*out)) + { + state->error = 83; /*alloc fail*/ + } + else state->error = lodepng_convert(*out, data, &state->info_raw, &state->info_png.color, *w, *h); + lodepng_free(data); + } + return state->error; +} + +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + error = lodepng_decode(out, w, h, &state, in, insize); + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) +{ + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); +} + +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) +{ + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, + LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error; + error = lodepng_load_file(&buffer, &buffersize, filename); + if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) +{ + return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); +} + +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) +{ + return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) +{ + settings->color_convert = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->read_text_chunks = 1; + settings->remember_unknown_chunks = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + settings->ignore_crc = 0; + lodepng_decompress_settings_init(&settings->zlibsettings); +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) + +void lodepng_state_init(LodePNGState* state) +{ +#ifdef LODEPNG_COMPILE_DECODER + lodepng_decoder_settings_init(&state->decoder); +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + lodepng_encoder_settings_init(&state->encoder); +#endif /*LODEPNG_COMPILE_ENCODER*/ + lodepng_color_mode_init(&state->info_raw); + lodepng_info_init(&state->info_png); + state->error = 1; +} + +void lodepng_state_cleanup(LodePNGState* state) +{ + lodepng_color_mode_cleanup(&state->info_raw); + lodepng_info_cleanup(&state->info_png); +} + +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) +{ + lodepng_state_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->info_raw); + lodepng_info_init(&dest->info_png); + dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return; + dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); if(dest->error) return; +} + +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Encoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*chunkName must be string of 4 characters*/ +static unsigned addChunk(ucvector* out, const char* chunkName, const unsigned char* data, size_t length) +{ + CERROR_TRY_RETURN(lodepng_chunk_create(&out->data, &out->size, (unsigned)length, chunkName, data)); + out->allocsize = out->size; /*fix the allocsize again*/ + return 0; +} + +static void writeSignature(ucvector* out) +{ + /*8 bytes PNG signature, aka the magic bytes*/ + ucvector_push_back(out, 137); + ucvector_push_back(out, 80); + ucvector_push_back(out, 78); + ucvector_push_back(out, 71); + ucvector_push_back(out, 13); + ucvector_push_back(out, 10); + ucvector_push_back(out, 26); + ucvector_push_back(out, 10); +} + +static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) +{ + unsigned error = 0; + ucvector header; + ucvector_init(&header); + + lodepng_add32bitInt(&header, w); /*width*/ + lodepng_add32bitInt(&header, h); /*height*/ + ucvector_push_back(&header, (unsigned char)bitdepth); /*bit depth*/ + ucvector_push_back(&header, (unsigned char)colortype); /*color type*/ + ucvector_push_back(&header, 0); /*compression method*/ + ucvector_push_back(&header, 0); /*filter method*/ + ucvector_push_back(&header, interlace_method); /*interlace method*/ + + error = addChunk(out, "IHDR", header.data, header.size); + ucvector_cleanup(&header); + + return error; +} + +static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) +{ + unsigned error = 0; + size_t i; + ucvector PLTE; + ucvector_init(&PLTE); + for(i = 0; i < info->palettesize * 4; i++) + { + /*add all channels except alpha channel*/ + if(i % 4 != 3) ucvector_push_back(&PLTE, info->palette[i]); + } + error = addChunk(out, "PLTE", PLTE.data, PLTE.size); + ucvector_cleanup(&PLTE); + + return error; +} + +static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) +{ + unsigned error = 0; + size_t i; + ucvector tRNS; + ucvector_init(&tRNS); + if(info->colortype == LCT_PALETTE) + { + size_t amount = info->palettesize; + /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ + for(i = info->palettesize; i > 0; i--) + { + if(info->palette[4 * (i - 1) + 3] == 255) amount--; + else break; + } + /*add only alpha channel*/ + for(i = 0; i < amount; i++) ucvector_push_back(&tRNS, info->palette[4 * i + 3]); + } + else if(info->colortype == LCT_GREY) + { + if(info->key_defined) + { + ucvector_push_back(&tRNS, (unsigned char)(info->key_r / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_r % 256)); + } + } + else if(info->colortype == LCT_RGB) + { + if(info->key_defined) + { + ucvector_push_back(&tRNS, (unsigned char)(info->key_r / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_r % 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_g / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_g % 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_b / 256)); + ucvector_push_back(&tRNS, (unsigned char)(info->key_b % 256)); + } + } + + error = addChunk(out, "tRNS", tRNS.data, tRNS.size); + ucvector_cleanup(&tRNS); + + return error; +} + +static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, + LodePNGCompressSettings* zlibsettings) +{ + ucvector zlibdata; + unsigned error = 0; + + /*compress with the Zlib compressor*/ + ucvector_init(&zlibdata); + error = zlib_compress(&zlibdata.data, &zlibdata.size, data, datasize, zlibsettings); + if(!error) error = addChunk(out, "IDAT", zlibdata.data, zlibdata.size); + ucvector_cleanup(&zlibdata); + + return error; +} + +static unsigned addChunk_IEND(ucvector* out) +{ + unsigned error = 0; + error = addChunk(out, "IEND", 0, 0); + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) +{ + unsigned error = 0; + size_t i; + ucvector text; + ucvector_init(&text); + for(i = 0; keyword[i] != 0; i++) ucvector_push_back(&text, (unsigned char)keyword[i]); + if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/ + ucvector_push_back(&text, 0); /*0 termination char*/ + for(i = 0; textstring[i] != 0; i++) ucvector_push_back(&text, (unsigned char)textstring[i]); + error = addChunk(out, "tEXt", text.data, text.size); + ucvector_cleanup(&text); + + return error; +} + +static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, + LodePNGCompressSettings* zlibsettings) +{ + unsigned error = 0; + ucvector data, compressed; + size_t i, textsize = strlen(textstring); + + ucvector_init(&data); + ucvector_init(&compressed); + for(i = 0; keyword[i] != 0; i++) ucvector_push_back(&data, (unsigned char)keyword[i]); + if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/ + ucvector_push_back(&data, 0); /*0 termination char*/ + ucvector_push_back(&data, 0); /*compression method: 0*/ + + // Ren/C: fix cast away of const + error = zlib_compress(&compressed.data, &compressed.size, + (const unsigned char*)textstring, textsize, zlibsettings); + if(!error) + { + for(i = 0; i < compressed.size; i++) ucvector_push_back(&data, compressed.data[i]); + error = addChunk(out, "zTXt", data.data, data.size); + } + + ucvector_cleanup(&compressed); + ucvector_cleanup(&data); + return error; +} + +static unsigned addChunk_iTXt(ucvector* out, unsigned compressed, const char* keyword, const char* langtag, + const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) +{ + unsigned error = 0; + ucvector data; + size_t i, textsize = strlen(textstring); + + ucvector_init(&data); + + for(i = 0; keyword[i] != 0; i++) ucvector_push_back(&data, (unsigned char)keyword[i]); + if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/ + ucvector_push_back(&data, 0); /*null termination char*/ + ucvector_push_back(&data, compressed ? 1 : 0); /*compression flag*/ + ucvector_push_back(&data, 0); /*compression method*/ + for(i = 0; langtag[i] != 0; i++) ucvector_push_back(&data, (unsigned char)langtag[i]); + ucvector_push_back(&data, 0); /*null termination char*/ + for(i = 0; transkey[i] != 0; i++) ucvector_push_back(&data, (unsigned char)transkey[i]); + ucvector_push_back(&data, 0); /*null termination char*/ + + if(compressed) + { + ucvector compressed_data; + ucvector_init(&compressed_data); + // Ren/C: Fix cast away of const + error = zlib_compress(&compressed_data.data, &compressed_data.size, + (const unsigned char*)textstring, textsize, zlibsettings); + if(!error) + { + for(i = 0; i < compressed_data.size; i++) ucvector_push_back(&data, compressed_data.data[i]); + } + ucvector_cleanup(&compressed_data); + } + else /*not compressed*/ + { + for(i = 0; textstring[i] != 0; i++) ucvector_push_back(&data, (unsigned char)textstring[i]); + } + + if(!error) error = addChunk(out, "iTXt", data.data, data.size); + ucvector_cleanup(&data); + return error; +} + +static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) +{ + unsigned error = 0; + ucvector bKGD; + ucvector_init(&bKGD); + if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_r % 256)); + } + else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_r % 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_g / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_g % 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_b / 256)); + ucvector_push_back(&bKGD, (unsigned char)(info->background_b % 256)); + } + else if(info->color.colortype == LCT_PALETTE) + { + ucvector_push_back(&bKGD, (unsigned char)(info->background_r % 256)); /*palette index*/ + } + + error = addChunk(out, "bKGD", bKGD.data, bKGD.size); + ucvector_cleanup(&bKGD); + + return error; +} + +static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) +{ + unsigned error = 0; + unsigned char* data = (unsigned char*)lodepng_malloc(7); + if(!data) return 83; /*alloc fail*/ + data[0] = (unsigned char)(time->year / 256); + data[1] = (unsigned char)(time->year % 256); + data[2] = time->month; + data[3] = time->day; + data[4] = time->hour; + data[5] = time->minute; + data[6] = time->second; + error = addChunk(out, "tIME", data, 7); + lodepng_free(data); + return error; +} + +static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) +{ + unsigned error = 0; + ucvector data; + ucvector_init(&data); + + lodepng_add32bitInt(&data, info->phys_x); + lodepng_add32bitInt(&data, info->phys_y); + ucvector_push_back(&data, info->phys_unit); + + error = addChunk(out, "pHYs", data.data, data.size); + ucvector_cleanup(&data); + + return error; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, + size_t length, size_t bytewidth, unsigned char filterType) +{ + size_t i; + switch(filterType) + { + case 0: /*None*/ + for(i = 0; i < length; i++) out[i] = scanline[i]; + break; + case 1: /*Sub*/ + if(prevline) + { + for(i = 0; i < bytewidth; i++) out[i] = scanline[i]; + for(i = bytewidth; i < length; i++) out[i] = scanline[i] - scanline[i - bytewidth]; + } + else + { + for(i = 0; i < bytewidth; i++) out[i] = scanline[i]; + for(i = bytewidth; i < length; i++) out[i] = scanline[i] - scanline[i - bytewidth]; + } + break; + case 2: /*Up*/ + if(prevline) + { + for(i = 0; i < length; i++) out[i] = scanline[i] - prevline[i]; + } + else + { + for(i = 0; i < length; i++) out[i] = scanline[i]; + } + break; + case 3: /*Average*/ + if(prevline) + { + for(i = 0; i < bytewidth; i++) out[i] = scanline[i] - prevline[i] / 2; + for(i = bytewidth; i < length; i++) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) / 2); + } + else + { + for(i = 0; i < bytewidth; i++) out[i] = scanline[i]; + for(i = bytewidth; i < length; i++) out[i] = scanline[i] - scanline[i - bytewidth] / 2; + } + break; + case 4: /*Paeth*/ + if(prevline) + { + /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ + for(i = 0; i < bytewidth; i++) out[i] = (scanline[i] - prevline[i]); + for(i = bytewidth; i < length; i++) + { + out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); + } + } + else + { + for(i = 0; i < bytewidth; i++) out[i] = scanline[i]; + /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ + for(i = bytewidth; i < length; i++) out[i] = (scanline[i] - scanline[i - bytewidth]); + } + break; + default: return; /*unexisting filter type given*/ + } +} + +/* log2 approximation. A slight bit faster than std::log. */ +static float flog2(float f) +{ + float result = 0; + while(f > 32) { result += 4; f /= 16; } + while(f > 2) { result++; f /= 2; } + return result + 1.442695f * (f * f * f / 3 - 3 * f * f / 2 + 3 * f - 1.83333f); +} + +static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* info, const LodePNGEncoderSettings* settings) +{ + /* + For PNG filter method 0 + out must be a buffer with as size: h + (w * h * bpp + 7) / 8, because there are + the scanlines with 1 extra byte per scanline + */ + + unsigned bpp = lodepng_get_bpp(info); + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = (w * bpp + 7) / 8; + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7) / 8; + const unsigned char* prevline = 0; + unsigned x, y; + unsigned error = 0; + LodePNGFilterStrategy strategy = settings->filter_strategy; + + /* + There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: + * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. + use fixed filtering, with the filter None). + * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is + not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply + all five filters and select the filter that produces the smallest sum of absolute values per row. + This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. + + If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, + but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum + heuristic is used. + */ + if(settings->filter_palette_zero && + (info->colortype == LCT_PALETTE || info->bitdepth < 8)) strategy = LFS_ZERO; + + if(bpp == 0) return 31; /*error: invalid color type*/ + + if(strategy == LFS_ZERO) + { + for(y = 0; y < h; y++) + { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + out[outindex] = 0; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, 0); + prevline = &in[inindex]; + } + } + else if(strategy == LFS_MINSUM) + { + /*adaptive filtering*/ + size_t sum[5]; + ucvector attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type, bestType = 0; + + for(type = 0; type < 5; type++) + { + ucvector_init(&attempt[type]); + if(!ucvector_resize(&attempt[type], linebytes)) + { + while(type > 0) { + free(attempt[type - 1].data); + type --; + } + return 83; /*alloc fail*/ + } + } + + if(!error) + { + for(y = 0; y < h; y++) + { + /*try the 5 filter types*/ + for(type = 0; type < 5; type++) + { + filterScanline(attempt[type].data, &in[y * linebytes], prevline, linebytes, bytewidth, type); + + /*calculate the sum of the result*/ + sum[type] = 0; + if(type == 0) + { + for(x = 0; x < linebytes; x++) sum[type] += (unsigned char)(attempt[type].data[x]); + } + else + { + for(x = 0; x < linebytes; x++) + { + /*For differences, each byte should be treated as signed, values above 127 are negative + (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. + This means filtertype 0 is almost never chosen, but that is justified.*/ + signed char s = (signed char)(attempt[type].data[x]); + sum[type] += s < 0 ? -s : s; + } + } + + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum[type] < smallest) + { + bestType = type; + smallest = sum[type]; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x < linebytes; x++) out[y * (linebytes + 1) + 1 + x] = attempt[bestType].data[x]; + } + } + + for(type = 0; type < 5; type++) ucvector_cleanup(&attempt[type]); + } + else if(strategy == LFS_ENTROPY) + { + float sum[5]; + ucvector attempt[5]; /*five filtering attempts, one for each filter type*/ + float smallest = 0; + unsigned type, bestType = 0; + unsigned count[256]; + + for(type = 0; type < 5; type++) + { + ucvector_init(&attempt[type]); + if(!ucvector_resize(&attempt[type], linebytes)) + { + while(type > 0) { + free(attempt[type - 1].data); + type --; + } + return 83; /*alloc fail*/ + } + } + + for(y = 0; y < h; y++) + { + /*try the 5 filter types*/ + for(type = 0; type < 5; type++) + { + filterScanline(attempt[type].data, &in[y * linebytes], prevline, linebytes, bytewidth, type); + for(x = 0; x < 256; x++) count[x] = 0; + for(x = 0; x < linebytes; x++) count[attempt[type].data[x]]++; + count[type]++; /*the filter type itself is part of the scanline*/ + sum[type] = 0; + for(x = 0; x < 256; x++) + { + float p = count[x] / (float)(linebytes + 1); + sum[type] += count[x] == 0 ? 0 : flog2(1 / p) * p; + } + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum[type] < smallest) + { + bestType = type; + smallest = sum[type]; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x < linebytes; x++) out[y * (linebytes + 1) + 1 + x] = attempt[bestType].data[x]; + } + + for(type = 0; type < 5; type++) ucvector_cleanup(&attempt[type]); + } + else if(strategy == LFS_PREDEFINED) + { + for(y = 0; y < h; y++) + { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + unsigned type = settings->predefined_filters[y]; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } + else if(strategy == LFS_BRUTE_FORCE) + { + /*brute force filter chooser. + deflate the scanline after every filter attempt to see which one deflates best. + This is very slow and gives only slightly smaller, sometimes even larger, result*/ + size_t size[5]; + ucvector attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type = 0, bestType = 0; + unsigned char* dummy; + LodePNGCompressSettings zlibsettings = settings->zlibsettings; + /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, + to simulate the true case where the tree is the same for the whole image. Sometimes it gives + better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare + cases better compression. It does make this a bit less slow, so it's worth doing this.*/ + zlibsettings.btype = 1; + /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG + images only, so disable it*/ + zlibsettings.custom_zlib = 0; + zlibsettings.custom_deflate = 0; + for(type = 0; type < 5; type++) + { + ucvector_init(&attempt[type]); + ucvector_resize(&attempt[type], linebytes); /*todo: give error if resize failed*/ + } + for(y = 0; y < h; y++) /*try the 5 filter types*/ + { + for(type = 0; type < 5; type++) + { + unsigned testsize = attempt[type].size; + /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ + + filterScanline(attempt[type].data, &in[y * linebytes], prevline, linebytes, bytewidth, type); + size[type] = 0; + dummy = 0; + zlib_compress(&dummy, &size[type], attempt[type].data, testsize, &zlibsettings); + lodepng_free(dummy); + /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || size[type] < smallest) + { + bestType = type; + smallest = size[type]; + } + } + prevline = &in[y * linebytes]; + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x < linebytes; x++) out[y * (linebytes + 1) + 1 + x] = attempt[bestType].data[x]; + } + for(type = 0; type < 5; type++) ucvector_cleanup(&attempt[type]); + } + else return 88; /* unknown filter strategy */ + + return error; +} + +static void addPaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) +{ + /*The opposite of the removePaddingBits function + olinebits must be >= ilinebits*/ + unsigned y; + size_t diff = olinebits - ilinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y < h; y++) + { + size_t x; + for(x = 0; x < ilinebits; x++) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + /*obp += diff; --> no, fill in some value in the padding bits too, to avoid + "Use of uninitialised value of size ###" warning from valgrind*/ + for(x = 0; x < diff; x++) setBitOfReversedStream(&obp, out, 0); + } +} + +/* +in: non-interlaced image with size w*h +out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with + no padding bits between scanlines, but between reduced images so that each + reduced image starts at a byte. +bpp: bits per pixel +there are no padding bits, not between scanlines, not between reduced images +in has the following size in bits: w * h * bpp. +out is possibly bigger due to padding bits between reduced images +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) +{ + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) + { + for(i = 0; i < 7; i++) + { + unsigned x, y, b; + size_t bytewidth = bpp / 8; + for(y = 0; y < passh[i]; y++) + for(x = 0; x < passw[i]; x++) + { + size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; + for(b = 0; b < bytewidth; b++) + { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } + else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ + { + for(i = 0; i < 7; i++) + { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; y++) + for(x = 0; x < passw[i]; x++) + { + ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + for(b = 0; b < bpp; b++) + { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. +return value is error**/ +static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, + unsigned w, unsigned h, + const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) +{ + /* + This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: + *) if no Adam7: 1) add padding bits (= posible extra bits per scanline if bpp < 8) 2) filter + *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + unsigned error = 0; + + if(info_png->interlace_method == 0) + { + *outsize = h + (h * ((w * bpp + 7) / 8)); /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ + + if(!error) + { + /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ + if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) + { + unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7) / 8)); + if(!padded) error = 83; /*alloc fail*/ + if(!error) + { + addPaddingBits(padded, in, ((w * bpp + 7) / 8) * 8, w * bpp, h); + error = filter(*out, padded, w, h, &info_png->color, settings); + } + lodepng_free(padded); + } + else + { + /*we can immediatly filter into the out buffer, no other steps needed*/ + error = filter(*out, in, w, h, &info_png->color, settings); + } + } + } + else /*interlace_method is 1 (Adam7)*/ + { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned char* adam7; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out)) error = 83; /*alloc fail*/ + + adam7 = (unsigned char*)lodepng_malloc(passstart[7]); + if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ + + if(!error) + { + unsigned i; + + Adam7_interlace(adam7, in, w, h, bpp); + for(i = 0; i < 7; i++) + { + if(bpp < 8) + { + unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); + if(!padded) ERROR_BREAK(83); /*alloc fail*/ + addPaddingBits(padded, &adam7[passstart[i]], + ((passw[i] * bpp + 7) / 8) * 8, passw[i] * bpp, passh[i]); + error = filter(&(*out)[filter_passstart[i]], padded, + passw[i], passh[i], &info_png->color, settings); + lodepng_free(padded); + } + else + { + error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], + passw[i], passh[i], &info_png->color, settings); + } + + if(error) break; + } + } + + lodepng_free(adam7); + } + + return error; +} + +/* +palette must have 4 * palettesize bytes allocated, and given in format RGBARGBARGBARGBA... +returns 0 if the palette is opaque, +returns 1 if the palette has a single color with alpha 0 ==> color key +returns 2 if the palette is semi-translucent. +*/ +static unsigned getPaletteTranslucency(const unsigned char* palette, size_t palettesize) +{ + size_t i, key = 0; + unsigned r = 0, g = 0, b = 0; /*the value of the color with alpha 0, so long as color keying is possible*/ + for(i = 0; i < palettesize; i++) + { + if(!key && palette[4 * i + 3] == 0) + { + r = palette[4 * i + 0]; g = palette[4 * i + 1]; b = palette[4 * i + 2]; + key = 1; + i = (size_t)(-1); /*restart from beginning, to detect earlier opaque colors with key's value*/ + } + else if(palette[4 * i + 3] != 255) return 2; + /*when key, no opaque RGB may have key's RGB*/ + else if(key && r == palette[i * 4 + 0] && g == palette[i * 4 + 1] && b == palette[i * 4 + 2]) return 2; + } + return key; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) +{ + unsigned char* inchunk = data; + while((size_t)(inchunk - data) < datasize) + { + CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); + out->allocsize = out->size; /*fix the allocsize again*/ + inchunk = lodepng_chunk_next(inchunk); + } + return 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state) +{ + LodePNGInfo info; + ucvector outv; + unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ + size_t datasize = 0; + unsigned char* converted; + size_t size; + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + state->error = 0; + + lodepng_info_init(&info); + lodepng_info_copy(&info, &state->info_png); + + if((info.color.colortype == LCT_PALETTE || state->encoder.force_palette) + && (info.color.palettesize == 0 || info.color.palettesize > 256)) + { + state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/ + return state->error; + } + + if(state->encoder.auto_convert != LAC_NO) + { + state->error = doAutoChooseColor(&info.color, image, w, h, &state->info_raw, + state->encoder.auto_convert); + } + if(state->error) return state->error; + + if(state->encoder.zlibsettings.windowsize > 32768) + { + CERROR_RETURN_ERROR(state->error, 60); /*error: windowsize larger than allowed*/ + } + if(state->encoder.zlibsettings.btype > 2) + { + CERROR_RETURN_ERROR(state->error, 61); /*error: unexisting btype*/ + } + if(state->info_png.interlace_method > 1) + { + CERROR_RETURN_ERROR(state->error, 71); /*error: unexisting interlace mode*/ + } + + state->error = checkColorValidity(info.color.colortype, info.color.bitdepth); + if(state->error) return state->error; /*error: unexisting color type given*/ + state->error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth); + if(state->error) return state->error; /*error: unexisting color type given*/ +// REBOL patch - we need to always convert BGRA->RGBA +// if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) +// { + size = (w * h * lodepng_get_bpp(&info.color) + 7) / 8; + + converted = (unsigned char*)lodepng_malloc(size); + if(!converted && size) state->error = 83; /*alloc fail*/ + if(!state->error) + { + if ( + info.color.colortype == LCT_RGBA && info.color.bitdepth == 8 && + lodepng_color_mode_equal(&state->info_raw, &info.color) + ){ + //convert BGRA format used by REBOL to RGBA + size_t i; + size_t size = w * h; + const unsigned char* src = image; + unsigned char* dst = converted; + + for (i=0; ierror = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); + } + } + if(!state->error) preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); + lodepng_free(converted); +// } +// else preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); + + ucvector_init(&outv); + while(!state->error) /*while only executed once, to break on error*/ + { +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + size_t i; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*write signature and chunks*/ + writeSignature(&outv); + /*IHDR*/ + addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*unknown chunks between IHDR and PLTE*/ + if(info.unknown_chunks_data[0]) + { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*PLTE*/ + if(info.color.colortype == LCT_PALETTE) + { + addChunk_PLTE(&outv, &info.color); + } + if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) + { + addChunk_PLTE(&outv, &info.color); + } + /*tRNS*/ + if(info.color.colortype == LCT_PALETTE && getPaletteTranslucency(info.color.palette, info.color.palettesize) != 0) + { + addChunk_tRNS(&outv, &info.color); + } + if((info.color.colortype == LCT_GREY || info.color.colortype == LCT_RGB) && info.color.key_defined) + { + addChunk_tRNS(&outv, &info.color); + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*bKGD (must come between PLTE and the IDAt chunks*/ + if(info.background_defined) addChunk_bKGD(&outv, &info); + /*pHYs (must come before the IDAT chunks)*/ + if(info.phys_defined) addChunk_pHYs(&outv, &info); + + /*unknown chunks between PLTE and IDAT*/ + if(info.unknown_chunks_data[1]) + { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*IDAT (multiple IDAT chunks must be consecutive)*/ + state->error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*tIME*/ + if(info.time_defined) addChunk_tIME(&outv, &info.time); + /*tEXt and/or zTXt*/ + for(i = 0; i < info.text_num; i++) + { + if(strlen(info.text_keys[i]) > 79) + { + state->error = 66; /*text chunk too large*/ + break; + } + if(strlen(info.text_keys[i]) < 1) + { + state->error = 67; /*text chunk too small*/ + break; + } + if(state->encoder.text_compression) + addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); + else + addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); + } + /*LodePNG version id in text chunk*/ + if(state->encoder.add_id) + { + unsigned alread_added_id_text = 0; + for(i = 0; i < info.text_num; i++) + { + if(!strcmp(info.text_keys[i], "LodePNG")) + { + alread_added_id_text = 1; + break; + } + } + if(alread_added_id_text == 0) + addChunk_tEXt(&outv, "LodePNG", VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ + } + /*iTXt*/ + for(i = 0; i < info.itext_num; i++) + { + if(strlen(info.itext_keys[i]) > 79) + { + state->error = 66; /*text chunk too large*/ + break; + } + if(strlen(info.itext_keys[i]) < 1) + { + state->error = 67; /*text chunk too small*/ + break; + } + addChunk_iTXt(&outv, state->encoder.text_compression, + info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i], + &state->encoder.zlibsettings); + } + + /*unknown chunks between IDAT and IEND*/ + if(info.unknown_chunks_data[2]) + { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*IEND*/ + addChunk_IEND(&outv); + + break; /*this isn't really a while loop; no error happened so break out now!*/ + } + + lodepng_info_cleanup(&info); + lodepng_free(data); + /*instead of cleaning the vector up, give it to the output*/ + *out = outv.data; + *outsize = outv.size; + + return state->error; +} + +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, + unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + state.info_png.color.colortype = colortype; + state.info_png.color.bitdepth = bitdepth; + lodepng_encode(out, outsize, image, w, h, &state); + error = state.error; + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); + if(!error) error = lodepng_save_file(buffer, buffersize, filename); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) +{ + return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) +{ + lodepng_compress_settings_init(&settings->zlibsettings); + settings->filter_palette_zero = 1; + settings->filter_strategy = LFS_MINSUM; + settings->auto_convert = LAC_AUTO; + settings->force_palette = 0; + settings->predefined_filters = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->add_id = 0; + settings->text_compression = 1; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/* +This returns the description of a numerical error code in English. This is also +the documentation of all the error codes. +*/ +const char* lodepng_error_text(unsigned code) +{ + switch(code) + { + case 0: return "no error, everything went ok"; + case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ + case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ + case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ + case 13: return "problem while processing dynamic deflate block"; + case 14: return "problem while processing dynamic deflate block"; + case 15: return "problem while processing dynamic deflate block"; + case 16: return "unexisting code while processing dynamic deflate block"; + case 17: return "end of out buffer memory reached while inflating"; + case 18: return "invalid distance code while inflating"; + case 19: return "end of out buffer memory reached while inflating"; + case 20: return "invalid deflate block BTYPE encountered while decoding"; + case 21: return "NLEN is not ones complement of LEN in a deflate block"; + /*end of out buffer memory reached while inflating: + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image.*/ + case 22: return "end of out buffer memory reached while inflating"; + case 23: return "end of in buffer memory reached while inflating"; + case 24: return "invalid FCHECK in zlib header"; + case 25: return "invalid compression method in zlib header"; + case 26: return "FDICT encountered in zlib header while it's not used for PNG"; + case 27: return "PNG file is smaller than a PNG header"; + /*Checks the magic file header, the first 8 bytes of the PNG file*/ + case 28: return "incorrect PNG signature, it's no PNG or corrupted"; + case 29: return "first chunk is not the header chunk"; + case 30: return "chunk length too large, chunk broken off at end of file"; + case 31: return "illegal PNG color type or bpp"; + case 32: return "illegal PNG compression method"; + case 33: return "illegal PNG filter method"; + case 34: return "illegal PNG interlace method"; + case 35: return "chunk length of a chunk is too large or the chunk too small"; + case 36: return "illegal PNG filter type encountered"; + case 37: return "illegal bit depth for this color type given"; + case 38: return "the palette is too big"; /*more than 256 colors*/ + case 39: return "more palette alpha values given in tRNS chunk than there are colors in the palette"; + case 40: return "tRNS chunk has wrong size for greyscale image"; + case 41: return "tRNS chunk has wrong size for RGB image"; + case 42: return "tRNS chunk appeared while it was not allowed for this color type"; + case 43: return "bKGD chunk has wrong size for palette image"; + case 44: return "bKGD chunk has wrong size for greyscale image"; + case 45: return "bKGD chunk has wrong size for RGB image"; + /*Is the palette too small?*/ + case 46: return "a value in indexed image is larger than the palette size (bitdepth = 8)"; + /*Is the palette too small?*/ + case 47: return "a value in indexed image is larger than the palette size (bitdepth < 8)"; + /*the input data is empty, maybe a PNG file doesn't exist or is in the wrong path*/ + case 48: return "empty input or file doesn't exist"; + case 49: return "jumped past memory while generating dynamic huffman tree"; + case 50: return "jumped past memory while generating dynamic huffman tree"; + case 51: return "jumped past memory while inflating huffman block"; + case 52: return "jumped past memory while inflating"; + case 53: return "size of zlib data too small"; + case 54: return "repeat symbol in tree while there was no value symbol yet"; + /*jumped past tree while generating huffman tree, this could be when the + tree will have more leaves than symbols after generating it out of the + given lenghts. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ + case 55: return "jumped past tree while generating huffman tree"; + case 56: return "given output image colortype or bitdepth not supported for color conversion"; + case 57: return "invalid CRC encountered (checking CRC can be disabled)"; + case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; + case 59: return "requested color conversion not supported"; + case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; + case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; + /*LodePNG leaves the choice of RGB to greyscale conversion formula to the user.*/ + case 62: return "conversion from color to greyscale not supported"; + case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; /*(2^31-1)*/ + /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ + case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; + case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; + case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; + case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; + case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; + case 71: return "unexisting interlace mode given to encoder (must be 0 or 1)"; + case 72: return "while decoding, unexisting compression method encountering in zTXt or iTXt chunk (it must be 0)"; + case 73: return "invalid tIME chunk size"; + case 74: return "invalid pHYs chunk size"; + /*length could be wrong, or data chopped off*/ + case 75: return "no null termination char found while decoding text chunk"; + case 76: return "iTXt chunk too short to contain required bytes"; + case 77: return "integer overflow in buffer size"; + case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ + case 79: return "failed to open file for writing"; + case 80: return "tried creating a tree of 0 symbols"; + case 81: return "lazy matching at pos 0 is impossible"; + case 82: return "color conversion to palette requested while a color isn't in palette"; + case 83: return "memory allocation failed"; + case 84: return "given image too small to contain all pixels to be encoded"; + case 85: return "internal color conversion bug"; + case 86: return "impossible offset in lz77 encoding (internal bug)"; + case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined"; + case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy"; + case 89: return "text chunk keyword too short or long: must have size 1-79"; + case 90: return "failed to seek in the file"; + case 91: return "failed to find the current position of the file"; + } + return "unknown error code"; +} +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // C++ Wrapper // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng +{ + +#ifdef LODEPNG_COMPILE_DISK +void load_file(std::vector& buffer, const std::string& filename) +{ + std::ifstream file(filename.c_str(), std::ios::in|std::ios::binary|std::ios::ate); + + /*get filesize*/ + std::streamsize size = 0; + if(file.seekg(0, std::ios::end).good()) size = file.tellg(); + if(file.seekg(0, std::ios::beg).good()) size -= file.tellg(); + + /*read contents of the file into the vector*/ + buffer.resize(size_t(size)); + if(size > 0) file.read((char*)(&buffer[0]), size); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +void save_file(const std::vector& buffer, const std::string& filename) +{ + std::ofstream file(filename.c_str(), std::ios::out|std::ios::binary); + // !!! Ren/C: Modified to use a 'const char*' buffer + file.write(buffer.empty() ? 0 : (const char*)&buffer[0], std::streamsize(buffer.size())); +} +#endif //LODEPNG_COMPILE_DISK + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings) +{ + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_decompress(&buffer, &buffersize, in, insize, &settings); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings) +{ + return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif //LODEPNG_COMPILE_DECODER + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings) +{ + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings) +{ + return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif //LODEPNG_COMPILE_ENCODER +#endif //LODEPNG_COMPILE_ZLIB + + +#ifdef LODEPNG_COMPILE_PNG + +State::State() +{ + lodepng_state_init(this); +} + +State::State(const State& other) +{ + lodepng_state_init(this); + lodepng_state_copy(this, &other); +} + +State::~State() +{ + lodepng_state_cleanup(this); +} + +State& State::operator=(const State& other) +{ + lodepng_state_copy(this, &other); + return *this; +} + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); + if(buffer && !error) + { + State state; + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, LodePNGColorType colortype, unsigned bitdepth) +{ + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize) +{ + unsigned char* buffer; + unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); + if(buffer && !error) + { + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in) +{ + return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, + LodePNGColorType colortype, unsigned bitdepth) +{ + std::vector buffer; + load_file(buffer, filename); + return decode(out, w, h, buffer, colortype, bitdepth); +} +#endif //LODEPNG_COMPILE_DECODER +#endif //LODEPNG_COMPILE_DISK + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} + +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state) +{ + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); + if(buffer) + { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state) +{ + if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, state); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + std::vector buffer; + unsigned error = encode(buffer, in, w, h, colortype, bitdepth); + if(!error) save_file(buffer, filename); + return error; +} + +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) +{ + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} +#endif //LODEPNG_COMPILE_DISK +#endif //LODEPNG_COMPILE_ENCODER +#endif //LODEPNG_COMPILE_PNG +} //namespace lodepng +#endif /*LODEPNG_COMPILE_CPP*/ diff --git a/src/extensions/png/lodepng.h b/src/extensions/png/lodepng.h new file mode 100644 index 0000000000..8122aa2ed9 --- /dev/null +++ b/src/extensions/png/lodepng.h @@ -0,0 +1,1692 @@ +/* +LodePNG version 20130311 + +Copyright (c) 2005-2013 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef LODEPNG_H +#define LODEPNG_H + +#include /*for size_t*/ + +#ifdef __cplusplus +#include +#include +#endif /*__cplusplus*/ + +/* +The following #defines are used to create code sections. They can be disabled +to disable code sections, which can give faster compile time and smaller binary. +The "NO_COMPILE" defines are designed to be used to pass as defines to the +compiler command to disable them without modifying this header, e.g. +-DLODEPNG_NO_COMPILE_ZLIB for gcc. +*/ +/*deflate & zlib. If disabled, you must specify alternative zlib functions in +the custom_zlib field of the compress and decompress settings*/ +#ifndef LODEPNG_NO_COMPILE_ZLIB +#define LODEPNG_COMPILE_ZLIB +#endif +/*png encoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_PNG +#define LODEPNG_COMPILE_PNG +#endif +/*deflate&zlib decoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_DECODER +#define LODEPNG_COMPILE_DECODER +#endif +/*deflate&zlib encoder and png encoder*/ +#ifndef LODEPNG_NO_COMPILE_ENCODER +#define LODEPNG_COMPILE_ENCODER +#endif +/*the optional built in harddisk file loading and saving functions*/ +#ifndef LODEPNG_NO_COMPILE_DISK +#define LODEPNG_COMPILE_DISK +#endif +/*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/ +#ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +#define LODEPNG_COMPILE_ANCILLARY_CHUNKS +#endif +/*ability to convert error numerical codes to English text string*/ +#ifndef LODEPNG_NO_COMPILE_ERROR_TEXT +#define LODEPNG_COMPILE_ERROR_TEXT +#endif +/*Compile the default allocators (C's free, malloc and realloc). If you disable this, +you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your +source files with custom allocators.*/ +#ifndef LODEPNG_NO_COMPILE_ALLOCATORS +#define LODEPNG_COMPILE_ALLOCATORS +#endif +/*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/ +#ifdef __cplusplus +#ifndef LODEPNG_NO_COMPILE_CPP +#define LODEPNG_COMPILE_CPP +#endif +#endif + +#ifdef LODEPNG_COMPILE_PNG +/*The PNG color types (also used for raw).*/ +typedef enum LodePNGColorType +{ + LCT_GREY = 0, /*greyscale: 1,2,4,8,16 bit*/ + LCT_RGB = 2, /*RGB: 8,16 bit*/ + LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ + LCT_GREY_ALPHA = 4, /*greyscale with alpha: 8,16 bit*/ + LCT_RGBA = 6 /*RGB with alpha: 8,16 bit*/ +} LodePNGColorType; + +#ifdef LODEPNG_COMPILE_DECODER +/* +Converts PNG data in memory to raw pixel data. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + After decoding, its size is w * h * (bytes per pixel) bytes larger than + initially. Bytes per pixel depends on colortype and bitdepth. + Must be freed after usage with free(*out). + Note: for 16-bit per channel colors, uses big endian format like PNG does. +w: Output parameter. Pointer to width of pixel data. +h: Output parameter. Pointer to height of pixel data. +in: Memory buffer with the PNG file. +insize: size of the in buffer. +colortype: the desired color type for the raw output image. See explanation on PNG color types. +bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/ +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +/*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/ +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_DISK +/* +Load PNG from disk, from file with given name. +Same as the other decode functions, but instead takes a filename as input. +*/ +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image.*/ +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); + +/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image.*/ +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Converts raw pixel data into a PNG image in memory. The colortype and bitdepth + of the output PNG image cannot be chosen, they are automatically determined + by the colortype, bitdepth and content of the input pixel data. + Note: for 16-bit per channel colors, needs big endian format like PNG does. +out: Output parameter. Pointer to buffer that will contain the PNG image data. + Must be freed after usage with free(*out). +outsize: Output parameter. Pointer to the size in bytes of the out buffer. +image: The raw pixel data to encode. The size of this buffer should be + w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth. +w: width of the raw pixel data in pixels. +h: height of the raw pixel data in pixels. +colortype: the color type of the raw input image. See explanation on PNG color types. +bitdepth: the bit depth of the raw input image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DISK +/* +Converts raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned lodepng_encode_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng +{ +#ifdef LODEPNG_COMPILE_DECODER +/*Same as lodepng_decode_memory, but decodes to an std::vector.*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts PNG file from disk to raw pixel data in memory. +Same as the other decode functions, but instead takes a filename as input. +*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::string& filename, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif //LODEPNG_COMPILE_DISK +#endif //LODEPNG_COMPILE_DECODER + +#ifdef LODEPNG_COMPILE_ENCODER +/*Same as lodepng_encode_memory, but encodes to an std::vector.*/ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts 32-bit RGBA raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif //LODEPNG_COMPILE_DISK +#endif //LODEPNG_COMPILE_ENCODER +} //namespace lodepng +#endif /*LODEPNG_COMPILE_CPP*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/*Returns an English description of the numerical error code.*/ +const char* lodepng_error_text(unsigned code); +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Settings for zlib decompression*/ +typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; +struct LodePNGDecompressSettings +{ + unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ + + /*use custom zlib decoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + /*use custom deflate decoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_inflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + + void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGDecompressSettings lodepng_default_decompress_settings; +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Settings for zlib compression. Tweaking these settings tweaks the balance +between speed and compression ratio. +*/ +typedef struct LodePNGCompressSettings LodePNGCompressSettings; +struct LodePNGCompressSettings /*deflate = compress*/ +{ + /*LZ77 related settings*/ + unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ + unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ + unsigned windowsize; /*the maximum is 32768, higher gives more compression but is slower. Typical value: 2048.*/ + unsigned minmatch; /*mininum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/ + unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/ + unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/ + + /*use custom zlib encoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + /*use custom deflate encoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_deflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + + void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGCompressSettings lodepng_default_compress_settings; +void lodepng_compress_settings_init(LodePNGCompressSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_PNG +/* +Color mode of an image. Contains all information required to decode the pixel +bits to RGBA colors. This information is the same as used in the PNG file +format, and is used both for PNG and raw image data in LodePNG. +*/ +typedef struct LodePNGColorMode +{ + /*header (IHDR)*/ + LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ + unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ + + /* + palette (PLTE and tRNS) + + Dynamically allocated with the colors of the palette, including alpha. + When encoding a PNG, to store your colors in the palette of the LodePNGColorMode, first use + lodepng_palette_clear, then for each color use lodepng_palette_add. + If you encode an image without alpha with palette, don't forget to put value 255 in each A byte of the palette. + + When decoding, by default you can ignore this palette, since LodePNG already + fills the palette colors in the pixels of the raw RGBA output. + + The palette is only supported for color type 3. + */ + unsigned char* palette; /*palette in RGBARGBA... order*/ + size_t palettesize; /*palette size in number of colors (amount of bytes is 4 * palettesize)*/ + + /* + transparent color key (tRNS) + + This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. + For greyscale PNGs, r, g and b will all 3 be set to the same. + + When decoding, by default you can ignore this information, since LodePNG sets + pixels with this key to transparent already in the raw RGBA output. + + The color key is only supported for color types 0 and 2. + */ + unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ + unsigned key_r; /*red/greyscale component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +} LodePNGColorMode; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_color_mode_init(LodePNGColorMode* info); +void lodepng_color_mode_cleanup(LodePNGColorMode* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source); + +void lodepng_palette_clear(LodePNGColorMode* info); +/*add 1 color to the palette*/ +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +/*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info); +/*get the amount of color channels used, based on colortype in the struct. +If a palette is used, it counts as 1 channel.*/ +unsigned lodepng_get_channels(const LodePNGColorMode* info); +/*is it a greyscale type? (only colortype 0 or 4)*/ +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info); +/*has it got an alpha channel? (only colortype 2 or 6)*/ +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info); +/*has it got a palette? (only colortype 3)*/ +unsigned lodepng_is_palette_type(const LodePNGColorMode* info); +/*only returns true if there is a palette and there is a value in the palette with alpha < 255. +Loops through the palette to check this.*/ +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info); +/* +Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. +Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). +Returns false if the image can only have opaque pixels. +In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, +or if "key_defined" is true. +*/ +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info); +/*Returns the byte size of a raw image buffer with given width, height and color mode*/ +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*The information of a Time chunk in PNG.*/ +typedef struct LodePNGTime +{ + unsigned year; /*2 bytes used (0-65535)*/ + unsigned month; /*1-12*/ + unsigned day; /*1-31*/ + unsigned hour; /*0-23*/ + unsigned minute; /*0-59*/ + unsigned second; /*0-60 (to allow for leap seconds)*/ +} LodePNGTime; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Information about the PNG image, except pixels, width and height.*/ +typedef struct LodePNGInfo +{ + /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ + unsigned compression_method;/*compression method of the original file. Always 0.*/ + unsigned filter_method; /*filter method of the original file*/ + unsigned interlace_method; /*interlace method of the original file*/ + LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /* + suggested background color chunk (bKGD) + This color uses the same color mode as the PNG (except alpha channel), which can be 1-bit to 16-bit. + + For greyscale PNGs, r, g and b will all 3 be set to the same. When encoding + the encoder writes the red one. For palette PNGs: When decoding, the RGB value + will be stored, not a palette index. But when encoding, specify the index of + the palette in background_r, the other two are then ignored. + + The decoder does not use this background color to edit the color of pixels. + */ + unsigned background_defined; /*is a suggested background color given?*/ + unsigned background_r; /*red component of suggested background color*/ + unsigned background_g; /*green component of suggested background color*/ + unsigned background_b; /*blue component of suggested background color*/ + + /* + non-international text chunks (tEXt and zTXt) + + The char** arrays each contain num strings. The actual messages are in + text_strings, while text_keys are keywords that give a short description what + the actual text represents, e.g. Title, Author, Description, or anything else. + + A keyword is minimum 1 character and maximum 79 characters long. It's + discouraged to use a single line length longer than 79 characters for texts. + + Don't allocate these text buffers yourself. Use the init/cleanup functions + correctly and use lodepng_add_text and lodepng_clear_text. + */ + size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ + char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/ + char** text_strings; /*the actual text*/ + + /* + international text chunks (iTXt) + Similar to the non-international text chunks, but with additional strings + "langtags" and "transkeys". + */ + size_t itext_num; /*the amount of international texts in this PNG*/ + char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/ + char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ + char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/ + char** itext_strings; /*the actual international text - UTF-8 string*/ + + /*time chunk (tIME)*/ + unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/ + LodePNGTime time; + + /*phys chunk (pHYs)*/ + unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ + unsigned phys_x; /*pixels per unit in x direction*/ + unsigned phys_y; /*pixels per unit in y direction*/ + unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ + + /* + unknown chunks + There are 3 buffers, one for each position in the PNG where unknown chunks can appear + each buffer contains all unknown chunks for that position consecutively + The 3 buffers are the unknown chunks between certain critical chunks: + 0: IHDR-PLTE, 1: PLTE-IDAT, 2: IDAT-IEND + Do not allocate or traverse this data yourself. Use the chunk traversing functions declared + later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct. + */ + unsigned char* unknown_chunks_data[3]; + size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGInfo; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_info_init(LodePNGInfo* info); +void lodepng_info_cleanup(LodePNGInfo* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/ + +void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/ +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/* +Converts raw buffer from one color type to another color type, based on +LodePNGColorMode structs to describe the input and output color type. +See the reference manual at the end of this header file to see which color conversions are supported. +return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) +The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel +of the output color type (lodepng_get_bpp) +Note: for 16-bit per channel colors, uses big endian format like PNG does. +*/ +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + LodePNGColorMode* mode_out, LodePNGColorMode* mode_in, + unsigned w, unsigned h); + + +#ifdef LODEPNG_COMPILE_DECODER +/* +Settings for the decoder. This contains settings for the PNG and the Zlib +decoder, but not the Info settings from the Info structs. +*/ +typedef struct LodePNGDecoderSettings +{ + LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + unsigned ignore_crc; /*ignore CRC checksums*/ + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/ + /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/ + unsigned remember_unknown_chunks; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGDecoderSettings; + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/ +typedef enum LodePNGFilterStrategy +{ + /*every filter at zero*/ + LFS_ZERO, + /*Use filter that gives minumum sum, as described in the official PNG filter heuristic.*/ + LFS_MINSUM, + /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending + on the image, this is better or worse than minsum.*/ + LFS_ENTROPY, + /* + Brute-force-search PNG filters by compressing each filter for each scanline. + Experimental, very slow, and only rarely gives better compression than MINSUM. + */ + LFS_BRUTE_FORCE, + /*use predefined_filters buffer: you specify the filter type for each scanline*/ + LFS_PREDEFINED +} LodePNGFilterStrategy; + +/*automatically use color type with less bits per pixel if losslessly possible. Default: LAC_AUTO*/ +typedef enum LodePNGAutoConvert +{ + LAC_NO, /*use color type user requested*/ + LAC_ALPHA, /*use color type user requested, but if only opaque pixels and RGBA or grey+alpha, use RGB or grey*/ + LAC_AUTO, /*use PNG color type that can losslessly represent the uncompressed image the smallest possible*/ + /* + like AUTO, but do not choose 1, 2 or 4 bit per pixel types. + sometimes a PNG image compresses worse if less than 8 bits per pixels. + */ + LAC_AUTO_NO_NIBBLES, + /* + like AUTO, but never choose palette color type. For small images, encoding + the palette may take more bytes than what is gained. Note that AUTO also + already prevents encoding the palette for extremely small images, but that may + not be sufficient because due to the compression it cannot predict when to + switch. + */ + LAC_AUTO_NO_PALETTE, + LAC_AUTO_NO_NIBBLES_NO_PALETTE +} LodePNGAutoConvert; + + +/*Settings for the encoder.*/ +typedef struct LodePNGEncoderSettings +{ + LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ + + LodePNGAutoConvert auto_convert; /*how to automatically choose output PNG color type, if at all*/ + + /*If true, follows the official PNG heuristic: if the PNG uses a palette or lower than + 8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to + completely follow the official PNG heuristic, filter_palette_zero must be true and + filter_strategy must be LFS_MINSUM*/ + unsigned filter_palette_zero; + /*Which filter strategy to use when not using zeroes due to filter_palette_zero. + Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/ + LodePNGFilterStrategy filter_strategy; + /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with + the same length as the amount of scanlines in the image, and each value must <= 5. You + have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero + must be set to 0 to ensure this is also used on palette or low bitdepth images.*/ + unsigned char* predefined_filters; + + /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). + If colortype is 3, PLTE is _always_ created.*/ + unsigned force_palette; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*add LodePNG identifier and version as a text chunk, for debugging*/ + unsigned add_id; + /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ + unsigned text_compression; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGEncoderSettings; + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) +/*The settings, state and information for extended encoding and decoding.*/ +typedef struct LodePNGState +{ +#ifdef LODEPNG_COMPILE_DECODER + LodePNGDecoderSettings decoder; /*the decoding settings*/ +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + LodePNGEncoderSettings encoder; /*the encoding settings*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ + LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ + unsigned error; +#ifdef LODEPNG_COMPILE_CPP + //For the lodepng::State subclass. + virtual ~LodePNGState(){} +#endif +} LodePNGState; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_state_init(LodePNGState* state); +void lodepng_state_cleanup(LodePNGState* state); +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source); +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_DECODER +/* +Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and +getting much more information about the PNG image and color mode. +*/ +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); + +/* +Read the PNG header, but not the actual data. This returns only the information +that is in the header chunk of the PNG, such as width, height and color type. The +information is placed in the info_png field of the LodePNGState. +*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* +The lodepng_chunk functions are normally not needed, except to traverse the +unknown chunks stored in the LodePNGInfo struct, or add new ones to it. +It also allows traversing the chunks of an encoded PNG file yourself. + +PNG standard chunk naming conventions: +First byte: uppercase = critical, lowercase = ancillary +Second byte: uppercase = public, lowercase = private +Third byte: must be uppercase +Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy +*/ + +/*get the length of the data of the chunk. Total chunk length has 12 bytes more.*/ +unsigned lodepng_chunk_length(const unsigned char* chunk); + +/*puts the 4-byte type in null terminated string*/ +void lodepng_chunk_type(char type[5], const unsigned char* chunk); + +/*check if the type is the given type*/ +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type); + +/*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/ +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk); + +/*0: public, 1: private (see PNG standard)*/ +unsigned char lodepng_chunk_private(const unsigned char* chunk); + +/*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/ +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk); + +/*get pointer to the data of the chunk, where the input points to the header of the chunk*/ +unsigned char* lodepng_chunk_data(unsigned char* chunk); +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk); + +/*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/ +unsigned lodepng_chunk_check_crc(const unsigned char* chunk); + +/*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ +void lodepng_chunk_generate_crc(unsigned char* chunk); + +/*iterate to next chunks. don't use on IEND chunk, as there is no next chunk then*/ +unsigned char* lodepng_chunk_next(unsigned char* chunk); +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk); + +/* +Appends chunk to the data in out. The given chunk should already have its chunk header. +The out variable and outlength are updated to reflect the new reallocated buffer. +Returns error code (0 if it went ok) +*/ +unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk); + +/* +Appends new chunk to out. The chunk to append is given by giving its length, type +and data separately. The type is a 4-letter string. +The out variable and outlength are updated to reflect the new reallocated buffer. +Returne error code (0 if it went ok) +*/ +unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data); + + +/*Calculate CRC32 of buffer*/ +unsigned lodepng_crc32(const unsigned char* buf, size_t len); +#endif /*LODEPNG_COMPILE_PNG*/ + + +#ifdef LODEPNG_COMPILE_ZLIB +/* +This zlib part can be used independently to zlib compress and decompress a +buffer. It cannot be used to create gzip files however, and it only supports the +part of zlib that is required for PNG, it does not support dictionaries. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/ +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); + +/* +Decompresses Zlib data. Reallocates the out buffer and appends the data. The +data must be according to the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Compresses data with Zlib. Reallocates the out buffer and appends the data. +Zlib adds a small header and trailer around the deflate data. +The data is output in the format of the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +/* +Find length-limited Huffman code for given frequencies. This function is in the +public interface only for tests, it's used internally by lodepng_deflate. +*/ +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen); + +/*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/ +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into buffer. The function allocates the out buffer, and +after usage you should free it. +out: output parameter, contains pointer to loaded buffer. +outsize: output parameter, size of the allocated out buffer +filename: the path to the file to load +return value: error code (0 means ok) +*/ +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename); + +/* +Save a file from buffer to disk. Warning, if it exists, this function overwrites +the file without warning! +buffer: the buffer to write +buffersize: size of the buffer to write +filename: the path to the file to save to +return value: error code (0 means ok) +*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef LODEPNG_COMPILE_CPP +//The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. +namespace lodepng +{ +#ifdef LODEPNG_COMPILE_PNG +class State : public LodePNGState +{ + public: + State(); + State(const State& other); + virtual ~State(); + State& operator=(const State& other); +}; + +#ifdef LODEPNG_COMPILE_DECODER +//Same as other lodepng::decode, but using a State for more settings and information. +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +//Same as other lodepng::encode, but using a State for more settings and information. +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into an std::vector. If the vector is empty, then either +the file doesn't exist or is an empty file. +*/ +void load_file(std::vector& buffer, const std::string& filename); + +/* +Save the binary data in an std::vector to a file on disk. The file is overwritten +without warning. +*/ +void save_file(const std::vector& buffer, const std::string& filename); +#endif //LODEPNG_COMPILE_DISK +#endif //LODEPNG_COMPILE_PNG + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +//Zlib-decompress an unsigned char buffer +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); + +//Zlib-decompress an std::vector +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); +#endif //LODEPNG_COMPILE_DECODER + +#ifdef LODEPNG_COMPILE_ENCODER +//Zlib-compress an unsigned char buffer +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); + +//Zlib-compress an std::vector +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); +#endif //LODEPNG_COMPILE_ENCODER +#endif //LODEPNG_COMPILE_ZLIB +} //namespace lodepng +#endif /*LODEPNG_COMPILE_CPP*/ + +/* +TODO: +[.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often +[.] check compatibility with vareous compilers - done but needs to be redone for every newer version +[X] converting color to 16-bit per channel types +[ ] read all public PNG chunk types (but never let the color profile and gamma ones touch RGB values) +[ ] make sure encoder generates no chunks with size > (2^31)-1 +[ ] partial decoding (stream processing) +[X] let the "isFullyOpaque" function check color keys and transparent palettes too +[X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" +[ ] don't stop decoding on errors like 69, 57, 58 (make warnings) +[ ] make option to choose if the raw image with non multiple of 8 bits per scanline should have padding bits or not +[ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes +*/ + +#endif /*LODEPNG_H inclusion guard*/ + +/* +LodePNG Documentation +--------------------- + +0. table of contents +-------------------- + + 1. about + 1.1. supported features + 1.2. features not supported + 2. C and C++ version + 3. security + 4. decoding + 5. encoding + 6. color conversions + 6.1. PNG color types + 6.2. color conversions + 6.3. padding bits + 6.4. A note about 16-bits per channel and endianness + 7. error values + 8. chunks and PNG editing + 9. compiler support + 10. examples + 10.1. decoder C++ example + 10.2. decoder C example + 11. changes + 12. contact information + + +1. about +-------- + +PNG is a file format to store raster images losslessly with good compression, +supporting different color types and alpha channel. + +LodePNG is a PNG codec according to the Portable Network Graphics (PNG) +Specification (Second Edition) - W3C Recommendation 10 November 2003. + +The specifications used are: + +*) Portable Network Graphics (PNG) Specification (Second Edition): + http://www.w3.org/TR/2003/REC-PNG-20031110 +*) RFC 1950 ZLIB Compressed Data Format version 3.3: + http://www.gzip.org/zlib/rfc-zlib.html +*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: + http://www.gzip.org/zlib/rfc-deflate.html + +The most recent version of LodePNG can currently be found at +http://lodev.org/lodepng/ + +LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds +extra functionality. + +LodePNG exists out of two files: +-lodepng.h: the header file for both C and C++ +-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage + +If you want to start using LodePNG right away without reading this doc, get the +examples from the LodePNG website to see how to use it in code, or check the +smaller examples in chapter 13 here. + +LodePNG is simple but only supports the basic requirements. To achieve +simplicity, the following design choices were made: There are no dependencies +on any external library. There are functions to decode and encode a PNG with +a single function call, and extended versions of these functions taking a +LodePNGState struct allowing to specify or get more information. By default +the colors of the raw image are always RGB or RGBA, no matter what color type +the PNG file uses. To read and write files, there are simple functions to +convert the files to/from buffers in memory. + +This all makes LodePNG suitable for loading textures in games, demos and small +programs, ... It's less suitable for full fledged image editors, loading PNGs +over network (it requires all the image data to be available before decoding can +begin), life-critical systems, ... + +1.1. supported features +----------------------- + +The following features are supported by the decoder: + +*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, + or the same color type as the PNG +*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image +*) Adam7 interlace and deinterlace for any color type +*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk +*) support for alpha channels, including RGBA color model, translucent palettes and color keying +*) zlib decompression (inflate) +*) zlib compression (deflate) +*) CRC32 and ADLER32 checksums +*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. +*) the following chunks are supported (generated/interpreted) by both encoder and decoder: + IHDR: header information + PLTE: color palette + IDAT: pixel data + IEND: the final chunk + tRNS: transparency for palettized images + tEXt: textual information + zTXt: compressed textual information + iTXt: international textual information + bKGD: suggested background color + pHYs: physical dimensions + tIME: modification time + +1.2. features not supported +--------------------------- + +The following features are _not_ supported: + +*) some features needed to make a conformant PNG-Editor might be still missing. +*) partial loading/stream processing. All data must be available and is processed in one call. +*) The following public chunks are not supported but treated as unknown chunks by LodePNG + cHRM, gAMA, iCCP, sRGB, sBIT, hIST, sPLT + Some of these are not supported on purpose: LodePNG wants to provide the RGB values + stored in the pixels, not values modified by system dependent gamma or color models. + + +2. C and C++ version +-------------------- + +The C version uses buffers allocated with alloc that you need to free() +yourself. You need to use init and cleanup functions for each struct whenever +using a struct from the C version to avoid exploits and memory leaks. + +The C++ version has extra functions with std::vectors in the interface and the +lodepng::State class which is a LodePNGState with constructor and destructor. + +These files work without modification for both C and C++ compilers because all +the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers +ignore it, and the C code is made to compile both with strict ISO C90 and C++. + +To use the C++ version, you need to rename the source file to lodepng.cpp +(instead of lodepng.c), and compile it with a C++ compiler. + +To use the C version, you need to rename the source file to lodepng.c (instead +of lodepng.cpp), and compile it with a C compiler. + + +3. Security +----------- + +Even if carefully designed, it's always possible that LodePNG contains possible +exploits. If you discover one, please let me know, and it will be fixed. + +When using LodePNG, care has to be taken with the C version of LodePNG, as well +as the C-style structs when working with C++. The following conventions are used +for all C-style structs: + +-if a struct has a corresponding init function, always call the init function when making a new one +-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks +-if a struct has a corresponding copy function, use the copy function instead of "=". + The destination must also be inited already. + + +4. Decoding +----------- + +Decoding converts a PNG compressed image to a raw pixel buffer. + +Most documentation on using the decoder is at its declarations in the header +above. For C, simple decoding can be done with functions such as +lodepng_decode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_decode. For C++, all decoding can be done with the +various lodepng::decode functions, and lodepng::State can be used for advanced +features. + +When using the LodePNGState, it uses the following fields for decoding: +*) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here +*) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get +*) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use + +LodePNGInfo info_png +-------------------- + +After decoding, this contains extra information of the PNG image, except the actual +pixels, width and height because these are already gotten directly from the decoder +functions. + +It contains for example the original color type of the PNG image, text comments, +suggested background color, etc... More details about the LodePNGInfo struct are +at its declaration documentation. + +LodePNGColorMode info_raw +------------------------- + +When decoding, here you can specify which color type you want +the resulting raw image to be. If this is different from the colortype of the +PNG, then the decoder will automatically convert the result. This conversion +always works, except if you want it to convert a color PNG to greyscale or to +a palette with missing colors. + +By default, 32-bit color is used for the result. + +LodePNGDecoderSettings decoder +------------------------------ + +The settings can be used to ignore the errors created by invalid CRC and Adler32 +chunks, and to disable the decoding of tEXt chunks. + +There's also a setting color_convert, true by default. If false, no conversion +is done, the resulting data will be as it was in the PNG (after decompression) +and you'll have to puzzle the colors of the pixels together yourself using the +color type information in the LodePNGInfo. + + +5. Encoding +----------- + +Encoding converts a raw pixel buffer to a PNG compressed image. + +Most documentation on using the encoder is at its declarations in the header +above. For C, simple encoding can be done with functions such as +lodepng_encode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_encode. For C++, all encoding can be done with the +various lodepng::encode functions, and lodepng::State can be used for advanced +features. + +Like the decoder, the encoder can also give errors. However it gives less errors +since the encoder input is trusted, the decoder input (a PNG image that could +be forged by anyone) is not trusted. + +When using the LodePNGState, it uses the following fields for encoding: +*) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be. +*) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has +*) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use + +LodePNGInfo info_png +-------------------- + +When encoding, you use this the opposite way as when decoding: for encoding, +you fill in the values you want the PNG to have before encoding. By default it's +not needed to specify a color type for the PNG since it's automatically chosen, +but it's possible to choose it yourself given the right settings. + +The encoder will not always exactly match the LodePNGInfo struct you give, +it tries as close as possible. Some things are ignored by the encoder. The +encoder uses, for example, the following settings from it when applicable: +colortype and bitdepth, text chunks, time chunk, the color key, the palette, the +background color, the interlace method, unknown chunks, ... + +When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk. +If the palette contains any colors for which the alpha channel is not 255 (so +there are translucent colors in the palette), it'll add a tRNS chunk. + +LodePNGColorMode info_raw +------------------------- + +You specify the color type of the raw image that you give to the input here, +including a possible transparent color key and palette you happen to be using in +your raw image data. + +By default, 32-bit color is assumed, meaning your input has to be in RGBA +format with 4 bytes (unsigned chars) per pixel. + +LodePNGEncoderSettings encoder +------------------------------ + +The following settings are supported (some are in sub-structs): +*) auto_convert: when this option is enabled, the encoder will +automatically choose the smallest possible color mode (including color key) that +can encode the colors of all pixels without information loss. +*) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, + 2 = dynamic huffman tree (best compression). Should be 2 for proper + compression. +*) use_lz77: whether or not to use LZ77 for compressed block types. Should be + true for proper compression. +*) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value + 2048 by default, but can be set to 32768 for better, but slow, compression. +*) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE + chunk if force_palette is true. This can used as suggested palette to convert + to by viewers that don't support more than 256 colors (if those still exist) +*) add_id: add text chunk "Encoder: LodePNG " to the image. +*) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks. + zTXt chunks use zlib compression on the text. This gives a smaller result on + large texts but a larger result on small texts (such as a single program name). + It's all tEXt or all zTXt though, there's no separate setting per text yet. + + +6. color conversions +-------------------- + +An important thing to note about LodePNG, is that the color type of the PNG, and +the color type of the raw image, are completely independent. By default, when +you decode a PNG, you get the result as a raw image in the color type you want, +no matter whether the PNG was encoded with a palette, greyscale or RGBA color. +And if you encode an image, by default LodePNG will automatically choose the PNG +color type that gives good compression based on the values of colors and amount +of colors in the image. It can be configured to let you control it instead as +well, though. + +To be able to do this, LodePNG does conversions from one color mode to another. +It can convert from almost any color type to any other color type, except the +following conversions: RGB to greyscale is not supported, and converting to a +palette when the palette doesn't have a required color is not supported. This is +not supported on purpose: this is information loss which requires a color +reduction algorithm that is beyong the scope of a PNG encoder (yes, RGB to grey +is easy, but there are multiple ways if you want to give some channels more +weight). + +By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB +color, no matter what color type the PNG has. And by default when encoding, +LodePNG automatically picks the best color model for the output PNG, and expects +the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control +the color format of the images yourself, you can skip this chapter. + +6.1. PNG color types +-------------------- + +A PNG image can have many color types, ranging from 1-bit color to 64-bit color, +as well as palettized color modes. After the zlib decompression and unfiltering +in the PNG image is done, the raw pixel data will have that color type and thus +a certain amount of bits per pixel. If you want the output raw image after +decoding to have another color type, a conversion is done by LodePNG. + +The PNG specification gives the following color types: + +0: greyscale, bit depths 1, 2, 4, 8, 16 +2: RGB, bit depths 8 and 16 +3: palette, bit depths 1, 2, 4 and 8 +4: greyscale with alpha, bit depths 8 and 16 +6: RGBA, bit depths 8 and 16 + +Bit depth is the amount of bits per pixel per color channel. So the total amount +of bits per pixel is: amount of channels * bitdepth. + +6.2. color conversions +---------------------- + +As explained in the sections about the encoder and decoder, you can specify +color types and bit depths in info_png and info_raw to change the default +behaviour. + +If, when decoding, you want the raw image to be something else than the default, +you need to set the color type and bit depth you want in the LodePNGColorMode, +or the parameters of the simple function of LodePNG you're using. + +If, when encoding, you use another color type than the default in the input +image, you need to specify its color type and bit depth in the LodePNGColorMode +of the raw image, or use the parameters of the simplefunction of LodePNG you're +using. + +If, when encoding, you don't want LodePNG to choose the output PNG color type +but control it yourself, you need to set auto_convert in the encoder settings +to LAC_NONE, and specify the color type you want in the LodePNGInfo of the +encoder. + +If you do any of the above, LodePNG may need to do a color conversion, which +follows the rules below, and may sometimes not be allowed. + +To avoid some confusion: +-the decoder converts from PNG to raw image +-the encoder converts from raw image to PNG +-the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image +-the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG +-when encoding, the color type in LodePNGInfo is ignored if auto_convert + is enabled, it is automatically generated instead +-when decoding, the color type in LodePNGInfo is set by the decoder to that of the original + PNG image, but it can be ignored since the raw image has the color type you requested instead +-if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion + between the color types is done if the color types are supported. If it is not + supported, an error is returned. If the types are the same, no conversion is done. +-even though some conversions aren't supported, LodePNG supports loading PNGs from any + colortype and saving PNGs to any colortype, sometimes it just requires preparing + the raw image correctly before encoding. +-both encoder and decoder use the same color converter. + +Non supported color conversions: +-color to greyscale: no error is thrown, but the result will look ugly because +only the red channel is taken +-anything, to palette when that palette does not have that color in it: in this +case an error is thrown + +Supported color conversions: +-anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA +-any grey or grey+alpha, to grey or grey+alpha +-anything to a palette, as long as the palette has the requested colors in it +-removing alpha channel +-higher to smaller bitdepth, and vice versa + +If you want no color conversion to be done: +-In the encoder, you can make it save a PNG with any color type by giving the +raw color mode and LodePNGInfo the same color mode, and setting auto_convert to +LAC_NO. +-In the decoder, you can make it store the pixel data in the same color type +as the PNG has, by setting the color_convert setting to false. Settings in +info_raw are then ignored. + +The function lodepng_convert does the color conversion. It is available in the +interface but normally isn't needed since the encoder and decoder already call +it. + +6.3. padding bits +----------------- + +In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines +have a bit amount that isn't a multiple of 8, then padding bits are used so that each +scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output. +The raw input image you give to the encoder, and the raw output image you get from the decoder +will NOT have these padding bits, e.g. in the case of a 1-bit image with a width +of 7 pixels, the first pixel of the second scanline will the the 8th bit of the first byte, +not the first bit of a new byte. + +6.4. A note about 16-bits per channel and endianness +---------------------------------------------------- + +LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like +for any other color format. The 16-bit values are stored in big endian (most +significant byte first) in these arrays. This is the opposite order of the +little endian used by x86 CPU's. + +LodePNG always uses big endian because the PNG file format does so internally. +Conversions to other formats than PNG uses internally are not supported by +LodePNG on purpose, there are myriads of formats, including endianness of 16-bit +colors, the order in which you store R, G, B and A, and so on. Supporting and +converting to/from all that is outside the scope of LodePNG. + +This may mean that, depending on your use case, you may want to convert the big +endian output of LodePNG to little endian with a for loop. This is certainly not +always needed, many applications and libraries support big endian 16-bit colors +anyway, but it means you cannot simply cast the unsigned char* buffer to an +unsigned short* buffer on x86 CPUs. + + +7. error values +--------------- + +All functions in LodePNG that return an error code, return 0 if everything went +OK, or a non-zero code if there was an error. + +The meaning of the LodePNG error values can be retrieved with the function +lodepng_error_text: given the numerical error code, it returns a description +of the error in English as a string. + +Check the implementation of lodepng_error_text to see the meaning of each code. + + +8. chunks and PNG editing +------------------------- + +If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG +editor that should follow the rules about handling of unknown chunks, or if your +program is able to read other types of chunks than the ones handled by LodePNG, +then that's possible with the chunk functions of LodePNG. + +A PNG chunk has the following layout: + +4 bytes length +4 bytes type name +length bytes data +4 bytes CRC + +8.1. iterating through chunks +----------------------------- + +If you have a buffer containing the PNG image data, then the first chunk (the +IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the +signature of the PNG and are not part of a chunk. But if you start at byte 8 +then you have a chunk, and can check the following things of it. + +NOTE: none of these functions check for memory buffer boundaries. To avoid +exploits, always make sure the buffer contains all the data of the chunks. +When using lodepng_chunk_next, make sure the returned value is within the +allocated memory. + +unsigned lodepng_chunk_length(const unsigned char* chunk): + +Get the length of the chunk's data. The total chunk length is this length + 12. + +void lodepng_chunk_type(char type[5], const unsigned char* chunk): +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type): + +Get the type of the chunk or compare if it's a certain type + +unsigned char lodepng_chunk_critical(const unsigned char* chunk): +unsigned char lodepng_chunk_private(const unsigned char* chunk): +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk): + +Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). +Check if the chunk is private (public chunks are part of the standard, private ones not). +Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical +chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your +program doesn't handle that type of unknown chunk. + +unsigned char* lodepng_chunk_data(unsigned char* chunk): +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk): + +Get a pointer to the start of the data of the chunk. + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk): +void lodepng_chunk_generate_crc(unsigned char* chunk): + +Check if the crc is correct or generate a correct one. + +unsigned char* lodepng_chunk_next(unsigned char* chunk): +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk): + +Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these +functions do no boundary checking of the allocated data whatsoever, so make sure there is enough +data available in the buffer to be able to go to the next chunk. + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk): +unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length, + const char* type, const unsigned char* data): + +These functions are used to create new chunks that are appended to the data in *out that has +length *outlength. The append function appends an existing chunk to the new data. The create +function creates a new chunk with the given parameters and appends it. Type is the 4-letter +name of the chunk. + +8.2. chunks in info_png +----------------------- + +The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3 +buffers (each with size) to contain 3 types of unknown chunks: +the ones that come before the PLTE chunk, the ones that come between the PLTE +and the IDAT chunks, and the ones that come after the IDAT chunks. +It's necessary to make the distionction between these 3 cases because the PNG +standard forces to keep the ordering of unknown chunks compared to the critical +chunks, but does not force any other ordering rules. + +info_png.unknown_chunks_data[0] is the chunks before PLTE +info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT +info_png.unknown_chunks_data[2] is the chunks after IDAT + +The chunks in these 3 buffers can be iterated through and read by using the same +way described in the previous subchapter. + +When using the decoder to decode a PNG, you can make it store all unknown chunks +if you set the option settings.remember_unknown_chunks to 1. By default, this +option is off (0). + +The encoder will always encode unknown chunks that are stored in the info_png. +If you need it to add a particular chunk that isn't known by LodePNG, you can +use lodepng_chunk_append or lodepng_chunk_create to the chunk data in +info_png.unknown_chunks_data[x]. + +Chunks that are known by LodePNG should not be added in that way. E.g. to make +LodePNG add a bKGD chunk, set background_defined to true and add the correct +parameters there instead. + + +9. compiler support +------------------- + +No libraries other than the current standard C library are needed to compile +LodePNG. For the C++ version, only the standard C++ library is needed on top. +Add the files lodepng.c(pp) and lodepng.h to your project, include +lodepng.h where needed, and your program can read/write PNG files. + +If performance is important, use optimization when compiling! For both the +encoder and decoder, this makes a large difference. + +Make sure that LodePNG is compiled with the same compiler of the same version +and with the same settings as the rest of the program, or the interfaces with +std::vectors and std::strings in C++ can be incompatible. + +CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. + +*) gcc and g++ + +LodePNG is developed in gcc so this compiler is natively supported. It gives no +warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ +version 4.7.1 on Linux, 32-bit and 64-bit. + +*) Mingw + +The Mingw compiler (a port of gcc) for Windows is fully supported by LodePNG. + +*) Visual Studio 2005 and up, Visual C++ Express Edition 2005 and up + +Visual Studio may give warnings about 'fopen' being deprecated. A multiplatform library +can't support the proposed Visual Studio alternative however, so LodePNG keeps using +fopen. If you don't want to see the deprecated warnings, put this on top of lodepng.h +before the inclusions: +#define _CRT_SECURE_NO_DEPRECATE + +Other than the above warnings, LodePNG should be warning-free with warning +level 3 (W3). Warning level 4 (W4) will give warnings about integer conversions. +I'm not planning to resolve these warnings. To get rid of them, let Visual +Studio use warning level W3 for lodepng.cpp only: right click lodepng.cpp, +Properties, C/C++, General, Warning Level: Level 3 (/W3). + +Visual Studio may want "stdafx.h" files to be included in each source file and +give an error "unexpected end of file while looking for precompiled header". +That is not standard C++ and will not be added to the stock LodePNG. You can +disable it for lodepng.cpp only by right clicking it, Properties, C/C++, +Precompiled Headers, and set it to Not Using Precompiled Headers there. + +*) Visual Studio 6.0 + +LodePNG support for Visual Studio 6.0 is not guaranteed because VS6 doesn't +follow the C++ standard correctly. + +*) Comeau C/C++ + +Vesion 20070107 compiles without problems on the Comeau C/C++ Online Test Drive +at http://www.comeaucomputing.com/tryitout in both C90 and C++ mode. + +*) Compilers on Macintosh + +LodePNG has been reported to work both with the gcc and LLVM for Macintosh, both +for C and C++. + +*) Other Compilers + +If you encounter problems on other compilers, feel free to let me know and I may +try to fix it if the compiler is modern standards complient. + + +10. examples +------------ + +This decoder example shows the most basic usage of LodePNG. More complex +examples can be found on the LodePNG website. + +10.1. decoder C++ example +------------------------- + +#include "lodepng.h" +#include + +int main(int argc, char *argv[]) +{ + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector image; + unsigned width, height; + unsigned error = lodepng::decode(image, width, height, filename); + + //if there's an error, display it + if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl; + + //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... +} + +10.2. decoder C example +----------------------- + +#include "lodepng.h" + +int main(int argc, char *argv[]) +{ + unsigned error; + unsigned char* image; + size_t width, height; + const char* filename = argc > 1 ? argv[1] : "test.png"; + + error = lodepng_decode32_file(&image, &width, &height, filename); + + if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error)); + + / * use image here * / + + free(image); + return 0; +} + + +11. changes +----------- + +The version number of LodePNG is the date of the change given in the format +yyyymmdd. + +Some changes aren't backwards compatible. Those are indicated with a (!) +symbol. + +*) 11 mar 2013 (!): Bugfix with custom free. Changed from "my" to "lodepng_" + prefix for the custom allocators and made it possible with a new #define to + use custom ones in your project without needing to change lodepng's code. +*) 28 jan 2013: Bugfix with color key. +*) 27 okt 2012: Tweaks in text chunk keyword length error handling. +*) 8 okt 2012 (!): Added new filter strategy (entropy) and new auto color mode. + (no palette). Better deflate tree encoding. New compression tweak settings. + Faster color conversions while decoding. Some internal cleanups. +*) 23 sep 2012: Reduced warnings in Visual Studio a little bit. +*) 1 sep 2012 (!): Removed #define's for giving custom (de)compression functions + and made it work with function pointers instead. +*) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc + and free functions and toggle #defines from compiler flags. Small fixes. +*) 6 may 2012 (!): Made plugging in custom zlib/deflate functions more flexible. +*) 22 apr 2012 (!): Made interface more consistent, renaming a lot. Removed + redundant C++ codec classes. Reduced amount of structs. Everything changed, + but it is cleaner now imho and functionality remains the same. Also fixed + several bugs and shrinked the implementation code. Made new samples. +*) 6 nov 2011 (!): By default, the encoder now automatically chooses the best + PNG color model and bit depth, based on the amount and type of colors of the + raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. +*) 9 okt 2011: simpler hash chain implementation for the encoder. +*) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. +*) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. + A bug with the PNG filtertype heuristic was fixed, so that it chooses much + better ones (it's quite significant). A setting to do an experimental, slow, + brute force search for PNG filter types is added. +*) 17 aug 2011 (!): changed some C zlib related function names. +*) 16 aug 2011: made the code less wide (max 120 characters per line). +*) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. +*) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. +*) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman + to optimize long sequences of zeros. +*) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and + LodePNG_InfoColor_canHaveAlpha functions for convenience. +*) 7 nov 2010: added LodePNG_error_text function to get error code description. +*) 30 okt 2010: made decoding slightly faster +*) 26 okt 2010: (!) changed some C function and struct names (more consistent). + Reorganized the documentation and the declaration order in the header. +*) 08 aug 2010: only changed some comments and external samples. +*) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. +*) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. +*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could + read by ignoring the problem but windows apps couldn't. +*) 06 jun 2008: added more error checks for out of memory cases. +*) 26 apr 2008: added a few more checks here and there to ensure more safety. +*) 06 mar 2008: crash with encoding of strings fixed +*) 02 feb 2008: support for international text chunks added (iTXt) +*) 23 jan 2008: small cleanups, and #defines to divide code in sections +*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. +*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. +*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added + Also vareous fixes, such as in the deflate and the padding bits code. +*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved + filtering code of encoder. +*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A + C++ wrapper around this provides an interface almost identical to before. + Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code + are together in these files but it works both for C and C++ compilers. +*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks +*) 30 aug 2007: bug fixed which makes this Borland C++ compatible +*) 09 aug 2007: some VS2005 warnings removed again +*) 21 jul 2007: deflate code placed in new namespace separate from zlib code +*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images +*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing + invalid std::vector element [0] fixed, and level 3 and 4 warnings removed +*) 02 jun 2007: made the encoder add a tag with version by default +*) 27 may 2007: zlib and png code separated (but still in the same file), + simple encoder/decoder functions added for more simple usage cases +*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), + moved some examples from here to lodepng_examples.cpp +*) 12 may 2007: palette decoding bug fixed +*) 24 apr 2007: changed the license from BSD to the zlib license +*) 11 mar 2007: very simple addition: ability to encode bKGD chunks. +*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding + palettized PNG images. Plus little interface change with palette and texts. +*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. + Fixed a bug where the end code of a block had length 0 in the Huffman tree. +*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented + and supported by the encoder, resulting in smaller PNGs at the output. +*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. +*) 24 jan 2007: gave encoder an error interface. Added color conversion from any + greyscale type to 8-bit greyscale with or without alpha. +*) 21 jan 2007: (!) Totally changed the interface. It allows more color types + to convert to and is more uniform. See the manual for how it works now. +*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: + encode/decode custom tEXt chunks, separate classes for zlib & deflate, and + at last made the decoder give errors for incorrect Adler32 or Crc. +*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. +*) 29 dec 2006: Added support for encoding images without alpha channel, and + cleaned out code as well as making certain parts faster. +*) 28 dec 2006: Added "Settings" to the encoder. +*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. + Removed some code duplication in the decoder. Fixed little bug in an example. +*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. + Fixed a bug of the decoder with 16-bit per color. +*) 15 okt 2006: Changed documentation structure +*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the + given image buffer, however for now it's not compressed. +*) 08 sep 2006: (!) Changed to interface with a Decoder class +*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different + way. Renamed decodePNG to decodePNGGeneric. +*) 29 jul 2006: (!) Changed the interface: image info is now returned as a + struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. +*) 28 jul 2006: Cleaned the code and added new error checks. + Corrected terminology "deflate" into "inflate". +*) 23 jun 2006: Added SDL example in the documentation in the header, this + example allows easy debugging by displaying the PNG and its transparency. +*) 22 jun 2006: (!) Changed way to obtain error value. Added + loadFile function for convenience. Made decodePNG32 faster. +*) 21 jun 2006: (!) Changed type of info vector to unsigned. + Changed position of palette in info vector. Fixed an important bug that + happened on PNGs with an uncompressed block. +*) 16 jun 2006: Internally changed unsigned into unsigned where + needed, and performed some optimizations. +*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them + in LodePNG namespace. Changed the order of the parameters. Rewrote the + documentation in the header. Renamed files to lodepng.cpp and lodepng.h +*) 22 apr 2006: Optimized and improved some code +*) 07 sep 2005: (!) Changed to std::vector interface +*) 12 aug 2005: Initial release (C++, decoder only) + + +12. contact information +----------------------- + +Feel free to contact me with suggestions, problems, comments, ... concerning +LodePNG. If you encounter a PNG image that doesn't work properly with this +decoder, feel free to send it and I'll use it to find and fix the problem. + +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. + + +Copyright (c) 2005-2012 Lode Vandevenne +*/ diff --git a/src/extensions/png/mod-lodepng.c b/src/extensions/png/mod-lodepng.c new file mode 100644 index 0000000000..4396bb4c6a --- /dev/null +++ b/src/extensions/png/mod-lodepng.c @@ -0,0 +1,116 @@ +// +// File: %mod-lodepng.c +// Summary: "Native Functions for cryptography" +// Section: Extension +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The original cryptography additions to Rebol were done by Saphirion, at +// a time prior to Rebol's open sourcing. They had to go through a brittle, +// incomplete, and difficult to read API for extending the interpreter with +// C code. +// +// This contains a simplification of %host-core.c, written directly to the +// native API. It also includes the longstanding (but not standard, and not +// particularly secure) ENCLOAK and DECLOAK operations from R3-Alpha. +// + +#include "lodepng.h" + +#include "sys-core.h" +#include "sys-ext.h" + +#include "tmp-mod-lodepng-first.h" + +// !!! This didn't really have anywhere to go. It came from %host-core.c, +// and it's not part of the historical PNG code, but apparently Saphirion +// found a problem with that in terms of saving (saving only?) which they +// added in lodepng for. This is unfortunate as lodepng repeats deflate +// code already available in Zlib. +// +// It is used as an override for the encoder from R3-Alpha, which is found +// in %u-png.c as ENCODE-PNG. + +// +// encode-png-lodepng: native [ +// +// {Codec for encoding a PNG image (via LODEPNG, plain ENCODE-PNG is buggy)} +// +// return: [binary!] +// image [image!] +// ] +// +REBNATIVE(encode_png_lodepng) +{ + INCLUDE_PARAMS_OF_ENCODE_PNG_LODEPNG; + + REBVAL *image = ARG(image); + + LodePNGState state; + lodepng_state_init(&state); + + // "disable autopilot" + state.encoder.auto_convert = LAC_NO; + + // input format + state.info_raw.colortype = LCT_RGBA; + state.info_raw.bitdepth = 8; + + // output format + state.info_png.color.colortype = LCT_RGBA; + state.info_png.color.bitdepth = 8; + + size_t buffersize; + REBYTE *buffer = NULL; + + REBINT w = VAL_IMAGE_WIDE(image); + REBINT h = VAL_IMAGE_HIGH(image); + + unsigned error = lodepng_encode( + &buffer, // freed with free()...so must be allocated via malloc() ? + &buffersize, + SER_DATA_RAW(VAL_SERIES(image)), + w, + h, + &state + ); + + lodepng_state_cleanup(&state); + + if (error != 0) { + if (buffer != NULL) free(buffer); + return R_BLANK; + } + + REBSER *binary = Make_Binary(buffersize); + memcpy(SER_DATA_RAW(binary), buffer, buffersize); + SET_SERIES_LEN(binary, buffersize); + free(buffer); + + Init_Binary(D_OUT, binary); + return R_OUT; +} + +#include "tmp-mod-lodepng-last.h" diff --git a/src/extensions/png/u-png.c b/src/extensions/png/u-png.c new file mode 100644 index 0000000000..883526bcb7 --- /dev/null +++ b/src/extensions/png/u-png.c @@ -0,0 +1,934 @@ +// +// File: %u-png.c +// Summary: "PNG image format conversion" +// Section: utility +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This is an optional part of R3. This file can be replaced by +// library function calls into an updated implementation. +// + +#include "sys-core.h" +#include "sys-zlib.h" +#include // remove this later !!!! + +#include "sys-ext.h" +#include "tmp-mod-upng-first.h" + +#ifdef STRICT_BOOL_COMPILER_TEST + // + // This is third party code that is not written to use REBOOL, and hence + // the definitions of TRUE and FALSE used in the "fake" build will trip + // it up. We substitute in normal definitions for this file. See + // the explanations of this test in %reb-c.h for more information. + // + #undef TRUE + #undef FALSE + #define TRUE 1 + #define FALSE 0 +#endif + +#if defined(ENDIAN_LITTLE) +#define CVT_END_L(a) a=(a<<24)|(((a>>8)&255)<<16)|(((a>>16)&255)<<8)|(a>>24) +#elif defined(ENDIAN_BIG) +#define CVT_END_L(a) +#else +#error Endianness must be defined in system.h +#endif + +#define int_abs(a) (((a)<0)?(-(a)):(a)) + +/**********************************************************************/ + +static struct png_ihdr { + unsigned int width; + unsigned int height; + unsigned char bit_depth; + unsigned char color_type; + unsigned char compression_method; + unsigned char filter_method; + unsigned char interlace_method; +} png_ihdr; + +static unsigned char colormodes[]={0x1f,0x00,0x18,0x0f,0x18,0x00,0x18}; +static unsigned char colormult[]={1,0,3,1,2,0,4}; + +static unsigned char adam7hoff[]={0,4,0,2,0,1,0}; +static unsigned char adam7hskip[]={8,8,4,4,2,2,1}; +static unsigned char adam7voff[]={0,0,4,0,2,0,1}; +static unsigned char adam7vskip[]={8,8,8,4,4,2,2}; +static unsigned char bytetab2[]={0x00,0x55,0xaa,0xff}; + +static int log2bitdepth; +static char haspalette; +static int bytesperpixel; +static int bitsperpixel; +static int rowlength; +static char hasalpha; +static unsigned char *imgbuffer; +static unsigned int palette[256]; +static unsigned short palette_alpha[256]; +static unsigned int *img_output; +static unsigned int transparent_red,transparent_green,transparent_blue; +static unsigned int transparent_gray; +static void (*process_row)(unsigned char *p,int width,int r,int hoff,int hskip); + +typedef void (*ROW_PROCESSOR)(unsigned char *, int, int, int, int); + +static void process_row_0_1(unsigned char *p,int width,int r,int hoff,int hskip); +static void process_row_0_2(unsigned char *p,int width,int r,int hoff,int hskip); +static void process_row_0_4(unsigned char *p,int width,int r,int hoff,int hskip); +static void process_row_0_8(unsigned char *p,int width,int r,int hoff,int hskip); +static void process_row_0_16(unsigned char *p,int width,int r,int hoff,int hskip); +static void process_row_2_8(unsigned char *p,int width,int r,int hoff,int hskip); +static void process_row_2_16(unsigned char *p,int width,int r,int hoff,int hskip); +static void process_row_3_1(unsigned char *p,int width,int r,int hoff,int hskip); +static void process_row_3_2(unsigned char *p,int width,int r,int hoff,int hskip); +static void process_row_3_4(unsigned char *p,int width,int r,int hoff,int hskip); +static void process_row_3_8(unsigned char *p,int width,int r,int hoff,int hskip); +static void process_row_4_8(unsigned char *p,int width,int r,int hoff,int hskip); +static void process_row_4_16(unsigned char *p,int width,int r,int hoff,int hskip); +static void process_row_6_8(unsigned char *p,int width,int r,int hoff,int hskip); +static void process_row_6_16(unsigned char *p,int width,int r,int hoff,int hskip); + +static ROW_PROCESSOR process_row0[] = { + process_row_0_1, process_row_0_2, process_row_0_4, + process_row_0_8, process_row_0_16 +}; +static ROW_PROCESSOR process_row2[] = { + NULL, NULL, NULL, process_row_2_8, process_row_2_16 +}; +static ROW_PROCESSOR process_row3[] = { + process_row_3_1, process_row_3_2, process_row_3_4, + process_row_3_8, NULL +}; +static ROW_PROCESSOR process_row4[] = { + NULL, NULL, NULL, process_row_4_8, process_row_4_16 +}; +static ROW_PROCESSOR process_row6[] = { + NULL, NULL, NULL, process_row_6_8, process_row_6_16 +}; +static ROW_PROCESSOR *process_row_lookup[]={ + process_row0, + NULL, + process_row2, + process_row3, + process_row4, + NULL, + process_row6 +}; + + +jmp_buf png_state; + +static void trap_png(void) +{ + longjmp(png_state, 1); +} + +/**********************************************************************/ + +static int find_msb(int val) { + int i; + for(i=30;val<(1<256*3)) + trap_png(); + for(i=0;i256) + length=256; + for(i=0;i>16, color>>8&255, color&255, 0xff); + }else if(alpha==0) { + hasalpha=TRUE; + return 0x00000000; + } else { + unsigned int red,green,blue; + hasalpha=TRUE; + red=color>>16; + green=(color>>8)&255; + blue=color&255; + return TO_PIXEL_COLOR(red, green, blue, (alpha/255)); + } +} + +static void process_row_0_1(unsigned char *p,int width,int r,int hoff,int hskip) { + int c; + unsigned char m = 0xAE; // overwritten, but avoid uninitialized warning + unsigned int v,*imgp; + imgp=img_output+r*png_ihdr.width+hoff; + for(c=0;c>7; + if(v==transparent_gray) { + hasalpha=TRUE; + *imgp=0x00000000; + } else { + v ? v=0xff : 0x00; + *imgp=TO_PIXEL_COLOR(v, v, v, 0xff); + } + imgp+=hskip; + m<<=1; + } +} + +static void process_row_0_2(unsigned char *p,int width,int r,int hoff,int hskip) { + int c; + unsigned char m = 0xAE; // overwritten, but avoid uninitialized warning + unsigned int v,*imgp; + + imgp=img_output+r*png_ihdr.width+hoff; + for(c=0;c>6; + if(v==transparent_gray) { + hasalpha=TRUE; + *imgp=0x00000000; + } else { + v=bytetab2[v]; + *imgp=TO_PIXEL_COLOR(v, v, v, 0xff); + } + imgp+=hskip; + m<<=2; + } +} + +static void process_row_0_4(unsigned char *p,int width,int r,int hoff,int hskip) { + int c; + unsigned char m = 0xAE; // overwritten, but avoid uninitialized warning + unsigned int v,*imgp; + + imgp=img_output+r*png_ihdr.width+hoff; + for(c=0;c>4; + if(v==transparent_gray) { + hasalpha=TRUE; + *imgp=0x00000000; + } else { + v|=(v<<4); + *imgp=TO_PIXEL_COLOR(v, v, v, 0xff); + } + imgp+=hskip; + m<<=4; + } +} + +static void process_row_0_8(unsigned char *p,int width,int r,int hoff,int hskip) { + int c; + unsigned int v,*imgp; + + imgp=img_output+r*png_ihdr.width+hoff; + for(c=0;c>=8; + *imgp=TO_PIXEL_COLOR(v, v, v, 0xff); + } + imgp+=hskip; + } +} + +static void process_row_2_8(unsigned char *p,int width,int r,int hoff,int hskip) { + int c; + unsigned int *imgp,red,green,blue; + + imgp=img_output+r*png_ihdr.width+hoff; + for(c=0;c>8), (green&0xff00), (blue>>8), 0xff); + imgp+=hskip; + } +} + +static void process_row_3_1(unsigned char *p,int width,int r,int hoff,int hskip) { + int c; + unsigned char m = 0xAE; // overwritten, but avoid uninitialized warning + unsigned int v,*imgp; + + imgp=img_output+r*png_ihdr.width+hoff; + for(c=0;c>7; + *imgp=calc_color(palette[v],palette_alpha[v]); + imgp+=hskip; + m<<=1; + } +} + +static void process_row_3_2(unsigned char *p,int width,int r,int hoff,int hskip) { + int c; + unsigned char m = 0xAE; // overwritten, but avoid uninitialized warning + unsigned int v,*imgp; + + imgp=img_output+r*png_ihdr.width+hoff; + for(c=0;c>6; + *imgp=calc_color(palette[v],palette_alpha[v]); + imgp+=hskip; + m<<=2; + } +} + +static void process_row_3_4(unsigned char *p,int width,int r,int hoff,int hskip) { + int c; + unsigned char m = 0xAE; // overwritten, but avoid uninitialized warning + unsigned int v,*imgp; + + imgp=img_output+r*png_ihdr.width+hoff; + for(c=0;c>4; + *imgp=calc_color(palette[v],palette_alpha[v]); + imgp+=hskip; + m<<=4; + } +} + +static void process_row_3_8(unsigned char *p,int width,int r,int hoff,int hskip) { + int c; + unsigned int v,*imgp; + + imgp=img_output+r*png_ihdr.width+hoff; + for(c=0;c4)||(png_ihdr.color_type>6)||png_ihdr.compression_method|| + png_ihdr.filter_method||(png_ihdr.interlace_method>1)|| + (!(colormodes[png_ihdr.color_type]&(1<next=0; + deflateInit(&zstream, Z_DEFAULT_COMPRESSION); + zstream.next_out=currentidat->data; + zstream.avail_out=IDATLENGTH; + dp=VAL_IMAGE_BITS(ARG(image)); + for(y=0;y>16; + *cp++=cv>>8; + *cp++=cv; + if(hasalpha) + *cp++=(cv>>24); + } + zstream.next_in=linebuf; + zstream.avail_in=(hasalpha?(4*w+1):(3*w+1)); + while(zstream.avail_in||(y==h-1)) { + if(!zstream.avail_out) + goto refill; + ret=deflate(&zstream,(y==h-1)?Z_FINISH:0); + if((ret==Z_OK)||(ret==Z_BUF_ERROR)) + continue; + if(ret==Z_STREAM_END) + break; + + goto error; + + refill: + currentidat->length=IDATLENGTH; + currentidat->next=(struct idatnode*)malloc(sizeof(struct idatnode)); + currentidat=currentidat->next; + currentidat->next=0; + zstream.next_out=currentidat->data; + zstream.avail_out=IDATLENGTH; + } + } + currentidat->length=IDATLENGTH-zstream.avail_out; + deflateEnd(&zstream); + imgsize=8+(12+13)+(12+19)+(12+0); + currentidat=firstidat; + while(currentidat) { + imgsize+=12+currentidat->length; + currentidat=currentidat->next; + } + + REBSER *bin; // goto would cross initialization + bin = Make_Binary(imgsize); + + cp=(unsigned char *)BIN_HEAD(bin); + memcpy(cp,"\211\120\116\107\015\012\032\012", 8); + cp+=8; + emitchunk(&cp,"IHDR",(char *)&ihdr,13); + emitchunk(&cp,"tEXt","Software\000REBOL",14); + currentidat=firstidat; + while(currentidat) { + emitchunk(&cp,"IDAT",(char *)currentidat->data,currentidat->length); + currentidat=currentidat->next; + } + emitchunk(&cp,"IEND",0,0); + + TERM_BIN_LEN(bin, imgsize); + + Init_Binary(D_OUT, bin); + goto cleanup; + +error: + assert(IS_END(D_OUT)); + +cleanup: + free(linebuf); + while(firstidat) { + currentidat=firstidat->next; + free(firstidat); + firstidat=currentidat; + } + + if (IS_END(D_OUT)) + fail (Error_Bad_Media_Raw()); // better error? + + assert(IS_BINARY(D_OUT)); + return R_OUT; +} + + +// +// identify-png?: native [ +// +// {Codec for identifying BINARY! data for a PNG} +// +// return: [logic!] +// data [binary!] +// ] +// +REBNATIVE(identify_png_q) +{ + INCLUDE_PARAMS_OF_IDENTIFY_PNG_Q; + + // Error hook in the PNG decoder from R3-Alpha is done via longjmp. + // + if (setjmp(png_state)) { + return R_FALSE; + } + + REBYTE *data = VAL_BIN_AT(ARG(data)); + REBCNT len = VAL_LEN_AT(ARG(data)); + + // !!! Should codec identifiers return any optional information they just + // happen to get? Instead of passing NULL for the addresses of the width + // and the height, this could incidentally get that information back + // to return it. Then any non-FALSE result could be "identified" while + // still being potentially more informative about what was found out. + // + if (0 == png_info(data, len, NULL, NULL)) + return R_FALSE; + return R_TRUE; +} + + +// +// decode-png: native [ +// +// {Codec for decoding BINARY! data for a PNG} +// +// return: [image!] +// data [binary!] +// ] +// +REBNATIVE(decode_png) +{ + INCLUDE_PARAMS_OF_DECODE_PNG; + + // Error hook in the PNG decoder from R3-Alpha is done via longjmp. + // + if (setjmp(png_state)) { + fail (Error_Bad_Media_Raw()); // can the error be more specific? + } + + REBYTE *data = VAL_BIN_AT(ARG(data)); + REBCNT len = VAL_LEN_AT(ARG(data)); + + int w, h; + if (0 == png_info(data, len, &w, &h)) + trap_png(); // longjmps to the "bad media" call above. + + REBSER *ser = Make_Image(w, h, TRUE); + + BOOL alpha = 0; // Note: not a REBOOL (PNG lib has minimal modification) + png_load(data, len, cast(char*, IMG_DATA(ser)), &alpha); + + Init_Image(D_OUT, ser); + + // !!! This was commented out? + // if (alpha) VAL_IMAGE_TRANSP(D_OUT) = VITT_ALPHA; + + return R_OUT; +} + + +#include "tmp-mod-upng-last.h" diff --git a/src/extensions/process/README.md b/src/extensions/process/README.md new file mode 100644 index 0000000000..ba3e9ad9ca --- /dev/null +++ b/src/extensions/process/README.md @@ -0,0 +1,10 @@ +This extension implements a process spawning interface known as CALL. CALL was +in Rebol2, but was not a feature released in the open source R3-Alpha: + +http://www.rebol.com/docs/shell.html + +Atronix implemented a version of it which was forced to go through a somewhat +circuitous method of host services. This extension attempts to simplify the +mechanism by being a bit more like a single native with #ifdefs for the +platforms in question, which cuts down on redundancy and can also make use +of internal APIs that were not available to extensions in R3-Alpha. diff --git a/src/extensions/process/ext-process-init.reb b/src/extensions/process/ext-process-init.reb new file mode 100644 index 0000000000..5087924960 --- /dev/null +++ b/src/extensions/process/ext-process-init.reb @@ -0,0 +1,36 @@ +REBOL [ + Title: "CALL Extension" + name: 'Call + type: 'Extension + version: 1.0.0 + license: {Apache 2.0} +] + +; CALL is a native built by the C code, BROWSE depends on using that, as well +; as some potentially OS-specific detection on how to launch URLs (e.g. looks +; at registry keys on Windows. + +browse*: procedure [ + "Open web browser to a URL or local file." + + location [url! file! blank!] +][ + if blank? location [leave] + + ; Note that GET-OS-BROWSERS uses the Windows registry convention of having + ; %1 be what needs to be substituted. This may not be ideal, it was just + ; easy to do rather than have to add processing on the C side. Review. + ; + for-each template get-os-browsers [ + command: replace (copy template) "%1" location + trap/with [ + call/shell command ; don't use /WAIT + leave + ][ + ;-- Just keep trying + ] + ] + fail "Could not open web browser" +] + +hijack 'browse :browse* \ No newline at end of file diff --git a/src/extensions/process/ext-process.c b/src/extensions/process/ext-process.c new file mode 100644 index 0000000000..75b7963148 --- /dev/null +++ b/src/extensions/process/ext-process.c @@ -0,0 +1,53 @@ +// +// File: %ext-process.c +// Summary: "CALL functions" +// Section: Extension +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ================================================================== +// +#include "sys-core.h" +#include "sys-ext.h" + + +#include "tmp-ext-process-init.inc" + + +#define MODULE_INCLUDE_DECLARATION_ONLY +#include "tmp-mod-process-last.h" + +DEFINE_EXT_INIT_COMPRESSED(Process, //name of the extension + script_bytes, // REBOL script for the extension in the source form + { + // init all modules in this extension + int init = CALL_MODULE_INIT(Process); + if (init < 0) return init; + } +) + +DEFINE_EXT_QUIT(Process, +{ + return CALL_MODULE_QUIT(Process); +} +) diff --git a/src/extensions/process/mod-process.c b/src/extensions/process/mod-process.c new file mode 100644 index 0000000000..3f25eb9f38 --- /dev/null +++ b/src/extensions/process/mod-process.c @@ -0,0 +1,2163 @@ +// +// File: %mod-call.c +// Summary: "Native Functions for spawning and controlling processes" +// Section: Extension +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 Atronix Engineering +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + +#ifdef TO_WINDOWS + #include + #include + #include + + #ifdef IS_ERROR + #undef IS_ERROR //winerror.h defines, Rebol has a different meaning + #endif +#else + #if !defined( __cplusplus) && defined(TO_LINUX) + // + // See feature_test_macros(7), this definition is redundant under C++ + // + #define _GNU_SOURCE // Needed for pipe2 when #including + #endif + #include + + #include + #include + #include + #include + #include + #include +#endif + +#include "sys-core.h" +#include "sys-ext.h" + +#include "tmp-mod-process-first.h" + + +// !!! The original implementation of CALL from Atronix had to communicate +// between the CALL native (defined in the core) and the host routine +// OS_Create_Process, which was not designed to operate on Rebol types. +// Hence if the user was passing in a BINARY! to which the data for the +// standard out or standard error was to be saved, it was produced in full +// in a buffer and returned, then appended. This wastes space when compared +// to just appending to the string or binary itself. With CALL rethought +// as an extension with access to the internal API, this could be changed... +// though for the moment, a malloc()'d buffer is expanded independently by +// BUF_SIZE_CHUNK and returned to CALL. +// +#define BUF_SIZE_CHUNK 4096 + + +#ifdef TO_WINDOWS +// +// OS_Create_Process: C +// +// Return -1 on error. +// +int OS_Create_Process( + REBFRM *frame_, // stopgap: allows access to CALL's ARG() and REF() + const wchar_t *call, + int argc, + const wchar_t * argv[], + REBOOL flag_wait, + u64 *pid, + int *exit_code, + char *input, + u32 input_len, + char **output, + u32 *output_len, + char **err, + u32 *err_len +) { + INCLUDE_PARAMS_OF_CALL; + + UNUSED(ARG(command)); // turned into `call` and `argv/argc` by CALL + UNUSED(REF(wait)); // covered by flag_wait + + UNUSED(REF(console)); // actually not paid attention to + + if (call == NULL) + fail ("'argv[]'-style launching not implemented on Windows CALL"); + +#ifdef GET_IS_NT_FLAG // !!! Why was this here? + REBOOL is_NT; + OSVERSIONINFO info; + GetVersionEx(&info); + is_NT = info.dwPlatformId >= VER_PLATFORM_WIN32_NT; +#endif + + UNUSED(argc); + UNUSED(argv); + + REBINT result = -1; + REBINT ret = 0; + HANDLE hOutputRead = 0, hOutputWrite = 0; + HANDLE hInputWrite = 0, hInputRead = 0; + HANDLE hErrorWrite = 0, hErrorRead = 0; + wchar_t *cmd = NULL; + char *oem_input = NULL; + + UNUSED(REF(info)); + + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + STARTUPINFO si; + si.cb = sizeof(si); + si.lpReserved = NULL; + si.lpDesktop = NULL; + si.lpTitle = NULL; + si.dwFlags = STARTF_USESHOWWINDOW; + si.dwFlags |= STARTF_USESTDHANDLES; + si.wShowWindow = SW_SHOWNORMAL; + si.cbReserved2 = 0; + si.lpReserved2 = NULL; + + UNUSED(REF(input)); // implicitly covered by void ARG(in) + switch (VAL_TYPE(ARG(in))) { + case REB_STRING: + case REB_BINARY: + if (!CreatePipe(&hInputRead, &hInputWrite, NULL, 0)) { + goto input_error; + } + + // make child side handle inheritable + if (!SetHandleInformation( + hInputRead, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT + )){ + goto input_error; + } + si.hStdInput = hInputRead; + break; + + case REB_FILE: { + REBSER *path = Value_To_OS_Path(ARG(in), FALSE); + + hInputRead = CreateFile( + SER_HEAD(wchar_t, path), + GENERIC_READ, // desired mode + 0, // shared mode + &sa, // security attributes + OPEN_EXISTING, // creation disposition + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, // flags + NULL // template + ); + si.hStdInput = hInputRead; + + Free_Series(path); + break; } + + case REB_BLANK: + si.hStdInput = 0; + break; + + case REB_MAX_VOID: + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + break; + + default: + panic (ARG(in)); + } + + UNUSED(REF(output)); // implicitly covered by void ARG(out) + switch (VAL_TYPE(ARG(out))) { + case REB_STRING: + case REB_BINARY: + if (!CreatePipe(&hOutputRead, &hOutputWrite, NULL, 0)) { + goto output_error; + } + + // make child side handle inheritable + // + if (!SetHandleInformation( + hOutputWrite, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT + )){ + goto output_error; + } + si.hStdOutput = hOutputWrite; + break; + + case REB_FILE: { + REBSER *path = Value_To_OS_Path(ARG(out), FALSE); + + si.hStdOutput = CreateFile( + SER_HEAD(wchar_t, path), + GENERIC_WRITE, // desired mode + 0, // shared mode + &sa, // security attributes + CREATE_NEW, // creation disposition + FILE_ATTRIBUTE_NORMAL, // flag and attributes + NULL // template + ); + + if ( + si.hStdOutput == INVALID_HANDLE_VALUE + && GetLastError() == ERROR_FILE_EXISTS + ){ + si.hStdOutput = CreateFile( + SER_HEAD(wchar_t, path), + GENERIC_WRITE, // desired mode + 0, // shared mode + &sa, // security attributes + OPEN_EXISTING, // creation disposition + FILE_ATTRIBUTE_NORMAL, // flag and attributes + NULL // template + ); + } + + Free_Series(path); + break; } + + case REB_BLANK: + si.hStdOutput = 0; + break; + + case REB_MAX_VOID: + si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + break; + + default: + panic (ARG(out)); + } + + UNUSED(REF(error)); // implicitly covered by void ARG(err) + switch (VAL_TYPE(ARG(err))) { + case REB_STRING: + case REB_BINARY: + if (!CreatePipe(&hErrorRead, &hErrorWrite, NULL, 0)) { + goto error_error; + } + + // make child side handle inheritable + // + if (!SetHandleInformation( + hErrorWrite, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT + )){ + goto error_error; + } + si.hStdError = hErrorWrite; + break; + + case REB_FILE: { + REBSER *path = Value_To_OS_Path(ARG(out), FALSE); + + si.hStdError = CreateFile( + SER_HEAD(wchar_t, path), + GENERIC_WRITE, // desired mode + 0, // shared mode + &sa, // security attributes + CREATE_NEW, // creation disposition + FILE_ATTRIBUTE_NORMAL, // flag and attributes + NULL // template + ); + + if ( + si.hStdError == INVALID_HANDLE_VALUE + && GetLastError() == ERROR_FILE_EXISTS + ){ + si.hStdError = CreateFile( + SER_HEAD(wchar_t, path), + GENERIC_WRITE, // desired mode + 0, // shared mode + &sa, // security attributes + OPEN_EXISTING, // creation disposition + FILE_ATTRIBUTE_NORMAL, // flag and attributes + NULL // template + ); + } + + Free_Series(path); + break; } + + case REB_BLANK: + si.hStdError = 0; + break; + + case REB_MAX_VOID: + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + break; + + default: + panic (ARG(err)); + } + + if (REF(shell)) { + const wchar_t *sh = L"cmd.exe /C "; + size_t len = wcslen(sh) + wcslen(call) + 1; + + cmd = cast(wchar_t*, malloc(len * sizeof(wchar_t))); + cmd[0] = L'\0'; + wcscat(cmd, sh); + wcscat(cmd, call); + } + else { + // CreateProcess might write to this memory + // Duplicate it to be safe + cmd = _wcsdup(call); + } + + PROCESS_INFORMATION pi; + result = CreateProcess( + NULL, // executable name + cmd, // command to execute + NULL, // process security attributes + NULL, // thread security attributes + TRUE, // inherit handles, must be TRUE for I/O redirection + NORMAL_PRIORITY_CLASS | CREATE_DEFAULT_ERROR_MODE, // creation flags + NULL, // environment + NULL, // current directory + &si, // startup information + &pi // process information + ); + + free(cmd); + + *pid = pi.dwProcessId; + + if (hInputRead != NULL) + CloseHandle(hInputRead); + + if (hOutputWrite != NULL) + CloseHandle(hOutputWrite); + + if (hErrorWrite != NULL) + CloseHandle(hErrorWrite); + + // Wait for termination: + if (result != 0 && flag_wait) { + HANDLE handles[3]; + int count = 0; + DWORD output_size = 0; + DWORD err_size = 0; + + if (hInputWrite != NULL && input_len > 0) { + if (IS_STRING(ARG(in))) { + DWORD dest_len = 0; + /* convert input encoding from UNICODE to OEM */ + // !!! Is cast to wchar_t here legal? + dest_len = WideCharToMultiByte( + CP_OEMCP, + 0, + cast(wchar_t*, input), + input_len, + oem_input, + dest_len, + NULL, + NULL + ); + if (dest_len > 0) { + oem_input = cast(char*, malloc(dest_len)); + if (oem_input != NULL) { + WideCharToMultiByte( + CP_OEMCP, + 0, + cast(wchar_t*, input), + input_len, + oem_input, + dest_len, + NULL, + NULL + ); + input_len = dest_len; + input = oem_input; + handles[count ++] = hInputWrite; + } + } + } else { + assert(IS_BINARY(ARG(in))); + handles[count ++] = hInputWrite; + } + } + if (hOutputRead != NULL) { + output_size = BUF_SIZE_CHUNK; + *output_len = 0; + + *output = cast(char*, malloc(output_size)); + handles[count ++] = hOutputRead; + } + if (hErrorRead != NULL) { + err_size = BUF_SIZE_CHUNK; + *err_len = 0; + + *err = cast(char*, malloc(err_size)); + handles[count++] = hErrorRead; + } + + while (count > 0) { + DWORD wait_result = WaitForMultipleObjects( + count, handles, FALSE, INFINITE + ); + + // If we test wait_result >= WAIT_OBJECT_0 it will tell us "always + // true" with -Wtype-limits, since WAIT_OBJECT_0 is 0. Take that + // comparison out but add assert in case you're on some abstracted + // Windows and it isn't 0 for that implementation. + // + assert(WAIT_OBJECT_0 == 0); + if (wait_result < WAIT_OBJECT_0 + count) { + int i = wait_result - WAIT_OBJECT_0; + DWORD input_pos = 0; + DWORD n = 0; + + if (handles[i] == hInputWrite) { + if (!WriteFile( + hInputWrite, + cast(char*, input) + input_pos, + input_len - input_pos, + &n, + NULL + )){ + if (i < count - 1) { + memmove( + &handles[i], + &handles[i + 1], + (count - i - 1) * sizeof(HANDLE) + ); + } + count--; + } + else { + input_pos += n; + if (input_pos >= input_len) { + /* done with input */ + CloseHandle(hInputWrite); + hInputWrite = NULL; + free(oem_input); + oem_input = NULL; + if (i < count - 1) { + memmove( + &handles[i], + &handles[i + 1], + (count - i - 1) * sizeof(HANDLE) + ); + } + count--; + } + } + } + else if (handles[i] == hOutputRead) { + if (!ReadFile( + hOutputRead, + *cast(char**, output) + *output_len, + output_size - *output_len, + &n, + NULL + )){ + if (i < count - 1) { + memmove( + &handles[i], + &handles[i + 1], + (count - i - 1) * sizeof(HANDLE) + ); + } + count--; + } + else { + *output_len += n; + if (*output_len >= output_size) { + output_size += BUF_SIZE_CHUNK; + *output = cast(char*, realloc(*output, output_size)); + if (*output == NULL) goto kill; + } + } + } + else if (handles[i] == hErrorRead) { + if (!ReadFile( + hErrorRead, + *cast(char**, err) + *err_len, + err_size - *err_len, + &n, + NULL + )){ + if (i < count - 1) { + memmove( + &handles[i], + &handles[i + 1], + (count - i - 1) * sizeof(HANDLE) + ); + } + count--; + } + else { + *err_len += n; + if (*err_len >= err_size) { + err_size += BUF_SIZE_CHUNK; + *err = cast(char*, realloc(*err, err_size)); + if (*err == NULL) goto kill; + } + } + } + else { + //printf("Error READ"); + if (!ret) ret = GetLastError(); + goto kill; + } + } + else if (wait_result == WAIT_FAILED) { /* */ + //printf("Wait Failed\n"); + if (!ret) ret = GetLastError(); + goto kill; + } + else { + //printf("Wait returns unexpected result: %d\n", wait_result); + if (!ret) ret = GetLastError(); + goto kill; + } + } + + WaitForSingleObject(pi.hProcess, INFINITE); // check result?? + + DWORD temp; + GetExitCodeProcess(pi.hProcess, &temp); + *exit_code = temp; + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + if (IS_STRING(ARG(out)) && *output != NULL && *output_len > 0) { + /* convert to wide char string */ + int dest_len = 0; + wchar_t *dest = NULL; + dest_len = MultiByteToWideChar( + CP_OEMCP, 0, *output, *output_len, dest, 0 + ); + if (dest_len <= 0) { + free(*output); + *output = NULL; + *output_len = 0; + } + dest = cast(wchar_t*, malloc(*output_len * sizeof(wchar_t))); + if (dest == NULL) + goto cleanup; + MultiByteToWideChar( + CP_OEMCP, 0, *output, *output_len, dest, dest_len + ); + free(*output); + *output = cast(char*, dest); + *output_len = dest_len; + } + + if (IS_STRING(ARG(err)) && *err != NULL && *err_len > 0) { + /* convert to wide char string */ + int dest_len = 0; + wchar_t *dest = NULL; + dest_len = MultiByteToWideChar( + CP_OEMCP, 0, *err, *err_len, dest, 0 + ); + if (dest_len <= 0) { + free(*err); + *err = NULL; + *err_len = 0; + } + dest = cast(wchar_t*, malloc(*err_len * sizeof(wchar_t))); + if (dest == NULL) goto cleanup; + MultiByteToWideChar(CP_OEMCP, 0, *err, *err_len, dest, dest_len); + free(*err); + *err = cast(char*, dest); + *err_len = dest_len; + } + } else if (result) { + // + // No wait, close handles to avoid leaks + // + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } + else { + // CreateProcess failed + ret = GetLastError(); + } + + goto cleanup; + +kill: + if (TerminateProcess(pi.hProcess, 0)) { + WaitForSingleObject(pi.hProcess, INFINITE); + + DWORD temp; + GetExitCodeProcess(pi.hProcess, &temp); + *exit_code = temp; + } + else if (ret == 0) { + ret = GetLastError(); + } + + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + +cleanup: + if (oem_input != NULL) { + free(oem_input); + } + + if (output != NULL && *output != NULL && *output_len == 0) { + free(*output); + } + + if (err != NULL && *err != NULL && *err_len == 0) { + free(*err); + } + + if (hInputWrite != NULL) + CloseHandle(hInputWrite); + + if (hOutputRead != NULL) + CloseHandle(hOutputRead); + + if (hErrorRead != NULL) + CloseHandle(hErrorRead); + + if (IS_FILE(ARG(err))) { + CloseHandle(si.hStdError); + } + +error_error: + if (IS_FILE(ARG(out))) { + CloseHandle(si.hStdOutput); + } + +output_error: + if (IS_FILE(ARG(in))) { + CloseHandle(si.hStdInput); + } + +input_error: + return ret; // meaning depends on flags +} + +#else // !defined(TO_WINDOWS), so POSIX, LINUX, OS X, etc. + +static REBOOL Open_Nonblocking_Pipe_Fails(int pipefd[2]) { +#ifdef USE_PIPE2_NOT_PIPE + // + // NOTE: pipe() is POSIX, but pipe2() is Linux-specific. With pipe() it + // takes an additional call to fcntl() to request non-blocking behavior, + // so it's a small amount more work. However, there are other flags which + // if aren't passed atomically at the moment of opening allow for a race + // condition in threading if split, e.g. FD_CLOEXEC. + // + // (If you don't have FD_CLOEXEC set on the file descriptor, then all + // instances of CALL will act as a /WAIT.) + // + // At time of writing, this is mostly academic...but the code needed to be + // patched to work with pipe() since some older libcs do not have pipe2(). + // So the ability to target both are kept around, saving the pipe2() call + // for later Linuxes known to have it (and O_CLOEXEC). + // + if (pipe2(pipefd, O_CLOEXEC | O_NONBLOCK)) + return TRUE; +#else + if (pipe(pipefd) < 0) + return TRUE; + + int direction; // READ=0, WRITE=1 + for (direction = 0; direction < 2; ++direction) { + int oldflags; + oldflags = fcntl(pipefd[direction], F_GETFL); + if (oldflags < 0) + return TRUE; + if (fcntl(pipefd[direction], F_SETFL, oldflags | O_NONBLOCK) < 0) + return TRUE; + oldflags = fcntl(pipefd[direction], F_GETFD); + if (oldflags < 0) + return TRUE; + if (fcntl(pipefd[direction], F_SETFD, oldflags | FD_CLOEXEC) < 0) + return TRUE; + } +#endif + + return FALSE; +} + + +// +// OS_Create_Process: C +// +// flags: +// 1: wait, is implied when I/O redirection is enabled +// 2: console +// 4: shell +// 8: info +// 16: show +// +// Return -1 on error, otherwise the process return code. +// +// POSIX previous simple version was just 'return system(call);' +// This uses 'execvp' which is "POSIX.1 conforming, UNIX compatible" +// +int OS_Create_Process( + REBFRM *frame_, // stopgap: allows access to CALL's ARG() and REF() + const char *call, + int argc, + const char* argv[], + REBOOL flag_wait, // distinct from REF(wait) + u64 *pid, + int *exit_code, + char *input, + u32 input_len, + char **output, + u32 *output_len, + char **err, + u32 *err_len +) { + INCLUDE_PARAMS_OF_CALL; + + UNUSED(ARG(command)); // translated into call and argc/argv + UNUSED(REF(wait)); // flag_wait controls this + UNUSED(REF(input)); + UNUSED(REF(output)); + UNUSED(REF(error)); + + UNUSED(REF(console)); // actually not paid attention to + + UNUSED(call); + + int status = 0; + int ret = 0; + + // An "info" pipe is used to send back an error code from the child + // process back to the parent if there is a problem. It only writes + // an integer's worth of data in that case, but it may need a bigger + // buffer if more interesting data needs to pass between them. + // + char *info = NULL; + off_t info_size = 0; + u32 info_len = 0; + + // suppress unused warnings but keep flags for future use + UNUSED(REF(info)); + UNUSED(REF(console)); + + const unsigned int R = 0; + const unsigned int W = 1; + int stdin_pipe[] = {-1, -1}; + int stdout_pipe[] = {-1, -1}; + int stderr_pipe[] = {-1, -1}; + int info_pipe[] = {-1, -1}; + + if (IS_STRING(ARG(in)) || IS_BINARY(ARG(in))) { + if (Open_Nonblocking_Pipe_Fails(stdin_pipe)) + goto stdin_pipe_err; + } + + if (IS_STRING(ARG(out)) || IS_BINARY(ARG(out))) { + if (Open_Nonblocking_Pipe_Fails(stdout_pipe)) + goto stdout_pipe_err; + } + + if (IS_STRING(ARG(err)) || IS_BINARY(ARG(err))) { + if (Open_Nonblocking_Pipe_Fails(stderr_pipe)) + goto stdout_pipe_err; + } + + if (Open_Nonblocking_Pipe_Fails(info_pipe)) + goto info_pipe_err; + + pid_t fpid; // gotos would cross initialization + fpid = fork(); + if (fpid == 0) { + // + // This is the child branch of the fork. In GDB if you want to debug + // the child you need to use `set follow-fork-mode child`: + // + // http://stackoverflow.com/questions/15126925/ + + if (IS_STRING(ARG(in)) || IS_BINARY(ARG(in))) { + close(stdin_pipe[W]); + if (dup2(stdin_pipe[R], STDIN_FILENO) < 0) + goto child_error; + close(stdin_pipe[R]); + } + else if (IS_FILE(ARG(in))) { + REBSER *path = Value_To_OS_Path(ARG(in), FALSE); + int fd = open(SER_HEAD(char, path), O_RDONLY); + Free_Series(path); + + if (fd < 0) + goto child_error; + if (dup2(fd, STDIN_FILENO) < 0) + goto child_error; + close(fd); + } + else if (IS_BLANK(ARG(in))) { + int fd = open("/dev/null", O_RDONLY); + if (fd < 0) + goto child_error; + if (dup2(fd, STDIN_FILENO) < 0) + goto child_error; + close(fd); + } + else { + assert(IS_VOID(ARG(in))); + // inherit stdin from the parent + } + + if (IS_STRING(ARG(out)) || IS_BINARY(ARG(out))) { + close(stdout_pipe[R]); + if (dup2(stdout_pipe[W], STDOUT_FILENO) < 0) + goto child_error; + close(stdout_pipe[W]); + } + else if (IS_FILE(ARG(out))) { + REBSER *path = Value_To_OS_Path(ARG(out), FALSE); + int fd = open(SER_HEAD(char, path), O_CREAT | O_WRONLY, 0666); + Free_Series(path); + + if (fd < 0) + goto child_error; + if (dup2(fd, STDOUT_FILENO) < 0) + goto child_error; + close(fd); + } + else if (IS_BLANK(ARG(out))) { + int fd = open("/dev/null", O_WRONLY); + if (fd < 0) + goto child_error; + if (dup2(fd, STDOUT_FILENO) < 0) + goto child_error; + close(fd); + } + else { + assert(IS_VOID(ARG(out))); + // inherit stdout from the parent + } + + if (IS_STRING(ARG(err)) || IS_BINARY(ARG(err))) { + close(stderr_pipe[R]); + if (dup2(stderr_pipe[W], STDERR_FILENO) < 0) + goto child_error; + close(stderr_pipe[W]); + } + else if (IS_FILE(ARG(err))) { + REBSER *path = Value_To_OS_Path(ARG(err), FALSE); + int fd = open(SER_HEAD(char, path), O_CREAT | O_WRONLY, 0666); + Free_Series(path); + + if (fd < 0) + goto child_error; + if (dup2(fd, STDERR_FILENO) < 0) + goto child_error; + close(fd); + } + else if (IS_BLANK(ARG(err))) { + int fd = open("/dev/null", O_WRONLY); + if (fd < 0) + goto child_error; + if (dup2(fd, STDERR_FILENO) < 0) + goto child_error; + close(fd); + } + else { + assert(IS_VOID(ARG(err))); + // inherit stderr from the parent + } + + close(info_pipe[R]); + + /* printf("flag_shell in child: %hhu\n", flag_shell); */ + + // We want to be able to compile with all warnings as errors, and + // we'd like to use -Wcast-qual if possible. This is currently + // the only barrier in the codebase...so we tunnel under the cast. + // + char * const *argv_hack; + + if (REF(shell)) { + const char *sh = getenv("SHELL"); + + if (sh == NULL) { // shell does not exist + int err = 2; + if (write(info_pipe[W], &err, sizeof(err)) == -1) { + // + // Nothing we can do, but need to stop compiler warning + // (cast to void is insufficient for warn_unused_result) + } + exit(EXIT_FAILURE); + } + + const char ** argv_new = cast( + const char**, + malloc((argc + 3) * sizeof(argv[0]) + )); + argv_new[0] = sh; + argv_new[1] = "-c"; + memcpy(&argv_new[2], argv, argc * sizeof(argv[0])); + argv_new[argc + 2] = NULL; + + memcpy(&argv_hack, &argv_new, sizeof(argv_hack)); + execvp(sh, argv_hack); + } + else { + memcpy(&argv_hack, &argv, sizeof(argv_hack)); + execvp(argv[0], argv_hack); + } + + // Note: execvp() will take over the process and not return, unless + // there was a problem in the execution. So you shouldn't be able + // to get here *unless* there was an error, which will be in errno. + +child_error: ; + // + // The original implementation of this code would write errno to the + // info pipe. However, errno may be volatile (and it is on Android). + // write() does not accept volatile pointers, so copy it to a + // temporary value first. + // + int nonvolatile_errno = errno; + + if (write(info_pipe[W], &nonvolatile_errno, sizeof(int)) == -1) { + // + // Nothing we can do, but need to stop compiler warning + // (cast to void is insufficient for warn_unused_result) + // + assert(FALSE); + } + exit(EXIT_FAILURE); /* get here only when exec fails */ + } + else if (fpid > 0) { + // + // This is the parent branch, so it may (or may not) wait on the + // child fork branch, based on /WAIT. Even if you are not using + // /WAIT, it will use the info pipe to make sure the process did + // actually start. + // + nfds_t nfds = 0; + struct pollfd pfds[4]; + unsigned int i; + ssize_t nbytes; + off_t input_size = 0; + off_t output_size = 0; + off_t err_size = 0; + int valid_nfds; + + // Only put the input pipe in the consideration if we can write to + // it and we have data to send to it. + + if ((stdin_pipe[W] > 0) && (input_size = strlen(input)) > 0) { + /* printf("stdin_pipe[W]: %d\n", stdin_pipe[W]); */ + + // the passed in input_len is in characters, not in bytes + // + input_len = 0; + + pfds[nfds].fd = stdin_pipe[W]; + pfds[nfds].events = POLLOUT; + nfds++; + + close(stdin_pipe[R]); + stdin_pipe[R] = -1; + } + if (stdout_pipe[R] > 0) { + /* printf("stdout_pipe[R]: %d\n", stdout_pipe[R]); */ + + output_size = BUF_SIZE_CHUNK; + + *output = cast(char*, malloc(output_size)); + *output_len = 0; + + pfds[nfds].fd = stdout_pipe[R]; + pfds[nfds].events = POLLIN; + nfds++; + + close(stdout_pipe[W]); + stdout_pipe[W] = -1; + } + if (stderr_pipe[R] > 0) { + /* printf("stderr_pipe[R]: %d\n", stderr_pipe[R]); */ + + err_size = BUF_SIZE_CHUNK; + + *err = cast(char*, malloc(err_size)); + *err_len = 0; + + pfds[nfds].fd = stderr_pipe[R]; + pfds[nfds].events = POLLIN; + nfds++; + + close(stderr_pipe[W]); + stderr_pipe[W] = -1; + } + + if (info_pipe[R] > 0) { + pfds[nfds].fd = info_pipe[R]; + pfds[nfds].events = POLLIN; + nfds++; + + info_size = 4; + + info = cast(char*, malloc(info_size)); + + close(info_pipe[W]); + info_pipe[W] = -1; + } + + valid_nfds = nfds; + while (valid_nfds > 0) { + pid_t xpid = waitpid(fpid, &status, WNOHANG); + if (xpid == -1) { + ret = errno; + goto error; + } + + if (xpid == fpid) { + // + // try one more time to read any remainding output/err + // + if (stdout_pipe[R] > 0) { + nbytes = read( + stdout_pipe[R], + *output + *output_len, + output_size - *output_len + ); + + if (nbytes > 0) { + *output_len += nbytes; + } + } + + if (stderr_pipe[R] > 0) { + nbytes = read( + stderr_pipe[R], + *err + *err_len, + err_size - *err_len + ); + if (nbytes > 0) { + *err_len += nbytes; + } + } + + if (info_pipe[R] > 0) { + nbytes = read( + info_pipe[R], + info + info_len, + info_size - info_len + ); + if (nbytes > 0) { + info_len += nbytes; + } + } + + break; + } + + /* + for (i = 0; i < nfds; ++i) { + printf(" %d", pfds[i].fd); + } + printf(" / %d\n", nfds); + */ + if (poll(pfds, nfds, -1) < 0) { + ret = errno; + goto kill; + } + + for (i = 0; i < nfds && valid_nfds > 0; ++i) { + /* printf("check: %d [%d/%d]\n", pfds[i].fd, i, nfds); */ + + if (pfds[i].revents & POLLERR) { + /* printf("POLLERR: %d [%d/%d]\n", pfds[i].fd, i, nfds); */ + + close(pfds[i].fd); + pfds[i].fd = -1; + valid_nfds --; + } + else if (pfds[i].revents & POLLOUT) { + /* printf("POLLOUT: %d [%d/%d]\n", pfds[i].fd, i, nfds); */ + + nbytes = write(pfds[i].fd, input, input_size - input_len); + if (nbytes <= 0) { + ret = errno; + goto kill; + } + /* printf("POLLOUT: %d bytes\n", nbytes); */ + input_len += nbytes; + if (cast(off_t, input_len) >= input_size) { + close(pfds[i].fd); + pfds[i].fd = -1; + valid_nfds --; + } + } + else if (pfds[i].revents & POLLIN) { + /* printf("POLLIN: %d [%d/%d]\n", pfds[i].fd, i, nfds); */ + char **buffer = NULL; + u32 *offset; + ssize_t to_read = 0; + size_t size; + if (pfds[i].fd == stdout_pipe[R]) { + buffer = output; + offset = output_len; + size = output_size; + } + else if (pfds[i].fd == stderr_pipe[R]) { + buffer = err; + offset = err_len; + size = err_size; + } + else { + assert(pfds[i].fd == info_pipe[R]); + buffer = &info; + offset = &info_len; + size = info_size; + } + + do { + to_read = size - *offset; + /* printf("to read %d bytes\n", to_read); */ + nbytes = read(pfds[i].fd, *buffer + *offset, to_read); + if (nbytes < 0) + break; + + if (nbytes == 0) { // closed + /* printf("the other end closed\n"); */ + close(pfds[i].fd); + pfds[i].fd = -1; + valid_nfds --; + break; + } + + /* printf("POLLIN: %d bytes\n", nbytes); */ + + *offset += nbytes; + assert(*offset <= size); + + if (*offset == size) { + char *larger = cast( + char*, + malloc(size + BUF_SIZE_CHUNK) + ); + if (larger == NULL) + goto kill; + memcpy(larger, *buffer, size); + free(*buffer); + *buffer = larger; + size += BUF_SIZE_CHUNK; + } + } while (nbytes == to_read); + } + else if (pfds[i].revents & POLLHUP) { + /* printf("POLLHUP: %d [%d/%d]\n", pfds[i].fd, i, nfds); */ + close(pfds[i].fd); + pfds[i].fd = -1; + valid_nfds --; + } + else if (pfds[i].revents & POLLNVAL) { + /* printf("POLLNVAL: %d [%d/%d]\n", pfds[i].fd, i, nfds); */ + ret = errno; + goto kill; + } + } + } + + if (valid_nfds == 0 && flag_wait) { + if (waitpid(fpid, &status, 0) < 0) { + ret = errno; + goto error; + } + } + + } + else { // error + ret = errno; + goto error; + } + + if (info_len == sizeof(int)) { + // + // exec in child process failed, set to errno for reporting. + // + ret = *cast(int*, info); + } + else if (WIFEXITED(status)) { + assert(info_len == 0); + + *exit_code = WEXITSTATUS(status); + *pid = fpid; + } + else { + ret = -1; + goto error; + } + + goto cleanup; + +kill: + kill(fpid, SIGKILL); + waitpid(fpid, NULL, 0); + +error: + if (ret == 0) + ret = -1; + +cleanup: + // CALL only expects to have to free the output or error buffer if there + // was a non-zero number of bytes returned. If there was no data, take + // care of it here. + // + // !!! This won't be done this way when this routine actually appends to + // the BINARY! or STRING! itself. + // + if (output != NULL && *output != NULL) + if (*output_len == 0) { // buffer allocated but never used + free(*output); + *output = NULL; + } + + if (err != NULL && *err != NULL) + if (*err_len == 0) { // buffer allocated but never used + free(*err); + *err = NULL; + } + + if (info != NULL) + free(info); + + if (info_pipe[R] > 0) + close(info_pipe[R]); + + if (info_pipe[W] > 0) + close(info_pipe[W]); + +info_pipe_err: + if (stderr_pipe[R] > 0) + close(stderr_pipe[R]); + + if (stderr_pipe[W] > 0) + close(stderr_pipe[W]); + + goto stderr_pipe_err; // no jumps here yet, avoid warning + +stderr_pipe_err: + if (stdout_pipe[R] > 0) + close(stdout_pipe[R]); + + if (stdout_pipe[W] > 0) + close(stdout_pipe[W]); + +stdout_pipe_err: + if (stdin_pipe[R] > 0) + close(stdin_pipe[R]); + + if (stdin_pipe[W] > 0) + close(stdin_pipe[W]); + +stdin_pipe_err: + // + // We will get to this point on success, as well as error (so ret may + // be 0. This is the return value of the host kit function to Rebol, not + // the process exit code (that's written into the pointer arg 'exit_code') + // + return ret; +} + +#endif + + +// +// call: native/export [ +// +// "Run another program; return immediately (unless /WAIT)." +// +// command [string! block! file!] +// {An OS-local command line (quoted as necessary), a block with +// arguments, or an executable file} +// /wait +// "Wait for command to terminate before returning" +// /console +// "Runs command with I/O redirected to console" +// /shell +// "Forces command to be run from shell" +// /info +// "Returns process information object" +// /input +// "Redirects stdin to in" +// in [string! binary! file! blank!] +// /output +// "Redirects stdout to out" +// out [string! binary! file! blank!] +// /error +// "Redirects stderr to err" +// err [string! binary! file! blank!] +// ] +// +REBNATIVE(call) +// +// !!! Parameter usage may require WAIT mode even if not explicitly requested. +// /WAIT should be default, with /ASYNC (or otherwise) as exception! +{ + INCLUDE_PARAMS_OF_CALL; + + UNUSED(REF(shell)); // looked at via frame_ by OS_Create_Process + UNUSED(REF(console)); // same + + // SECURE was never actually done for R3-Alpha + // + Check_Security(Canon(SYM_CALL), POL_EXEC, ARG(command)); + + // Make sure that if the output or error series are STRING! or BINARY!, + // they are not read-only, before we try appending to them. + // + if (IS_STRING(ARG(out)) || IS_BINARY(ARG(out))) + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(ARG(out))); + if (IS_STRING(ARG(err)) || IS_BINARY(ARG(err))) + FAIL_IF_READ_ONLY_SERIES(VAL_SERIES(ARG(err))); + + // If input_ser is set, it will be both managed and guarded + // + REBSER *input_ser; + char *os_input; + REBCNT input_len; + + UNUSED(REF(input)); // implicit by void ARG(in) + switch (VAL_TYPE(ARG(in))) { + case REB_STRING: + input_ser = NULL; + os_input = cast(char*, Val_Str_To_OS_Managed(&input_ser, ARG(in))); + PUSH_GUARD_SERIES(input_ser); + input_len = VAL_LEN_AT(ARG(in)); + break; + + case REB_BINARY: + input_ser = NULL; + os_input = s_cast(VAL_BIN_AT(ARG(in))); + input_len = VAL_LEN_AT(ARG(in)); + break; + + case REB_FILE: + input_ser = Value_To_OS_Path(ARG(in), FALSE); + MANAGE_SERIES(input_ser); + PUSH_GUARD_SERIES(input_ser); + os_input = SER_HEAD(char, input_ser); + input_len = SER_LEN(input_ser); + break; + + case REB_BLANK: + case REB_MAX_VOID: + input_ser = NULL; + os_input = NULL; + input_len = 0; + break; + + default: + panic(ARG(in)); + } + + UNUSED(REF(output)); + UNUSED(REF(error)); + + REBOOL flag_wait; + if ( + REF(wait) || + ( + IS_STRING(ARG(in)) || IS_BINARY(ARG(in)) + || IS_STRING(ARG(out)) || IS_BINARY(ARG(out)) + || IS_STRING(ARG(err)) || IS_BINARY(ARG(err)) + ) // I/O redirection implies /WAIT + ){ + flag_wait = TRUE; + } + else + flag_wait = FALSE; + + // We synthesize the argc and argv from the "command", and in the + // process we may need to do dynamic allocations of argc strings. In + // Rebol this is always done by making a series, and if those series + // are managed then we need to keep them SAVEd from the GC for the + // duration they will be used. Due to an artifact of the current + // implementation, FILE! and STRING! turned into OS-compatible character + // representations must be managed...so we need to save them over + // the duration of the call. We hold the pointers to remember to unsave. + // + int argc; + const REBCHR **argv; + REBCHR *cmd; + REBSER *argv_ser; + REBSER *argv_saved_sers; + REBSER *cmd_ser; + + if (IS_STRING(ARG(command))) { + // `call {foo bar}` => execute %"foo bar" + + // !!! Interpreting string case as an invocation of %foo with argument + // "bar" has been requested and seems more suitable. Question is + // whether it should go through the shell parsing to do so. + + cmd = Val_Str_To_OS_Managed(&cmd_ser, ARG(command)); + PUSH_GUARD_SERIES(cmd_ser); + + argc = 1; + argv_ser = Make_Series(argc + 1, sizeof(REBCHR*)); + argv_saved_sers = NULL; + argv = SER_HEAD(const REBCHR*, argv_ser); + + argv[0] = cmd; + // Already implicitly SAVEd by cmd_ser, no need for argv_saved_sers + + argv[argc] = NULL; + } + else if (IS_BLOCK(ARG(command))) { + // `call ["foo" "bar"]` => execute %foo with arg "bar" + + cmd = NULL; + cmd_ser = NULL; + + REBVAL *block = ARG(command); + + argc = VAL_LEN_AT(block); + + if (argc <= 0) + fail (Error_Too_Short_Raw()); + + argv_ser = Make_Series(argc + 1, sizeof(REBCHR*)); + argv_saved_sers = Make_Series(argc, sizeof(REBSER*)); + argv = SER_HEAD(const REBCHR*, argv_ser); + + int i; + for (i = 0; i < argc; i ++) { + RELVAL *param = VAL_ARRAY_AT_HEAD(block, i); + if (IS_STRING(param)) { + REBSER *ser; + argv[i] = Val_Str_To_OS_Managed(&ser, KNOWN(param)); + PUSH_GUARD_SERIES(ser); + SER_HEAD(REBSER*, argv_saved_sers)[i] = ser; + } + else if (IS_FILE(param)) { + REBSER *path = Value_To_OS_Path(KNOWN(param), FALSE); + argv[i] = SER_HEAD(REBCHR, path); + + MANAGE_SERIES(path); + PUSH_GUARD_SERIES(path); + SER_HEAD(REBSER*, argv_saved_sers)[i] = path; + } + else + fail (Error_Invalid_Arg_Core(param, VAL_SPECIFIER(block))); + } + argv[argc] = NULL; + } + else if (IS_FILE(ARG(command))) { + // `call %"foo bar"` => execute %"foo bar" + + cmd = NULL; + cmd_ser = NULL; + + argc = 1; + argv_ser = Make_Series(argc + 1, sizeof(REBCHR*)); + argv_saved_sers = Make_Series(argc, sizeof(REBSER*)); + + argv = SER_HEAD(const REBCHR*, argv_ser); + + REBSER *path = Value_To_OS_Path(ARG(command), FALSE); + argv[0] = SER_HEAD(REBCHR, path); + MANAGE_SERIES(path); + PUSH_GUARD_SERIES(path); + SER_HEAD(REBSER*, argv_saved_sers)[0] = path; + + argv[argc] = NULL; + } + else + fail (ARG(command)); + + REBU64 pid; + int exit_code; + + // If a STRING! or BINARY! is used for the output or error, then that + // is treated as a request to append the results of the pipe to them. + // + // !!! At the moment this is done by having the OS-specific routine + // pass back a buffer it malloc()s and reallocates to be the size of the + // full data, which is then appended after the operation is finished. + // With CALL now an extension where all parts have access to the internal + // API, it could be added directly to the binary or string as it goes. + // + char *os_output; + REBCNT output_len; + char *os_err; + REBCNT err_len; + + REBINT r = OS_Create_Process( + frame_, +#ifdef TO_WINDOWS + cast(const wchar_t*, cmd), + argc, + cast(const wchar_t**, argv), +#else + cast(const char*, cmd), + argc, + cast(const char**, argv), +#endif + flag_wait, + &pid, + &exit_code, + os_input, + input_len, + IS_STRING(ARG(out)) || IS_BINARY(ARG(out)) ? &os_output : NULL, + IS_STRING(ARG(out)) || IS_BINARY(ARG(out)) ? &output_len : NULL, + IS_STRING(ARG(err)) || IS_BINARY(ARG(err)) ? &os_err : NULL, + IS_STRING(ARG(err)) || IS_BINARY(ARG(err)) ? &err_len : NULL + ); + + // Call may not succeed if r != 0, but we still have to run cleanup + // before reporting any error... + // + if (argv_saved_sers) { + int i = argc; + assert(argc > 0); + do { + // Count down: must unsave the most recently saved series first! + DROP_GUARD_SERIES(*SER_AT(REBSER*, argv_saved_sers, i - 1)); + --i; + } while (i != 0); + Free_Series(argv_saved_sers); + } + if (cmd_ser != NULL) + DROP_GUARD_SERIES(cmd_ser); + Free_Series(argv_ser); // Unmanaged, so we can free it + + if (IS_STRING(ARG(out))) { + if (output_len > 0) { + // !!! Somewhat inefficient: should there be Append_OS_Str? + REBSER *ser = Copy_OS_Str(os_output, output_len); + Append_String(VAL_SERIES(ARG(out)), ser, 0, SER_LEN(ser)); + free(os_output); + Free_Series(ser); + } + } + else if (IS_BINARY(ARG(out))) { + if (output_len > 0) { + Append_Unencoded_Len(VAL_SERIES(ARG(out)), os_output, output_len); + free(os_output); + } + } + + if (IS_STRING(ARG(err))) { + if (err_len > 0) { + // !!! Somewhat inefficient: should there be Append_OS_Str? + REBSER *ser = Copy_OS_Str(os_err, err_len); + Append_String(VAL_SERIES(ARG(err)), ser, 0, SER_LEN(ser)); + free(os_err); + Free_Series(ser); + } + } else if (IS_BINARY(ARG(err))) { + if (err_len > 0) { + Append_Unencoded_Len(VAL_SERIES(ARG(err)), os_err, err_len); + free(os_err); + } + } + + // If we used (and possibly created) a series for input, then that series + // was managed and saved from GC. Unsave it now. Note backwardsness: + // must unsave the most recently saved series first!! + // + if (input_ser != NULL) + DROP_GUARD_SERIES(input_ser); + + if (REF(info)) { + REBCTX *info = Alloc_Context(REB_OBJECT, 2); + + Init_Integer(Append_Context(info, NULL, Canon(SYM_ID)), pid); + if (REF(wait)) + Init_Integer( + Append_Context(info, NULL, Canon(SYM_EXIT_CODE)), + exit_code + ); + + Init_Object(D_OUT, info); + return R_OUT; + } + + if (r != 0) { + Make_OS_Error(D_OUT, r); + fail (Error_Call_Fail_Raw(D_OUT)); + } + + // We may have waited even if they didn't ask us to explicitly, but + // we only return a process ID if /WAIT was not explicitly used + // + if (REF(wait)) + Init_Integer(D_OUT, exit_code); + else + Init_Integer(D_OUT, pid); + + return R_OUT; +} + + +// +// get-os-browsers: native/export [ +// +// "Ask the OS or registry what command(s) to use for starting a browser." +// +// return: [block!] +// {Block of strings, where %1 should be substituted with the string} +// ] +// +REBNATIVE(get_os_browsers) +// +// !!! Using the %1 convention is not necessarily ideal vs. having some kind +// of more "structural" result, it was just easy because it's how the string +// comes back from the Windows registry. Review. +{ + INCLUDE_PARAMS_OF_GET_OS_BROWSERS; + + REBDSP dsp_orig = DSP; + +#if defined(TO_WINDOWS) + + HKEY key; + if ( + RegOpenKeyEx( + HKEY_CLASSES_ROOT, + L"http\\shell\\open\\command", + 0, + KEY_READ, + &key + ) != ERROR_SUCCESS + ){ + fail ("Could not open registry key for http\\shell\\open\\command"); + } + + static_assert_c(sizeof(REBUNI) == sizeof(wchar_t)); + + DWORD num_bytes = 0; // pass NULL and use 0 for initial length, to query + + DWORD type; + DWORD flag = RegQueryValueExW(key, L"", 0, &type, NULL, &num_bytes); + + if ( + (flag != ERROR_MORE_DATA && flag != ERROR_SUCCESS) + || num_bytes == 0 + || type != REG_SZ // RegQueryValueExW returns unicode + || num_bytes % 2 != 0 // byte count should be even for unicode + ) { + RegCloseKey(key); + fail ("Could not read registry key for http\\shell\\open\\command"); + } + + REBCNT len = num_bytes / 2; + + REBSER *ser = Make_Unicode(len); + flag = RegQueryValueEx( + key, L"", 0, &type, cast(LPBYTE, UNI_HEAD(ser)), &num_bytes + ); + RegCloseKey(key); + + if (flag != ERROR_SUCCESS) + fail ("Could not read registry key for http\\shell\\open\\command"); + + while (*UNI_AT(ser, len - 1) == 0) { + // + // Don't count terminators; seems the guarantees are a bit fuzzy + // about whether the string in the registry has one included in the + // byte count or not. + // + --len; + } + TERM_UNI_LEN(ser, len); + + DS_PUSH_TRASH; + Init_String(DS_TOP, ser); + +#elif defined(TO_LINUX) + + // Caller should try xdg-open first, then try x-www-browser otherwise + // + DS_PUSH_TRASH; + Init_String(DS_TOP, Make_UTF8_May_Fail("xdg-open %1")); + DS_PUSH_TRASH; + Init_String(DS_TOP, Make_UTF8_May_Fail("x-www-browser %1")); + +#else // Just try /usr/bin/open on POSIX, OS X, Haiku, etc. + + // Just use /usr/bin/open + // + DS_PUSH_TRASH; + Init_String(DS_TOP, Make_UTF8_May_Fail("/usr/bin/open %1")); + +#endif + + Init_Block(D_OUT, Pop_Stack_Values(dsp_orig)); + return R_OUT; +} + + +// +// sleep: native [ +// +// "Use system sleep to wait a certain amount of time (doesn't use PORT!s)." +// +// return: [] +// duration [integer! decimal! time!] +// {Length to sleep (integer and decimal are measuring seconds)} +// +// ] +// +REBNATIVE(sleep) +// +// !!! This is a temporary workaround for the fact that it is not currently +// possible to do a WAIT on a time from within an AWAKE handler. A proper +// solution would presumably solve that problem, so two different functions +// would not be needed. +// +// This function was needed by @GrahamChiu, and putting it in the CALL module +// isn't necessarily ideal, but it's better than making the core dependent +// on Sleep() vs. usleep()...and all the relevant includes have been +// established here. +{ + INCLUDE_PARAMS_OF_SLEEP; + + REBCNT msec = Milliseconds_From_Value(ARG(duration)); + +#ifdef TO_WINDOWS + Sleep(msec); +#else + usleep(msec * 1000); +#endif + + return R_VOID; +} + + +// +// get-pid: native [ +// +// "Get ID of the process" +// +// return: [integer!] +// +// ] +// +static REBNATIVE(get_pid) +{ + INCLUDE_PARAMS_OF_GET_PID; + +#ifdef TO_WINDOWS + Init_Integer(D_OUT, GetCurrentProcessId()); +#else + Init_Integer(D_OUT, getpid()); +#endif + + return R_OUT; +} + + + +// +// get-uid: native [ +// +// "Get real user ID of the process" +// +// return: [integer!] +// +// ] +// +static REBNATIVE(get_uid) +{ + INCLUDE_PARAMS_OF_GET_UID; + +#ifdef TO_WINDOWS + UNUSED(frame_); + fail ("get-uid is not implemented on Windows"); +#else + Init_Integer(D_OUT, getuid()); +#endif + + return R_OUT; +} + + + +// +// get-euid: native [ +// +// "Get effective user ID of the process" +// +// return: [integer!] +// +// ] +// +static REBNATIVE(get_euid) +{ + INCLUDE_PARAMS_OF_GET_EUID; + +#ifdef TO_WINDOWS + UNUSED(frame_); + fail ("get-euid is not implemented on Windows"); +#else + Init_Integer(D_OUT, geteuid()); +#endif + + return R_OUT; +} + +// +// get-gid: native [ +// +// "Get real group ID of the process" +// +// return: [integer!] +// +// ] +// +static REBNATIVE(get_gid) +{ + INCLUDE_PARAMS_OF_GET_UID; + +#ifdef TO_WINDOWS + UNUSED(frame_); + fail ("get-gid is not implemented on Windows"); +#else + Init_Integer(D_OUT, getgid()); +#endif + + return R_OUT; +} + + + +// +// get-egid: native [ +// +// "Get effective group ID of the process" +// +// return: [integer!] +// +// ] +// +static REBNATIVE(get_egid) +{ + INCLUDE_PARAMS_OF_GET_EUID; + +#ifdef TO_WINDOWS + UNUSED(frame_); + fail ("get-egid is not implemented on Windows"); +#else + Init_Integer(D_OUT, getegid()); +#endif + + return R_OUT; +} + + + +// +// set-uid: native [ +// +// "Set real user ID of the process" +// +// return: [] +// uid [integer!] +// {The effective user ID} +// ] +// new-errors: [ +// invalid-uid: ["User id is invalid or not supported:" :arg1] +// permission-denied: ["The process does not have enough permission"] +// set-uid-failed: ["set-uid failed with error number:" :arg1] +// ] +// +static REBNATIVE(set_uid) +{ + INCLUDE_PARAMS_OF_SET_UID; + +#ifdef TO_WINDOWS + UNUSED(frame_); + UNUSED(ARG(uid)); + fail ("set-uid is not implemented on Windows"); +#else + if (setuid(VAL_INT32(ARG(uid))) < 0) { + switch (errno) { + case EINVAL: + fail (Error(RE_EXT_PROCESS_INVALID_UID, ARG(uid), END)); + case EPERM: + fail (Error(RE_EXT_PROCESS_PERMISSION_DENIED, END)); + default: { + DECLARE_LOCAL(err); + Init_Integer(err, errno); + fail (Error(RE_EXT_PROCESS_SET_UID_FAILED, err, END)); + } + } + } +#endif + + return R_VOID; +} + + + +// +// set-euid: native [ +// +// "Get effective user ID of the process" +// +// return: [] +// euid [integer!] +// {The effective user ID} +// ] +// new-errors: [ +// invalid-euid: ["user id is invalid or not supported:" :arg1] +// set-euid-failed: ["set-euid failed with error number:" :arg1] +// ] +// +static REBNATIVE(set_euid) +{ + INCLUDE_PARAMS_OF_SET_EUID; + +#ifdef TO_WINDOWS + UNUSED(frame_); + UNUSED(ARG(euid)); + fail ("set-euid is not implemented on Windows"); +#else + if (seteuid(VAL_INT32(ARG(euid))) < 0) { + switch (errno) { + case EINVAL: + fail (Error(RE_EXT_PROCESS_INVALID_EUID, ARG(euid), END)); + case EPERM: + fail (Error(RE_EXT_PROCESS_PERMISSION_DENIED, END)); + default: { + DECLARE_LOCAL(err); + Init_Integer(err, errno); + fail (Error(RE_EXT_PROCESS_SET_EUID_FAILED, err, END)); + } + } + } +#endif + + return R_VOID; +} + + + +// +// set-gid: native [ +// +// "Set real group ID of the process" +// +// return: [] +// gid [integer!] +// {The effective group ID} +// ] +// new-errors: [ +// invalid-gid: ["group id is invalid or not supported:" :arg1] +// set-gid-failed: ["set-gid failed with error number:" :arg1] +// ] +// +static REBNATIVE(set_gid) +{ + INCLUDE_PARAMS_OF_SET_GID; + +#ifdef TO_WINDOWS + UNUSED(frame_); + UNUSED(ARG(gid)); + fail ("set-gid is not implemented on Windows"); +#else + if (setgid(VAL_INT32(ARG(gid))) < 0) { + switch (errno) { + case EINVAL: + fail (Error(RE_EXT_PROCESS_INVALID_GID, ARG(gid), END)); + case EPERM: + fail (Error(RE_EXT_PROCESS_PERMISSION_DENIED, END)); + default: { + DECLARE_LOCAL(err); + Init_Integer(err, errno); + fail (Error(RE_EXT_PROCESS_SET_GID_FAILED, err, END)); + } + } + } +#endif + + return R_VOID; +} + + + +// +// set-egid: native [ +// +// "Get effective group ID of the process" +// +// return: [] +// egid [integer!] +// {The effective group ID} +// ] +// new-errors: [ +// invalid-egid: ["group id is invalid or not supported:" :arg1] +// set-egid-failed: ["set-egid failed with error number:" :arg1] +// ] +// +static REBNATIVE(set_egid) +{ + INCLUDE_PARAMS_OF_SET_EGID; + +#ifdef TO_WINDOWS + UNUSED(frame_); + UNUSED(ARG(egid)); + fail ("set-egid is not implemented on Windows"); +#else + if (setegid(VAL_INT32(ARG(egid))) < 0) { + switch (errno) { + case EINVAL: + fail (Error(RE_EXT_PROCESS_INVALID_EGID, ARG(egid), END)); + case EPERM: + fail (Error(RE_EXT_PROCESS_PERMISSION_DENIED, END)); + default: { + DECLARE_LOCAL(err); + Init_Integer(err, errno); + fail (Error(RE_EXT_PROCESS_SET_EGID_FAILED, err, END)); + } + } + } +#endif + + return R_VOID; +} + +#if !defined(TO_WINDOWS) +static void kill_process(REBINT pid, REBINT signal) +{ + if (kill(pid, signal) < 0) { + DECLARE_LOCAL(arg1); + switch (errno) { + case EINVAL: + Init_Integer(arg1, signal); + fail (Error(RE_EXT_PROCESS_INVALID_SIGNAL, arg1, END)); + case EPERM: + fail (Error(RE_EXT_PROCESS_PERMISSION_DENIED, END)); + case ESRCH: + Init_Integer(arg1, pid); + fail (Error(RE_EXT_PROCESS_NO_PROCESS, arg1, END)); + default: + Init_Integer(arg1, errno); + fail (Error(RE_EXT_PROCESS_SEND_SIGNAL_FAILED, arg1, END)); + } + } +} +#endif + + +// +// send-signal: native [ +// +// "Send signal to a process" +// +// return: [] +// pid [integer!] +// {The process ID} +// signal [integer!] +// {The signal number} +// ] +// new-errors: [ +// invalid-signal: ["An invalid signal is specified:" :arg1] +// no-process: ["The target process (group) does not exist:" :arg1] +// send-signal-failed: ["send-signal failed with error number:" :arg1] +// ] +// +static REBNATIVE(send_signal) +{ + INCLUDE_PARAMS_OF_SEND_SIGNAL; + +#ifdef TO_WINDOWS + UNUSED(frame_); + UNUSED(ARG(pid)); + UNUSED(ARG(signal)); + fail ("send-signal is not implemented on Windows"); +#else + kill_process(VAL_INT32(ARG(pid)), VAL_INT32(ARG(signal))); +#endif + + return R_VOID; +} + + + +// +// terminate: native [ +// +// "Terminate a process (not current one)" +// +// return: [] +// pid [integer!] +// {The process ID} +// ] +// new-errors: [ +// terminate-failed: ["terminate failed with error number:" :arg1] +// ] +// +static REBNATIVE(terminate) +{ + INCLUDE_PARAMS_OF_TERMINATE; + +#ifdef TO_WINDOWS + if (GetCurrentProcessId() == cast(DWORD, VAL_INT32(ARG(pid)))) { + fail ("Use QUIT or EXIT-REBOL to terminate current process, instead"); + } + REBINT err = 0; + HANDLE ph = OpenProcess(PROCESS_TERMINATE, FALSE, VAL_INT32(ARG(pid))); + if (ph == NULL) { + err = GetLastError(); + switch (err) { + case ERROR_ACCESS_DENIED: + fail (Error(RE_EXT_PROCESS_PERMISSION_DENIED, END)); + case ERROR_INVALID_PARAMETER: + fail (Error(RE_EXT_PROCESS_NO_PROCESS, ARG(pid), END)); + default: { + DECLARE_LOCAL(val); + Init_Integer(val, err); + fail (Error(RE_EXT_PROCESS_TERMINATE_FAILED, val, END)); + } + } + } + if (TerminateProcess(ph, 0)) { + CloseHandle(ph); + return R_VOID; + } + err = GetLastError(); + CloseHandle(ph); + switch (err) { + case ERROR_INVALID_HANDLE: + fail (Error(RE_EXT_PROCESS_NO_PROCESS, ARG(pid), END)); + default: { + DECLARE_LOCAL(val); + Init_Integer(val, err); + fail (Error(RE_EXT_PROCESS_TERMINATE_FAILED, val, END)); + } + } +#else + if (getpid() == VAL_INT32(ARG(pid))) { + // signal is not as reliable for this purpose + // it's caught in host-main.c as to stop the evaluation + fail ("Use QUIT or EXIT-REBOL to terminate current process, instead"); + } + kill_process(VAL_INT32(ARG(pid)), SIGTERM); +#endif + + return R_VOID; +} + + + +#include "tmp-mod-process-last.h" diff --git a/src/include/README.md b/src/include/README.md new file mode 100644 index 0000000000..79208623e6 --- /dev/null +++ b/src/include/README.md @@ -0,0 +1,19 @@ +This directory contains include files for code that is written to either the +internal API, which you get with `#include "sys-core.h"`...or to the external +API, which you get with `#include "reb-host.h"`. + +Code written to the internal API has to deal with the specific issues of +series and garbage collection. It has access to the data stack and can do +anything that a native function could do. This means functions like ARR_AT(), +PUSH_GUARD_SERIES(), Pop_Stack_Values(). etc are available. The result is +efficiency at the cost of needing to worry about details, as well as being +more likely to need to change the code if the internals change. + +Code written to the external API in Ren-C operates on REBVAL pointers only, +and has no API for extracting REBSER* or REBCTX*. Values created by this API +cannot live on the stack, and they will be garbage collected. + +Each of the `reb-xxx.h` files is included by %reb-host.h, and each of the +`sys-xxx.h` files are included by %sys-core.h. The sub-files don't use +include guards and the order of inclusion is important...so they should not be +#include'd individually. diff --git a/src/include/assert-fixes.h b/src/include/assert-fixes.h new file mode 100644 index 0000000000..a89853eb15 --- /dev/null +++ b/src/include/assert-fixes.h @@ -0,0 +1,20 @@ +#if !defined(NDEBUG) && defined(__GLIBC__) && defined(__GLIBC_MINOR__) && (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 23)) + +// fix bug https://sourceware.org/bugzilla/show_bug.cgi?id=18604 +#undef assert +# if !defined __GNUC__ || defined __STRICT_ANSI__ +# define assert(expr) \ + ((expr) \ + ? __ASSERT_VOID_CAST (0) \ + : __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION)) +# else +# define assert(expr) \ + ({ \ + if (expr) \ + ; /* empty */ \ + else \ + __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION); \ + }) +# endif + +#endif //__GLIBC__ diff --git a/src/include/debugbreak.h b/src/include/debugbreak.h new file mode 100644 index 0000000000..863c248556 --- /dev/null +++ b/src/include/debugbreak.h @@ -0,0 +1,170 @@ +// This allows for the programmatic triggering of debugger breaks from C +// code, using `debug_break()`. +// +// The file was obtained from: +// +// https://github.com/scottt/debugbreak +// +// Supported platforms are listed as: +// +// "gcc and Clang, works well on ARM, AArch64, i686, x86-64 and has +// a fallback code path for other architectures." +// +// Ren-C modifications: +// +// + integrates iOS ARM64 patch (an un-processed PR as of 21-Dec-2015) +// + __inline__ moved to beginning of declarations (suppresses warning) +// + tabs converted to spaces +// + Nullified __builtin_trap for compilers not supporting it (TinyCC) +// + Added a macros for compilers not defining __i386__ or __x86_64__ (TinyCC) +// + +/* Copyright (c) 2011-2015, Scott Tsai + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DEBUG_BREAK_H +#define DEBUG_BREAK_H + +#ifdef _MSC_VER + +#define debug_break __debugbreak + +#else + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __has_builtin + #define __has_builtin(x) 0 +#endif + +#if !__has_builtin(__builtin_trap) && !defined(__GNUC__) +#define __builtin_trap() +#endif + + enum { + /* gcc optimizers consider code after __builtin_trap() dead. + * Making __builtin_trap() unsuitable for breaking into the debugger */ + DEBUG_BREAK_PREFER_BUILTIN_TRAP_TO_SIGTRAP = 0 + }; + +#if defined(__i386__) || defined(__x86_64__) || defined(TRAP_IS_INT3) + enum { HAVE_TRAP_INSTRUCTION = 1 }; + __attribute__((gnu_inline, always_inline)) + __inline__ static void trap_instruction(void) + { + __asm__ volatile("int $0x03"); + } +#elif defined(__thumb__) + enum { HAVE_TRAP_INSTRUCTION = 1, }; + /* FIXME: handle __THUMB_INTERWORK__ */ + __attribute__((gnu_inline, always_inline)) + __inline__ static void trap_instruction(void) + { + /* See 'arm-linux-tdep.c' in GDB source. + * Both instruction sequences below work. */ +#if 1 + /* 'eabi_linux_thumb_le_breakpoint' */ + __asm__ volatile(".inst 0xde01"); +#else + /* 'eabi_linux_thumb2_le_breakpoint' */ + __asm__ volatile(".inst.w 0xf7f0a000"); +#endif + + /* Known problem: + * After a breakpoint hit, can't stepi, step, or continue in GDB. + * 'step' stuck on the same instruction. + * + * Workaround: a new GDB command, + * 'debugbreak-step' is defined in debugbreak-gdb.py + * that does: + * (gdb) set $instruction_len = 2 + * (gdb) tbreak *($pc + $instruction_len) + * (gdb) jump *($pc + $instruction_len) + */ + } +#elif defined(__arm__) && !defined(__thumb__) + enum { HAVE_TRAP_INSTRUCTION = 1, }; + __attribute__((gnu_inline, always_inline)) + __inline__ static void trap_instruction(void) + { + /* See 'arm-linux-tdep.c' in GDB source, + * 'eabi_linux_arm_le_breakpoint' */ + __asm__ volatile(".inst 0xe7f001f0"); + /* Has same known problem and workaround + * as Thumb mode */ + } +#elif defined(__aarch64__) && defined(__APPLE__) + enum { HAVE_TRAP_INSTRUCTION = 1, }; + __attribute__((gnu_inline, always_inline)) + __inline__ static void trap_instruction(void) + { + __builtin_trap(); + } +#elif defined(__aarch64__) + enum { HAVE_TRAP_INSTRUCTION = 1, }; + __attribute__((gnu_inline, always_inline)) + __inline__ static void trap_instruction(void) + { + /* See 'aarch64-tdep.c' in GDB source, + * 'aarch64_default_breakpoint' */ + __asm__ volatile(".inst 0xd4200000"); + } +#else + enum { HAVE_TRAP_INSTRUCTION = 0, }; +#endif + + __attribute__((gnu_inline, always_inline)) + __inline__ static void debug_break(void) + { + if (HAVE_TRAP_INSTRUCTION) { + trap_instruction(); + } else if (DEBUG_BREAK_PREFER_BUILTIN_TRAP_TO_SIGTRAP) { + /* raises SIGILL on Linux x86{,-64}, to continue in gdb: + * (gdb) handle SIGILL stop nopass + * */ + __builtin_trap(); + } else { +#ifdef _WIN32 + /* SIGTRAP available only on POSIX-compliant operating systems + * use builtin trap instead */ + __builtin_trap(); +#else + raise(SIGTRAP); +#endif + } + } + +#ifdef __cplusplus +} +#endif + +#endif + +#endif diff --git a/src/include/mem-pools.h b/src/include/mem-pools.h new file mode 100644 index 0000000000..4b0d90ca97 --- /dev/null +++ b/src/include/mem-pools.h @@ -0,0 +1,102 @@ +// +// File: %sys-mem.h +// Summary: "Memory allocation" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + + +/*********************************************************************** +** +*/ typedef struct rebol_mem_segment +/* +** Linked list of used memory segments. +** +** Size: 8 bytes +** +***********************************************************************/ +{ + struct rebol_mem_segment *next; + REBUPT size; +} REBSEG; + + +/*********************************************************************** +** +*/ typedef struct rebol_mem_spec +/* +** Specifies initial pool sizes +** +***********************************************************************/ +{ + REBCNT wide; // size of allocation unit + REBCNT units; // units per segment allocation +} REBPOOLSPEC; + + +/*********************************************************************** +** +*/ struct rebol_mem_pool +/* +** Pools manage fixed sized blocks of memory. +** +***********************************************************************/ +{ + REBSEG *segs; // first memory segment + REBNOD *first; // first free node in pool + REBNOD *last; // last free node in pool + REBCNT wide; // size of allocation unit + REBCNT units; // units per segment allocation + REBCNT free; // number of units remaining + REBCNT has; // total number of units +// UL total; // total bytes for all segs +// char *name; // identifying string +// UL extra; // reserved +}; + + +/*********************************************************************** +** +*/ enum Mem_Pool_Specs +/* +***********************************************************************/ +{ + MEM_TINY_POOL = 0, + MEM_SMALL_POOLS = MEM_TINY_POOL + 16, + MEM_MID_POOLS = MEM_SMALL_POOLS + 4, + MEM_BIG_POOLS = MEM_MID_POOLS + 4, // larger pools + SER_POOL = MEM_BIG_POOLS, + GOB_POOL, + SYSTEM_POOL, + MAX_POOLS +}; + +#define DEF_POOL(size, count) {size, count} +#define MOD_POOL(size, count) {size * MEM_MIN_SIZE, count} + +#define MEM_MIN_SIZE sizeof(REBVAL) +#define MEM_BIG_SIZE 1024 + +#define MEM_BALLAST 3000000 diff --git a/src/include/mem-series.h b/src/include/mem-series.h new file mode 100644 index 0000000000..3313ef4ac3 --- /dev/null +++ b/src/include/mem-series.h @@ -0,0 +1,79 @@ +// +// File: %mem-series.h +// Summary: {Low level memory-oriented access routines for series} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// These are implementation details of series that most code should not need +// to use. +// + +// Non-series-internal code needs to read SER_WIDE but should not be +// needing to set it directly. +// +// !!! Can't `assert((w) < MAX_SERIES_WIDE)` without triggering "range of +// type makes this always false" warning; C++ build could sense if it's a +// REBYTE and dodge the comparison if so. +// + +#define MAX_SERIES_WIDE 0x100 + +inline static void SER_SET_WIDE(REBSER *s, REBYTE w) { + CLEAR_8_RIGHT_BITS(s->info.bits); + s->info.bits |= FLAGBYTE_RIGHT(w); +} + +// +// Bias is empty space in front of head: +// + +inline static REBCNT SER_BIAS(REBSER *s) { + assert(GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC)); + return cast(REBCNT, ((s)->content.dynamic.bias >> 16) & 0xffff); +} + +#define MAX_SERIES_BIAS 0x1000 + +inline static void SER_SET_BIAS(REBSER *s, REBCNT bias) { + assert(GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC)); + s->content.dynamic.bias = + (s->content.dynamic.bias & 0xffff) | (bias << 16); +} + +#define SER_ADD_BIAS(s,b) \ + ((s)->content.dynamic.bias += (b << 16)) + +#define SER_SUB_BIAS(s,b) \ + ((s)->content.dynamic.bias -= (b << 16)) + +inline static size_t SER_TOTAL(REBSER *s) { + return (SER_REST(s) + SER_BIAS(s)) * SER_WIDE(s); +} + +inline static size_t SER_TOTAL_IF_DYNAMIC(REBSER *s) { + if (NOT_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC)) + return 0; + return SER_TOTAL(s); +} \ No newline at end of file diff --git a/src/include/reb-args.h b/src/include/reb-args.h deleted file mode 100644 index f406a5035d..0000000000 --- a/src/include/reb-args.h +++ /dev/null @@ -1,95 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Program startup arguments -** Module: reb-args.h -** Author: Carl Sassenrath -** Notes: -** Arg struct is used by R3 lib, so must not be modified. -** -***********************************************************************/ - -// REBOL startup option structure: -typedef struct rebol_args { - REBCNT options; - REBCHR *script; - REBCHR *args; - REBCHR *do_arg; - REBCHR *version; - REBCHR *debug; - REBCHR *import; - REBCHR *secure; - REBCHR *boot; - REBCHR *exe_path; - REBCHR *home_dir; -} REBARGS; - -// REBOL arg option flags: -// Must stay matched to system/catalog/boot-flags. -enum arg_opts { - ROF_EXT, - - ROF_SCRIPT, - ROF_ARGS, - ROF_DO, - ROF_IMPORT, - ROF_VERSION, - ROF_DEBUG, - ROF_SECURE, - - ROF_HELP, - ROF_VERS, - ROF_QUIET, - ROF_VERBOSE, - ROF_SECURE_MIN, - ROF_SECURE_MAX, - ROF_TRACE, - ROF_HALT, - ROF_CGI, - ROF_BOOT, - ROF_NO_WINDOW, - - ROF_IGNORE, // not an option -}; - -#define RO_EXT (1<C11 and +// C++98=>C++17 as well. Some extended checks are provided for these macros +// if building under various versions of C++. Also, C99 definitions are +// taken advantage of if they are available. +// + + +// +// FEATURE TESTING MACROS +// +// Feature testing macros were a Clang extension, but GCC added support for +// them. If compiler doesn't have them, default all features unavailable. +// +// http://clang.llvm.org/docs/LanguageExtensions.html#feature-checking-macros +// + +#ifndef __has_builtin + #define __has_builtin(x) 0 +#endif + +#ifndef __has_feature + #define __has_feature(x) 0 +#endif + +#ifdef __GNUC__ + #define GCC_VERSION_AT_LEAST(m, n) \ + (__GNUC__ > (m) || (__GNUC__ == (m) && __GNUC_MINOR__ >= (n))) +#else + #define GCC_VERSION_AT_LEAST(m, n) 0 +#endif + -#ifndef FALSE -#define FALSE 0 -#define TRUE (!0) +// +// TYPE TRAITS +// +// One of the most powerful tools you can get from allowing a C codebase to +// compile as C++ comes from type_traits: +// +// http://en.cppreference.com/w/cpp/header/type_traits +// +// This is essentially an embedded query language for types, allowing one to +// create compile-time errors for any C construction that isn't being used +// in the way one might want. While some static analysis tools for C offer +// their own plugins for such checks, the prevalance of the C++ standard +// and compilers that implement it make it a perfect tool for checking a C +// codebase on the fly to see if it follows certain rules. +// +#if defined(__cplusplus) && __cplusplus >= 201103L + #include #endif + +// +// STATIC ASSERT +// +// Some conditions can be checked at compile-time, instead of deferred to a +// runtime assert. This macro triggers an error message at compile time. +// `static_assert` is an arity-2 keyword in C++11 (which was expanded in +// C++17 to have an arity-1 form). This uses the name `static_assert_c` to +// implement a poor-man's version of the arity-1 form in C, that only works +// inside of function bodies. +// +// !!! This was the one being used, but review if it's the best choice: +// +// http://stackoverflow.com/questions/3385515/static-assert-in-c +// +#define static_assert_c(e) \ + do {(void)sizeof(char[1 - 2*!(e)]);} while(0) + + +// +// CONDITIONAL C++ NAME MANGLING MACRO +// +// When linking C++ code, different functions with the same name need to be +// discerned by the types of their parameters. This means their name is +// "decorated" (or "mangled") from the fairly simple and flat convention of +// a C function. +// +// https://en.wikipedia.org/wiki/Name_mangling +// +// When built as C++, Ren-C needs to inform the compiler that the functions +// it exports to the outside world should *not* use C++ name mangling, so that +// they can be called sensibly from C. This conditional macro avoids needing +// to put #ifdefs around those prototypes. +// +#if defined(__cplusplus) + #define EXTERN_C extern "C" +#else + #define EXTERN_C extern +#endif + + +// +// CASTING MACROS +// +// The following code and explanation is from "Casts for the Masses (in C)": +// +// http://blog.hostilefork.com/c-casts-for-the-masses/ +// + +#if !defined(__cplusplus) + /* These macros are easier-to-spot variants of the parentheses cast. + * The 'm_cast' is when getting [M]utablity on a const is okay (RARELY!) + * Plain 'cast' can do everything else (except remove volatile) + * The 'c_cast' helper ensures you're ONLY adding [C]onst to a value + */ + #define m_cast(t,v) ((t)(v)) + #define cast(t,v) ((t)(v)) + #define c_cast(t,v) ((t)(v)) + /* + * Q: Why divide roles? A: Frequently, input to cast is const but you + * "just forget" to include const in the result type, gaining mutable + * access. Stray writes to that can cause even time-traveling bugs, with + * effects *before* that write is made...due to "undefined behavior". + */ +#elif defined(__cplusplus) /* for gcc -Wundef */ && (__cplusplus < 201103L) + /* Well-intentioned macros aside, C has no way to enforce that you can't + * cast away a const without m_cast. C++98 builds can do that, at least: + */ + #define m_cast(t,v) const_cast(v) + #define cast(t,v) ((t)(v)) + #define c_cast(t,v) const_cast(v) +#else + /* __cplusplus >= 201103L has C++11's type_traits, where we get some + * actual power. cast becomes a reinterpret_cast for pointers and a + * static_cast otherwise. We ensure c_cast added a const and m_cast + * removed one, and that neither affected volatility. + */ + template + T m_cast_helper(V v) { + static_assert(!std::is_const::value, + "invalid m_cast() - requested a const type for output result"); + static_assert(std::is_volatile::value == std::is_volatile::value, + "invalid m_cast() - input and output have mismatched volatility"); + return const_cast(v); + } + /* reinterpret_cast for pointer to pointer casting (non-class source)*/ + template::value + && (std::is_pointer::value || std::is_pointer::value) + >::type* = nullptr> + T cast_helper(V v) { return reinterpret_cast(v); } + /* static_cast for non-pointer to non-pointer casting (non-class source) */ + template::value + && (!std::is_pointer::value && !std::is_pointer::value) + >::type* = nullptr> + T cast_helper(V v) { return static_cast(v); } + /* use static_cast on all classes, to go through their cast operators */ + template::value + >::type* = nullptr> + T cast_helper(V v) { return static_cast(v); } + template + T c_cast_helper(V v) { + static_assert(!std::is_const::value, + "invalid c_cast() - did not request const type for output result"); + static_assert(std::is_volatile::value == std::is_volatile::value, + "invalid c_cast() - input and output have mismatched volatility"); + return const_cast(v); + } + #define m_cast(t, v) m_cast_helper(v) + #define cast(t, v) cast_helper(v) + #define c_cast(t, v) c_cast_helper(v) +#endif +#if defined(NDEBUG) || !defined(REB_DEF) + /* These [S]tring and [B]inary casts are for "flips" between a 'char *' + * and 'unsigned char *' (or 'const char *' and 'const unsigned char *'). + * Being single-arity with no type passed in, they are succinct to use: + */ + #define s_cast(b) ((char *)(b)) + #define cs_cast(b) ((const char *)(b)) + #define b_cast(s) ((unsigned char *)(s)) + #define cb_cast(s) ((const unsigned char *)(s)) + /* + * In C++ (or C with '-Wpointer-sign') this is powerful. 'char *' can + * be used with string functions like strlen(). Then 'unsigned char *' + * can be saved for things you shouldn't _accidentally_ pass to functions + * like strlen(). (One GREAT example: encoded UTF-8 byte strings.) + */ +#else + /* We want to ensure the input type is what we thought we were flipping, + * particularly not the already-flipped type. Instead of type_traits, 4 + * functions check in both C and C++ (here only during Debug builds): + * (Definitions are in n-strings.c w/prototypes built by make-headers.r) + */ + #define s_cast(b) s_cast_(b) + #define cs_cast(b) cs_cast_(b) + #define b_cast(s) b_cast_(s) + #define cb_cast(s) cb_cast_(s) +#endif + + +// +// NOOP a.k.a. VOID GENERATOR +// +// Creating a void value conveniently is useful for a few reasons. One is +// that it can serve as a NO-OP and suppress a compiler warning you might +// get if you try to use just ';' to do it. Another is that there is a +// difference between C and C++ in parenthesized expressions, where +// '(foo(), bar())' will return the result of bar in C++ but not in C. +// So such a macro could be written as '(foo(), bar(), NOOP)' to avoid +// leaking the result. +// +// VOID would be a more purposeful name, but Windows headers define that +// for the type (as used in types like LPVOID) +// +#ifndef NOOP + #define NOOP \ + ((void)(0)) +#endif + + /*********************************************************************** ** ** C-Code Types @@ -52,81 +269,254 @@ #include -typedef int8_t i8; -typedef uint8_t u8; -typedef int16_t i16; -typedef uint16_t u16; -typedef int32_t i32; -typedef uint32_t u32; -typedef int64_t i64; -typedef uint64_t u64; -typedef intptr_t REBIPT; // integral counterpart of void* -typedef uintptr_t REBUPT; // unsigned counterpart of void* +typedef int8_t i8; +typedef uint8_t u8; +typedef int16_t i16; +typedef uint16_t u16; +typedef int32_t i32; +typedef uint32_t u32; +typedef int64_t i64; +typedef uint64_t u64; +typedef intptr_t REBIPT; // integral counterpart of void* +typedef uintptr_t REBUPT; // unsigned counterpart of void* #define MAX_I32 INT32_MAX #define MIN_I32 INT32_MIN #define MAX_I64 INT64_MAX #define MIN_I64 INT64_MIN +#define I8_C(c) INT8_C(c) +#define U8_C(c) UINT8_C(c) + +#define I16_C(c) INT16_C(c) +#define U16_C(c) UINT16_C(c) + +#define I32_C(c) INT32_C(c) +#define U32_C(c) UINT32_C(c) + +#define I64_C(c) INT64_C(c) +#define U64_C(c) UINT64_C(c) + #else /* C-code types: C99 definitions unavailable, do it ourselves */ -typedef char i8; -typedef unsigned char u8; -typedef short i16; -typedef unsigned short u16; +typedef signed char i8; +typedef unsigned char u8; +#define I8(c) c +#define U8(c) c + +typedef short i16; +typedef unsigned short u16; +#define I16(c) c +#define U16(c) c + #ifdef __LP64__ -typedef int i32; -typedef unsigned int u32; +typedef int i32; +typedef unsigned int u32; #else -typedef long i32; -typedef unsigned long u32; - #endif -#ifdef ODD_INT_64 // Windows VC6 nonstandard typing for 64 bits +typedef long i32; +typedef unsigned long u32; +#endif +#define I32_C(c) c +#define U32_C(c) c ## U + +#ifdef WEIRD_INT_64 // Windows VC6 nonstandard typing for 64 bits typedef _int64 i64; typedef unsigned _int64 u64; +#define I64_C(c) c ## I64 +#define U64_C(c) c ## U64 #else typedef long long i64; typedef unsigned long long u64; +#define I64_C(c) c ## LL +#define U64_C(c) c ## ULL #endif #ifdef __LLP64__ -typedef long long REBIPT; // integral counterpart of void* -typedef unsigned long long REBUPT; // unsigned counterpart of void* +typedef long long REBIPT; // integral counterpart of void* +typedef unsigned long long REBUPT; // unsigned counterpart of void* #else -typedef long REBIPT; // integral counterpart of void* -typedef unsigned long REBUPT; // unsigned counterpart of void* +typedef long REBIPT; // integral counterpart of void* +typedef unsigned long REBUPT; // unsigned counterpart of void* #endif -#define MAX_I32 ((i32)0x7fffffff) -#define MIN_I32 ((i32)0x80000000) -#ifdef HAS_LL_CONSTS -#define MAX_I64 ((i64)0x7fffffffffffffffLL) -#define MIN_I64 ((i64)0x8000000000000000LL) -#else -#define MAX_I64 ((i64)0x7fffffffffffffffI64) -#define MIN_I64 ((i64)0x8000000000000000I64) +#define MAX_I32 I32_C(0x7fffffff) +#define MIN_I32 ((i32)I32_C(0x80000000)) //compiler treats the hex literal as unsigned without casting +#define MAX_I64 I64_C(0x7fffffffffffffff) +#define MIN_I64 ((i64)I64_C(0x8000000000000000)) //compiler treats the hex literal as unsigned without casting + #endif +#define MAX_U32 U32_C(0xffffffff) +#define MAX_U64 U64_C(0xffffffffffffffff) + + +// +// BOOLEAN DEFINITION +// +// The C language defines the value 0 as false, while all non-zero things are +// considered logically true. Yet the language standard mandates that the +// comparison operators (==, !=, >, <, etc) will return either 0 or 1, and +// the C++ language standard defines conversion of its built-in boolean type +// to an integral value as either 0 or 1. +// +// This could be exploited by optimized code *IF* it could truly trust a true +// "boolean" is exactly 0 or 1. But unfortunately, C only standardized an +// actual boolean type in C99 with . Older compilers have to use +// integral types for booleans, and may wind up in situations like this: +// +// #define REBOOL int +// int My_Optimized_Function(REBOOL logic) { +// return logic << 4; // should be 16 if logic is TRUE, 0 if FALSE +// } +// int zero_or_sixteen = My_Optimized_Function(flags & SOME_BIT_FLAG); +// +// The caller may feel they are passing something that is validly "truthy" or +// "falsey", yet if the bit flag is shifted at all then the optimization won't +// be able to work. The type system will not catch the mistake, and hence +// anyone who needs logics to be 0 or 1 must inject code to enforce that +// translation, which the optimizer cannot leave out. +// +// This code takes advantage of the custom definition with a mode to build in +// that makes assignments to REBOOL reject integers entirely. It still +// allows testing via if() and the logic operations, but merely disables +// direct assignments or passing integers as parameters to bools: +// +// REBOOL b = 1 > 2; // illegal: 1 > 2 is 0 (integer) in C +// REBOOL b = LOGICAL(1 > 2); // Ren-C legal form of assignment +// +// The macro LOGICAL() lets you convert any truthy C value to a REBOOL, and +// NOT() lets you do the inverse. This is better than what was previously +// used often, a (REBOOL)cast_of_expression. And it makes it much safer to +// use ordinary `&` operations to test for flags, more succinctly even: +// +// REBOOL b = GET_FLAG(flags, SOME_FLAG_ORDINAL); +// REBOOL b = !GET_FLAG(flags, SOME_FLAG_ORDINAL); +// +// vs. +// +// REBOOL b = LOGICAL(flags & SOME_FLAG_BITWISE); // same +// REBOOL b = NOT(flags & SOME_FLAG_BITWISE); // 5 less chars +// +// (Bitwise vs. ordinal also permits initializing options by just |'ing them.) +// +// The compile-time checks for enforcing this don't lead to a working binary +// being built. Hence the source will get out of sync with the check, so a +// CI build should be added to confirm STRICT_BOOL_COMPILER_TEST works. +// + +#ifndef HAS_BOOL + // + // Some systems define a cpu-optimal BOOL already. (Of course, all of + // this should have been built into C in 1970.) But if they don't, go + // with whatever the compiler decides `int` is, as it is the default + // "speedy choice" for modern CPUs + // + typedef int BOOL; #endif -/* C-code types */ -#ifndef DEF_UINT // some systems define it, don't define it again -typedef unsigned int uint; +#ifdef STRICT_BOOL_COMPILER_TEST + // + // Force type errors on direct assignments of integers to booleans or + // vice-versa (leading to a broken executable in the process). Although + // this catches some errors that wouldn't be caught otherwise, it notably + // does not notice when a literal 0 is passed to a REBOOL, because that + // is a valid pointer value. However, this case is tested for by the + // enum method of declaration in ordinary non-Windows builds. + // + // Use a #define and not a typedef so it can be selectively overridden. + // + typedef struct Bool_Dummy { int dummy; } * DUMMYBOOL; + #define REBOOL DUMMYBOOL + #define FALSE cast(struct Bool_Dummy*, 0x6466AE99) + #define TRUE cast(struct Bool_Dummy*, 0x0421BD75) +#else + #if (defined(FALSE) && (!FALSE)) && (defined(TRUE) && TRUE) + + #if defined(TO_WINDOWS) && !((FALSE == 0) && (TRUE == 1)) + // + // The Windows API specifically mandates the value of TRUE as 1. + // If you are compiling on Windows with something that has + // predefined the constant as some other value, it will be + // inconsistent...and won't work out. + // + #error "Compiler's FALSE != 0 or TRUE != 1, invalid for Win32" + #else + // Outside of Win32, assume any C truthy/falsey definition that + // the compiler favors is all right. + #endif + + // There's a FALSE and TRUE defined and they are logically false and + // true respectively, so just use those definitions but make REBOOL + // + typedef BOOL REBOOL; + + #elif !defined(FALSE) && !defined(TRUE) + // + // An enum-based definition would prohibit the usage of TRUE and FALSE + // in preprocessor macros, but offer some amount of type safety. + // + // http://stackoverflow.com/a/23666263/211160 + // + // The tradeoff is worth it, but interferes with the hardcoded Windows + // definitions of TRUE as 1 and FALSE as 0. So only use it outside + // of Windows. + // + #ifdef TO_WINDOWS + #define FALSE 0 + #define TRUE 1 + + typedef BOOL REBOOL; + #else + typedef enum {FALSE = 0, TRUE = !FALSE} REBOOL; + #endif + + #else + // TRUE and FALSE are defined but are not their logic meanings. + // + #error "Bad TRUE and FALSE definitions in compiler environment" + #endif #endif -// Some systems define a cpu-optimal BOOL already. It is assumed that the -// R3 lib will use that same definition (so sizeof() is identical.) -// (Of course, all of this should have been built into C in 1970.) -#ifndef HAS_BOOL -typedef int BOOL; // (int is used for speed in modern CPUs) +#if defined(__cplusplus) && __cplusplus >= 199711L + // + // In the C++ build, we can help reduce confusion by making sure that + // LOGICAL and NOT are only applied to integral types. Using it on + // pointers to test if they are NULL is somewhat unclear for readability + // at the callsite, and one certainly doesn't want it on floating point. + // + template + inline static REBOOL LOGICAL(T x) { + static_assert( + std::is_same::value || std::is_integral::value, + "LOGICAL(x) can only be used on integral types" + ); + return x ? TRUE : FALSE; + } + template + inline static REBOOL NOT(T x) { + static_assert( + std::is_same::value || std::is_integral::value, + "NOT(x) can only be used on integral types" + ); + return x ? FALSE : TRUE; + } +#else + #define LOGICAL(x) \ + ((x) ? TRUE : FALSE) + #define NOT(x) \ + ((x) ? FALSE : TRUE) #endif +typedef i8 REBOOL8; // Small for struct packing (memory optimization vs CPU) + + + // Used for cases where we need 64 bits, even in 32 bit mode. // (Note: compatible with FILETIME used in Windows) #pragma pack(4) typedef struct sInt64 { - i32 l; - i32 h; + i32 l; + i32 h; } I64; #pragma pack() @@ -136,38 +526,26 @@ typedef struct sInt64 { ** ***********************************************************************/ -typedef i32 REBINT; // 32 bit (64 bit defined below) -typedef u32 REBCNT; // 32 bit (counting number) -typedef i64 REBI64; // 64 bit integer -typedef u64 REBU64; // 64 bit unsigned integer -typedef i8 REBOOL; // 8 bit flag (for struct usage) -typedef u32 REBFLG; // 32 bit flag (for cpu efficiency) -typedef float REBD32; // 32 bit decimal -typedef double REBDEC; // 64 bit decimal - -typedef unsigned char REBYTE; // unsigned byte data -typedef u16 REBUNI; // unicode char - -// REBCHR - only to refer to OS char strings (not internal strings) -#ifdef OS_WIDE_CHAR -typedef REBUNI REBCHR; -#else -typedef REBYTE REBCHR; -#endif +typedef i32 REBINT; // 32 bit (64 bit defined below) +typedef u32 REBCNT; // 32 bit (counting number) +typedef i64 REBI64; // 64 bit integer +typedef u64 REBU64; // 64 bit unsigned integer +typedef float REBD32; // 32 bit decimal +typedef double REBDEC; // 64 bit decimal -#define MAX_UNI ((1 << (8*sizeof(REBUNI))) - 1) +typedef unsigned char REBYTE; // unsigned byte data #define MIN_D64 ((double)-9.2233720368547758e18) #define MAX_D64 ((double) 9.2233720368547758e18) // Useful char constants: enum { - BEL = 7, - BS = 8, - LF = 10, - CR = 13, - ESC = 27, - DEL = 127 + BEL = 7, + BS = 8, + LF = 10, + CR = 13, + ESC = 27, + DEL = 127 }; // Used for MOLDing: @@ -180,100 +558,671 @@ enum { ** ***********************************************************************/ -#define MAX_INT_LEN 20 +#define MAX_INT_LEN 21 #define MAX_HEX_LEN 16 #ifdef ITOA64 // Integer to ascii conversion -#define INT_TO_STR(n,s) _i64toa(n, s, 10) +#define INT_TO_STR(n,s) _i64toa(n, s_cast(s), 10) #else #define INT_TO_STR(n,s) Form_Int_Len(s, n, MAX_INT_LEN) #endif #ifdef ATOI64 // Ascii to integer conversion -#define CHR_TO_INT(s) _atoi64(s) +#define CHR_TO_INT(s) _atoi64(cs_cast(s)) #else -#define CHR_TO_INT(s) strtoll(s, 0, 10) +#define CHR_TO_INT(s) strtoll(cs_cast(s), 0, 10) #endif #define LDIV lldiv #define LDIV_T lldiv_t + +// +// C FUNCTION TYPE (__cdecl) +// +// Note that you *CANNOT* cast something like a `void *` to (or from) a +// function pointer. Pointers to functions are not guaranteed to be the same +// size as to data, in either C or C++. A compiler might count the number of +// functions in your program, find less than 255, and use bytes for function +// pointers: +// +// http://stackoverflow.com/questions/3941793/ +// +// So if you want something to hold either a function pointer or a data +// pointer, you have to implement that as a union...and know what you're doing +// when writing and reading it. +// +// For info on the difference between __stdcall and __cdecl: +// +// http://stackoverflow.com/questions/3404372/ +// +// +#ifdef TO_WINDOWS + typedef void (__cdecl CFUNC)(void); +#else + typedef void (CFUNC)(void); +#endif + + +// +// TESTING IF A NUMBER IS FINITE +// +// C89 and C++98 had no standard way of testing for if a number was finite or +// not. Windows and POSIX came up with their own methods. Finally it was +// standardized in C99 and C++11: +// +// http://en.cppreference.com/w/cpp/numeric/math/isfinite +// +// The name was changed to `isfinite()`. And conforming C99 and C++11 +// compilers can omit the old versions, so one cannot necessarily fall back on +// the old versions still being there. Yet the old versions don't have +// isfinite(), so those have to be worked around here as well. +// +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + // C99 or later + #define FINITE isfinite +#elif defined(__cplusplus) && __cplusplus >= 199711L + // C++11 or later + #define FINITE isfinite +#else + // Other fallbacks... + #ifdef TO_WINDOWS + #define FINITE _finite // The usual answer for Windows + #else + #define FINITE finite // The usual answer for POSIX + #endif +#endif + + +// +// UNICODE CHARACTER TYPE +// +// REBUNI is a two-byte UCS-2 representation of a Unicode codepoint. Some +// routines once errantly conflated wchar_t with REBUNI, but a wchar_t is not +// 2 bytes on all platforms (it's 4 on GCC in 64-bit Linux, for instance). +// Routines for handling UCS-2 must be custom-coded or come from a library. +// (For example: you can't use wcslen() so Strlen_Uni() is implemented inside +// of Rebol.) +// +// Rebol is able to have its strings start out as UCS-1, with a single byte +// per character. For that it uses REBYTEs. But when you insert something +// requiring a higher codepoint, it goes to UCS-2 with REBUNI and will not go +// back (at time of writing). +// +// !!! BEWARE that several lower level routines don't do this widening, so be +// sure that you check which are which. +// +// Longer term, the growth of emoji usage in Internet communication has led +// to supporting higher "astral" codepoints as being a priority. This means +// either being able to "double-widen" to UCS-4, as is Red's strategy: +// +// http://www.red-lang.org/2012/09/plan-for-unicode-support.html +// +// Or it could also mean shifting to "UTF-8 everywhere": +// +// http://utf8everywhere.org +// + +typedef u16 REBUNI; + +#define MAX_UNI \ + ((1 << (8 * sizeof(REBUNI))) - 1) + + +// +// MEMORY POISONING and POINTER TRASHING +// +// If one wishes to indicate a region of memory as being "off-limits", modern +// tools like Address Sanitizer allow instrumented builds to augment reads +// from memory to check to see if that region is in a blacklist. +// +// These "poisoned" areas are generally sub-regions of valid malloc()'d memory +// that contain bad data. Yet they cannot be free()d because they also +// contain some good data. (Or it is merely desirable to avoid freeing and +// then re-allocating them for performance reasons, yet a debug build still +// would prefer to intercept accesses as if they were freed.) +// +// Also, in order to overwrite a pointer with garbage, the historical method +// of using 0xBADF00D or 0xDECAFBAD is formalized with TRASH_POINTER_IF_DEBUG. +// This makes the instances easier to find and standardizes how it is done. +// +#if __has_feature(address_sanitizer) + #include + + #define ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__ ((no_sanitize_address)) + + // Address sanitizer's memory poisoning must not have two + // threads both poisoning/unpoisoning the same addresses at the same time. + + #define POISON_MEMORY(reg, mem_size) \ + ASAN_POISON_MEMORY_REGION(reg, mem_size) + + #define UNPOISON_MEMORY(reg, mem_size) \ + ASAN_UNPOISON_MEMORY_REGION(reg, mem_size) +#else + // !!! @HostileFork wrote a tiny C++ "poor man's memory poisoner" that + // uses XOR to poison bits and then unpoison them back. This might be + // useful to instrument C++-based DEBUG builds on platforms that did not + // have address sanitizer (if that ever becomes interesting). + // + // http://blog.hostilefork.com/poison-memory-without-asan/ + + #define ATTRIBUTE_NO_SANITIZE_ADDRESS + + #define POISON_MEMORY(reg, mem_size) \ + NOOP + + #define UNPOISON_MEMORY(reg, mem_size) \ + NOOP +#endif + +#ifdef NDEBUG + #define TRASH_POINTER_IF_DEBUG(p) \ + NOOP + + #define TRASH_CFUNC_IF_DEBUG(p) \ + NOOP +#else + #if defined(__cplusplus) + template + inline static void TRASH_POINTER_IF_DEBUG(T* &p) { + p = reinterpret_cast(static_cast(0xDECAFBAD)); + } + + template + inline static void TRASH_CFUNC_IF_DEBUG(T* &p) { + p = reinterpret_cast(static_cast(0xDECAFBAD)); + } + + template + inline static REBOOL IS_POINTER_TRASH_DEBUG(T* p) { + return LOGICAL( + p == reinterpret_cast(static_cast(0xDECAFBAD)) + ); + } + + template + inline static REBOOL IS_CFUNC_TRASH_DEBUG(T* p) { + return LOGICAL( + p == reinterpret_cast(static_cast(0xDECAFBAD)) + ); + } + #else + #define TRASH_POINTER_IF_DEBUG(p) \ + ((p) = cast(void*, cast(REBUPT, 0xDECAFBAD))) + + #define TRASH_CFUNC_IF_DEBUG(p) \ + ((p) = cast(CFUNC*, cast(REBUPT, 0xDECAFBAD))) + + #define IS_POINTER_TRASH_DEBUG(p) \ + LOGICAL((p) == cast(void*, cast(REBUPT, 0xDECAFBAD))) + + #define IS_CFUNC_TRASH_DEBUG(p) \ + LOGICAL((p) == cast(CFUNC*, cast(REBUPT, 0xDECAFBAD))) + #endif +#endif + + +// +// MARK UNUSED VARIABLES +// +// Used in coordination with the `-Wunused-variable` setting of the compiler. +// While a simple cast to void is what people usually use for this purpose, +// there's some potential for side-effects with volatiles: +// +// http://stackoverflow.com/a/4030983/211160 +// +// The tricks suggested there for avoiding it seem to still trigger warnings +// as compilers get new ones, so assume that won't be an issue. As an +// added check, this gives the UNUSED() macro "teeth" in C++11: +// +// http://codereview.stackexchange.com/q/159439 +// +// Though the version here is more verbose, it uses the specializations to +// avoid excessive calls to memset() in the debug build. +// +#if defined(NDEBUG) || !defined(__cplusplus) || __cplusplus < 199711L + #define UNUSED(x) \ + ((void)(x)) +#else + // Can't trash the variable if it's not an lvalue. So for the basic + // SFINAE overload, just cast void. Do this also for cases that are + // lvalues, but we don't really know how to "trash" them. + // + template< + typename T, + typename TRR = typename std::remove_reference::type, + typename std::enable_if< + !std::is_lvalue_reference::value + || std::is_const::value + || ( + !std::is_pointer::value + && !std::is_arithmetic::value + && !std::is_pod::value + ) + >::type* = nullptr + > + void UNUSED(T && v) { + ((void)(v)); + } + + // For example: if you have an lvalue reference to a pointer, you can + // set it to DECAFBAD...which will likely be caught if it's a lie and it + // is getting used in the debug build. + // + template< + typename T, + typename TRR = typename std::remove_reference::type, + typename std::enable_if< + std::is_lvalue_reference::value + && !std::is_const::value + && std::is_pointer::value + >::type* = nullptr + > + void UNUSED(T && v) { + TRASH_POINTER_IF_DEBUG(v); + } + + // Any integral or floating type, set to a spam number. Use 123 just to + // avoid having to write separate handlers for all arithmetic types, as + // it fits in a signed char (but not 127), and looks a bit unnatural. + // + template< + typename T, + typename TRR = typename std::remove_reference::type, + typename std::enable_if< + std::is_lvalue_reference::value + && !std::is_const::value + && std::is_arithmetic::value + >::type* = nullptr + > + void UNUSED(T && v) { + v = 123; + } + + // It's unsafe to memory fill an arbitrary C++ class by value with + // garbage bytes, because of all the "extra" stuff in them. You can + // crash the destructor. But this is a C codebase which only occasionally + // uses C++ features in the C++ build. Most will be "Plain Old Data", + // so fill those with garbage as well. + // + // (Note: this one methodology could be applied to all pod types, + // including arithmetic and pointers, but this shows how to do it + // with custom ways and avoids function calls to memset in non-optimized + // debug builds for most cases.) + // + template< + typename T, + typename TRR = typename std::remove_reference::type, + typename std::enable_if< + std::is_lvalue_reference::value + && !std::is_const::value + && std::is_pod::value + && ( + !std::is_pointer::value + && !std::is_arithmetic::value + ) + >::type* = nullptr + > + void UNUSED(T && v) { + memset(&v, 123, sizeof(TRR)); + } +#endif + + /*********************************************************************** ** -** Address and Function Pointers +** ATTRIBUTES +** +** The __attribute__ feature is non-standard and only available +** in some compilers. Individual attributes themselves are +** also available on a case-by-case basis. +** +** Note: Placing the attribute after the prototype seems to lead +** to complaints, and technically there is a suggestion you may +** only define attributes on prototypes--not definitions: +** +** http://stackoverflow.com/q/23917031/211160 +** +** Putting the attribute *before* the prototype seems to allow +** it on both the prototype and definition in gcc, however. ** ***********************************************************************/ -#ifdef TO_WIN32 -typedef long (__stdcall *FUNCPTR)(); -typedef void(__cdecl *CFUNC)(void *); +#if defined(__clang__) || GCC_VERSION_AT_LEAST(2, 5) + #define ATTRIBUTE_NO_RETURN __attribute__ ((noreturn)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define ATTRIBUTE_NO_RETURN _Noreturn +#elif defined(_MSC_VER) + #define ATTRIBUTE_NO_RETURN __declspec(noreturn) #else -typedef long (*FUNCPTR)(); -typedef void(*CFUNC)(void *); + #define ATTRIBUTE_NO_RETURN #endif +#if __has_builtin(__builtin_unreachable) || GCC_VERSION_AT_LEAST(4, 5) + #define DEAD_END __builtin_unreachable() +#elif defined(_MSC_VER) + __declspec(noreturn) static inline void msvc_unreachable() { + while (TRUE) { } + } + #define DEAD_END msvc_unreachable() +#else + #define DEAD_END +#endif + + + +//=////////////////////////////////////////////////////////////////////////=// +// +// BIT FLAGS & MASKING +// +//=////////////////////////////////////////////////////////////////////////=// +// +// When flags are needed, the platform-natural unsigned integer is used +// (REBUPT, a `uintptr_t` equivalent). +// +// The 64-bit macro is used to get a 64-bit flag even on 32-bit platforms. +// Hence it should be stored in a REBU64 and not in a REBFLGS. +// + +typedef REBUPT REBFLGS; + +#define FLAGIT(f) \ + ((REBUPT)1 << (f)) + + // !!! These are leftovers from old code which used integers instead of +// masks to indicate flags. Using masks then it's easy enough to read using +// C's plain bit masking operators. +// +#define GET_FLAG(v,f) \ + LOGICAL((v) & (cast(REBUPT, 1) << (f))) + +#define GET_FLAGS(v,f,g) \ + LOGICAL((v) & ((cast(REBUPT, 1) << (f)) | (cast(REBUPT, 1) << (g)))) + +#define SET_FLAG(v,f) \ + cast(void, (v) |= (cast(REBUPT, 1) << (f))) + +#define CLR_FLAG(v,f) \ + cast(void, (v) &= ~(cast(REBUPT, 1) << (f))) + +#define CLR_FLAGS(v,f,g) \ + cast(void, (v) &= ~((cast(REBUPT, 1) << (f)) | (cast(REBUPT, 1) << (g)))) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// BYTE-ORDER SENSITIVE BIT FLAGS & MASKING +// +//=////////////////////////////////////////////////////////////////////////=// +// +// These macros are for purposefully arranging bit flags with respect to the +// "leftmost" and "rightmost" bytes of the underlying platform: +// +// REBFLGS flags = FLAGIT_LEFT(0); +// unsigned char *ch = (unsigned char*)&flags; +// +// In the code above, the leftmost bit of the flags has been set to 1, +// resulting in `ch == 128` on all supported platforms. +// +// Quantities smaller than a byte can be mixed in on the right with flags +// from the left. These form single optimized constants, which can be +// assigned to an integer. They can be masked or shifted out efficiently: +// +// REBFLGS flags = FLAGIT_LEFT(0) | FLAGIT_LEFT(1) | FLAGBYTE_RIGHT(13); +// +// REBCNT left = LEFT_N_BITS(flags, 3); // == 6 (binary `110`) +// REBCNT right = RIGHT_N_BITS(flags, 3); // == 5 (binary `101`) +// +// `left` gets `110` because it asked for the left 3 bits, of which only +// the first and the second had been set. +// +// `right` gets `101` because 13 was binary `1101` that was added into the +// value. Yet only the rightmost 3 bits were requested. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Note: It is simpler to not worry about the underlying bytes and just use +// ordinary bit masking. But this is used for an important feature (the +// discernment of a `void*` to a REBVAL from that of a valid UTF-8 string). +// Other tools that might be tried with this all have downsides: +// +// * bitfields arranged in a `union` with integers have no layout guarantee +// * `#pragma pack` is not standard C98 or C99...nor is any #pragma +// * `char[4]` or `char[8]` can't generally be assigned in one instruction +// + +#if defined(__LP64__) || defined(__LLP64__) + #define PLATFORM_BITS 64 +#else + #define PLATFORM_BITS 32 +#endif + +#if defined(ENDIAN_BIG) // Byte w/most significant bit first + + #define FLAGIT_LEFT(n) \ + ((REBUPT)1 << (PLATFORM_BITS - (n) - 1)) // 63,62,61.. or 32,31,30.. + + #define FLAGBYTE_FIRST(val) \ + ((REBUPT)val << (PLATFORM_BITS - 8)) // val <= 255 + + #define FLAGBYTE_RIGHT(val) \ + ((REBUPT)val) // little endian needs val <= 255 + + #define FLAGBYTE_MID(val) \ + (((REBUPT)val) << 8) // little endian needs val <= 255 + + #define FLAGUINT16_RIGHT(val) \ + ((REBUPT)val) // litte endian needs val <= 65535 + + #define RIGHT_16_BITS(flags) \ + ((flags) & 0xFFFF) + +#elif defined(ENDIAN_LITTLE) // Byte w/least significant bit first (e.g. x86) + + #define FLAGIT_LEFT(n) \ + ((REBUPT)1 << (7 + ((n) / 8) * 8 - (n) % 8)) // 7,6,5..0,15,14..8,23.. + + #define FLAGBYTE_FIRST(val) \ + ((REBUPT)val) // val <= 255 + + #define FLAGBYTE_RIGHT(val) \ + ((REBUPT)(val) << (PLATFORM_BITS - 8)) // val <= 255 + + #define FLAGBYTE_MID(val) \ + ((REBUPT)(val) << (PLATFORM_BITS - 16)) // val <= 255 + + #define FLAGUINT16_RIGHT(val) \ + ((REBUPT)(val) << (PLATFORM_BITS - 16)) + + #define RIGHT_16_BITS(flags) \ + ((flags) >> (PLATFORM_BITS - 16)) // unsigned, should zero fill left +#else + // !!! There are macro hacks which can actually make reasonable guesses + // at endianness, and should probably be used in the config if nothing is + // specified explicitly. + // + // http://stackoverflow.com/a/2100549/211160 + // + #error "ENDIAN_BIG or ENDIAN_LITTLE must be defined" +#endif + +// These specialized extractions of N bits out of the leftmost, rightmost, +// or "middle" byte (one step to the left of rightmost) can be expressed in +// a platform-agnostic way. The constructions by integer to establish these +// positions are where the the difference is. +// +// !!! It would be possible to do this with integer shifting on big endian +// in a "simpler" way, e.g.: +// +// #define LEFT_N_BITS(flags,n) ((flags) >> PLATFORM_BITS - (n)) +// +// But in addition to big endian platforms being kind of rare, it's not clear +// that would be faster than a byte operation, especially with optimization. +// + +#define LEFT_8_BITS(flags) \ + (((const REBYTE*)&flags)[0]) // reminds that 8 is faster + +#define LEFT_N_BITS(flags,n) \ + (((const REBYTE*)&flags)[0] >> (8 - (n))) // n <= 8 + +#define RIGHT_N_BITS(flags,n) \ + (((const REBYTE*)&flags)[sizeof(REBUPT) - 1] & ((1 << (n)) - 1)) // n <= 8 + +#define RIGHT_8_BITS(flags) \ + (((const REBYTE*)&flags)[sizeof(REBUPT) - 1]) // reminds that 8 is faster + +#define CLEAR_N_RIGHT_BITS(flags,n) \ + (((REBYTE*)&flags)[sizeof(REBUPT) - 1] &= ~((1 << (n)) - 1)) // n <= 8 + +#define CLEAR_8_RIGHT_BITS(flags) \ + (((REBYTE*)&flags)[sizeof(REBUPT) - 1] = 0) // reminds that 8 is faster + +#define MID_N_BITS(flags,n) \ + (((const REBYTE*)&flags)[sizeof(REBUPT) - 2] & ((1 << (n)) - 1)) // n <= 8 + +#define MID_8_BITS(flags) \ + (((const REBYTE*)&flags)[sizeof(REBUPT) - 2]) // reminds that 8 is faster + +#define CLEAR_N_MID_BITS(flags,n) \ + (((REBYTE*)&flags)[sizeof(REBUPT) - 2] &= ~((1 << (n)) - 1)) // n <= 8 + +#define CLEAR_8_MID_BITS(flags) \ + (((REBYTE*)&flags)[sizeof(REBUPT) - 2] = 0) // reminds that 8 is faster + +#define CLEAR_16_RIGHT_BITS(flags) \ + (((REBYTE*)&flags)[sizeof(REBUPT) - 1] = \ + ((REBYTE*)&flags)[sizeof(REBUPT) - 2] = 0) + + + /*********************************************************************** ** ** Useful Macros ** ***********************************************************************/ -#define FLAGIT(f) (1<<(f)) -#define GET_FLAG(v,f) (((v) & (1<<(f))) != 0) -#define GET_FLAGS(v,f,g) (((v) & ((1<<(f)) | (1<<(g)))) != 0) -#define SET_FLAG(v,f) ((v) |= (1<<(f))) -#define CLR_FLAG(v,f) ((v) &= ~(1<<(f))) -#define CLR_FLAGS(v,f,g) ((v) &= ~((1<<(f)) | (1<<(g)))) +// Skip to the specified byte but not past the provided end +// pointer of the byte string. Return NULL if byte is not found. +// +inline static const REBYTE *Skip_To_Byte( + const REBYTE *cp, + const REBYTE *ep, + REBYTE b +) { + while (cp != ep && *cp != b) cp++; + if (*cp == b) return cp; + return 0; +} + +// It is common for MIN and MAX to be defined in C to macros; and equally +// common to assume that undefining them and redefining them to something +// that acts like one would expect is "probably ok". :-/ +// +#undef MIN +#undef MAX #ifdef min -#define MIN(a,b) min(a,b) -#define MAX(a,b) max(a,b) + #define MIN(a,b) min(a,b) + #define MAX(a,b) max(a,b) #else -#define MIN(a,b) (((a) < (b)) ? (a) : (b)) -#define MAX(a,b) (((a) > (b)) ? (a) : (b)) + #define MIN(a,b) (((a) < (b)) ? (a) : (b)) + #define MAX(a,b) (((a) > (b)) ? (a) : (b)) #endif -// Memory related functions: -#define MAKE_MEM(n) malloc(n) -#define MAKE_NEW(s) malloc(sizeof(s)) -#define FREE_MEM(m) free(m) -#define CLEAR(m, s) memset((void*)(m), 0, s); -#define CLEARS(m) memset((void*)(m), 0, sizeof(*m)); -#define COPY_MEM(t,f,l) memcpy((void*)(t), (void*)(f), l) -#define MOVE_MEM(t,f,l) memmove((void*)(t), (void*)(f), l) // Byte string functions: -#define COPY_BYTES(t,f,l) strncpy((char*)t, (char*)f, l) -// For APPEND_BYTES, l is the max-size allocated for t (dest) -#define APPEND_BYTES(t,f,l) strncat((char*)t, (char*)f, MAX((l)-strlen(t)-1, 0)) -#define LEN_BYTES(s) strlen((char*)s) -#define CMP_BYTES(s,t) strcmp((char*)s, (char*)t) -#define BYTES(s) (REBYTE*)(s) - -// OS has wide char string interfaces: -#ifdef OS_WIDE_CHAR -#define OS_WIDE TRUE -#define TXT(s) (L##s) -#define COPY_STR(t,f,l) wcsncpy(t, f, l) -#define JOIN_STR(d,s,l) wcsncat(d,s,l) -#define FIND_STR(d,s) wcsstr(d,s) -#define FIND_CHR(d,s) wcschr(d,s) -#define LEN_STR(s) wcslen(s) -#define TO_OS_STR(s1,s2,l) mbstowcs(s1,s2,l) -#define FROM_OS_STR(s1,s2,l) wcstombs(s1,s2,l) +// Use these when you semantically are talking about unsigned REBYTEs +// +// (e.g. if you want to count unencoded chars in 'char *' use strlen(), and +// the reader will know that is a count of letters. If you have something +// like UTF-8 with more than one byte per character, use LEN_BYTES.) +// +// For APPEND_BYTES_LIMIT, m is the max-size allocated for d (dest) +#if defined(NDEBUG) || !defined(REB_DEF) + #define LEN_BYTES(s) \ + strlen((const char*)(s)) + #define COPY_BYTES(d,s,n) \ + strncpy((char*)(d), (const char*)(s), (n)) + #define COMPARE_BYTES(l,r) \ + strcmp((const char*)(l), (const char*)(r)) + #define APPEND_BYTES_LIMIT(d,s,m) \ + strncat((char*)d, (const char*)s, MAX((m) - strlen((char*)d) - 1, 0)) #else -// OS has UTF-8 byte string interfaces: -#define OS_WIDE FALSE -#define TXT(s) (s) -#define COPY_STR(t,f,l) strncpy(t, f, l) -#define JOIN_STR(d,s,l) strncat(d,s,l) -#define FIND_STR(d,s) strstr(d,s) -#define FIND_CHR(d,s) strchr(d,s) -#define LEN_STR(s) strlen(s) -#define TO_OS_STR(s1,s2,l) strncpy(s1,s2,l) -#define FROM_OS_STR(s1,s2,l) strncpy(s1,s2,l) + // Debug build uses function stubs to ensure you pass in REBYTE * + // (But only if building in Rebol Core, host doesn't get the exports) + #define LEN_BYTES(s) \ + LEN_BYTES_(s) + #define COPY_BYTES(d,s,n) \ + COPY_BYTES_((d), (s), (n)) + #define COMPARE_BYTES(l,r) \ + COMPARE_BYTES_((l), (r)) + #define APPEND_BYTES_LIMIT(d,s,m) \ + APPEND_BYTES_LIMIT_((d), (s), (m)) #endif -#define MAKE_STR(n) (REBCHR*)(malloc((n) * sizeof(REBCHR))) // OS chars! +#define ROUND_TO_INT(d) (REBINT)(floor((MAX(MIN_I32, MIN(MAX_I32, d))) + 0.5)) + +// Global pixel format setup for REBOL image!, image loaders, color handling, +// tuple! conversions etc. The graphics compositor code should rely on this +// setting(and do specific conversions if needed) +// +// TO_RGBA_COLOR always returns 32bit RGBA value, converts R,G,B,A +// components to native RGBA order +// +// TO_PIXEL_COLOR must match internal image! datatype byte order, converts +// R,G,B,A components to native image format +// +// C_R, C_G, C_B, C_A Maps color components to correct byte positions for +// image! datatype byte order + +#ifdef ENDIAN_BIG // ARGB pixel format on big endian systems + #define TO_RGBA_COLOR(r,g,b,a) \ + (cast(REBCNT, (r)) << 24 \ + | cast(REBCNT, (g)) << 16 \ + | cast(REBCNT, (b)) << 8 \ + | cast(REBCNT, (a))) + + #define C_A 0 + #define C_R 1 + #define C_G 2 + #define C_B 3 + + #define TO_PIXEL_COLOR(r,g,b,a) \ + (cast(REBCNT, (a)) << 24 \ + | cast(REBCNT, (r)) << 16 \ + | cast(REBCNT, (g)) << 8 \ + | cast(REBCNT, (b))) +#else + #define TO_RGBA_COLOR(r,g,b,a) \ + (cast(REBCNT, (a)) << 24 \ + | cast(REBCNT, (b)) << 16 \ + | cast(REBCNT, (g)) << 8 \ + | cast(REBCNT, (r))) + + #ifdef TO_ANDROID_ARM // RGBA pixel format on Android + #define C_R 0 + #define C_G 1 + #define C_B 2 + #define C_A 3 + + #define TO_PIXEL_COLOR(r,g,b,a) \ + (cast(REBCNT, (a)) << 24 \ + | cast(REBCNT, (b)) << 16 \ + | cast(REBCNT, (g)) << 8 \ + | cast(REBCNT, (r))) -#define ROUND_TO_INT(d) (REBINT)(floor((d) + 0.5)) + #else // BGRA pixel format on Windows + #define C_B 0 + #define C_G 1 + #define C_R 2 + #define C_A 3 + + #define TO_PIXEL_COLOR(r,g,b,a) \ + (cast(REBCNT, (a)) << 24 \ + | cast(REBCNT, (r)) << 16 \ + | cast(REBCNT, (g)) << 8 \ + | cast(REBCNT, (b))) + #endif +#endif diff --git a/src/include/reb-codec.h b/src/include/reb-codec.h deleted file mode 100644 index dd05889056..0000000000 --- a/src/include/reb-codec.h +++ /dev/null @@ -1,91 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: REBOL Codec Definitions -** Module: reb-codec.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ - -#define CODI_DEFINED - -// Codec image interface: -// -// If your codec routine returns CODI_IMAGE, it is expected that the -// ->bits field contains a block of memory allocated with Make_Mem -// of size (->w * ->h * 4). This will be freed by the -// REBNATIVE(do_codec) in n-system.c -// -// If your codec routine returns CODI_BINARY, it is -// expected that the ->data field contains a block of memory -// allocated with Make_Mem of size ->len. This will be freed by -// the REBNATIVE(do_codec) in n-system.c -// -// If your codec routine returns CODI_TEXT, it is -// expected that the ->data field is 3rd input binary! argument in -// the REBNATIVE(do_codec) in n-system.c -// so the deallocation is left to GC -// -typedef struct reb_codec_image { - int action; - int w; - int h; - int len; - int alpha; - unsigned char *data; - union { - u32 *bits; - void *other; - }; - int error; -} REBCDI; - -typedef REBINT (*codo)(REBCDI *cdi); - -// Media types: -enum { - CODI_ERROR, - CODI_CHECK, // error code is inverted result (IDENTIFY) - CODI_BINARY, - CODI_TEXT, - CODI_IMAGE, - CODI_SOUND, - CODI_BLOCK, -}; - -// Codec commands: -enum { - CODI_IDENTIFY, - CODI_DECODE, - CODI_ENCODE, -}; - -// Codec errors: -enum { - CODI_ERR_NA = 1, // Feature not available - CODI_ERR_NO_ACTION, // Requested action unknown - CODI_ERR_ENCODING, // Encoding method not supported - CODI_ERR_SIGNATURE, // Header signature is not correct - CODI_ERR_BIT_LEN, // Bit length is not supported - CODI_ERR_BAD_TABLE, // Image tables are wrong - CODI_ERR_BAD_DATA, // Generic -}; diff --git a/src/include/reb-config.h b/src/include/reb-config.h index 20de1dccba..fc39eadf41 100644 --- a/src/include/reb-config.h +++ b/src/include/reb-config.h @@ -1,220 +1,270 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: General build configuration -** Module: reb-config.h -** Author: Carl Sassenrath -** Notes: -** This is the first file included. -** -***********************************************************************/ - -// The TO_ define comes from -DTO_ in gcc cmd line +// +// File: %reb-config.h +// Summary: "General build configuration" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This is the first file included. It is included by both +// reb-host.h and sys-core.h, and all Rebol code can include +// one (and only one) of those...based on whether the file is +// part of the core or in the "host". +// +// Many of the flags controlling the build (such as +// the TO_ definitions) come from -DTO_ in the +// compiler command-line. These command lines are generally +// produced automatically, based on the build that is picked +// from %systems.r. +// +// However, some flags require the preprocessor's help to +// decide if they are relevant, for instance if they involve +// detecting features of the compiler while it's running. +// Or they may adjust a feature so narrowly that putting it +// into the system configuration would seem unnecessary. +// +// Over time, this file should be balanced and adjusted with +// %systems.r in order to make the most convenient and clear +// build process. If there is difficulty in making a build +// work on a system, use that as an opportunity to reflect +// how to make this better. +// + /** Primary Configuration ********************************************** The primary target system is defined by: - TO_target - for example TO_WIN32 or TO_LINUX + TO_(os-base) - for example TO_WINDOWS or TO_LINUX + TO_(os-name) - for example TO_WINDOWS_X86 or TO_LINUX_X64 The default config builds an R3 HOST executable program. To change the config, host-kit developers can define: - REB_EXT - build an extension module - * create a DLL, not a host executable - * do not export a host lib (OS_ lib) - * call r3lib via struct and macros + REB_EXT - build an extension module + * create a DLL, not a host executable + * do not export a host lib (OS_ lib) + * call r3lib via struct and macros - REB_CORE - build /core only, no graphics, windows, etc. + REB_CORE - build /core only, no graphics, windows, etc. Special internal defines used by RT, not Host-Kit developers: - REB_API - build r3lib as API - * export r3lib functions - * build r3lib dispatch table - * call host lib (OS_) via struct and macros - - REB_EXE - build r3 as a standalone executable - - REB_DEF - special includes, symbols, and tables + REB_API - build r3lib as API + * export r3lib functions + * build r3lib dispatch table + * call host lib (OS_) via struct and macros -These are now obsolete (as of A107) and should be removed: + REB_EXE - build r3 as a standalone executable - REB_LIB - CORE_ONLY - REBOL_ONLY - FULL_DEFS - AS_LIB + REB_DEF - special includes, symbols, and tables */ //* Common ************************************************************* -#define THREADED // enable threads -#ifdef REB_EXE // standalone exe from RT -#define RL_API +#ifdef REB_EXE + // standalone exe from RT + // Export all of the APIs such that they can be referenced by extensions. + // The purpose is to have one exe and some dynamic libraries for extensions (.dll, .so etc.) + #define RL_API API_EXPORT #else -#ifdef REB_API // r3lib dll from RT -#define RL_API API_EXPORT -#else -#define RL_API API_IMPORT // for host exe (not used for extension dlls) -#endif + #ifdef REB_API + // r3lib dll from RT + #define RL_API API_EXPORT + #elif defined(EXT_DLL) || defined(REB_HOST) + // Building extensions as external libraries (.dll, .so etc.) + // or r3 host against r3lib dll + #define RL_API API_IMPORT + #else + // Extensions are builtin + #define RL_API + #endif #endif -//* MS Windows 32 ****************************************************** -#ifdef TO_WIN32 // Win32/Intel -#define WIN32_LEAN_AND_MEAN // trim down the Win32 headers -#define ENDIAN_LITTLE // uses little endian byte order -#define OS_WIDE_CHAR // OS uses WIDE_CHAR API -#define OS_CRLF TRUE // uses CRLF as line terminator -#define OS_DIR_SEP '\\' // file path separator (Thanks Bill.) -#define HAS_ASYNC_DNS // supports it -#define ATOI // supports it -#define ATOI64 // supports it -#define ITOA64 // supports it -#define NO_TTY_ATTRIBUTES // used in read-line.c -#define FINITE _finite // name used for it -#define INLINE __inline // name used for it +//* MS Windows ******************************************************** -#ifdef THREADED -#ifndef __MINGW32__ -#define THREAD __declspec(thread) +#ifdef TO_WINDOWS_X86 #endif + +#ifdef TO_WINDOWS_X64 #endif -// Used when we build REBOL as a DLL: -#define API_EXPORT __declspec(dllexport) -#define API_IMPORT __declspec(dllimport) +#ifdef TO_WINDOWS + #define OS_DIR_SEP '\\' // file path separator (Thanks Bill.) + #define OS_CRLF TRUE // uses CRLF as line terminator -// Use non-standard int64 declarations: -#if (defined(_MSC_VER) && (_MSC_VER <= 1200)) -#define ODD_INT_64 -#else -#define HAS_LL_CONSTS -#endif + #if (defined(_MSC_VER) && (_MSC_VER <= 1200)) + #define WEIRD_INT_64 // non-standard MSVC int64 declarations + #else + #define HAS_LL_CONSTS + #endif -// Disable various warnings -#pragma warning(disable : 4201) // nameless unions -#pragma warning(disable : 4100) // unreferenced formal parameter -#pragma warning(disable : 4127) // conditional expression is constant -#pragma warning(disable : 4244) // float conversion - temporary -//#pragma warning(disable : 4057) -//#pragma warning(disable : 4701) + #define OS_WIDE_CHAR // wchar_t used strings passed to OS API + #include -#define AGG_WIN32_FONTS //use WIN32 api for font handling -#else + // ASCII strings to Integer + #define ATOI // supports it + #define ATOI64 // supports it + #define ITOA64 // supports it -//* Non Windows ******************************************************** + #define HAS_ASYNC_DNS // supports it -#define MIN_OS // not all devices are working -#define NO_GRAPHICS // no graphics yet -#define AGG_FREETYPE //use freetype2 library for fonts by default -#define FINITE finite -#define INLINE + #define NO_TTY_ATTRIBUTES // used in read-line.c -#ifndef TO_HAIKU -// Unsupported by gcc 2.95.3-haiku-121101 -#define API_EXPORT __attribute__((visibility("default"))) + // Used when we build REBOL as a DLL: + #define API_EXPORT __declspec(dllexport) + #define API_IMPORT __declspec(dllimport) + + #define WIN32_LEAN_AND_MEAN // trim down the Win32 headers #else -#define API_EXPORT -#define DEF_UINT -#endif + #define OS_DIR_SEP '/' // rest of the world uses it + #define OS_CRLF 0 // just LF in strings + + #define API_IMPORT + // Note: Unsupported by gcc 2.95.3-haiku-121101 + // (We #undef it in the Haiku section) + #define API_EXPORT __attribute__((visibility("default"))) +#endif + + +//* Linux ******************************************************** -#define API_IMPORT +#ifdef TO_LINUX_X86 #endif -#ifdef TO_LINUX // Linux/Intel -#define ENDIAN_LITTLE -#define HAS_LL_CONSTS +#ifdef TO_LINUX_X64 #endif -#ifdef TO_LINUX_PPC // Linux/PPC -#define ENDIAN_BIG -#define HAS_LL_CONSTS +#ifdef TO_LINUX_PPC #endif -#ifdef TO_LINUX_ARM // Linux/ARM -#define ENDIAN_LITTLE -#define HAS_LL_CONSTS +#ifdef TO_LINUX_ARM +#endif + +#ifdef TO_LINUX_AARCH64 #endif #ifdef TO_LINUX_MIPS -#define ENDIAN_LITTLE -#define HAS_LL_CONSTS #endif -#ifdef TO_HAIKU // same as Linux/Intel seems to work -#define ENDIAN_LITTLE -#define HAS_LL_CONSTS +#ifdef TO_LINUX + #define HAS_POSIX_SIGNAL + + // !!! The Atronix build introduced a differentiation between + // a Linux build and a POSIX build, and one difference is the + // usage of some signal functions that are not available if + // you compile with a strict --std=c99 switch: + // + // http://stackoverflow.com/a/22913324/211160 + // + // Yet it appears that defining _POSIX_C_SOURCE is good enough + // to get it working in --std=gnu99. Because there are some + // other barriers to pure C99 for the moment in the additions + // from Saphirion (such as the use of alloca()), backing off the + // pure C99 and doing it this way for now. + // + // These files may not include reb-config.h as the first include, + // so be sure to say: + // + // #define _POSIX_C_SOURCE 199309L + // + // ...at the top of the file. + + #define PROC_EXEC_PATH "/proc/self/exe" +#endif + + +//* Mac OS/X ******************************************************** + +#ifdef TO_OSX_PPC #endif -#ifdef TO_OSXI // OSX/Intel -#define ENDIAN_LITTLE -#define HAS_LL_CONSTS +#ifdef TO_OSX_X86 #endif -#ifdef TO_OSX // OSX/PPC -#define ENDIAN_BIG -#define HAS_LL_CONSTS -#define OLD_COMPILER +#ifdef TO_OSX_X64 #endif -#ifdef TO_FREEBSD -#define ENDIAN_LITTLE -#define HAS_LL_CONSTS + +//* Android ***************************************************** + +#ifdef TO_ANDROID_ARM #endif -#ifdef TO_OPENBSD -#define ENDIAN_LITTLE -#define HAS_LL_CONSTS +#ifdef TO_ANDROID + #define PROC_EXEC_PATH "/proc/self/exe" #endif -#ifdef TO_OBSD // OpenBSD -#define COPY_STR(d,s,m) strlcpy(d,s,m) -#define JOIN_STR(d,s,m) strlcat(d,s,m) + +//* BSD ******************************************************** + +#ifdef TO_FREEBSD_X86 #endif -#ifdef TO_AMIGA // Target for OS4 -#define ENDIAN_BIG -#define HAS_BOOL -#define HAS_LL_CONSTS -#define HAS_SMART_CONSOLE -#define NO_DL_LIB +#ifdef TO_FREEBSD_X64 #endif +#ifdef TO_FREEBSD + #define HAVE_PROC_PATHNAME +#endif -//* Defaults *********************************************************** +#ifdef TO_NETBSD + #define PROC_EXEC_PATH "/proc/curproc/exe" +#endif -#ifndef THREAD -#define THREAD +#ifdef TO_OPENBSD #endif -#ifndef OS_DIR_SEP -#define OS_DIR_SEP '/' // rest of the world uses it + +//* HaikuOS ******************************************************** + +#ifdef TO_HAIKU + #undef API_EXPORT + #define API_EXPORT + + #define DEF_UINT #endif -#ifndef OS_CRLF -#define OS_CRLF FALSE + +//* Amiga ******************************************************** + +// Note: The Amiga target is kept for its historical significance. +// Rebol required Amiga OS4 to be able to run, and the only +// machines that could run it had third-party add-on boards with +// PowerPC processors. Hence stock machines like the Amiga4000 +// which had a Motorola 68040 cannot built Rebol. +// +// To date, there has been no success reported in building Rebol +// for an Amiga emulator. The last known successful build on +// Amiga hardware is dated 5-Mar-2011 + +#ifdef TO_AMIGA + #define HAS_BOOL + #define HAS_SMART_CONSOLE + #define NO_DL_LIB #endif diff --git a/src/include/reb-defs.h b/src/include/reb-defs.h index e8553fcd7e..05627b839e 100644 --- a/src/include/reb-defs.h +++ b/src/include/reb-defs.h @@ -1,78 +1,127 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Miscellaneous structures and definitions -** Module: reb-defs.h -** Author: Carl Sassenrath -** Notes: -** This file is used by internal and external C code. It -** should not depend on many other header files prior to it. -** -***********************************************************************/ +// +// File: %reb-defs.h +// Summary: "Miscellaneous structures and definitions" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This file is used by internal and external C code. It should not depend +// on any other include files before it. +// +// If REB_DEF is defined, it expects full definitions of the structures behind +// REBVAL and REBSER. If not, then it treats them opaquely. The reason this +// is done in a single file with an #ifdef as opposed to just doing the +// opaque definitions in %reb-ext.h (and not including %reb-defs.h there) is +// because of %a-lib.c - which wants to use the non-opaque definitions to +// implement the API while still having the various enums in %reb-ext.h +// available to the compiler. +// #ifndef REB_DEFS_H // due to sequences within the lib build itself #define REB_DEFS_H -#ifndef REB_DEF -typedef void *REBSER; -typedef void *REBOBJ; -#endif +// +// Forward declarations of the series subclasses defined in %sys-series.h +// Because the Reb_Series structure includes a Reb_Value by value, it +// must be included *after* %sys-value.h +// +#ifdef REB_DEF + struct Reb_Value; + #define RELVAL struct Reb_Value // maybe IS_RELATIVE() + + #ifdef __cplusplus + #define REBVAL struct Reb_Specific_Value // guaranteed IS_SPECIFIC() + #else + #define REBVAL struct Reb_Value // IS_SPECIFIC(), unchecked + #endif + + struct Reb_Series; // Rebol series node + typedef struct Reb_Series REBSER; + + // UTF-8 Everywhere series (used for WORD!s only ATM) + typedef REBSER REBSTR; + + struct Reb_Array; // REBSER containing REBVALs ("Rebol Array") + typedef struct Reb_Array REBARR; + + struct Reb_Context; // parallel REBARR key/var arrays + ANY-CONTEXT! value + typedef struct Reb_Context REBCTX; + + struct Reb_Func; // function parameters + FUNCTION! value + typedef struct Reb_Func REBFUN; + + struct Reb_Map; // REBARR listing key/value pairs with hash + typedef struct Reb_Map REBMAP; + + struct Reb_Frame; // Non-GC'd raw call frame, see %sys-frame.h + typedef struct Reb_Frame REBFRM; -#pragma pack(4) - -// X/Y coordinate pair as floats: -typedef struct rebol_xy_float { - float x; - float y; -} REBXYF; - -// X/Y coordinate pair as integers: -typedef struct rebol_xy_int { - int x; - int y; -} REBXYI; - -#define REBPAR REBXYI // temporary until all sources are converted - -// Standard date and time: -typedef struct rebol_dat { - int year; - int month; - int day; - int time; - int nano; - int zone; -} REBOL_DAT; // not same as REBDAT - -// OS metrics: (not used as of A100!) -typedef struct rebol_met { - int len; // # entries in this table - REBPAR screen_size; - REBPAR title_size; - REBPAR border_size; - REBPAR border_fixed; - REBPAR work_origin; - REBPAR work_size; -} X_REBOL_OS_METRICS; - -#pragma pack() + struct Reb_Binder; // used as argument in %tmp-funcs.h, needs forward decl + + struct Reb_Path_Value_State; + typedef struct Reb_Path_Value_State REBPVS; + + typedef REBINT (*REBPEF)(REBPVS *pvs); // Path evaluator function + + typedef REBINT (*REBCTF)(const RELVAL *a, const RELVAL *b, REBINT s); + + // A standard integer is currently used to represent the data stack + // pointer. `unsigned int` instead of a `REBCNT` in order to leverage the + // native performance of the integer type unconstrained by bit size, as + // data stack pointers are not stored in REBVALs or similar, and + // performance in comparing and manipulation is more important than size. + // + // Note that a value of 0 indicates an empty stack; the [0] entry is made + // to be alerting trash to trap invalid reads or writes of empty stacks. + // + typedef unsigned int REBDSP; + struct Reb_Chunk; + struct Reb_Chunker; + + struct Reb_Node; + typedef struct Reb_Node REBNOD; + + typedef struct Reb_Node REBSPC; + + #define END \ + ((const REBVAL*)&PG_End_Node) // sizeof(REBVAL) but not NODE_FLAG_CELL +#else + // The %reb-xxx.h files define structures visible to host code (client) + // which don't also require pulling in all of the %sys-xxx.h files and + // dependencies. Some of these definitions are shared with the core, + // and mention things like REBVAL. When building as core that's fine, + // but when building as host this will be undefined unless something + // is there. Define as a void so that it can point at it, but not know + // anything else about it (including size). + // + // Note: R3-Alpha allowed stack instantiation of values as "RXIARG". But + // the Ren-C API version of REBVAL* needs to know where all the values are + // so they can be GC managed, since external clients are not expected to + // know how to do that. Hence the values are opaque. + // + typedef void REBVAL; + typedef void REBFRM; +#endif #endif diff --git a/src/include/reb-device.h b/src/include/reb-device.h index 00aafba866..26684785a8 100644 --- a/src/include/reb-device.h +++ b/src/include/reb-device.h @@ -1,113 +1,144 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: External REBOL Devices (OS Independent) -** Module: reb-device.h -** Author: Carl Sassenrath -** Notes: -** Critical: all struct alignment must be 4 bytes (see compile options) -** -***********************************************************************/ +// +// File: %reb-device.h +// Summary: "External REBOL Devices (OS Independent)" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Critical: all struct alignment must be 4 bytes (see compile options) +// + +#ifdef HAS_POSIX_SIGNAL +#include +#endif + +#include +#include "assert-fixes.h" // REBOL Device Identifiers: // Critical: Must be in same order as Device table in host-device.c enum { - RDI_SYSTEM, - RDI_STDIO, - RDI_CONSOLE, - RDI_FILE, - RDI_EVENT, - RDI_NET, - RDI_DNS, - RDI_CLIPBOARD, - RDI_MAX, - RDI_LIMIT = 32 + RDI_SYSTEM, + RDI_STDIO, + RDI_CONSOLE, + RDI_FILE, + RDI_EVENT, + RDI_NET, + RDI_DNS, + RDI_CLIPBOARD, + RDI_SERIAL, +#ifdef HAS_POSIX_SIGNAL + RDI_SIGNAL, +#endif + RDI_MAX, + RDI_LIMIT = 32 }; // REBOL Device Commands: enum { - RDC_INIT, // init device driver resources - RDC_QUIT, // cleanup device driver resources + RDC_DEVREQ_SIZE,// Find out the devreq size + RDC_INIT, // init device driver resources + RDC_QUIT, // cleanup device driver resources - RDC_OPEN, // open device unit (port) - RDC_CLOSE, // close device unit + RDC_OPEN, // open device unit (port) + RDC_CLOSE, // close device unit - RDC_READ, // read from unit - RDC_WRITE, // write to unit + RDC_READ, // read from unit + RDC_WRITE, // write to unit - RDC_POLL, // check for activity - RDC_CONNECT, // connect (in or out) + RDC_POLL, // check for activity + RDC_CONNECT, // connect (in or out) - RDC_QUERY, // query unit info - RDC_MODIFY, // set modes (also get modes) + RDC_QUERY, // query unit info + RDC_MODIFY, // set modes (also get modes) - RDC_CREATE, // create unit target - RDC_DELETE, // delete unit target - RDC_RENAME, - RDC_LOOKUP, - RDC_MAX, + RDC_CREATE, // create unit target + RDC_DELETE, // delete unit target + RDC_RENAME, + RDC_LOOKUP, + RDC_MAX, - RDC_CUSTOM=32 // start of custom commands + RDC_CUSTOM=32 // start of custom commands }; // Device Request (Command) Return Codes: -#define DR_PEND 1 // request is still pending -#define DR_DONE 0 // request is complete w/o errors -#define DR_ERROR -1 // request had an error +#define DR_PEND 1 // request is still pending +#define DR_DONE 0 // request is complete w/o errors +#define DR_ERROR -1 // request had an error // REBOL Device Flags and Options (bitnums): enum { - // Status flags: - RDF_INIT, // Device is initialized - RDF_OPEN, // Global open (for devs that cannot multi-open) - // Options: - RDO_MUST_INIT = 16, // Do not allow auto init (manual init required) - RDO_AUTO_POLL, // Poll device, even if no requests (e.g. interrupts) + // Status flags: + RDF_INIT, // Device is initialized + RDF_OPEN, // Global open (for devs that cannot multi-open) + // Options: + RDO_MUST_INIT = 16, // Do not allow auto init (manual init required) + RDO_AUTO_POLL, // Poll device, even if no requests (e.g. interrupts) + RDO_MAX }; // REBOL Request Flags (bitnums): enum { - RRF_OPEN, // Port is open - RRF_DONE, // Request is done (used when extern proc changes it) - RRF_FLUSH, // Flush WRITE -// RRF_PREWAKE, // C-callback before awake happens (to update port object) - RRF_PENDING, // Request is attached to pending list - RRF_ALLOC, // Request is allocated, not a temp on stack - RRF_WIDE, // Wide char IO + RRF_OPEN, // Port is open + RRF_DONE, // Request is done (used when extern proc changes it) + RRF_FLUSH, // Flush WRITE +// RRF_PREWAKE, // C-callback before awake happens (to update port object) + RRF_PENDING, // Request is attached to pending list + RRF_ALLOC, // Request is allocated, not a temp on stack + RRF_WIDE, // Wide char IO + RRF_ACTIVE, // Port is active, even no new events yet + RRF_MAX }; // REBOL Device Errors: enum { - RDE_NONE, - RDE_NO_DEVICE, // command did not provide device - RDE_NO_COMMAND, // command past end - RDE_NO_INIT, // device has not been inited + RDE_NONE, + RDE_NO_DEVICE, // command did not provide device + RDE_NO_COMMAND, // command past end + RDE_NO_INIT, // device has not been inited + RDE_MAX +}; + +enum { + RDM_NULL, // Null device + RDM_MAX }; +// Serial Parity enum { - RDM_NULL, // Null device + SERIAL_PARITY_NONE, + SERIAL_PARITY_ODD, + SERIAL_PARITY_EVEN }; -#pragma pack(4) +// Serial Flow Control +enum { + SERIAL_FLOW_CONTROL_NONE, + SERIAL_FLOW_CONTROL_HARDWARE, + SERIAL_FLOW_CONTROL_SOFTWARE +}; // Forward references: typedef struct rebol_device REBDEV; @@ -119,70 +150,115 @@ typedef i32 (*DEVICE_CMD_FUNC)(REBREQ *req); // Device structure: struct rebol_device { - char *title; // title of device - u32 version; // version, revision, release - u32 date; // year, month, day, hour - DEVICE_CMD_FUNC *commands; // command dispatch table - u32 max_command; // keep commands in bounds - REBREQ *pending; // pending requests - u32 flags; // state: open, signal - i32 req_size; // size of request struct + const char *title; // title of device + u32 version; // version, revision, release + u32 date; // year, month, day, hour + DEVICE_CMD_FUNC *commands; // command dispatch table + u32 max_command; // keep commands in bounds + REBREQ *pending; // pending requests + u32 flags; // state: open, signal }; // Inializer (keep ordered same as above) -#define DEFINE_DEV(w,t,v,c,m,s) REBDEV w = {t, v, 0, c, m, 0, 0, s} +#define DEFINE_DEV(w,t,v,c,m) REBDEV w = {t, v, 0, c, m, 0, 0} -// Request structure: // Allowed to be extended by some devices +// Request structure: // Allowed to be extended by some devices struct rebol_devreq { - u32 clen; // size of extended structure - - // Linkages: - u32 device; // device id (dev table) - REBREQ *next; // linked list (pending or done lists) - void *port; // link back to REBOL port object - union { - void *handle; // OS object - int socket; // OS identifier - int id; - }; - - // Command info: - i32 command; // command code - u32 error; // error code - u32 modes; // special modes, types or attributes - u16 flags; // request flags - u16 state; // device process flags - i32 timeout; // request timeout -// int (*prewake)(void *); // callback before awake - - // Common fields: - union { - REBYTE *data; // data to transfer - REBREQ *sock; // temp link to related socket - }; - u32 length; // length to transfer - u32 actual; // length actually transferred - - // Special fields for common IO uses: - union { - struct { - REBCHR *path; // file string (in OS local format) - i64 size; // file size - i64 index; // file index position - I64 time; // file modification time (struct) - } file; - struct { - u32 local_ip; // local address used - u32 local_port; // local port used - u32 remote_ip; // remote address - u32 remote_port; // remote port - void *host_info; // for DNS usage - } net; - }; + + // Linkages: + u32 device; // device id (dev table) + REBREQ *next; // linked list (pending or done lists) + void *port; // link back to REBOL port object + union { + void *handle; // OS object + int socket; // OS identifier + int id; + } requestee; // !!! REVIEW: Not always "receiver"? The name is + // "bad" (?) but at least unique, making it easy + // to change. See also Reb_Event->eventee + + // Command info: + i32 command; // command code + i32 error; // error code + u32 modes; // special modes, types or attributes + u16 flags; // request flags + u16 state; // device process flags + i32 timeout; // request timeout +// int (*prewake)(void *); // callback before awake + + // Common fields: + union { + REBYTE *data; // data to transfer + REBREQ *sock; // temp link to related socket + } common; + u32 length; // length to transfer + u32 actual; // length actually transferred }; -#pragma pack() + +#define AS_REBREQ(req) (&(req)->devreq) + +#ifdef HAS_POSIX_SIGNAL +struct devreq_posix_signal { + struct rebol_devreq devreq; + sigset_t mask; // signal mask +}; + +#if !defined(NDEBUG) +#define DEVREQ_POSIX_SIGNAL(req) (assert(req->device == RDI_SIGNAL), cast(struct devreq_posix_signal*, req)) +#else +#define DEVREQ_POSIX_SIGNAL(req) cast(struct devreq_posix_signal*, req) +#endif +#endif + +struct devreq_file { + struct rebol_devreq devreq; + REBCHR *path; // file string (in OS local format) + i64 size; // file size + i64 index; // file index position + I64 time; // file modification time (struct) +}; + +struct devreq_net { + struct rebol_devreq devreq; + u32 local_ip; // local address used + u32 local_port; // local port used + u32 remote_ip; // remote address + u32 remote_port; // remote port + void *host_info; // for DNS usage + }; + +struct devreq_serial { + struct rebol_devreq devreq; + REBCHR *path; //device path string (in OS local format) + void *prior_attr; // termios: retain previous settings to revert on close + i32 baud; // baud rate of serial port + u8 data_bits; // 5, 6, 7 or 8 + u8 parity; // odd, even, mark or space + u8 stop_bits; // 1 or 2 + u8 flow_control; // hardware or software +}; + +inline static struct devreq_file* DEVREQ_FILE(struct rebol_devreq *req) { + assert(req->device == RDI_FILE); + return cast(struct devreq_file*, req); +} + +inline static struct devreq_net *DEVREQ_NET(struct rebol_devreq *req) { + assert(req->device == RDI_NET || req->device == RDI_DNS); + return cast(struct devreq_net*, req); +} + +inline static struct devreq_serial *DEVREQ_SERIAL(struct rebol_devreq *req) { + assert(req->device == RDI_SERIAL); + return cast(struct devreq_serial*, req); +} // Simple macros for common OPEN? test (for some but not all ports): -#define SET_OPEN(r) SET_FLAG(((REBREQ*)(r))->flags, RRF_OPEN) -#define SET_CLOSED(r) CLR_FLAG(((REBREQ*)(r))->flags, RRF_OPEN) -#define IS_OPEN(r) GET_FLAG(((REBREQ*)(r))->flags, RRF_OPEN) +#define SET_OPEN(r) SET_FLAG(((REBREQ*)(r))->flags, RRF_OPEN) +#define SET_CLOSED(r) CLR_FLAG(((REBREQ*)(r))->flags, RRF_OPEN) +#define IS_OPEN(r) GET_FLAG(((REBREQ*)(r))->flags, RRF_OPEN) + +#define OS_ENA -1 +#define OS_EINVAL -2 +#define OS_EPERM -3 +#define OS_ESRCH -4 diff --git a/src/include/reb-dtoa.h b/src/include/reb-dtoa.h index fe7c0afe1b..0ac5e78e42 100644 --- a/src/include/reb-dtoa.h +++ b/src/include/reb-dtoa.h @@ -1,40 +1,31 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** REBOL is a trademark of REBOL Technologies -** -** Copyright 2013 Saphirion AG -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Settings for the f-dtoa.c file -** Author: Ladislav Mecir -** Notes: -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ +// +// File: %reb-dtoa.h +// Summary: "Settings for the f-dtoa.c file" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 Saphirion AG +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// /* dtoa needs float.h */ #include @@ -48,9 +39,9 @@ #define strtod STRTOD /* #define IEEE_8087 for IEEE-arithmetic machines where the least - * significant byte has the lowest address. + * significant byte has the lowest address. * #define IEEE_MC68k for IEEE-arithmetic machines where the most - * significant byte has the lowest address. */ + * significant byte has the lowest address. */ #ifdef ENDIAN_LITTLE #define IEEE_8087 #else @@ -66,143 +57,143 @@ /* #define VAX for VAX-style floating-point arithmetic (D_floating). */ /* #define No_leftright to omit left-right logic in fast floating-point - * computation of dtoa. This will cause dtoa modes 4 and 5 to be - * treated the same as modes 2 and 3 for some inputs. */ + * computation of dtoa. This will cause dtoa modes 4 and 5 to be + * treated the same as modes 2 and 3 for some inputs. */ /* #define Honor_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 - * and strtod and dtoa should round accordingly. Unless Trust_FLT_ROUNDS - * is also #defined, fegetround() will be queried for the rounding mode. - * Note that both FLT_ROUNDS and fegetround() are specified by the C99 - * standard (and are specified to be consistent, with fesetround() - * affecting the value of FLT_ROUNDS), but that some (Linux) systems - * do not work correctly in this regard, so using fegetround() is more - * portable than using FLT_ROUNDS directly. */ + * and strtod and dtoa should round accordingly. Unless Trust_FLT_ROUNDS + * is also #defined, fegetround() will be queried for the rounding mode. + * Note that both FLT_ROUNDS and fegetround() are specified by the C99 + * standard (and are specified to be consistent, with fesetround() + * affecting the value of FLT_ROUNDS), but that some (Linux) systems + * do not work correctly in this regard, so using fegetround() is more + * portable than using FLT_ROUNDS directly. */ /* #define Check_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 - * and Honor_FLT_ROUNDS is not #defined. */ + * and Honor_FLT_ROUNDS is not #defined. */ /* #define RND_PRODQUOT to use rnd_prod and rnd_quot (assembly routines - * that use extended-precision instructions to compute rounded - * products and quotients) with IBM. */ + * that use extended-precision instructions to compute rounded + * products and quotients) with IBM. */ /* #define ROUND_BIASED for IEEE-format with biased rounding and arithmetic - * that rounds toward +Infinity. */ + * that rounds toward +Infinity. */ /* #define ROUND_BIASED_without_Round_Up for IEEE-format with biased - * rounding when the underlying floating-point arithmetic uses - * unbiased rounding. This prevent using ordinary floating-point - * arithmetic when the result could be computed with one rounding error. */ + * rounding when the underlying floating-point arithmetic uses + * unbiased rounding. This prevent using ordinary floating-point + * arithmetic when the result could be computed with one rounding error. */ /* #define Inaccurate_Divide for IEEE-format with correctly rounded - * products but inaccurate quotients, e.g., for Intel i860. */ + * products but inaccurate quotients, e.g., for Intel i860. */ /* #define NO_LONG_LONG on machines that do not have a "long long" - * integer type (of >= 64 bits). On such machines, you can - * #define Just_16 to store 16 bits per 32-bit Long when doing - * high-precision integer arithmetic. Whether this speeds things - * up or slows things down depends on the machine and the number - * being converted. If long long is available and the name is - * something other than "long long", #define Llong to be the name, - * and if "unsigned Llong" does not work as an unsigned version of - * Llong, #define #ULLong to be the corresponding unsigned type. */ + * integer type (of >= 64 bits). On such machines, you can + * #define Just_16 to store 16 bits per 32-bit Long when doing + * high-precision integer arithmetic. Whether this speeds things + * up or slows things down depends on the machine and the number + * being converted. If long long is available and the name is + * something other than "long long", #define Llong to be the name, + * and if "unsigned Llong" does not work as an unsigned version of + * Llong, #define #ULLong to be the corresponding unsigned type. */ #define Llong REBI64 #define ULLong REBU64 /* #define KR_headers for old-style C function headers. */ /* #define Bad_float_h if your system lacks a float.h or if it does not - * define some or all of DBL_DIG, DBL_MAX_10_EXP, DBL_MAX_EXP, - * FLT_RADIX, FLT_ROUNDS, and DBL_MAX. */ + * define some or all of DBL_DIG, DBL_MAX_10_EXP, DBL_MAX_EXP, + * FLT_RADIX, FLT_ROUNDS, and DBL_MAX. */ #if !defined DBL_DIG || !defined DBL_MAX_10_EXP || !defined DBL_MAX_EXP || \ - ! defined FLT_RADIX || !defined FLT_ROUNDS || !defined DBL_MAX + ! defined FLT_RADIX || !defined FLT_ROUNDS || !defined DBL_MAX #define Bad_float_h #endif /* #define MALLOC your_malloc, where your_malloc(n) acts like malloc(n) - * if memory is available and otherwise does something you deem - * appropriate. If MALLOC is undefined, malloc will be invoked - * directly -- and assumed always to succeed. Similarly, if you - * want something other than the system's free() to be called to - * recycle memory acquired from MALLOC, #define FREE to be the - * name of the alternate routine. (FREE or free is only called in - * pathological cases, e.g., in a dtoa call after a dtoa return in - * mode 3 with thousands of digits requested.) */ + * if memory is available and otherwise does something you deem + * appropriate. If MALLOC is undefined, malloc will be invoked + * directly -- and assumed always to succeed. Similarly, if you + * want something other than the system's free() to be called to + * recycle memory acquired from MALLOC, #define FREE to be the + * name of the alternate routine. (FREE or free is only called in + * pathological cases, e.g., in a dtoa call after a dtoa return in + * mode 3 with thousands of digits requested.) */ #undef FREE /* #define Omit_Private_Memory to omit logic (added Jan. 1998) for making - * memory allocations from a private pool of memory when possible. - * When used, the private pool is PRIVATE_MEM bytes long: 2304 bytes, - * unless #defined to be a different length. This default length - * suffices to get rid of MALLOC calls except for unusual cases, - * such as decimal-to-binary conversion of a very long string of - * digits. The longest string dtoa can return is about 751 bytes - * long. For conversions by strtod of strings of 800 digits and - * all dtoa conversions in single-threaded executions with 8-byte - * pointers, PRIVATE_MEM >= 7400 appears to suffice; with 4-byte - * pointers, PRIVATE_MEM >= 7112 appears adequate. */ + * memory allocations from a private pool of memory when possible. + * When used, the private pool is PRIVATE_MEM bytes long: 2304 bytes, + * unless #defined to be a different length. This default length + * suffices to get rid of MALLOC calls except for unusual cases, + * such as decimal-to-binary conversion of a very long string of + * digits. The longest string dtoa can return is about 751 bytes + * long. For conversions by strtod of strings of 800 digits and + * all dtoa conversions in single-threaded executions with 8-byte + * pointers, PRIVATE_MEM >= 7400 appears to suffice; with 4-byte + * pointers, PRIVATE_MEM >= 7112 appears adequate. */ /* #define NO_INFNAN_CHECK if you do not wish to have INFNAN_CHECK - * #defined automatically on IEEE systems. On such systems, - * when INFNAN_CHECK is #defined, strtod checks - * for Infinity and NaN (case insensitively). On some systems - * (e.g., some HP systems), it may be necessary to #define NAN_WORD0 - * appropriately -- to the most significant word of a quiet NaN. - * (On HP Series 700/800 machines, -DNAN_WORD0=0x7ff40000 works.) - * When INFNAN_CHECK is #defined and No_Hex_NaN is not #defined, - * strtod also accepts (case insensitively) strings of the form - * NaN(x), where x is a string of hexadecimal digits and spaces; - * if there is only one string of hexadecimal digits, it is taken - * for the 52 fraction bits of the resulting NaN; if there are two - * or more strings of hex digits, the first is for the high 20 bits, - * the second and subsequent for the low 32 bits, with intervening - * white space ignored; but if this results in none of the 52 - * fraction bits being on (an IEEE Infinity symbol), then NAN_WORD0 - * and NAN_WORD1 are used instead. */ + * #defined automatically on IEEE systems. On such systems, + * when INFNAN_CHECK is #defined, strtod checks + * for Infinity and NaN (case insensitively). On some systems + * (e.g., some HP systems), it may be necessary to #define NAN_WORD0 + * appropriately -- to the most significant word of a quiet NaN. + * (On HP Series 700/800 machines, -DNAN_WORD0=0x7ff40000 works.) + * When INFNAN_CHECK is #defined and No_Hex_NaN is not #defined, + * strtod also accepts (case insensitively) strings of the form + * NaN(x), where x is a string of hexadecimal digits and spaces; + * if there is only one string of hexadecimal digits, it is taken + * for the 52 fraction bits of the resulting NaN; if there are two + * or more strings of hex digits, the first is for the high 20 bits, + * the second and subsequent for the low 32 bits, with intervening + * white space ignored; but if this results in none of the 52 + * fraction bits being on (an IEEE Infinity symbol), then NAN_WORD0 + * and NAN_WORD1 are used instead. */ /* #define MULTIPLE_THREADS if the system offers preemptively scheduled - * multiple threads. In this case, you must provide (or suitably - * #define) two locks, acquired by ACQUIRE_DTOA_LOCK(n) and freed - * by FREE_DTOA_LOCK(n) for n = 0 or 1. (The second lock, accessed - * in pow5mult, ensures lazy evaluation of only one copy of high - * powers of 5; omitting this lock would introduce a small - * probability of wasting memory, but would otherwise be harmless.) - * You must also invoke freedtoa(s) to free the value s returned by - * dtoa. You may do so whether or not MULTIPLE_THREADS is #defined. */ + * multiple threads. In this case, you must provide (or suitably + * #define) two locks, acquired by ACQUIRE_DTOA_LOCK(n) and freed + * by FREE_DTOA_LOCK(n) for n = 0 or 1. (The second lock, accessed + * in pow5mult, ensures lazy evaluation of only one copy of high + * powers of 5; omitting this lock would introduce a small + * probability of wasting memory, but would otherwise be harmless.) + * You must also invoke freedtoa(s) to free the value s returned by + * dtoa. You may do so whether or not MULTIPLE_THREADS is #defined. */ /* #define NO_IEEE_Scale to disable new (Feb. 1997) logic in strtod that - * avoids underflows on inputs whose result does not underflow. - * If you #define NO_IEEE_Scale on a machine that uses IEEE-format - * floating-point numbers and flushes underflows to zero rather - * than implementing gradual underflow, then you must also #define - * Sudden_Underflow. */ + * avoids underflows on inputs whose result does not underflow. + * If you #define NO_IEEE_Scale on a machine that uses IEEE-format + * floating-point numbers and flushes underflows to zero rather + * than implementing gradual underflow, then you must also #define + * Sudden_Underflow. */ /* #define USE_LOCALE to use the current locale's decimal_point value. */ /* #define SET_INEXACT if IEEE arithmetic is being used and extra - * computation should be done to set the inexact flag when the - * result is inexact and avoid setting inexact when the result - * is exact. In this case, dtoa.c must be compiled in - * an environment, perhaps provided by #include "dtoa.c" in a - * suitable wrapper, that defines two functions, - * int get_inexact(void); - * void clear_inexact(void); - * such that get_inexact() returns a nonzero value if the - * inexact bit is already set, and clear_inexact() sets the - * inexact bit to 0. When SET_INEXACT is #defined, strtod - * also does extra computations to set the underflow and overflow - * flags when appropriate (i.e., when the result is tiny and - * inexact or when it is a numeric value rounded to +-infinity). */ + * computation should be done to set the inexact flag when the + * result is inexact and avoid setting inexact when the result + * is exact. In this case, dtoa.c must be compiled in + * an environment, perhaps provided by #include "dtoa.c" in a + * suitable wrapper, that defines two functions, + * int get_inexact(void); + * void clear_inexact(void); + * such that get_inexact() returns a nonzero value if the + * inexact bit is already set, and clear_inexact() sets the + * inexact bit to 0. When SET_INEXACT is #defined, strtod + * also does extra computations to set the underflow and overflow + * flags when appropriate (i.e., when the result is tiny and + * inexact or when it is a numeric value rounded to +-infinity). */ /* #define NO_ERRNO if strtod should not assign errno = ERANGE when - * the result overflows to +-Infinity or underflows to 0. */ + * the result overflows to +-Infinity or underflows to 0. */ /* #define NO_HEX_FP to omit recognition of hexadecimal floating-point - * values by strtod. */ + * values by strtod. */ /* #define NO_STRTOD_BIGCOMP (on IEEE-arithmetic systems only for now) - * to disable logic for "fast" testing of very long input strings - * to strtod. This testing proceeds by initially truncating the - * input string, then if necessary comparing the whole string with - * a decimal expansion to decide close cases. This logic is only - * used for input more than STRTOD_DIGLIM digits long (default 40). + * to disable logic for "fast" testing of very long input strings + * to strtod. This testing proceeds by initially truncating the + * input string, then if necessary comparing the whole string with + * a decimal expansion to decide close cases. This logic is only + * used for input more than STRTOD_DIGLIM digits long (default 40). */ diff --git a/src/include/reb-event.h b/src/include/reb-event.h index b9eef67b73..71302b4b00 100644 --- a/src/include/reb-event.h +++ b/src/include/reb-event.h @@ -1,66 +1,100 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: REBOL event definitions -** Module: reb-event.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ - -// Note: size must be 12 bytes! +// +// File: %reb-event.h +// Summary: "REBOL event definitions" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// !!! The R3-Alpha host model and eventing system is generally deprecated +// in Ren-C, but is being kept working due to dependencies for R3/View. +// +// One change that was necessary in Ren-C was for payloads inside of REBVALs +// to be split into a 64-bit aligned portion, and a common 32-bit "extra" +// portion that would be 32-bit aligned on 32-bit platforms. This change +// was needed in order to write a common member of a union without +// disengaging the rest of the payload. +// +// That required the Reb_Event--which was previously three 32-bit quantities, +// to split its payload up. Now to get a complete event structure through +// the API, a full alias to a REBVAL is given. +// #pragma pack(4) -typedef struct rebol_event { - u8 type; // event id (mouse-move, mouse-button, etc) - u8 flags; // special flags - u8 win; // window id - u8 model; // port, object, gui, callback - u32 data; // an x/y position or keycode (raw/decoded) - union { - REBREQ *req; // request (for device events) - void *ser; // port or object - }; -} REBEVT; +struct Reb_Event { + u8 type; // event id (mouse-move, mouse-button, etc) + u8 flags; // special flags + u8 win; // window id + u8 model; // port, object, gui, callback + u32 data; // an x/y position or keycode (raw/decoded) +}; + +union Reb_Eventee { + REBREQ *req; // request (for device events) +#ifdef REB_DEF + REBSER *ser; // port or object +#else + void *ser; +#endif +}; + +typedef struct { + void *header; + union Reb_Eventee eventee; + u8 type; + u8 flags; + u8 win; + u8 model; + u32 data; +#if defined(__LP64__) || defined(__LLP64__) + void *padding; +#endif +} REBEVT; // mirrors REBVAL holding a Reb_Event payload, should be compatible + +// Note: the "eventee" series and the "request" live in the REBVAL #pragma pack() // Special event flags: enum { - EVF_COPIED, // event data has been copied - EVF_HAS_XY, // map-event will work on it - EVF_DOUBLE, // double click detected - EVF_CONTROL, - EVF_SHIFT, + EVF_COPIED, // event data has been copied + EVF_HAS_XY, // map-event will work on it + EVF_DOUBLE, // double click detected + EVF_CONTROL, + EVF_SHIFT, + EVF_MAX }; // Event port data model enum { - EVM_DEVICE, // I/O request holds the port pointer - EVM_PORT, // event holds port pointer - EVM_OBJECT, // event holds object frame pointer - EVM_GUI, // GUI event uses system/view/event/port - EVM_CALLBACK, // Callback event uses system/ports/callback port + EVM_DEVICE, // I/O request holds the port pointer + EVM_PORT, // event holds port pointer + EVM_OBJECT, // event holds object context pointer + EVM_GUI, // GUI event uses system/view/event/port + EVM_CALLBACK, // Callback event uses system/ports/callback port + EVM_MAX }; // Special messages diff --git a/src/include/reb-ext.h b/src/include/reb-ext.h index 9cf92ca66a..7124563723 100644 --- a/src/include/reb-ext.h +++ b/src/include/reb-ext.h @@ -1,163 +1,247 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Extensions Include File -** Module: reb-ext.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %reb-ext.h +// Summary: "R3-Alpha Extension Mechanism API" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// NOTE: The R3-Alpha extension mechanism and API are deprecated in Ren-C. +// +// This contains support routines for what was known in the R3-Alpha as a +// "COMMAND!". This was a way of extending Rebol using C routines that +// could be wrapped to act as a Rebol function: +// +// http://www.rebol.com/r3/docs/concepts/extensions-embedded.html +// +// Like a "Native", a "Command" is implemented as a C function. Unlike a +// native, a command cannot directly process REBVAL*s. Instead, it speaks in +// tems of something called an RXIARG--which is a very thin abstraction to +// permit interacting with some kinds of Rebol values. +// +// Operations on RXIARG values are a parallel subset to operations on REBVALs, +// but using entirely different routines and constants. So getting the +// RXI_TYPE(rxiarg) could come back with RXT_BLOCK, while VAL_TYPE(value) +// for the same item would give REB_BLOCK. This duplication is intended to +// provide a layer of abstraction so that changes to the internals (that +// Rebol natives would have to deal with) would not necessarily need to +// affect extension code. +// +// As a way of attempting to make it easier to maintain these parallel APIs, +// Rebol scripts that are part of the build process would produce things +// like the enumerated types from tables. See %make-host-ext.r +// +// For the implementation of dispatch connecting Rebol to extensions, see +// the code in %f-extension.c. +// +// Subsequent to the open-sourcing, the Ren-C initiative is not focusing on +// the REB_COMMAND model--preferring to connect the Rebol core directly as +// a library to bindings. However, as it was the only extension model +// available under closed-source Rebol, several pieces of code were built +// to depend upon it for functionality. This included the cryptography +// extensions needed for secure sockets and a large part of R3-View. +// +// Being able to quarantine the REB_COMMAND machinery to only builds that +// need it is a working objective. +// + +// Naming conventions: +// +// RL: REBOL library API function (or function access macro) +// RXI: REBOL eXtensions Interface (general constructs) +// RXA: REBOL eXtensions function Argument (value) +// RXR: REBOL eXtensions function Return types +// RXE: REBOL eXtensions Error codes +// #include "reb-defs.h" -#include "ext-types.h" - -/* Prefix naming conventions: - - RL: REBOL library API function (or function access macro) - RXI: REBOL eXtensions Interface (general constructs) - RXA: REBOL eXtensions function Argument (value) - RXR: REBOL eXtensions function Return types - RXE: REBOL eXtensions Error codes - RXC: REBOL eXtensions Callback flag - -*/ - - -// Value structure (for passing args to and from): -#pragma pack(4) -typedef union rxi_arg_val { - void *addr; - i64 int64; - double dec64; - REBXYF pair; - REBYTE bytes[8]; - struct { - i32 int32a; - i32 int32b; - }; - struct { - REBD32 dec32a; - REBD32 dec32b; - }; - struct { - void *series; - u32 index; - }; - struct { - void *image; - int width:16; - int height:16; - }; -} RXIARG; - -// For direct access to arg array: -#define RXI_COUNT(a) (a[0].bytes[0]) -#define RXI_TYPE(a,n) (a[0].bytes[n]) - -// Command function call frame: -typedef struct rxi_cmd_frame { - RXIARG args[8]; // arg values (64 bits each) -} RXIFRM; - -typedef struct rxi_cmd_context { - void *envr; // for holding a reference to your environment - REBSER *block; // block being evaluated - REBCNT index; // 0-based index of current command in block -} REBCEC; - -typedef int (*RXICAL)(int cmd, RXIFRM *args, REBCEC *ctx); - -#pragma pack() - -// Access macros (indirect access via RXIFRM pointer): -#define RXA_ARG(f,n) ((f)->args[n]) -#define RXA_COUNT(f) (RXA_ARG(f,0).bytes[0]) // number of args -#define RXA_TYPE(f,n) (RXA_ARG(f,0).bytes[n]) // types (of first 7 args) -#define RXA_REF(f,n) (RXA_ARG(f,n).int32a) - -#define RXA_INT64(f,n) (RXA_ARG(f,n).int64) -#define RXA_INT32(f,n) (i32)(RXA_ARG(f,n).int64) -#define RXA_DEC64(f,n) (RXA_ARG(f,n).dec64) -#define RXA_LOGIC(f,n) (RXA_ARG(f,n).int32a) -#define RXA_CHAR(f,n) (RXA_ARG(f,n).int32a) -#define RXA_TIME(f,n) (RXA_ARG(f,n).int64) -#define RXA_DATE(f,n) (RXA_ARG(f,n).int32a) -#define RXA_WORD(f,n) (RXA_ARG(f,n).int32a) -#define RXA_PAIR(f,n) (RXA_ARG(f,n).pair) -#define RXA_TUPLE(f,n) (RXA_ARG(f,n).bytes) -#define RXA_SERIES(f,n) (RXA_ARG(f,n).series) -#define RXA_INDEX(f,n) (RXA_ARG(f,n).index) -#define RXA_OBJECT(f,n) (RXA_ARG(f,n).addr) -#define RXA_MODULE(f,n) (RXA_ARG(f,n).addr) -#define RXA_HANDLE(f,n) (RXA_ARG(f,n).addr) -#define RXA_IMAGE(f,n) (RXA_ARG(f,n).image) -#define RXA_IMAGE_BITS(f,n) ((REBYTE *)RL_SERIES((RXA_ARG(f,n).image), RXI_SER_DATA)) -#define RXA_IMAGE_WIDTH(f,n) (RXA_ARG(f,n).width) -#define RXA_IMAGE_HEIGHT(f,n) (RXA_ARG(f,n).height) - -// Command function return values: -enum rxi_return { - RXR_UNSET, - RXR_NONE, - RXR_TRUE, - RXR_FALSE, - - RXR_VALUE, - RXR_BLOCK, - RXR_ERROR, - RXR_BAD_ARGS, - RXR_NO_COMMAND, -}; -// Used with RXI_SERIES_INFO: -enum { - RXI_SER_DATA, // pointer to data - RXI_SER_TAIL, // series tail index (length of data) - RXI_SER_SIZE, // size of series (in units) - RXI_SER_WIDE, // width of series (in bytes) - RXI_SER_LEFT, // units free in series (past tail) +// This table of types used to be automatically generated by complex scripts. +// Yet the original theory of these values is that they would be kept in a +// strict order while REB_XXX values might be rearranged for other reasons. +// While the future of the RL_API is in flux, these are now just hardcoded +// as an enum for simplicity, and the tables mapping them to Rebol types are +// built by C code in RL_Init(). +// +// !!! It was purposefully the case in R3-Alpha that not all internal REB_XXX +// types had corresponding RXT_XXX types. But its not clear that all such +// cases were excluded becaues they weren't supposed to be exported...some +// may have just not been implemented. Now that "RXIARG" is not a separate +// entity from a REBVAL, "exporting" types should be less involved. +// +// !!! Currently these are hardcoded at their "historical" values, which +// gives a feeling of how it might come to have gaps over time if this +// parallel table which tries to stay constant is kept. Though there's no +// code that could successfully link against the other changes to the API, +// so they could be compacted if need be. +// + +enum REBOL_Ext_Types +{ + RXT_0 = 0, // "void" indicator, though not technically a "datatype" + + RXT_BLANK = 1, + RXT_HANDLE = 2, + RXT_LOGIC = 3, + RXT_INTEGER = 4, + RXT_DECIMAL = 5, + RXT_PERCENT = 6, + + RXT_CHAR = 10, + RXT_PAIR = 11, + RXT_TUPLE = 12, + RXT_TIME = 13, + RXT_DATE = 14, + + RXT_WORD = 16, + RXT_SET_WORD = 17, + RXT_GET_WORD = 18, + RXT_LIT_WORD = 19, + RXT_REFINEMENT = 20, + RXT_ISSUE = 21, + + RXT_STRING = 24, + RXT_FILE = 25, + RXT_EMAIL = 26, + RXT_URL = 27, + RXT_TAG = 28, + + RXT_BLOCK = 32, + RXT_GROUP = 33, + RXT_PATH = 34, + RXT_SET_PATH = 35, + RXT_GET_PATH = 36, + RXT_LIT_PATH = 37, + + RXT_BINARY = 40, + RXT_BITSET = 41, + RXT_VECTOR = 42, + RXT_IMAGE = 43, + + RXT_GOB = 47, + RXT_OBJECT = 48, + RXT_MODULE = 49, + + RXT_MAX }; -// Error Codes (returned in result value from some API functions): -enum { - RXE_NO_ERROR, - RXE_NO_WORD, // the word cannot be found (e.g. in an object) - RXE_NOT_FUNC, // the value is not a function (for callback) - RXE_BAD_ARGS, // function arguments to not match -}; -#define SET_EXT_ERROR(v,n) ((v)->int32a = (n)) -#define GET_EXT_ERROR(v) ((v)->int32a) - -typedef struct rxi_callback_info { - u32 flags; - REBSER *obj; // object that holds the function - u32 word; // word id for function (name) - RXIARG *args; // argument list for function - RXIARG result; // result from function -} RXICBI; - -enum { - RXC_NONE, - RXC_ASYNC, // async callback - RXC_QUEUED, // pending in event queue - RXC_DONE, // call completed, structs can be freed -}; +// !!! Going forward in terms of the "RL_" API (for those who do not have +// access to the sensitive internal details of the interpreter), it will only +// speak in terms of REBVALs. So instead of proxying the volatile stack-based +// `struct Reb_Frame *`, the same protections as of a FRAME! will be given +// in the evaluator. (e.g. Through the REBVAL it will be possible to tell if +// the frame is off the stack and `fail()` instead of crashing.) +// +// (Right now this concept is in its early stages, so there are still direct +// exports of subclasses of REBSER and other things that are too bit fiddly +// to be putting in the non-internal API. This will be adapted over time as +// the API develops.) +// +// Note that REBVAL is "opaque" as far as the RL_API clients know, and they +// can only extract and adjust properties through the API. However, these +// pointers are to legitimate REBVALs. +// +typedef REBVAL RXIARG; +typedef REBVAL RXIFRM; + + +typedef unsigned char REBRXT; + + +#define RXA_COUNT(f) \ + RL_FRM_NUM_ARGS(f) + +#define RXA_ARG(f,n) \ + RL_FRM_ARG((f), (n)) + +#define RXA_REF(f,n) \ + RL_VAL_LOGIC(RL_FRM_ARG((f), (n))) + +#define RXA_TYPE(f,n) \ + RL_VAL_TYPE(RL_FRM_ARG((f), (n))) + +#define RXA_INT64(f,n) \ + RL_VAL_INT64(RL_FRM_ARG((f), (n))) + +#define RXA_INT32(f,n) \ + RL_VAL_INT32(RL_FRM_ARG((f), (n))) + +#define RXA_DEC64(f,n) \ + RL_VAL_DECIMAL(RL_FRM_ARG((f), (n))) + +#define RXA_LOGIC(f,n) \ + RL_VAL_LOGIC(RL_FRM_ARG((f), (n))) + +#define RXA_CHAR(f,n) \ + RL_VAL_CHAR(RL_FRM_ARG((f), (n))) + +#define RXA_TIME(f,n) \ + RL_VAL_TIME(RL_FRM_ARG((f), (n))) + +#define RXA_DATE(f,n) \ + RL_VAL_DATE(RL_FRM_ARG((f), (n))) + +// !!! See notes on RL_Val_Word_Canon_Or_Logic +#define RXA_WORD(f,n) \ + RL_VAL_WORD_CANON_OR_LOGIC(RL_FRM_ARG((f), (n))) + +#define RXA_PAIR(f,n) \ + RL_FRM_ARG((f), (n)) // no "pair" object, just return the value + +#define RXA_TUPLE(f,n) \ + RL_VAL_TUPLE_DATA(RL_FRM_ARG((f), (n))) + +#define RXA_SERIES(f,n) \ + RL_VAL_SERIES(RL_FRM_ARG((f), (n))) + +#define RXA_INDEX(f,n) \ + RL_VAL_INDEX(RL_FRM_ARG((f), (n))) + +#define RXA_HANDLE(f,n) \ + RL_VAL_HANDLE_POINTER(RL_FRM_ARG((f), (n))) + +#define RXA_OBJECT(f,n) \ + RL_VAL_CONTEXT(RL_FRM_ARG((f), (n))) + +#define RXA_IMAGE(f,n) \ + RL_VAL_SERIES(RL_FRM_ARG((f), (n))) + +#define RXA_IMAGE_BITS(f,n) \ + cast(REBYTE*, RL_SERIES(RXA_IMAGE(f,n), RXI_SER_DATA)) + +#define RXA_IMAGE_WIDTH(f,n) \ + RL_VAL_IMAGE_WIDE(RL_FRM_ARG((f), (n))) + +#define RXA_IMAGE_HEIGHT(f,n) \ + RL_VAL_IMAGE_HIGH(RL_FRM_ARG((f), (n))) + +#define RXA_COLOR_TUPLE(f,n) \ + (TO_RGBA_COLOR(RXA_TUPLE(f,n)[1], RXA_TUPLE(f,n)[2], RXA_TUPLE(f,n)[3], \ + RXA_TUPLE(f,n)[0] > 3 ? RXA_TUPLE(f,n)[4] : 0xff)) //always RGBA order + +#define RXI_LOG_PAIR(v) \ + {LOG_COORD_X(RL_VAL_PAIR_X_FLOAT(v)), LOG_COORD_Y(RL_VAL_PAIR_Y_FLOAT(v))} + +#define RXA_LOG_PAIR(f,n) \ + RXI_LOG_PAIR(RL_FRM_ARG((f), (n))) diff --git a/src/include/reb-file.h b/src/include/reb-file.h index baec4acda3..3a19fb3289 100644 --- a/src/include/reb-file.h +++ b/src/include/reb-file.h @@ -1,56 +1,59 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Special file device definitions -** Module: reb-file.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %reb-file.h +// Summary: "Special file device definitions" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// // RFM - REBOL File Modes enum { - RFM_READ = 0, - RFM_WRITE, - RFM_APPEND, - RFM_SEEK, - RFM_NEW, - RFM_READONLY, - RFM_TRUNCATE, - RFM_RESEEK, // file index has moved, reseek - RFM_NAME_MEM, // converted name allocated in mem - RFM_DIR = 16, + RFM_READ = 0, + RFM_WRITE, + RFM_APPEND, + RFM_SEEK, + RFM_NEW, + RFM_READONLY, + RFM_TRUNCATE, + RFM_RESEEK, // file index has moved, reseek + RFM_NAME_MEM, // converted name allocated in mem + RFM_DIR = 16, + RFM_MAX }; // RFE - REBOL File Error enum { - RFE_BAD_PATH = 1, - RFE_NO_MODES, // No file modes specified - RFE_OPEN_FAIL, // File open failed - RFE_BAD_SEEK, // Seek not supported for this file - RFE_NO_HANDLE, // File struct has no handle - RFE_NO_SEEK, // Seek action failed - RFE_BAD_READ, // Read failed (general) - RFE_BAD_WRITE, // Write failed (general) - RFE_DISK_FULL, // No space on target volume + RFE_BAD_PATH = 1, + RFE_NO_MODES, // No file modes specified + RFE_OPEN_FAIL, // File open failed + RFE_BAD_SEEK, // Seek not supported for this file + RFE_NO_HANDLE, // File struct has no handle + RFE_NO_SEEK, // Seek action failed + RFE_BAD_READ, // Read failed (general) + RFE_BAD_WRITE, // Write failed (general) + RFE_DISK_FULL, // No space on target volume + RFE_MAX }; #define MAX_FILE_NAME 1022 diff --git a/src/include/reb-filereq.h b/src/include/reb-filereq.h index 44f00bd843..ddea77a56c 100644 --- a/src/include/reb-filereq.h +++ b/src/include/reb-filereq.h @@ -1,49 +1,49 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: File requestor definitions -** Module: reb-filereq.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %reb-filereq.h +// Summary: "File requestor definitions" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #define MAX_FILE_REQ_BUF (16*1024) -#pragma pack(4) typedef struct Reb_File_Requestor { - REBCNT flags; // multi, load/save, unicode - REBCHR *title; // title of requestor - REBCHR *button; // button name - REBCHR *dir; // dir path - REBCHR *files; // buffer to hold results - REBCHR *filter; // buffer to hold results - REBINT len; // length of buffer + REBCNT flags; // multi, load/save, unicode + REBCHR *title; // title of requestor + REBCHR *button; // button name + REBCHR *dir; // dir path + REBCHR *files; // buffer to hold results + REBCHR *filter; // buffer to hold results + REBINT len; // length of buffer } REBRFR; -#pragma pack() // File Request Flags: enum { - FRF_MULTI, - FRF_SAVE, - FRF_KEEP, + FRF_MULTI, + FRF_SAVE, + FRF_KEEP, + FRF_MAX }; diff --git a/src/include/reb-gob.h b/src/include/reb-gob.h index 10eee60295..db7224d7c8 100644 --- a/src/include/reb-gob.h +++ b/src/include/reb-gob.h @@ -1,190 +1,247 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Graphical compositing objects -** Module: reb-gob.h -** Author: Carl Sassenrath -** Description: -** GOBs are lower-level graphics object used by the compositing -** and rendering system. Because a GUI can contain thousands of -** GOBs, they are designed and structured to be simple and small. -** Note that GOBs are also used for windowing. -** Warning: -** GOBs are allocated from a special pool and -** are accounted for by the standard garbage collector. -** -***********************************************************************/ - -enum GOB_FLAGS { // GOB attribute and option flags - GOBF_TOP = 0, // Top level (window or output image) - GOBF_WINDOW, // Window (parent is OS window reference) - GOBF_OPAQUE, // Has no alpha - GOBF_STATIC, // Does not change - GOBF_HIDDEN, // Is hidden (e.g. hidden window) - GOBF_RESIZE, // Can be resized - GOBF_NO_TITLE, // Has window title - GOBF_NO_BORDER, // Has window border - GOBF_DROPABLE, // Let window receive drag and drop - GOBF_TRANSPARENT, // Window is in transparent mode - GOBF_POPUP, // Window is a popup (with owner window) - GOBF_MODAL, // Modal event filtering - GOBF_ON_TOP, // The window is always on top +// +// File: %reb-gob.h +// Summary: "Graphical compositing objects" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// GOBs are lower-level graphics object used by the compositing +// and rendering system. Because a GUI can contain thousands of +// GOBs, they are designed and structured to be simple and small. +// Note that GOBs are also used for windowing. +// +// GOBs are allocated from a special pool and +// are accounted for by the standard garbage collector. +// + +// We accept GOB for the moment in Core, but not view in general... +// Ultimatley GOB represents a category of Ren/C external items that +// can participate with the system and its GC but are not part of core. +// +// Atronix repository included host-view.h while rebol open source didn't + +// #include "host-view.h" + +enum GOB_FLAGS { // GOB attribute and option flags + GOBF_0_IS_TRUE, // aligns with NODE_FLAG_NODE + GOBF_1_IS_FALSE, // aligns with NODE_FLAG_FREE + GOBF_MARK = 2, + GOBF_TOP, // Top level (window or output image) + GOBF_WINDOW, // Window (parent is OS window reference) + GOBF_OPAQUE, // Has no alpha + GOBF_STATIC, // Does not change + GOBF_HIDDEN, // Is hidden (e.g. hidden window) + GOBF_RESIZE, // Can be resized + GOBF_NO_TITLE, // Has window title + GOBF_NO_BORDER, // Has window border + GOBF_DROPABLE, // Let window receive drag and drop + GOBF_TRANSPARENT, // Window is in transparent mode + GOBF_POPUP, // Window is a popup (with owner window) + GOBF_MODAL, // Modal event filtering + GOBF_ON_TOP, // The window is always on top + GOBF_ACTIVE, // Window is active + GOBF_MINIMIZE, // Window is minimized + GOBF_MAXIMIZE, // Window is maximized + GOBF_RESTORE, // Window is restored + GOBF_FULLSCREEN, // Window is fullscreen + GOBF_MAX }; -enum GOB_STATE { // GOB state flags - GOBS_OPEN = 0, // Window is open - GOBS_ACTIVE, // Window is active - GOBS_NEW, // Gob is new to pane (old-offset, old-size wrong) +enum GOB_STATE { // GOB state flags + GOBS_OPEN = 0, // Window is open + GOBS_ACTIVE, // Window is active + GOBS_NEW, // Gob is new to pane (old-offset, old-size wrong) + GOBS_MAX }; -enum GOB_TYPES { // Types of content - GOBT_NONE = 0, - GOBT_COLOR, - GOBT_IMAGE, - GOBT_STRING, - GOBT_DRAW, - GOBT_TEXT, - GOBT_EFFECT, - GOBT_MAX +enum GOB_TYPES { // Types of content + GOBT_NONE = 0, + GOBT_COLOR, + GOBT_IMAGE, + GOBT_STRING, + GOBT_DRAW, + GOBT_TEXT, + GOBT_EFFECT, + GOBT_MAX }; -enum GOB_DTYPES { // Userdata types - GOBD_NONE = 0, - GOBD_OBJECT, - GOBD_BLOCK, - GOBD_STRING, - GOBD_BINARY, - GOBD_RESV, // unicode - GOBD_INTEGER +enum GOB_DTYPES { // Userdata types + GOBD_NONE = 0, + GOBD_OBJECT, + GOBD_BLOCK, + GOBD_STRING, + GOBD_BINARY, + GOBD_RESV, // unicode + GOBD_INTEGER, + GOBD_MAX }; - #pragma pack(4) +// These packed values for Rebol pairs are "X and Y coordinates" as "F"loat. +// (For PAIR! in Ren-C, actual pairing series are used, which +// can hold two values at full REBVAL precision (either integer or decimal) + +typedef struct { + float x; + float y; +} REBXYF; + + typedef struct rebol_gob REBGOB; -struct rebol_gob { // size: 64 bytes! - REBCNT flags; // option flags - REBCNT state; // state flags +struct rebol_gob { + struct Reb_Header header; - REBSER *pane; // List of child GOBs - REBGOB *parent; // Parent GOB (or window ptr) + REBCNT state; // state flags - REBYTE alpha; // transparency - REBYTE ctype; // content data type - REBYTE dtype; // pointer data type - REBYTE resv; // reserved +#ifdef REB_DEF + REBSER *pane; // List of child GOBs +#else + void *pane; +#endif + + REBGOB *parent; // Parent GOB (or window ptr) + + REBYTE alpha; // transparency + REBYTE ctype; // content data type + REBYTE dtype; // pointer data type + REBYTE resv; // reserved - union { - REBGOB *owner; // temp field - reused for different things - }; + REBGOB *owner; // !!! was a singular item in a union + +#ifdef REB_DEF + REBSER *content; // content value (block, string, color) + REBSER *data; // user defined data +#else + void *content; + void *data; +#endif - REBSER *content; // content value (block, string, color) - REBSER *data; // user defined data + REBXYF offset; // location + REBXYF size; + REBXYF old_offset; // prior location + REBXYF old_size; // prior size - REBXYF offset; // location - REBXYF size; - REBXYF old_offset; // prior location - REBXYF old_size; // prior size +#if defined(__LP64__) || defined(__LLP64__) + // + // Depending on how the fields are arranged, this may require padding to + // make sure the REBNOD-derived type is a multiple of 64-bits in size. + // +#endif }; #pragma pack() -#define GOB_X(g) ((g)->offset.x) -#define GOB_Y(g) ((g)->offset.y) -#define GOB_W(g) ((g)->size.x) -#define GOB_H(g) ((g)->size.y) - -#define GOB_X_INT(g) ROUND_TO_INT((g)->offset.x) -#define GOB_Y_INT(g) ROUND_TO_INT((g)->offset.y) -#define GOB_W_INT(g) ROUND_TO_INT((g)->size.x) -#define GOB_H_INT(g) ROUND_TO_INT((g)->size.y) - -#define GOB_XO(g) ((g)->old_offset.x) -#define GOB_YO(g) ((g)->old_offset.y) -#define GOB_WO(g) ((g)->old_size.x) -#define GOB_HO(g) ((g)->old_size.y) -#define GOB_XO_INT(g) ROUND_TO_INT((g)->old_offset.x) -#define GOB_YO_INT(g) ROUND_TO_INT((g)->old_offset.y) -#define GOB_WO_INT(g) ROUND_TO_INT((g)->old_size.x) -#define GOB_HO_INT(g) ROUND_TO_INT((g)->old_size.y) +typedef struct gob_window { // Maps gob to window + REBGOB *gob; + void* win; + void* compositor; +} REBGOBWINDOWS; + +#define GOB_X(g) ((g)->offset.x) +#define GOB_Y(g) ((g)->offset.y) +#define GOB_W(g) ((g)->size.x) +#define GOB_H(g) ((g)->size.y) + +#define GOB_LOG_X(g) (LOG_COORD_X((g)->offset.x)) +#define GOB_LOG_Y(g) (LOG_COORD_Y((g)->offset.y)) +#define GOB_LOG_W(g) (LOG_COORD_X((g)->size.x)) +#define GOB_LOG_H(g) (LOG_COORD_Y((g)->size.y)) + +#define GOB_X_INT(g) ROUND_TO_INT((g)->offset.x) +#define GOB_Y_INT(g) ROUND_TO_INT((g)->offset.y) +#define GOB_W_INT(g) ROUND_TO_INT((g)->size.x) +#define GOB_H_INT(g) ROUND_TO_INT((g)->size.y) + +#define GOB_LOG_X_INT(g) ROUND_TO_INT(LOG_COORD_X((g)->offset.x)) +#define GOB_LOG_Y_INT(g) ROUND_TO_INT(LOG_COORD_Y((g)->offset.y)) +#define GOB_LOG_W_INT(g) ROUND_TO_INT(LOG_COORD_X((g)->size.x)) +#define GOB_LOG_H_INT(g) ROUND_TO_INT(LOG_COORD_Y((g)->size.y)) + +#define GOB_XO(g) ((g)->old_offset.x) +#define GOB_YO(g) ((g)->old_offset.y) +#define GOB_WO(g) ((g)->old_size.x) +#define GOB_HO(g) ((g)->old_size.y) +#define GOB_XO_INT(g) ROUND_TO_INT((g)->old_offset.x) +#define GOB_YO_INT(g) ROUND_TO_INT((g)->old_offset.y) +#define GOB_WO_INT(g) ROUND_TO_INT((g)->old_size.x) +#define GOB_HO_INT(g) ROUND_TO_INT((g)->old_size.y) #define CLEAR_GOB_STATE(g) ((g)->state = 0) -#define SET_GOB_FLAG(g,f) SET_FLAG((g)->flags, f) -#define GET_GOB_FLAG(g,f) GET_FLAG((g)->flags, f) -#define CLR_GOB_FLAG(g,f) CLR_FLAG((g)->flags, f) -#define SET_GOB_STATE(g,f) SET_FLAG((g)->state, f) -#define GET_GOB_STATE(g,f) GET_FLAG((g)->state, f) -#define CLR_GOB_STATE(g,f) CLR_FLAG((g)->state, f) - -#define GOB_ALPHA(g) ((g)->alpha) -#define GOB_TYPE(g) ((g)->ctype) -#define SET_GOB_TYPE(g,t) ((g)->ctype = (t)) -#define GOB_DTYPE(g) ((g)->dtype) -#define SET_GOB_DTYPE(g,t) ((g)->dtype = (t)) -#define GOB_DATA(g) ((g)->data) -#define SET_GOB_DATA(g,v) ((g)->data = (v)) -#define GOB_TMP_OWNER(g) ((g)->owner) +#define SET_GOB_FLAG(g,f) \ + cast(void, (g)->header.bits |= (cast(REBUPT, 1) << (f))) + +#define GET_GOB_FLAG(g,f) \ + LOGICAL((g)->header.bits & (cast(REBUPT, 1) << (f))) + +#define CLR_GOB_FLAG(g,f) \ + cast(void, (g)->header.bits &= ~(cast(REBUPT, 1) << (f))) + + +#define SET_GOB_STATE(g,f) SET_FLAG((g)->state, f) +#define GET_GOB_STATE(g,f) GET_FLAG((g)->state, f) +#define CLR_GOB_STATE(g,f) CLR_FLAG((g)->state, f) + +#define GOB_ALPHA(g) ((g)->alpha) +#define GOB_TYPE(g) ((g)->ctype) +#define SET_GOB_TYPE(g,t) ((g)->ctype = (t)) +#define GOB_DTYPE(g) ((g)->dtype) +#define SET_GOB_DTYPE(g,t) ((g)->dtype = (t)) +#define GOB_DATA(g) ((g)->data) +#define SET_GOB_DATA(g,v) ((g)->data = (v)) +#define GOB_TMP_OWNER(g) ((g)->owner) #define IS_GOB_OPAQUE(g) GET_GOB_FLAG(g, GOBF_OPAQUE) #define SET_GOB_OPAQUE(g) SET_GOB_FLAG(g, GOBF_OPAQUE) #define CLR_GOB_OPAQUE(g) CLR_GOB_FLAG(g, GOBF_OPAQUE) -#define GOB_PANE(g) ((g)->pane) -#define GOB_PARENT(g) ((g)->parent) -#define GOB_CONTENT(g) ((g)->content) +#define GOB_PANE(g) ((g)->pane) +#define GOB_PARENT(g) ((g)->parent) +#define GOB_CONTENT(g) ((g)->content) // Control dependencies on series structures: #ifdef REB_DEF -#define GOB_STRING(g) SERIES_DATA(GOB_CONTENT(g)) -#define GOB_TAIL(g) SERIES_TAIL((g)->pane) -#define GOB_HEAD(g) ((REBGOB **)(SERIES_DATA(GOB_PANE(g)))) +#define GOB_STRING(g) SER_HEAD(GOB_CONTENT(g)) +#define GOB_LEN(g) SER_LEN((g)->pane) +#define SET_GOB_LEN(g,l) SET_SERIES_LEN((g)->pane, (l)) +#define GOB_HEAD(g) SER_HEAD(REBGOB*, GOB_PANE(g)) #else -#define GOB_STRING(g) ((REBYTE *)RL_Series(GOB_CONTENT(g), (REBCNT)RXI_SER_DATA)) -#define GOB_TAIL(g) ((REBCNT)RL_Series(GOB_PANE(g), (REBCNT)RXI_SER_TAIL)) -#define GOB_HEAD(g) ((REBGOB **)RL_Series(GOB_PANE(g), (REBCNT)RXI_SER_DATA)) +#define GOB_STRING(g) RL_Gob_String(g) +#define GOB_LEN(g) RL_Gob_Len(g) +#define GOB_HEAD(g) RL_Gob_Head(g) #endif -#define GOB_BITMAP(g) GOB_STRING(g) -#define GOB_SKIP(g,n) (GOB_HEAD(g)+n) +#define GOB_BITMAP(g) GOB_STRING(g) +#define GOB_AT(g,n) (GOB_HEAD(g)+n) -#define IS_WINDOW(g) (GOB_PARENT(g) == Gob_Root && GET_GOB_FLAG(g, GOBF_WINDOW)) +#define IS_WINDOW(g) (GOB_PARENT(g) == Gob_Root && GET_GOB_FLAG(g, GOBF_WINDOW)) -#define IS_GOB_COLOR(g) (GOB_TYPE(g) == GOBT_COLOR) -#define IS_GOB_DRAW(g) (GOB_CONTENT(g) && GOB_TYPE(g) == GOBT_DRAW) -#define IS_GOB_IMAGE(g) (GOB_CONTENT(g) && GOB_TYPE(g) == GOBT_IMAGE) +#define IS_GOB_COLOR(g) (GOB_TYPE(g) == GOBT_COLOR) +#define IS_GOB_DRAW(g) (GOB_CONTENT(g) && GOB_TYPE(g) == GOBT_DRAW) +#define IS_GOB_IMAGE(g) (GOB_CONTENT(g) && GOB_TYPE(g) == GOBT_IMAGE) #define IS_GOB_EFFECT(g) (GOB_CONTENT(g) && GOB_TYPE(g) == GOBT_EFFECT) #define IS_GOB_STRING(g) (GOB_CONTENT(g) && GOB_TYPE(g) == GOBT_STRING) #define IS_GOB_TEXT(g) (GOB_CONTENT(g) && GOB_TYPE(g) == GOBT_TEXT) -// GC Flags: -enum { - GOB_MARK = 1, // Gob was found during GC mark scan. - GOB_USED = 1<<1 // Gob is used (not free). -}; - -#define IS_GOB_MARK(g) ((g)->resv & GOB_MARK) -#define MARK_GOB(g) ((g)->resv |= GOB_MARK) -#define UNMARK_GOB(g) ((g)->resv &= ~GOB_MARK) -#define IS_GOB_USED(g) ((g)->resv & GOB_USED) -#define USE_GOB(g) ((g)->resv |= GOB_USED) -#define FREE_GOB(g) ((g)->resv &= ~GOB_USED) - extern REBGOB *Gob_Root; // Top level GOB (the screen) diff --git a/src/include/reb-host.h b/src/include/reb-host.h index 82ebbb889b..39085f57ea 100644 --- a/src/include/reb-host.h +++ b/src/include/reb-host.h @@ -1,42 +1,80 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Include files for hosting -** Module: reb-host.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %reb-host.h +// Summary: "Include files for hosting" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include "reb-config.h" #include "reb-c.h" -#include "reb-ext.h" // includes reb-defs.h -#include "reb-args.h" + +#ifdef STRICT_BOOL_COMPILER_TEST + // + // %reb-host.h is often used in third party code that was not written to + // use REBOOL. Hence the definitions of TRUE and FALSE used in the "fake" + // build will trip it up. We substitute in normal definitions for this + // file. See explanations of this test in %reb-c.h for more information. + // + #undef REBOOL + #define REBOOL int + #undef TRUE + #undef FALSE + #define TRUE 1 + #define FALSE 0 +#endif + +// Must be defined at the end of reb-c.h, but not *in* reb-c.h so that +// files including sys-core.h and reb-host.h can have differing +// definitions of REBCHR. (We want it opaque to the core, but the +// host to have it compatible with the native character type w/o casting) +#ifdef OS_WIDE_CHAR + typedef wchar_t REBCHR; +#else + typedef char REBCHR; +#endif + +#include "reb-ext.h" // includes reb-defs.h #include "reb-device.h" #include "reb-file.h" #include "reb-event.h" #include "reb-evtypes.h" -#include "reb-net.h" #include "reb-filereq.h" +#include "sys-rebnod.h" // !!! Legacy dependency, REBGOB should not be REBNOD #include "reb-gob.h" + #include "reb-lib.h" + +// !!! None of the above currently include anything that *necessarily* defines +// size_t. However the host-lib API currently uses it in defining its +// allocator. In order to match the signature of Alloc_Mem() and malloc(), +// we include it for the moment, but a more formal policy decision on "what +// parameter types are legal in the host API" would be ideal. +// +#include + +#include "host-lib.h" + diff --git a/src/include/reb-math.h b/src/include/reb-math.h index 9d1816195d..c28ce24272 100644 --- a/src/include/reb-math.h +++ b/src/include/reb-math.h @@ -1,36 +1,37 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Math related definitions -** Module: reb-math.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %reb-math.h +// Summary: "Math related definitions" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// // Decimal number formatting specifications: typedef struct Reb_Deci_Spec { - REBDEC dec; // number to form - REBINT len; // # of digits requested - REBCHR *out; // result: string of digits (no point or sign) - REBINT point; // result: position of decimal point - REBINT sign; // result: sign of number + REBDEC dec; // number to form + REBINT len; // # of digits requested + REBCHR *out; // result: string of digits (no point or sign) + REBINT point; // result: position of decimal point + REBINT sign; // result: sign of number } REBDCS; diff --git a/src/include/reb-net.h b/src/include/reb-net.h index d13cecc596..e67031658b 100644 --- a/src/include/reb-net.h +++ b/src/include/reb-net.h @@ -1,48 +1,51 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Network device definitions -** Module: reb-net.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %reb-net.h +// Summary: "Network device definitions" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// // REBOL Socket types: enum socket_types { - RST_UDP, // TCP or UDP - RST_LISTEN = 8, // LISTEN - RST_REVERSE, // DNS reverse + RST_UDP, // TCP or UDP + RST_LISTEN = 8, // LISTEN + RST_REVERSE, // DNS reverse + RST_MAX }; // REBOL Socket Modes (state flags) enum { - RSM_OPEN = 0, // socket is allocated - RSM_ATTEMPT, // attempting connection - RSM_CONNECT, // connection is open - RSM_BIND, // socket is bound to port - RSM_LISTEN, // socket is listening (TCP) - RSM_SEND, // sending - RSM_RECEIVE, // receiving - RSM_ACCEPT, // an inbound connection + RSM_OPEN = 0, // socket is allocated + RSM_ATTEMPT, // attempting connection + RSM_CONNECT, // connection is open + RSM_BIND, // socket is bound to port + RSM_LISTEN, // socket is listening (TCP) + RSM_SEND, // sending + RSM_RECEIVE, // receiving + RSM_ACCEPT, // an inbound connection + RSM_MAX }; #define IPA(a,b,c,d) (a<<24 | b<<16 | c<<8 | d) diff --git a/src/include/reb-series.h b/src/include/reb-series.h deleted file mode 100644 index 6af0527ec4..0000000000 --- a/src/include/reb-series.h +++ /dev/null @@ -1,48 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: REBOL series structure -** Module: reb-series.h -** Author: Carl Sassenrath -** Notes: -** WARNING: struct size may change -- do not malloc() -** -***********************************************************************/ - -typedef struct rebol_series { - REBYTE *data; - REBCNT tail; - REBCNT rest; - REBINT info; - REBCNT size; // Temp - size of image w/h - // OPTIONAL Extensions -} REBSER; - -#define SERIES_TAIL(s) ((s)->tail) -#define SERIES_DATA(s) ((s)->data) - -#define BLK_HEAD(s) ((REBVAL *)((s)->data)) -#define STR_HEAD(s) ((REBYTE *)((s)->data)) - -#define IMG_SIZE(s) ((s)->size) -#define IMG_WIDE(s) ((s)->size & 0xffff) -#define IMG_HIGH(s) ((s)->size >> 16) -#define IMG_DATA(s) ((REBYTE *)((s)->data)) diff --git a/src/include/reb-struct.h b/src/include/reb-struct.h new file mode 100644 index 0000000000..f97fde5425 --- /dev/null +++ b/src/include/reb-struct.h @@ -0,0 +1,599 @@ +// +// File: %reb-struct.h +// Summary: "Struct to C function" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2014 Atronix Engineering, Inc. +// Copyright 2014-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + + +//=////////////////////////////////////////////////////////////////////////=// +// +// LIBRARY! (`struct Reb_Library`) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// A library represents a loaded .DLL or .so file. This contains native +// code, which can be executed either through the "COMMAND" method (Rebol +// extensions) or the FFI interface. +// +// !!! The COMMAND method of extension is being deprecated by Ren-C, instead +// leaning on the idea of writing new natives using the same API that +// the system uses internally. +// + +inline static void *LIB_FD(REBLIB *l) { + return SER(l)->misc.fd; // file descriptor +} + +inline static REBOOL IS_LIB_CLOSED(REBLIB *l) { + return LOGICAL(SER(l)->misc.fd == NULL); +} + +inline static REBCTX *VAL_LIBRARY_META(const RELVAL *v) { + return SER(v->payload.library.singular)->link.meta; +} + +inline static REBLIB *VAL_LIBRARY(const RELVAL *v) { + return v->payload.library.singular; +} + +inline static void *VAL_LIBRARY_FD(const RELVAL *v) { + return LIB_FD(VAL_LIBRARY(v)); +} + + + +#ifdef HAVE_LIBFFI_AVAILABLE + #include +#else + // Non-functional stubs, see notes at top of t-routine.c + + typedef struct _ffi_type + { + size_t size; + unsigned short alignment; + unsigned short type; + struct _ffi_type **elements; + } ffi_type; + + #define FFI_TYPE_VOID 0 + #define FFI_TYPE_INT 1 + #define FFI_TYPE_FLOAT 2 + #define FFI_TYPE_DOUBLE 3 + #define FFI_TYPE_LONGDOUBLE 4 + #define FFI_TYPE_UINT8 5 + #define FFI_TYPE_SINT8 6 + #define FFI_TYPE_UINT16 7 + #define FFI_TYPE_SINT16 8 + #define FFI_TYPE_UINT32 9 + #define FFI_TYPE_SINT32 10 + #define FFI_TYPE_UINT64 11 + #define FFI_TYPE_SINT64 12 + #define FFI_TYPE_STRUCT 13 + #define FFI_TYPE_POINTER 14 + #define FFI_TYPE_COMPLEX 15 + + // !!! Heads-up to FFI lib authors: these aren't const definitions. :-/ + // Stray modifications could ruin these "constants". Being const-correct + // in the parameter structs for the type arrays would have been nice... + + extern ffi_type ffi_type_void; + extern ffi_type ffi_type_uint8; + extern ffi_type ffi_type_sint8; + extern ffi_type ffi_type_uint16; + extern ffi_type ffi_type_sint16; + extern ffi_type ffi_type_uint32; + extern ffi_type ffi_type_sint32; + extern ffi_type ffi_type_uint64; + extern ffi_type ffi_type_sint64; + extern ffi_type ffi_type_float; + extern ffi_type ffi_type_double; + extern ffi_type ffi_type_pointer; + + // Switched from an enum to allow Panic w/o complaint + typedef int ffi_status; + #define FFI_OK 0 + #define FFI_BAD_TYPEDEF 1 + #define FFI_BAD_ABI 2 + + typedef enum ffi_abi + { + // !!! The real ffi_abi constants will be different per-platform, + // you would not have the full list. Interestingly, a subsetting + // script *might* choose to alter libffi to produce a larger list + // vs being full of #ifdefs (though that's rather invasive change + // to the libffi code to be maintaining!) + + FFI_FIRST_ABI = 0x0BAD, + FFI_WIN64, + FFI_STDCALL, + FFI_SYSV, + FFI_THISCALL, + FFI_FASTCALL, + FFI_MS_CDECL, + FFI_UNIX64, + FFI_VFP, + FFI_O32, + FFI_N32, + FFI_N64, + FFI_O32_SOFT_FLOAT, + FFI_N32_SOFT_FLOAT, + FFI_N64_SOFT_FLOAT, + FFI_LAST_ABI, + FFI_DEFAULT_ABI = FFI_FIRST_ABI + } ffi_abi; + + typedef struct { + ffi_abi abi; + unsigned nargs; + ffi_type **arg_types; + ffi_type *rtype; + unsigned bytes; + unsigned flags; + } ffi_cif; + + // The closure is a "black box" but client code takes the sizeof() to + // pass into the alloc routine... + + typedef struct { + int stub; + } ffi_closure; + +#endif // HAVE_LIBFFI_AVAILABLE + + +// Returns an ffi_type* (which contains a ->type field, that holds the +// FFI_TYPE_XXX enum). +// +// !!! In the original Atronix implementation this was done with a table +// indexed by FFI_TYPE_XXX constants. But since those constants do not have +// guaranteed values or ordering, there was a parallel separate enum to use +// for indexing (STRUCT_TYPE_XXX). Getting rid of the STRUCT_TYPE_XXX and +// just using a switch statement should effectively act as a table anyway +// if the SYM_XXX numbers are in sequence. :-/ +// +inline static ffi_type *Get_FFType_For_Sym(REBSYM sym) { + switch (sym) { + case SYM_UINT8: + return &ffi_type_uint8; + + case SYM_INT8: + return &ffi_type_sint8; + + case SYM_UINT16: + return &ffi_type_uint16; + + case SYM_INT16: + return &ffi_type_sint16; + + case SYM_UINT32: + return &ffi_type_uint32; + + case SYM_INT32: + return &ffi_type_sint32; + + case SYM_UINT64: + return &ffi_type_uint64; + + case SYM_INT64: + return &ffi_type_sint64; + + case SYM_FLOAT: + return &ffi_type_float; + + case SYM_DOUBLE: + return &ffi_type_double; + + case SYM_POINTER: + return &ffi_type_pointer; + + case SYM_REBVAL: + return &ffi_type_pointer; + + // !!! SYM_INTEGER, SYM_DECIMAL, SYM_STRUCT was "-1" in original table + + default: + return NULL; + } +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// FIELD (FLD) describing an FFI struct element +// +//=////////////////////////////////////////////////////////////////////////=// +// +// A field is used by the FFI code to describe an element inside the layout +// of a C `struct`, so that Rebol data can be proxied to and from C. It +// contains field type descriptions, dimensionality, and name of the field. +// It is implemented as a small BLOCK!, which should eventually be coupled +// with a keylist so it can be an easy-to-read OBJECT! +// + +typedef REBARR REBFLD; + +enum { + // A WORD! name for the field (or BLANK! if anonymous ?) What should + // probably happen here is that structs should use a keylist for this; + // though that would mean anonymous fields would not be legal. + // + IDX_FIELD_NAME = 0, + + // WORD! type symbol or a BLOCK! of fields if this is a struct. Symbols + // generally map to FFI_TYPE_XXX constant (e.g. UINT8) but may also + // be a special extension, such as REBVAL. + // + IDX_FIELD_TYPE = 1, + + // An INTEGER! of the array dimensionality, or BLANK! if not an array. + // + IDX_FIELD_DIMENSION = 2, + + // HANDLE! to the ffi_type* representing this entire field. If it's a + // premade ffi_type then it's a simple HANDLE! with no GC participation. + // If it's a struct then it will use the shared form of HANDLE!, which + // will GC the memory pointed to when the last reference goes away. + // + IDX_FIELD_FFTYPE = 3, + + // An INTEGER! of the offset this field is relative to the beginning + // of its entire containing structure. Will be BLANK! if the structure + // is actually the root structure itself. + // + // !!! Comment said "size is limited by struct->offset, so only 16-bit"? + // + IDX_FIELD_OFFSET = 4, + + // An INTEGER! size of an individual field element ("wide"), in bytes. + // + IDX_FIELD_WIDE = 5, + + IDX_FIELD_MAX +}; + +#define FLD_AT(a, n) SER_AT(REBVAL, SER(a), (n)) // locate index access + +inline static REBSTR *FLD_NAME(REBFLD *f) { + if (IS_BLANK(FLD_AT(f, IDX_FIELD_NAME))) + return NULL; + return VAL_WORD_SPELLING(FLD_AT(f, IDX_FIELD_NAME)); +} + +inline static REBOOL FLD_IS_STRUCT(REBFLD *f) + { return IS_BLOCK(FLD_AT(f, IDX_FIELD_TYPE)); } + +inline static REBSYM FLD_TYPE_SYM(REBFLD *f) { + if (FLD_IS_STRUCT(f)) { + // + // We could return SYM_STRUCT_X for structs, but it's probably better + // to have callers test FLD_IS_STRUCT() separately for clarity. + // + assert(FALSE); + return SYM_STRUCT_X; + } + + assert(IS_WORD(FLD_AT(f, IDX_FIELD_TYPE))); + return VAL_WORD_SYM(FLD_AT(f, IDX_FIELD_TYPE)); +} + +inline static REBARR *FLD_FIELDLIST(REBFLD *f) { + assert(FLD_IS_STRUCT(f)); + return VAL_ARRAY(FLD_AT(f, IDX_FIELD_TYPE)); +} + +inline static REBOOL FLD_IS_ARRAY(REBFLD *f) { + if (IS_BLANK(FLD_AT(f, IDX_FIELD_DIMENSION))) + return FALSE; + assert(IS_INTEGER(FLD_AT(f, IDX_FIELD_DIMENSION))); + return TRUE; +} + +inline static REBCNT FLD_DIMENSION(REBFLD *f) { + assert(FLD_IS_ARRAY(f)); + return VAL_UNT32(FLD_AT(f, IDX_FIELD_DIMENSION)); +} + +inline static ffi_type *FLD_FFTYPE(REBFLD *f) + { return VAL_HANDLE_POINTER(ffi_type, FLD_AT(f, IDX_FIELD_FFTYPE)); } + +inline static REBCNT FLD_OFFSET(REBFLD *f) + { return VAL_UNT32(FLD_AT(f, IDX_FIELD_OFFSET)); } + +inline static REBCNT FLD_WIDE(REBFLD *f) + { return VAL_UNT32(FLD_AT(f, IDX_FIELD_WIDE)); } + +inline static REBCNT FLD_LEN_BYTES_TOTAL(REBFLD *f) { + if (FLD_IS_ARRAY(f)) + return FLD_WIDE(f) * FLD_DIMENSION(f); + return FLD_WIDE(f); +} + +inline static ffi_type* SCHEMA_FFTYPE(const RELVAL *schema) { + if (IS_BLOCK(schema)) { + REBFLD *field = VAL_ARRAY(schema); + return FLD_FFTYPE(field); + } + + // Avoid creating a "VOID" type in order to not give the illusion of + // void parameters being legal. The NONE! return type is handled + // exclusively by the return value, to prevent potential mixups. + // + assert(IS_WORD(schema)); + return Get_FFType_For_Sym(VAL_WORD_SYM(schema)); +} + + +#define VAL_STRUCT_LIMIT MAX_U32 + + +//=////////////////////////////////////////////////////////////////////////=// +// +// STRUCT! (`struct Reb_Struct`) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Struct is a value type that is the the combination of a "schema" (field or +// list of fields) along with a blob of binary data described by that schema. +// + +inline static REBVAL *STU_VALUE(REBSTU *stu) { + assert(ARR_LEN(stu) == 1); + return KNOWN(ARR_HEAD(stu)); +} + +#define STU_INACCESSIBLE(stu) \ + VAL_STRUCT_INACCESSIBLE(STU_VALUE(stu)) + +inline static REBFLD *STU_SCHEMA(REBSTU *stu) { + REBFLD *schema = SER(stu)->link.schema; + assert(FLD_IS_STRUCT(schema)); + return schema; +} + +inline static REBARR *STU_FIELDLIST(REBSTU *stu) { + return FLD_FIELDLIST(STU_SCHEMA(stu)); +} + +inline static REBCNT STU_SIZE(REBSTU *stu) { + return FLD_WIDE(STU_SCHEMA(stu)); +} + +inline static REBCNT STU_OFFSET(REBSTU *stu) { + return STU_VALUE(stu)->extra.struct_offset; +} + +#define STU_FFTYPE(stu) \ + FLD_FFTYPE(STU_SCHEMA(stu)) + +#define VAL_STRUCT(v) \ + ((v)->payload.structure.stu) + +#define VAL_STRUCT_SCHEMA(v) \ + STU_SCHEMA(VAL_STRUCT(v)) + +#define VAL_STRUCT_SIZE(v) \ + STU_SIZE(VAL_STRUCT(v)) + +inline static REBYTE *VAL_STRUCT_DATA_HEAD(const RELVAL *v) { + REBSER *data = v->payload.structure.data; + if (NOT_SER_FLAG(data, SERIES_FLAG_ARRAY)) + return BIN_HEAD(data); + + RELVAL *handle = ARR_HEAD(ARR(data)); + assert(VAL_HANDLE_LEN(handle) != 0); + return VAL_HANDLE_POINTER(REBYTE, handle); +} + +inline static REBYTE *STU_DATA_HEAD(REBSTU *stu) { + return VAL_STRUCT_DATA_HEAD(STU_VALUE(stu)); +} + +#define VAL_STRUCT_OFFSET(v) \ + ((v)->extra.struct_offset) + +inline static REBYTE *VAL_STRUCT_DATA_AT(const RELVAL *v) { + return VAL_STRUCT_DATA_HEAD(v) + VAL_STRUCT_OFFSET(v); +} + +inline static REBCNT VAL_STRUCT_DATA_LEN(const RELVAL *v) { + REBSER *data = v->payload.structure.data; + if (NOT_SER_FLAG(data, SERIES_FLAG_ARRAY)) + return BIN_LEN(data); + + RELVAL *handle = ARR_HEAD(ARR(data)); + assert(VAL_HANDLE_LEN(handle) != 0); + return VAL_HANDLE_LEN(handle); +} + +inline static REBCNT STU_DATA_LEN(REBSTU *stu) { + return VAL_STRUCT_DATA_LEN(STU_VALUE(stu)); +} + +inline static REBOOL VAL_STRUCT_INACCESSIBLE(const RELVAL *v) { + REBSER *data = v->payload.structure.data; + if (NOT_SER_FLAG(data, SERIES_FLAG_ARRAY)) + return FALSE; // it's not "external", so never inaccessible + + RELVAL *handle = ARR_HEAD(ARR(data)); + if (VAL_HANDLE_LEN(handle) != 0) + return FALSE; // !!! TBD: double check size is correct for mem block + + return TRUE; +} + +#define VAL_STRUCT_FIELDLIST(v) \ + STU_FIELDLIST(VAL_STRUCT(v)) + +#define VAL_STRUCT_FFTYPE(v) \ + STU_FFTYPE(VAL_STRUCT(v)) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// ROUTINE SUPPORT +// +//=////////////////////////////////////////////////////////////////////////=// +// +// "Routine info" used to be a specialized C structure, which referenced +// Rebol functions/values/series. This meant there had to be specialized +// code in the garbage collector. It actually went as far as to have a memory +// pool for objects that was sizeof(Reb_Routine_Info), which complicates the +// concerns further. +// +// That "invasive" approach is being gradually generalized to speak in the +// natural vocabulary of Rebol values. What enables the transition is that +// arbitrary C allocations (such as an ffi_closure*) can use the new freeing +// handler feature of a GC'd HANDLE! value. So now "routine info" is just +// a BLOCK! REBVAL*, which lives in the FUNC_BODY of a routine, and has some +// HANDLE!s in it that array. +// +// !!! An additional benefit is that if the structures used internally +// are actual Rebol-manipulatable values, then that means more parts of the +// FFI extension itself could be written as Rebol. e.g. the FFI spec analysis +// could be done with PARSE, as opposed to harder-to-edit-and-maintain +// internal API C code. +// + +enum { + // The HANDLE! of a CFUNC*, obeying the interface of the C-format call. + // If it's a routine, then it's the pointer to a pre-existing function + // in the DLL that the routine intends to wrap. If a callback, then + // it's a fabricated function pointer returned by ffi_closure_alloc, + // which presents the "thunk"...a C function that other C functions can + // call which will then delegate to Rebol to call the wrapped FUNCTION!. + // + // Additionally, callbacks poke a data pointer into the HANDLE! with + // ffi_closure*. (The closure allocation routine gives back a void* and + // not an ffi_closure* for some reason. Perhaps because it takes a + // size that might be bigger than the size of a closure?) + // + IDX_ROUTINE_CFUNC = 0, + + // An INTEGER! indicating which ABI is used by the CFUNC (enum ffi_abi) + // + // !!! It would be better to change this to use a WORD!, especially if + // the routine descriptions will ever become user visible objects. + // + IDX_ROUTINE_ABI = 1, + + // The LIBRARY! the CFUNC* lives in if a routine, or the FUNCTION! to + // be called if this is a callback. + // + IDX_ROUTINE_ORIGIN = 2, + + // The "schema" of the return type. This is either a WORD! (which + // is a symbol corresponding to the FFI_TYPE constant of the return) or + // a BLOCK! representing a field (this REBFLD will hopefully become + // OBJECT! at some point). If it is BLANK! then there is no return type. + // + IDX_ROUTINE_RET_SCHEMA = 3, + + // An ARRAY! of the argument schemas; each also WORD! or ARRAY!, following + // the same pattern as the return value...but not allowed to be blank + // (no such thing as a void argument) + // + IDX_ROUTINE_ARG_SCHEMAS = 4, + + // A HANDLE! containing one ffi_cif*, or BLANK! if variadic. The Call + // InterFace (CIF) for a C function with fixed arguments can be created + // once and then used many times. For a variadic routine, it must be + // created on each call to match the number and types of arguments. + // + IDX_ROUTINE_CIF = 5, + + // A HANDLE! which is actually an array of ffi_type*, so a C array of + // pointers. This array was passed into the CIF at its creation time, + // and it holds references to them as long as you use that CIF...so this + // array must survive as long as the CIF does. BLANK! if variadic. + // + IDX_ROUTINE_ARG_FFTYPES = 6, + + // A LOGIC! of whether this routine is variadic. Since variadic-ness is + // something that gets exposed in the FUNCTION! interface itself, this + // may become redundant as an internal property of the implementation. + // + IDX_ROUTINE_IS_VARIADIC = 7, + + // An ffi_closure which for a callback stores the place where the CFUNC* + // lives, or BLANK! otherwise. + // + IDX_ROUTINE_CLOSURE = 8, + + IDX_ROUTINE_MAX +}; + +#define RIN_AT(a, n) SER_AT(REBVAL, SER(a), (n)) // locate index access + +inline static CFUNC *RIN_CFUNC(REBRIN *r) + { return VAL_HANDLE_CFUNC(RIN_AT(r, IDX_ROUTINE_CFUNC)); } + +inline static ffi_abi RIN_ABI(REBRIN *r) + { return cast(ffi_abi, VAL_INT32(RIN_AT(r, IDX_ROUTINE_ABI))); } + +inline static REBOOL RIN_IS_CALLBACK(REBRIN *r) { + if (IS_FUNCTION(RIN_AT(r, IDX_ROUTINE_ORIGIN))) + return TRUE; + assert( + IS_LIBRARY(RIN_AT(r, IDX_ROUTINE_ORIGIN)) + || IS_BLANK(RIN_AT(r, IDX_ROUTINE_ORIGIN)) + ); + return FALSE; +} + +inline static ffi_closure* RIN_CLOSURE(REBRIN *r) { + assert(RIN_IS_CALLBACK(r)); // only callbacks have ffi_closure + return VAL_HANDLE_POINTER(ffi_closure, RIN_AT(r, IDX_ROUTINE_CLOSURE)); +} + +inline static REBLIB *RIN_LIB(REBRIN *r) { + assert(NOT(RIN_IS_CALLBACK(r))); + return VAL_LIBRARY(RIN_AT(r, IDX_ROUTINE_ORIGIN)); +} + +inline static REBFUN *RIN_CALLBACK_FUNC(REBRIN *r) { + assert(RIN_IS_CALLBACK(r)); + return VAL_FUNC(RIN_AT(r, IDX_ROUTINE_ORIGIN)); +} + +inline static REBVAL *RIN_RET_SCHEMA(REBRIN *r) + { return KNOWN(RIN_AT(r, IDX_ROUTINE_RET_SCHEMA)); } + +inline static REBCNT RIN_NUM_FIXED_ARGS(REBRIN *r) + { return VAL_LEN_HEAD(RIN_AT(r, IDX_ROUTINE_ARG_SCHEMAS)); } + +inline static REBVAL *RIN_ARG_SCHEMA(REBRIN *r, REBCNT n) { // 0-based index + return KNOWN(VAL_ARRAY_AT_HEAD(RIN_AT(r, IDX_ROUTINE_ARG_SCHEMAS), n)); +} + +inline static ffi_cif *RIN_CIF(REBRIN *r) + { return VAL_HANDLE_POINTER(ffi_cif, RIN_AT(r, IDX_ROUTINE_CIF)); } + +inline static ffi_type** RIN_ARG_FFTYPES(REBRIN *r) { + return VAL_HANDLE_POINTER(ffi_type*, RIN_AT(r, IDX_ROUTINE_ARG_FFTYPES)); +} + +inline static REBOOL RIN_IS_VARIADIC(REBRIN *r) + { return VAL_LOGIC(RIN_AT(r, IDX_ROUTINE_IS_VARIADIC)); } diff --git a/src/include/reb-value.h b/src/include/reb-value.h deleted file mode 100644 index 7ac85c5493..0000000000 --- a/src/include/reb-value.h +++ /dev/null @@ -1,85 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: REBOL Values for External Usage -** Module: reb-value.h -** Author: Carl Sassenrath -** Notes: -** Important: Compile with 4 byte alignment on structures. -** -***********************************************************************/ - -struct rebol_value; -typedef struct rebol_value REBVAL; - -typedef struct rebol_tuple { - REBYTE tuple[12]; -} REBTUP; - -typedef struct rebol_series_index -{ - REBSER *series; - REBCNT index; -} REBSRI; - -typedef struct rebol_word { - REBCNT sym; // Index of the word's symbol - REBINT index; // Index of the word in the frame - union { - REBSER *frame; // Frame in which the word is defined - REBCNT typeset;// Typeset number - } c; -} REBWRD; - -struct rebol_value { - REBINT flags; - union REBOL_Val_Data { - REBI64 integer; - REBINT int32; - REBDEC decimal; - REBPAR pair; - REBTUP tuple; - REBGOB *gob; - REBWRD word; - REBSRI series; - } data; -}; - -#define VAL_TYPE(v) ((REBYTE)((v)->flags)) // get only the type, not flags - -#define VAL_INT32(v) (REBINT)((v)->data.integer) -#define VAL_INT64(v) ((v)->data.integer) -#define VAL_DECIMAL(v) ((v)->data.decimal) -#define VAL_LOGIC(v) ((v)->data.int32) -#define VAL_TUPLE(v) ((v)->data.tuple.tuple+1) -#define VAL_TUPLE_LEN(v) ((v)->data.tuple.tuple[0]) -#define VAL_PAIR(v) ((v)->data.pair) -#define VAL_WORD(v) ((v)->data.word.index) -#define VAL_WORD_SYM(v) ((v)->data.word.sym) - -#define VAL_SERIES(v) ((v)->data.series.series) -#define VAL_STRING(v) STR_HEAD(VAL_SERIES(v)) - -#define VAL_IMAGE_SIZE(v) (IMG_SIZE(VAL_SERIES(v))) -#define VAL_IMAGE_WIDE(v) (IMG_WIDE(VAL_SERIES(v))) -#define VAL_IMAGE_HIGH(v) (IMG_HIGH(VAL_SERIES(v))) -#define VAL_IMAGE_DATA(v) (IMG_DATA(VAL_SERIES(v))) - diff --git a/src/include/sys-action.h b/src/include/sys-action.h new file mode 100644 index 0000000000..834e970cae --- /dev/null +++ b/src/include/sys-action.h @@ -0,0 +1,255 @@ +// +// File: %sys-action.h +// Summary: "Definition of action dispatchers" +// Section: core +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + + +// !!! Originally, REB_R was a REBCNT from reb-c.h (not this enumerated type +// containing its legal values). That's because enums in C have no guaranteed +// size, yet Rebol wants to use known size types in its interfaces. +// +// However, there are other enums in %tmp-funcs.h, and the potential for bugs +// is too high to not let the C++ build check the types. So for now, REB_R +// uses this enum. +// +enum Reb_Result { + // + // Returning boolean results is specially chosen as the 0 and 1 values, + // so that a logic result can just be cast, as with R_FROM_BOOL(). + // See remarks on REBOOL about how it is ensured that TRUE is 1, and + // that this is the standard for C++ bool conversion: + // + // http://stackoverflow.com/questions/2725044/ + // + R_FALSE = 0, // => Init_Logic(D_OUT, FALSE); return R_OUT; + R_TRUE = 1, // => Init_Logic(D_OUT, TRUE); return R_OUT; + + // Void and blank are also common results. + // + R_VOID, // => Init_Void(D_OUT); return R_OUT; + R_BLANK, // => Init_Blank(D_OUT); return R_OUT; + R_BAR, // Init_Bar(D_OUT); return R_OUT; + + // This means that the value in D_OUT is to be used as the return result. + // Note that value starts as an END, and must be written to some other + // value before this return can be used (checked by assert in debug build) + // + R_OUT, + + // By default, all return results will not have the VALUE_FLAG_UNEVALUATED + // bit when they come back from a function. To override that, this asks + // the dispatch to clear the bit instead. It should be noted that since + // there is no meaningful way to carry the bit when copying values around + // internally, this is only a useful bit to read on things that were + // known to go directly through an evaluation step...e.g. arguments to + // functions on their initial fulfillment. So this is returned by the + // QUOTE native (for instance). + // + R_OUT_UNEVALUATED, + + // See comments on VALUE_FLAG_THROWN about the migration of "thrownness" + // from being a property signaled to the evaluator. + // + // R_OUT_IS_THROWN is a test of that signaling mechanism. It is currently + // being kept in parallel with the THROWN() bit and ensured as matching. + // Being in the state of doing a stack unwind will likely be knowable + // through other mechanisms even once the thrown bit on the value is + // gone...so it may not be the case that natives are asked to do their + // own separate indication, so this may wind up replaced with R_OUT. For + // the moment it is good as a double-check. + // + R_OUT_IS_THROWN, + + // Since all dispatchers get END markers in the f->out slot (a.k.a. D_OUT) + // then it can be used to tell if the output has been written "in band" + // by a legal value or void. This returns TRUE if D_OUT is not END, + // and FALSE if it still is. + // + R_OUT_TRUE_IF_WRITTEN, + + // Similar to R_OUT_TRUE_IF_WRITTEN, this converts an illegal END marker + // return value in R_OUT to simply a void. + // + R_OUT_VOID_IF_UNWRITTEN, + + // This converts END into void, but void or conditional false into BAR! + // + R_OUT_VOID_IF_UNWRITTEN_TRUTHIFY, + + // This converts void into BLANK!, and is used by control constructs + // so that they can reserve void for the case they didn't run any branch. + // + R_OUT_BLANK_IF_VOID, + + // This combines the unwritten and blank path for control constructs. + // While it may seem they could do it themselves, having a different + // return code is a reminder that some optimization may be possible. + // + R_OUT_VOID_IF_UNWRITTEN_BLANK_IF_VOID, + + // If Do_Core gets back an R_REDO from a dispatcher, it will re-execute + // the f->phase in the frame. This function may be changed by the + // dispatcher from what was originally called. + // + R_REDO_CHECKED, // check the types again, fill in exits + R_REDO_UNCHECKED, // don't recheck types, just run next function in stack + + // EVAL is special because it stays at the frame level it is already + // running, but re-evaluates. In order to do this, it must protect its + // argument during that evaluation, so it writes into the frame's + // "eval cell". + // + R_REEVALUATE, + R_REEVALUATE_ONLY, + + // This is a signal that isn't accepted as a return value from a native, + // so it can be used by common routines that return REB_R values and need + // an "escape" code. + // + R_UNHANDLED +}; +typedef enum Reb_Result REB_R; + +// Convenience function for getting behaviors like WHILE/LOOPED?", and +// doing the default thing--assuming END is being left in the D_OUT slot if +// the tested-for condition is not met. +// +inline static REB_R R_OUT_Q(REBOOL q) { + if (q) return R_OUT_TRUE_IF_WRITTEN; + return R_OUT_VOID_IF_UNWRITTEN; +} + +// Specially chosen 0 and 1 values for R_FALSE and R_TRUE enable this. +// +inline static REB_R R_FROM_BOOL(REBOOL b) { +#ifdef STRICT_BOOL_COMPILER_TEST + return b ? R_TRUE : R_FALSE; +#else + return cast(REB_R, b); +#endif +} + +// R3-Alpha's concept was that all words got persistent integer values, which +// prevented garbage collection. Ren-C only gives built-in words integer +// values--or SYMs--while others must be compared by pointers to their +// name or canon-name pointers. A non-built-in symbol will return SYM_0 as +// its symbol, allowing it to fall through to defaults in case statements. +// +// Though it works fine for switch statements, it creates a problem if someone +// writes `VAL_WORD_SYM(a) == VAL_WORD_SYM(b)`, because all non-built-ins +// will appear to be equal. It's a tricky enough bug to catch to warrant an +// extra check in C++ that disallows comparing SYMs with == +// +#if !defined(NDEBUG) && defined(__cplusplus) + struct REBSYM; + + struct OPT_REBSYM { // can only be converted to REBSYM, no comparisons + enum REBOL_Symbols n; + OPT_REBSYM (const REBSYM& sym); + REBOOL operator==(enum REBOL_Symbols other) const { + return LOGICAL(n == other); + } + REBOOL operator!=(enum REBOL_Symbols other) const { + return LOGICAL(n != other); + } + #if __cplusplus >= 201103L // http://stackoverflow.com/a/35399513/211160 + REBOOL operator==(OPT_REBSYM &&other) const; + REBOOL operator!=(OPT_REBSYM &&other) const; + #endif + operator unsigned int() const { + return cast(unsigned int, n); + } + }; + + struct REBSYM { // acts like a REBOL_Symbol with no OPT_REBSYM compares + enum REBOL_Symbols n; + REBSYM () {} + REBSYM (int n) : n (cast(enum REBOL_Symbols, n)) {} + REBSYM (OPT_REBSYM opt_sym) : n (opt_sym.n) {} + operator unsigned int() const { + return cast(unsigned int, n); + } + REBOOL operator>=(enum REBOL_Symbols other) const { + assert(other != SYM_0); + return LOGICAL(n >= other); + } + REBOOL operator<=(enum REBOL_Symbols other) const { + assert(other != SYM_0); + return LOGICAL(n <= other); + } + REBOOL operator>(enum REBOL_Symbols other) const { + assert(other != SYM_0); + return LOGICAL(n > other); + } + REBOOL operator<(enum REBOL_Symbols other) const { + assert(other != SYM_0); + return LOGICAL(n < other); + } + REBOOL operator==(enum REBOL_Symbols other) const { + return LOGICAL(n == other); + } + REBOOL operator!=(enum REBOL_Symbols other) const { + return LOGICAL(n != other); + } + REBOOL operator==(REBSYM &other) const; // could be SYM_0! + void operator!=(REBSYM &other) const; // could be SYM_0! + REBOOL operator==(const OPT_REBSYM &other) const; // could be SYM_0! + void operator!=(const OPT_REBSYM &other) const; // could be SYM_0! + }; + + inline OPT_REBSYM::OPT_REBSYM(const REBSYM &sym) : n (sym.n) {} +#else + typedef enum REBOL_Symbols REBSYM; + typedef enum REBOL_Symbols OPT_REBSYM; // act sameas REBSYM in C build +#endif + +inline static REBOOL SAME_SYM_NONZERO(REBSYM a, REBSYM b) { + assert(a != SYM_0 && b != SYM_0); + return LOGICAL(cast(REBCNT, a) == cast(REBCNT, b)); +} + +// NATIVE! function +typedef REB_R (*REBNAT)(REBFRM *frame_); +#define REBNATIVE(n) \ + REB_R N_##n(REBFRM *frame_) + +// ACTION! function (one per each DATATYPE!) +typedef REB_R (*REBACT)(REBFRM *frame_, REBSYM a); +#define REBTYPE(n) \ + REB_R T_##n(REBFRM *frame_, REBSYM action) + +// PORT!-action function +typedef REB_R (*REBPAF)(REBFRM *frame_, REBCTX *p, REBSYM a); + +// COMMAND! function +typedef REB_R (*CMD_FUNC)(REBCNT n, REBSER *args); + +// "Routine INfo" was once a specialized C structure, now an ordinary Rebol +// REBARR pointer. +// +typedef REBARR REBRIN; diff --git a/src/include/sys-array.h b/src/include/sys-array.h new file mode 100644 index 0000000000..4db209fe43 --- /dev/null +++ b/src/include/sys-array.h @@ -0,0 +1,421 @@ +// +// File: %sys-array.h +// Summary: {Definitions for REBARR} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// A "Rebol Array" is a series of REBVAL values which is terminated by an +// END marker. In R3-Alpha, the END marker was itself a full-sized REBVAL +// cell...so code was allowed to write one cell past the capacity requested +// when Make_Array() was called. But this always had to be an END. +// +// In Ren-C, there is an implicit END marker just past the last cell in the +// capacity. Allowing a SET_END() on this position could corrupt the END +// signaling slot, which only uses a bit out of a Reb_Header sized item to +// signal. Use TERM_ARRAY_LEN() to safely terminate arrays and respect not +// writing if it's past capacity. +// +// While many operations are shared in common with REBSER, there is a +// (deliberate) type incompatibility introduced. The type compatibility is +// implemented in a way that works in C or C++ (though it should be reviewed +// for strict aliasing compliance). To get the underlying REBSER of a REBARR +// use the SER() operation. +// +// An ARRAY is the main place in the system where "relative" values come +// from, because all relative words are created during the copy of the +// bodies of functions. The array accessors must err on the safe side and +// give back a relative value. Many inspection operations are legal on +// a relative value, but it cannot be copied without a "specifier" FRAME! +// context (which is also required to do a GET_VAR lookup). +// + +struct Reb_Array { + struct Reb_Series series; // http://stackoverflow.com/a/9747062 +}; + +// These do REBSER <=> REBARR coercion. Although it's desirable to make +// them type incompatible for most purposes, some operations require treating +// one kind of pointer as the other (and they are both Reb_Series) +// +#if defined(__cplusplus) && __cplusplus >= 201103L + template + inline REBARR *ARR(T *p) { + static_assert( + std::is_same::value + || std::is_same::value + || std::is_same::value, + "ARR works on: void*, REBNOD*, REBSER*" + ); + REBSER *s = cast(REBSER*, p); + assert(NOT_SER_FLAG(s, NODE_FLAG_FREE)); + assert(ALL_SER_FLAGS(s, NODE_FLAG_NODE | SERIES_FLAG_ARRAY)); + return cast(REBARR*, s); + } +#else + #define ARR(p) \ + cast(REBARR*, (p)) +#endif + + +// HEAD, TAIL, and LAST refer to specific value pointers in the array. An +// empty array should have an END marker in its head slot, and since it has +// no last value then ARR_LAST should not be called (this is checked in +// debug builds). A fully constructed array should always have an END +// marker in its tail slot, which is one past the last position that is +// valid for writing a full REBVAL. + +inline static RELVAL *ARR_AT(REBARR *a, REBCNT n) + { return SER_AT(RELVAL, SER(a), (n)); } + +inline static RELVAL *ARR_HEAD(REBARR *a) + { return SER_HEAD(RELVAL, SER(a)); } + +inline static RELVAL *ARR_TAIL(REBARR *a) + { return SER_TAIL(RELVAL, SER(a)); } + +inline static RELVAL *ARR_LAST(REBARR *a) + { return SER_LAST(RELVAL, SER(a)); } + +// As with an ordinary REBSER, a REBARR has separate management of its length +// and its terminator. Many routines seek to choose the precise moment to +// sync these independently for performance reasons (for better or worse). +// +#define ARR_LEN(a) \ + SER_LEN(SER(a)) + + +// TERM_ARRAY_LEN sets the length and terminates the array, and to get around +// the problem it checks to see if the length is the rest - 1. Another +// possibility would be to check to see if the cell was already marked with +// END...however, that would require initialization of all cells in an array +// up front, to legitimately examine the bits (and decisions on how to init) +// +inline static void TERM_ARRAY_LEN(REBARR *a, REBCNT len) { + REBCNT rest = SER_REST(SER(a)); + assert(len < rest); + SET_SERIES_LEN(SER(a), len); + if (len + 1 == rest) + assert(IS_END(ARR_TAIL(a))); + else + SET_END(ARR_TAIL(a)); +} + +inline static void SET_ARRAY_LEN_NOTERM(REBARR *a, REBCNT len) { + SET_SERIES_LEN(SER(a), len); // call out non-terminating usages +} + +inline static void RESET_ARRAY(REBARR *a) { + TERM_ARRAY_LEN(a, 0); +} + +inline static void TERM_SERIES(REBSER *s) { + if (GET_SER_FLAG(s, SERIES_FLAG_ARRAY)) + TERM_ARRAY_LEN(ARR(s), SER_LEN(s)); + else + memset(SER_AT_RAW(SER_WIDE(s), s, SER_LEN(s)), 0, SER_WIDE(s)); +} + + +// Setting and getting array flags is common enough to want a macro for it +// vs. having to extract the ARR_SERIES to do it each time. +// +#define IS_ARRAY_MANAGED(a) \ + IS_SERIES_MANAGED(SER(a)) + +#define MANAGE_ARRAY(a) \ + MANAGE_SERIES(SER(a)) + +#define ENSURE_ARRAY_MANAGED(a) \ + ENSURE_SERIES_MANAGED(SER(a)) + +#define PUSH_GUARD_ARRAY(a) \ + PUSH_GUARD_SERIES(SER(a)) + +#define DROP_GUARD_ARRAY(a) \ + DROP_GUARD_SERIES(SER(a)) + +inline static void PUSH_GUARD_ARRAY_CONTENTS(REBARR *a) { + assert(!IS_ARRAY_MANAGED(a)); // if managed, just use PUSH_GUARD_ARRAY + Guard_Node_Core(NOD(a)); +} + +inline static void DROP_GUARD_ARRAY_CONTENTS(REBARR *a) { +#if !defined(NDEBUG) + // + // Make sure no unmanaged values were put in the array, because they + // would have caused errors if the GC had seen them! + // + RELVAL *test = ARR_HEAD(a); + for (; NOT_END(test); ++test) + ASSERT_VALUE_MANAGED(test); +#endif + DROP_GUARD_SERIES(SER(a)); +} + + +// +// Locking +// + +inline static REBOOL Is_Array_Deeply_Frozen(REBARR *a) { + return GET_SER_INFO(a, SERIES_INFO_FROZEN); + + // should be frozen all the way down (can only freeze arrays deeply) +} + +inline static void Deep_Freeze_Array(REBARR *a) { + Protect_Series( + SER(a), + 0, // start protection at index 0 + FLAGIT(PROT_DEEP) | FLAGIT(PROT_SET) | FLAGIT(PROT_FREEZE) + ); + Uncolor_Array(a); +} + +#define Is_Array_Shallow_Read_Only(a) \ + Is_Series_Read_Only(a) + +#define FAIL_IF_READ_ONLY_ARRAY(a) \ + FAIL_IF_READ_ONLY_SERIES(SER(a)) + + +// Make a series that is the right size to store REBVALs (and +// marked for the garbage collector to look into recursively). +// Terminator included implicitly. Sets TAIL to zero. +// +inline static REBARR *Make_Array_Core(REBCNT capacity, REBUPT flags) +{ + REBSER *s = Make_Series_Core( + capacity + 1, + sizeof(REBVAL), + flags | SERIES_FLAG_ARRAY + ); + + assert( + capacity <= 1 + ? NOT(GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC)) + : GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC) + ); + + REBARR *a = ARR(s); + TERM_ARRAY_LEN(a, 0); + return a; +} + +#define Make_Array(capacity) \ + Make_Array_Core((capacity), SERIES_FLAG_FILE_LINE) + + +// A singular array is specifically optimized to hold *one* value in a REBSER +// directly, and stay fixed at that size. Note that the internal logic of +// series will give you this optimization even if you don't ask for it if +// a series or array is small. However, this allocator adds the fixed size +// bit and defaults the array to an uninitialized cell with length 1, vs. +// going through a length 0 step. +// +inline static REBARR *Alloc_Singular_Array_Core(REBUPT flags) { + REBSER *s = Make_Series_Core( + 2, // Length 2 is requested, but there is no "real" second slot + sizeof(REBVAL), + SERIES_FLAG_ARRAY | SERIES_FLAG_FIXED_SIZE | flags + ); + assert(NOT_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC)); + + // The length still needs to be set in the header, as it defaults + // to 0 and we want it to be 1. + // + CLEAR_8_MID_BITS(s->info.bits); + s->info.bits |= FLAGBYTE_MID(1); + assert(SER_LEN(s) == 1); + + REBARR *a = ARR(s); + assert(IS_END(ARR_TAIL(a))); + return a; +} + +#define Alloc_Singular_Array() \ + Alloc_Singular_Array_Core(0) + + +#define Append_Value(a,v) \ + (Move_Value(Alloc_Tail_Array(a), (v)), NOOP) + +#define Append_Value_Core(a,v,s) \ + Derelativize(Alloc_Tail_Array(a), (v), (s)) + + +#define Copy_Values_Len_Shallow(v,s,l) \ + Copy_Values_Len_Extra_Skip_Shallow_Core((v), (s), (l), 0, 1, 0) + +#define Copy_Values_Len_Shallow_Core(v,s,l,f) \ + Copy_Values_Len_Extra_Skip_Shallow_Core((v), (s), (l), 0, 1, (f)) + +#define Copy_Values_Len_Reversed_Shallow(v,s,l) \ + Copy_Values_Len_Extra_Skip_Shallow_Core((v), (s), (l), 0, -1, 0) + +#define Copy_Values_Len_Extra_Shallow(v, s, l, e) \ + Copy_Values_Len_Extra_Skip_Shallow_Core((v), (s), (l), (e), 1, 0) + + +#define Copy_Array_Shallow(a,s) \ + Copy_Array_At_Shallow((a), 0, (s)) + +#define Copy_Array_Deep_Managed(a,s) \ + Copy_Array_At_Extra_Deep_Managed((a), 0, (s), 0) + +#define Copy_Array_At_Deep_Managed(a,i,s) \ + Copy_Array_At_Extra_Deep_Managed((a), (i), (s), 0) + +#define COPY_ANY_ARRAY_AT_DEEP_MANAGED(v) \ + Copy_Array_At_Extra_Deep_Managed( \ + VAL_ARRAY(v), VAL_INDEX(v), VAL_SPECIFIER(v), 0) + +#define Copy_Array_At_Shallow(a,i,s) \ + Copy_Array_At_Extra_Shallow((a), (i), (s), 0) + +#define Copy_Array_Extra_Shallow(a,s,e) \ + Copy_Array_At_Extra_Shallow((a), 0, (s), (e)) + + +#define Free_Array(a) \ + Free_Series(SER(a)) + + + +//=////////////////////////////////////////////////////////////////////////=// +// +// ANY-ARRAY! (uses `struct Reb_Any_Series`) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// See INIT_SPECIFIC and INIT_RELATIVE in %sys-bind.h +// + +#define EMPTY_BLOCK \ + ROOT_EMPTY_BLOCK + +#define EMPTY_ARRAY \ + VAL_ARRAY(ROOT_EMPTY_BLOCK) + +#define EMPTY_STRING \ + ROOT_EMPTY_STRING + +inline static REBSPC* AS_SPECIFIER(REBCTX *context) { + return cast(REBSPC*, context); +} + +inline static REBSPC *VAL_SPECIFIER(const REBVAL *v) { + assert(ANY_ARRAY(v)); + return AS_SPECIFIER(VAL_SPECIFIC(v)); +} + +inline static void INIT_VAL_ARRAY(RELVAL *v, REBARR *a) { + v->extra.binding = (REBARR*)SPECIFIED; // !!! cast() complains, investigate + v->payload.any_series.series = SER(a); +} + +// These array operations take the index position into account. The use +// of the word AT with a missing index is a hint that the index is coming +// from the VAL_INDEX() of the value itself. +// +#define VAL_ARRAY_AT(v) \ + ARR_AT(VAL_ARRAY(v), VAL_INDEX(v)) + +#define VAL_ARRAY_LEN_AT(v) \ + VAL_LEN_AT(v) + +// These operations do not need to take the value's index position into +// account; they strictly operate on the array series +// +inline static REBARR *VAL_ARRAY(const RELVAL *v) { + assert(ANY_ARRAY(v)); + return ARR(v->payload.any_series.series); +} + +#define VAL_ARRAY_HEAD(v) \ + ARR_HEAD(VAL_ARRAY(v)) + +inline static RELVAL *VAL_ARRAY_TAIL(const RELVAL *v) { + return ARR_AT(VAL_ARRAY(v), VAL_ARRAY_LEN_AT(v)); +} + + +// !!! VAL_ARRAY_AT_HEAD() is a leftover from the old definition of +// VAL_ARRAY_AT(). Unlike SKIP in Rebol, this definition did *not* take +// the current index position of the value into account. It rather extracted +// the array, counted rom the head, and disregarded the index entirely. +// +// The best thing to do with it is probably to rewrite the use cases to +// not need it. But at least "AT HEAD" helps communicate what the equivalent +// operation in Rebol would be...and you know it's not just giving back the +// head because it's taking an index. So it looks weird enough to suggest +// looking here for what the story is. +// +#define VAL_ARRAY_AT_HEAD(v,n) \ + ARR_AT(VAL_ARRAY(v), (n)) + +#define Init_Any_Array_At(v,t,a,i) \ + Init_Any_Series_At((v), (t), SER(a), (i)) + +#define Init_Any_Array(v,t,a) \ + Init_Any_Array_At((v), (t), (a), 0) + +#define Init_Block(v,s) \ + Init_Any_Array((v), REB_BLOCK, (s)) + +#define Init_Group(v,s) \ + Init_Any_Array((v), REB_GROUP, (s)) + +#define Init_Path(v,s) \ + Init_Any_Array((v), REB_PATH, (s)) + + + +#ifdef NDEBUG + #define ASSERT_ARRAY(s) \ + NOOP + + #define ASSERT_ARRAY_MANAGED(array) \ + NOOP + + #define ASSERT_SERIES(s) \ + NOOP +#else + #define ASSERT_ARRAY(s) \ + Assert_Array_Core(s) + + #define ASSERT_ARRAY_MANAGED(array) \ + ASSERT_SERIES_MANAGED(SER(array)) + + static inline void ASSERT_SERIES(REBSER *s) { + if (GET_SER_FLAG(s, SERIES_FLAG_ARRAY)) + Assert_Array_Core(ARR(s)); + else + Assert_Series_Core(s); + } + + #define IS_VALUE_IN_ARRAY_DEBUG(a,v) \ + (ARR_LEN(a) != 0 && (v) >= ARR_HEAD(a) && (v) < ARR_TAIL(a)) +#endif diff --git a/src/include/sys-binary.h b/src/include/sys-binary.h new file mode 100644 index 0000000000..23b2f52864 --- /dev/null +++ b/src/include/sys-binary.h @@ -0,0 +1,112 @@ +// +// File: %sys-binary.h +// Summary: {Definitions for binary series} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Byte-sized series are also used by the STRING! datatype. There is no +// technical difference between such series used as strings or used as binary, +// the difference comes from being marked REB_BINARY or REB_STRING in the +// header of the value carrying the series. +// +// For easier type-correctness, the series macros are given with names BIN_XXX +// and UNI_XXX. There aren't distinct data types for the series themselves, +// just REBSER* is used. Hence BIN_LEN() and UNI_LEN() aren't needed as you +// could just use SER_LEN(), but it helps a bit for readability...and an +// assert is included to ensure the size matches up. +// + + +// Is it a byte-sized series? +// +#define BYTE_SIZE(s) \ + LOGICAL(SER_WIDE(s) == 1) + + +// +// BIN_XXX: Binary or byte-size string seres macros +// + +#define BIN_AT(s,n) \ + SER_AT(REBYTE, (s), (n)) + +#define BIN_HEAD(s) \ + SER_HEAD(REBYTE, (s)) + +#define BIN_TAIL(s) \ + SER_TAIL(REBYTE, (s)) + +#define BIN_LAST(s) \ + SER_LAST(REBYTE, (s)) + +inline static REBCNT BIN_LEN(REBSER *s) { + assert(BYTE_SIZE(s)); + return SER_LEN(s); +} + +inline static void TERM_BIN(REBSER *s) { + BIN_HEAD(s)[SER_LEN(s)] = 0; +} + +inline static void TERM_BIN_LEN(REBSER *s, REBCNT len) { + SET_SERIES_LEN(s, len); + BIN_HEAD(s)[len] = 0; +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// BINARY! (uses `struct Reb_Any_Series`) +// +//=////////////////////////////////////////////////////////////////////////=// + +#define VAL_BIN(v) \ + BIN_HEAD(VAL_SERIES(v)) + +#define VAL_BIN_HEAD(v) \ + BIN_HEAD(VAL_SERIES(v)) + +inline static REBYTE *VAL_BIN_AT(const RELVAL *v) { + return BIN_AT(VAL_SERIES(v), VAL_INDEX(v)); +} + +inline static REBYTE *VAL_BIN_TAIL(const RELVAL *v) { + return SER_TAIL(REBYTE, VAL_SERIES(v)); +} + +// !!! RE: VAL_BIN_AT_HEAD() see remarks on VAL_ARRAY_AT_HEAD() +// +#define VAL_BIN_AT_HEAD(v,n) \ + BIN_AT(VAL_SERIES(v), (n)) + +#define VAL_BYTE_SIZE(v) \ + BYTE_SIZE(VAL_SERIES(v)) + +// defined as an inline to avoid side effects in: +// Init_Binary(v, Make_Binary()) +inline static void Init_Binary(RELVAL *out, REBSER *s) { + assert(BYTE_SIZE(s)); + Init_Any_Series(out, REB_BINARY, s); +} diff --git a/src/include/sys-bind.h b/src/include/sys-bind.h new file mode 100644 index 0000000000..5aa2d2fbd2 --- /dev/null +++ b/src/include/sys-bind.h @@ -0,0 +1,543 @@ +// +// File: %sys-bind.h +// Summary: "System Binding Include" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// R3-Alpha had a per-thread "bind table"; a large and sparsely populated hash +// into which index numbers would be placed, for what index those words would +// have as keys or parameters. Ren-C's strategy is that binding information +// is wedged into REBSER nodes that represent the canon words themselves. +// +// This would create problems if multiple threads were trying to bind at the +// same time. While threading was never realized in R3-Alpha, Ren-C doesn't +// want to have any "less of a plan". So the Reb_Binder is used by binding +// clients as a placeholder for whatever actual state would be used to augment +// the information in the canon word series about which client is making a +// request. This could be coupled with some kind of lockfree adjustment +// strategy whereby a word that was contentious would cause a structure to +// "pop out" and be pointed to by some atomic thing inside the word. +// +// For the moment, a binder has some influence by saying whether the high 16 +// bits or low 16 bits of the canon's misc.index are used. If the index +// were atomic this would--for instance--allow two clients to bind at once. +// It's just a demonstration of where more general logic using atomics +// that could work for N clients would be. +// +// The debug build also adds another feature, that makes sure the clear count +// matches the set count. +// + +// Modes allowed by Bind related functions: +enum { + BIND_0 = 0, // Only bind the words found in the context. + BIND_DEEP = 1 << 1, // Recurse into sub-blocks. + BIND_FUNC = 1 << 2 // Recurse into functions. +}; + + +struct Reb_Binder { + REBOOL high; +#if !defined(NDEBUG) + REBCNT count; +#endif +}; + + +inline static void INIT_BINDER(struct Reb_Binder *binder) { + binder->high = TRUE; //LOGICAL(SPORADICALLY(2)); sporadic? +#if !defined(NDEBUG) + binder->count = 0; +#endif +} + + +inline static void SHUTDOWN_BINDER(struct Reb_Binder *binder) { +#ifdef NDEBUG + UNUSED(binder); +#else + assert(binder->count == 0); +#endif +} + + +// Tries to set the binder index, but return false if already there. +// +inline static REBOOL Try_Add_Binder_Index( + struct Reb_Binder *binder, + REBSTR *canon, + REBINT index +){ + assert(index != 0); + assert(GET_SER_INFO(canon, STRING_INFO_CANON)); + if (binder->high) { + if (canon->misc.bind_index.high != 0) + return FALSE; + canon->misc.bind_index.high = index; + } + else { + if (canon->misc.bind_index.low != 0) + return FALSE; + canon->misc.bind_index.low = index; + } + +#if !defined(NDEBUG) + ++binder->count; +#endif + return TRUE; +} + + +inline static void Add_Binder_Index( + struct Reb_Binder *binder, + REBSTR *canon, + REBINT index +){ + REBOOL success = Try_Add_Binder_Index(binder, canon, index); + +#ifdef NDEBUG + UNUSED(success); +#else + assert(success); +#endif +} + + +inline static REBINT Try_Get_Binder_Index( // 0 if not present + struct Reb_Binder *binder, + REBSTR *canon +){ + assert(GET_SER_INFO(canon, STRING_INFO_CANON)); + + if (binder->high) + return canon->misc.bind_index.high; + else + return canon->misc.bind_index.low; +} + + +inline static REBINT Try_Remove_Binder_Index( // 0 if failure, else old index + struct Reb_Binder *binder, + REBSTR *canon +){ + assert(GET_SER_INFO(canon, STRING_INFO_CANON)); + + REBINT old_index; + if (binder->high) { + old_index = canon->misc.bind_index.high; + if (old_index == 0) + return 0; + canon->misc.bind_index.high = 0; + } + else { + old_index = canon->misc.bind_index.low; + if (old_index == 0) + return 0; + canon->misc.bind_index.low = 0; + } + +#if !defined(NDEBUG) + --binder->count; +#endif + return old_index; +} + + +inline static void Remove_Binder_Index( + struct Reb_Binder *binder, + REBSTR *canon +){ + REBINT old_index = Try_Remove_Binder_Index(binder, canon); + +#if defined(NDEBUG) + UNUSED(old_index); +#else + assert(old_index != 0); +#endif +} + + +// Modes allowed by Collect keys functions: +enum { + COLLECT_ONLY_SET_WORDS = 0, + COLLECT_ANY_WORD = 1 << 1, + COLLECT_DEEP = 1 << 2, + COLLECT_NO_DUP = 1 << 3, // Do not allow dups during collection (for specs) + COLLECT_ENSURE_SELF = 1 << 4 // !!! Ensure SYM_SELF in context (temp) +}; + + +//=////////////////////////////////////////////////////////////////////////=// +// +// VARIABLE ACCESS +// +//=////////////////////////////////////////////////////////////////////////=// +// +// When a word is bound to a context by an index, it becomes a means of +// reading and writing from a persistent storage location. We use "variable" +// or just VAR to refer to REBVAL slots reached via binding in this way. +// More narrowly, a VAR that represents an argument to a function invocation +// may be called an ARG (and an ARG's "persistence" is only as long as that +// function call is on the stack). +// +// All variables can be put in a protected state where they cannot be written. +// This protection status is marked on the KEY of the context. Again, more +// narrowly we may refer to a KEY that represents a parameter to a function +// as a PARAM. +// +// The Get_Opt_Var_May_Fail() function takes the conservative default that +// only const access is needed. A const pointer to a REBVAL is given back +// which may be inspected, but the contents not modified. While a bound +// variable that is not currently set will return a REB_MAX_VOID value, trying +// to Get_Opt_Var_May_Fail() on an *unbound* word will raise an error. +// +// Get_Mutable_Var_May_Fail() offers a parallel facility for getting a +// non-const REBVAL back. It will fail if the variable is either unbound +// -or- marked with OPT_TYPESET_LOCKED to protect against modification. +// + + +enum { + GETVAR_READ_ONLY = 0, + GETVAR_MUTABLE = 1 << 0, + GETVAR_END_IF_UNAVAILABLE = 1 << 1 +}; + + +// Get the word--variable--value. (Generally, use the macros like +// GET_VAR or GET_MUTABLE_VAR instead of this). This routine is +// called quite a lot and so attention to performance is important. +// +// Coded assuming most common case is to give an error on unbounds, and +// that only read access is requested (so no checking on protection) +// +// Due to the performance-critical nature of this routine, it is declared +// as inline so that locations using it can avoid overhead in invocation. +// +inline static REBVAL *Get_Var_Core( + const RELVAL *any_word, + REBSPC *specifier, + REBFLGS flags +) { + REBCTX *context; + + assert(ANY_WORD(any_word)); + + if (GET_VAL_FLAG(any_word, VALUE_FLAG_RELATIVE)) { + // + // RELATIVE BINDING: The word was made during a deep copy of the block + // that was given as a function's body, and stored a reference to that + // FUNCTION! as its binding. To get a variable for the word, we must + // find the right function call on the stack (if any) for the word to + // refer to (the FRAME!) + // + assert(GET_VAL_FLAG(any_word, WORD_FLAG_BOUND)); // should be set too + + #if !defined(NDEBUG) + if (specifier == SPECIFIED) { + printf("Get_Var_Core on relative value without specifier\n"); + panic (any_word); + } + #endif + + context = CTX(specifier); + + assert( + VAL_WORD_FUNC(any_word) == VAL_FUNC(CTX_FRAME_FUNC_VALUE(context)) + ); + } + else if (GET_VAL_FLAG(any_word, WORD_FLAG_BOUND)) { + // + // SPECIFIC BINDING: The context the word is bound to is explicitly + // contained in the `any_word` REBVAL payload. Just extract it. + // + // We use VAL_SPECIFIC_COMMON() here instead of the heavy-checked + // VAL_WORD_CONTEXT(), because const_KNOWN() checks for specificity + // and the context operations will ensure it's a context. + // + context = VAL_SPECIFIC_COMMON(const_KNOWN(any_word)); + } + else { + // UNBOUND: No variable location to retrieve. + + if (flags & GETVAR_END_IF_UNAVAILABLE) + return m_cast(REBVAL*, END); // only const callers should use + + fail (Error_Not_Bound_Raw(any_word)); + } + + REBCNT index = VAL_WORD_INDEX(any_word); + assert(index != 0); + + REBVAL *key = CTX_KEY(context, index); +#ifdef NDEBUG + UNUSED(key); +#else + assert(VAL_WORD_CANON(any_word) == VAL_KEY_CANON(key)); +#endif + + if (CTX_VARS_UNAVAILABLE(context)) { + // + // Currently if a context has a stack component, then the vars + // are "all stack"...so when that level is popped, all the vars + // will be unavailable. There is a mechanism, but that + // makes all the variables come from an ordinary pool-allocated + // series. Hybrid approaches which have "some stack and some + // durable" will be possible in the future, as a context can + // mechanically have both stackvars and a dynamic data pointer. + + if (flags & GETVAR_END_IF_UNAVAILABLE) + return m_cast(REBVAL*, END); // only const callers should use + + DECLARE_LOCAL (unbound); + Init_Any_Word( + unbound, + VAL_TYPE(any_word), + VAL_WORD_SPELLING(any_word) + ); + + fail (Error_No_Relative_Raw(unbound)); + } + + REBVAL *var = CTX_VAR(context, index); + + if (flags & GETVAR_MUTABLE) { + // + // A context can be permanently frozen (`lock obj`) or temporarily + // protected, e.g. `protect obj | unprotect obj`. + // + // !!! Technically speaking it could also be marked as immutable due + // to "running", though that feature is not used at this time. + // All 3 bits are checked in the same instruction. + // + FAIL_IF_READ_ONLY_CONTEXT(context); + + // The PROTECT command has a finer-grained granularity for marking + // not just contexts, but individual fields as protected. + // + if (GET_VAL_FLAG(var, VALUE_FLAG_PROTECTED)) + fail (Error_Protected_Word_Raw(any_word)); + + } + + assert(!THROWN(var)); + return var; +} + +static inline const REBVAL *Get_Opt_Var_May_Fail( + const RELVAL *any_word, + REBSPC *specifier +) { + return Get_Var_Core(any_word, specifier, GETVAR_READ_ONLY); +} + +static inline const REBVAL *Get_Opt_Var_Else_End( + const RELVAL *any_word, + REBSPC *specifier +) { + return Get_Var_Core( + any_word, specifier, GETVAR_READ_ONLY | GETVAR_END_IF_UNAVAILABLE + ); +} + +inline static void Copy_Opt_Var_May_Fail( + REBVAL *out, + const RELVAL *any_word, + REBSPC *specifier +) { + Move_Value(out, Get_Var_Core(any_word, specifier, GETVAR_READ_ONLY)); +} + +static inline REBVAL *Get_Mutable_Var_May_Fail( + const RELVAL *any_word, + REBSPC *specifier +) { + return Get_Var_Core(any_word, specifier, GETVAR_MUTABLE); +} + +#define Sink_Var_May_Fail(any_word,specifier) \ + SINK(Get_Mutable_Var_May_Fail(any_word, specifier)) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// DETERMINING SPECIFIER FOR CHILDREN IN AN ARRAY +// +//=////////////////////////////////////////////////////////////////////////=// +// +// A relative array must be combined with a specifier in order to find the +// actual context instance where its values can be found. Since today's +// specifiers are always nothing or a FRAME!'s context, this is fairly easy... +// if you find a specific child value living inside a relative array then +// it's that child's specifier that overrides the specifier in effect. +// +// With virtual binding this could get more complex, since a specifier may +// wish to augment or override the binding in a deep way on read-only blocks. +// That means specifiers may need to be chained together. This would create +// needs for GC or reference counting mechanics, which may defy a simple +// solution in C89. +// +// But as a first step, this function locates all the places in the code that +// would need such derivation. +// + +inline static REBSPC *Derive_Specifier(REBSPC *parent, const RELVAL *child) { + if (IS_SPECIFIC(child)) + return VAL_SPECIFIER(const_KNOWN(child)); + return parent; +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// COPYING RELATIVE VALUES TO SPECIFIC +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This can be used to turn a RELVAL into a REBVAL. If the RELVAL is indeed +// relative and needs to be made specific to be put into the target, then the +// specifier is used to do that. +// +// It is nearly as fast as just assigning the value directly in the release +// build, though debug builds assert that the function in the specifier +// indeed matches the target in the relative value (because relative values +// in an array may only be relative to the function that deep copied them, and +// that is the only kind of specifier you can use with them). +// +// Interface designed to line up with Move_Value() +// + +inline static REBVAL *Derelativize( + RELVAL *out, // relative destinations are overwritten with specified value + const RELVAL *v, + REBSPC *specifier +) { + assert(NOT_END(v)); + assert(!IS_TRASH_DEBUG(v)); + + ASSERT_CELL_WRITABLE(out, __FILE__, __LINE__); + + out->header.bits &= CELL_MASK_RESET; + + if (IS_RELATIVE(v)) { + #if !defined(NDEBUG) + assert(ANY_WORD(v) || ANY_ARRAY(v)); + if (specifier == SPECIFIED) { + printf("Relative item used with SPECIFIED\n"); + panic (v); + } + else if ( + VAL_RELATIVE(v) + != VAL_FUNC(CTX_FRAME_FUNC_VALUE(CTX(specifier))) + ){ + printf("Function mismatch in specific binding, expected:\n"); + PROBE(FUNC_VALUE(VAL_RELATIVE(v))); + printf("Panic on relative value\n"); + panic (v); + } + #endif + + out->header.bits |= + v->header.bits + & CELL_MASK_COPY + & ~cast(REBUPT, VALUE_FLAG_RELATIVE); // !!! flag is going away + + out->extra.binding = cast(REBARR*, specifier); + } + else { + out->header.bits |= v->header.bits & CELL_MASK_COPY; + out->extra.binding = v->extra.binding; + } + out->payload = v->payload; + + // in case the caller had a relative value slot and wants to use its + // known non-relative form... this is inline, so no cost if not used. + // + return KNOWN(out); +} + + +// In the C++ build, defining this overload that takes a REBVAL* instead of +// a RELVAL*, and then not defining it...will tell you that you do not need +// to use Derelativize. Juse Move_Value() if your source is a REBVAL! +// +#ifdef __cplusplus + REBVAL *Derelativize(RELVAL *dest, const REBVAL *v, REBSPC *specifier); +#endif + + +inline static void DS_PUSH_RELVAL(const RELVAL *v, REBSPC *specifier) { + ASSERT_VALUE_MANAGED(v); // would fail on END marker + DS_PUSH_TRASH; + Derelativize(DS_TOP, v, specifier); +} + + +// +// BINDING CONVENIENCE MACROS +// +// WARNING: Don't pass these routines something like a singular REBVAL* (such +// as a REB_BLOCK) which you wish to have bound. You must pass its *contents* +// as an array...as the plural "values" in the name implies! +// +// So don't do this: +// +// REBVAL *block = ARG(block); +// REBVAL *something = ARG(next_arg_after_block); +// Bind_Values_Deep(block, context); +// +// What will happen is that the block will be treated as an array of values +// and get incremented. In the above case it would reach to the next argument +// and bind it too (likely crashing at some point not too long after that). +// +// Instead write: +// +// Bind_Values_Deep(VAL_ARRAY_HEAD(block), context); +// +// That will pass the address of the first value element of the block's +// contents. You could use a later value element, but note that the interface +// as written doesn't have a length limit. So although you can control where +// it starts, it will keep binding until it hits an end marker. +// + +#define Bind_Values_Deep(values,context) \ + Bind_Values_Core((values), (context), TS_ANY_WORD, 0, BIND_DEEP) + +#define Bind_Values_All_Deep(values,context) \ + Bind_Values_Core((values), (context), TS_ANY_WORD, TS_ANY_WORD, BIND_DEEP) + +#define Bind_Values_Shallow(values, context) \ + Bind_Values_Core((values), (context), TS_ANY_WORD, 0, BIND_0) + +// Gave this a complex name to warn of its peculiarities. Calling with +// just BIND_SET is shallow and tricky because the set words must occur +// before the uses (to be applied to bindings of those uses)! +// +#define Bind_Values_Set_Midstream_Shallow(values, context) \ + Bind_Values_Core( \ + (values), (context), TS_ANY_WORD, FLAGIT_KIND(REB_SET_WORD), BIND_0) + +#define Unbind_Values_Deep(values) \ + Unbind_Values_Core((values), NULL, TRUE) diff --git a/src/include/sys-context.h b/src/include/sys-context.h new file mode 100644 index 0000000000..858e58c7cc --- /dev/null +++ b/src/include/sys-context.h @@ -0,0 +1,464 @@ +// +// File: %sys-context.h +// Summary: {Definitions for REBCTX} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// In Rebol terminology, a "context" is an abstraction which gives two +// parallel arrays, whose indices line up in a correspondence: +// +// * "keylist" - an array that contains TYPESET! values, but which have a +// symbol ID encoded as an extra piece of information for that key. +// +// * "varlist" - an array of equal length to the keylist, which holds an +// arbitrary REBVAL in each position that corresponds to its key. +// +// Contexts coordinate with words, which can have their VAL_WORD_CONTEXT() +// set to a context's series pointer. Then they cache the index of that +// word's symbol in the context's keylist, for a fast lookup to get to the +// corresponding var. The key is a typeset which has several flags +// controlling behaviors like whether the var is protected or hidden. +// +// !!! This "caching" mechanism is not actually "just a cache". Once bound +// the index is treated as permanent. This is why objects are "append only" +// because disruption of the index numbers would break the extant words +// with index numbers to that position. Ren-C might wind up undoing this by +// paying for the check of the symbol number at the time of lookup, and if +// it does not match consider it a cache miss and re-lookup...adjusting the +// index inside of the word. For efficiency, some objects could be marked +// as not having this property, but it may be just as efficient to check +// the symbol match as that bit. +// +// Frame key/var indices start at one, and they leave two REBVAL slots open +// in the 0 spot for other uses. With an ANY-CONTEXT!, the use for the +// "ROOTVAR" is to store a canon value image of the ANY-CONTEXT!'s REBVAL +// itself. This trick allows a single REBCTX* to be passed around rather +// than the REBVAL struct which is 4x larger, yet still reconstitute the +// entire REBVAL if it is needed. +// + +struct Reb_Context { + struct Reb_Array varlist; // keylist is held in ->link.keylist +}; + +#ifdef NDEBUG + #define ASSERT_CONTEXT(c) cast(void, 0) +#else + #define ASSERT_CONTEXT(c) Assert_Context_Core(c) +#endif + +// Series-to-Frame coercion, see notes in %sys-array.h header +// +inline static REBCTX *CTX(void *p) { + REBARR *a = ARR(p); + assert(GET_SER_FLAG(a, ARRAY_FLAG_VARLIST)); + return cast(REBCTX*, a); +} + +inline static REBARR *CTX_VARLIST(REBCTX *c) { + return &c->varlist; +} + + +// +// Special property: keylist pointer is stored in the misc field of REBSER +// + +inline static REBARR *CTX_KEYLIST(REBCTX *c) { + return SER(CTX_VARLIST(c))->link.keylist; +} + +static inline void INIT_CTX_KEYLIST_SHARED(REBCTX *c, REBARR *keylist) { + SET_SER_INFO(keylist, SERIES_INFO_SHARED_KEYLIST); + SER(CTX_VARLIST(c))->link.keylist = keylist; +} + +static inline void INIT_CTX_KEYLIST_UNIQUE(REBCTX *c, REBARR *keylist) { + assert(NOT_SER_INFO(keylist, SERIES_INFO_SHARED_KEYLIST)); + SER(CTX_VARLIST(c))->link.keylist = keylist; +} + +// Navigate from context to context components. Note that the context's +// "length" does not count the [0] cell of either the varlist or the keylist. +// Hence it must subtract 1. Internally to the context building code, the +// real length of the two series must be accounted for...so the 1 gets put +// back in, but most clients are only interested in the number of keys/values +// (and getting an answer for the length back that was the same as the length +// requested in context creation). +// +#define CTX_LEN(c) \ + (ARR_LEN(CTX_KEYLIST(c)) - 1) + +#define CTX_ROOTKEY(c) \ + SER_HEAD(REBVAL, SER(CTX_KEYLIST(c))) + +#define CTX_TYPE(c) \ + VAL_TYPE(CTX_VALUE(c)) + +// The keys and vars are accessed by positive integers starting at 1. If +// indexed access is used then the debug build will check to be sure that +// the indexing is legal. To get a pointer to the first key or value +// regardless of length (e.g. will be an END if 0 keys/vars) use HEAD +// +// Rather than use ARR_AT (which returns RELVAL*) for the vars, this uses +// SER_AT to get REBVALs back, because the values of the context are known to +// not live in function body arrays--hence they can't hold relative words. +// Keys can't hold relative values either. +// +inline static REBVAL *CTX_KEYS_HEAD(REBCTX *c) { + return SER_AT(REBVAL, SER(CTX_KEYLIST(c)), 1); +} + +// There may not be any dynamic or stack allocation available for a stack +// allocated context, and in that case it will have to come out of the +// REBSER node data itself. +// +inline static REBVAL *CTX_VALUE(REBCTX *c) { + return GET_SER_INFO(CTX_VARLIST(c), CONTEXT_INFO_STACK) + ? KNOWN(&SER(CTX_VARLIST(c))->content.values[0]) + : KNOWN(ARR_HEAD(CTX_VARLIST(c))); // not a RELVAL +} + +inline static REBFRM *CTX_FRAME_IF_ON_STACK(REBCTX *c) { + assert(IS_FRAME(CTX_VALUE(c))); + REBFRM *f = SER(CTX_VARLIST(c))->misc.f; + assert( + f == NULL + || ( + f->eval_type <= REB_FUNCTION + && f->label != NULL + ) // Note: inlining of Is_Any_Function_Frame() to break dependency + ); + return f; +} + +inline static REBVAL *CTX_VARS_HEAD(REBCTX *c) { + if (NOT(GET_SER_INFO(CTX_VARLIST(c), CONTEXT_INFO_STACK))) + return KNOWN(ARR_AT(CTX_VARLIST(c), 1)); + + REBFRM *f = CTX_FRAME_IF_ON_STACK(c); + assert(f != NULL); + return f->args_head; +} + +inline static REBVAL *CTX_KEY(REBCTX *c, REBCNT n) { + assert(n != 0 && n <= CTX_LEN(c)); + REBVAL *key = CTX_KEYS_HEAD(c) + (n) - 1; + assert(key->extra.key_spelling != NULL); + return key; +} + +inline static REBVAL *CTX_VAR(REBCTX *c, REBCNT n) { + REBVAL *var; + assert(n != 0 && n <= CTX_LEN(c)); + assert(GET_SER_FLAG(CTX_VARLIST(c), ARRAY_FLAG_VARLIST)); + + var = CTX_VARS_HEAD(c) + (n) - 1; + + assert(NOT(var->header.bits & VALUE_FLAG_RELATIVE)); + + return var; +} + +inline static REBSTR *CTX_KEY_SPELLING(REBCTX *c, REBCNT n) { + return CTX_KEY(c, n)->extra.key_spelling; +} + +inline static REBSTR *CTX_KEY_CANON(REBCTX *c, REBCNT n) { + return STR_CANON(CTX_KEY_SPELLING(c, n)); +} + +inline static REBSYM CTX_KEY_SYM(REBCTX *c, REBCNT n) { + return STR_SYMBOL(CTX_KEY_SPELLING(c, n)); // should be same as canon +} + +inline static REBCTX *CTX_META(REBCTX *c) { + return SER(CTX_KEYLIST(c))->link.meta; +} + +#define FAIL_IF_READ_ONLY_CONTEXT(c) \ + FAIL_IF_READ_ONLY_ARRAY(CTX_VARLIST(c)) + +inline static void FREE_CONTEXT(REBCTX *c) { + Free_Array(CTX_KEYLIST(c)); + Free_Array(CTX_VARLIST(c)); +} + +#define PUSH_GUARD_CONTEXT(c) \ + PUSH_GUARD_ARRAY(CTX_VARLIST(c)) // varlist points to/guards keylist + +#define DROP_GUARD_CONTEXT(c) \ + DROP_GUARD_ARRAY(CTX_VARLIST(c)) + + +inline static REBOOL CTX_VARS_UNAVAILABLE(REBCTX *c) { + // + // Mechanically any array can become inaccessible, but really the varlist + // of a stack context is the only case that should happen today. + // + if (GET_SER_INFO(CTX_VARLIST(c), SERIES_INFO_INACCESSIBLE)) { + assert(GET_SER_INFO(CTX_VARLIST(c), CONTEXT_INFO_STACK)); + return TRUE; + } + return FALSE; +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// ANY-CONTEXT! (`struct Reb_Any_Context`) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The Reb_Any_Context is the basic struct used currently for OBJECT!, +// MODULE!, ERROR!, and PORT!. It builds upon the context datatype REBCTX, +// which permits the storage of associated KEYS and VARS. +// + +#ifdef NDEBUG + #define ANY_CONTEXT_FLAG(n) \ + FLAGIT_LEFT(TYPE_SPECIFIC_BIT + (n)) +#else + #define ANY_CONTEXT_FLAG(n) \ + (FLAGIT_LEFT(TYPE_SPECIFIC_BIT + (n)) | HEADERIZE_KIND(REB_OBJECT)) +#endif + +// `ANY_CONTEXT_FLAG_OWNS_PAIRED` is particular to the idea of a "Paired" +// REBSER, which is actually just two REBVALs. For purposes of the API, +// it is possible for one of those values to be used to manage the +// lifetime of the pair. One technique is to tie the value's lifetime +// to that of a particular FRAME! +// +#define ANY_CONTEXT_FLAG_OWNS_PAIRED ANY_CONTEXT_FLAG(0) + + +inline static REBCTX *VAL_CONTEXT(const RELVAL *v) { + assert(ANY_CONTEXT(v)); + assert(v->payload.any_context.phase == NULL || VAL_TYPE(v) == REB_FRAME); + return CTX(v->payload.any_context.varlist); +} + +inline static void INIT_VAL_CONTEXT(REBVAL *v, REBCTX *c) { + v->payload.any_context.varlist = CTX_VARLIST(c); +} + +// Convenience macros to speak in terms of object values instead of the context +// +#define VAL_CONTEXT_VAR(v,n) \ + CTX_VAR(VAL_CONTEXT(v), (n)) + +#define VAL_CONTEXT_KEY(v,n) \ + CTX_KEY(VAL_CONTEXT(v), (n)) + +inline static REBCTX *VAL_CONTEXT_META(const RELVAL *v) { + return SER( + CTX_KEYLIST(CTX(v->payload.any_context.varlist)) + )->link.meta; +} + +#define VAL_CONTEXT_KEY_SYM(v,n) \ + CTX_KEY_SYM(VAL_CONTEXT(v), (n)) + +inline static void INIT_CONTEXT_META(REBCTX *c, REBCTX *m) { + SER(CTX_KEYLIST(c))->link.meta = m; +} + +inline static REBVAL *CTX_FRAME_FUNC_VALUE(REBCTX *c) { + assert(IS_FUNCTION(CTX_ROOTKEY(c))); + return CTX_ROOTKEY(c); +} + +// The movement of the SELF word into the domain of the object generators +// means that an object may wind up having a hidden SELF key (and it may not). +// Ultimately this key may well occur at any position. While user code is +// discouraged from accessing object members by integer index (`pick obj 1` +// is an error), system code has historically relied upon this. +// +// During a transitional period where all MAKE OBJECT! constructs have a +// "real" SELF key/var in the first position, there needs to be an adjustment +// to the indexing of some of this system code. Some of these will be +// temporary, because not all objects will need a definitional SELF (just as +// not all functions need a definitional RETURN). Exactly which require it +// and which do not remains to be seen, so this macro helps review the + 1 +// more easily than if it were left as just + 1. +// +#define SELFISH(n) \ + ((n) + 1) + +#define Init_Any_Context(out,kind,context) \ + Init_Any_Context_Core((out), (kind), (context)) + +#define Init_Object(v,c) \ + Init_Any_Context((v), REB_OBJECT, (c)) + +#define Init_Port(v,c) \ + Init_Any_Context((v), REB_PORT, (c)) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// COMMON INLINES (macro-like) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// By putting these functions in a header file, they can be inlined by the +// compiler, rather than add an extra layer of function call. +// + +#define Copy_Context_Shallow(src) \ + Copy_Context_Shallow_Extra((src), 0) + +// Returns true if the keylist had to be changed to make it unique. +// +#define Ensure_Keylist_Unique_Invalidated(context) \ + Expand_Context_Keylist_Core((context), 0) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// FIELD SELECTION +// +//=////////////////////////////////////////////////////////////////////////=// +// +// For performance reasons, most code within the core does not use lookups +// by symbol in objects. The specific objects the core deals with (e.g. in +// %sysobj.r) have the indexes hardcoded for the fields it wants to access, +// so it can just use CTX_VAR() to get the pointer directly, without needing +// to canonize symbols or walk the keylist. These routines are provided as +// a convenience. +// + +inline static REBVAL *Get_Typed_Field( + REBCTX *c, + REBSTR *spelling, // will be canonized + enum Reb_Kind kind // REB_0 to not check the kind +) { + REBCNT n = Find_Canon_In_Context(c, STR_CANON(spelling), FALSE); + if (n == 0) + fail ("Field not found"); // improve error + + REBVAL *var = CTX_VAR(c, n); + if (kind == REB_0) + return var; + + if (kind != VAL_TYPE(var)) + fail ("Invalid type of field"); // improve error + return var; +} + +#define Get_Field(c, spelling) \ + Get_Typed_Field((c), (spelling), REB_0) // will canonize + +#define Sink_Field(c, spelling) \ + SINK(Get_Typed_Field(c, (spelling), REB_0)) // will canonize + + +//=////////////////////////////////////////////////////////////////////////=// +// +// LOCKING +// +//=////////////////////////////////////////////////////////////////////////=// + +inline static void Deep_Freeze_Context(REBCTX *c) { + Protect_Context( + c, + FLAGIT(PROT_SET) | FLAGIT(PROT_DEEP) | FLAGIT(PROT_FREEZE) + ); + Uncolor_Array(CTX_VARLIST(c)); +} + +inline static REBOOL Is_Context_Deeply_Frozen(REBCTX *c) { + return GET_SER_INFO(CTX_VARLIST(c), SERIES_INFO_FROZEN); +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// ERROR! (uses `struct Reb_Any_Context`) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Errors are a subtype of ANY-CONTEXT! which follow a standard layout. +// That layout is in %boot/sysobj.r as standard/error. +// +// Historically errors could have a maximum of 3 arguments, with the fixed +// names of `arg1`, `arg2`, and `arg3`. They would also have a numeric code +// which would be used to look up a a formatting block, which would contain +// a block for a message with spots showing where the args were to be inserted +// into a message. These message templates can be found in %boot/errors.r +// +// Ren-C is exploring the customization of user errors to be able to provide +// arbitrary named arguments and message templates to use them. It is +// a work in progress, but refer to the FAIL native, the corresponding +// `fail()` C macro inside the source, and the various routines in %c-error.c +// + +#define ERR_VARS(e) \ + cast(ERROR_VARS*, CTX_VARS_HEAD(e)) + +inline static REBCNT ERR_NUM(REBCTX *e) { + ERROR_VARS* vars = ERR_VARS(e); + if (IS_BLANK(&vars->code)) + return RE_USER; + assert(IS_INTEGER(&vars->code)); // Note: C build doesn't check VAL_INT32 + return cast(REBCNT, VAL_INT32(&vars->code)); +} + +#define VAL_ERR_VARS(v) \ + ERR_VARS(VAL_CONTEXT(v)) + +#define VAL_ERR_NUM(v) \ + ERR_NUM(VAL_CONTEXT(v)) + +#define Init_Error(v,c) \ + Init_Any_Context((v), REB_ERROR, (c)) + + +// Ports are unusual hybrids of user-mode code dispatched with native code, so +// some things the user can do to the internals of a port might cause the +// C code to crash. This wasn't very well thought out in R3-Alpha, but there +// was some validation checking. This factors out that check instead of +// repeating the code. +// +inline static void FAIL_IF_BAD_PORT(REBCTX *port) { + assert(GET_SER_FLAG(CTX_VARLIST(port), ARRAY_FLAG_VARLIST)); + + if ( + (CTX_LEN(port) < STD_PORT_MAX - 1) || + !IS_OBJECT(CTX_VAR(port, STD_PORT_SPEC)) + ) { + fail (Error_Invalid_Port_Raw()); + } +} + +// It's helpful to show when a test for a native port actor is being done, +// rather than just having the code say IS_HANDLE(). +// +inline static REBOOL Is_Native_Port_Actor(const REBVAL *actor) { + if (IS_HANDLE(actor)) + return TRUE; + assert(IS_OBJECT(actor)); + return FALSE; +} diff --git a/src/include/sys-core.h b/src/include/sys-core.h index 0dceae36b4..1f1e7f380f 100644 --- a/src/include/sys-core.h +++ b/src/include/sys-core.h @@ -1,448 +1,747 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: System Core Include -** Module: sys-core.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %sys-core.h +// Summary: "Single Complete Include File for Using the Internal Api" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This is the main include file used in the implementation of the core. +// +// * It defines all the data types and structures used by the auto-generated +// function prototypes. This includes the obvious REBINT, REBVAL, REBSER. +// It also includes any enumerated type parameters to functions which are +// shared between various C files. +// +// * With those types defined, it includes %tmp-funcs.h - which is basically +// all the non-inline "internal API" functions. This list of function +// prototypes is generated automatically by a Rebol script that scans the +// %.c files during the build process. +// +// * Next it starts including various headers in a specific order. These +// build on the data definitions and call into the internal API. Since they +// are often inline functions and not macros, the complete prototypes and +// data definitions they use must have already been defined. +// +// %sys-core.h is supposed to be platform-agnostic. All the code which would +// include something like would be linked in as "host code". Yet +// if a file wishes to include %sys-core.h and , it should do: +// +// #define UNICODE // enable unicode OS API in windows.h +// #include +// +// /* #include any non-Rebol windows dependencies here */ +// +// #undef IS_ERROR // means something different +// #undef max // same +// #undef min // same +// +// #include "sys-core.h" +// #include "reb-config.h" -// Set as compiler symbol flags: -//#define UNICODE // enable unicode OS API (windows) - // Internal configuration: -#define REB_DEF // kernel definitions and structs -#define ASSERTIONS // special run-time checks -//#define DEBUGGING // debug output and debugger assistance -//#define SERIES_LABELS // enable identifier labels for series -//#define MUNGWALL // memory allocation bounds checking -#define STACK_MIN 4000 // data stack increment size -#define STACK_LIMIT 400000 // data stack max (6.4MB) -#define MIN_COMMON 10000 // min size of common buffer -#define MAX_COMMON 100000 // max size of common buffer (shrink trigger) -#define MAX_NUM_LEN 64 // As many numeric digits we will accept on input -#define MAX_SAFE_SERIES 5 // quanitity of most recent series to not GC. -#define MAX_EXPAND_LIST 5 // number of series-1 in Prior_Expand list -#define USE_UNICODE 1 // scanner uses unicode -#define UNICODE_CASES 0x2E00 // size of unicode folding table -#define HAS_SHA1 // allow it -#define HAS_MD5 // allow it +#define REB_DEF // kernel definitions and structs +#define STACK_MIN 4000 // data stack increment size +#define STACK_LIMIT 400000 // data stack max (6.4MB) +#define MIN_COMMON 10000 // min size of common buffer +#define MAX_COMMON 100000 // max size of common buffer (shrink trigger) +#define MAX_NUM_LEN 64 // As many numeric digits we will accept on input +#define MAX_EXPAND_LIST 5 // number of series-1 in Prior_Expand list +#define UNICODE_CASES 0x2E00 // size of unicode folding table +#define HAS_SHA1 // allow it +#define HAS_MD5 // allow it // External system includes: #include -#include // For var-arg Print functions +#include // For var-arg Print functions #include #include #include - -// Special OS-specific definitions: -#ifdef OS_DEFS - #ifdef TO_WIN32 - #include - #undef IS_ERROR - #endif - //#error The target platform must be specified (TO_* define) +#include // for offsetof() + + +// +// ASSERTIONS +// +// Assertions are in debug builds only, and use the conventional standard C +// assert macro. The code inside the assert will be removed if the flag +// NDEBUG is defined to indicate "NoDEBUGging". While negative logic is +// counter-intuitive (e.g. `#ifndef NDEBUG` vs. `#ifdef DEBUG`) it's the +// standard and is the least of evils: +// +// http://stackoverflow.com/a/17241278/211160 +// +// Assertions should mostly be used as a kind of "traffic cone" when working +// on new code (or analyzing a bug you're trying to trigger in development). +// It's preferable to update the design via static typing or otherwise as the +// code hardens. +// +#include +#include "assert-fixes.h" + + +// +// DISABLE STDIO.H IN RELEASE BUILD +// +// The core build of Rebol published in R3-Alpha sought to not be dependent +// on . The intent--ostensibly--was since Rebol had richer tools +// like WORD!s and BLOCK! for dialecting, that including a brittle historic +// string-based C "mini-language" of printf into the executable was a +// wasteful dependency. Also, many implementations are clunky: +// +// http://blog.hostilefork.com/where-printf-rubber-meets-road/ +// +// Hence formatted output was not presumed as a host service, which only +// provided raw character string output. +// +// This "radical decoupling" idea was undermined by including a near-rewrite +// of printf() called Debug_Fmt(). This was a part of release builds, and +// added format specifiers for Rebol values ("%r") or series, as well as +// handling a subset of basic C types. +// +// Ren-C's long-term goal is to not include any string-based dialect for +// formatting output. Low-level diagnostics in the debug build will rely on +// printf, while all release build formatting will be done through Rebol +// code...where the format specification is done with a Rebol BLOCK! dialect +// that could be used by client code as well. +// +// To formalize this rule, these definitions will help catch uses of +// in the release build, and give a hopefully informative error. +// +#ifdef NDEBUG + #if !defined(REN_C_STDIO_OK) + #define printf dont_include_stdio_h + #define fprintf dont_include_stdio_h + #define putc dont_include_stdio_h + #endif +#else + // Desire to not bake in notwithstanding, in debug builds it + // can be convenient (or even essential) to have access to stdio. This + // is especially true when trying to debug the core I/O routines and + // unicode/UTF8 conversions that Rebol seeks to replace stdio with. + // + // Hence debug builds are allowed to use stdio.h conveniently. The + // release build should catch if any of these aren't #if !defined(NDEBUG) + // + #include #endif -#ifdef OS_IO - #include - #include + +// +// PROGRAMMATIC C BREAKPOINT +// +// This header file brings in the ability to trigger a programmatic breakpoint +// in C code, by calling `debug_break();` It is not supported by HaikuOS R1, +// so instead kick into an infinite loop which can be broken and stepped out +// of in the debugger. +// +#if !defined(NDEBUG) + #if defined(TO_HAIKU) + inline static int debug_break() { + int x = 0; + while (1) { ++x; } + x = 0; // set next statement in debugger to here + } + #else + #include "debugbreak.h" + #endif #endif -// Local includes: + +// The %reb-c.h file includes something like C99's for setting up +// a basis for concrete data type sizes, which define the Rebol basic types +// (such as REBOOL, REBYTE, REBU64, etc.) It also contains some other helpful +// macros and tools for C programming. +// #include "reb-c.h" + + +// !!! Is there a more ideal location for these prototypes? +typedef int cmp_t(void *, const void *, const void *); +extern void reb_qsort_r(void *a, size_t n, size_t es, void *thunk, cmp_t *cmp); + + +// Must be defined at the end of reb-c.h, but not *in* reb-c.h so that +// files including sys-core.h and reb-host.h can have differing +// definitions of REBCHR. (We want it opaque to the core, but the +// host to have it compatible with the native character type w/o casting) +// +#ifdef OS_WIDE_CHAR + #ifdef NDEBUG + typedef REBUNI REBCHR; + #else + typedef struct tagREBCHR { + REBUNI num; + } REBCHR; + #endif +#else + #ifdef NDEBUG + typedef REBYTE REBCHR; + #else + typedef struct tagREBCHR { + REBYTE num; + } REBCHR; + #endif +#endif + #include "reb-defs.h" -#include "reb-args.h" -#include "tmp-bootdefs.h" -#define PORT_ACTIONS A_CREATE // port actions begin here #include "reb-device.h" #include "reb-types.h" #include "reb-event.h" +#include "reb-file.h" +#include "reb-filereq.h" +#include "reb-math.h" + +#include "sys-rebnod.h" + #include "sys-deci.h" -#include "sys-value.h" -#include "tmp-strings.h" -#include "tmp-funcargs.h" +#include "tmp-bootdefs.h" + +#include "sys-rebval.h" // REBVAL structure definition +#include "sys-action.h" + +typedef void (*CLEANUP_FUNC)(const REBVAL*); // for some HANDLE!s GC callback + +#include "sys-rebser.h" // REBSER series definition (embeds REBVAL definition) + +typedef void (*MAKE_FUNC)(REBVAL*, enum Reb_Kind, const REBVAL*); +typedef void (*TO_FUNC)(REBVAL*, enum Reb_Kind, const REBVAL*); + +#include "sys-state.h" +#include "sys-rebfrm.h" // `REBFRM` definition (also used by value) +#include "sys-indexor.h" // REBIXO definition //-- Port actions (for native port schemes): + typedef struct rebol_port_action_map { - const REBCNT action; - const REBPAF func; + const REBSYM action; + const REBPAF func; } PORT_ACTION; typedef struct rebol_mold { - REBSER *series; // destination series (uni) - REBCNT opts; // special option flags - REBINT indent; // indentation amount -// REBYTE space; // ? - REBYTE period; // for decimal point - REBYTE dash; // for date fields - REBYTE digits; // decimal digits + REBSER *series; // destination series (uni) + REBCNT start; // index where this mold starts within series + REBFLGS opts; // special option flags + REBCNT limit; // how many characters before cutting off with "..." + REBCNT reserve; // how much capacity to reserve at the outset + REBINT indent; // indentation amount + REBYTE period; // for decimal point + REBYTE dash; // for date fields + REBYTE digits; // decimal digits } REB_MOLD; -#include "reb-file.h" -#include "reb-filereq.h" -#include "reb-math.h" -#include "reb-codec.h" +#define Drop_Mold_If_Pushed(mo) \ + Drop_Mold_Core((mo), TRUE) -#include "tmp-sysobj.h" -#include "tmp-sysctx.h" +#define Drop_Mold(mo) \ + Drop_Mold_Core((mo), FALSE) + +#define Pop_Molded_String(mo) \ + Pop_Molded_String_Core((mo), UNKNOWN) + +#define Pop_Molded_String_Len(mo,len) \ + Pop_Molded_String_Core((mo), (len)) + + + +/*********************************************************************** +** +** Structures +** +***********************************************************************/ + +//-- Measurement Variables: +typedef struct rebol_stats { + REBI64 Series_Memory; + REBCNT Series_Made; + REBCNT Series_Freed; + REBCNT Series_Expanded; + REBCNT Recycle_Counter; + REBCNT Recycle_Series_Total; + REBCNT Recycle_Series; + REBI64 Recycle_Prior_Eval; + REBCNT Mark_Count; + REBCNT Blocks; + REBCNT Objects; +} REB_STATS; + +//-- Options of various kinds: +typedef struct rebol_opts { + REBOOL watch_recycle; + REBOOL watch_series; + REBOOL watch_expand; + REBOOL crash_dump; +} REB_OPTS; + +typedef struct rebol_time_fields { + REBCNT h; + REBCNT m; + REBCNT s; + REBCNT n; +} REB_TIMEF; -//#include "reb-net.h" -#include "sys-panics.h" -#include "tmp-boot.h" -#include "sys-mem.h" -#include "tmp-errnums.h" -#include "host-lib.h" -#include "sys-stack.h" /*********************************************************************** ** -** Constants +** Constants ** ***********************************************************************/ enum Boot_Phases { - BOOT_START = 0, - BOOT_LOADED, - BOOT_ERRORS, - BOOT_MEZZ, - BOOT_DONE + BOOT_START = 0, + BOOT_LOADED, + BOOT_ERRORS, + BOOT_MEZZ, + BOOT_DONE }; enum Boot_Levels { - BOOT_LEVEL_BASE, - BOOT_LEVEL_SYS, - BOOT_LEVEL_MODS, - BOOT_LEVEL_FULL + BOOT_LEVEL_BASE, + BOOT_LEVEL_SYS, + BOOT_LEVEL_MODS, + BOOT_LEVEL_FULL }; -// Modes allowed by Copy_Block function: +// Modes allowed by Make_Function: enum { - COPY_SHALLOW = 0, - COPY_DEEP, // recurse into blocks - COPY_STRINGS, // copy strings in blocks - COPY_ALL, // both deep, strings (3) -// COPY_IGNORE = 4, // ignore tail position (used for stack args) - COPY_OBJECT = 8, // copy an object - COPY_SAME = 16, + MKF_NONE = 0, // no special handling (e.g. MAKE FUNCTION!) + MKF_RETURN = 1 << 0, // has definitional RETURN + MKF_LEAVE = 1 << 1, // has definitional LEAVE + MKF_KEYWORDS = 1 << 2, // respond to tags like , , + MKF_ANY_VALUE = 1 << 3, // args and return are [ any-value!] + MKF_FAKE_RETURN = 1 << 4 // has RETURN but not actually in frame }; -#define CP_DEEP TYPESET(63) +// Modes allowed by FORM +enum { + FORM_FLAG_ONLY = 0, + FORM_FLAG_REDUCE = 1 << 0, + FORM_FLAG_NEWLINE_SEQUENTIAL_STRINGS = 1 << 1, + FORM_FLAG_NEWLINE_UNLESS_EMPTY = 1 << 2, + FORM_FLAG_MOLD = 1 << 3 +}; -#define TS_NOT_COPIED (TYPESET(REB_IMAGE) | TYPESET(REB_VECTOR) | TYPESET(REB_TASK) | TYPESET(REB_PORT)) -#define TS_STD_SERIES (TS_SERIES & ~TS_NOT_COPIED) -#define TS_SERIES_OBJ ((TS_SERIES | TS_OBJECT) & ~TS_NOT_COPIED) -#define TS_BLOCKS_OBJ ((TS_BLOCK | TS_OBJECT) & ~TS_NOT_COPIED) +// Modes allowed by Copy_Block function: +enum { + COPY_SHALLOW = 0, + COPY_DEEP, // recurse into blocks + COPY_STRINGS, // copy strings in blocks + COPY_ALL, // both deep, strings (3) +// COPY_IGNORE = 4, // ignore tail position (used for stack args) + COPY_OBJECT = 8, // copy an object + COPY_SAME = 16 +}; -#define TS_CODE ((CP_DEEP | TS_SERIES) & ~TS_NOT_COPIED) -#define TS_FUNCLOS (TYPESET(REB_FUNCTION) | TYPESET(REB_CLOSURE)) -#define TS_CLONE ((CP_DEEP | TS_SERIES | TS_FUNCLOS) & ~TS_NOT_COPIED) +// Breakpoint hook callback +typedef REBOOL (*REBBRK)(REBVAL *instruction_out, REBOOL interrupted); -// Modes allowed by Bind related functions: -enum { - BIND_ONLY = 0, // Only bind the words found in the context. - BIND_SET, // Add set-words to the context during the bind. - BIND_ALL, // Add words to the context during the bind. - BIND_DEEP = 4, // Recurse into sub-blocks. - BIND_GET = 8, // Lookup :word and use its word value - BIND_NO_DUP = 16, // Do not allow dups during word collection (for specs) - BIND_FUNC = 32, // Recurse into functions. - BIND_NO_SELF = 64, // Do not bind SELF (in closures) -}; -// Modes for Rebind_Block: +// Flags used for Protect functions +// enum { - REBIND_TYPE = 1, // Change frame type when rebinding - REBIND_FUNC = 2, // Rebind function and closure bodies - REBIND_TABLE = 4, // Use bind table when rebinding + PROT_SET, + PROT_DEEP, + PROT_HIDE, + PROT_WORD, + PROT_FREEZE, + PROT_MAX }; // Mold and form options: enum REB_Mold_Opts { - MOPT_MOLD_ALL, // Output lexical types in #[type...] format - MOPT_COMMA_PT, // Decimal point is a comma. - MOPT_SLASH_DATE, // Date as 1/1/2000 -// MOPT_MOLD_VALS, // Value parts are molded (strings are kept as is) - MOPT_FILE, // Molding %file - MOPT_INDENT, // Indentation - MOPT_TIGHT, // No space between block values - MOPT_NO_NONE, // Do not output UNSET or NONE object vars - MOPT_EMAIL, - MOPT_ONLY, // Mold/only - no outer block [] - MOPT_LINES, // add a linefeed between each value + MOPT_MOLD_ALL, // Output lexical types in #[type...] format + MOPT_COMMA_PT, // Decimal point is a comma. + MOPT_SLASH_DATE, // Date as 1/1/2000 + MOPT_FILE, // Molding %file + MOPT_INDENT, // Indentation + MOPT_TIGHT, // No space between block values + MOPT_EMAIL, // ? + MOPT_ONLY, // Mold/only - no outer block [] + MOPT_LINES, // add a linefeed between each value + MOPT_LIMIT, // Limit length of mold to mold->limit, then "..." + MOPT_RESERVE, // At outset, reserve space for buffer (with length 0) + MOPT_MAX }; #define GET_MOPT(v, f) GET_FLAG(v->opts, f) // Special flags for decimal formatting: -#define DEC_MOLD_PERCENT 1 // follow num with % -#define DEC_MOLD_MINIMAL 2 // allow decimal to be integer +enum { + DEC_MOLD_PERCENT = 1 << 0, // follow num with % + DEC_MOLD_MINIMAL = 1 << 1 // allow decimal to be integer +}; // Temporary: -#define MOPT_ANSI_ONLY MOPT_MOLD_ALL // Non ANSI chars are ^() escaped - -// Reflector words (words-of, body-of, etc.) -enum Reb_Reflectors { - OF_BASE, - OF_WORDS, // to be compatible with R2 - OF_BODY, - OF_SPEC, - OF_VALUES, - OF_TYPES, - OF_TITLE, +#define MOPT_NON_ANSI_PARENED MOPT_MOLD_ALL // Non ANSI chars are ^() escaped + +// Options for To_REBOL_Path +enum { + PATH_OPT_UNI_SRC = 1 << 0, // whether the source series is uni + PATH_OPT_FORCE_UNI_DEST = 1 << 1, // even if just latin1 chars, do uni + PATH_OPT_SRC_IS_DIR = 1 << 2 }; // Load option flags: enum { - LOAD_ALL = 0, // Returns header along with script if present - LOAD_HEADER, // Converts header to object, checks values - LOAD_NEXT, // Load next value - LOAD_NORMAL, // Convert header, load script - LOAD_REQUIRE // Header is required, else error + LOAD_ALL = 0, // Returns header along with script if present + LOAD_HEADER, // Converts header to object, checks values + LOAD_NEXT, // Load next value + LOAD_NORMAL, // Convert header, load script + LOAD_REQUIRE, // Header is required, else error + LOAD_MAX }; -// General constants: -#define NOT_FOUND ((REBCNT)-1) -#define UNKNOWN ((REBCNT)-1) -#define LF 10 -#define CR 13 -#define TAB '\t' -#define CRLF "\r\n" #define TAB_SIZE 4 -// Move this: -enum Insert_Arg_Nums { - AN_SERIES = 1, - AN_VALUE, - AN_PART, - AN_LENGTH, - AN_ONLY, - AN_DUP, - AN_COUNT +// Move these things: +enum act_modify_mask { + AM_BINARY_SERIES = 1 << 0, + AM_PART = 1 << 1, + AM_ONLY = 1 << 2 +}; +enum act_find_mask { + AM_FIND_ONLY = 1 << 0, + AM_FIND_CASE = 1 << 1, + AM_FIND_LAST = 1 << 2, + AM_FIND_REVERSE = 1 << 3, + AM_FIND_TAIL = 1 << 4, + AM_FIND_MATCH = 1 << 5 +}; +enum act_open_mask { + AM_OPEN_NEW = 1 << 0, + AM_OPEN_READ = 1 << 1, + AM_OPEN_WRITE = 1 << 2, + AM_OPEN_SEEK = 1 << 3, + AM_OPEN_ALLOW = 1 << 4 +}; +// Rounding flags (passed as refinements to ROUND function): +enum { + RF_TO = 1 << 0, + RF_EVEN = 1 << 1, + RF_DOWN = 1 << 2, + RF_HALF_DOWN = 1 << 3, + RF_FLOOR = 1 << 4, + RF_CEILING = 1 << 5, + RF_HALF_CEILING = 1 << 6 }; enum rebol_signals { - SIG_RECYCLE, - SIG_ESCAPE, - SIG_EVENT_PORT, + // + // SIG_RECYCLE indicates a need to run the garbage collector, when + // running it synchronously could be dangerous. This is important in + // particular during memory allocation, which can detect crossing a + // memory usage boundary that suggests GC'ing would be good...but might + // be in the middle of code that is halfway through manipulating a + // managed series. + // + SIG_RECYCLE, + + // SIG_HALT means return to the topmost level of the evaluator, regardless + // of how deep a debug stack might be. It is the only instruction besides + // QUIT and RESUME that can currently get past a breakpoint sandbox. + // + SIG_HALT, + + // SIG_INTERRUPT indicates a desire to enter an interactive debugging + // state. Because the ability to manage such a state may not be + // registered by the host, this could generate an error. + // + SIG_INTERRUPT, + + // SIG_EVENT_PORT is to-be-documented + // + SIG_EVENT_PORT, + + SIG_MAX }; // Security flags: enum { - SEC_ALLOW, - SEC_ASK, - SEC_THROW, - SEC_QUIT, + SEC_ALLOW, + SEC_ASK, + SEC_THROW, + SEC_QUIT, + SEC_MAX }; // Security policy byte offsets: enum { - POL_READ, - POL_WRITE, - POL_EXEC, + POL_READ, + POL_WRITE, + POL_EXEC, + POL_MAX }; // Encoding options: enum encoding_opts { - ENC_OPT_BIG, // big endian (not little) - ENC_OPT_UTF8, // UTF-8 - ENC_OPT_UTF16, // UTF-16 - ENC_OPT_UTF32, // UTF-32 - ENC_OPT_BOM, // byte order marker - ENC_OPT_CRLF, // CR line termination - ENC_OPT_NO_COPY, // do not copy if ASCII + OPT_ENC_BIG_ENDIAN = 1 << 0, // little is default + OPT_ENC_UTF8 = 1 << 1, + OPT_ENC_UTF16 = 1 << 2, + OPT_ENC_UTF32 = 1 << 3, + OPT_ENC_BOM = 1 << 4, // byte order marker + OPT_ENC_CRLF = 1 << 5, // CR line termination, see OPT_ENC_CRLF_MAYBE + OPT_ENC_UNISRC = 1 << 6, // source is UCS2 + OPT_ENC_RAW = 1 << 7 // raw binary, no encoding }; -#define ENCF_NO_COPY (1<0) -#define ASSERT1(c,m) if (!(c)) Crash(m); // Not in beta releases -#if (ALEVEL>1) -#define ASSERT2(c,m) if (!(c)) Crash(m); // Not in any releases -#endif -#endif -#define MEM_CARE 5 // Lower number for more frequent checks +#include "sys-trap.h" // includes PUSH_TRAP, fail(), and panic() macros +#include "sys-node.h" -#define LOOP(n) for (; n > 0; n--) -#define FOREACH(n, limit) for (n = 0; n < limit; n++) -#define FOR_BLK(b, v, t) for (v = VAL_BLK_DATA(b), t = VAL_BLK_TAIL(b); v != t; v++) -#define FOR_SER(b, v, i, s) for (; v = BLK_SKIP(b, i), i < SERIES_TAIL(b); i += skip) +#include "sys-value.h" // basic definitions that don't need series accessrors -#define UP_CASE(c) Upper_Cases[c] -#define LO_CASE(c) Lower_Cases[c] -#define IS_WHITE(c) ((c) <= 32 && (White_Chars[c]&1) != 0) -#define IS_SPACE(c) ((c) <= 32 && (White_Chars[c]&2) != 0) +#include "sys-series.h" +#include "sys-binary.h" +#include "sys-string.h" -#define SET_SIGNAL(f) SET_FLAG(Eval_Signals, f) -#define GET_SIGNAL(f) GET_FLAG(Eval_Signals, f) -#define CLR_SIGNAL(f) CLR_FLAG(Eval_Signals, f) +#include "sys-array.h" -#define DECIDE(cond) if (cond) goto is_true; else goto is_false -#define REM2(a, b) ((b)!=-1 ? (a) % (b) : 0) -//#define DO_BLOCK(v) Do_Block(VAL_SERIES(v), VAL_INDEX(v)) -#define DO_BLK(v) Do_Blk(VAL_SERIES(v), VAL_INDEX(v)) +#include "sys-handle.h" -#define DEAD_END return 0 // makes compiler happy (for never used return case) +#include "sys-typeset.h" +#include "sys-context.h" +#include "sys-function.h" +#include "sys-word.h" -#define NO_RESULT ((REBCNT)(-1)) -#define ALL_BITS ((REBCNT)(-1)) -#ifdef HAS_LL_CONSTS -#define ALL_64 ((REBU64)0xffffffffffffffffLL) -#else -#define ALL_64 ((REBU64)0xffffffffffffffffL) -#endif +#include "sys-pair.h" +#include "sys-map.h" -#define BOOT_STR(c,i) PG_Boot_Strs[(c)+(i)] +#include "sys-varargs.h" -//-- Temporary Buffers -// These are reused for cases for appending, when length cannot be known. -#define BUF_EMIT VAL_SERIES(TASK_BUF_EMIT) -#define BUF_WORDS VAL_SERIES(TASK_BUF_WORDS) -#define BUF_PRINT VAL_SERIES(TASK_BUF_PRINT) -#define BUF_FORM VAL_SERIES(TASK_BUF_FORM) -#define BUF_MOLD VAL_SERIES(TASK_BUF_MOLD) -#define BUF_UTF8 VAL_SERIES(TASK_BUF_UTF8) -#define MOLD_LOOP VAL_SERIES(TASK_MOLD_LOOP) +#include "sys-stack.h" -#ifdef OS_WIDE_CHAR -#define BUF_OS_STR BUF_MOLD -#else -#define BUF_OS_STR BUF_FORM -#endif +#include "sys-frame.h" +#include "sys-bind.h" -// Save/Unsave Macros: -#define SAVE_SERIES(s) Save_Series(s) -#ifdef ASSERTIONS -#define UNSAVE_SERIES(s) GC_Protect->tail--;\ - ASSERT(((REBSER **)GC_Protect->data)[GC_Protect->tail] == s, RP_HOLD_SERIES_MALIGN) -#else -#define UNSAVE_SERIES(s) GC_Protect->tail-- -#endif +#include "sys-scan.h" -#ifdef OS_STACK_GROWS_UP -#define CHECK_STACK(v) if ((REBCNT)(v) >= Stack_Limit) Trap_Stack(); -#else -#define CHECK_STACK(v) if ((REBCNT)(v) <= Stack_Limit) Trap_Stack(); -#endif -#define STACK_BOUNDS (4*1024*1000) // note: need a better way to set it !! -// Also: made somewhat smaller than linker setting to allow trapping it +#include "reb-struct.h" +#include "host-lib.h" /*********************************************************************** ** -** Structures +** Macros ** ***********************************************************************/ -// Word Table Structure - used to manage hashed word tables (symbol tables). -typedef struct rebol_word_table -{ - REBSER *series; // Global block of words - REBSER *hashes; // Hash table -// REBCNT count; // Number of units used in hash table -} WORD_TABLE; +// Generic defines: +#define ALIGN(s, a) (((s) + (a)-1) & ~((a)-1)) -//-- Measurement Variables: -typedef struct rebol_stats { - REBI64 Series_Memory; - REBCNT Series_Made; - REBCNT Series_Freed; - REBCNT Series_Expanded; - REBCNT Recycle_Counter; - REBCNT Recycle_Series_Total; - REBCNT Recycle_Series; - REBI64 Recycle_Prior_Eval; - REBCNT Mark_Count; - REBCNT Free_List_Checked; - REBCNT Blocks; - REBCNT Objects; -} REB_STATS; +#define MEM_CARE 5 // Lower number for more frequent checks -//-- Options of various kinds: -typedef struct rebol_opts { - REBFLG watch_obj_copy; - REBFLG watch_recycle; - REBFLG watch_series; - REBFLG watch_expand; - REBFLG crash_dump; -} REB_OPTS; +#define UP_CASE(c) Upper_Cases[c] +#define LO_CASE(c) Lower_Cases[c] +#define IS_WHITE(c) ((c) <= 32 && (White_Chars[c]&1) != 0) +#define IS_SPACE(c) ((c) <= 32 && (White_Chars[c]&2) != 0) + +inline static void SET_SIGNAL(REBFLGS f) { + SET_FLAG(Eval_Signals, f); + Eval_Count = 1; +} + +#define GET_SIGNAL(f) GET_FLAG(Eval_Signals, (f)) +#define CLR_SIGNAL(f) CLR_FLAG(Eval_Signals, (f)) + + +//-- Temporary Buffers +// These are reused for cases for appending, when length cannot be known. + +#define BUF_COLLECT VAL_ARRAY(TASK_BUF_COLLECT) +#define MOLD_STACK VAL_ARRAY(TASK_MOLD_STACK) + +#define BYTE_BUF VAL_SERIES(TASK_BYTE_BUF) +#define UNI_BUF VAL_SERIES(TASK_UNI_BUF) +#define BUF_UTF8 VAL_SERIES(TASK_BUF_UTF8) -typedef struct rebol_time_fields { - REBCNT h; - REBCNT m; - REBCNT s; - REBCNT n; -} REB_TIMEF; /*********************************************************************** ** -** Thread Shared Variables +** Legacy Modes Checking ** -** Set by main boot and not changed after that. +** Ren/C wants to try out new things that will likely be included +** it the official Rebol3 release. But it also wants transitioning +** to be feasible from Rebol2 and R3-Alpha, without paying that +** much to check for "old" modes if they're not being used. So +** system/options contains flags used for enabling specific +** features relied upon by old code. +** +** In order to keep these easements from adding to the measured +** performance cost in the system (and to keep them from being +** used for anything besides porting), they are only supported in +** debug builds. ** ***********************************************************************/ -extern const REBACT Value_Dispatch[]; -//extern const REBYTE Upper_Case[]; -//extern const REBYTE Lower_Case[]; +#ifdef NDEBUG + #define SET_VOID_UNLESS_LEGACY_NONE(v) \ + Init_Void(v) // LEGACY() only available in Debug builds +#else + #define LEGACY(option) ( \ + (PG_Boot_Phase >= BOOT_ERRORS) \ + && IS_CONDITIONAL_TRUE(Get_System(SYS_OPTIONS, (option))) \ + ) + + #define LEGACY_RUNNING(option) \ + (LEGACY(option) && In_Legacy_Function_Debug()) + + // In legacy mode Ren-C still supports the old convention that IFs that + // don't take the true branch or a WHILE loop that never runs a body + // return a BLANK! value instead of no value. See implementation notes. + // + #ifdef NDEBUG + #define SET_VOID_UNLESS_LEGACY_NONE(v) \ + Init_Void(v) // LEGACY() only available in Debug builds + #else + #define SET_VOID_UNLESS_LEGACY_NONE(v) \ + SET_VOID_UNLESS_LEGACY_NONE_Debug(v, __FILE__, __LINE__); + #endif +#endif -#include "tmp-funcs.h" +// +// Dispatch Table Prototypes +// +// These dispatch tables are generated and have data declarations in .inc +// files. Those data declarations can only be included once, yet the tables +// may be used in multiple modules. +// +// The tables never contain NULL values. Instead there is a dispatcher in +// the slot which will fail if it is ever called. +// -/*********************************************************************** -** -** Threaded Global Variables -** -***********************************************************************/ +extern const REBACT Value_Dispatch[REB_MAX]; // in %tmp-evaltypes.inc +extern const REBPEF Path_Dispatch[REB_MAX]; // in %tmp-evaltypes.inc +extern const REBCTF Compare_Types[REB_MAX]; // in %tmp-comptypes.inc +extern const MAKE_FUNC Make_Dispatch[REB_MAX]; // in %tmp-maketypes.inc +extern const TO_FUNC To_Dispatch[REB_MAX]; // in %tmp-maketypes.inc -#define PVAR extern -#define TVAR extern THREAD -#include "sys-globals.h" +#include "sys-do.h" +#include "sys-path.h" diff --git a/src/include/sys-dec-to-char.h b/src/include/sys-dec-to-char.h index a159bd7020..e1727bc891 100644 --- a/src/include/sys-dec-to-char.h +++ b/src/include/sys-dec-to-char.h @@ -1,40 +1,39 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** REBOL is a trademark of REBOL Technologies -** -** Copyright 2012 Saphirion AG -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Decimal conversion -** Author: Ladislav Mecir -** Notes: -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ +// +// File: %sys-dec-to-char.h +// Summary: "Decimal conversion wrapper" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 Saphirion AG +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// -char *dtoa(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve); -double STRTOD(const char *s00, char **se); +EXTERN_C char *dtoa( + double dd, + int mode, + int ndigits, + int *decpt, + int *sign, + char **rve +); + +EXTERN_C double STRTOD(const char *s00, const char **se); \ No newline at end of file diff --git a/src/include/sys-deci-funcs.h b/src/include/sys-deci-funcs.h index 1563635189..5f4cefce64 100644 --- a/src/include/sys-deci-funcs.h +++ b/src/include/sys-deci-funcs.h @@ -1,41 +1,43 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Deci Datatype Functions -** Module: sys-deci-funcs.h -** Notes: -** -***********************************************************************/ +// +// File: %sys-deci-funcs.h +// Summary: "Deci Datatype Functions" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// /* unary operators - logic */ -REBFLG deci_is_zero (const deci a); +REBOOL deci_is_zero (const deci a); /* unary operators - deci */ deci deci_abs (deci a); deci deci_negate (deci a); /* binary operators - logic */ -REBFLG deci_is_equal (deci a, deci b); -REBFLG deci_is_lesser_or_equal (deci a, deci b); -REBFLG deci_is_same (deci a, deci b); +REBOOL deci_is_equal (deci a, deci b); +REBOOL deci_is_lesser_or_equal (deci a, deci b); +REBOOL deci_is_same (deci a, deci b); /* binary operators - deci */ deci deci_add (deci a, deci b); @@ -47,8 +49,8 @@ deci deci_mod (deci a, deci b); /* conversion to deci */ deci int_to_deci (REBI64 a); deci decimal_to_deci (REBDEC a); -deci string_to_deci (REBYTE *s, REBYTE **endptr); -deci binary_to_deci(REBYTE *s); +deci string_to_deci (const REBYTE *s, const REBYTE **endptr); +deci binary_to_deci(const REBYTE *s); /* conversion to other datatypes */ REBI64 deci_to_int (const deci a); diff --git a/src/include/sys-deci.h b/src/include/sys-deci.h index 171a8d8125..f878718933 100644 --- a/src/include/sys-deci.h +++ b/src/include/sys-deci.h @@ -1,35 +1,37 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Deci Datatype -** Module: sys-deci.h -** Notes: -** -***********************************************************************/ +// +// File: %sys-deci.h +// Summary: "Deci Datatype" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// typedef struct deci { - unsigned m0:32; /* significand, lowest part */ - unsigned m1:32; /* significand, continuation */ - unsigned m2:23; /* significand, highest part */ - unsigned s:1; /* sign, 0 means nonnegative, 1 means nonpositive */ - int e:8; /* exponent */ + unsigned m0:32; /* significand, lowest part */ + unsigned m1:32; /* significand, continuation */ + unsigned m2:23; /* significand, highest part */ + unsigned s:1; /* sign, 0 means nonnegative, 1 means nonpositive */ + int e:8; /* exponent */ } deci; diff --git a/src/include/sys-do.h b/src/include/sys-do.h new file mode 100644 index 0000000000..3d799e0634 --- /dev/null +++ b/src/include/sys-do.h @@ -0,0 +1,969 @@ +// +// File: %sys-do.h +// Summary: {Evaluator Helper Functions and Macros} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The primary routine that performs DO and DO/NEXT is called Do_Core(). It +// takes a single parameter which holds the running state of the evaluator. +// This state may be allocated on the C variable stack: Do_Core() is +// written such that a longjmp up to a failure handler above it can run +// safely and clean up even though intermediate stacks have vanished. +// +// Ren-C can run the evaluator across a REBARR-style series of input based on +// index. It can also enumerate through C's `va_list`, providing the ability +// to pass pointers as REBVAL* to comma-separated input at the source level. +// (Someday it may fetch values from a standard C array of REBVAL[] as well.) +// +// To provide even greater flexibility, it allows the very first element's +// pointer in an evaluation to come from an arbitrary source. It doesn't +// have to be resident in the same sequence from which ensuing values are +// pulled, allowing a free head value (such as a FUNCTION! REBVAL in a local +// C variable) to be evaluated in combination from another source (like a +// va_list or series representing the arguments.) This avoids the cost and +// complexity of allocating a series to combine the values together. +// +// These features alone would not cover the case when REBVAL pointers that +// are originating with C source were intended to be supplied to a function +// with no evaluation. In R3-Alpha, the only way in an evaluative context +// to suppress such evaluations would be by adding elements (such as QUOTE). +// Besides the cost and labor of inserting these, the risk is that the +// intended functions to be called without evaluation, if they quoted +// arguments would then receive the QUOTE instead of the arguments. +// +// The problem was solved by adding a feature to the evaluator which was +// also opened up as a new privileged native called EVAL. EVAL's refinements +// completely encompass evaluation possibilities in R3-Alpha, but it was also +// necessary to consider cases where a value was intended to be provided +// *without* evaluation. This introduced EVAL/ONLY. +// + + +// Each iteration of DO bumps a global count, that in deterministic repro +// cases can be very helpful in identifying the "tick" where certain problems +// are occurring. The SPORADICALLY() macro uses this to allow flipping +// between different behaviors in debug builds--usually to run the release +// behavior some of the time, and the debug behavior some of the time. This +// exercises the release code path even when doing a debug build. +// +#ifdef NDEBUG + #define SPORADICALLY(modulus) \ + FALSE +#else + #define SPORADICALLY(modulus) \ + (TG_Do_Count % modulus == 0) +#endif + +inline static REBOOL IS_QUOTABLY_SOFT(const RELVAL *v) { + return LOGICAL(IS_GROUP(v) || IS_GET_WORD(v) || IS_GET_PATH(v)); +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// DO's LOWEST-LEVEL EVALUATOR HOOKING +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This API is used internally in the implementation of Do_Core. It does +// not speak in terms of arrays or indices, it works entirely by setting +// up a call frame (f), and threading that frame's state through successive +// operations, vs. setting it up and disposing it on each DO/NEXT step. +// +// Like higher level APIs that move through the input series, this low-level +// API can move at full DO/NEXT intervals. Unlike the higher APIs, the +// possibility exists to move by single elements at a time--regardless of +// if the default evaluation rules would consume larger expressions. Also +// making it different is the ability to resume after a DO/NEXT on value +// sources that aren't random access (such as C's va_arg list). +// +// One invariant of access is that the input may only advance. Before any +// operations are called, any low-level client must have already seeded +// f->value with a valid "fetched" REBVAL*. END is not valid input, so +// callers beginning a Do_To_End must pre-check that condition themselves +// before calling Do_Core. And if an operation sets the c->index to END_FLAG +// then that must be checked--it's not legal to call more operations on a +// call frame after a fetch reports the end. +// +// Operations are: +// +// Fetch_Next_In_Frame() +// +// Retrieve next pointer for examination to f->value. The previous +// f->value pointer is overwritten. (No REBVAL bits are moved by +// this operation, only the 'currently processing' pointer reassigned.) +// f->value may become an END marker...test with IS_END() +// +// Do_Next_In_Frame_Throws() +// +// Executes the already-fetched pointer, consuming as much of the input +// as necessary to complete a /NEXT (or failing with an error). This +// writes the computed REBVAL into a destination location. After the +// operation, the next f->value pointer will already be fetched and +// waiting for examination or use. The returned value may be THROWN(), +// and IS_END(f->value) may be true after the operation. +// +// Quote_Next_In_Frame() +// +// This operation is fairly trivial in the sense that it just assigns +// the REBVAL bits pointed to by the current value to the destination +// cell. Then it does a simple fetch. The main reason for making an +// operation vs just having callers do the two steps is to monitor +// when some of the input has been "consumed" vs. merely fetched. +// +// This is not intending to be a "published" API of Rebol/Ren-C. But the +// privileged level of access can be used by natives that feel they can +// optimize performance by working with the evaluator directly. +// + +inline static void Push_Frame_Core(REBFRM *f) +{ + // All calls to a Do_Core() are assumed to happen at the same C stack + // level for a pushed frame (though this is not currently enforced). + // Hence it's sufficient to check for C stack overflow only once, e.g. + // not on each Do_Next_In_Frame_Throws() for `reduce [a | b | ... | z]`. + // + if (C_STACK_OVERFLOWING(&f)) + Trap_Stack_Overflow(); + + f->prior = TG_Frame_Stack; + TG_Frame_Stack = f; + if (NOT(f->flags.bits & DO_FLAG_VA_LIST)) { + if (GET_SER_INFO(f->source.array, SERIES_INFO_RUNNING)) + NOOP; // already temp-locked + else { + SET_SER_INFO(f->source.array, SERIES_INFO_RUNNING); + f->flags.bits |= DO_FLAG_TOOK_FRAME_LOCK; + } + } +} + +inline static void UPDATE_EXPRESSION_START(REBFRM *f) { + f->expr_index = f->index; // note this is garbage if DO_FLAG_VA_LIST +} + +inline static void Drop_Frame_Core(REBFRM *f) { + if (f->flags.bits & DO_FLAG_TOOK_FRAME_LOCK) { + assert(GET_SER_INFO(f->source.array, SERIES_INFO_RUNNING)); + CLEAR_SER_INFO(f->source.array, SERIES_INFO_RUNNING); + } + assert(TG_Frame_Stack == f); + TG_Frame_Stack = f->prior; +} + + +// +// Code that walks across Rebol arrays and performs evaluations must consider +// that arbitrary user code may disrupt the array being enumerated. If the +// array is to expand, it might have a different data pointer entirely. +// + +inline static void Push_Frame_At( + REBFRM *f, + REBARR *array, + REBCNT index, + REBSPC *specifier, + REBUPT flags +) { + SET_FRAME_VALUE(f, ARR_AT(array, index)); + f->source.array = array; + + Init_Endlike_Header(&f->flags, flags); + + f->gotten = END; // tells ET_WORD and ET_GET_WORD they must do a get + f->index = index + 1; + f->specifier = specifier; + f->pending = NULL; + + // The goal of pushing a frame is to reuse it for several sequential + // operations, when not using DO_FLAG_TO_END. This is found in operations + // like ANY and ALL, or anything that needs to do additional processing + // beyond a plain DO. Each time those operations run, they can set the + // output to a new location, and Do_Next_In_Frame_Throws() will call into + // Do_Core() and properly configure the eval_type. + // + // But to make the frame safe for Recycle() in-between the calls to + // Do_Next_In_Frame_Throws(), the eval_type and output cannot be left as + // uninitialized bits. So start with an unwritable END, and then + // each evaluation will canonize the eval_type to REB_0 in-between. + // (Do_Core() does not do this, but the wrappers that need it do.) + // + f->eval_type = REB_0; + f->out = m_cast(REBVAL*, END); + + Push_Frame_Core(f); +} + +inline static void Push_Frame(REBFRM *f, const REBVAL *v) +{ + Push_Frame_At( + f, VAL_ARRAY(v), VAL_INDEX(v), VAL_SPECIFIER(v), DO_FLAG_NORMAL + ); +} + +inline static void Drop_Frame(REBFRM *f) +{ + assert(f->eval_type == REB_0); + Drop_Frame_Core(f); +} + + +#define VA_LIST_PENDING \ + cast(const RELVAL*, &PG_Va_List_Pending) + + +// +// Fetch_Next_In_Frame() (see notes above) +// +// This routine is optimized assuming the common case is that values are +// being read out of an array. Whether to read out of a C va_list or to use +// a "virtual" next value (e.g. an old value saved by EVAL) are both indicated +// by f->pending, hence a NULL test of that can be executed quickly. +// +inline static void Fetch_Next_In_Frame(REBFRM *f) { + // + // If f->value is pointing to f->cell, it's possible that it may wind up + // with an END in it between fetches if f->cell gets reused (as in when + // arguments are pushed for a function) + // + assert(NOT_END(f->value) || f->value == &f->cell); + + assert(f->gotten == END); // we'd be invalidating it! + + if (f->pending == NULL) { + SET_FRAME_VALUE(f, ARR_AT(f->source.array, f->index)); + ++f->index; + } + else if (f->pending == VA_LIST_PENDING) { + SET_FRAME_VALUE(f, va_arg(*f->source.vaptr, const REBVAL*)); + assert( + IS_END(f->value) + || (IS_VOID(f->value) && (f->flags.bits & DO_FLAG_NO_ARGS_EVALUATE)) + || !IS_RELATIVE(f->value) + ); + assert(NOT(IN_DATA_STACK_DEBUG(f->value))); + } + else { + SET_FRAME_VALUE(f, f->pending); + if (f->flags.bits & DO_FLAG_VA_LIST) + f->pending = VA_LIST_PENDING; + else + f->pending = NULL; + } +} + + +// This is a very light wrapper over Do_Core(), which is used with +// Push_Frame_At() for operations like ANY or REDUCE that wish to perform +// several successive operations on an array, without creating a new frame +// each time. +// +inline static REBOOL Do_Next_In_Frame_Throws( + REBVAL *out, + REBFRM *f +){ + assert(f->eval_type == REB_0); // see notes in Push_Frame_At() + assert(NOT(f->flags.bits & DO_FLAG_TO_END)); + + SET_END(out); + f->out = out; + Do_Core(f); // should already be pushed + + // Since Do_Core() currently makes no guarantees about the state of + // f->eval_type when an operation is over, restore it to a benign REB_0 + // so that a GC between calls to Do_Next_In_Frame_Throws() doesn't think + // it has to protect the frame as another running type. + // + f->eval_type = REB_0; + return THROWN(out); +} + + +// Slightly heavier wrapper over Do_Core() than Do_Next_In_Frame_Throws(). +// It also reuses the frame...but has to clear and restore the frame's +// flags. It is currently used only by SET-WORD! and SET-PATH!. +// +// Note: Consider pathological case `x: eval quote y: eval eval quote z: ...` +// This can be done without making a new frame, but the eval cell which holds +// the SET-WORD! needs to be put back in place before returning, so that the +// set knows where to write. The caller handles this with the data stack. +// +// !!! Review how much cheaper this actually is than making a new frame. +// +inline static REBOOL Do_Next_Mid_Frame_Throws(REBFRM *f) { + assert(f->eval_type == REB_SET_WORD || f->eval_type == REB_SET_PATH); + + REBFLGS prior_flags = f->flags.bits; + Init_Endlike_Header(&f->flags, DO_FLAG_NORMAL); // e.g. no DO_FLAG_TO_END + + REBDSP prior_dsp_orig = f->dsp_orig; +#if !defined(NDEBUG) + assert(f->state_debug.dsp == f->dsp_orig); +#endif + + SET_END(f->out); + Do_Core(f); // should already be pushed + + // The & on the following line is purposeful. See Init_Endlike_Header. + // + (&f->flags)->bits = prior_flags; // e.g. restore DO_FLAG_TO_END + + f->dsp_orig = prior_dsp_orig; +#if !defined(NDEBUG) + f->state_debug.dsp = prior_dsp_orig; +#endif + + // Note: f->eval_type will have changed, but it should not matter to + // REB_SET_WORD or REB_SET_PATH, which will either continue executing + // the frame and fetch a new eval_type (if DO_FLAG_TO_END) else return + // with no guarantee about f->eval_type. + + return THROWN(f->out); +} + +// +// !!! This operation used to provide some optimization beyond setting up +// a frame for a nested Do_Core(). It would take simpler cases which could +// be done without a nested frame and hand them back more immediately, and +// if it found it couldn't do an optimization then the work done in any +// word fetches could be reused by keeping the fetch result in `f->gotten` +// +// Checking for whether an optimization would be legal or not was complex, +// as even something inert like `1` cannot be evaluated into a slot as `1` +// unless one is sure that there isn't an ensuing `+` or other enfixed +// operation. Hence, complex evaluator logic had to be reproduced here +// and second-guessed, often falling through to no optimization. +// +// Over time as the evaluator got more complicated, the redundant work and +// conditional code paths showed a slight *slowdown* over just having an +// inline straight-line function that built a frame and recursed Do_Core(). +// Future investigation could attack the problem again and see if there is +// any common case that actually offered an advantage to optimize for here. +// +inline static REBOOL Do_Next_In_Subframe_Throws( + REBVAL *out, + REBFRM *parent, + REBUPT flags +){ + // It should not be necessary to use a subframe unless there is meaningful + // state which would be overwritten in the parent frame. For the moment, + // that only happens if a function call is in effect. Otherwise, it is + // more efficient to use Do_Next_In_Frame_Throws(). + // + // !!! Note: It is currently the case that SET-WORD! and SET-PATH! also + // generate a new frame, in order that lookback quoting can find them. + // This method is being reviewed in order to generalize it, hopefully + // saving on frame creations in the process. + // + assert( + parent->eval_type == REB_FUNCTION + || parent->eval_type == REB_SET_WORD + || parent->eval_type == REB_SET_PATH + ); + + DECLARE_FRAME (child); + + child->gotten = parent->gotten; + + SET_END(out); + child->out = out; + + child->source = parent->source; + SET_FRAME_VALUE(child, parent->value); + child->index = parent->index; + child->specifier = parent->specifier; + Init_Endlike_Header(&child->flags, flags); + child->pending = parent->pending; + + Push_Frame_Core(child); + Do_Core(child); + Drop_Frame_Core(child); + + // !!! `print 1 + 2 <| print 1 + 7` wishes to print 3 and then 8, rather + // than print 8 and then evaluate...(is that good?) + + assert( + (child->flags.bits & DO_FLAG_VA_LIST) + || parent->index != child->index + || THROWN(out) + ); + parent->pending = child->pending; + SET_FRAME_VALUE(parent, child->value); + parent->index = child->index; + parent->gotten = child->gotten; + + return THROWN(out); +} + + +inline static void Quote_Next_In_Frame(REBVAL *dest, REBFRM *f) { + Derelativize(dest, f->value, f->specifier); + SET_VAL_FLAG(dest, VALUE_FLAG_UNEVALUATED); + f->gotten = END; + Fetch_Next_In_Frame(f); +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// BASIC API: DO_NEXT_MAY_THROW and DO_ARRAY_THROWS +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This is a wrapper for a single evaluation. If one is planning to do +// multiple evaluations, it is not as efficient as creating a frame and then +// doing `Do_Next_In_Frame_Throws()` calls into it. +// +// DO_NEXT_MAY_THROW takes in an array and a REBCNT offset into that array +// of where to execute. Although the return value is a REBCNT, it is *NOT* +// always a series index!!! It may return END_FLAG, THROWN_FLAG, VA_LIST_FLAG +// +// Do_Any_Array_At_Throws is another helper for the frequent case where one +// has a BLOCK! or a GROUP! REBVAL at an index which already indicates the +// point where execution is to start. +// +// (The "Throws" name is because it's expected to usually be used in an +// 'if' statement. It cues you into realizing that it returns TRUE if a +// THROW interrupts this current DO_BLOCK execution--not asking about a +// "THROWN" that happened as part of a prior statement.) +// +// If it returns FALSE, then the DO completed successfully to end of input +// without a throw...and the output contains the last value evaluated in the +// block (empty blocks give void). If it returns TRUE then it will be the +// THROWN() value. +// +inline static REBIXO DO_NEXT_MAY_THROW( + REBVAL *out, + REBARR *array, + REBCNT index, + REBSPC *specifier +){ + DECLARE_FRAME (f); + + SET_FRAME_VALUE(f, ARR_AT(array, index)); + if (IS_END(f->value)) { + Init_Void(out); + return END_FLAG; + } + + f->source.array = array; + f->specifier = specifier; + f->index = index + 1; + + Init_Endlike_Header(&f->flags, DO_FLAG_NORMAL); + + f->pending = NULL; + f->gotten = END; + + SET_END(out); + f->out = out; + + Push_Frame_Core(f); + Do_Core(f); + Drop_Frame_Core(f); // Drop_Frame() requires f->eval_type to be REB_0 + + if (THROWN(out)) + return THROWN_FLAG; + + if (IS_END(f->value)) + return END_FLAG; + + assert(f->index > 1); + return f->index - 1; +} + + +// Most common case of evaluator invocation in Rebol: the data lives in an +// array series. Generic routine takes flags and may act as either a DO +// or a DO/NEXT at the position given. Option to provide an element that +// may not be resident in the array to kick off the execution. +// +inline static REBIXO Do_Array_At_Core( + REBVAL *out, + const RELVAL *opt_first, // must also be relative to specifier if relative + REBARR *array, + REBCNT index, + REBSPC *specifier, + REBFLGS flags +) { + DECLARE_FRAME (f); + + if (opt_first) { + SET_FRAME_VALUE(f, opt_first); + f->index = index; + } + else { + // Do_Core() requires caller pre-seed first value, always + // + SET_FRAME_VALUE(f, ARR_AT(array, index)); + f->index = index + 1; + } + + if (IS_END(f->value)) { + Init_Void(out); + return END_FLAG; + } + + SET_END(out); + f->out = out; + + f->source.array = array; + f->specifier = specifier; + + Init_Endlike_Header(&f->flags, flags); // see notes on definition + + f->gotten = END; // so ET_WORD and ET_GET_WORD do their own Get_Var + f->pending = NULL; + + Push_Frame_Core(f); + Do_Core(f); + Drop_Frame_Core(f); + + if (THROWN(f->out)) + return THROWN_FLAG; // !!! prohibits recovery from exits + + return IS_END(f->value) ? END_FLAG : f->index; +} + + +// !!! Not yet implemented--concept is to accept a REBVAL[] array, rather +// than a REBARR of values. +// +// !!! Considerations of this core interface are to see the values as being +// potentially in non-contiguous points in memory, and advanced with some +// skip length between them. Additionally the idea of some kind of special +// Rebol value or "REB_INSTRUCTION" to say how far to skip is a possibility, +// which would be more general in the sense that it would allow the skip +// distances to be generalized, though this would cost a pointer size +// entity at each point. The advantage of REB_INSTRUCTION is that only the +// clients using the esoteric ability would be paying anything for it or +// the API complexity, but if an important client like Ren-C++ it might +// be worth the savings. +// +// Note: Functionally it would be possible to assume a 0 index and require +// the caller to bump the value pointer as necessary. But an index-based +// interface is likely useful to avoid the bookkeeping required for the caller. +// +/*inline static REBIXO Do_Values_At_Core( + REBVAL *out, + REBFLGS flags, + const REBVAL *opt_head, + const REBVAL values[], + REBCNT index +) { + fail (Error_Not_Done_Raw()); +}*/ + + +// +// Reify_Va_To_Array_In_Frame: C +// +// For performance and memory usage reasons, a variadic C function call that +// wants to invoke the evaluator with just a comma-delimited list of REBVAL* +// does not need to make a series to hold them. Do_Core is written to use +// the va_list traversal as an alternate to DO-ing an ARRAY. +// +// However, va_lists cannot be backtracked once advanced. So in a debug mode +// it can be helpful to turn all the va_lists into arrays before running +// them, so stack frames can be inspected more meaningfully--both for upcoming +// evaluations and those already past. +// +// A non-debug reason to reify a va_list into an array is if the garbage +// collector needs to see the upcoming values to protect them from GC. In +// this case it only needs to protect those values that have not yet been +// consumed. +// +// Because items may well have already been consumed from the va_list() that +// can't be gotten back, we put in a marker to help hint at the truncation +// (unless told that it's not truncated, e.g. a debug mode that calls it +// before any items are consumed). +// +inline static void Reify_Va_To_Array_In_Frame( + REBFRM *f, + REBOOL truncated +) { + REBDSP dsp_orig = DSP; + + assert(f->flags.bits & DO_FLAG_VA_LIST); + + if (truncated) { + DS_PUSH_TRASH; + Init_Word(DS_TOP, Canon(SYM___OPTIMIZED_OUT__)); + } + + if (NOT_END(f->value)) { + do { + DS_PUSH_RELVAL(f->value, f->specifier); // may be void + Fetch_Next_In_Frame(f); + } while (NOT_END(f->value)); + + if (truncated) + f->index = 2; // skip the --optimized-out-- + else + f->index = 1; // position at the start of the extracted values + } + else { + // Leave at the END, but give back the array to serve as + // notice of the truncation (if it was truncated) + // + f->index = 0; + } + + // We're about to overwrite the va_list pointer in the f->source union, + // which means there'd be no way to call va_end() on it if we don't do it + // now. The Do_Va_Core() routine is aware of this, and doesn't try to do + // a second va_end() if the conversion has happened here. + // + // Note: Fail_Core() also has to do this tie-up of the va_list, since + // the Do_Core() call in Do_Va_Core() never returns. + // + va_end(*f->source.vaptr); + + f->source.array = Pop_Stack_Values(dsp_orig); // may contain voids + MANAGE_ARRAY(f->source.array); // held alive while frame running + SET_SER_FLAG(f->source.array, ARRAY_FLAG_VOIDS_LEGAL); + + // The array just popped into existence, and it's tied to a running + // frame...so safe to say we locked it. (This would be more complex if + // we reused the empty array if dsp_orig == DSP, since someone else + // might have it locked...not worth the complexity.) + // + SET_SER_INFO(f->source.array, SERIES_INFO_RUNNING); + f->flags.bits |= DO_FLAG_TOOK_FRAME_LOCK; + + if (truncated) + SET_FRAME_VALUE(f, ARR_AT(f->source.array, 1)); // skip `--optimized--` + else + SET_FRAME_VALUE(f, ARR_HEAD(f->source.array)); + + // We clear the DO_FLAG_VA_LIST, assuming that the truncation marker is + // enough information to record the fact that it was a va_list (revisit + // if there's another reason to know what it was...) + + f->flags.bits &= ~cast(REBUPT, DO_FLAG_VA_LIST); + + assert(f->pending == VA_LIST_PENDING); + f->pending = NULL; + + assert(NOT(FRM_IS_VALIST(f))); // no longer a va_list fed frame +} + + +// (va_list by pointer: http://stackoverflow.com/a/3369762/211160) +// +// Central routine for doing an evaluation of an array of values by calling +// a C function with those parameters (e.g. supplied as arguments, separated +// by commas). Uses same method to do so as functions like printf() do. +// +// In R3-Alpha this style of invocation was specifically used to call single +// Rebol functions. It would use a list of REBVAL*s--each of which could +// come from disjoint memory locations and be passed directly with no +// evaluation. Ren-C replaced this entirely by adapting the evaluator to +// use va_arg() lists for the same behavior as a DO of an ARRAY. +// +// The previously accomplished style of execution with a function which may +// not be in the arglist can be accomplished using `opt_first` to put that +// function into the optional first position. To instruct the evaluator not +// to do any evaluation on the values supplied as arguments after that +// (corresponding to R3-Alpha's APPLY/ONLY) then DO_FLAG_NO_ARGS_EVALUATE +// should be used--otherwise they will be evaluated normally. +// +// NOTE: Ren-C no longer supports the built-in ability to supply refinements +// positionally, due to the brittleness of this approach (for both system +// and user code). The `opt_head` value should be made a path with the +// function at the head and the refinements specified there. Future +// additions could do this more efficiently by allowing the refinement words +// to be pushed directly to the data stack. +// +// !!! C's va_lists are very dangerous, there is no type checking! The +// C++ build should be able to check this for the callers of this function +// *and* check that you ended properly. It means this function will need +// two different signatures (and so will each caller of this routine). +// +// Returns THROWN_FLAG, END_FLAG, or VA_LIST_FLAG +// +inline static REBIXO Do_Va_Core( + REBVAL *out, + const REBVAL *opt_first, + va_list *vaptr, + REBFLGS flags +) { + DECLARE_FRAME (f); + + if (opt_first) + SET_FRAME_VALUE(f, opt_first); // no specifier, not relative + else { + SET_FRAME_VALUE(f, va_arg(*vaptr, const REBVAL*)); + assert(!IS_RELATIVE(f->value)); + } + + if (IS_END(f->value)) { + Init_Void(out); + return END_FLAG; + } + + SET_END(out); + f->out = out; + +#if !defined(NDEBUG) + f->index = TRASHED_INDEX; +#endif + f->source.vaptr = vaptr; + f->gotten = END; // so REB_WORD and REB_GET_WORD do their own Get_Var + f->specifier = SPECIFIED; // va_list values MUST be full REBVAL* already + f->pending = VA_LIST_PENDING; + + Init_Endlike_Header(&f->flags, flags | DO_FLAG_VA_LIST); // see notes + + Push_Frame_Core(f); + Do_Core(f); + Drop_Frame_Core(f); + + // Note: While on many platforms va_end() is a no-op, the C standard is + // clear that it must be called...it's undefined behavior if you skip it: + // + // http://stackoverflow.com/a/32259710/211160 + // + // Yet fail() will longjmp above this stack level, never getting here. So + // it is necessary that the call to va_end be done by Fail_Core() *before* + // that longjmp, by walking the stack list and looking for any va_list + // frames between the failure point and the trapper. + // + // But additionally, a frame may have to be "reified" if a GC runs while + // this va_list is being processed. (The reason is that the va_list has + // to have its values examined to be GC protected, but there's no API to + // allow the values to be examined for GC and then "rewound" to be fed + // into evaluation, so they must be stored in an intermediate array.) + // Reification also does a va_end(), so we don't want to do it again. + // + if (FRM_IS_VALIST(f)) // didn't get reified, so va_end() not called... + va_end(*vaptr); + + if (THROWN(f->out)) + return THROWN_FLAG; // !!! prohibits recovery from exits + + return IS_END(f->value) ? END_FLAG : VA_LIST_FLAG; +} + + +// Wrapper around Do_Va_Core which has the actual variadic interface (as +// opposed to taking the `va_list` which has been captured out of the +// variadic interface). +// +inline static REBOOL Do_Va_Throws(REBVAL *out, ...) +{ + va_list va; + + va_start(va, out); // must mention last param before the "..." + + REBIXO indexor = Do_Va_Core( + out, + NULL, // opt_first + &va, + DO_FLAG_TO_END + ); + + // Note: va_end() is handled by Do_Va_Core (one way or another) + + assert(indexor == THROWN_FLAG || indexor == END_FLAG); + return LOGICAL(indexor == THROWN_FLAG); +} + + +// Takes a list of arguments terminated by an end marker and will do something +// similar to R3-Alpha's "apply/only" with a value. If that value is a +// function, it will be called...if it's a SET-WORD! it will be assigned, etc. +// +// This is equivalent to putting the value at the head of the input and +// then calling EVAL/ONLY on it. If all the inputs are not consumed, an +// error will be thrown. +// +// The boolean result will be TRUE if an argument eval or the call created +// a THROWN() value, with the thrown value in `out`. +// +inline static REBOOL Apply_Only_Throws( + REBVAL *out, + REBOOL fully, + const REBVAL *applicand, + ... +) { + va_list va; + va_start(va, applicand); // must mention last param before the "..." + + REBIXO indexor = Do_Va_Core( + out, + applicand, // opt_first + &va, + DO_FLAG_NO_ARGS_EVALUATE | DO_FLAG_NO_LOOKAHEAD + ); + + if (fully && indexor == VA_LIST_FLAG) { + // + // Not consuming all the arguments given suggests a problem if `fully` + // is passed in as TRUE. + // + fail (Error_Apply_Too_Many_Raw()); + } + + // Note: va_end() is handled by Do_Va_Core (one way or another) + + assert( + indexor == THROWN_FLAG + || indexor == END_FLAG + || (NOT(fully) && indexor == VA_LIST_FLAG) + ); + return LOGICAL(indexor == THROWN_FLAG); +} + + +inline static REBOOL Do_At_Throws( + REBVAL *out, + REBARR *array, + REBCNT index, + REBSPC *specifier +){ + return LOGICAL( + THROWN_FLAG == Do_Array_At_Core( + out, + NULL, + array, + index, + specifier, + DO_FLAG_TO_END + ) + ); +} + +// Note: It is safe for `out` and `array` to be the same variable. The +// array and index are extracted, and will be protected from GC by the DO +// state...so it is legal to e.g Do_Any_Array_At_Throws(D_OUT, D_OUT). +// +inline static REBOOL Do_Any_Array_At_Throws( + REBVAL *out, + const REBVAL *any_array +){ + return Do_At_Throws( + out, + VAL_ARRAY(any_array), + VAL_INDEX(any_array), + VAL_SPECIFIER(any_array) + ); +} + +// Because Do_Core can seed with a single value, we seed with our value and +// an EMPTY_ARRAY. Revisit if there's a "best" dispatcher. Note this is +// an EVAL and not a DO...hence if you pass it a block, then the block will +// just evaluate to itself! +// +inline static REBOOL Eval_Value_Core_Throws( + REBVAL *out, + const RELVAL *value, + REBSPC *specifier +){ + return LOGICAL( + THROWN_FLAG == Do_Array_At_Core( + out, + value, + EMPTY_ARRAY, + 0, + specifier, + DO_FLAG_TO_END + ) + ); +} + +#define Eval_Value_Throws(out,value) \ + Eval_Value_Core_Throws((out), (value), SPECIFIED) + + +// When running a "branch" of code in conditional execution, Ren-C allows the +// use of single-arity functions. +// +// >> foo: does [print "Hello"] +// >> if true :foo +// Hello +// +// This was not allowed in R3-Alpha, and given the fact that you could write +// that as `if true [foo]` the added flexibility may not be necessary. But +// there is a feature in Ren-C which allows you to have a branch evaluate to +// itself literally, e.g. +// +// [print "doesn't print"] = case/only [true [print "doesn't print"]] +// +// In order to capture all the "branch-like" decisions into one place, the +// shared decision of what to allow or not allow is captured in this one +// inline function, used by conditional and loop constructs instead of a +// plain DO. +// +inline static REBOOL Run_Branch_Throws( + REBVAL *out, + const REBVAL *branch, + REBOOL only +) { + assert(branch != out); // !!! review, CASE can perhaps do better... + + if (only) { + Move_Value(out, branch); + } + else if (IS_BLOCK(branch)) { + if (Do_Any_Array_At_Throws(out, branch)) + return TRUE; + } + else if (IS_FUNCTION(branch)) { + // + // The function is allowed to be arity-0 only. + // + // !!! Might it be interesting if arity-1 functions were also allowed, + // by passing in the condition evaluation that caused the branch? + // + // >> if 1 + 2 func [x] [print x] + // 3 + // + // This could look even better with lambdas: + // + // >> if 1 + 2 (x -> print x) + // 3 + // + // To implement that feature, callers would have to pass in the + // condition, then the argument would be included in the apply, with + // `fully` not enforced...so the function could either consume the + // argument or not. Review. + // + const REBOOL fully = TRUE; + if (Apply_Only_Throws(out, fully, branch, END)) + return TRUE; + } + else + Move_Value(out, branch); // it's not code -- nothing to run + + return FALSE; +} + + +enum { + REDUCE_FLAG_INTO = 1 << 0, + REDUCE_FLAG_DROP_BARS = 1 << 1, + REDUCE_FLAG_KEEP_BARS = 1 << 2 +}; diff --git a/src/include/sys-ext.h b/src/include/sys-ext.h new file mode 100644 index 0000000000..f21c346826 --- /dev/null +++ b/src/include/sys-ext.h @@ -0,0 +1,57 @@ +// Extension entry point functions: + + +#if defined(EXT_DLL) // External extensions +#if defined(REB_EXE) +#define EXT_API EXTERN_C API_IMPORT +#else +#define EXT_API EXTERN_C API_EXPORT +#endif +#define EXT_INIT(e) RX_Init +#define EXT_QUIT(e) RX_Quit +#else // Builtin extensions +#define EXT_API EXTERN_C +#define EXT_INIT(e) RX_Init_ ## e +#define EXT_QUIT(e) RX_Quit_ ## e +#endif + +typedef int (*INIT_FUNC)(REBVAL *, REBVAL *); +typedef int (*QUIT_FUNC)(); + +// Extension macros +#define DECLARE_EXT_INIT(e) \ +EXT_API int EXT_INIT(e) (REBVAL *header, REBVAL *out) + +#define DEFINE_EXT_INIT(e, script_bytes, code) \ +EXT_API int EXT_INIT(e) (REBVAL *script, REBVAL *out) \ +{\ + code \ + Init_String(script, Copy_Bytes(script_bytes, sizeof(script_bytes) - 1)); \ + return 0;\ +} + +#define DEFINE_EXT_INIT_COMPRESSED(e, script_bytes, code) \ +EXT_API int EXT_INIT(e) (REBVAL *script, REBVAL *out) \ +{\ + code \ + /* binary does not have a \0 terminator */ \ + Init_Binary(script, Copy_Bytes(script_bytes, sizeof(script_bytes))); \ + return 0;\ +} + +#define DECLARE_EXT_QUIT(e) \ +EXT_API int EXT_QUIT(e) () + +#define DEFINE_EXT_QUIT(e, code) \ +EXT_API int EXT_QUIT(e) () code + +#define LOAD_EXTENSION(exts, e) do { \ + Add_Boot_Extension(exts, EXT_INIT(e), EXT_QUIT(e)); \ +} while(0) + +// Module macros +#define DECLARE_MODULE_INIT(m) int Module_Init_ ## m (REBVAL* out) +#define CALL_MODULE_INIT(m) Module_Init_ ## m (out) + +#define DECLARE_MODULE_QUIT(m) int Module_Quit_ ## m () +#define CALL_MODULE_QUIT(m) Module_Quit_ ## m () diff --git a/src/include/sys-frame.h b/src/include/sys-frame.h new file mode 100644 index 0000000000..f05f8b8eec --- /dev/null +++ b/src/include/sys-frame.h @@ -0,0 +1,616 @@ +// +// File: %sys-frame.h +// Summary: {Accessors and Argument Pushers/Poppers for Function Call Frames} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + + +// +// Relative and specific values +// + +inline static REBARR *VAL_BINDING(const RELVAL *v) { + assert( + ANY_ARRAY(v) + || IS_FUNCTION(v) + || ANY_CONTEXT(v) + || IS_VARARGS(v) + || ANY_WORD(v) + ); + return v->extra.binding; +} + +inline static void INIT_RELATIVE(RELVAL *v, REBFUN *func) { + assert(GET_VAL_FLAG(v, VALUE_FLAG_RELATIVE)); + v->extra.binding = FUNC_PARAMLIST(func); +} + +inline static void INIT_SPECIFIC(RELVAL *v, REBCTX *context) { + assert(NOT_VAL_FLAG(v, VALUE_FLAG_RELATIVE)); + v->extra.binding = CTX_VARLIST(context); +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// THROWN status +// +//=////////////////////////////////////////////////////////////////////////=// +// +// All THROWN values have two parts: the REBVAL arg being thrown and +// a REBVAL indicating the /NAME of a labeled throw. (If the throw was +// created with plain THROW instead of THROW/NAME then its name is NONE!). +// You cannot fit both values into a single value's bits of course, but +// since only one THROWN() value is supposed to exist on the stack at a +// time the arg part is stored off to the side when one is produced +// during an evaluation. It must be processed before another evaluation +// is performed, and if the GC or DO are ever given a value with a +// THROWN() bit they will assert! +// +// A reason to favor the name as "the main part" is that having the name +// value ready-at-hand allows easy testing of it to see if it needs +// to be passed on. That happens more often than using the arg, which +// will occur exactly once (when it is caught). +// + +#define THROWN(v) \ + GET_VAL_FLAG((v), VALUE_FLAG_THROWN) + +static inline void CONVERT_NAME_TO_THROWN( + REBVAL *name, const REBVAL *arg +){ + assert(!THROWN(name)); + SET_VAL_FLAG(name, VALUE_FLAG_THROWN); + + assert(IS_UNREADABLE_IF_DEBUG(&TG_Thrown_Arg)); + Move_Value(&TG_Thrown_Arg, arg); +} + +static inline void CATCH_THROWN(REBVAL *arg_out, REBVAL *thrown) { + // + // Note: arg_out and thrown may be the same pointer + // + assert(NOT_END(thrown)); + assert(THROWN(thrown)); + CLEAR_VAL_FLAG(thrown, VALUE_FLAG_THROWN); + + assert(!IS_UNREADABLE_IF_DEBUG(&TG_Thrown_Arg)); + Move_Value(arg_out, &TG_Thrown_Arg); + SET_UNREADABLE_BLANK(&TG_Thrown_Arg); +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// LOW-LEVEL FRAME ACCESSORS +// +//=////////////////////////////////////////////////////////////////////////=// + +#define FS_TOP (TG_Frame_Stack + 0) // avoid assignment to FS_TOP via + 0 + +#define FRM_IS_VALIST(f) \ + LOGICAL((f)->flags.bits & DO_FLAG_VA_LIST) + +inline static REBARR *FRM_ARRAY(REBFRM *f) { + assert(!FRM_IS_VALIST(f)); + return f->source.array; +} + +// !!! Though the evaluator saves its `index`, the index is not meaningful +// in a valist. Also, if `opt_head` values are used to prefetch before an +// array, those will be lost too. A true debugging mode would need to +// convert these cases to ordinary arrays before running them, in order +// to accurately present any errors. +// +inline static REBCNT FRM_INDEX(REBFRM *f) { + assert(!FRM_IS_VALIST(f)); + return IS_END(f->value) + ? ARR_LEN(f->source.array) + : f->index - 1; +} + +inline static REBCNT FRM_EXPR_INDEX(REBFRM *f) { + assert(!FRM_IS_VALIST(f)); + return f->expr_index == END_FLAG + ? ARR_LEN((f)->source.array) + : f->expr_index - 1; +} + +#define FRM_OUT(f) \ + cast(REBVAL * const, (f)->out) // writable Lvalue + +// Note about FRM_NUM_ARGS: A native should generally not detect the arity it +// was invoked with, (and it doesn't make sense as most implementations get +// the full list of arguments and refinements). However, ACTION! dispatch +// has several different argument counts piping through a switch, and often +// "cheats" by using the arity instead of being conditional on which action +// ID ran. Consider when reviewing the future of ACTION!. +// +#define FRM_NUM_ARGS(f) \ + FUNC_FACADE_NUM_PARAMS((f)->phase) + +inline static REBVAL *FRM_CELL(REBFRM *f) { + // + // An earlier optimization would use the frame's cell if a function + // took exactly one argument for that argument. This meant it was not + // available to those functions to use as a GC-protected temporary. The + // optimization made it complex for the generalized code that does + // stack level discovery from a value pointer, and was removed. + // + return &f->cell; // otherwise, it's available... +} + +#define FRM_PRIOR(f) \ + ((f)->prior) + +#define FRM_LABEL(f) \ + ((f)->label) + +inline static REBFUN *FRM_UNDERLYING(REBFRM *f) { + assert(FUNC_UNDERLYING(f->phase) == FUNC_UNDERLYING(f->original)); + return FUNC_UNDERLYING(f->phase); +} + +#define FRM_DSP_ORIG(f) \ + ((f)->dsp_orig + 0) // Lvalue + +// `arg` is in use to point at the arguments during evaluation, and `param` +// may hold a SET-WORD! or SET-PATH! available for a lookback to quote. +// But during evaluations, `refine` is free. +// +// Since the GC is aware of the pointers, it can protect whatever refine is +// pointing at. This can be useful for routines that have a local +// memory cell. This does not require a push or a pop of anything--it only +// protects as long as the native is running. (This trick is available to +// the dispatchers as well.) +// +#define PROTECT_FRM_X(f,v) \ + ((f)->refine = (v)) + + +// ARGS is the parameters and refinements +// 1-based indexing into the arglist (0 slot is for object/function value) +#ifdef NDEBUG + #define FRM_ARG(f,n) \ + ((f)->args_head + (n) - 1) +#else + inline static REBVAL *FRM_ARG(REBFRM *f, REBCNT n) { + assert(n != 0 && n <= FRM_NUM_ARGS(f)); + + REBVAL *var = &f->args_head[n - 1]; + + assert(!THROWN(var)); + assert(NOT_VAL_FLAG(var, VALUE_FLAG_RELATIVE)); + return var; + } +#endif + + +// Quick access functions from natives (or compatible functions that name a +// Reb_Frame pointer `frame_`) to get some of the common public fields. +// +#define D_OUT FRM_OUT(frame_) // GC-safe slot for output value +#define D_CELL FRM_CELL(frame_) // GC-safe cell if > 1 argument +#define D_ARGC FRM_NUM_ARGS(frame_) // count of args+refinements/args +#define D_ARG(n) FRM_ARG(frame_, (n)) // pass 1 for first arg +#define D_FUNC FRM_FUNC(frame_) // REBVAL* of running function +#define D_LABEL_SYM FRM_LABEL(frame_) // symbol or placeholder for call +#define D_DSP_ORIG FRM_DSP_ORIG(frame_) // Original data stack pointer + +#define D_PROTECT_X(v) PROTECT_FRM_X(frame_, (v)) + +#define REB_0_PICKUP REB_0 + +inline static REBOOL Is_Any_Function_Frame(REBFRM *f) { + if (f->eval_type == REB_FUNCTION) { + // + // Do not count as a function frame unless it's gotten to the point + // of setting the label. + // + return LOGICAL(f->label != NULL); + } + return FALSE; +} + +// While a function frame is fulfilling its arguments, the `f->param` will +// be pointing to a typeset. The invariant that is maintained is that +// `f->param` will *not* be a typeset when the function is actually in the +// process of running. (So no need to set/clear/test another "mode".) +// +inline static REBOOL Is_Function_Frame_Fulfilling(REBFRM *f) +{ + assert(Is_Any_Function_Frame(f)); + return NOT_END(f->param); +} + + +// It's helpful when looking in the debugger to be able to look at a frame +// and see a cached string for the function it's running (if there is one). +// The release build only considers the frame symbol valid if ET_FUNCTION +// +inline static void SET_FRAME_LABEL(REBFRM *f, REBSTR *label) { + assert(f->eval_type == REB_FUNCTION); + f->label = label; +#if !defined(NDEBUG) + f->label_debug = cast(const char*, STR_HEAD(label)); +#endif +} + +inline static void CLEAR_FRAME_LABEL(REBFRM *f) { + f->label = NULL; +#if !defined(NDEBUG) + f->label_debug = NULL; +#endif +} + +inline static void SET_FRAME_VALUE(REBFRM *f, const RELVAL *value) { + f->value = value; + +#if !defined(NDEBUG) + if (NOT_END(f->value)) + f->kind_debug = VAL_TYPE(f->value); + else + f->kind_debug = REB_0; +#endif +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// ARGUMENT AND PARAMETER ACCESS HELPERS +// +//=////////////////////////////////////////////////////////////////////////=// +// +// These accessors are designed to make it convenient for natives written in +// C to access their arguments and refinements. (They are what is behind the +// implementation of the INCLUDE_PARAMS_OF_XXX macros that are used in +// natives.) +// +// They are able to bind to the implicit Reb_Frame* passed to every +// REBNATIVE() and read the information out cleanly, like this: +// +// PARAM(1, foo); +// REFINE(2, bar); +// +// if (IS_INTEGER(ARG(foo)) && REF(bar)) { ... } +// +// Though REF can only be used with a REFINE() declaration, ARG can be used +// with either. +// +// Under the hood `PARAM(1, foo)` and `REFINE(2, bar)` make const structs. +// In an optimized build, these structures disappear completely, with all +// addressing done directly into the call frame's cached `arg` pointer. +// It is also possible to get the typeset-with-symbol for a particular +// parameter or refinement, e.g. with `PAR(foo)` or `PAR(bar)`. +// +// The PARAM and REFINE macros use token pasting to name the variables they +// are declaring `p_name` instead of just `name`. This prevents collisions +// with C++ identifiers, so PARAM(case) and REFINE(new) would make `p_case` +// and `p_new` instead of just `case` and `new` as the variable names. (This +// is only visible in the debugger.) +// +// As a further aid, the debug build version of the structures contain the +// actual pointers to the arguments. It also keeps a copy of a cache of the +// type for the arguments, because the numeric type encoding in the bits of +// the header requires a debug call (or by-hand-binary decoding) to interpret +// Whether a refinement was used or not at time of call is also cached. +// + +#ifdef NDEBUG + #define PARAM(n,name) \ + static const int p_##name = n + + #define REFINE(n,name) \ + static const int p_##name = n + + #define ARG(name) \ + FRM_ARG(frame_, (p_##name)) + + #define PAR(name) \ + FUNC_PARAM(frame_->phase, (p_##name)) /* a TYPESET! */ + + #define REF(name) \ + IS_CONDITIONAL_TRUE(ARG(name)) +#else + struct Native_Param { + enum Reb_Kind kind_cache; + REBVAL *arg; + const int num; + }; + + struct Native_Refine { + REBOOL used_cache; + REBVAL *arg; + const int num; + }; + + #define PARAM(n,name) \ + const struct Native_Param p_##name = { \ + VAL_TYPE(FRM_ARG(frame_, (n))), /* watchlist cache */ \ + FRM_ARG(frame_, (n)), /* watchlist cache */ \ + (n) \ + } + + #define REFINE(n,name) \ + const struct Native_Refine p_##name = { \ + IS_CONDITIONAL_TRUE(FRM_ARG(frame_, (n))), /* watchlist cache */ \ + FRM_ARG(frame_, (n)), /* watchlist cache */ \ + (n) \ + } + + #define ARG(name) \ + FRM_ARG(frame_, (p_##name).num) + + #define PAR(name) \ + FUNC_PARAM(frame_->phase, (p_##name).num) /* a TYPESET! */ + + #define REF(name) \ + ((p_##name).used_cache /* used_cache use stops REF() on PARAM()s */ \ + ? IS_CONDITIONAL_TRUE(ARG(name)) \ + : IS_CONDITIONAL_TRUE(ARG(name))) +#endif + + +// The native entry prelude makes sure that once native code starts running, +// then a reified frame will be locked or a non-reified frame will be flagged +// in such a way as to indicate that it should be locked when reified. This +// prevents a FRAME! generated for a native from being able to get write +// access to the variables, which could cause crashes, as raw C code is not +// insulated against having bit patterns for types in cells that aren't +// expected. +// +// !!! Debug injection of bad types into usermode code may cause havoc as +// well, and should be considered a security/permissions issue. It just won't +// (or shouldn't) crash the evaluator itself. +// +// This is automatically injected by the INCLUDE_PARAMS_OF_XXX macros. The +// reason this is done with code inlined into the native itself instead of +// based on an IS_NATIVE() test is to avoid the cost of the testing--which +// is itself a bit dodgy to tell a priori if a dispatcher is native or not. +// This way there is no test and only natives pay the cost of flag setting. +// +inline static void Enter_Native(REBFRM *f) { + f->flags.bits |= DO_FLAG_NATIVE_HOLD; + if (f->varlist != NULL) + SET_SER_INFO(f->varlist, SERIES_INFO_RUNNING); +} + + +// Allocate the series of REBVALs inspected by a function when executed (the +// values behind ARG(name), REF(name), D_ARG(3), etc.) +// +// This only allocates space for the arguments, it does not initialize. +// Do_Core initializes as it goes, and updates f->param so the GC knows how +// far it has gotten so as not to see garbage. APPLY has different handling +// +// If the function is a specialization, then the parameter list of that +// specialization will have *fewer* parameters than the full function would. +// For this reason we push the arguments for the "underlying" function. +// Yet if there are specialized values, they must be filled in from the +// exemplar frame. +// +// Rather than "dig" through layers of functions to find the underlying +// function or the specialization's exemplar frame, those properties are +// cached during the creation process. +// +inline static void Push_Or_Alloc_Args_For_Underlying_Func( + REBFRM *f, + const REBVAL *gotten +){ + assert(IS_FUNCTION(gotten)); + + // We need the actual REBVAL of the function here, and not just the REBFUN. + // This is true even though you can get an archetype REBVAL from a function + // pointer with FUNC_VALUE(). That archetype--as with RETURN and LEAVE-- + // will not carry the specific `binding` information of a value. + // + f->original = f->phase = VAL_FUNC(gotten); + f->binding = VAL_BINDING(gotten); + + // The underlying function is whose parameter list must be enumerated. + // Even though this underlying function can have more arguments than the + // "interface" function being called from gotten, any parameters more + // than in that interface won't be gathered at the callsite because they + // will not contain END markers. + // + // The "facade" is the interface this function uses, which must have the + // same number of arguments and be compatible with the underlying + // function. At this point in time a facade might be a paramlist, but + // it could also just be an array with an unreadable blank in slot 0. + // + REBCNT num_args = FUNC_FACADE_NUM_PARAMS(f->phase); + + // Note: A previous optimization would use the frame's evaluation cell + // for the argument in the case of an arity-1 function. While this + // avoided a chunk stack allocation, it complicates the nature of + // looking backwards for a VALUE_FLAG_STACK's frame by introducing a + // new parameter layout. It also caused the code to branch more on both + // the push and drop side, and made that cell unavailable for 1-argument + // functions to use as a temporary. So the optimization was removed. + + if (IS_FUNC_DURABLE(VAL_FUNC(gotten))) { // !!! Who decides durability? + // + // !!! It's hoped that stack frames can be "hybrids" with some pooled + // allocated vars that survive a call, and some that go away when the + // stack frame is finished. The groundwork for this is laid but it's + // not quite ready--so the classic interpretation is that it's all or + // nothing (similar to FUNCTION! vs. CLOSURE! in this respect) + // + // Note we *don't* set ARRAY_FLAG_VARLIST here, because it is being + // used as a signal as to whether the varlist is "valid" and fully + // reified for use. + // + f->varlist = Make_Array_Core(num_args + 1, SERIES_FLAG_FIXED_SIZE); + TERM_ARRAY_LEN(f->varlist, num_args + 1); + + // Skip the [0] slot which will be filled with the CTX_VALUE + // !!! Note: Make_Array made the 0 slot an end marker + // + TRASH_CELL_IF_DEBUG(ARR_AT(f->varlist, 0)); + f->args_head = SINK(ARR_AT(f->varlist, 1)); + + // Similarly, it should not be possible to use CTX_FRAME_IF_ON_STACK + // if the varlist does not become reified. + // + TRASH_POINTER_IF_DEBUG(SER(f->varlist)->misc.f); + } + else { + // We start by allocating the data for the args and locals on the chunk + // stack. However, this can be "promoted" into being the data for a + // frame context if it becomes necessary to refer to the variables + // via words or an object value. That object's data will still be this + // chunk, but the chunk can be freed...so the words can't be looked up. + // + // Note that chunks implicitly have an END at the end; no need to + // put one there. + // + f->varlist = NULL; + f->args_head = Push_Value_Chunk_Of_Length(num_args); + assert(CHUNK_LEN_FROM_VALUES(f->args_head) == num_args); + } + + REBCTX *exemplar = FUNC_EXEMPLAR(f->phase); + if (exemplar) + f->special = CTX_VARS_HEAD(exemplar); + else + f->special = m_cast(REBVAL*, END); // literal pointer used as test + + // We want the cell to be GC safe; whether it's used by an argument or + // not. If it's being used as an argument then this just gets overwritten + // but the 0 case would not initialize it...so cheaper to just set than + // to check. Note that this can only be done after extracting the function + // properties, as f->gotten may be f->cell. + // + SET_END(&f->cell); +} + + +// This routine needs to be shared with the error handling code. It would be +// nice if it were inlined into Do_Core...but repeating the code just to save +// the function call overhead is second-guessing the optimizer and would be +// a cause of bugs. +// +// Note that in response to an error, we do not want to drop the chunks, +// because there are other clients of the chunk stack that may be running. +// Hence the chunks will be freed by the error trap helper. +// +inline static void Drop_Function_Args_For_Frame_Core( + REBFRM *f, + REBOOL drop_chunks +) { + // The frame may be reused for another function call, and that function + // may not start with native code (or use native code at all). + // + // !!! Should the code be willing to drop the running flag off the varlist + // as well if it is persistent, so that the values can be modified once + // the native code is no longer running? + // + f->flags.bits &= ~DO_FLAG_NATIVE_HOLD; + + if (drop_chunks) { + if (f->varlist == NULL) { + Drop_Chunk_Of_Values(f->args_head); + + goto finished; // nothing else to do... + } + + // A varlist may happen even with stackvars...if "singular" (e.g. + // it's just a REBSER node for purposes of GC-referencing, but gets + // its actual content from the stackvars. + // + if (ARR_LEN(f->varlist) == 1) + Drop_Chunk_Of_Values(f->args_head); + } + else { + if (f->varlist == NULL) + goto finished; + } + + assert(GET_SER_FLAG(f->varlist, SERIES_FLAG_ARRAY)); + + if (NOT(IS_ARRAY_MANAGED(f->varlist))) { + // + // It's an array, but hasn't become managed yet...either because + // it couldn't be (args still being fulfilled, may have bad cells) or + // didn't need to be (no Context_For_Frame_May_Reify_Managed). We + // can just free it. + // + assert(IS_POINTER_TRASH_DEBUG(SER(f->varlist)->misc.f)); + Free_Array(f->varlist); + goto finished; + } + + // The varlist is going to outlive this call, so the frame correspondence + // in it needs to be cleared out, so callers will know the frame is dead. + // + assert(SER(f->varlist)->misc.f == f); + SER(f->varlist)->misc.f = NULL; + + // The varlist might have been for indefinite extent variables, or it + // might be a stub holder for a stack context. + + ASSERT_ARRAY_MANAGED(f->varlist); + + if (NOT(GET_SER_INFO(f->varlist, CONTEXT_INFO_STACK))) { + // + // If there's no stack memory being tracked by this context, it + // has dynamic memory and is being managed by the garbage collector + // so there's nothing to do. + // + assert(GET_SER_INFO(f->varlist, SERIES_INFO_HAS_DYNAMIC)); + goto finished; + } + + // It's reified but has its data pointer into the chunk stack, which + // means we have to free it and mark the array inaccessible. + + assert(GET_SER_FLAG(f->varlist, ARRAY_FLAG_VARLIST)); + assert(NOT_SER_INFO(f->varlist, SERIES_INFO_HAS_DYNAMIC)); + + assert(NOT_SER_INFO(f->varlist, SERIES_INFO_INACCESSIBLE)); + SET_SER_INFO(f->varlist, SERIES_INFO_INACCESSIBLE); + +finished: + + TRASH_POINTER_IF_DEBUG(f->args_head); + TRASH_POINTER_IF_DEBUG(f->varlist); + + return; // needed for release build so `finished:` labels a statement +} + + +// This routine ensures that a valid REBCTX* (suitable for putting into a +// FRAME! REBVAL) exists for a Reb_Frame stack structure. +// +inline static REBCTX *Context_For_Frame_May_Reify_Managed(REBFRM *f) +{ + assert(NOT(Is_Function_Frame_Fulfilling(f))); + + if (f->varlist == NULL || NOT_SER_FLAG(f->varlist, ARRAY_FLAG_VARLIST)) + Reify_Frame_Context_Maybe_Fulfilling(f); // it's not fulfilling, here + + return CTX(f->varlist); +} diff --git a/src/include/sys-function.h b/src/include/sys-function.h new file mode 100644 index 0000000000..d8303ab2c7 --- /dev/null +++ b/src/include/sys-function.h @@ -0,0 +1,332 @@ +// +// File: %sys-function.h +// Summary: {Definitions for REBFUN} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Using a technique strongly parallel to CONTEXT, a function is identified +// by a series which acts as its paramlist, in which the 0th element is an +// ANY-FUNCTION! value. Unlike a CONTEXT, a FUNC does not have values of its +// own... only parameter definitions (or "params"). The arguments ("args") +// come from finding a function instantiation on the stack. +// + +struct Reb_Func { + struct Reb_Array paramlist; +}; + +#if !defined(NDEBUG) && defined(__cplusplus) && __cplusplus >= 201103L + template + inline REBFUN *AS_FUNC(T *p) { + static_assert( + std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value, + "AS_FUNC works on: void*, REBNOD*, REBSER*, REBARR*" + ); + REBARR *paramlist = cast(REBARR*, p); + assert(GET_SER_FLAG(paramlist, ARRAY_FLAG_PARAMLIST)); + return cast(REBFUN*, paramlist); + } +#else + #define AS_FUNC(p) \ + cast(REBFUN*, (p)) +#endif + + +inline static REBARR *FUNC_PARAMLIST(REBFUN *f) { + assert(GET_SER_FLAG(&f->paramlist, ARRAY_FLAG_PARAMLIST)); + return &f->paramlist; +} + +inline static REBVAL *FUNC_VALUE(REBFUN *f) { + return SER_AT(REBVAL, SER(FUNC_PARAMLIST(f)), 0); +} + +inline static REBNAT FUNC_DISPATCHER(REBFUN *f) { + return SER( + FUNC_VALUE(f)->payload.function.body_holder + )->misc.dispatcher; +} + +inline static RELVAL *FUNC_BODY(REBFUN *f) { + assert(ARR_LEN(FUNC_VALUE(f)->payload.function.body_holder) == 1); + return ARR_HEAD(FUNC_VALUE(f)->payload.function.body_holder); +} + +inline static REBVAL *FUNC_PARAM(REBFUN *f, REBCNT n) { + assert(n != 0 && n < ARR_LEN(FUNC_PARAMLIST(f))); + return SER_AT(REBVAL, SER(FUNC_PARAMLIST(f)), n); +} + +inline static REBCNT FUNC_NUM_PARAMS(REBFUN *f) { + return ARR_LEN(FUNC_PARAMLIST(f)) - 1; +} + +inline static REBCTX *FUNC_META(REBFUN *f) { + return SER(FUNC_PARAMLIST(f))->link.meta; +} + +// *** These FUNC_FACADE fetchers are called VERY frequently, so it is best +// to keep them light (as the debug build does not inline). Integrity checks +// of the function facades are deferred to the GC, see the REB_FUNCTION case +// in the switch(), and don't turn these into inline functions without a +// really good reason...and seeing the impact on the debug build!!! *** + +#define FUNC_FACADE(f) \ + SER(FUNC_PARAMLIST(f))->misc.facade + +#define FUNC_FACADE_NUM_PARAMS(f) \ + (ARR_LEN(FUNC_FACADE(f)) - 1) + +#define FUNC_FACADE_HEAD(f) \ + KNOWN(ARR_AT(FUNC_FACADE(f), 1)) + + +// The concept of the "underlying" function is that which has the right +// number of arguments for the frame to be built--and which has the actual +// correct paramlist identity to use for binding in adaptations. +// +// So if you specialize a plain function with 2 arguments so it has just 1, +// and then specialize the specialization so that it has 0, your call still +// needs to be building a frame with 2 arguments. Because that's what the +// code that ultimately executes--after the specializations are peeled away-- +// will expect. +// +// And if you adapt an adaptation of a function, the keylist referred to in +// the frame has to be the one for the inner function. Using the adaptation's +// parameter list would write variables the adapted code wouldn't read. +// +// For efficiency, the underlying pointer can be derived from the "facade". +// Though the facade may not be the underlying paramlist (it could have its +// parameter types tweaked for the purposes of that composition), it will +// always have a FUNCTION! value in its 0 slot as the underlying function. +// +inline static REBFUN *FUNC_UNDERLYING(REBFUN *f) { + return AS_FUNC(ARR_HEAD(FUNC_FACADE(f))->payload.function.paramlist); +} + +inline static REBCTX *FUNC_EXEMPLAR(REBFUN *f) { + REBCTX *exemplar = + SER(FUNC_VALUE(f)->payload.function.body_holder)->link.exemplar; + +#if !defined(NDEBUG) + if (exemplar != NULL) { + assert(FUNC_FACADE_NUM_PARAMS(f) == CTX_LEN(exemplar)); + }; +#endif + return exemplar; +} + + +// Note: On Windows, FUNC_DISPATCH is already defined in the header files +// +#define FUNC_DISPATCHER(f) \ + (SER(FUNC_VALUE(f)->payload.function.body_holder)->misc.dispatcher) + +// There is no binding information in a function parameter (typeset) so a +// REBVAL should be okay. +// +inline static REBVAL *FUNC_PARAMS_HEAD(REBFUN *f) { + return SER_AT(REBVAL, SER(FUNC_PARAMLIST(f)), 1); +} + +inline static REBRIN *FUNC_ROUTINE(REBFUN *f) { + return VAL_ARRAY(FUNC_BODY(f)); +} + + + +//=////////////////////////////////////////////////////////////////////////=// +// +// FUNCTION! (`struct Reb_Function`) +// +//=////////////////////////////////////////////////////////////////////////=// + +#ifdef NDEBUG + #define FUNC_FLAG(n) \ + FLAGIT_LEFT(TYPE_SPECIFIC_BIT + (n)) +#else + #define FUNC_FLAG(n) \ + (FLAGIT_LEFT(TYPE_SPECIFIC_BIT + (n)) | HEADERIZE_KIND(REB_FUNCTION)) +#endif + +// RETURN will always be in the last paramlist slot (if present) +// +#define FUNC_FLAG_RETURN FUNC_FLAG(0) + +// LEAVE will always be in the last paramlist slot (if present) +// +#define FUNC_FLAG_LEAVE FUNC_FLAG(1) + +// DEFERS_LOOKBACK_ARG flag is a cached property, which tells you whether a +// function defers its first real argument when used as a lookback. Because +// lookback dispatches cannot use refinements at this time, the answer is +// static for invocation via a plain word. This property is calculated at +// the time of Make_Function(). +// +#define FUNC_FLAG_DEFERS_LOOKBACK FUNC_FLAG(2) + +// This is another cached property, needed because lookahead/lookback is done +// so frequently, and it's quicker to check a bit on the function than to +// walk the parameter list every time that function is called. +// +#define FUNC_FLAG_QUOTES_FIRST_ARG FUNC_FLAG(3) + +// The COMPILE-NATIVES command wants to operate on user natives, and be able +// to recompile unchanged natives as part of a unit even after they were +// initially compiled. But since that replaces their dispatcher with an +// arbitrary function, they can't be recognized to know they have the specific +// body structure of a user native. So this flag is used. +// +#define FUNC_FLAG_USER_NATIVE FUNC_FLAG(4) + +// This flag is set when the native (e.g. extensions) can be unloaded +// +#define FUNC_FLAG_UNLOADABLE_NATIVE FUNC_FLAG(5) + +#if !defined(NDEBUG) + // + // BLANK! ("none!") for unused refinements instead of FALSE + // Also, BLANK! for args of unused refinements instead of not set + // + #define FUNC_FLAG_LEGACY_DEBUG FUNC_FLAG(6) + + // If a function is a native then it may provide return information as + // documentation, but not want to pay for the run-time check of whether + // the type is correct or not. In the debug build though, it's good + // to double-check. So when MKF_FAKE_RETURN is used in a debug build, + // it leaves this flag on the function. + // + #define FUNC_FLAG_RETURN_DEBUG FUNC_FLAG(7) +#endif + +// These are the flags which are scanned for and set during Make_Function +// +#define FUNC_FLAG_CACHED_MASK \ + (FUNC_FLAG_DEFERS_LOOKBACK | FUNC_FLAG_QUOTES_FIRST_ARG) + + +inline static REBFUN *VAL_FUNC(const RELVAL *v) { + assert(IS_FUNCTION(v)); + return AS_FUNC(v->payload.function.paramlist); +} + +inline static REBARR *VAL_FUNC_PARAMLIST(const RELVAL *v) + { return FUNC_PARAMLIST(VAL_FUNC(v)); } + +inline static REBCNT VAL_FUNC_NUM_PARAMS(const RELVAL *v) + { return FUNC_NUM_PARAMS(VAL_FUNC(v)); } + +inline static REBVAL *VAL_FUNC_PARAMS_HEAD(const RELVAL *v) + { return FUNC_PARAMS_HEAD(VAL_FUNC(v)); } + +inline static REBVAL *VAL_FUNC_PARAM(const RELVAL *v, REBCNT n) + { return FUNC_PARAM(VAL_FUNC(v), n); } + +inline static RELVAL *VAL_FUNC_BODY(const RELVAL *v) + { return ARR_HEAD(v->payload.function.body_holder); } + +inline static REBNAT VAL_FUNC_DISPATCHER(const RELVAL *v) + { return SER(v->payload.function.body_holder)->misc.dispatcher; } + +inline static REBCTX *VAL_FUNC_META(const RELVAL *v) + { return SER(v->payload.function.paramlist)->link.meta; } + +inline static REBOOL IS_FUNCTION_INTERPRETED(const RELVAL *v) { + // + // !!! Review cases where this is supposed to matter, because they are + // probably all bad. With the death of function categories, code should + // be able to treat functions as "black boxes" and not know which of + // the dispatchers they run on...with only the dispatch itself caring. + // + return LOGICAL( + VAL_FUNC_DISPATCHER(v) == &Noop_Dispatcher + || VAL_FUNC_DISPATCHER(v) == &Unchecked_Dispatcher + || VAL_FUNC_DISPATCHER(v) == &Voider_Dispatcher + || VAL_FUNC_DISPATCHER(v) == &Returner_Dispatcher + ); +} + +inline static REBOOL IS_FUNCTION_ACTION(const RELVAL *v) + { return LOGICAL(VAL_FUNC_DISPATCHER(v) == &Action_Dispatcher); } + +inline static REBOOL IS_FUNCTION_SPECIALIZER(const RELVAL *v) + { return LOGICAL(VAL_FUNC_DISPATCHER(v) == &Specializer_Dispatcher); } + +inline static REBOOL IS_FUNCTION_CHAINER(const RELVAL *v) + { return LOGICAL(VAL_FUNC_DISPATCHER(v) == &Chainer_Dispatcher); } + +inline static REBOOL IS_FUNCTION_ADAPTER(const RELVAL *v) + { return LOGICAL(VAL_FUNC_DISPATCHER(v) == &Adapter_Dispatcher); } + +inline static REBOOL IS_FUNCTION_RIN(const RELVAL *v) + { return LOGICAL(VAL_FUNC_DISPATCHER(v) == &Routine_Dispatcher); } + +inline static REBOOL IS_FUNCTION_HIJACKER(const RELVAL *v) + { return LOGICAL(VAL_FUNC_DISPATCHER(v) == &Hijacker_Dispatcher); } + +inline static REBRIN *VAL_FUNC_ROUTINE(const RELVAL *v) { + return VAL_ARRAY(VAL_FUNC_BODY(v)); +} + + +// !!! At the moment functions are "all durable" or "none durable" w.r.t. the +// survival of their arguments and locals after the call. +// +inline static REBOOL IS_FUNC_DURABLE(REBFUN *f) { + return LOGICAL( + FUNC_NUM_PARAMS(f) != 0 + && GET_VAL_FLAG(FUNC_PARAM(f, 1), TYPESET_FLAG_DURABLE) + ); +} + +// Native values are stored in an array at boot time. This is a convenience +// accessor for getting the "FUNC" portion of the native--e.g. the paramlist. +// It should compile to be as efficient as fetching any global pointer. + +#define NAT_VALUE(name) \ + (&Natives[N_##name##_ID]) + +#define NAT_FUNC(name) \ + VAL_FUNC(NAT_VALUE(name)) + + + +// Gets a system function with tolerance of it not being a function. +// +// (Extraction of a feature that formerly was part of a dedicated dual +// function to Apply_Func_Throws (Do_Sys_Func_Throws()) +// +inline static REBVAL *Sys_Func(REBCNT inum) +{ + REBVAL *value = CTX_VAR(Sys_Context, inum); + + if (!IS_FUNCTION(value)) + fail (Error_Bad_Sys_Func_Raw(value)); + + return value; +} diff --git a/src/include/sys-globals.h b/src/include/sys-globals.h index 613a7ee8d0..bdd422f0b8 100644 --- a/src/include/sys-globals.h +++ b/src/include/sys-globals.h @@ -1,49 +1,74 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Program and Thread Globals -** Module: sys-globals.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %sys-globals.h +// Summary: "Program and Thread Globals" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// //-- Bootstrap variables: -PVAR REBINT PG_Boot_Phase; // To know how far in the boot we are. -PVAR REBINT PG_Boot_Level; // User specified startup level -PVAR REBYTE **PG_Boot_Strs; // Special strings in boot.r (RS_ constants) - -//-- Various statistics about memory, etc. -PVAR REB_STATS *PG_Reb_Stats; -PVAR REBU64 PG_Mem_Usage; // Overall memory used -PVAR REBU64 PG_Mem_Limit; // Memory limit set by SECURE - -//-- Symbol Table: -PVAR REBSER *PG_Word_Names; // Holds all word strings. Never removed. -PVAR WORD_TABLE PG_Word_Table; // Symbol values accessed by hash +PVAR REBINT PG_Boot_Phase; // To know how far in the boot we are. +PVAR REBINT PG_Boot_Level; // User specified startup level + +// PG_Reb_Stats - Various statistics about memory, etc. This is only tracked +// in the debug build, as this data gathering is a sort of constant "tax" on +// the system. While it might arguably be interesting to non-debug build +// users who are trying to optimize their code, the compromise of having to +// maintain the numbers suggests those users should be empowered with a debug +// build if they are doing such work (they should probably have one for other +// reasons; note this has been true of things like Windows NT where there were +// indeed "checked" builds given to those who had such interest.) +// +#if !defined(NDEBUG) + PVAR REB_STATS *PG_Reb_Stats; +#endif + +PVAR REBU64 PG_Mem_Usage; // Overall memory used +PVAR REBU64 PG_Mem_Limit; // Memory limit set by SECURE + +// In Ren-C, words are REBSER nodes (REBSTR subtype). They may be GC'd (unless +// they are in the %words.r list, in which case their canon forms are +// protected in order to do SYM_XXX switch statements in the C source, etc.) +// +// There is a global hash table which accelerates finding a word's REBSER +// node from a UTF-8 source string. Entries are added to it when new canon +// forms of words are created, and removed when they are GC'd. It is scaled +// according to the total number of canons in the system. +// +PVAR REBSTR *PG_Symbol_Canons; // Canon symbol pointers for words in %words.r +PVAR REBSTR *PG_Canons_By_Hash; // Canon REBSER pointers indexed by hash +PVAR REBCNT PG_Num_Canon_Slots_In_Use; // Total canon hash slots (+ deleteds) +#if !defined(NDEBUG) + PVAR REBCNT PG_Num_Canon_Deleteds; // Deleted canon hash slots "in use" +#endif //-- Main contexts: -PVAR ROOT_CTX *Root_Context; // System root variables -PVAR REBSER *Lib_Context; -PVAR REBSER *Sys_Context; +PVAR REBARR *PG_Root_Array; // Frame that holds Root_Vars +PVAR ROOT_VARS *Root_Vars; // PG_Root_Array's values as a C structure + +PVAR REBCTX *Lib_Context; +PVAR REBCTX *Sys_Context; //-- Various char tables: PVAR REBYTE *White_Chars; @@ -51,18 +76,47 @@ PVAR REBUNI *Upper_Cases; PVAR REBUNI *Lower_Cases; // Other: -PVAR REBYTE *PG_Pool_Map; // Memory pool size map (created on boot) -PVAR REBSER *PG_Root_Words; // Root object word table (reused by threads) +PVAR REBYTE *PG_Pool_Map; // Memory pool size map (created on boot) -PVAR REBI64 PG_Boot_Time; // Counter when boot started -PVAR REBINT Current_Year; +PVAR REBI64 PG_Boot_Time; // Counter when boot started PVAR REB_OPTS *Reb_Opts; +#ifndef NDEBUG + PVAR REBOOL PG_Always_Malloc; // For memory-related troubleshooting +#endif + +// These are some canon BLANK, TRUE, and FALSE values (and void/end cells). +// In two-element arrays in order that those using them don't accidentally +// pass them to routines that will increment the pointer as if they are +// arrays--they are singular values, and the second element is set to +// be trash to trap any unwanted access. +// +PVAR RELVAL PG_End_Node; +PVAR REBVAL PG_Void_Cell[2]; + +PVAR REBVAL PG_Blank_Value[2]; +PVAR REBVAL PG_Bar_Value[2]; +PVAR REBVAL PG_False_Value[2]; +PVAR REBVAL PG_True_Value[2]; + +// Special (but standards-legal) REBVAL* used in the `pending` field of a frame +// to indicate it fetches its values from a C va_list. +// +PVAR REBVAL PG_Va_List_Pending; + // This signal word should be thread-local, but it will not work // when implemented that way. Needs research!!!! -PVAR REBCNT Eval_Signals; // Signal flags +PVAR REBFLGS Eval_Signals; // Signal flags +// Hook called when BREAKPOINT is hit. It will return TRUE if the breakpoint +// is quitting, or FALSE if it is continuing. (Note that if one is HALTing, +// then it won't return at all...because that is done via longjmp.) +// +PVAR REBBRK PG_Breakpoint_Quitting_Hook; +// !!! See bad hack in %t-port.c that uses this for the moment. +// +PVAR REBVAL PG_Write_Action; /*********************************************************************** ** @@ -70,46 +124,84 @@ PVAR REBCNT Eval_Signals; // Signal flags ** ***********************************************************************/ -TVAR TASK_CTX *Task_Context; // Main per-task variables -TVAR REBSER *Task_Series; // Series that holds Task_Context +TVAR REBARR *TG_Task_Array; // Array that holds Task_Vars +TVAR TASK_VARS *Task_Vars; // TG_Task_Array's values as a C structure + +TVAR REBVAL TG_Thrown_Arg; // Non-GC protected argument to THROW //-- Memory and GC: -TVAR REBPOL *Mem_Pools; // Memory pool array -TVAR REBCNT GC_Disabled; // GC disabled counter for critical sections. -TVAR REBINT GC_Ballast; // Bytes allocated to force automatic GC -TVAR REBOOL GC_Active; // TRUE when recycle is enabled (set by RECYCLE func) -TVAR REBSER *GC_Protect; // A stack of protected series (removed by pop) -TVAR REBSER *GC_Series; // An array of protected series (removed by address) -TVAR REBSER **GC_Infants; // A small list of last N series created (nursery) -TVAR REBINT GC_Last_Infant; // Index to last infant above (circular) -TVAR REBFLG GC_Stay_Dirty; // Do not free memory, fill it with 0xBB -TVAR REBSER **Prior_Expand; // Track prior series expansions (acceleration) - -TVAR REBCNT Stack_Limit; // Limit address for CPU stack. +TVAR REBPOL *Mem_Pools; // Memory pool array +TVAR REBOOL GC_Recycling; // True when the GC is in a recycle +TVAR REBINT GC_Ballast; // Bytes allocated to force automatic GC +TVAR REBOOL GC_Disabled; // TRUE when RECYCLE/OFF is run +TVAR REBSER *GC_Guarded; // A stack of GC protected series and values +PVAR REBSER *GC_Mark_Stack; // Series pending to mark their reachables as live +TVAR REBSER **Prior_Expand; // Track prior series expansions (acceleration) + +// These manually-managed series must either be freed with Free_Series() +// or handed over to the GC at certain synchronized points, else they +// would represent a memory leak in the release build. +TVAR REBSER *GC_Manuals; // Manually memory managed (not by GC) + +TVAR REBUPT Stack_Limit; // Limit address for CPU stack. + +#if !defined(NDEBUG) + // This counter is incremented each time through the DO loop, and can be + // used for many purposes...including setting breakpoints in routines + // other than Do_Next that are contingent on a certain "tick" elapsing. + // + TVAR REBUPT TG_Do_Count; + + TVAR REBIPT TG_Num_Black_Series; +#endif + +// Each time Do_Core is called a Reb_Frame* is pushed to the "frame stack". +// Some pushed entries will represent groups or paths being executed, and +// some will represent functions that are gathering arguments...hence they +// have been "pushed" but are not yet actually running. This stack must +// be filtered to get an understanding of something like a "backtrace of +// currently running functions". +// +TVAR REBFRM *TG_Frame_Stack; //-- Evaluation stack: -TVAR REBSER *DS_Series; -TVAR REBVAL *DS_Base; // Data stack base -TVAR REBINT DSP; // Data stack pointer -TVAR REBINT DSF; // Data stack frame (function base) - -TVAR jmp_buf *Saved_State; // Pointer to saved CPU state +TVAR REBARR *DS_Array; +TVAR REBDSP DS_Index; +TVAR REBVAL *DS_Movable_Base; + +// We store the head chunk of the current chunker even though it could be +// computed, because it's quicker to compare to a pointer than to do the +// math to calculate it on each Drop_Chunk...and it only needs to be updated +// when a chunk boundary gets crossed (pushing or dropping) +// +TVAR struct Reb_Chunk *TG_Top_Chunk; +TVAR struct Reb_Chunk *TG_Head_Chunk; +TVAR struct Reb_Chunker *TG_Root_Chunker; + +TVAR struct Reb_State *Saved_State; // Saved state for Catch (CPU state, etc.) + +#if !defined(NDEBUG) + // In debug builds, the `panic` and `fail` macros capture the file and + // line number of instantiation so any Make_Error can pick it up. + TVAR const char *TG_Erroring_C_File; + TVAR int TG_Erroring_C_Line; + + TVAR REBOOL TG_Pushing_Mold; // Push_Mold should not directly recurse +#endif //-- Evaluation variables: -TVAR REBI64 Eval_Cycles; // Total evaluation counter (upward) -TVAR REBI64 Eval_Limit; // Evaluation limit (set by secure) -TVAR REBINT Eval_Count; // Evaluation counter (downward) -TVAR REBINT Eval_Dose; // Evaluation counter reset value -TVAR REBCNT Eval_Sigmask; // Masking out signal flags - -TVAR REBCNT Trace_Flags; // Trace flag -TVAR REBINT Trace_Level; // Trace depth desired -TVAR REBINT Trace_Depth; // Tracks trace indentation -TVAR REBCNT Trace_Limit; // Backtrace buffering limit -TVAR REBSER *Trace_Buffer; // Holds backtrace lines - -TVAR REBI64 Eval_Natives; +TVAR REBI64 Eval_Cycles; // Total evaluation counter (upward) +TVAR REBI64 Eval_Limit; // Evaluation limit (set by secure) +TVAR REBINT Eval_Count; // Evaluation counter (downward) +TVAR REBCNT Eval_Dose; // Evaluation counter reset value +TVAR REBFLGS Eval_Sigmask; // Masking out signal flags + +TVAR REBFLGS Trace_Flags; // Trace flag +TVAR REBINT Trace_Level; // Trace depth desired +TVAR REBINT Trace_Depth; // Tracks trace indentation +TVAR REBCNT Trace_Limit; // Backtrace buffering limit +TVAR REBSER *Trace_Buffer; // Holds backtrace lines + TVAR REBI64 Eval_Functions; -//-- Other per thread globals: -TVAR REBSER *Bind_Table; // Used to quickly bind words to contexts +TVAR REBVAL Callback_Error; //Error produced by callback!, note it's not callback:// diff --git a/src/include/sys-handle.h b/src/include/sys-handle.h new file mode 100644 index 0000000000..2d8be5c45a --- /dev/null +++ b/src/include/sys-handle.h @@ -0,0 +1,214 @@ +// +// File: %sys-handle.h +// Summary: "Definitions for GC-able and non-GC-able Handles" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// In Rebol terminology, a HANDLE! is a pointer to a function or data that +// represents an arbitrary external resource. While such data could also +// be encoded as a BINARY! "blob" (as it might be in XML), the HANDLE! type +// is intentionally "opaque" to user code so that it is a black box. +// +// Additionally, Ren-C added the idea of a garbage collector callback for +// "Managed" handles. This is implemented by means of making the handle cost +// a single REBSER node shared among its instances, which is a "singular" +// Array containing a canon value of the handle itself. When there are no +// references left to the handle and the GC runs, it will run a hook stored +// in the ->misc field of the singular array. +// +// As an added benefit of the Managed form, the code and data pointers in the +// value itself are not used; instead preferring the data held in the REBARR. +// This allows one instance of a managed handle to have its code or data +// pointer changed and be reflected in all instances. The simple form of +// handle however is such that each REBVAL copied instance is independent, +// and changing one won't change the others. +// + +#ifdef NDEBUG + #define HANDLE_FLAG(n) \ + FLAGIT_LEFT(TYPE_SPECIFIC_BIT + (n)) +#else + #define HANDLE_FLAG(n) \ + (FLAGIT_LEFT(TYPE_SPECIFIC_BIT + (n)) | HEADERIZE_KIND(REB_HANDLE)) +#endif + +// Note: In the C language, sizeof(void*) may not be the same size as a +// function pointer; hence they can't necessarily be cast between each other. +// In practice, a void* is generally big enough to hold a CFUNC*, and many +// APIs do assume this. +// +#define HANDLE_FLAG_CFUNC HANDLE_FLAG(0) + + +inline static REBUPT VAL_HANDLE_LEN(const RELVAL *v) { + assert(IS_HANDLE(v)); + if (v->extra.singular) + return ARR_HEAD(v->extra.singular)->payload.handle.length; + else + return v->payload.handle.length; +} + +inline static void *VAL_HANDLE_VOID_POINTER(const RELVAL *v) { + assert(IS_HANDLE(v)); + assert(NOT_VAL_FLAG(v, HANDLE_FLAG_CFUNC)); + if (v->extra.singular) + return ARR_HEAD(v->extra.singular)->payload.handle.data.pointer; + else + return v->payload.handle.data.pointer; +} + +#define VAL_HANDLE_POINTER(t, v) \ + cast(t *, VAL_HANDLE_VOID_POINTER(v)) + +inline static CFUNC *VAL_HANDLE_CFUNC(const RELVAL *v) { + assert(IS_HANDLE(v)); + assert(GET_VAL_FLAG(v, HANDLE_FLAG_CFUNC)); + if (v->extra.singular) + return ARR_HEAD(v->extra.singular)->payload.handle.data.cfunc; + else + return v->payload.handle.data.cfunc; +} + +inline static CLEANUP_FUNC VAL_HANDLE_CLEANER(const RELVAL *v) { + assert(IS_HANDLE(v)); + REBARR *singular = v->extra.singular; + return singular ? SER(singular)->misc.cleaner : NULL; +} + +inline static void SET_HANDLE_LEN(RELVAL *v, REBUPT length) { + assert(IS_HANDLE(v)); + if (v->extra.singular) + ARR_HEAD(v->extra.singular)->payload.handle.length = length; + else + v->payload.handle.length = length; +} + +inline static void SET_HANDLE_POINTER(RELVAL *v, void *pointer) { + assert(IS_HANDLE(v)); + assert(NOT_VAL_FLAG(v, HANDLE_FLAG_CFUNC)); + if (v->extra.singular) + ARR_HEAD(v->extra.singular)->payload.handle.data.pointer = pointer; + else + v->payload.handle.data.pointer = pointer; +} + +inline static void SET_HANDLE_CFUNC(RELVAL *v, CFUNC *cfunc) { + assert(IS_HANDLE(v)); + assert(GET_VAL_FLAG(v, HANDLE_FLAG_CFUNC)); + if (v->extra.singular) + ARR_HEAD(v->extra.singular)->payload.handle.data.cfunc = cfunc; + else + v->payload.handle.data.cfunc = cfunc; +} + +inline static void Init_Handle_Simple( + RELVAL *out, + void *pointer, + REBUPT length +){ + VAL_RESET_HEADER(out, REB_HANDLE); + out->extra.singular = NULL; + out->payload.handle.data.pointer = pointer; + out->payload.handle.length = length; +} + +inline static void Init_Handle_Cfunc( + RELVAL *out, + CFUNC *cfunc, + REBUPT length +){ + VAL_RESET_HEADER_EXTRA(out, REB_HANDLE, HANDLE_FLAG_CFUNC); + out->extra.singular = NULL; + out->payload.handle.data.cfunc = cfunc; + out->payload.handle.length = length; +} + +inline static void Init_Handle_Managed_Common( + RELVAL *out, + REBUPT length, + CLEANUP_FUNC cleaner +){ + REBARR *singular = Alloc_Singular_Array(); + SER(singular)->misc.cleaner = cleaner; + + RELVAL *v = ARR_HEAD(singular); + v->extra.singular = singular; + v->payload.handle.length = length; + + // Caller will fill in whichever field is needed. Note these are both + // the same union member, so trashing them both is semi-superfluous, but + // serves a commentary purpose here. + // + TRASH_POINTER_IF_DEBUG(v->payload.handle.data.pointer); + TRASH_CFUNC_IF_DEBUG(v->payload.handle.data.cfunc); + + MANAGE_ARRAY(singular); + + // Don't fill the handle properties in the instance if it's the managed + // form. This way, you can set the properties in the canon value and + // effectively update all instances...since the bits live in the shared + // series component. + // + TRASH_CELL_IF_DEBUG(out); + VAL_RESET_HEADER(out, REB_HANDLE); + out->extra.singular = singular; + TRASH_POINTER_IF_DEBUG(out->payload.handle.data.pointer); +} + +inline static void Init_Handle_Managed( + RELVAL *out, + void *pointer, + REBUPT length, + CLEANUP_FUNC cleaner +){ + Init_Handle_Managed_Common(out, length, cleaner); + + // Leave the non-singular cfunc as trash; clients should not be using + // + VAL_RESET_HEADER(out, REB_HANDLE); + + VAL_RESET_HEADER(ARR_HEAD(out->extra.singular), REB_HANDLE); + ARR_HEAD(out->extra.singular)->payload.handle.data.pointer = pointer; +} + +inline static void Init_Handle_Managed_Cfunc( + RELVAL *out, + CFUNC *cfunc, + REBUPT length, + CLEANUP_FUNC cleaner +){ + Init_Handle_Managed_Common(out, length, cleaner); + + // Leave the non-singular cfunc as trash; clients should not be using + // + VAL_RESET_HEADER_EXTRA(out, REB_HANDLE, HANDLE_FLAG_CFUNC); + + VAL_RESET_HEADER_EXTRA( + ARR_HEAD(out->extra.singular), + REB_HANDLE, + HANDLE_FLAG_CFUNC + ); + ARR_HEAD(out->extra.singular)->payload.handle.data.cfunc = cfunc; +} diff --git a/src/include/sys-indexor.h b/src/include/sys-indexor.h new file mode 100644 index 0000000000..b3e89f341f --- /dev/null +++ b/src/include/sys-indexor.h @@ -0,0 +1,208 @@ +// +// File: %sys-indexor.h +// Summary: {Definitions for "INDEX-OR-a-flag", and C++ supplementary checks} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2016 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// R3-Alpha wished to encode "magic values" into the integer index which is +// used for stepping through arrays. Hence 0, 1, 2, etc. would be normal +// indices, but 2,147,483,647 and 2,147,483,648 would be "magic" values +// (for instance) to indicate a status result of THROWN or END of input. +// +// Ren-C gave this encoded value a separate REBIXO type and the name "Indexor" +// to mean "Index-OR-a-Flag". In the C build this is the same old unsigned +// integer. But the C++ debug build uses a class that at compile time checks +// to make sure no flag value is implicitly converted to a REBCNT, and at +// runtime checks that explicit casts don't violate the rule either. +// +// !!! This could be enhanced so that the REBIXO would keep track of whether +// or not it had been tested for THROWN_FLAG and END_FLAG. However, this +// would take more bits out of the index, if a REBIXO seeks to be the same +// size and bit pattern in the C and C++ build. (Losing the has-been-checked +// bits would be less intrusive in a 64-bit build.) +// +// The extra code and checking is only paid for in the *debug* C++ build. +// See "Static-and-Dynamic-Analysis-in-the-Cpp-Build" on: +// +// https://github.com/metaeducation/ren-c/wiki/ +// + +#if defined(NDEBUG) || !defined(__cplusplus) || (__cplusplus < 201103L) + typedef REBUPT REBIXO; + + #define END_FLAG 0x80000000 // end of block as index + #define THROWN_FLAG (END_FLAG - 0x75) // throw as an index + + // The VA_LIST_FLAG is the index used when a C va_list pointer is input. + // Because access to a `va_list` is strictly increasing through va_arg(), + // there is no way to track an index; fetches are indexed automatically + // and sequentially without possibility for mutation of the list. Should + // this index be used it will always be the index of a DO_NEXT until + // either an END_FLAG or a THROWN_FLAG is reached. + // + #define VA_LIST_FLAG (END_FLAG - 0xBD) + + // These are not actually used with REBIXO, but have a similar purpose... + // fold a flag into an integer (like a std::optional). + // + #define NOT_FOUND ((REBCNT)-1) + #define UNKNOWN ((REBCNT)-1) +#else + class NOT_FOUND_t { + public: + NOT_FOUND_t () {} // clang won't initialize const object w/o this + operator REBCNT() const { + return ((REBCNT)-1); + } + }; + const NOT_FOUND_t NOT_FOUND; + const NOT_FOUND_t UNKNOWN; + + class REBIXO { + // + // If an equality or inequality test is done against THROWN_FLAG or + // END_FLAG, this mutates the bits to indicate the test has been + // done. Coercion to a plain integer will not be allowed unless both + // have been tested for. + // + REBUPT bits; + + public: + // Make sure you can't assign or compare from NOT_FOUND or UNKNOWN + // + REBIXO (NOT_FOUND_t const &) = delete; + void operator=(NOT_FOUND_t const &) = delete; + int operator==(NOT_FOUND_t const &rhs) const = delete; + int operator!=(NOT_FOUND_t const &rhs) const = delete; + + public: + REBIXO () {} // simulate C uninitialization + REBIXO (REBCNT bits) : bits (bits) { + assert(bits != ((REBCNT)-1)); // not with REBIXO! + } + + void operator=(REBCNT rhs) { + assert(rhs != ((REBCNT)-1)); // not with REBIXO! + bits = rhs; + } + int operator==(REBIXO const &rhs) const { return bits == rhs.bits; } + int operator!=(REBIXO const &rhs) const { return !((*this) == rhs); } + + // Basic check: whenever one tries to get an actual REBCNT out of + // an indexor, it is asserted not to be a magic value. Called by + // the math operations, as well as any explicit `cast(REBCNT, indexor)` + // + explicit operator REBCNT() const { + // + // Individual asserts so the line number tells you which it is. + // + assert(bits != 0x80000000); // END_FLAG + assert(bits != 0x80000000 - 0x75); // THROWN_FLAG + assert(bits != 0x80000000 - 0xBD); // VA_LIST_FLAG + #if !defined(NDEBUG) + assert(bits != 0x80000000 - 0xAE); // TRASHED_INDEX + #endif + return bits; + } + + // Subset of operations that are exported to be legal to perform with + // an unsigned integer and an indexor. Comparisons for equality and + // addition and subtraction are allowed. While more operations could + // be added, the best course of action is generally that if one is + // to do a lot of math on an indexor it is not a special value...so it + // should be extracted by casting to a REBCNT. + // + friend int operator==(REBCNT lhs, const REBIXO &rhs) { + assert(lhs != UNKNOWN && lhs != NOT_FOUND); + return lhs == rhs.bits; + } + friend int operator!=(REBCNT lhs, const REBIXO &rhs) { + return !(lhs == rhs); + } + int operator<(REBCNT rhs) const { + return cast(REBCNT, *this) < rhs; + } + friend int operator<(REBCNT lhs, const REBIXO &rhs) { + return lhs < rhs.bits; + } + int operator>(REBCNT rhs) const { + return cast(REBCNT, *this) > rhs; + } + friend int operator>(REBCNT lhs, const REBIXO &rhs) { + return lhs > rhs.bits; + } + int operator<=(REBCNT rhs) const { + return cast(REBCNT, *this) <= rhs; + } + friend int operator<=(REBCNT lhs, const REBIXO &rhs) { + return lhs <= rhs.bits; + } + REBCNT operator+(REBCNT rhs) const { + return cast(REBCNT, *this) + rhs; + } + friend REBCNT operator+(REBCNT lhs, const REBIXO &rhs) { + return rhs + lhs; + } + REBCNT operator-(REBCNT rhs) const { + return cast(REBCNT, *this) - rhs; + } + friend REBCNT operator-(REBCNT lhs, const REBIXO &rhs) { + return rhs - lhs; + } + REBCNT operator*(REBCNT rhs) const { + return cast(REBCNT, *this) * rhs; + } + friend REBCNT operator*(REBCNT lhs, const REBIXO &rhs) { + return rhs * lhs; + } + + REBIXO& operator++ () { + bits = cast(REBCNT, *this) + 1; // cast ensures no END, THROWN + return *this; + } + REBIXO& operator-- () { + assert(bits != 0); // additional check + bits = cast(REBCNT, *this) - 1; // cast ensures no END, THROWN + return *this; + } + }; + + const REBIXO END_FLAG (0x80000000); + const REBIXO THROWN_FLAG (0x80000000 - 0x75); + const REBIXO VA_LIST_FLAG (0x80000000 - 0xBD); + + // We want the C++ class to be the same size and compatible bit patterns + // to the C build, so their binary code works together. + // + static_assert( + sizeof(REBIXO) == sizeof(REBUPT), "REBIXO size must equal REBUPT" + ); +#endif + +// This is used internally in frames in the debug build when the index +// does not apply (e.g. END, THROWN, VA_LIST) +// +#if !defined(NDEBUG) + #define TRASHED_INDEX (0x80000000 - 0xAE) +#endif diff --git a/src/include/sys-int-funcs.h b/src/include/sys-int-funcs.h new file mode 100644 index 0000000000..7fbeac19a1 --- /dev/null +++ b/src/include/sys-int-funcs.h @@ -0,0 +1,155 @@ +// +// File: %sys-int-funcs.h +// Summary: "Integer Datatype Functions" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2014 Atronix Engineering, Inc. +// Copyright 2014-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// To grok these routine names, consider unsigned multiplication: +// +// umull is 'U MUL L' for unsigned multiplication of long +// umulll is 'U MUL LL' for unsigned multiplication of long long +// +// REBU64 may be an unsigned long long of equivalent size to +// unsigned long, and similarly for REBI64 and long long. But +// the types may be incidentally the same size, if you turn up +// warnings it will require a cast instead of silently passing +// pointers of one to routines expecting a pointer to the other. +// So we cast to the singularly-long variant before calling any +// of the __builtin 'l' variants with a 64-bit REBU64 or REBI64. +// + +#ifndef __SYS_INT_FUNCS_H_ +#define __SYS_INT_FUNCS_H_ + +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + +#ifdef __GNUC__ +#define GCC_VERSION_AT_LEAST(m, n) \ + (__GNUC__ > (m) || (__GNUC__ == (m) && __GNUC_MINOR__ >= (n))) +#else +#define GCC_VERSION_AT_LEAST(m, n) 0 +#endif + +#if __has_builtin(__builtin_sadd_overflow) || GCC_VERSION_AT_LEAST(5, 1) +#define REB_I32_ADD_OF(x, y, sum) __builtin_sadd_overflow((x), (y), (sum)) +#else +REBOOL reb_i32_add_overflow(i32 x, i32 y, i32 *sum); +#define REB_I32_ADD_OF(x, y, sum) reb_i32_add_overflow((x), (y), (sum)) +#endif + +#if __has_builtin(__builtin_uadd_overflow) || GCC_VERSION_AT_LEAST(5, 1) +#define REB_U32_ADD_OF(x, y, sum) __builtin_uadd_overflow((x), (y), (sum)) +#else +REBOOL reb_u32_add_overflow(u32 x, u32 y, u32 *sum); +#define REB_U32_ADD_OF(x, y, sum) reb_u32_add_overflow((x), (y), (sum)) +#endif + +#if __has_builtin(__builtin_saddl_overflow) && __has_builtin(__builtin_saddll_overflow) || GCC_VERSION_AT_LEAST(5, 1) +#ifdef __LP64__ +#define REB_I64_ADD_OF(x, y, sum) \ + __builtin_saddl_overflow((x), (y), cast(long*, sum)) +#else // presumably __LLP64__ or __LP32__ +#define REB_I64_ADD_OF(x, y, sum) __builtin_saddll_overflow((x), (y), (sum)) +#endif //__LP64__ +#else +REBOOL reb_i64_add_overflow(i64 x, i64 y, i64 *sum); +#define REB_I64_ADD_OF(x, y, sum) reb_i64_add_overflow((x), (y), (sum)) +#endif + +#if __has_builtin(__builtin_uaddl_overflow) && __has_builtin(__builtin_uaddll_overflow) || GCC_VERSION_AT_LEAST(5, 1) +#ifdef __LP64__ +#define REB_U64_ADD_OF(x, y, sum) \ + __builtin_uaddl_overflow((x), (y), cast(unsigned long*, sum)) +#else // presumably __LLP64__ or __LP32__ +#define REB_U64_ADD_OF(x, y, sum) __builtin_uaddll_overflow((x), (y), (sum)) +#endif //__LP64__ +#else +REBOOL reb_u64_add_overflow(u64 x, u64 y, u64 *sum); +#define REB_U64_ADD_OF(x, y, sum) reb_u64_add_overflow((x), (y), (sum)) +#endif + +#if __has_builtin(__builtin_ssub_overflow) || GCC_VERSION_AT_LEAST(5, 1) +#define REB_I32_SUB_OF(x, y, diff) __builtin_ssub_overflow((x), (y), (diff)) +#else +REBOOL reb_i32_sub_overflow(i32 x, i32 y, i32 *diff); +#define REB_I32_SUB_OF(x, y, diff) reb_i32_sub_overflow((x), (y), (diff)) +#endif + +#if __has_builtin(__builtin_ssubl_overflow) && __has_builtin(__builtin_ssubll_overflow) || GCC_VERSION_AT_LEAST(5, 1) +#ifdef __LP64__ +#define REB_I64_SUB_OF(x, y, diff) \ + __builtin_ssubl_overflow((x), (y), cast(long*, (diff))) +#else // presumably __LLP64__ or __LP32__ +#define REB_I64_SUB_OF(x, y, diff) __builtin_ssubll_overflow((x), (y), (diff)) +#endif //__LP64__ +#else +REBOOL reb_i64_sub_overflow(i64 x, i64 y, i64 *diff); +#define REB_I64_SUB_OF(x, y, diff) reb_i64_sub_overflow((x), (y), (diff)) +#endif + +#if __has_builtin(__builtin_smul_overflow) || GCC_VERSION_AT_LEAST(5, 1) +#define REB_I32_MUL_OF(x, y, prod) __builtin_smul_overflow((x), (y), (prod)) +#else +REBOOL reb_i32_mul_overflow(i32 x, i32 y, i32 *prod); +#define REB_I32_MUL_OF(x, y, prod) reb_i32_mul_overflow((x), (y), (prod)) +#endif + +#if __has_builtin(__builtin_umul_overflow) || GCC_VERSION_AT_LEAST(5, 1) +#define REB_U32_MUL_OF(x, y, prod) __builtin_umul_overflow((x), (y), (prod)) +#else +REBOOL reb_u32_mul_overflow(u32 x, u32 y, u32 *prod); +#define REB_U32_MUL_OF(x, y, prod) reb_u32_mul_overflow((x), (y), (prod)) +#endif + +#if __has_builtin(__builtin_smull_overflow) && __has_builtin(__builtin_smulll_overflow) || GCC_VERSION_AT_LEAST(5, 1) +#ifdef __LP64__ +#define REB_I64_MUL_OF(x, y, prod) \ + __builtin_smull_overflow((x), (y), cast(long*, prod)) +#elif !defined(__clang__) //__builtin_smulll_overflow doesn't work on 32-bit systems yet, causing undefined reference to __mulodi4 +#define REB_I64_MUL_OF(x, y, prod) __builtin_smulll_overflow((x), (y), cast(long long*, prod)) +#else // presumably __LLP64__ or __LP32__ +REBOOL reb_i64_mul_overflow(i64 x, i64 y, i64 *prod); +#define REB_I64_MUL_OF(x, y, prod) reb_i64_mul_overflow((x), (y), cast(long long*, prod)) +#endif //__LP64__ +#else +REBOOL reb_i64_mul_overflow(i64 x, i64 y, i64 *prod); +#define REB_I64_MUL_OF(x, y, prod) reb_i64_mul_overflow((x), (y), (prod)) +#endif + +#if __has_builtin(__builtin_umull_overflow) && __has_builtin(__builtin_umulll_overflow) || GCC_VERSION_AT_LEAST(5, 1) +#ifdef __LP64__ +#define REB_U64_MUL_OF(x, y, prod) \ + __builtin_umull_overflow((x), (y), cast(unsigned long*, (prod))) +#else // presumably __LLP64__ or __LP32__ +#define REB_U64_MUL_OF(x, y, prod) __builtin_umulll_overflow((x), (y), (prod)) +#endif //__LP64__ +#else +REBOOL reb_u64_mul_overflow(u64 x, u64 y, u64 *prod); +#define REB_U64_MUL_OF(x, y, prod) reb_u64_mul_overflow((x), (y), (prod)) +#endif + +#endif //__SYS_INT_FUNCS_H_ diff --git a/src/include/sys-map.h b/src/include/sys-map.h new file mode 100644 index 0000000000..9c629fcd21 --- /dev/null +++ b/src/include/sys-map.h @@ -0,0 +1,89 @@ +// +// File: %sys-map.h +// Summary: {Definitions for REBMAP} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Maps are implemented as a light hashing layer on top of an array. The +// hash indices are stored in the series node's "misc", while the values are +// retained in pairs as `[key val key val key val ...]`. +// +// When there are too few values to warrant hashing, no hash indices are +// made and the array is searched linearly. This is indicated by the hashlist +// being NULL. +// +// Though maps are not considered a series in the "ANY-SERIES!" value sense, +// they are implemented using series--and hence are in %sys-series.h, at least +// until a better location for the definition is found. +// +// !!! Should there be a MAP_LEN()? Current implementation has NONE in +// slots that are unused, so can give a deceptive number. But so can +// objects with hidden fields, locals in paramlists, etc. +// + +struct Reb_Map { + struct Reb_Array pairlist; // hashlist is held in ->link.hashlist +}; + +inline static REBARR *MAP_PAIRLIST(REBMAP *m) { + assert(GET_SER_FLAG(&(m)->pairlist, ARRAY_FLAG_PAIRLIST)); + return (&(m)->pairlist); +} + +#define MAP_HASHLIST(m) \ + (SER(MAP_PAIRLIST(m))->link.hashlist) + +#define MAP_HASHES(m) \ + SER_HEAD(MAP_HASHLIST(m)) + +inline static REBMAP *MAP(void *p) { + REBARR *a = ARR(p); + assert(GET_SER_FLAG(a, ARRAY_FLAG_PAIRLIST)); + return cast(REBMAP*, a); +} + + +inline static REBMAP *VAL_MAP(const RELVAL *v) { + assert(IS_MAP(v)); + + // Ren-C introduced const REBVAL* usage, but propagating const vs non + // const REBSER pointers didn't show enough benefit to be worth the + // work in supporting them (at this time). Mutability cast needed. + // + return MAP(m_cast(RELVAL*, v)->payload.any_series.series); +} + +inline static REBCNT Length_Map(REBMAP *map) +{ + REBVAL *v = KNOWN(ARR_HEAD(MAP_PAIRLIST(map))); + + REBCNT count = 0; + for (; NOT_END(v); v += 2) { + if (!IS_VOID(v + 1)) + ++count; + } + + return count; +} diff --git a/src/include/sys-mem.h b/src/include/sys-mem.h deleted file mode 100644 index 277435ef42..0000000000 --- a/src/include/sys-mem.h +++ /dev/null @@ -1,139 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Memory allocation -** Module: sys-mem.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ - -#ifdef DBG_CHECK_MEM -#define CHECK_MEMORY(n) if (n > MEM_CARE) Check_Memory() -#else -#define CHECK_MEMORY(n) -#endif - -typedef void *REBNOD; // Just used for linking free nodes - -/*********************************************************************** -** -*/ typedef struct rebol_mem_segment -/* -** Linked list of used memory segments. -** -** Size: 8 bytes -** -***********************************************************************/ -{ - struct rebol_mem_segment *next; - REBCNT size; -} REBSEG; - - -/*********************************************************************** -** -*/ typedef struct rebol_mem_spec -/* -** Specifies initial pool sizes -** -***********************************************************************/ -{ - REBCNT wide; // size of allocation unit - REBCNT units; // units per segment allocation -} REBPOOLSPEC; - - -/*********************************************************************** -** -*/ typedef struct rebol_mem_pool -/* -** Pools manage fixed sized blocks of memory. -** -***********************************************************************/ -{ - REBCNT wide; // size of allocation unit - REBCNT units; // units per segment allocation - REBCNT free; // number of units remaining - REBSEG *segs; // first memory segment - REBNOD *first; // first free node in pool - REBCNT has; // total number of units -// UL total; // total bytes for all segs -// char *name; // identifying string -// UL extra; // reserved -} REBPOL; - - -/*********************************************************************** -** -*/ enum Mem_Pool_Specs -/* -***********************************************************************/ -{ - MEM_TINY_POOL = 1, - MEM_SMALL_POOLS = MEM_TINY_POOL + 16, - MEM_MID_POOLS = MEM_SMALL_POOLS + 4, - MEM_BIG_POOLS = MEM_MID_POOLS + 4, // larger pools - SERIES_POOL = MEM_BIG_POOLS, - GOB_POOL, - SYSTEM_POOL, - MAX_POOLS -}; - -#define DEF_POOL(size, count) {size, count} -#define MOD_POOL(size, count) {size * MEM_MIN_SIZE, count} - -#define MEM_MIN_SIZE sizeof(REBVAL) -#define MEM_BIG_SIZE 1024 - -#define MEM_BALLAST 3000000 - -// Disable GC - Only necessary if DO_NEXT with non-referenced series. -#define DISABLE_GC GC_Disabled++ -#define ENABLE_GC GC_Disabled-- -//Was: if (--GC_Disabled <= 0 && GC_Pending) Recycle() - -/***************************************************************************** -** -** MUNGWALL -** Define MUNGWALL to enable "MungWall"-style sentinels for REBNODEs -** -*****************************************************************************/ - -#ifdef MUNGWALL -#define MUNG_PATTERN1 "Don't overwrite!" -#define MUNG_PATTERN2 "Magic protection" -#define MUNG_SIZE 16 -#define MUNG_CHECK(a,b,c) Mung_Check((a),(REBYTE *)(b),(c)) -#ifdef TO_WIN32 -void mywrite(int a, char *b, int c) {int i;for(i=0;i - -#define GET_ERROR WSAGetLastError() -#define IOCTL ioctlsocket -#define CLOSE_SOCKET closesocket - -#define NE_ISCONN WSAEISCONN -#define NE_WOULDBLOCK WSAEWOULDBLOCK -#define NE_INPROGRESS WSAEINPROGRESS -#define NE_ALREADY WSAEALREADY -#define NE_NOTCONN WSAENOTCONN -#define NE_INVALID WSAEINVAL - -//----- BSD - The network standard the rest of the world uses -#else - -#ifdef TO_AMIGA -typedef char __BYTE; -typedef unsigned char __UBYTE; -typedef char * __STRPTR; -typedef long __LONG; -#endif - -#include -#include -#include -#include -#include - -#define GET_ERROR errno -#define IOCTL ioctl -#define CLOSE_SOCKET close -#define SOCKET unsigned int - -#define NE_ISCONN EISCONN -#define NE_WOULDBLOCK EAGAIN // see include/asm/errno.h -#define NE_INPROGRESS EINPROGRESS -#define NE_ALREADY EALREADY -#define NE_NOTCONN ENOTCONN -#define NE_INVALID EINVAL - -// Null Win32 functions: -#define WSADATA int - -// FreeBSD mystery define: -#ifndef u_int32_t -#define u_int32_t long -#endif - -#ifndef HOSTENT -typedef struct hostent HOSTENT; -#endif - -#ifndef MAXGETHOSTSTRUCT -#define MAXGETHOSTSTRUCT ((sizeof(struct hostent)+15) & ~15) -#endif - -#endif // BSD - -typedef struct sockaddr_in SOCKAI; // Internet extensions - -#define BAD_SOCKET (~0) -#define MAX_TRANSFER 32000 // Max send/recv buffer size -#define MAX_HOST_NAME 256 // Max length of host name diff --git a/src/include/sys-node.h b/src/include/sys-node.h new file mode 100644 index 0000000000..b53ec41da2 --- /dev/null +++ b/src/include/sys-node.h @@ -0,0 +1,47 @@ +// +// File: %sys-node.h +// Summary: {Convenience routines for the Node "superclass" structure} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This provides some convenience routines that require more definitions than +// are available when %sys-rebnod.h is being processed. (e.g. REBVAL, +// REBSER, REBFRM...) +// +// See %sys-rebnod.h for what a "node" means in this context. +// + +// !!! TBD: Make a fancier checking version of this +// +inline static REBNOD *NOD(void *p) { + assert(p != NULL); + + REBNOD *n = cast(REBNOD*, p); + assert( + (n->header.bits & NODE_FLAG_NODE) + && NOT(n->header.bits & NODE_FLAG_FREE) + ); + return n; +} diff --git a/src/include/sys-pair.h b/src/include/sys-pair.h new file mode 100644 index 0000000000..ebde3f5fc3 --- /dev/null +++ b/src/include/sys-pair.h @@ -0,0 +1,88 @@ +// +// File: %sys-pair.h +// Summary: {Definitions for Pairing Series and the Pair Datatype} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// A "paired" series hands out its handle as the REBVAL that does *not* have +// REBSER header bits scanned on it. This value is always mutable. The +// key, on the other hand, will only allow modifications if it is unmanaged +// (this stops inadvertent writes for other purposes from clearing the managed +// bit). +// +// !!! There is consideration of whether series payloads of length 2 might +// be directly allocated as paireds. This would require positioning such +// series in the pool so that they abutted against END markers. It would be +// premature optimization to do it right now, but the design leaves it open. +// +// PAIR! values are implemented using the pairing in Ren-C, which is to say +// that they are garbage collected and can hold any two values--not just +// two numbers. +// + +inline static REBVAL *PAIRING_KEY(REBVAL *pairing) { + return pairing - 1; +} + + + +#define VAL_PAIR(v) \ + ((v)->payload.pair) + +#define VAL_PAIR_X(v) \ + VAL_DECIMAL(PAIRING_KEY((v)->payload.pair)) + +#define VAL_PAIR_Y(v) \ + VAL_DECIMAL((v)->payload.pair) + +#define VAL_PAIR_X_INT(v) \ + ROUND_TO_INT(VAL_PAIR_X(v)) + +#define VAL_PAIR_Y_INT(v) \ + ROUND_TO_INT(VAL_PAIR_Y(v)) + +inline static void SET_PAIR(RELVAL *v, float x, float y) { + VAL_RESET_HEADER(v, REB_PAIR); + v->payload.pair = Alloc_Pairing(NULL); + Init_Decimal(PAIRING_KEY((v)->payload.pair), x); + Init_Decimal((v)->payload.pair, y); + Manage_Pairing((v)->payload.pair); +} + +inline static void SET_ZEROED(RELVAL *v, enum Reb_Kind kind) { + // + // !!! SET_ZEROED is a capturing of a dodgy behavior of R3-Alpha, + // which was to assume that clearing the payload of a value and then + // setting the header made it the `zero?` of that type. Review uses. + // + if (kind == REB_PAIR) { + SET_PAIR(v, 0, 0); // !!! inefficient, performs allocation, review + } + else { + VAL_RESET_HEADER(v, kind); + CLEAR(&v->extra, sizeof(union Reb_Value_Extra)); + CLEAR(&v->payload, sizeof(union Reb_Value_Payload)); + } +} diff --git a/src/include/sys-panics.h b/src/include/sys-panics.h deleted file mode 100644 index 58926f4b83..0000000000 --- a/src/include/sys-panics.h +++ /dev/null @@ -1,105 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: REBOL Panic Values -** Module: sys-panics.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ - -enum reb_panics { - -// Boot Errors (very limited environment avaliable) - RP_BOOT_DATA = 1000, // no boot.r text found - RP_REBVAL_ALIGNMENT, // not aligned perfectly in memory - RP_BAD_SIZE, // expected size did not match - RP_NO_BUFFER, // buffer not yet allocated - RP_BAD_BOOT_STRING, // boot strings area is invalid - RP_BAD_BOOT_TYPE_BLOCK, // boot block is wrong size - RP_BAD_END_TYPE_WORD, // the end word is not correct - RP_ACTION_OVERFLOW, // more actions than we should have - RE_NATIVE_BOOT, // bad boot.r native ordering - RP_EARLY_ERROR, // error before error handling - RP_BAD_END_CANON_WORD, // END was not found - RP_BAD_TRUE_CANON_WORD, // TRUE was not found - -// Internal Errors (other things that could go wrong) - RP_INTERNAL = 1100, - RP_BAD_EVALTYPE, // invalid datatype for evaluation - RP_CORRUPT_MEMORY, // Check_Memory() found a problem - RP_HASH_OVERFLOW, // Hash ran out of space - RP_NO_PRINT_PTR, // print is missing string pointer - -// Assertion Errors (very rare, not worth using strings) -// NOTE: THESE ARE OPTIONAL. Many are only checked in debug builds. - RP_ASSERTS = 1200, - RP_BAD_SERIES, // zero width series requested - RP_OVER_SERIES, // series overflow happened - RP_BIND_TABLE_SIZE, // word table does not match bind table - RP_BAD_SET_INDEX, // set word has no index - RP_BAD_SET_CONTEXT, // set word has no frame - RP_UNEXPECTED_END, // in GC, block ended before length reached - RP_MISSING_END, // block did not have an END marker - RP_NULL_MARK_SERIES, // in GC, mark series pointer is null - RP_NULL_SERIES, // in GC, a series is null - RP_HOLD_SERIES_MALIGN, // GC_Protect tail was wrong on UNSAVE - RP_THROW_IN_GC, // tried to GC a THROW error value - RP_FREE_NODE_SIZE, // node size is not what it should be - RP_NO_OBJECT_FRAME, // object frame is missing - RP_BAD_OBJ_FRAME, // object frame is invalid - RP_BAD_OBJ_INDEX, // object index past tail - RP_BAD_IMPORT_WORD, // the word index of context is invalid - RP_BAD_TYPE_ACTION, // datatype out of range in action dispatch - RP_BAD_PORT_ACTION, // datatype out of range for ports - RP_NO_ACTION, // Action Value_Dispatch empty for this type - RP_GC_STUCK, // GC_Disable did not get decremented - RP_GC_OF_BLOCK, // Block has been GC'd - RP_TOS_DRIFT, // TOS drifts during Do_Block - RP_MAX_SCHEMES, // Too many native schemes - RP_BIND_BOUNDS, // Bind is out of bounds for the frame - RP_SERIES_OVERFLOW, // Tail has gone past end of series - -// Datatype Errors (300 + N --indicates location) - RP_DATATYPE = 1300, - -// Documented Errors (keep in-sync with error strings in boot.r!) - RP_STR_BASE = 1400, - RP_NO_MEMORY, // not enough memory: %d bytes - RP_BAD_WIDTH, // invalid series width: %d %d %d - RP_ERROR_CATCH, // error already caught - RP_STACK_OVERFLOW, // data stack overflow - RP_IO_ERROR, // problem with IO - RP_MAX_WORDS, // too many words - RP_WORD_LIST, // word list (cache) already in use - RP_LOCKED_SERIES, // locked series expansion - RP_ERROR_RECYCLED, // the error object was gc'd! - RP_NO_CATCH, // top level uncaught error - RP_NO_SAVED_STATE, // saved state frame is missing - RP_MAX_EVENTS, // event queue overflow - RP_NA, // not available - -// Unspecified (just count them) - RP_MISC, -}; - -#define RP_ RP_ASSERTS - diff --git a/src/include/sys-path.h b/src/include/sys-path.h new file mode 100644 index 0000000000..4ec637d73c --- /dev/null +++ b/src/include/sys-path.h @@ -0,0 +1,164 @@ +// +// File: %sys-path.h +// Summary: "Definition of Structures for Path Processing" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// When a path like `a/(b + c)/d` is evaluated, it moves in steps. The +// evaluative result of chaining the prior steps is offered as input to +// the next step. The path evaluator `Do_Path_Throws` delegates steps to +// type-specific "(P)ath (D)ispatchers" with names like PD_Context, +// PD_Array, etc. +// +// R3-Alpha left several open questions about the handling of paths. One +// of the trickiest regards the mechanics of how to use a SET-PATH! to +// write data into native structures when more than one path step is +// required. For instance: +// +// >> gob/size +// == 10x20 +// +// >> gob/size/x: 304 +// >> gob/size +// == 10x304 +// +// Because GOB! stores its size as packed bits that are not a full PAIR!, +// the `gob/size` path dispatch can't give back a pointer to a REBVAL* to +// which later writes will update the GOB!. It can only give back a +// temporary value built from its internal bits. So workarounds are needed, +// as they are for a similar situation in trying to set values inside of +// C arrays in STRUCT!. +// +// The way the workaround works involves allowing a SET-PATH! to run forward +// and write into a temporary value. Then in these cases the temporary +// REBVAL is observed and used to write back into the native bits before the +// SET-PATH! evaluation finishes. This means that it's not currently +// prohibited for the effect of a SET-PATH! to be writing into a temporary. +// +// Further, the `value` slot is writable...even when it is inside of the path +// that is being dispatched: +// +// >> code: compose [(make set-path! [12-Dec-2012 day]) 1] +// == [12-Dec-2012/day: 1] +// +// >> do code +// +// >> probe code +// [1-Dec-2012/day: 1] +// +// Ren-C has largely punted on resolving these particular questions in order +// to look at "more interesting" ones. However, names and functions have +// been updated during investigation of what was being done. +// + + +//=////////////////////////////////////////////////////////////////////////=// +// +// PATH VALUE STATE "PVS" +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The path value state structure is used by `Do_Path_Throws()` and passed +// to the dispatch routines. See additional comments in %c-path.c. +// + +struct Reb_Path_Value_State { + // + // `item` is the current element within the path that is being processed. + // It is advanced as the path is consumed. + // + const RELVAL *item; + + // A specifier is needed because the PATH! is processed by incrementing + // through values, which may be resident in an array that was part of + // the cloning of a function body. The specifier allows the path + // evaluation to disambiguate which variable a word's relative binding + // would match. + // + REBSPC *item_specifier; + + // `picker` is the result of evaluating the current path item if + // necessary. So if the path is `a/(1 + 2)` and processing the second + // `item`, then the picker would be the computed value `3`. + // + // (This is what the individual path dispatchers should use.) + // + const REBVAL *picker; + REBVAL picker_cell; // picker = &picker_cell (GC guarded value) + + // `value` holds the path value that should be chained from. (It is the + // type of `value` that dictates which dispatcher is given the `selector` + // to get the next step.) This has to be a relative value in order to + // use the SET_IF_END option which writes into arrays. + // + RELVAL *value; + + // `value_specifier` has to be updated whenever value is updated + // + REBSPC *value_specifier; + + // `store` is the storage for constructed values, and also where any + // thrown value will be written. + // + REBVAL *store; + + // `setval` is non-NULL if this is a SET-PATH!, and it is the value to + // ultimately set the path to. The set should only occur at the end + // of the path, so most setters should check `IS_END(pvs->item + 1)` + // before setting. + // + // !!! See notes at top of file about why the path dispatch is more + // complicated than simply being able to only pass the setval to the last + // item being dispatched (which would be cleaner, but some cases must + // look ahead with alternate handling). + // + const REBVAL *opt_setval; + + // `orig` original path input, saved for error messages + // + const RELVAL *orig; + + // `label` is a concept that `obj/fun/refinement` would come back with + // the symbol FUN to identify a function, for the stack trace. This + // idea throws away information and is a little sketchy, not to mention + // that anonymous functions throw a wrench into it. But it is roughly + // what R3-Alpha did. + // + // !!! A better idea is probably to just temporarily lock the executing + // path until the function is done running, and use the path itself as + // the label. This provides more information and doesn't require the + // sketchy extraction logic. + // + REBSTR **label_out; +}; + + +enum Path_Eval_Result { + PE_OK, // pvs->value points to the element to take the next selector + PE_SET_IF_END, // only sets if end of path + PE_USE_STORE, // set pvs->value to be pvs->store + PE_NONE // set pvs->store to NONE and then pvs->value to pvs->store +}; + diff --git a/src/include/sys-rebfrm.h b/src/include/sys-rebfrm.h new file mode 100644 index 0000000000..39359f47b4 --- /dev/null +++ b/src/include/sys-rebfrm.h @@ -0,0 +1,624 @@ +// +// File: %sys-frame.h +// Summary: {Evaluator "Do State"} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The primary routine that performs DO and DO/NEXT is called Do_Core(). It +// takes a single parameter which holds the running state of the evaluator. +// This state may be allocated on the C variable stack. +// +// Do_Core() is written such that a longjmp up to a failure handler above it +// can run safely and clean up even though intermediate stacks have vanished. +// This is because Push_Frame and Drop_Frame maintain an independent global +// list of the frames in effect, so that the Fail_Core() routine can unwind +// all the associated storage and structures for each frame. +// +// Ren-C can not only run the evaluator across a REBARR-style series of +// input based on index, it can also enumerate through C's `va_list`, +// providing the ability to pass pointers as REBVAL* in a variadic function +// call from the C (comma-separated arguments, as with printf()). Future data +// sources might also include a REBVAL[] raw C array. +// +// To provide even greater flexibility, it allows the very first element's +// pointer in an evaluation to come from an arbitrary source. It doesn't +// have to be resident in the same sequence from which ensuing values are +// pulled, allowing a free head value (such as a FUNCTION! REBVAL in a local +// C variable) to be evaluated in combination from another source (like a +// va_list or series representing the arguments.) This avoids the cost and +// complexity of allocating a series to combine the values together. +// +// These features alone would not cover the case when REBVAL pointers that +// are originating with C source were intended to be supplied to a function +// with no evaluation. In R3-Alpha, the only way in an evaluative context +// to suppress such evaluations would be by adding elements (such as QUOTE). +// Besides the cost and labor of inserting these, the risk is that the +// intended functions to be called without evaluation, if they quoted +// arguments would then receive the QUOTE instead of the arguments. +// +// The problem was solved by adding a feature to the evaluator which was +// also opened up as a new privileged native called EVAL. EVAL's refinements +// completely encompass evaluation possibilities in R3-Alpha, but it was also +// necessary to consider cases where a value was intended to be provided +// *without* evaluation. This introduced EVAL/ONLY. +// +// The default for a DO operation is just a single DO/NEXT, where args +// to functions are evaluated (vs. quoted), and lookahead is enabled. +// + +#define DO_FLAG_NORMAL 0 + +// See Init_Endlike_Header() for why these are chosen the way they are. This +// means that the Reb_Frame->flags field can function as an implicit END for +// Reb_Frame->cell, as well as be distinguished from a REBVAL*, a REBSER*, or +// a UTF8 string. +// +#define DO_FLAG_0_IS_TRUE FLAGIT_LEFT(0) // NODE_FLAG_NODE +#define DO_FLAG_1_IS_FALSE FLAGIT_LEFT(1) // NOT(NODE_FLAG_FREE) + + +//=//// DO_FLAG_TO_END ////////////////////////////////////////////////////=// +// +// As exposed by the DO native and its /NEXT refinement, a call to the +// evaluator can either run to the finish from a position in an array or just +// do one eval. Rather than achieve execution to the end by iterative +// function calls to the /NEXT variant (as in R3-Alpha), Ren-C offers a +// controlling flag to do it from within the core evaluator as a loop. +// +// However: since running to the end follows a different code path than +// performing DO/NEXT several times, it is important to ensure they achieve +// equivalent results. There are nuances to preserve this invariant and +// especially in light of interaction with lookahead. +// +#define DO_FLAG_TO_END \ + FLAGIT_LEFT(2) + + +//=//// DO_FLAG_VA_LIST ///////////////////////////////////////////////////=// +// +// Usually VA_LIST_FLAG is enough to tell when there is a source array to +// examine or not. However, when the end is reached it is written over with +// END_FLAG and it's no longer possible to tell if there's an array available +// to inspect or not. The few cases that "need to know" are things like +// error delivery, which want to process the array after expression evaluation +// is complete. Review to see if they actually would rather know something +// else, but this is a cheap flag for now. +// +#define DO_FLAG_VA_LIST \ + FLAGIT_LEFT(3) + + +#define DO_FLAG_4_IS_TRUE FLAGIT_LEFT(4) // NODE_FLAG_END + + +//=//// DO_FLAG_TOOK_FRAME_LOCK ///////////////////////////////////////////=// +// +// While R3-Alpha permitted modifications of an array while it was being +// executed, Ren-C does not. It takes a lock if the source is not already +// read only, and sets it back when Do_Core is finished (or on errors) +// +#define DO_FLAG_TOOK_FRAME_LOCK \ + FLAGIT_LEFT(5) + + +//=//// DO_FLAG_APPLYING ///.......////////////////////////////////////////=// +// +// Used to indicate that the Do_Core code is entering a situation where the +// frame was already set up. +// +#define DO_FLAG_APPLYING \ + FLAGIT_LEFT(6) + + +#define DO_FLAG_7_IS_FALSE FLAGIT_LEFT(7) // NOT(NODE_FLAG_CELL) + + +//=//// DO_FLAG_FULFILLING_ARG ////////////////////////////////////////////=// +// +// Deferred lookback operations need to know when they are dealing with an +// argument fulfillment for a function, e.g. `summation 1 2 3 |> 100` should +// be `(summation 1 2 3) |> 100` and not `summation 1 2 (3 |> 100)`. This +// also means that `add 1 <| 2` will act as an error. +// +#define DO_FLAG_FULFILLING_ARG \ + FLAGIT_LEFT(8) + + +//=//// DO_FLAG_NO_ARGS_EVALUATE //////////////////////////////////////////=// +// +// Sometimes a DO operation has already calculated values, and does not want +// to interpret them again. e.g. the call to the function wishes to use a +// precalculated WORD! value, and not look up that word as a variable. This +// is common when calling Rebol functions from C code when the parameters are +// known, or what R3-Alpha called "APPLY/ONLY" +// +// !!! It's questionable as to whether this flag needs to exist, or if C +// code should use some kind of special out of band quoting operator to mean +// "literally this value". (The problem with using the QUOTE word or function +// in this capacity is that then functions that quote their arguments will +// receive the literal QUOTE word or function, but a variadic call from C +// could subvert that with an invisible instruction.) Currently the existence +// of this mode is leaked to Rebol users through EVAL/ONLY, which may be +// unnecessary complexity to expose. +// +#define DO_FLAG_NO_ARGS_EVALUATE \ + FLAGIT_LEFT(9) + + +//=//// DO_FLAG_NO_LOOKAHEAD //////////////////////////////////////////////=// +// +// R3-Alpha had a property such that when it was in mid-dispatch of an infix +// function, it would suppress further infix lookahead while getting the +// arguments. (e.g. with `1 + 2 * 3` it didn't want infix `+` to "look ahead" +// past the 2 to see the infix `*`) +// +// This amounted to what was basically another parameter acquisition mode for +// the right hand sides of OP!, which became named . Because tight +// parameter fulfillment added variation into the evaluator, it is being +// replaced by a strategy to use the quoted or non-quoted status of the left +// hand argument of enfixed functions to guide evaluator behavior. The worst +// case scenario will be that `1 + 2 * 3` becomes 7 instead of 9. +// +// !!! The flag will be needed as long as legacy support is required, because +// this fundamentally different mode of parameter acquisition is controlled at +// the frame level and can't be achieved (reasonably) by other means. +// +#define DO_FLAG_NO_LOOKAHEAD \ + FLAGIT_LEFT(10) + + +//=//// DO_FLAG_NATIVE_HOLD ///////////////////////////////////////////////=// +// +// When a REBNATIVE()'s code starts running, it means that the associated +// frame must consider itself locked to user code modification. This is +// because native code does not check the datatypes of its frame contents, +// and if access through the debug API were allowed to modify those contents +// out from under it then it could crash. +// +// A native may wind up running in a reified frame from the get-go (e.g. if +// there is an ADAPT that created the frame and ran user code into it prior +// to the native.) But the average case is that the native will run on a +// frame that is using the chunk stack, and has no varlist to lock. But if +// a frame reification happens after the fact, it needs to know to take a +// lock if the native code has started running. +// +// The current solution is that all natives set this flag on the frame as +// part of their entry. If they have a varlist, they will also lock that... +// but if they don't have a varlist, this flag controls the locking when +// the reification happens. +// +#define DO_FLAG_NATIVE_HOLD \ + FLAGIT_LEFT(11) + + +// Currently the rightmost two bytes of the Reb_Frame->flags are not used, +// so the flags could theoretically go up to 31. It could hold something +// like the ->eval_type, but performance is probably better to put such +// information in a platform aligned position of the frame. +// +#if defined(__cplusplus) && (__cplusplus >= 201103L) + static_assert(11 < 32, "DO_FLAG_XXX too high"); +#endif + + + +//=////////////////////////////////////////////////////////////////////////=// +// +// DO INDEX OR FLAG (a.k.a. "INDEXOR") +// +//=////////////////////////////////////////////////////////////////////////=// +// +// * END_FLAG if end of series prohibited a full evaluation +// +// * THROWN_FLAG if the output is THROWN()--you MUST check! +// +// * ...or the next index position where one might continue evaluation +// +// ===========================((( IMPORTANT )))============================== +// +// The THROWN_FLAG means your value does not represent a directly +// usable value, so you MUST check for it. It signifies getting +// back a THROWN()--see notes in sys-value.h about what that means. +// If you don't know how to handle it, then at least do: +// +// fail (Error_No_Catch_For_Throw(out)); +// +// If you *do* handle it, be aware it's a throw label with +// VALUE_FLAG_THROWN set in its header, and shouldn't leak to the +// rest of the system. +// +// =========================================================================== +// +// Note that THROWN() is not an indicator of an error, rather something that +// ordinary language constructs might meaningfully want to process as they +// bubble up the stack. Some examples would be BREAK, RETURN, and QUIT. +// +// Errors are handled with a different mechanism using longjmp(). So if an +// actual error happened during the DO then there wouldn't even *BE* a return +// value...because the function call would never return! See PUSH_TRAP() +// and fail() for more information. +// + + +#define IS_KIND_INERT(k) \ + LOGICAL((k) >= REB_BLOCK) + + +union Reb_Frame_Source { + REBARR *array; + va_list *vaptr; +}; + +// NOTE: The ordering of the fields in `Reb_Frame` are specifically done so +// as to accomplish correct 64-bit alignment of pointers on 64-bit systems. +// +// Because performance in the core evaluator loop is system-critical, this +// uses full platform `int`s instead of REBCNTs. +// +// If modifying the structure, be sensitive to this issue--and that the +// layout of this structure is mirrored in Ren-Cpp. +// +struct Reb_Frame { + // + // `cell` + // + // * This is where the EVAL instruction stores the temporary item that it + // splices into the evaluator feed, e.g. for `eval (first [x:]) 10 + 20` + // would be the storage for the `x:` SET-WORD! during the addition. + // + // * While a function is running, it is free to use it as a GC-safe spot, + // which is also implicitly terminated. See D_CELL. + // + REBVAL cell; + + // `flags` + // + // These are DO_FLAG_XXX or'd together--see their documentation above. + // A Reb_Header is used so that it can implicitly terminate `cell`, + // giving natives an enumerable single-cell slot if they need it. + // See Init_Endlike_Header() + // + struct Reb_Header flags; + + // `prior` + // + // The prior call frame (may be NULL if this is the topmost stack call). + // + // !!! Should there always be a known "top stack level" so prior does + // not ever have to be tested for NULL from within Do_Core? + // + struct Reb_Frame *prior; + + // `dsp_orig` + // + // The data stack pointer captured on entry to the evaluation. It is used + // by debug checks to make sure the data stack stays balanced after each + // sub-operation. It's also used to measure how many refinements have + // been pushed to the data stack by a path evaluation. + // + REBUPT dsp_orig; // type is REBDSP, but enforce alignment here + + // `out` + // + // This is where to write the result of the evaluation. It should not be + // in "movable" memory, hence not in a series data array. Often it is + // used as an intermediate free location to do calculations en route to + // a final result, due to being GC-safe during function evaluation. + // + REBVAL *out; + + // `source.array`, `source.vaptr` + // + // This is the source from which new values will be fetched. In addition + // to working with an array, it is also possible to feed the evaluator + // arbitrary REBVAL*s through a variable argument list on the C stack. + // This means no array needs to be dynamically allocated (though some + // conditions require the va_list to be converted to an array, see notes + // on Reify_Va_To_Array_In_Frame().) + // + union Reb_Frame_Source source; + + // `specifier` + // + // This is used for relatively bound words to be looked up to become + // specific. Typically the specifier is extracted from the payload of the + // ANY-ARRAY! value that provided the source.array for the call to DO. + // It may also be NULL if it is known that there are no relatively bound + // words that will be encountered from the source--as in va_list calls. + // + REBSPC *specifier; + + // `value` + // + // This is the value currently being processed. Callers pass in the + // first value pointer...which for any successive evaluations will be + // updated via picking from `array` based on `index`. But having the + // caller pass in the initial value gives the *option* of that value + // not being resident in the series. + // + // (Hence if one has the series `[[a b c] [d e]]` it would be possible to + // have an independent path value `append/only` and NOT insert it in the + // series, yet get the effect of `append/only [a b c] [d e]`. This only + // works for one value, but is a convenient no-cost trick for apply-like + // situations...as insertions usually have to "slide down" the values in + // the series and may also need to perform alloc/free/copy to expand.) + // + // !!! Review impacts on debugging; e.g. a debug mode should hold onto + // the initial value in order to display full error messages. + // + const RELVAL *value; + + // `index` + // + // This holds the index of the *next* item in the array to fetch as + // f->value for processing. It's invalid if the frame is for a C va_list. + // + REBUPT index; + + // `expr_index` + // + // The error reporting machinery doesn't want where `index` is right now, + // but where it was at the beginning of a single DO/NEXT step. + // + REBUPT expr_index; + + // `eval_type` + // + // This is the enumerated type upon which the evaluator's main switch + // statement is driven, to indicate what the frame is actually doing. + // e.g. REB_FUNCTION means "running a function". + // + // It may not always tell the whole story due to frame reuse--a running + // state may have stored enough information to not worry about a recursion + // overwriting it. See Do_Next_Mid_Frame_Throws() for that case. + // + // Additionally, the actual dispatch may not have started, so if a fail() + // or other operation occurs it may not be able to assume that eval_type + // of REB_FUNCTION implies that the arguments have been pushed yet. + // See Is_Any_Function_Frame() for notes on this detection. + // + enum Reb_Kind eval_type; + + // `gotten` + // + // There is a lookahead step to see if the next item in an array is a + // WORD!. If so it is checked to see if that word is a "lookback word" + // (e.g. one that was SET/LOOKBACK to serve as an infix function). + // Performing that lookup has the same cost as getting the variable value. + // Considering that the value will need to be used anyway--infix or not-- + // the pointer is held in this field for WORD!s (and sometimes FUNCTION!) + // + // This carries a risk if a DO_NEXT is performed--followed by something + // that changes variables or the array--followed by another DO_NEXT. + // There is an assert to check this, and clients wishing to be robust + // across this (and other modifications) need to use the INDEXOR-based API. + // + const REBVAL *gotten; + + // `pending` + // + // Mechanically speaking, running an EVAL has to overwrite `value` from + // the natural pre-fetching course, so that the evaluated value can be + // simulated as living in the line of execution. Because fetching moves + // forward only, we'd lose the next value if we didn't save it somewhere. + // + // This pointer saves the prefetched value that eval overwrites, and + // by virtue of not being NULL signals to just use the value on the + // next fetch instead of fetching again. + // + const RELVAL *pending; + + // `phase` and `original` + // + // If a function call is currently in effect, `phase` holds a pointer to + // the function being run. Because functions are identified and passed + // by a platform pointer as their paramlist REBSER*, you must use + // `FUNC_VALUE(c->phase)` to get a pointer to a canon REBVAL representing + // that function (to examine its function flags, for instance). + // + // Compositions of functions (adaptations, specializations, hijacks, etc) + // update `f->phase` in their dispatcher and then signal to resume the + // evaluation in that same frame in some way. The `original` function + // + REBFUN *original; + REBFUN *phase; + + // `binding` + // + // A REBFUN* alone is not enough to fully specify a function, because + // it may be an "archetype". For instance, the archetypal RETURN native + // doesn't have enough specific information in it to know *which* function + // to exit. The additional pointer of context is binding, and it is + // extracted from the function REBVAL. + // + REBARR *binding; // either a varlist of a FRAME! or function paramlist + + // `label` + // + // Functions don't have "names", though they can be assigned to words. + // The evaluator only enforces that the symbol be set during function + // calls--in the release build, it is allowed to be garbage otherwise. + // + REBSTR *label; + + // `varlist` + // + // For functions with "indefinite extent", the varlist is the CTX_VARLIST + // of a FRAME! context in which the function's arguments live. It is + // also possible for this varlist to come into existence even for functions + // like natives, if the frame's context is "reified" (e.g. by the debugger) + // If neither of these conditions are true, it will be NULL + // + // This can contain END markers at any position during arg fulfillment, + // and this means it cannot have a MANAGE_ARRAY call until that is over. + // + REBARR *varlist; + + // `param` + // + // We use the convention that "param" refers to the TYPESET! (plus symbol) + // from the spec of the function--a.k.a. the "formal argument". This + // pointer is moved in step with `arg` during argument fulfillment. + // + // (Note: It is const because we don't want to be changing the params, + // but also because it is used as a temporary to store value if it is + // advanced but we'd like to hold the old one...this makes it important + // to protect it from GC if we have advanced beyond as well!) + // + // Made relative just to have another RELVAL on hand. + // + const RELVAL *param; + + // `args_head` + // + // For functions without "indefinite extent", the invocation arguments are + // stored in the "chunk stack", where allocations are fast, address stable, + // and implicitly terminated. If a function has indefinite extent, this + // will be set to NULL. + // + // This can contain END markers at any position during arg fulfillment, + // but must all be non-END when the function actually runs. + // + // If a function is indefinite extent, this just points to the front of + // the head of varlist. + // + REBVAL *args_head; + + // `arg` + // + // "arg" is the "actual argument"...which holds the pointer to the + // REBVAL slot in the `arglist` for that corresponding `param`. These + // are moved in sync during parameter fulfillment. + // + // While a function is running, `arg` is a cache to the data pointer for + // arglist. It is used by the macros ARG() and PARAM()...which index + // by integer constants and may be used several times. Avoiding the + // extra indirection can be beneficial. + // + REBVAL *arg; + + // `special` + // + // The specialized argument parallels arg if non-NULL, and contains the + // value to substitute in the case of a specialized call. It is END + // if no specialization in effect, and parallels arg (so it may be + // incremented on a common code path) if arguments are just being checked + // vs. fulfilled. + // + REBVAL *special; + + // `refine` + // + // During parameter fulfillment, this might point to the `arg` slot + // of a refinement which is having its arguments processed. Or it may + // point to another *read-only* value whose content signals information + // about how arguments should be handled. The specific address of the + // value can be used to test without typing, but then can also be + // checked with conditional truth and falsehood. + // + // * If VOID_CELL, then refinements are being skipped and the arguments + // that follow should not be written to. + // + // * If BLANK_VALUE, this is an arg to a refinement that was not used in + // the invocation. No consumption should be performed, arguments should + // be written as unset, and any non-unset specializations of arguments + // should trigger an error. + // + // * If FALSE_VALUE, this is an arg to a refinement that was used in the + // invocation but has been *revoked*. It still consumes expressions + // from the callsite for each remaining argument, but those expressions + // must not evaluate to any value. + // + // * If IS_TRUE() the refinement is active but revokable. So if evaluation + // produces no value, `refine` must be mutated to be FALSE. + // + // * If EMPTY_BLOCK, it's an ordinary arg...and not a refinement. It will + // be evaluated normally but is not involved with revocation. + // + // * If EMPTY_STRING, the evaluator's next argument fulfillment is the + // left-hand argument of a lookback operation. After that fulfillment, + // it will be transitioned to EMPTY_BLOCK. + // + // Because of how this lays out, IS_CONDITIONAL_TRUE() can be used to + // determine if an argument should be type checked normally...while + // IS_CONDITIONAL_FALSE() means that the arg's bits must be set to void. + // + REBVAL *refine; + REBOOL doing_pickups; // want to encode + +#if !defined(NDEBUG) + // + // `label_debug` [DEBUG] + // + // Knowing the label symbol is not as handy as knowing the actual string + // of the function this call represents (if any). It is in UTF8 format, + // and cast to `char*` to help debuggers that have trouble with REBYTE. + // + const char *label_debug; + + // `file_debug` [DEBUG] + // + // An emerging feature in the system is the ability to connect user-seen + // series to a file and line number associated with their creation, + // either their source code or some trace back to the code that generated + // them. As the feature gets better, it will certainly be useful to be + // able to quickly see the information in the debugger for f->source. + // + const char *file_debug; + int line_debug; + + // `kind_debug` [DEBUG] + // + // The fetching mechanics cache the type of f->value + // + enum Reb_Kind kind_debug; + + // `do_count_debug` [DEBUG] + // + // The `do_count` represents the expression evaluation "tick" where the + // Reb_Frame is starting its processing. This is helpful for setting + // breakpoints on certain ticks in reproducible situations. + // + REBUPT do_count_debug; // !!! Should this be available in release builds? + + // `state_debug` [DEBUG] + // + // Debug reuses PUSH_TRAP's snapshotting to check for leaks at each stack + // level. It can also be made to use a more aggresive leak check at every + // evaluator step--see BALANCE_CHECK_EVERY_EVALUATION_STEP. + // + struct Reb_State state_debug; +#endif +}; + + +// It is more pleasant to have a uniform way of speaking of frames by pointer, +// so this macro sets that up for you, the same way DECLARE_LOCAL does. The +// optimizer should eliminate the extra pointer. +// +#define DECLARE_FRAME(name) \ + REBFRM name##struct; \ + REBFRM * const name = &name##struct; \ + Prep_Global_Cell(&name->cell) diff --git a/src/include/sys-rebnod.h b/src/include/sys-rebnod.h new file mode 100644 index 0000000000..2f448b030d --- /dev/null +++ b/src/include/sys-rebnod.h @@ -0,0 +1,454 @@ +// +// File: %sys-rebnod.h +// Summary: {Definitions for the Rebol_Header-having "superclass" structure} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// In order to implement several "tricks", the first pointer-size slots of +// many datatypes is a `Reb_Header` structure. The bit layout of this header +// is chosen in such a way that not only can Rebol value pointers (REBVAL*) +// be distinguished from Rebol series pointers (REBSER*), but these can be +// discerned from a valid UTF-8 string just by looking at the first byte. +// +// On a semi-superficial level, this permits a kind of dynamic polymorphism, +// such as that used by panic(): +// +// REBVAL *value = ...; +// panic (value); // can tell this is a value +// +// REBSER *series = ...; +// panic (series) // can tell this is a series +// +// const char *utf8 = ...; +// panic (utf8); // can tell this is UTF-8 data (not a series or value) +// +// But a more compelling case is the planned usage through the API, so that +// variadic combinations of strings and values can be intermixed, as in: +// +// rebDo("[", "poke", series, "1", value, "]") +// +// Internally, the ability to discern these types helps certain structures or +// arrangements from having to find a place to store a kind of "flavor" bit +// for a stored pointer's type. They can just check the first byte instead. +// +// For lack of a better name, the generic type covering the superclass is +// called a "Rebol Node". +// + + +//=////////////////////////////////////////////////////////////////////////=// +// +// NODE HEADER a.k.a `struct Reb_Header` (for REBVAL and REBSER uses) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Assignments to bits and fields in the header are done through a native +// platform-sized integer...while still being able to control the underlying +// ordering of those bits in memory. See FLAGIT_LEFT() in %reb-c.h for how +// this is achieved. +// +// This control allows the leftmost byte of a Rebol header (the one you'd +// get by casting REBVAL* to an unsigned char*) to always start with the bit +// pattern `10`. This pattern corresponds to what UTF-8 calls "continuation +// bytes", which may never legally start a UTF-8 string: +// +// https://en.wikipedia.org/wiki/UTF-8#Codepage_layout +// +// There are also applications of Reb_Header as an "implicit terminator". +// Such header patterns don't actually start valid REBNODs, but have a bit +// pattern able to signal the IS_END() test for REBVAL. See notes on +// NODE_FLAG_END and NODE_FLAG_CELL. +// + +struct Reb_Header { + // + // Uses REBUPT which is 32-bits on 32 bit platforms and 64-bits on 64 bit + // machines. Note the numbers and layout in the headers will not be + // directly comparable across architectures. + // + // !!! A clever future application of the 32 unused header bits on 64-bit + // architectures might be able to add optimization or instrumentation + // abilities as a bonus. + // + REBUPT bits; +}; + +enum Reb_Pointer_Detect { + DETECTED_AS_UTF8 = 0, + + DETECTED_AS_SERIES = 1, + DETECTED_AS_FREED_SERIES = 2, + + DETECTED_AS_VALUE = 3, + DETECTED_AS_END = 4, // may be a cell, or made with Init_Endlike_Header() + DETECTED_AS_TRASH_CELL = 5 +}; + + +//=////////////////////////////////////////////////////////////////////////=// +// +// NODE_FLAG_NODE (leftmost bit) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// For the sake of simplicity, the leftmost bit in a node is always one. This +// is because every UTF-8 string starting with a bit pattern 10xxxxxxx in the +// first byte is invalid. +// +// Warning: Previous attempts to multiplex this with an information-bearing +// bit were tricky, and wound up ultimately paying for a fixed bit in some +// other situations. Better to sacrifice the bit and keep it straightforward. +// +#define NODE_FLAG_NODE \ + FLAGIT_LEFT(0) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// NODE_FLAG_FREE (second-leftmost bit) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The second-leftmost bit will be 0 for all Reb_Header in the system that +// are "valid". This completes the plan of making sure all REBVAL and REBSER +// that are usable will start with the bit pattern 10xxxxxx, hence not be +// confused with a string...since that always indicates an invalid leading +// byte in UTF-8. +// +// The exception are freed nodes, but they use 11000000 and 110000001 for +// freed REBSER nodes and "freed" value nodes (trash). These are the bytes +// 192 and 193, which are specifically illegal in any UTF8 sequence. So +// even these cases may be safely distinguished from strings. See the +// NODE_FLAG_CELL for why it is chosen to be that 8th bit. +// +#define NODE_FLAG_FREE \ + FLAGIT_LEFT(1) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// NODE_FLAG_MANAGED (third-leftmost bit) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The GC-managed bit is used on series to indicate that its lifetime is +// controlled by the garbage collector. If this bit is not set, then it is +// still manually managed...and during the GC's sweeping phase the simple fact +// that it isn't NODE_FLAG_MARKED won't be enough to consider it for freeing. +// +// See MANAGE_SERIES for details on the lifecycle of a series (how it starts +// out manually managed, and then must either become managed or be freed +// before the evaluation that created it ends). +// +#define NODE_FLAG_MANAGED \ + FLAGIT_LEFT(2) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// NODE_FLAG_MARKED (fourth-leftmost bit) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This flag is used by the mark-and-sweep of the garbage collector, and +// should not be referenced outside of %m-gc.c. +// +// See `SERIES_INFO_BLACK` for a generic bit available to other routines +// that wish to have an arbitrary marker on series (for things like +// recursion avoidance in algorithms). +// +#define NODE_FLAG_MARKED \ + FLAGIT_LEFT(3) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// NODE_FLAG_END (fifth-leftmost bit) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// If set, it means this header should signal the termination of an array +// of REBVAL, as in `for (; NOT_END(value); ++value) {}` loops. In this +// sense it means the header is functioning much like a null-terminator for +// C strings. +// +// *** This bit being set does not necessarily mean the header is sitting at +// the head of a full REBVAL-sized slot! *** +// +// Some data structures punctuate arrays of REBVALs with a Reb_Header that +// has the NODE_FLAG_END bit set, and the NODE_FLAG_CELL bit clear. This +// functions fine as the terminator for a finite number of REBVAL cells, but +// can only be read with IS_END() with no other operations legal. +// +// It's only valid to overwrite end markers when NODE_FLAG_CELL is set. +// +#define NODE_FLAG_END \ + FLAGIT_LEFT(4) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// NODE_FLAG_ROOT (sixth-leftmost bit) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This indicates the node should be treated as a root for GC purposes. It +// only means anything on a REBVAL if that REBVAL happens to live in the key +// slot of a paired REBSER--it should not generally be set otherwise. +// +// !!! Review the implications of this flag "leaking" if a key is ever bit +// copied out of a pairing that uses it. It might not be a problem so long +// as the key is ensured read-only, so that the bit is just noise on any +// non-key that has it...but the consequences may be more sinister. +// +#define NODE_FLAG_ROOT \ + FLAGIT_LEFT(5) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// NODE_FLAG_SPECIAL (seventh-leftmost bit) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// It's a bit of a pun to try and come up with a meaning that is shared +// between REBSER and REBVAL for this bit, But the specific desire to put the +// NODE_FLAG_CELL in eighth from the left position means it's easier to make +// this a generic node flag to keep the first byte layout knowledge here. +// +// For a REBVAL, this means THROWN. For a REBSER, this means marked as +// voids being legal. They alias this as ARRAY_FLAG_VOIDS_LEGAL and +// VALUE_FLAG_THROWN. +// +#define NODE_FLAG_SPECIAL \ + FLAGIT_LEFT(6) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// NODE_FLAG_CELL (eighth-leftmost bit) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// If this bit is set in the header, it indicates the slot the header is for +// is `sizeof(REBVAL)`. +// +// Originally it was just for the debug build, to make it safer to use the +// implementation trick of "implicit END markers". Checking NODE_FLAG_CELL +// before allowing an operation like Init_Word() to write a location +// avoided clobbering NODE_FLAG_END signals that were backed by only +// `sizeof(struct Reb_Header)`. +// +// However, in the release build it became used to distinguish "pairing" +// nodes (holders for two REBVALs in the same pool as ordinary REBSERs) +// from an ordinary REBSER node. Plain REBSERs have the cell mask clear, +// while paring values have it set. +// +// The position chosen is not random. It is picked as the 8th bit from the +// left so that freed nodes can still express a distinction between +// being a cell and not, due to 11000000 (192) and 11000001 (193) are both +// invalid UTF-8 bytes, hence these two free states are distinguishable from +// a leading byte of a string. +// +#define NODE_FLAG_CELL \ + FLAGIT_LEFT(7) + + +// v-- BEGIN GENERAL VALUE AND SERIES BITS WITH THIS INDEX + +#define GENERAL_VALUE_BIT 8 +#define GENERAL_SERIES_BIT 8 + + +// There are two special invalid bytes in UTF8 which have a leading "110" +// bit pattern, and these are used to signal the header bytes in trashed +// values...this is why NODE_FLAG_CELL is chosen at its position. +// +#define FREED_SERIES_BYTE 192 +#define TRASH_CELL_BYTE 193 + + +//=////////////////////////////////////////////////////////////////////////=// +// +// NODE STRUCTURE +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Though the name Node is used for a superclass that can be "in use" or +// "free", this is the definition of the structure for its layout when it +// has NODE_FLAG_FREE set. In that case, the memory manager will set the +// header bits to have the leftmost byte as FREED_SERIES_BYTE, and use the +// pointer slot right after the header for its linked list of free nodes. +// + +struct Reb_Node { + struct Reb_Header header; // leftmost byte FREED_SERIES_BYTE if free + + struct Reb_Node *next_if_free; // if not free, entire node is available + + // Size of a node must be a multiple of 64-bits. This is because there + // must be a baseline guarantee for node allocations to be able to know + // where 64-bit alignment boundaries are. + // + /* REBI64 payload[N];*/ +}; + +inline static REBOOL IS_FREE_NODE(void *p) { + struct Reb_Node *n = cast(struct Reb_Node*, p); + + if (NOT(n->header.bits & NODE_FLAG_FREE)) + return FALSE; + + REBYTE left_8 = LEFT_8_BITS(n->header.bits); + assert(left_8 == FREED_SERIES_BYTE || left_8 == TRASH_CELL_BYTE); + UNUSED(left_8); + return TRUE; +} + + +// +// With these definitions: +// +// struct Foo_Type { struct Reb_Header header; int x; } +// struct Foo_Type *foo = ...; +// +// struct Bar_Type { struct Reb_Header header; float x; } +// struct Bar_Type *bar = ...; +// +// This C code: +// +// foo->header.bits = 1020; +// +// ...is actually different *semantically* from this code: +// +// struct Reb_Header *alias = &foo->header; +// alias->bits = 1020; +// +// The first is considered as not possibly able to affect the header in a +// Bar_Type. It only is seen as being able to influence the header in other +// Foo_Type instances. +// +// The second case, by forcing access through a generic aliasing pointer, +// will cause the optimizer to realize all bets are off for any type which +// might contain a `struct Reb_Header`. +// +// This is an important point to know, with certain optimizations of writing +// headers through one type and then reading them through another. That +// trick is used for "implicit termination", see documentation of IS_END(). +// +// (Note that this "feature" of writing through pointers actually slows +// things down. Desire to control this behavior is why the `restrict` +// keyword exists in C99: https://en.wikipedia.org/wiki/Restrict ) +// +inline static void Init_Endlike_Header(struct Reb_Header *alias, REBUPT bits) +{ + // Endlike headers have the leading bits `10` so they don't look like a + // UTF-8 string. This makes them look like an "in use node", and they + // of course have NODE_FLAG_END set. They do not have NODE_FLAG_CELL + // set, however, which prevents value writes to them. + // + assert( + NOT(bits & ( + NODE_FLAG_NODE | NODE_FLAG_FREE | NODE_FLAG_END | NODE_FLAG_CELL + )) + ); + alias->bits = bits | NODE_FLAG_NODE | NODE_FLAG_END; +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// MEMORY ALLOCATION AND FREEING MACROS +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Rebol's internal memory management is done based on a pooled model, which +// use Alloc_Mem and Free_Mem instead of calling malloc directly. (See the +// comments on those routines for explanations of why this was done--even in +// an age of modern thread-safe allocators--due to Rebol's ability to exploit +// extra data in its pool block when a series grows.) +// +// Since Free_Mem requires the caller to pass in the size of the memory being +// freed, it can be tricky. These macros are modeled after C++'s new/delete +// and new[]/delete[], and allocations take either a type or a type and a +// length. The size calculation is done automatically, and the result is cast +// to the appropriate type. The deallocations also take a type and do the +// calculations. +// +// In a C++11 build, an extra check is done to ensure the type you pass in a +// FREE or FREE_N lines up with the type of pointer being freed. +// + +// !!! Definitions for the memory allocator generally don't need to be +// included by all clients, though currently it is necessary to indicate +// whether a "node" is to be allocated from the REBSER pool or the REBGOB +// pool. Hence, the REBPOL has to be exposed to be included in the +// function prototypes. Review this necessity when REBGOB is changed. +// +typedef struct rebol_mem_pool REBPOL; + +#define ALLOC(t) \ + cast(t *, Alloc_Mem(sizeof(t))) + +#define ALLOC_ZEROFILL(t) \ + cast(t *, memset(ALLOC(t), '\0', sizeof(t))) + +#define ALLOC_N(t,n) \ + cast(t *, Alloc_Mem(sizeof(t) * (n))) + +#define ALLOC_N_ZEROFILL(t,n) \ + cast(t *, memset(ALLOC_N(t, (n)), '\0', sizeof(t) * (n))) + +#if defined(__cplusplus) && __cplusplus >= 201103L + #define FREE(t,p) \ + do { \ + static_assert( \ + std::is_same::type>::value, \ + "mismatched FREE type" \ + ); \ + Free_Mem(p, sizeof(t)); \ + } while (0) + + #define FREE_N(t,n,p) \ + do { \ + static_assert( \ + std::is_same::type>::value, \ + "mismatched FREE_N type" \ + ); \ + Free_Mem(p, sizeof(t) * (n)); \ + } while (0) +#else + #define FREE(t,p) \ + Free_Mem((p), sizeof(t)) + + #define FREE_N(t,n,p) \ + Free_Mem((p), sizeof(t) * (n)) +#endif + +#define CLEAR(m, s) \ + memset((void*)(m), 0, s) + +#define CLEARS(m) \ + memset((void*)(m), 0, sizeof(*m)) diff --git a/src/include/sys-rebser.h b/src/include/sys-rebser.h new file mode 100644 index 0000000000..4efba18ec8 --- /dev/null +++ b/src/include/sys-rebser.h @@ -0,0 +1,743 @@ +// +// File: %sys-rebser.h +// Summary: {Structure Definition for Series (REBSER)} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This contains the struct definition for the "REBSER" struct Reb_Series. +// It is a small-ish descriptor for a series (though if the amount of data +// in the series is small enough, it is embedded into the structure itself.) +// +// Every string, block, path, etc. in Rebol has a REBSER. The implementation +// of them is reused in many places where Rebol needs a general-purpose +// dynamically growing structure. It is also used for fixed size structures +// which would like to participate in garbage collection. +// +// The REBSER is fixed-size, and is allocated as a "node" from a memory pool. +// That pool quickly grants and releases memory ranges that are sizeof(REBSER) +// without needing to use malloc() and free() for each individual allocation. +// These nodes can also be enumerated in the pool without needing the series +// to be tracked via a linked list or other structure. The garbage collector +// is one example of code that performs such an enumeration. +// +// A REBSER node pointer will remain valid as long as outstanding references +// to the series exist in values visible to the GC. On the other hand, the +// series's data pointer may be freed and reallocated to respond to the needs +// of resizing. (In the future, it may be reallocated just as an idle task +// by the GC to reclaim or optimize space.) Hence pointers into data in a +// managed series *must not be held onto across evaluations*, without +// special protection or accomodation. +// +//=//// NOTES /////////////////////////////////////////////////////////////=// +// +// * For the forward declarations of series subclasses, see %reb-defs.h +// +// * Because a series contains a union member that embeds a REBVAL directly, +// `struct Reb_Value` must be fully defined before this file can compile. +// Hence %sys-rebval.h must already be included. +// +// * For the API of operations available on REBSER types, see %sys-series.h +// +// * REBARR is a series that contains Rebol values (REBVALs). It has many +// concerns specific to special treatment and handling, in interaction with +// the garbage collector as well as handling "relative vs specific" values. +// +// * Several related types (REBFUN for function, REBCTX for context) are +// actually stylized arrays. They are laid out with special values in their +// content (e.g. at the [0] index), or by links to other series in their +// `->misc` field of the REBSER node. Hence series are the basic building +// blocks of nearly all variable-size structures in the system. +// + + +//=////////////////////////////////////////////////////////////////////////=// +// +// SERIES <
> FLAGS +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Series have two places to store bits...in the "header" and in the "info". +// The following are the SERIES_FLAG_XXX that are used in the header, while +// the SERIES_INFO_XXX flags will be found in the info. +// +// As a general rule for choosing which place to put a bit, if it may be +// interesting to test/set multiple bits at the same time, then they should +// be in the same flag group. Also, SERIES_FLAG_XXX are passed to the +// Make_Series() function, so anything that controls series creation is best +// put in there. +// +// !!! Perhaps things that don't change for the lifetime of the series should +// also prefer the header vs. info? Such separation might help with caching. +// + +//=//// ARRAY_FLAG_VOIDS_LEGAL ////////////////////////////////////////////=// +// +// Identifies arrays in which it is legal to have void elements. This is true +// for instance on reified C va_list()s which were being used for unevaluated +// applies (like R3-Alpha's APPLY/ONLY). When those va_lists need to be put +// into arrays for the purposes of GC protection, they may contain voids which +// they need to track. +// +// Note: ARRAY_FLAG_VARLIST also implies legality of voids, which +// are used to represent unset variables. +// +#define ARRAY_FLAG_VOIDS_LEGAL \ + NODE_FLAG_SPECIAL + + +//=//// SERIES_FLAG_FIXED_SIZE ////////////////////////////////////////////=// +// +// This means a series cannot be expanded or contracted. Values within the +// series are still writable (assuming it isn't otherwise locked). +// +// !!! Is there checking in all paths? Do series contractions check this? +// +// One important reason for ensuring a series is fixed size is to avoid +// the possibility of the data pointer being reallocated. This allows +// code to ignore the usual rule that it is unsafe to hold a pointer to +// a value inside the series data. +// +// !!! Strictly speaking, SERIES_FLAG_NO_RELOCATE could be different +// from fixed size... if there would be a reason to reallocate besides +// changing size (such as memory compaction). +// +#define SERIES_FLAG_FIXED_SIZE \ + FLAGIT_LEFT(GENERAL_SERIES_BIT + 0) + + +//=//// SERIES_FLAG_FILE_LINE /////////////////////////////////////////////=// +// +// The Reb_Series node has two pointers in it, ->link and ->misc, which are +// used for a variety of purposes (pointing to the keylist for an object, +// the C code that runs as the dispatcher for a function, etc.) But for +// regular source series, they can be used to store the filename and line +// number, if applicable. +// +#define SERIES_FLAG_FILE_LINE \ + FLAGIT_LEFT(GENERAL_SERIES_BIT + 1) + + +//=//// SERIES_FLAG_UTF8_STRING ///////////////////////////////////////////=// +// +// Indicates the series holds a UTF-8 encoded string. +// +// !!! Currently this is only used to store ANY-WORD! symbols, which are +// read-only and cannot be indexed into, e.g. with `next 'foo`. This is +// because UTF-8 characters are encoded at variable sizes, and the series +// indexing does not support that at this time. However, it would be nice +// if a way could be figured out to unify ANY-STRING! with ANY-WORD! somehow +// in order to implement the "UTF-8 Everywhere" manifesto: +// +// http://utf8everywhere.org/ +// +#define SERIES_FLAG_UTF8_STRING \ + FLAGIT_LEFT(GENERAL_SERIES_BIT + 2) + + +//=//// SERIES_FLAG_POWER_OF_2 ////////////////////////////////////////////=// +// +// This is set when an allocation size was rounded to a power of 2. The bit +// was introduced in Ren-C when accounting was added to make sure the system's +// notion of how much memory allocation was outstanding would balance out to +// zero by the time of exiting the interpreter. +// +// The problem was that the allocation size was measured in terms of the +// number of elements in the series. If the elements themselves were not the +// size of a power of 2, then to get an even power-of-2 size of memory +// allocated, the memory block would not be an even multiple of the element +// size. So rather than track the "actual" memory allocation size as a 32-bit +// number, a single bit flag remembering that the allocation was a power of 2 +// was enough to recreate the number to balance accounting at free time. +// +// !!! The original code which created series with items which were not a +// width of a power of 2 was in the FFI. It has been rewritten to not use +// such custom structures, but the support for this remains in case there +// was a good reason to have a non-power-of-2 size in the future. +// +// !!! ...but rationale for why series were ever allocated to a power of 2 +// should be revisited. Current conventional wisdom suggests that asking +// for the amount of memory you need and not using powers of 2 is +// generally a better idea: +// +// http://stackoverflow.com/questions/3190146/ +// +#define SERIES_FLAG_POWER_OF_2 \ + FLAGIT_LEFT(GENERAL_SERIES_BIT + 3) + + +//=//// SERIES_FLAG_ARRAY /////////////////////////////////////////////////=// +// +// Indicates that this is a series of REBVAL value cells, and suitable for +// using as the payload of an ANY-ARRAY! value. When a series carries this +// bit, then if it is also NODE_FLAG_MANAGED the garbage ollector will process +// its transitive closure to make sure all the values it contains (and the +// values its references contain) do not have series GC'd out from under them. +// +// Note: R3-Alpha used `SER_WIDE(s) == sizeof(REBVAL)` as the test for if +// something was an array. But this allows creation of series that have +// items which are incidentally the size of a REBVAL, but not actually arrays. +// +#define SERIES_FLAG_ARRAY \ + FLAGIT_LEFT(GENERAL_SERIES_BIT + 4) + + +//=//// ARRAY_FLAG_PARAMLIST //////////////////////////////////////////////=// +// +// ARRAY_FLAG_PARAMLIST indicates the array is the parameter list of a +// FUNCTION! (the first element will be a canon value of the function) +// +#define ARRAY_FLAG_PARAMLIST \ + FLAGIT_LEFT(GENERAL_SERIES_BIT + 5) + + +//=//// ARRAY_FLAG_VARLIST ////////////////////////////////////////////////=// +// +// This indicates this series represents the "varlist" of a context (which is +// interchangeable with the identity of the varlist itself). A second series +// can be reached from it via the `->misc` field in the series node, which is +// a second array known as a "keylist". +// +// See notes on REBCTX for further details about what a context is. +// +#define ARRAY_FLAG_VARLIST \ + FLAGIT_LEFT(GENERAL_SERIES_BIT + 6) + + +//=//// ARRAY_FLAG_PAIRLIST ///////////////////////////////////////////////=// +// +// Indicates that this series represents the "pairlist" of a map, so the +// series also has a hashlist linked to in the series node. +// +#define ARRAY_FLAG_PAIRLIST \ + FLAGIT_LEFT(GENERAL_SERIES_BIT + 7) + + +// ^-- STOP AT FLAGIT_LEFT(15) --^ +// +// The rightmost 16 bits of the series flags are used to store an arbitrary +// per-series-type 16 bit number. Right now, that's used by the string series +// to save their REBSYM id integer(if they have one). Note that the flags +// are flattened in kind of a wasteful way...some are mutually exclusive and +// could use the same bit, if needed. +// +#if defined(__cplusplus) && (__cplusplus >= 201103L) + static_assert(GENERAL_SERIES_BIT + 7 < 16, "SERIES_FLAG_XXX too high"); +#endif + + +//=////////////////////////////////////////////////////////////////////////=// +// +// SERIES <> BITS +// +//=////////////////////////////////////////////////////////////////////////=// +// +// See remarks above about the two places where series store bits. These +// are the info bits, which are more likely to be changed over the lifetime +// of the series--defaulting to FALSE. +// +// See Init_Endlike_Header() for why the bits are chosen the way they are. +// 4 are reserved, this means that the Reb_Series->info field can function as +// an implicit END for Reb_Series->content, as well as be distinguished from +// a REBVAL*, a REBSER*, or a UTF8 string. +// +// Review: Due to the Init_Endlike_Header trick, it might be safer with the +// aliasing to make the info contain the properties that *don't* change over +// the lifetime of the series. (?) +// + +#define SERIES_INFO_0_IS_TRUE FLAGIT_LEFT(0) // NODE_FLAG_NODE +#define SERIES_INFO_1_IS_FALSE FLAGIT_LEFT(1) // NOT(NODE_FLAG_FREE) + + +//=//// SERIES_INFO_HAS_DYNAMIC ///////////////////////////////////////////=// +// +// Indicates that this series has a dynamically allocated portion. If it does +// not, then its data pointer is the address of the embedded value inside of +// it, and that the length is stored in the rightmost byte of the header +// bits (of which this is one bit). +// +// This bit will be flipped if a series grows. (In the future it should also +// be flipped when the series shrinks, but no shrinking in the GC yet.) +// +// Note: Same bit as NODE_FLAG_MANAGED, should not be relevant. +// +#define SERIES_INFO_HAS_DYNAMIC \ + FLAGIT_LEFT(2) + + +//=//// SERIES_INFO_BLACK /////////////////////////////////////////////////=// +// +// This is a generic bit for the "coloring API", e.g. Is_Series_Black(), +// Flip_Series_White(), etc. These let native routines engage in marking +// and unmarking nodes without potentially wrecking the garbage collector by +// reusing NODE_FLAG_MARKED. Purposes could be for recursion protection or +// other features, to avoid having to make a map from REBSER to REBOOL. +// +// Note: Same bit as NODE_FLAG_MARKED, interesting but irrelevant. +// +#define SERIES_INFO_BLACK \ + FLAGIT_LEFT(3) + + +#define SERIES_INFO_4_IS_TRUE FLAGIT_LEFT(4) // NODE_FLAG_END + + +//=//// SERIES_INFO_PROTECTED /////////////////////////////////////////////=// +// +// This indicates that the user had a tempoary desire to protect a series +// size or values from modification. It is the usermode analogue of +// SERIES_INFO_FROZEN, but can be reversed. +// +// Note: There is a feature in PROTECT (VALUE_FLAG_PROTECTED) which protects +// a certain variable in a context from being changed. It is similar, but +// distinct. SERIES_INFO_PROTECTED is a protection on a series itself--which +// ends up affecting all values with that series in the payload. +// +// Note: Same bit as NODE_FLAG_ROOT, should not be relevant. +// +#define SERIES_INFO_PROTECTED \ + FLAGIT_LEFT(5) + + +//=//// SERIES_INFO_RUNNING ///////////////////////////////////////////////=// +// +// Set in the header while a DO is happening on (or a PARSE, etc.) and gives +// it a temporarily protected state. It will be released when the execution +// is finished, which distinguishes it from SERIES_INFO_FROZEN, from which it +// will never come back, as long as it lives... +// +// Note: Same bit as NODE_FLAG_SPECIAL, should not be relevant. +// +#define SERIES_INFO_RUNNING \ + FLAGIT_LEFT(6) + + +#define SERIES_INFO_7_IS_FALSE FLAGIT_LEFT(7) // NOT(NODE_FLAG_CELL) + + +//=//// SERIES_INFO_FROZEN ////////////////////////////////////////////////=// +// +// Indicates that the length or values cannot be modified...ever. It has been +// locked and will never be released from that state for its lifetime, and if +// it's an array then everything referenced beneath it is also frozen. This +// means that if a read-only copy of it is required, no copy needs to be made. +// +// (Contrast this with the temporary condition like caused by something +// like REBSER_FLAG_RUNNING or REBSER_FLAG_PROTECTED.) +// +// Note: This and the other read-only series checks are honored by some layers +// of abstraction, but if one manages to get a raw non-const pointer into a +// value in the series data...then by that point it cannot be enforced. +// +#define SERIES_INFO_FROZEN \ + FLAGIT_LEFT(8) + + +//=//// SERIES_INFO_INACCESSIBLE //////////////////////////////////////////=// +// +// Currently this used to note when a CONTEXT_INFO_STACK series has had its +// stack level popped (there's no data to lookup for words bound to it). +// +// !!! This is currently redundant with checking if a CONTEXT_INFO_STACK +// series has its `misc.f` (REBFRM) nulled out, but it means both can be +// tested at the same time with a single bit. +// +// !!! It is conceivable that there would be other cases besides frames that +// would want to expire their contents, and it's also conceivable that frames +// might want to *half* expire their contents (e.g. have a hybrid of both +// stack and dynamic values+locals). These are potential things to look at. +// +#define SERIES_INFO_INACCESSIBLE \ + FLAGIT_LEFT(9) + + +//=//// STRING_INFO_CANON /////////////////////////////////////////////////=// +// +// This is used to indicate when a SERIES_FLAG_UTF8_STRING series represents +// the canon form of a word. This doesn't mean anything special about the +// case of its letters--just that it was loaded first. Canon forms can be +// GC'd and then delegate the job of being canon to another spelling. +// +// A canon string is unique because it does not need to store a pointer to +// its canon form. So it can use the REBSER.misc field for the purpose of +// holding an index during binding. +// +#define STRING_INFO_CANON \ + FLAGIT_LEFT(10) + + +//=//// SERIES_INFO_SHARED_KEYLIST ////////////////////////////////////////=// +// +// This is indicated on the keylist array of a context when that same array +// is the keylist for another object. If this flag is set, then modifying an +// object using that keylist (such as by adding a key/value pair) will require +// that object to make its own copy. +// +// Note: This flag did not exist in R3-Alpha, so all expansions would copy-- +// even if expanding the same object by 1 item 100 times with no sharing of +// the keylist. That would make 100 copies of an arbitrary long keylist that +// the GC would have to clean up. +// +#define SERIES_INFO_SHARED_KEYLIST \ + FLAGIT_LEFT(11) + + +//=//// CONTEXT_INFO_STACK ////////////////////////////////////////////////=// +// +// This indicates that a context's varlist data lives on the stack. That +// means that when the function terminates, the data will no longer be +// accessible (so SERIES_INFO_INACCESSIBLE will be true). +// +// !!! Ultimately this flag may be unnecessary because stack-based and +// dynamic series will "hybridize" so that they may have some stack +// fields and some fields in dynamic memory. For now it's a good sanity +// check that things which should only happen to stack contexts (like becoming +// inaccessible) are checked against this flag. +// +#define CONTEXT_INFO_STACK \ + FLAGIT_LEFT(12) + + +#if !defined(NDEBUG) + //=//// SERIES_INFO_LEGACY_DEBUG //////////////////////////////////////=// + // + // This is a flag which is marked at the root set of the body of legacy + // functions. It can be used in a dynamic examination of a call to see if + // it "originates from legacy code". This is a vague concept given the + // ability to create blocks and run them--so functions like COPY would + // have to propagate the flag to make it "more accurate". But it's good + // enough for casual compatibility in many cases. + // + #define SERIES_INFO_LEGACY_DEBUG \ + FLAGIT_LEFT(13) +#endif + +// ^-- STOP AT FLAGIT_LEFT(15) --^ +// +// The rightmost 16 bits of the series info is used to store an 8 bit length +// for non-dynamic series and an 8 bit width of the series. So the info +// flags need to stop at FLAGIT_LEFT(15). +// +#if defined(__cplusplus) && (__cplusplus >= 201103L) + static_assert(13 < 16, "SERIES_INFO_XXX too high"); +#endif + + +//=////////////////////////////////////////////////////////////////////////=// +// +// SERIES NODE ("REBSER") STRUCTURE DEFINITION +// +//=////////////////////////////////////////////////////////////////////////=// +// +// A REBSER node is the size of two REBVALs, and there are 3 basic layouts +// which can be overlaid inside the node: +// +// Dynamic: [header [allocation tracking] info link misc] +// Singular: [header [REBVAL cell] info link misc] +// Pairing: [[REBVAL cell] [REBVAL cell]] +// +// `info` is not the start of a "Rebol Node" (REBNODE, e.g. either a REBSER or +// a REBVAL cell). But in the singular case it is positioned right where +// the next cell after the embedded cell *would* be. Hence the bit in the +// info corresponding to NODE_FLAG_END is set, making it conform to the +// "terminating array" pattern. To lower the risk of this implicit terminator +// being accidentally overwritten (which would corrupt link and misc), the +// bit corresponding to NODE_FLAG_CELL is clear. +// +// Singulars have widespread applications in the system, notably the +// efficient implementation of FRAME!. They also narrow the gap in overhead +// between COMPOSE [A (B) C] vs. REDUCE ['A B 'C] such that the memory cost +// of the array is nearly the same as just having another value in the array. +// +// Pair REBSERs are allocated from the REBSER pool instead of their own to +// help exchange a common "currency" of allocation size more efficiently. +// They are planned for use in the PAIR! and MAP! datatypes, and anticipated +// to play a crucial part in the API--allowing a persistent handle for a +// GC'able REBVAL and associated "meta" value (which can be used for +// reference counting or other tracking.) +// +// Most of the time, code does not need to be concerned about distinguishing +// Pair from the Dynamic and Singular layouts--because it already knows +// which kind it has. Only the GC needs to be concerned when marking +// and sweeping. +// + +struct Reb_Series_Dynamic { + // + // `data` is the "head" of the series data. It may not point directly at + // the memory location that was returned from the allocator if it has + // bias included in it. + // + REBYTE *data; + + // `len` is one past end of useful data. + // + REBCNT len; + + // `rest` is the total number of units from bias to end. Having a + // slightly weird name draws attention to the idea that it's not really + // the "capacity", just the "rest of the capacity after the bias". + // + REBCNT rest; + + // This is the 4th pointer on 32-bit platforms which could be used for + // something when a series is dynamic. Previously the bias was not + // a full REBCNT but was limited in range to 16 bits or so. This means + // 16 info bits are likely available if needed for dynamic series. + // + REBCNT bias; + +#if defined(__LP64__) || defined(__LLP64__) + // + // The Reb_Series_Dynamic is used in Reb_Series inside of a union with a + // REBVAL. On 64-bit machines this will leave one unused 32-bit slot + // (which will couple with the previous REBCNT) and one naturally aligned + // 64-bit pointer. These could be used for some enhancement that would + // be available per-dynamic-REBSER on 64-bit architectures. + // + REBCNT unused_32; + void *unused_64; +#endif +}; + + +union Reb_Series_Content { + // + // If the series does not fit into the REBSER node, then it must be + // dynamically allocated. This is the tracking structure for that + // dynamic data allocation. + // + struct Reb_Series_Dynamic dynamic; + + // If not SERIES_INFO_HAS_DYNAMIC, 0 or 1 length arrays can be held in + // the series node. This trick is accomplished via "implicit termination" + // in the ->info bits that come directly after ->content. + // + // (See NODE_FLAG_END and NODE_FLAG_CELL for how this is done.) + // + RELVAL values[1]; +}; + + +struct Reb_Series { + + // The low 2 bits in the header must be 00 if this is an "ordinary" REBSER + // node. This allows such nodes to implicitly terminate a "doubular" + // REBSER node, that is being used as storage for exactly 2 REBVALs. + // As long as there aren't two of those REBSERs sequentially in the pool, + // an unused node or a used ordinary one can terminate it. + // + // The other bit that is checked in the header is the USED bit, which is + // bit #9. This is set on all REBVALs and also in END marking headers, + // and should be set in used series nodes. + // + // The remaining bits are free, and used to hold SYM values for those + // words that have them. + // + struct Reb_Header header; + + // The `link` field is generally used for pointers to something that + // when updated, all references to this series would want to be able + // to see. + // + // This field is in the second pointer-sized slot in the REBSER node to + // push the `content` so it is 64-bit aligned on 32-bit platforms. This + // is because a REBVAL may be the actual content, and a REBVAL assumes + // it is on a 64-bit boundary to start with...in order to position its + // "payload" which might need to be 64-bit aligned as well. + // + union { + // Ordinary source series use their ->link field to point to an + // interned file name string from which the code was loaded. If a + // series was not created from a file, then the information from the + // source that was running at the time is propagated into the new + // second-generation series. + // + REBSTR *filename; + + // REBCTX types use this to point from the varlist (the object's + // values, which is the identity of the object) to the keylist. One + // reason why this is stored in the REBSER node of the varlist REBARR + // as opposed to in the REBVAL of the ANY-CONTEXT! is so that the + // keylist can be changed without needing to update all the REBVALs + // for that object. + // + // (Note: The main reason a keylist pointer needs to change--at least + // at the moment--is when an object instance is expanded, and the + // keylist needs to be disconnected from sharing with other objects.) + // + REBARR *keylist; + + // paramlists and keylists can store a "meta" object + // + REBCTX *meta; + + // For REBSTR, circularly linked list of othEr-CaSed string forms + // + REBSTR *synonym; + + // On Reb_Function body_holders, this is the specialization frame for + // a function--or NULL if none. + // + REBCTX *exemplar; + + // The MAP! datatype uses this. + // + REBSER *hashlist; + + // for STRUCT, this is a "REBFLD" array. It parallels an object's + // keylist, giving not only names of the fields in the structure but + // also the types and sizes. + // + // !!! The Atronix FFI has been gradually moved away from having its + // hooks directly into the low-level implemetation and the garbage + // collector. With the conversion of REBFLD to a REBARR instead of + // a custom C type, it is one step closer to making STRUCT! a very + // OBJECT!-like type extension. When there is a full story told on + // user-defined types, this should be excisable from the core. + // + REBARR *schema; + } link; + + union Reb_Series_Content content; + + // `info` is the information about the series which needs to be known + // even if it is not using a dynamic allocation. + // + // It is purposefully positioned in the structure directly after the + // ->content field, because it has NODE_FLAG_END set to true. Hence it + // appears to terminate an array of values if the content is not dynamic. + // Yet NODE_FLAG_CELL is set to false, so it is not a writable location + // (an "implicit terminator"). + // + // !!! Only 32-bits are used on 64-bit platforms. There could be some + // interesting added caching feature or otherwise that would use + // it, while not making any feature specifically require a 64-bit CPU. + // + struct Reb_Header info; + + // The `misc` field is an extra pointer-sized piece of data which is + // resident in the series node, and hence visible to all REBVALs that + // might be referring to the series. + // + union { + // Ordinary source series store the line number here. It probably + // could have some bits taken out of it, vs. being a full 32-bit + // integer on 32-bit platforms. + // + REBUPT line; + + // native dispatcher code, see Reb_Function's body_holder + // + REBNAT dispatcher; + + // The facade is a REBARR which is a proxy for the paramlist of the + // underlying frame which is pushed when a function is called. For + // instance, if a specialization of APPEND provides the value to + // append, that removes a parameter from the paramlist. So the + // specialization will not have the value. However, the frame that + // needs to be pushed for the call ultimately needs to have the + // value--so it must be pushed. + // + // Originally this was done just by caching the paramlist of the + // "underlying" function. However, that can be limiting if one wants + // to constrain the types or change the parameter classes. The facade + // *can* be the the paramlist of the underlying function, but it is + // not necessarily. + // + REBARR *facade; + + // If this is the varlist of the REBCTX of a FRAME! series, this is + // the Reb_Frame pointer containing the C runtime state of the frame. + // If the call corresponding to the frame is no longer on the stack, + // then this will be NULL. + // + REBFRM *f; + + // For REBSTR the canon cased form of this symbol, if it isn't canon + // itself. If it *is* a canon, then the field is free and is used + // instead for `bind_index` + // + REBSTR *canon; + + // When binding words into a context, it's necessary to keep a table + // mapping those words to indices in the context's keylist. R3-Alpha + // had a global "binding table" for the spellings of words, where + // those spellings were not garbage collected. Ren-C uses REBSERs + // to store word spellings, and then has a hash table indexing them. + // + // So the "binding table" is chosen to be indices reachable from the + // REBSER nodes of the words themselves. If it were necessary for + // multiple clients to have bindings at the same time, this could be + // done through a pointer that would "pop out" into some kind of + // linked list. For now, the binding API just demonstrates having + // up to 2 different indices in effect at once. + // + struct { + REBINT high:16; + REBINT low:16; + } bind_index; + + // some HANDLE!s use this for GC finalization + // + CLEANUP_FUNC cleaner; + + // Because a bitset can get very large, the negation state is stored + // as a boolean in the series. Since negating a bitset is intended + // to affect all values, it has to be stored somewhere that all + // REBVALs would see a change--hence the field is in the series. + // + REBOOL negated; + + // used for vectors and bitsets + // + REBCNT size; + + // For LIBRARY!, the file descriptor. This is set to NULL when the + // library is not loaded. + // + // !!! As with some other types, this may not need the optimization of + // being in the Reb_Series node--but be handled via user defined types + // + void *fd; + + // used for IMAGE! + // + // !!! The optimization by which images live in a single REBSER vs. + // actually being a class of OBJECT! with something like an ordinary + // PAIR! for its size is superfluous, and would be excised when it + // is possible to make images a user-defined type. + // + struct { + REBCNT wide:16; + REBCNT high:16; + } area; + } misc; + +#if !defined(NDEBUG) + int *guard; // intentionally alloc'd and freed for use by Panic_Series + REBUPT do_count; // also maintains sizeof(REBSER) % sizeof(REBI64) == 0 +#endif +}; diff --git a/src/include/sys-rebval.h b/src/include/sys-rebval.h new file mode 100644 index 0000000000..145556dd53 --- /dev/null +++ b/src/include/sys-rebval.h @@ -0,0 +1,934 @@ +// +// File: %sys-rebval.h +// Summary: {Definitions for the Rebol Boxed Value Struct (REBVAL)} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// REBVAL is the structure/union for all Rebol values. It's designed to be +// four C pointers in size (so 16 bytes on 32-bit platforms and 32 bytes +// on 64-bit platforms). Operation will be most efficient with those sizes, +// and there are checks on boot to ensure that `sizeof(REBVAL)` is the +// correct value for the platform. But from a mechanical standpoint, the +// system should be *able* to work even if the size is different. +// +// Of the four 32-or-64-bit slots that each value has, the first is used for +// the value's "Header". This includes the data type, such as REB_INTEGER, +// REB_BLOCK, REB_STRING, etc. Then there are flags which are for general +// purposes that could apply equally well to any type of value (including +// whether the value should have a new-line after it when molded out inside +// of a block). Followed by that are bits which are custom to each type (for +// instance whether a key in an object is hidden or not). +// +// Obviously, an arbitrary long string won't fit into the remaining 3*32 bits, +// or even 3*64 bits! You can fit the data for an INTEGER or DECIMAL in that +// (at least until they become arbitrary precision) but it's not enough for +// a generic BLOCK! or a FUNCTION! (for instance). So the remaining bits +// often will point to one or more Rebol "nodes" (see %sys-series.h for an +// explanation of REBSER, REBARR, REBCTX, and REBMAP.) +// +// So the next part of the structure is the "Extra". This is the size of one +// pointer, which sits immediately after the header (that's also the size of +// one pointer). +// +// This sets things up for the "Payload"--which is the size of two pointers. +// It is broken into a separate structure at this position so that on 32-bit +// platforms, it can be aligned on a 64-bit boundary (assuming the REBVAL's +// starting pointer was aligned on a 64-bit boundary to start with). This is +// important for 64-bit value processing on 32-bit platforms, which will +// either be slow or crash if reads of 64-bit floating points/etc. are done +// on unaligned locations. +// +//=//// NOTES /////////////////////////////////////////////////////////////=// +// +// * Forward declarations are in %reb-defs.h +// +// * See %sys-rebnod.h for an explanation of FLAGIT_LEFT. This file defines +// those flags which are common to every value of every type. Due to their +// scarcity, they are chosen carefully. +// + + +//=////////////////////////////////////////////////////////////////////////=// +// +// VALUE_FLAG_THROWN +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This is how a REBVAL signals that it is a "throw" (e.g. a RETURN, BREAK, +// CONTINUE or generic THROW signal). +// +// The bit being set does not mean the cell contains the thrown quantity +// (e.g. it would not be the `1020` in `throw 1020`) The evaluator thread +// enters a modal "thrown state", and it's the state which holds the value. +// It must be processed (or trigger an error) before another throw occurs. +// +// What the bit actually indicates is a cell containing the "label" or "name" +// of the throw. Having the label quickly available in the slot being bubbled +// up makes it easy for recipients to decide if they are interested in throws +// of that type or not--after which they can request the thrown value. +// +// R3-Alpha code would frequently forget to check for thrown values, and +// wind up acting as if they did not happen. In addition to enforcing that +// all thrown values are handled by entering a "thrown state" for the +// interpreter, all routines that can potentially return thrown values +// have been adapted to return a boolean and adopt the XXX_Throws() +// naming convention: +// +// if (XXX_Throws()) { +// /* handling code */ +// } +// +#define VALUE_FLAG_THROWN \ + NODE_FLAG_SPECIAL + + +//=////////////////////////////////////////////////////////////////////////=// +// +// VALUE_FLAG_CONDITIONAL_FALSE +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This flag is used as a quick cache on BLANK! or LOGIC! false values. +// These are the only two values that return true from the FALSE? native +// (a.k.a. "conditionally false"). All other types are TRUE?. +// +// Because of this cached bit, LOGIC! does not need to store any data in its +// payload... its data of being true or false is already covered by this +// header bit. +// +// !!! Since tests for conditional truth or falsehood are extremely common +// (not just in IF and EITHER, but in CASE and ANY and many other constructs), +// it seems like a good optimization. But it is a cache and could be done +// with a slightly more expensive test. Given the scarcity of header bits in +// the modern codebase, this optimization may need to be sacrificed to +// reclaim the bit for a "higher purpose". +// +#define VALUE_FLAG_CONDITIONAL_FALSE \ + FLAGIT_LEFT(GENERAL_VALUE_BIT + 0) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// VALUE_FLAG_LINE +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This is a line marker bit, such that when the value is molded it will put a +// newline before the value. (The details are a little more subtle than that, +// because an ANY-PATH! could not be LOADed back if this were allowed.) +// +// The bit is set initially by what the scanner detects, and then left to the +// user's control after that. +// +// !!! The native `new-line` is used set this, which has a somewhat poor +// name considering its similarity to `newline` the line feed char. +// +#define VALUE_FLAG_LINE \ + FLAGIT_LEFT(GENERAL_VALUE_BIT + 1) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// VALUE_FLAG_RELATIVE +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This flag is used to indicate a value that needs to have a specific context +// added into it before it can have its bits copied--or used for some other +// purposes. +// +// An ANY-WORD! is relative if it refers to a local or argument of a function, +// and has its bits resident in the deep copy of that function's body. +// +// An ANY-ARRAY! in the deep copy of a function body must be relative also to +// the same function if it contains any instances of such relative words. +// +#define VALUE_FLAG_RELATIVE \ + FLAGIT_LEFT(GENERAL_VALUE_BIT + 2) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// VALUE_FLAG_UNEVALUATED +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Some functions wish to be sensitive to whether or not their argument came +// as a literal in source or as a product of an evaluation. While all values +// carry the bit, it is only guaranteed to be meaningful on arguments in +// function frames...though it is valid on any result at the moment of taking +// it from Do_Core(). +// +// It is in the negative sense because the act of requesting it is uncommon, +// e.g. from the QUOTE operator. So most Init_Blank() or other assignment +// should default to being "evaluative". +// +// !!! This concept is somewhat dodgy and experimental, but it shows promise +// in addressing problems like being able to give errors if a user writes +// something like `if [x > 2] [print "true"]` vs. `if x > 2 [print "true"]`, +// while still tolerating `item: [a b c] | if item [print "it's an item"]`. +// That has a lot of impact for the new user experience. +// +#define VALUE_FLAG_UNEVALUATED \ + FLAGIT_LEFT(GENERAL_VALUE_BIT + 3) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// VALUE_FLAG_STACK +// +//=////////////////////////////////////////////////////////////////////////=// +// +// When writing to a value cell, it is sometimes necessary to know how long +// that cell will "be alive". This is important if there is some stack-based +// transient structure in the source cell, which would need to be converted +// into something longer-lived if the destination cell will outlive it. +// +// Hence cells must be formatted to say whether they are VALUE_FLAG_STACK or +// not, before any writing can be done to them. If they are not then they +// are presumed to be indefinite lifetime (e.g. cells resident inside of an +// array managed by the garbage collector). +// +// But if a cell is marked with VALUE_FLAG_STACK, that means it is expected +// that scanning *backwards* in memory will find a specially marked REB_FRAME +// cell, which will lead to the frame to whose lifetime the cell is bound. +// +// !!! This feature is a work in progress. +// +#define VALUE_FLAG_STACK \ + FLAGIT_LEFT(GENERAL_VALUE_BIT + 4) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// VALUE_FLAG_ENFIXED +// +//=////////////////////////////////////////////////////////////////////////=// +// +// In R3-Alpha and Rebol2, there was a special kind of function known as an +// OP! which would acquire its first argument from the left hand side. In +// Ren-C, there is only one kind of function, but it's possible to tag a +// particular function value cell in a context as being "enfixed", hence it +// will acquire its first argument from the left. +// +// This bit is not copied by Move_Value. As a result, if you say something +// like `foo: :+`, foo will contain the non-enfixed form of the function. +// +// !!! The feature of not carrying over enfixedness in assignment was designed +// as part of the "OneFunction" initiative, to try and make it so that when +// something like a SORT function was passed a comparator, it would not have +// to worry about that function being infix or not. However, the addition of +// the parameter convention throws in a potential wrench to the idea +// that callees can somehow ignore variances in how functions process their +// arguments. It may be that this should be a function flag, and carried +// over normally...but conservatively the feature is implemented like this. +// + +#define VALUE_FLAG_ENFIXED \ + FLAGIT_LEFT(GENERAL_VALUE_BIT + 5) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// VALUE_FLAG_PROTECTED +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Values can carry a user-level protection bit. The bit is not copied by +// Move_Value(), and hence reading a protected value and writing it to +// another location will not propagate the protectedness from the original +// value to the copy. +// + +#define VALUE_FLAG_PROTECTED \ + FLAGIT_LEFT(GENERAL_VALUE_BIT + 6) + + +// v-- BEGIN TYPE SPECIFIC BITS HERE + + +#define TYPE_SPECIFIC_BIT \ + (GENERAL_VALUE_BIT + 7) + + +// Technically speaking, this only needs to use 6 bits of the rightmost byte +// to store the type. So using a full byte wastes 2 bits. However, the +// performance advantage of not needing to mask to do VAL_TYPE() is worth +// it...also there may be a use for 256 types (even though the type bitsets +// are only 64-bits at the moment) +// +#define HEADERIZE_KIND(kind) \ + FLAGBYTE_RIGHT(kind) + + + +//=////////////////////////////////////////////////////////////////////////=// +// +// TRACK payload (not a value type, only in DEBUG) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// `struct Reb_Track` is the value payload in debug builds for any REBVAL +// whose VAL_TYPE() doesn't need any information beyond the header. This +// offers a chance to inject some information into the payload to help +// know where the value originated. It is used by voids (and void trash), +// NONE!, LOGIC!, and BAR!. +// +// In addition to the file and line number where the assignment was made, +// the "tick count" of the DO loop is also saved. This means that it can +// be possible in a repro case to find out which evaluation step produced +// the value--and at what place in the source. Repro cases can be set to +// break on that tick count, if it is deterministic. +// + +#if !defined(NDEBUG) + struct Reb_Track { + const char *filename; + int line; + }; +#endif + +struct Reb_Datatype { + enum Reb_Kind kind; + REBARR *spec; +}; + +// !!! In R3-alpha, the money type was implemented under a type called "deci". +// The payload for a deci was more than 64 bits in size, which meant it had +// to be split across the separated union components in Ren-C. (The 64-bit +// aligned "payload" and 32-bit aligned "extra" were broken out independently, +// so that setting one union member would not disengage the other.) + +struct Reb_Money { + unsigned m1:32; /* significand, continuation */ + unsigned m2:23; /* significand, highest part */ + unsigned s:1; /* sign, 0 means nonnegative, 1 means nonpositive */ + int e:8; /* exponent */ +}; + +typedef struct reb_ymdz { +#ifdef ENDIAN_LITTLE + REBINT zone:7; // +/-15:00 res: 0:15 + REBCNT day:5; + REBCNT month:4; + REBCNT year:16; +#else + REBCNT year:16; + REBCNT month:4; + REBCNT day:5; + REBINT zone:7; // +/-15:00 res: 0:15 +#endif +} REBYMD; + +typedef union reb_date { + REBYMD date; + REBCNT bits; +} REBDAT; + +struct Reb_Time { + REBI64 nanoseconds; +}; + +typedef struct Reb_Tuple { + REBYTE tuple[8]; +} REBTUP; + + +struct Reb_Any_Series { + // + // `series` represents the actual physical underlying data, which is + // essentially a vector of equal-sized items. The length of the item + // (the series "width") is kept within the REBSER abstraction. See the + // file %sys-series.h for notes. + // + REBSER *series; + + // `index` is the 0-based position into the series represented by this + // ANY-VALUE! (so if it is 0 then that means a Rebol index of 1). + // + // It is possible that the index could be to a point beyond the range of + // the series. This is intrinsic, because the series can be modified + // through other values and not update the others referring to it. Hence + // VAL_INDEX() must be checked, or the routine called with it must. + // + // !!! Review that it doesn't seem like these checks are being done + // in a systemic way. VAL_LEN_AT() bounds the length at the index + // position by the physical length, but VAL_ARRAY_AT() doesn't check. + // + REBCNT index; +}; + +struct Reb_Typeset { + REBU64 bits; // One bit for each DATATYPE! (use with FLAGIT_KIND) +}; + + +struct Reb_Any_Word { + // + // This is the word's non-canonized spelling. It is a UTF-8 string. + // + REBSTR *spelling; + + // Index of word in context (if word is bound, e.g. `binding` is not NULL) + // + // !!! Intended logic is that if the index is positive, then the word + // is looked for in the context's pooled memory data pointer. If the + // index is negative or 0, then it's assumed to be a stack variable, + // and looked up in the call's `stackvars` data. + // + // But now there are no examples of contexts which have both pooled + // and stack memory, and the general issue of mapping the numbers has + // not been solved. However, both pointers are available to a context + // so it's awaiting some solution for a reasonably-performing way to + // do the mapping from [1 2 3 4 5 6] to [-3 -2 -1 0 1 2] (or whatever) + // + REBINT index; +}; + + +struct Reb_Function { + // + // `paramlist` is a Rebol Array whose 1..NUM_PARAMS values are all + // TYPESET! values, with an embedded symbol (a.k.a. a "param") as well + // as other bits, including the parameter class (PARAM_CLASS). This + // is the list that is processed to produce WORDS-OF, and which is + // consulted during invocation to fulfill the arguments + // + // In addition, its [0]th element contains a FUNCTION! value which is + // self-referentially the function itself. This means that the paramlist + // can be passed around as a single pointer from which a whole REBVAL + // for the function can be found (although this value is archetypal, and + // loses the `binding` property--which must be preserved other ways) + // + // The `link.meta` field of the paramlist holds a meta object (if any) + // that describes the function. This is read by help. + // + REBARR *paramlist; + + // `body_holder` is an optimized "singular" REBSER, the size of exactly + // one value. This is because the information for a function body is an + // array in the majority of function instances, and also because it can + // standardize the native dispatcher code in the REBARR's series "misc" + // field. This gives two benefits: no need for a switch on the function's + // type to figure out the dispatcher, and also to move the dispatcher out + // of the REBVAL itself into something that can be revectored or "hooked" + // for all instances of the function. + // + // PLAIN FUNCTIONS: body is a BLOCK!, the body of the function, obviously + // NATIVES: body is "equivalent code for native" (if any) in help + // ACTIONS: body is a WORD! for the verb of the action (OPEN, APPEND, etc) + // SPECIALIZATIONS: body is a 1-element array containing a FRAME! + // CALLBACKS: body a HANDLE! (REBRIN*) + // ROUTINES: body a HANDLE! (REBRIN*) + // + // The `link.underlying` field of the body_holder may point to the + // specialization whose frame should be used to set the default values + // for the arguments during a call. Or it will point directly to the + // function whose paramlist should be used in the frame pushed. This is + // different in hijackers, adapters, and chainers. + // + REBARR *body_holder; +}; + +struct Reb_Any_Context { + // + // `varlist` is a Rebol Array that from 1..NUM_VARS contains REBVALs + // representing the stored values in the context. + // + // As with the `paramlist` of a FUNCTION!, the varlist uses the [0]th + // element specially. It stores a copy of the ANY-CONTEXT! value that + // refers to itself. + // + // The `keylist` is held in the varlist's Reb_Series.misc field, and it + // may be shared with an arbitrary number of other contexts. Changing + // the keylist involves making a copy if it is shared. + // + // REB_MODULE depends on a property stored in the "meta" miscellaneous + // field of the keylist, which is another object's-worth of data *about* + // the module's contents (e.g. the processed header) + // + REBARR *varlist; + + // A single FRAME! can go through multiple phases of evaluation, some of + // which should expose more fields than others. For instance, when you + // specialize a function that has 10 parameters so it has only 8, then + // the specialization frame should not expose the 2 that have been + // removed. It's as if the WORDS-OF the spec is shorter than the actual + // length which is used. + // + // Hence, each independent value that holds a frame must remember the + // function whose "view" it represents. This field is only applicable + // to frames, and so it could be used for something else on other types + // + // !!! Note that the binding on a FRAME! can't be used for this purpose, + // because it's already used to hold the binding of the function it + // represents. e.g. if you have a definitional return value with a + // binding, and try to MAKE FRAME! on it, the paramlist alone is not + // enough to remember which specific frame that function should exit. + // + REBFUN *phase; +}; + + +// The order in which refinements are defined in a function spec may not match +// the order in which they are mentioned on a path. As an efficiency trick, +// a word on the data stack representing a refinement usage request can be +// mutated to store the pointer to its `param` and `arg` positions, so that +// they may be returned to after the later-defined refinement has had its +// chance to take the earlier fulfillments. +// +struct Reb_Varargs { + // + // If the extra->binding of the varargs is not NULL, it represents the + // frame in which this VARARGS! was tied to a parameter. This 0-based + // offset can be used to find the param the varargs is tied to, in order + // to know whether it is quoted or not (and its name for error delivery). + // + // It can also find the arg. Similar to the param, the arg is only good + // for the lifetime of the FRAME! in extra->binding...but even less so, + // because VARARGS! can (currently) be overwritten with another value in + // the function frame at any point. Despite this, we proxy the + // VALUE_FLAG_UNEVALUATED from the last TAKE to reflect its status. + // + REBCNT param_offset; + + // Data source for the VARARGS!. This can come from a frame (and is often + // the same as extra->binding), or from an array if MAKE ARRAY! is the + // source of the variadic data. + // + REBARR *feed; +}; + + +// This is an internal type, used to memoize the location of a refinement +// which was invoked by the path but out of order from the refinement order +// in the function definition. Because these can only exist on the stack +// they are given a REB_0 type, as opposed to having their own REB_XXX type. +// +struct Reb_Pickup { + const REBVAL *param; + REBVAL *arg; +}; + + +// Handles hold a pointer and a size...which allows them to stand-in for +// a binary REBSER. +// +// Since a function pointer and a data pointer aren't necessarily the same +// size, the data has to be a union. +// +// Note that the ->extra field of the REBVAL may contain a singular REBARR +// that is leveraged for its GC-awareness. +// +struct Reb_Handle { + union { + void *pointer; + CFUNC *cfunc; + } data; + + REBUPT length; +}; + + +// Meta information in singular->link.meta +// File descriptor in singular->misc.fd +// +struct Reb_Library { + REBARR *singular; // singular array holding this library value +}; + +typedef REBARR REBLIB; + + +// The general FFI direction is to move it so that it is "baked in" less, +// and represents an instance of a generalized extension mechanism (like GOB! +// should be). On that path, a struct's internals are simplified to being +// just an array: +// +// [0] is a specification OBJECT! which contains all the information about +// the structure's layout, regardless of what offset it would find itself at +// inside of a data blob. This includes the total size, and arrays of +// field definitions...essentially, the validated spec. It also contains +// a HANDLE! which contains the FFI-type. +// +// [1] is the content BINARY!. The VAL_INDEX of the binary indicates the +// offset within the struct. +// +// As an interim step, the [0] is the ordinary struct fields series as an +// ordinary BINARY! +// +struct Reb_Struct { + REBARR *stu; // [0] is canon self value, ->misc.schema is schema + REBSER *data; // binary data series (may be shared with other structs) +}; + +struct Struct_Field; // forward decl avoids conflict in Prepare_Field_For_FFI + +typedef REBARR REBSTU; + +#include "reb-gob.h" + +struct Reb_Gob { + REBGOB *gob; + REBCNT index; +}; + + +// Reb_All is a structure type designed specifically for getting at +// the underlying bits of whichever union member is in effect inside +// the Reb_Value_Data. This is not actually legal, although if types +// line up in unions it could be possibly be made "more legal": +// +// http://stackoverflow.com/questions/11639947/ +// +struct Reb_All { + REBUPT bits[2]; +}; + + +//=////////////////////////////////////////////////////////////////////////=// +// +// VALUE CELL DEFINITION (`struct Reb_Value`) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The value is defined to have the header, "extra", and payload. Having +// the header come first is taken advantage of by the trick for allowing +// a single REBUPT-sized value (32-bit on 32 bit builds, 64-bit on 64-bit +// builds) be examined to determine if a value is an END marker or not. +// +// Conceptually speaking, one might think of the "extra" as being part of +// the payload. But it is broken out into a separate union. This is because +// the `binding` property is written using common routines for several +// different types. If the common routine picked just one of the payload +// unions to initialize, it would "disengage" the other unions. +// +// (C permits *reading* of common leading elements from another union member, +// even if that wasn't the last union used to write it. But all bets are off +// for other unions if you *write* a leading member through another one. +// For longwinded details: http://stackoverflow.com/a/11996970/211160 ) +// +// Another aspect of breaking out the "extra" is so that on 32-bit platforms, +// the starting address of the payload is on a 64-bit alignment boundary. +// See Reb_Integer, Reb_Decimal, and Reb_Typeset for examples where the 64-bit +// quantity requires things like REBDEC to have 64-bit alignment. At time of +// writing, this is necessary for the "C-to-Javascript" emscripten build to +// work. It's also likely preferred by x86. +// +// (Note: The reason why error-causing alignments were ever possible at all +// was due to a #pragma pack(4) that was used in R3-Alpha...Ren-C removed it.) +// + +union Reb_Value_Extra { + // + // The binding will be either a REBFUN (relative to a function) or a + // REBCTX (specific to a context). ARRAY_FLAG_VARLIST can be + // used to tell which it is. + // + // ANY-WORD!: binding is the word's binding + // + // ANY-ARRAY!: binding is the relativization or specifier for the REBVALs + // which can be found inside of the frame (for recursive resolution + // of ANY-WORD!s) + // + // FUNCTION!: binding is the instance data for archetypal invocation, so + // although all the RETURN instances have the same paramlist, it is + // the binding which is unique to the REBVAL specifying which to exit + // + // ANY-CONTEXT!: if a FRAME!, the binding carries the instance data from + // the function it is for. So if the frame was produced for an instance + // of RETURN, the keylist only indicates the archetype RETURN. Putting + // the binding back together can indicate the instance. + // + // VARARGS!: the binding is the frame context where the variadic parameter + // lives (or NULL if it was made with MAKE VARARGS! and hasn't been + // passed through a parameter yet). + // + REBARR *binding; + + // The remaining properties are the "leftovers" of what won't fit in the + // payload for other types. If those types have a quanitity that requires + // 64-bit alignment, then that gets the priority for being in the payload, + // with the "Extra" pointer-sized item here. + + REBSTR *key_spelling; // if typeset is key of object or function parameter + REBDAT date; // time's payload holds the nanoseconds, this is the date + REBCNT struct_offset; // offset for struct in the possibly shared series + + // !!! Biasing Ren-C to helping solve its technical problems led the + // REBEVT stucture to get split up. The "eventee" is now in the extra + // field, while the event payload is elsewhere. This brings about a long + // anticipated change where REBEVTs would need to be passed around in + // clients as REBVAL-sized entities. + // + // See also rebol_devreq->requestee + + union Reb_Eventee eventee; + + unsigned m0:32; // !!! significand, lowest part - see notes on Reb_Money + + // There are two types of HANDLE!, and one version leverages the GC-aware + // ability of a REBSER to know when no references to the handle exist and + // call a cleanup function. The GC-aware variant allocates a "singular" + // array, which is the exact size of a REBSER and carries the canon data. + // If the cheaper kind that's just raw data and no callback, this is NULL. + // + REBARR *singular; + +#if !defined(NDEBUG) + REBUPT do_count; // used by track payloads +#endif +}; + +union Reb_Value_Payload { + struct Reb_All all; + +#if !defined(NDEBUG) + struct Reb_Track track; // debug only for void/trash, BLANK!, LOGIC!, BAR! +#endif + + REBUNI character; // It's CHAR! (for now), but 'char' is a C keyword + REBI64 integer; + REBDEC decimal; + + REBVAL *pair; // actually a "pairing" pointer + struct Reb_Money money; + struct Reb_Handle handle; + struct Reb_Time time; + struct Reb_Tuple tuple; + struct Reb_Datatype datatype; + struct Reb_Typeset typeset; + + struct Reb_Library library; + struct Reb_Struct structure; // It's STRUCT!, but 'struct' is a C keyword + + struct Reb_Event event; + struct Reb_Gob gob; + + // These use `specific` or `relative` in `binding`, based on IS_RELATIVE() + + struct Reb_Any_Word any_word; + struct Reb_Any_Series any_series; + struct Reb_Function function; + struct Reb_Any_Context any_context; + struct Reb_Varargs varargs; + + // This is only used on the data stack as an internal type by the + // evaluator, in order to find where not-yet-used refinements are, with + // REB_0 (REB_0_PICKUP) as the type. + // + struct Reb_Pickup pickup; +}; + +struct Reb_Value +{ + struct Reb_Header header; + union Reb_Value_Extra extra; + union Reb_Value_Payload payload; +}; + + +//=////////////////////////////////////////////////////////////////////////=// +// +// Cell Reset and Copy Masks +// +//=////////////////////////////////////////////////////////////////////////=// +// +// It's important for operations that write to cells not to overwrite *all* +// the bits in the header, because some of those bits give information about +// the nature of the cell's storage and lifetime. Similarly, if bits are +// being copied from one cell to another, those header bits must be masked +// out to avoid corrupting the information in the target cell. +// +// !!! Future optimizations may put the integer stack level of the cell in +// the header in the unused 32 bits for the 64-bit build. That would also +// be kept in this mask. +// +// Additionally, operations that copy need to not copy any of those bits that +// are owned by the cell, plus additional bits that would be reset in the +// cell if overwritten but not copied. For now, this is why `foo: :+` does +// not make foo an enfixed operation. +// +// Note that this will clear NODE_FLAG_FREE, so it should be checked by the +// debug build before resetting. +// + +#define CELL_MASK_RESET \ + (NODE_FLAG_NODE | NODE_FLAG_CELL \ + | NODE_FLAG_MANAGED | VALUE_FLAG_STACK) + +#define CELL_MASK_COPY \ + ~(CELL_MASK_RESET \ + | VALUE_FLAG_ENFIXED | VALUE_FLAG_PROTECTED | VALUE_FLAG_UNEVALUATED) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// REBVAL ("fully specified" value) and RELVAL ("possibly relative" value) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// A relative value is the identical struct to Reb_Value, but is allowed to +// have the relative bit set. Hence a relative value pointer can point to a +// specific value, but a relative word or array cannot be pointed to by a +// plain REBVAL*. The RELVAL-vs-REBVAL distinction is purely commentary +// in the C build, but the C++ build makes REBVAL a type derived from RELVAL. +// +// RELVAL exists to help quarantine the bit patterns for relative words into +// the deep-copied-body of the function they are for. To actually look them +// up, they must be paired with a FRAME! matching the actual instance of the +// running function on the stack they correspond to. Once made specific, +// a word may then be freely copied into any REBVAL slot. +// +// In addition to ANY-WORD!, an ANY-ARRAY! can also be relative, if it is +// part of the deep-copied function body. The reason that arrays must be +// relative too is in case they contain relative words. If they do, then +// recursion into them must carry forward the resolving "specifier" pointer +// to be combined with any relative words that are seen later. +// + +#define REB_MAX_VOID REB_MAX // there is no VOID! datatype, use REB_MAX + +#ifdef __cplusplus + struct Reb_Specific_Value : public Reb_Value { + #if !defined(NDEBUG) + // + // In C++11, it is now formally legal to add constructors to types + // without interfering with their "standard layout" properties, or + // making them uncopyable with memcpy(), etc. For the rules, see: + // + // http://stackoverflow.com/a/7189821/211160 + // + // No required functionality should be implemented via the constructor + // but optional debug features can be added. + // + Reb_Specific_Value () { + } + + // The destructor checks that all REBVALs wound up with NODE_FLAG_CELL + // set on them. This would be done by DECLARE_LOCAL () if a stack + // value, and by the Make_Series() construction for SERIES_FLAG_ARRAY. + // + ~Reb_Specific_Value() { + assert(header.bits & NODE_FLAG_CELL); + + enum Reb_Kind kind = cast(enum Reb_Kind, RIGHT_8_BITS(header.bits)); + assert( + header.bits & NODE_FLAG_FREE + ? kind == REB_MAX_VOID + 1 + : kind <= REB_MAX_VOID + ); + } + + // Overwriting one REBVAL* with another REBVAL* cannot be done with + // a direct assignment, such as `*dest = *src;` Instead one is + // supposed to use `Move_Value(dest, src);` because the copying needs + // to be sensitive to the nature of the target slot. If that slot + // is at a higher stack level than the source (or persistent in an + // array) then special handling is necessary to make sure any stack + // constrained pointers are "reified" + // + // !!! Note that "= delete" only works in C++11, and can be achieved + // less clearly but still work just by making assignment and copying + // constructors private. + private: + Reb_Specific_Value (Reb_Specific_Value const & other); + void operator= (Reb_Specific_Value const &rhs); + #endif + }; +#endif + +inline static REBOOL IS_RELATIVE(const RELVAL *v) { + return LOGICAL(v->header.bits & VALUE_FLAG_RELATIVE); +} + +#if defined(__cplusplus) + // + // Take special advantage of the fact that C++ can help catch when we are + // trying to see if a REBVAL is specific or relative (it will always + // be specific, so the call is likely in error). In the C build, they + // are the same type so there will be no error. + // + REBOOL IS_RELATIVE(const REBVAL *v); +#endif + +#define IS_SPECIFIC(v) \ + NOT(IS_RELATIVE(v)) + +inline static REBFUN *VAL_RELATIVE(const RELVAL *v) { + assert(IS_RELATIVE(v)); + //assert(NOT(GET_SER_FLAG(v->extra.binding, ARRAY_FLAG_VARLIST))); + return cast(REBFUN*, v->extra.binding); +} + +inline static REBCTX *VAL_SPECIFIC_COMMON(const RELVAL *v) { + assert(IS_SPECIFIC(v)); + //assert( + // v->extra.binding == SPECIFIED + // || GET_SER_FLAG(v->extra.binding, ARRAY_FLAG_VARLIST) + //); + return cast(REBCTX*, v->extra.binding); +} + +#ifdef NDEBUG + #define VAL_SPECIFIC(v) \ + VAL_SPECIFIC_COMMON(v) +#else + #define VAL_SPECIFIC(v) \ + VAL_SPECIFIC_Debug(v) +#endif + +// When you have a RELVAL* (e.g. from a REBARR) that you "know" to be specific, +// the KNOWN macro can be used for that. Checks to make sure in debug build. +// +// Use for: "invalid conversion from 'Reb_Value*' to 'Reb_Specific_Value*'" + +inline static const REBVAL *const_KNOWN(const RELVAL *value) { + assert(IS_SPECIFIC(value)); + return cast(const REBVAL*, value); // we asserted it's actually specific +} + +inline static REBVAL *KNOWN(RELVAL *value) { + assert(IS_SPECIFIC(value)); + return cast(REBVAL*, value); // we asserted it's actually specific +} + +inline static const RELVAL *const_REL(const REBVAL *v) { + return cast(const RELVAL*, v); // cast w/input restricted to REBVAL +} + +inline static RELVAL *REL(REBVAL *v) { + return cast(RELVAL*, v); // cast w/input restricted to REBVAL +} + +#define SPECIFIED NULL + + +#ifdef NDEBUG + #define ASSERT_NO_RELATIVE(array,deep) NOOP +#else + #define ASSERT_NO_RELATIVE(array,deep) \ + Assert_No_Relative((array),(deep)) +#endif diff --git a/src/include/sys-scan.h b/src/include/sys-scan.h index e5002ad4bb..0067d6c1b7 100644 --- a/src/include/sys-scan.h +++ b/src/include/sys-scan.h @@ -1,69 +1,74 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Lexical Scanner Definitions -** Module: sys-scan.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %sys-scan.h +// Summary: "Lexical Scanner Definitions" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// -/* -** Tokens returned by the scanner. Keep in sync with boot.r strings area. -*/ -enum Value_Types { - TOKEN_EOF = 0, - TOKEN_LINE, - TOKEN_BLOCK_END, - TOKEN_PAREN_END, - TOKEN_WORD, - TOKEN_SET, - TOKEN_GET, - TOKEN_LIT, - TOKEN_NONE, // not needed - TOKEN_LOGIC, // not needed - TOKEN_INTEGER, - TOKEN_DECIMAL, - TOKEN_PERCENT, - TOKEN_MONEY, - TOKEN_TIME, - TOKEN_DATE, - TOKEN_CHAR, - TOKEN_BLOCK, - TOKEN_PAREN, - TOKEN_STRING, - TOKEN_BINARY, - TOKEN_PAIR, - TOKEN_TUPLE, - TOKEN_FILE, - TOKEN_EMAIL, - TOKEN_URL, - TOKEN_ISSUE, - TOKEN_TAG, - TOKEN_PATH, - TOKEN_REFINE, - TOKEN_CONSTRUCT, - TOKEN_MAX +// +// Tokens returned by the scanner. Keep in sync with Token_Names[]. +// +enum Reb_Token { + TOKEN_END = 0, + TOKEN_NEWLINE, + TOKEN_BLOCK_END, + TOKEN_GROUP_END, + TOKEN_WORD, + TOKEN_SET, // order matters (see KIND_OF_WORD_FROM_TOKEN) + TOKEN_GET, // ^-- same + TOKEN_LIT, // ^-- same + TOKEN_BLANK, // not needed + TOKEN_BAR, + TOKEN_LIT_BAR, + TOKEN_LOGIC, // not needed + TOKEN_INTEGER, + TOKEN_DECIMAL, + TOKEN_PERCENT, + TOKEN_MONEY, + TOKEN_TIME, + TOKEN_DATE, + TOKEN_CHAR, + TOKEN_BLOCK_BEGIN, + TOKEN_GROUP_BEGIN, + TOKEN_STRING, + TOKEN_BINARY, + TOKEN_PAIR, + TOKEN_TUPLE, + TOKEN_FILE, + TOKEN_EMAIL, + TOKEN_URL, + TOKEN_ISSUE, + TOKEN_TAG, + TOKEN_PATH, + TOKEN_REFINE, + TOKEN_CONSTRUCT, + TOKEN_MAX }; +#define KIND_OF_WORD_FROM_TOKEN(t) \ + cast(enum Reb_Kind, REB_WORD + ((t) - TOKEN_WORD)) /* ** Lexical Table Entry Encoding @@ -77,29 +82,31 @@ enum Value_Types { /* -** Delimiting Chars (encoded in the LEX_VALUE field) +** Delimiting Chars (encoded in the LEX_VALUE field) +** NOTE: Macros do make assumption that _RETURN is the last space delimiter */ enum LEX_DELIMIT_ENUM { - LEX_DELIMIT_SPACE, /* 20 space */ - LEX_DELIMIT_END_FILE, /* 00 EOF */ - LEX_DELIMIT_LINEFEED, /* 0A line-feed */ - LEX_DELIMIT_RETURN, /* 0D return */ - LEX_DELIMIT_LEFT_PAREN, /* 28 ( */ - LEX_DELIMIT_RIGHT_PAREN, /* 29 ) */ - LEX_DELIMIT_LEFT_BRACKET, /* 5B [ */ - LEX_DELIMIT_RIGHT_BRACKET, /* 5D ] */ - LEX_DELIMIT_LEFT_BRACE, /* 7B } */ - LEX_DELIMIT_RIGHT_BRACE, /* 7D } */ - LEX_DELIMIT_QUOTE, /* 22 " */ - LEX_DELIMIT_SLASH, /* 2F / - date, path, file */ - LEX_DELIMIT_SEMICOLON, /* 3B ; */ - LEX_DELIMIT_UTF8_ERROR, - LEX_DELIMIT_MAX + LEX_DELIMIT_SPACE, /* 20 space */ + LEX_DELIMIT_END, /* 00 null terminator, end of input */ + LEX_DELIMIT_LINEFEED, /* 0A line-feed */ + LEX_DELIMIT_RETURN, /* 0D return */ + LEX_DELIMIT_LEFT_PAREN, /* 28 ( */ + LEX_DELIMIT_RIGHT_PAREN, /* 29 ) */ + LEX_DELIMIT_LEFT_BRACKET, /* 5B [ */ + LEX_DELIMIT_RIGHT_BRACKET, /* 5D ] */ + LEX_DELIMIT_LEFT_BRACE, /* 7B } */ + LEX_DELIMIT_RIGHT_BRACE, /* 7D } */ + LEX_DELIMIT_DOUBLE_QUOTE, /* 22 " */ + LEX_DELIMIT_SLASH, /* 2F / - date, path, file */ + LEX_DELIMIT_SEMICOLON, /* 3B ; */ + LEX_DELIMIT_UTF8_ERROR, + LEX_DELIMIT_MAX }; /* ** General Lexical Classes (encoded in the LEX_CLASS field) +** NOTE: macros do make assumptions on the order, and that there are 4! */ enum LEX_CLASS_ENUM { LEX_CLASS_DELIMIT = 0, @@ -121,37 +128,40 @@ enum LEX_CLASS_ENUM { #define MASK_LEX_CLASS(c) (Lex_Map[(REBYTE)c] & LEX_CLASS) #define IS_LEX_SPACE(c) (!Lex_Map[(REBYTE)c]) -#define IS_LEX_ANY_SPACE(c) (Lex_Map[(REBYTE)c]<=LEX_DELIMIT_RETURN) +#define IS_LEX_ANY_SPACE(c) (Lex_Map[(REBYTE)c]<=LEX_DELIMIT_RETURN) #define IS_LEX_DELIMIT(c) (MASK_LEX_CLASS(c) == LEX_DELIMIT) #define IS_LEX_SPECIAL(c) (MASK_LEX_CLASS(c) == LEX_SPECIAL) #define IS_LEX_WORD(c) (MASK_LEX_CLASS(c) == LEX_WORD) -#define IS_LEX_NUMBER(c) (MASK_LEX_CLASS(c) == LEX_NUMBER) +// Optimization (necessary?) +#define IS_LEX_NUMBER(c) (Lex_Map[(REBYTE)c] >= LEX_NUMBER) -#define IS_LEX_AT_LEAST_SPECIAL(c) (Lex_Map[(REBYTE)c] >= LEX_SPECIAL) -#define IS_LEX_AT_LEAST_WORD(c) (Lex_Map[(REBYTE)c] >= LEX_WORD) -#define IS_LEX_AT_LEAST_NUMBER(c) (Lex_Map[(REBYTE)c] >= LEX_NUMBER) +#define IS_LEX_NOT_DELIMIT(c) (Lex_Map[(REBYTE)c] >= LEX_SPECIAL) +#define IS_LEX_WORD_OR_NUMBER(c) (Lex_Map[(REBYTE)c] >= LEX_WORD) /* ** Special Chars (encoded in the LEX_VALUE field) */ enum LEX_SPECIAL_ENUM { /* The order is important! */ - LEX_SPECIAL_AT, /* 40 @ - email */ - LEX_SPECIAL_PERCENT, /* 25 % - file name */ - LEX_SPECIAL_BACKSLASH, /* 5C \ */ - LEX_SPECIAL_COLON, /* 3A : - time, get, set */ - LEX_SPECIAL_TICK, /* 27 ' - literal */ - LEX_SPECIAL_LESSER, /* 3C < - compare or tag */ - LEX_SPECIAL_GREATER, /* 3E > - compare or end tag */ - LEX_SPECIAL_PLUS, /* 2B + - positive number */ - LEX_SPECIAL_MINUS, /* 2D - - date, negative number */ - LEX_SPECIAL_TILDE, /* 7E ~ - complement number */ - /** Any of these can follow - or ~ : */ - LEX_SPECIAL_PERIOD, /* 2E . - decimal number */ - LEX_SPECIAL_COMMA, /* 2C , - decimal number */ - LEX_SPECIAL_POUND, /* 23 # - hex number */ - LEX_SPECIAL_DOLLAR, /* 24 $ - money */ - LEX_SPECIAL_WORD, /* SPECIAL - used for word chars (for nums) */ - LEX_SPECIAL_MAX + LEX_SPECIAL_AT, /* 40 @ - email */ + LEX_SPECIAL_PERCENT, /* 25 % - file name */ + LEX_SPECIAL_BACKSLASH, /* 5C \ */ + LEX_SPECIAL_COLON, /* 3A : - time, get, set */ + LEX_SPECIAL_APOSTROPHE, /* 27 ' - literal */ + LEX_SPECIAL_LESSER, /* 3C < - compare or tag */ + LEX_SPECIAL_GREATER, /* 3E > - compare or end tag */ + LEX_SPECIAL_PLUS, /* 2B + - positive number */ + LEX_SPECIAL_MINUS, /* 2D - - date, negative number */ + LEX_SPECIAL_TILDE, /* 7E ~ - complement number */ + LEX_SPECIAL_BAR, /* 7C | - expression barrier */ + LEX_SPECIAL_BLANK, /* 5F _ - blank */ + + /** Any of these can follow - or ~ : */ + LEX_SPECIAL_PERIOD, /* 2E . - decimal number */ + LEX_SPECIAL_COMMA, /* 2C , - decimal number */ + LEX_SPECIAL_POUND, /* 23 # - hex number */ + LEX_SPECIAL_DOLLAR, /* 24 $ - money */ + LEX_SPECIAL_WORD, /* SPECIAL - used for word chars (for nums) */ + LEX_SPECIAL_MAX }; /* @@ -159,6 +169,14 @@ enum LEX_SPECIAL_ENUM { /* The order is important! */ */ #define LEX_DEFAULT (LEX_DELIMIT|LEX_DELIMIT_SPACE) /* control chars = spaces */ +// In UTF8 C0, C1, F5, and FF are invalid. Ostensibly set to default because +// it's not necessary to use a bit for a special designation, since they +// should not occur. +// +// !!! If a bit is free, should it be used for errors in the debug build? +// +#define LEX_UTFE LEX_DEFAULT + /* ** Characters not allowed in Words */ @@ -166,21 +184,21 @@ enum LEX_SPECIAL_ENUM { /* The order is important! */ LEX_FLAG(LEX_SPECIAL_PERCENT) | \ LEX_FLAG(LEX_SPECIAL_BACKSLASH) | \ LEX_FLAG(LEX_SPECIAL_COMMA) | \ - LEX_FLAG(LEX_SPECIAL_POUND) | \ - LEX_FLAG(LEX_SPECIAL_DOLLAR) | \ + LEX_FLAG(LEX_SPECIAL_POUND) | \ + LEX_FLAG(LEX_SPECIAL_DOLLAR) | \ LEX_FLAG(LEX_SPECIAL_COLON)) enum rebol_esc_codes { - // Must match Esc_Names[]! - ESC_LINE, - ESC_TAB, - ESC_PAGE, - ESC_ESCAPE, - ESC_ESC, - ESC_BACK, - ESC_DEL, - ESC_NULL, - ESC_MAX + // Must match Esc_Names[]! + ESC_LINE, + ESC_TAB, + ESC_PAGE, + ESC_ESCAPE, + ESC_ESC, + ESC_BACK, + ESC_DEL, + ESC_NULL, + ESC_MAX }; @@ -189,41 +207,93 @@ enum rebol_esc_codes { */ typedef struct rebol_scan_state { - REBYTE *begin; - REBYTE *end; - REBYTE const *limit; /* no chars after this point */ -// REBYTE const *error_id; /* id string for errors (file name or URL path) */ - REBCNT line_count; - REBYTE *head_line; // head of current line (used for errors) - REBCNT opts; - REBCNT errors; -} SCAN_STATE; + const REBYTE *begin; + const REBYTE *end; + const REBYTE *limit; /* no chars after this point */ + + REBCNT line; + const REBYTE *line_head; // head of current line (used for errors) + REBCNT start_line; + const REBYTE *start_line_head; -#define ACCEPT_TOKEN(s) ((s)->begin = (s)->end) + REBSTR *filename; -#define NOT_NEWLINE(c) ((c) && (c) != CR && (c) != LF) + REBFLGS opts; + enum Reb_Token token; +} SCAN_STATE; + +#define ANY_CR_LF_END(c) (!(c) || (c) == CR || (c) == LF) enum { - SCAN_NEXT, // load/next feature - SCAN_ONLY, // only single value (no blocks) - SCAN_RELAX, // no error throw + SCAN_NEXT, // load/next feature + SCAN_ONLY, // only single value (no blocks) + SCAN_RELAX, // no error throw + SCAN_MAX }; + +// +// MAXIMUM LENGTHS +// +// These are the maximum input lengths in bytes needed for a buffer to give +// to Scan_XXX (not including terminator?) The TO conversions from strings +// tended to hardcode the numbers, so that hardcoding is excised here to +// make it more clear what those numbers are and what their motivation might +// have been (not all were explained). +// +// (See also MAX_HEX_LEN, MAX_INT_LEN) +// + +// 30-September-10000/12:34:56.123456789AM/12:34 +#define MAX_SCAN_DATE 45 + +// The maximum length a tuple can be in characters legally for Scan_Tuple +// (should be in a better location, but just excised it for clarity. +#define MAX_SCAN_TUPLE (11 * 4 + 1) + +#define MAX_SCAN_DECIMAL 24 + +#define MAX_SCAN_MONEY 36 + +#define MAX_SCAN_TIME 30 + +#define MAX_SCAN_WORD 255 + + /* ** Externally Accessed Variables */ extern const REBYTE Lex_Map[256]; -/*********************************************************************** -** -*/ static INLINE REBYTE *Skip_To_Char(REBYTE *cp, REBYTE *ep, REBYTE chr) -/* -** Skip to the specified character but not past the end -** of the string. Return zero if the char is not found. -** -***********************************************************************/ -{ - while (cp != ep && *cp != chr) cp++; - if (*cp == chr) return cp; - return 0; + +// R3-Alpha did not support unicode codepoints higher than 0xFFFF, because +// strings were only 1 or 2 bytes per character. Future plans for Ren-C may +// use the "UTF8 everywhere" philosophy as opposed to extending this to +// strings which have more bytes. +// +// Until support for "astral plane" characters is added, this inline function +// traps large characters when strings are being scanned. If a client wishes +// to handle them explicitly, use Back_Scan_UTF8_Char_Core(). +// +// Though the machinery can decode a UTF32 32-bit codepoint, the interface +// uses a 16-bit REBUNI (due to that being all that Rebol supports at this +// time). If a codepoint that won't fit in 16-bits is found, it will raise +// an error vs. return NULL. This makes it clear that the problem is not +// with the data itself being malformed (the usual assumption of callers) +// but rather a limit of the implementation. +// +inline static const REBYTE *Back_Scan_UTF8_Char( + REBUNI *out, + const REBYTE *bp, + REBCNT *len +){ + unsigned long ch; // "UTF32" is defined as unsigned long + const REBYTE *bp_new = Back_Scan_UTF8_Char_Core(&ch, bp, len); + if (bp_new != NULL && ch > 0xFFFF) { + DECLARE_LOCAL (num); + Init_Integer(num, cast(REBI64, ch)); + fail (Error_Codepoint_Too_High_Raw(num)); + } + *out = cast(REBUNI, ch); + return bp_new; } diff --git a/src/include/sys-series.h b/src/include/sys-series.h new file mode 100644 index 0000000000..fcdb069ef4 --- /dev/null +++ b/src/include/sys-series.h @@ -0,0 +1,681 @@ +// +// File: %sys-series.h +// Summary: {Definitions for Series (REBSER) plus Array, Frame, and Map} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Note: the word "Series" is overloaded in Rebol to refer to two related but +// distinct concepts: +// +// * The internal system datatype, also known as a REBSER. It's a low-level +// implementation of something similar to a vector or an array in other +// languages. It is an abstraction which represents a contiguous region +// of memory containing equally-sized elements. +// +// * The user-level value type ANY-SERIES!. This might be more accurately +// called ITERATOR!, because it includes both a pointer to a REBSER of +// data and an index offset into that data. Attempts to reconcile all +// the naming issues from historical Rebol have not yielded a satisfying +// alternative, so the ambiguity has stuck. +// +// This file regards the first meaning of the word "series" and covers the +// low-level implementation details of a REBSER and its subclasses. For info +// about the higher-level ANY-SERIES! value type and its embedded index, +// see %sys-value.h in the definition of `struct Reb_Any_Series`. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// A REBSER is a contiguous-memory structure with an optimization of behaving +// like a kind of "double-ended queue". It is able to reserve capacity at +// both the tail and the head, and when data is taken from the head it will +// retain that capacity...reusing it on later insertions at the head. +// +// The space at the head is called the "bias", and to save on pointer math +// per-access, the stored data pointer is actually adjusted to include the +// bias. This biasing is backed out upon insertions at the head, and also +// must be subtracted completely to free the pointer using the address +// originally given by the allocator. +// +// The element size in a REBSER is known as the "width". It is designed +// to support widths of elements up to 255 bytes. (See note on SER_FREED +// about accomodating 256-byte elements.) +// +// REBSERs may be either manually memory managed or delegated to the garbage +// collector. Free_Series() may only be called on manual series. See +// MANAGE_SERIES() and PUSH_GUARD_SERIES() for remarks on how to work safely +// with pointers to garbage-collected series, to avoid having them be GC'd +// out from under the code while working with them. +// +// This file defines series subclasses which are type-incompatible with +// REBSER for safety. (In C++ they would be derived classes, so common +// operations would not require casting...but this is C.) The subclasses +// are explained where they are defined. +// +// Notes: +// +// * For the struct definition of REBSER, see %sys-rebser.h +// + + +//=////////////////////////////////////////////////////////////////////////=// +// +// SERIES COERCION +// +//=////////////////////////////////////////////////////////////////////////=// +// +// It is desirable to have series subclasses be different types, even though +// there are some common routines for processing them. e.g. not every +// function that would take a REBSER* would actually be handled in the same +// way for a REBARR*. Plus, just because a REBCTX* is implemented as a +// REBARR* with a link to another REBARR* doesn't mean most clients should +// be accessing the array--in a C++ build this would mean it would have some +// kind of protected inheritance scheme. +// +// The SER() macro provides a compromise besides a raw cast of a +// pointer to a REBSER*, because in the C++ build it makes sure that the +// incoming pointer type is to a simple series subclass. (It's just a raw +// cast in the C build.) +// + +#if !defined(NDEBUG) && defined(__cplusplus) && __cplusplus >= 201103L + template + inline REBSER *SER(T *p) { + static_assert( + // see specializations for void* and REBNOD*, which do more checks + std::is_same::value + || std::is_same::value, + "SER works on: void*, REBNOD*, REBSTR*, REBARR*" + ); + return cast(REBSER*, p); + } + + template <> + inline REBSER *SER(void *p) { + REBNOD *n = NOD(p); // ensures NOT(NODE_FLAG_FREE) + assert( + NOT(n->header.bits & NODE_FLAG_CELL) + && NOT(n->header.bits & NODE_FLAG_END) + ); + return cast(REBSER*, n); + } + + template <> + inline REBSER *SER(REBNOD *n) { + assert( + (n->header.bits & NODE_FLAG_NODE) + && NOT(n->header.bits & NODE_FLAG_FREE) // GET_SER_FLAG recurses! + && NOT(n->header.bits & NODE_FLAG_CELL) + && NOT(n->header.bits & NODE_FLAG_END) + ); + return cast(REBSER*, n); + } +#else + #define SER(p) \ + cast(REBSER*, (p)) +#endif + + +// +// The fundamental Make_Series creator makes a series which is not GC +// managed, and whose contents do not interact with the garbage collector. +// It is possible to pre-create a managed series by using Make_Series_Core() +// with the option NODE_FLAG_MANAGED...which bypasses the series being +// added (and later removed) from the manuals tracking list. +// +#define Make_Series(capacity, wide) \ + Make_Series_Core((capacity), (wide), 0) + + + +// +// Series header FLAGs (distinct from INFO bits) +// + +#define SET_SER_FLAG(s,f) \ + cast(void, (SER(s)->header.bits |= cast(REBUPT, (f)))) + +#define CLEAR_SER_FLAG(s,f) \ + cast(void, (SER(s)->header.bits &= ~cast(REBUPT, (f)))) + +#define GET_SER_FLAG(s,f) \ + LOGICAL(SER(s)->header.bits & (f)) // no single-flag check at present + +#define ANY_SER_FLAGS(s,f) \ + LOGICAL(SER(s)->header.bits & (f)) + +#define ALL_SER_FLAGS(s,f) \ + LOGICAL((SER(s)->header.bits & (f)) == (f)) + +#define NOT_SER_FLAG(s,f) \ + NOT(SER(s)->header.bits & (f)) + +#define SET_SER_FLAGS(s,f) \ + SET_SER_FLAG((s), (f)) + +#define CLEAR_SER_FLAGS(s,f) \ + CLEAR_SER_FLAG((s), (f)) + + +// +// Series INFO bits (distinct from header FLAGs) +// + +#define SET_SER_INFO(s,f) \ + cast(void, (SER(s)->info.bits |= cast(REBUPT, f))) + +#define CLEAR_SER_INFO(s,f) \ + cast(void, (SER(s)->info.bits &= ~cast(REBUPT, f))) + +#define GET_SER_INFO(s,f) \ + LOGICAL(SER(s)->info.bits & (f)) // no single-flag check at present + +#define ANY_SER_INFOS(s,f) \ + LOGICAL(SER(s)->info.bits & (f)) + +#define ALL_SER_INFOS(s,f) \ + LOGICAL((SER(s)->info.bits & (f)) == (f)) + +#define NOT_SER_INFO(s,f) \ + NOT(SER(s)->info.bits & (f)) + +#define SET_SER_INFOS(s,f) \ + SET_SER_INFO((s), (f)) + +#define CLEAR_SER_INFOS(s,f) \ + CLEAR_SER_INFO((s), (f)) + + +// +// The mechanics of the macros that get or set the length of a series are a +// little bit complicated. This is due to the optimization that allows data +// which is sizeof(REBVAL) or smaller to fit directly inside the series node. +// +// If a series is not "dynamic" (e.g. has a full pooled allocation) then its +// length is stored in the header. But if a series is dynamically allocated +// out of the memory pools, then without the data itself taking up the +// "content", there's room for a length in the node. +// + +#define SER_WIDE(s) \ + RIGHT_8_BITS((s)->info.bits) // inlining unnecessary + +inline static REBCNT SER_LEN(REBSER *s) { + return GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC) + ? s->content.dynamic.len + : MID_8_BITS(s->info.bits); +} + +inline static void SET_SERIES_LEN(REBSER *s, REBCNT len) { + assert(NOT_SER_INFO(s, CONTEXT_INFO_STACK)); + + if (GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC)) { + s->content.dynamic.len = len; + } + else { + assert(len < sizeof(s->content)); + CLEAR_8_MID_BITS(s->info.bits); + s->info.bits |= FLAGBYTE_MID(len); + assert(SER_LEN(s) == len); + } +} + +inline static REBCNT SER_REST(REBSER *s) { + if (GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC)) + return s->content.dynamic.rest; + + if (GET_SER_FLAG(s, SERIES_FLAG_ARRAY)) + return 2; // includes info bits acting as trick "terminator" + + assert(sizeof(s->content) % SER_WIDE(s) == 0); + return sizeof(s->content) / SER_WIDE(s); +} + +// Raw access does not demand that the caller know the contained type. So +// for instance a generic debugging routine might just want a byte pointer +// but have no element type pointer to pass in. +// +inline static REBYTE *SER_DATA_RAW(REBSER *s) { + // if updating, also update manual inlining in SER_AT_RAW + return GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC) + ? s->content.dynamic.data + : cast(REBYTE*, &s->content); +} + +inline static REBYTE *SER_AT_RAW(REBYTE w, REBSER *s, REBCNT i) { +#if !defined(NDEBUG) + if (w != SER_WIDE(s)) { + // + // This is usually a sign that the series was GC'd, as opposed to the + // caller passing in the wrong width (freeing sets width to 0). But + // give some debug tracking either way. + // + printf("SER_AT_RAW asked %d on width=%d\n", w, SER_WIDE(s)); + panic (s); + } +#endif + + return ((w) * (i)) + ( // v-- inlining of SER_DATA_RAW + GET_SER_INFO(s, SERIES_INFO_HAS_DYNAMIC) + ? s->content.dynamic.data + : cast(REBYTE*, &s->content) + ); +} + + +// +// In general, requesting a pointer into the series data requires passing in +// a type which is the correct size for the series. A pointer is given back +// to that type. +// +// Note that series indexing in C is zero based. So as far as SERIES is +// concerned, `SER_HEAD(t, s)` is the same as `SER_AT(t, s, 0)` +// + +#define SER_AT(t,s,i) \ + cast(t*, SER_AT_RAW(sizeof(t), (s), (i))) + +#define SER_HEAD(t,s) \ + SER_AT(t, (s), 0) + +inline static REBYTE *SER_TAIL_RAW(size_t w, REBSER *s) { + return SER_AT_RAW(w, s, SER_LEN(s)); +} + +#define SER_TAIL(t,s) \ + cast(t*, SER_TAIL_RAW(sizeof(t), (s))) + +inline static REBYTE *SER_LAST_RAW(size_t w, REBSER *s) { + assert(SER_LEN(s) != 0); + return SER_AT_RAW(w, s, SER_LEN(s) - 1); +} + +#define SER_LAST(t,s) \ + cast(t*, SER_LAST_RAW(sizeof(t), (s))) + + +#define SER_FULL(s) \ + (SER_LEN(s) + 1 >= SER_REST(s)) + +#define SER_AVAIL(s) \ + (SER_REST(s) - (SER_LEN(s) + 1)) // space available (minus terminator) + +#define SER_FITS(s,n) \ + ((SER_LEN(s) + (n) + 1) <= SER_REST(s)) + + +// +// Optimized expand when at tail (but, does not reterminate) +// + +inline static void EXPAND_SERIES_TAIL(REBSER *s, REBCNT delta) { + if (SER_FITS(s, delta)) + SET_SERIES_LEN(s, SER_LEN(s) + delta); + else + Expand_Series(s, SER_LEN(s), delta); +} + +// +// Termination +// + +inline static void TERM_SEQUENCE(REBSER *s) { + assert(NOT_SER_FLAG(s, SERIES_FLAG_ARRAY)); + memset(SER_AT_RAW(SER_WIDE(s), s, SER_LEN(s)), 0, SER_WIDE(s)); +} + +inline static void TERM_SEQUENCE_LEN(REBSER *s, REBCNT len) { + SET_SERIES_LEN(s, len); + TERM_SEQUENCE(s); +} + +#ifdef NDEBUG + #define ASSERT_SERIES_TERM(s) \ + NOOP +#else + #define ASSERT_SERIES_TERM(s) \ + Assert_Series_Term_Core(s) +#endif + +// Just a No-Op note to point out when a series may-or-may-not be terminated +// +#define NOTE_SERIES_MAYBE_TERM(s) NOOP + + +//=////////////////////////////////////////////////////////////////////////=// +// +// SERIES MANAGED MEMORY +// +//=////////////////////////////////////////////////////////////////////////=// +// +// When a series is allocated by the Make_Series routine, it is not initially +// visible to the garbage collector. To keep from leaking it, then it must +// be either freed with Free_Series or delegated to the GC to manage with +// MANAGE_SERIES. +// +// (In debug builds, there is a test at the end of every Rebol function +// dispatch that checks to make sure one of those two things happened for any +// series allocated during the call.) +// +// The implementation of MANAGE_SERIES is shallow--it only sets a bit on that +// *one* series, not any series referenced by values inside of it. This +// means that you cannot build a hierarchical structure that isn't visible +// to the GC and then do a single MANAGE_SERIES call on the root to hand it +// over to the garbage collector. While it would be technically possible to +// deeply walk the structure, the efficiency gained from pre-building the +// structure with the managed bit set is significant...so that's how deep +// copies and the scanner/load do it. +// +// (In debug builds, if any unmanaged series are found inside of values +// reachable by the GC, it will raise an alert.) +// + +inline static REBOOL IS_SERIES_MANAGED(REBSER *s) { + return LOGICAL(s->header.bits & NODE_FLAG_MANAGED); +} + +#define MANAGE_SERIES(s) \ + Manage_Series(s) + +inline static void ENSURE_SERIES_MANAGED(REBSER *s) { + if (NOT(IS_SERIES_MANAGED(s))) + MANAGE_SERIES(s); +} + +#ifdef NDEBUG + #define ASSERT_SERIES_MANAGED(s) \ + NOOP + + #define ASSERT_VALUE_MANAGED(v) \ + NOOP +#else + inline static void ASSERT_SERIES_MANAGED(REBSER *s) { + if (NOT(IS_SERIES_MANAGED(s))) + panic (s); + } + + #define ASSERT_VALUE_MANAGED(v) \ + assert(Is_Value_Managed(v)) +#endif + + +//=////////////////////////////////////////////////////////////////////////=// +// +// SERIES COLORING API +// +//=////////////////////////////////////////////////////////////////////////=// +// +// R3-Alpha re-used the same marking flag from the GC in order to do various +// other bit-twiddling tasks when the GC wasn't running. This is an +// unusually dangerous thing to be doing...because leaving a stray mark on +// during some other traversal could lead the GC to think it had marked +// things reachable from that series when it had not--thus freeing something +// that was still in use. +// +// While leaving a stray mark on is a bug either way, GC bugs are particularly +// hard to track down. So one doesn't want to risk them if not absolutely +// necessary. Not to mention that sharing state with the GC that you can +// only use when it's not running gets in the way of things like background +// garbage collection, etc. +// +// Ren-C keeps the term "mark" for the GC, since that's standard nomenclature. +// A lot of basic words are taken other places for other things (tags, flags) +// so this just goes with a series "color" of black or white, with white as +// the default. The debug build keeps a count of how many black series there +// are and asserts it's 0 by the time each evaluation ends, to ensure balance. +// + +static inline REBOOL Is_Series_Black(REBSER *s) { + return GET_SER_INFO(s, SERIES_INFO_BLACK); +} + +static inline REBOOL Is_Series_White(REBSER *s) { + return NOT_SER_INFO(s, SERIES_INFO_BLACK); +} + +static inline void Flip_Series_To_Black(REBSER *s) { + assert(NOT_SER_INFO(s, SERIES_INFO_BLACK)); + SET_SER_INFO(s, SERIES_INFO_BLACK); +#if !defined(NDEBUG) + ++TG_Num_Black_Series; +#endif +} + +static inline void Flip_Series_To_White(REBSER *s) { + assert(GET_SER_INFO(s, SERIES_INFO_BLACK)); + CLEAR_SER_INFO(s, SERIES_INFO_BLACK); +#if !defined(NDEBUG) + --TG_Num_Black_Series; +#endif +} + + +// +// Freezing and Locking +// + +inline static void Freeze_Sequence(REBSER *s) { // there is no unfreeze! + assert(NOT_SER_FLAG(s, SERIES_FLAG_ARRAY)); // use Deep_Freeze_Array + SET_SER_INFO(s, SERIES_INFO_FROZEN); +} + +inline static REBOOL Is_Series_Frozen(REBSER *s) { + assert(NOT_SER_FLAG(s, SERIES_FLAG_ARRAY)); // use Is_Array_Deeply_Frozen + return GET_SER_INFO(s, SERIES_INFO_FROZEN); +} + +inline static REBOOL Is_Series_Read_Only(REBSER *s) { // may be temporary... + return ANY_SER_INFOS( + s, SERIES_INFO_FROZEN | SERIES_INFO_RUNNING | SERIES_INFO_PROTECTED + ); +} + +// Gives the appropriate kind of error message for the reason the series is +// read only (frozen, running, protected). +// +// !!! Should probably report if more than one form of locking is in effect, +// but if only one error is to be reported then this is probably the right +// priority ordering. +// +inline static void FAIL_IF_READ_ONLY_SERIES(REBSER *s) { + if (Is_Series_Read_Only(s)) { + if (GET_SER_INFO(s, SERIES_INFO_RUNNING)) + fail (Error_Series_Running_Raw()); + + if (GET_SER_INFO(s, SERIES_INFO_FROZEN)) + fail (Error_Series_Frozen_Raw()); + + assert(GET_SER_INFO(s, SERIES_INFO_PROTECTED)); + fail (Error_Series_Protected_Raw()); + } +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// GUARDING SERIES FROM GARBAGE COLLECTION +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The garbage collector can run anytime the evaluator runs (and also when +// ports are used). So if a series has had MANAGE_SERIES run on it, the +// potential exists that any C pointers that are outstanding may "go bad" +// if the series wasn't reachable from the root set. This is important to +// remember any time a pointer is held across a call that runs arbitrary +// user code. +// +// This simple stack approach allows pushing protection for a series, and +// then can release protection only for the last series pushed. A parallel +// pair of macros exists for pushing and popping of guard status for values, +// to protect any series referred to by the value's contents. (Note: This can +// only be used on values that do not live inside of series, because there is +// no way to guarantee a value in a series will keep its address besides +// guarding the series AND locking it from resizing.) +// +// The guard stack is not meant to accumulate, and must be cleared out +// before a command ends. +// +// Also: Some REBVALs contain one or more series that need to be guarded. +// PUSH_GUARD_VALUE() makes it possible to not worry about what series are in +// a value, as it will take care of it if there are any. As with series +// guarding, the last value guarded must be the first one you DROP_GUARD on. +// + +inline static void PUSH_GUARD_SERIES(REBSER *s) { + ASSERT_SERIES_MANAGED(s); // see PUSH_GUARD_ARRAY_CONTENTS if you need it + Guard_Node_Core(cast(const REBNOD*, s)); +} + +inline static void PUSH_GUARD_VALUE(const RELVAL *v) { + Guard_Node_Core(cast(const REBNOD*, v)); +} + +inline static void Drop_Guard_Series_Common(REBSER *s) { + UNUSED(s); + GC_Guarded->content.dynamic.len--; +} + +inline static void Drop_Guard_Value_Common(const RELVAL *v) { + UNUSED(v); + GC_Guarded->content.dynamic.len--; +} + +#ifdef NDEBUG + #define DROP_GUARD_SERIES(s) \ + Drop_Guard_Series_Common(s); + + #define DROP_GUARD_VALUE(v) \ + Drop_Guard_Value_Common(v); +#else + inline static void Drop_Guard_Series_Debug( + REBSER *s, + const char *file, + int line + ) { + if (s != *SER_LAST(REBSER*, GC_Guarded)) + panic_at (s, file, line); + Drop_Guard_Series_Common(s); + } + + inline static void Drop_Guard_Value_Debug( + const RELVAL *v, + const char *file, + int line + ) { + if (v != *SER_LAST(RELVAL*, GC_Guarded)) + panic_at (v, file, line); + Drop_Guard_Value_Common(v); + } + + #define DROP_GUARD_SERIES(s) \ + Drop_Guard_Series_Debug(s, __FILE__, __LINE__); + + #define DROP_GUARD_VALUE(v) \ + Drop_Guard_Value_Debug(v, __FILE__, __LINE__); +#endif + + +//=////////////////////////////////////////////////////////////////////////=// +// +// ANY-SERIES! +// +//=////////////////////////////////////////////////////////////////////////=// + +inline static REBSER *VAL_SERIES(const RELVAL *v) { +#if !defined(NDEBUG) + // + // !!! In gcc 5.4, with a debug build, writing this expression as simply: + // + // assert(ANY_SERIES(v) || IS_MAP(v) || IS_IMAGE(v)); + // + // Appears to omit the ANY_SERIES() test entirely in -O2. Hence when a + // STRING! is passed in, it just fails the map and image test in the + // assembly. There is seemingly no good reason for this code to be + // missing, or that rewriting it as these ifs should fix it. But it does, + // so this is presumed to be an optimizer bug in that version. Review. + // + if (NOT(ANY_SERIES(v))) + if (NOT(IS_MAP(v))) + if (NOT(IS_IMAGE(v))) + panic (v); +#endif + return v->payload.any_series.series; +} + +inline static void INIT_VAL_SERIES(RELVAL *v, REBSER *s) { + assert(NOT_SER_FLAG(s, SERIES_FLAG_ARRAY)); + v->payload.any_series.series = s; +} + +#if defined(NDEBUG) || !defined(__cplusplus) + #define VAL_INDEX(v) \ + ((v)->payload.any_series.index) +#else + // allows an assert, but also lvalue: `VAL_INDEX(v) = xxx` + // + inline static REBCNT & VAL_INDEX(RELVAL *v) { // C++ reference type + assert(ANY_SERIES(v)); + return v->payload.any_series.index; + } + inline static REBCNT VAL_INDEX(const RELVAL *v) { + assert(ANY_SERIES(v)); + return v->payload.any_series.index; + } +#endif + +#define VAL_LEN_HEAD(v) \ + SER_LEN(VAL_SERIES(v)) + +inline static REBCNT VAL_LEN_AT(const RELVAL *v) { + if (VAL_INDEX(v) >= VAL_LEN_HEAD(v)) + return 0; // avoid negative index + return VAL_LEN_HEAD(v) - VAL_INDEX(v); // take current index into account +} + +inline static REBYTE *VAL_RAW_DATA_AT(const RELVAL *v) { + return SER_AT_RAW(SER_WIDE(VAL_SERIES(v)), VAL_SERIES(v), VAL_INDEX(v)); +} + +#define Init_Any_Series_At(v,t,s,i) \ + Init_Any_Series_At_Core((v), (t), (s), (i), SPECIFIED) + +#define Init_Any_Series(v,t,s) \ + Init_Any_Series_At((v), (t), (s), 0) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// BITSET! +// +//=////////////////////////////////////////////////////////////////////////=// +// +// !!! As written, bitsets use the Any_Series structure in their +// implementation, but are not considered to be an ANY-SERIES! type. +// + +#define VAL_BITSET(v) \ + VAL_SERIES(v) + +#define Init_Bitset(v,s) \ + Init_Any_Series((v), REB_BITSET, (s)) diff --git a/src/include/sys-stack.h b/src/include/sys-stack.h index 8464418544..41145ac185 100644 --- a/src/include/sys-stack.h +++ b/src/include/sys-stack.h @@ -1,115 +1,543 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: REBOL Stack Definitions -** Module: sys-stack.h -** Author: Carl Sassenrath -** Notes: -** -** DSP: index to the top of stack (active value) -** DSF: index to the base of stack frame (return value) -** -** Stack frame format: -** -** +---------------+ -** DSF->0:| Return Value | normally becomes TOS after func return -** +---------------+ -** 1:| Prior Frame | old DSF, block, and block index -** +---------------+ -** 2:| Func Word | for backtrace info -** +---------------+ -** 3:| Func Value | in case value is moved or modified -** +---------------+ -** 4:| Arg 1 | args begin here -** +---------------+ -** | Arg 2 | -** +---------------+ -** -***********************************************************************/ - -// Special stack controls (used by init and GC): -#define DS_RESET (DSP=DSF=0) -#define DS_TERMINATE (SERIES_TAIL(DS_Series) = DSP+1); - -// Access value at given stack location: -#define DS_VALUE(d) (&DS_Base[d]) - -// Stack pointer based actions: -#define DS_POP (&DS_Base[DSP--]) -#define DS_TOP (&DS_Base[DSP]) -#define DS_NEXT (&DS_Base[DSP+1]) -#define DS_SKIP (DSP++) -#define DS_DROP (DSP--) -#define DS_GET(d) (&DS_Base[d]) -#define DS_PUSH(v) (DS_Base[++DSP]=*(v)) // atomic -#define DS_PUSH_UNSET SET_UNSET(&DS_Base[++DSP]) // atomic -#define DS_PUSH_NONE SET_NONE(&DS_Base[++DSP]) // atomic -#define DS_PUSH_TRUE VAL_SET(&DS_Base[++DSP], REB_LOGIC), \ - VAL_LOGIC(&DS_Base[DSP]) = TRUE // not atomic -#define DS_PUSH_INTEGER(n) VAL_SET(&DS_Base[++DSP], REB_INTEGER), \ - VAL_INT64(&DS_Base[DSP]) = n // not atomic -#define DS_PUSH_DECIMAL(n) VAL_SET(&DS_Base[++DSP], REB_DECIMAL), \ - VAL_DECIMAL(&DS_Base[DSP]) = n // not atomic - -// References from DSF (stack frame base, RETURN value index): -#define DSF_SIZE 3 // from DSF to ARGS-1 -#define DSF_BIAS (DSF_SIZE+1) // from RETURN to DSP -#define DSF_RETURN(d) (&DS_Base[d]) // return value -#define DSF_BACK(d) (&DS_Base[(d)+1]) // block, index, prior DSF (VAL_BACK) -#define DSF_WORD(d) (&DS_Base[(d)+2]) // func word backtrace -#define DSF_FUNC(d) (&DS_Base[(d)+3]) // function value saved -#define DSF_ARGS(d,n) (&DS_Base[(d)+DSF_SIZE+(n)]) -#define PRIOR_DSF(d) VAL_BACK(DSF_BACK(d)) - -// Reference from ds that points to current return value: -#define D_RET (ds) -#define D_ARG(n) (ds+(DSF_SIZE+n)) -#define D_REF(n) (!IS_NONE(D_ARG(n))) - -// Reference from current DSF index: -#define DS_ARG_BASE (DSF+DSF_SIZE) -#define DS_ARG(n) DSF_ARGS(DSF, n) -#define DS_REF(n) (!IS_NONE(DS_ARG(n))) -#define DS_ARGC (DSP-DS_ARG_BASE) - -// RETURN operations: -#define DS_RETURN (&DS_Base[DSF]) -#define DS_RET_VALUE(v) (*DS_RETURN=*(v)) -#define DS_RET_INT(n) VAL_SET(DS_RETURN, REB_INTEGER), \ - VAL_INT64(DS_RETURN) = n // not atomic - -// Helpers: -#define DS_RELOAD(d) (d = DS_RETURN) -#define SET_BACK(v,b,i,f) VAL_SET((v), REB_BLOCK), VAL_SERIES(v)=(b), \ - VAL_INDEX(v)=i, VAL_BACK(v)=f - -enum { - R_RET = 0, - R_TOS, - R_TOS1, - R_NONE, - R_UNSET, - R_TRUE, - R_FALSE, - R_ARG1, - R_ARG2, - R_ARG3 +// +// File: %sys-stack.h +// Summary: {Definitions for "Data Stack", "Chunk Stack" and the C stack} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The data stack and chunk stack are two different data structures for +// temporarily storing REBVALs. With the data stack, values are pushed one +// at a time...while with the chunk stack, an array of value cells of a given +// length is returned. +// +// A key difference between the two stacks is pointer stability. Though the +// data stack can accept any number of pushes and then pop the last N pushes +// into a series, each push could potentially change the memory address of +// every other value in the stack. That's because the data stack is really +// a REBARR series under the hood. But the chunk stack is a custom structure, +// and guarantees that the address of the values in a chunk will stay stable +// until that chunk is popped. +// +// Another difference is that values on the data stack are implicitly GC safe, +// while clients of the chunk stack needing GC safety must do so manually. +// +// Because of their differences, they are applied to different problems: +// +// A notable usage of the data stack is by REDUCE and COMPOSE. They use it +// as a buffer for values that are being gathered to be inserted into the +// final array. It's better to use the data stack as a buffer because it +// means the size of the accumulated result is known before either creating +// a new series or inserting /INTO a target. This prevents wasting space on +// expansions or resizes and shuffling due to a guessed size. +// +// The chunk stack has an important use as the storage for arguments to +// functions being invoked. The pointers to these arguments are passed by +// natives through the stack to other routines, which may take arbitrarily +// long to return...and may call code involving many data stack pushes and +// pops. Argument pointers must be stable, so using the data stack would +// not work. Also, to efficiently implement argument fulfillment without +// pre-filling the cells, uninitialized memory is allowed in the chunk stack +// across potentical garbage collections. This means implicit GC protection +// can't be performed, with a subset of valid cells marked by the frame. +// + + +//=////////////////////////////////////////////////////////////////////////=// +// +// DATA STACK +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The data stack (DS_) is for pushing one individual REBVAL at a time. The +// values can then be popped in a Last-In-First-Out way. It is also possible +// to mark a stack position, do any number of pushes, and then ask for the +// range of values pushed since the mark to be placed into a REBARR array. +// As long as a value is on the data stack, any series it refers to will be +// protected from being garbage-collected. +// +// The data stack has many applications, and can be used by any piece of the +// system. But there is a rule that when that piece is finished, it must +// "balance" the stack back to where it was when it was called! There is +// a check in the main evaluator loop that the stack has been balanced to +// wherever it started by the time a function call ends. However, it is not +// necessary to balance the stack in the case of calling a `fail`--because +// it will be automatically restored to where it was at the PUSH_TRAP(). +// +// To speed pushes and pops to the stack while also making sure that each +// push is tested to see if an expansion is needed, a trick is used. This +// trick is to grow the stack in blocks, and always maintain that the block +// has an END marker at its point of capacity--and ensure that there are no +// end markers between the DSP and that capacity. This way, if a push runs +// up against an END it knows to do an expansion. +// + +// DSP stands for "(D)ata (S)tack "(P)osition", and is the index of the top +// of the data stack (last valid item in the underlying array) +// +#define DSP \ + DS_Index + +// DS_AT accesses value at given stack location. Test that it's not an END +// and that it's a cell, but, don't use the IS_END() test because that does +// not tolerate trash. +// +inline static REBVAL *DS_AT(REBDSP d) { + REBVAL *v = DS_Movable_Base + d; + assert( + v->header.bits & NODE_FLAG_CELL + && NOT(v->header.bits & NODE_FLAG_END) + ); + return v; +} + +// DS_TOP is the most recently pushed item +// +#define DS_TOP \ + DS_AT(DSP) + +#if !defined(NDEBUG) + #define IN_DATA_STACK_DEBUG(v) \ + IS_VALUE_IN_ARRAY_DEBUG(DS_Array, (v)) +#endif + +// +// PUSHING +// +// If you push "unsafe" trash to the stack, it has the benefit of costing +// nothing extra in a release build for setting the value (as it is just +// left uninitialized). But you must make sure that a GC can't run before +// you have put a valid value into the slot you pushed. +// +// If the stack runs out of capacity then it will be expanded by the basis +// defined below. The number is arbitrary and should be tuned. Note the +// number of bytes will be sizeof(REBVAL) * STACK_EXPAND_BASIS +// + +#define STACK_EXPAND_BASIS 128 + +// Note: DS_Movable_Base + DSP is just DS_TOP, but it asserts on ENDs. +// +#define DS_PUSH_TRASH \ + (++DSP, IS_END(DS_Movable_Base + DSP) \ + ? Expand_Data_Stack_May_Fail(STACK_EXPAND_BASIS) \ + : TRASH_CELL_IF_DEBUG(DS_Movable_Base + DSP)) + +inline static void DS_PUSH(const REBVAL *v) { + ASSERT_VALUE_MANAGED(v); // would fail on END marker + DS_PUSH_TRASH; + Move_Value(DS_TOP, v); +} + + +// +// POPPING +// +// Since it's known that END markers were never pushed, a pop can just leave +// whatever bits had been previously pushed, dropping only the index. The +// only END marker will be the one indicating the tail of the stack. +// + +#ifdef NDEBUG + #define DS_DROP \ + (--DS_Index) + + #define DS_DROP_TO(dsp) \ + (DS_Index = dsp) +#else + inline static void DS_DROP_Core() { + // Note: DS_TOP checks to make sure it's not an END. + SET_UNREADABLE_BLANK(DS_TOP); // TRASH would mean ASSERT_ARRAY failing + --DS_Index; + } + + #define DS_DROP \ + DS_DROP_Core() + + inline static void DS_DROP_TO_Core(REBDSP dsp) { + assert(DSP >= dsp); + while (DSP != dsp) + DS_DROP; + } + + #define DS_DROP_TO(dsp) \ + DS_DROP_TO_Core(dsp) +#endif + +// If Pop_Stack_Values_Core is used SERIES_FLAG_FILE_LINE, it means the system +// will try to capture the file and line number associated with the current +// frame into the generated array. But if there are other flags--like +// ARRAY_FLAG_PARAMLIST or ARRAY_FLAG_VARLIST--it's assumed that you don't +// want to do this, because the ->link and ->misc fields have other uses. +// +#define Pop_Stack_Values(dsp) \ + Pop_Stack_Values_Core((dsp), SERIES_FLAG_FILE_LINE) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// CHUNK STACK +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Unlike the data stack, values living in the chunk stack are not implicitly +// protected from garbage collection. +// +// Also, unlike the data stack, the chunk stack allows the pushing and popping +// of arbitrary-sized arrays of values which will not be relocated during +// their lifetime. +// +// This is accomplished using a custom "chunked" allocator. The two structs +// involved are a list of "Chunkers", which internally have a list of +// "Chunks" threaded between them. The method keeps one spare chunker +// allocated, and only frees a chunker when a full chunker prior has the last +// element popped out of it. In memory it looks like this: +// +// [chunker->next +// (->offset size [value1][value2][value3]...) // chunk 1 +// (->offset size [value1]...) // chunk 2 +// (->offset size [value1][value2]...) // chunk 3 +// ...remaining payload space in chunker... +// ] +// +// Since the chunker size is a known constant, it's possible to quickly deduce +// the chunker a chunk lives in from its pointer and the remaining payload +// amount in the chunker. +// + +struct Reb_Chunker; + +struct Reb_Chunker { + struct Reb_Chunker *next; + // use REBUPT for `size` so 'payload' is 64-bit aligned on 32-bit platforms + REBUPT size; + REBYTE payload[1]; +}; + +#define BASE_CHUNKER_SIZE (sizeof(struct Reb_Chunker*) + sizeof(REBUPT)) +#define CS_CHUNKER_PAYLOAD (4096 - BASE_CHUNKER_SIZE) // 12 bits for offset + + +struct Reb_Chunk; + +struct Reb_Chunk { + // + // We start the chunk with a Reb_Header, which has as its `bits` + // field a REBUPT (unsigned integer size of a pointer). We are relying + // on the fact that the high 2 bits of this value is always 0 in order + // for it to be an implicit END for the value array of the previous chunk. + // + // !!! Previously this was used to store arbitrary numbers that ended + // with the low 2 bits 0, e.g. the size. New endianness-dependent + // features restrict this somewhat, so it's really just a free set of + // flags and byte-sized quantities...currently not used except in this + // termination role. But available if needed... + // + struct Reb_Header header; + + REBUPT size; + + REBUPT offset; + + // Pointer to the previous chunk. As the second pointer in this chunk, + // with the chunk 64-bit aligned to start with, it means the values will + // be 64-bit aligned on 32-bit platforms. + // + struct Reb_Chunk *prev; + + // The `values` is an array whose real size exceeds the struct. (It is + // set to a size of one because it cannot be [0] if built with C++.) + // When the value pointer is given back to the user, the address of + // this array is how they speak about the chunk itself. + // + // See note above about how the next chunk's `size` header serves as + // an END marker for this array (which may or may not be necessary for + // the client's purposes, but function arg lists do make use of it) + // + // These are actually non-relative values, but REBVAL has a constructor + // and that interferes with the use of offsetof in the C++ build. So + // RELVAL is chosen as a POD-type to use in the structure. + // + RELVAL values[1]; }; + +inline static REBCNT CHUNK_SIZE(struct Reb_Chunk *chunk) { + return chunk->size; +} + +// The offset of this chunk in the memory chunker this chunk lives in +// (its own size has already been subtracted from the amount). +// +inline static REBCNT CHUNK_OFFSET(struct Reb_Chunk *chunk) { + return chunk->offset; +} + +// If we do a sizeof(struct Reb_Chunk) then it includes a value in it that we +// generally don't want for our math, due to C++ "no zero element array" rule +// +#define BASE_CHUNK_SIZE (sizeof(struct Reb_Chunk) - sizeof(REBVAL)) + +#define CHUNK_FROM_VALUES(v) \ + cast(struct Reb_Chunk *, cast(REBYTE*, (v)) \ + - offsetof(struct Reb_Chunk, values)) + +#define CHUNK_LEN_FROM_VALUES(v) \ + ((CHUNK_SIZE(CHUNK_FROM_VALUES(v)) - offsetof(struct Reb_Chunk, values)) \ + / sizeof(REBVAL)) + +inline static struct Reb_Chunker *CHUNKER_FROM_CHUNK(struct Reb_Chunk *c) { + return cast( + struct Reb_Chunker*, + cast(REBYTE*, c) + - CHUNK_OFFSET(c) + - offsetof(struct Reb_Chunker, payload) + ); +} + + +// This doesn't necessarily call Alloc_Mem, because chunks are allocated +// sequentially inside of "chunker" blocks, in their ordering on the stack. +// Allocation is only required if we need to step into a new chunk (and even +// then only if we aren't stepping into a chunk that we are reusing from +// a prior expansion). +// +// The "Ended" indicates that there is no need to manually put an end in the +// `num_values` slot. Chunks are implicitly terminated by their layout, +// because the low bit of subsequent chunks is set to 0, for data that does +// double-duty as a END marker. +// +inline static REBVAL* Push_Value_Chunk_Of_Length(REBCNT num_values) { + const REBCNT size = BASE_CHUNK_SIZE + num_values * sizeof(REBVAL); + assert(size % 4 == 0); // low 2 bits must be zero for terminator trick + + // an extra Reb_Header is placed at the very end of the array to + // denote a block terminator without a full REBVAL + // + const REBCNT size_with_terminator = size + sizeof(struct Reb_Header); + + struct Reb_Chunker *chunker = CHUNKER_FROM_CHUNK(TG_Top_Chunk); + + // Establish invariant where 'chunk' points to a location big enough to + // hold the data (with data's size accounted for in chunk_size). Note + // that TG_Top_Chunk is never NULL, due to the initialization leaving + // one empty chunk at the beginning and manually destroying it on + // shutdown (this simplifies Push) + // + const REBCNT payload_left = + chunker->size + - CHUNK_OFFSET(TG_Top_Chunk) + - CHUNK_SIZE(TG_Top_Chunk); + + assert(chunker->size >= CS_CHUNKER_PAYLOAD); + + struct Reb_Chunk *chunk; + if (payload_left >= size_with_terminator) { + // + // Topmost chunker has space for the chunk *and* a header to signal + // that chunk's END marker. So advance past the topmost chunk (whose + // size will depend upon num_values) + // + chunk = cast(struct Reb_Chunk*, + cast(REBYTE*, TG_Top_Chunk) + CHUNK_SIZE(TG_Top_Chunk) + ); + + Init_Endlike_Header(&chunk->header, 0); + chunk->size = size; + + // top's offset accounted for previous chunk, account for ours + // + chunk->offset = CHUNK_OFFSET(TG_Top_Chunk) + CHUNK_SIZE(TG_Top_Chunk); + } + else { // Topmost chunker has insufficient space + REBOOL need_alloc = TRUE; + if (chunker->next) { + // + // Previously allocated chunker exists, check if it is big enough + // + assert(!chunker->next->next); + if (chunker->next->size >= size_with_terminator) + need_alloc = FALSE; + else + Free_Mem(chunker->next, chunker->next->size + BASE_CHUNKER_SIZE); + } + if (need_alloc) { + // + // No previously allocated chunker...we have to allocate it + // + const REBCNT payload_size = BASE_CHUNKER_SIZE + + (size_with_terminator < CS_CHUNKER_PAYLOAD ? + CS_CHUNKER_PAYLOAD : (size_with_terminator << 1)); + chunker->next = cast(struct Reb_Chunker*, Alloc_Mem(payload_size)); + chunker->next->next = NULL; + chunker->next->size = payload_size - BASE_CHUNKER_SIZE; + } + + assert(chunker->next->size >= size_with_terminator); + + chunk = cast(struct Reb_Chunk*, &chunker->next->payload); + + Init_Endlike_Header(&chunk->header, 0); + chunk->size = size; + chunk->offset = 0; + } + + + // Set header in next element to 0, so it can serve as a terminator + // for the data range of this until it gets instantiated (if ever) + // + Init_Endlike_Header( + &cast(struct Reb_Chunk*, cast(REBYTE*, chunk) + size)->header, + 0 + ); + assert(IS_END(&chunk->values[num_values])); + + chunk->prev = TG_Top_Chunk; + + TG_Top_Chunk = chunk; + + + // Set all chunk cells writable. + // + // !!! Should be using VALUE_FLAG_STACK + { + REBCNT index; + for (index = 0; index < num_values; index++) + INIT_CELL(&chunk->values[index]); + } + + assert(CHUNK_FROM_VALUES(&chunk->values[0]) == chunk); + return KNOWN(&chunk->values[0]); +} + + +// Free an array of previously pushed REBVALs. This only occasionally +// requires an actual call to Free_Mem(), as the chunks are allocated +// sequentially inside containing allocations. +// +inline static void Drop_Chunk_Of_Values(REBVAL *opt_head) +{ + struct Reb_Chunk* chunk = TG_Top_Chunk; + + // Passing in `opt_head` is optional, but a good check to make sure you are + // actually dropping the chunk you think you are. (On an error condition + // when dropping chunks to try and restore the top chunk to a previous + // state, this information isn't available.) + // +#if defined(NDEBUG) + UNUSED(opt_head); +#else + assert(!opt_head || CHUNK_FROM_VALUES(opt_head) == chunk); +#endif + + // Drop to the prior top chunk + TG_Top_Chunk = chunk->prev; + + if (CHUNK_OFFSET(chunk) == 0) { + // This chunk sits at the head of a chunker. + + struct Reb_Chunker *chunker = CHUNKER_FROM_CHUNK(chunk); + + assert(TG_Top_Chunk); + + // When we've completely emptied a chunker, we check to see if the + // chunker after it is still live. If so, we free it. But we + // want to keep *this* just-emptied chunker alive for overflows if we + // rapidly get another push, to avoid Make_Mem()/Free_Mem() costs. + + if (chunker->next) { + Free_Mem(chunker->next, chunker->next->size + BASE_CHUNKER_SIZE); + chunker->next = NULL; + } + } + + // In debug builds we poison the memory for the chunk... but not the `prev` + // pointer because we expect that to stick around! + // +#if !defined(NDEBUG) + memset( + cast(REBYTE*, chunk) + sizeof(struct Reb_Chunk*), + 0xBD, + CHUNK_SIZE(chunk) - sizeof(struct Reb_Chunk*) + ); + assert(IS_END(cast(REBVAL*, chunk))); +#endif +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// C STACK +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Rebol doesn't want to crash in the event of a stack overflow, but would +// like to gracefully trap it and return the user to the console. While it +// is possible for Rebol to set a limit to how deeply it allows function +// calls in the interpreter to recurse, there's no *portable* way to +// catch a stack overflow in the C code of the interpreter itself. +// +// Hence, by default Rebol will use a non-standard heuristic. A flag is +// passed to say if OS_STACK_GROWS_UP. If so, it then extrapolates that C +// function call frames will be laid out consecutively, and the memory +// difference between a stack variable in the topmost stacks can be checked +// against some limit. +// +// This has nothing to do with guarantees in the C standard, and compilers +// can really put variables at any address they feel like: +// +// http://stackoverflow.com/a/1677482/211160 +// +// Additionally, it puts the burden on every recursive or deeply nested +// routine to sprinkle calls to the C_STACK_OVERFLOWING macro somewhere +// in it. The ideal answer is to make Rebol itself corral an interpreted +// script such that it can't cause the C code to stack overflow. Lacking +// that ideal this technique could break, so build configurations should +// be able to turn it off if needed. +// +// In the meantime, C_STACK_OVERFLOWING is a macro which takes the +// address of some variable local to the currently executed function. +// Note that because the limit is noticed before the C stack has *actually* +// overflowed, you still have a bit of stack room to do the cleanup and +// raise an error trap. (You need to take care of any unmanaged series +// allocations, etc). So cleaning up that state should be doable without +// making deep function calls. +// +// !!! Future approaches should look into use of Windows stack exceptions +// or libsigsegv: +// +// http://stackoverflow.com/questions/5013806/ +// + +#ifdef OS_STACK_GROWS_UP + #define C_STACK_OVERFLOWING(address_of_local_var) \ + (cast(REBUPT, address_of_local_var) >= Stack_Limit) +#else + #define C_STACK_OVERFLOWING(address_of_local_var) \ + (cast(REBUPT, address_of_local_var) <= Stack_Limit) +#endif + +#define STACK_BOUNDS (4*1024*1000) // note: need a better way to set it !! +// Also: made somewhat smaller than linker setting to allow trapping it + +#define Trap_Stack_Overflow() \ + fail (VAL_CONTEXT(TASK_STACK_ERROR)); diff --git a/src/include/sys-state.h b/src/include/sys-state.h index 1e98dcecfb..f173b993ca 100644 --- a/src/include/sys-state.h +++ b/src/include/sys-state.h @@ -1,63 +1,54 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: CPU State -** Module: sys-state.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ +// +// File: %sys-state.h +// Summary: "Interpreter State" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Structure holding the information about the last point in the stack that +// wanted to set up an opportunity to intercept a `fail (Error_XXX())` +// +// For operations using this structure, see %sys-trap.h +// -// Create this on your local stack frame or globally: -typedef struct { // State variables to save - jmp_buf *last_jmp_buf; - REBINT dsp; - REBINT dsf; - REBINT hold_tail; // Tail for GC_Protect - REBSER *error; - jmp_buf cpu_state; -} REBOL_STATE; +struct Reb_State { + struct Reb_State *last_state; -// Save current state info into a structure: -// g is Saved_State or Halt_State. -#define PUSH_STATE(s, g) do {\ - (s).last_jmp_buf = g;\ - (s).dsp = DSP;\ - (s).dsf = DSF;\ - (s).hold_tail = GC_Protect->tail;\ - (s).error = 0;\ - } while(0) + REBDSP dsp; + struct Reb_Chunk *top_chunk; + REBFRM *frame; + REBCNT guarded_len; + REBCNT value_guard_len; + REBCTX *error; -#define POP_STATE(s, g) do {\ - g = (s).last_jmp_buf;\ - DSP = (s).dsp;\ - DSF = (s).dsf;\ - GC_Protect->tail = (s).hold_tail;\ - } while (0) + REBCNT manuals_len; // Where GC_Manuals was when state started + REBCNT uni_buf_len; + REBCNT mold_loop_tail; -// Do not restore prior state: -#define DROP_STATE(s, g) g = (s).last_state - -// Set the pointer for the prior state: -#define SET_STATE(s, g) g = &(s).cpu_state - -// Store all CPU registers into the structure: -#define SET_JUMP(s) setjmp((s).cpu_state) +#ifdef HAS_POSIX_SIGNAL + sigjmp_buf cpu_state; +#else + jmp_buf cpu_state; +#endif +}; diff --git a/src/include/sys-string.h b/src/include/sys-string.h new file mode 100644 index 0000000000..9aec4a4239 --- /dev/null +++ b/src/include/sys-string.h @@ -0,0 +1,229 @@ +// +// File: %sys-string.h +// Summary: {Definitions for REBSTR (e.g. WORD!) and REBUNI (e.g. STRING!)} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// !!! R3-Alpha and Red would work with strings in their decoded form, in +// series of varying widths. Ren-C's goal is to replace this with the idea +// of "UTF-8 everywhere", working with the strings as UTF-8 and only +// converting if the platform requires it for I/O (e.g. Windows): +// +// http://utf8everywhere.org/ +// +// As a first step toward this goal, one place where strings were kept in +// UTF-8 form has been converted into series...the word table. So for now, +// all REBSTR instances are for ANY-WORD!. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The *current* implementation of Rebol's ANY-STRING! type has two different +// series widths that are used. One is the BYTE_SIZED() series which encodes +// ASCII in the low bits, and Latin-1 extensions in the range 0x80 - 0xFF. +// So long as a codepoint can fit in this range, the string can be stored in +// single bytes: +// +// https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block) +// +// (Note: This is not to be confused with the other "byte-width" encoding, +// which is UTF-8. Rebol series routines are not set up to handle insertions +// or manipulations of UTF-8 encoded data in a Reb_Any_String payload at +// this time...it is a format used only in I/O.) +// +// The second format that is used puts codepoints into a 16-bit REBUNI-sized +// element. If an insertion of a string or character into a byte sized +// string cannot be represented in 0xFF or lower, then the target string will +// be "widened"--doubling the storage space taken and requiring updating of +// the character data in memory. At this time there are no "in-place" +// cases where a string is reduced from REBUNI to byte sized, but operations +// like Copy_String_Slimming() will scan a source string to see if a byte-size +// copy can be made from a REBUNI-sized one without loss of information. +// +// Byte-sized series are also used by the BINARY! datatype. There is no +// technical difference between such series used as strings or used as binary, +// the difference comes from being marked REB_BINARY or REB_STRING in the +// header of the value carrying the series. +// +// For easier type-correctness, the series macros are given with names BIN_XXX +// and UNI_XXX. There aren't distinct data types for the series themselves, +// just REBSER* is used. Hence BIN_LEN() and UNI_LEN() aren't needed as you +// could just use SER_LEN(), but it helps a bit for readability...and an +// assert is included to ensure the size matches up. +// + + +//=////////////////////////////////////////////////////////////////////////=// +// +// REBSTR series for UTF-8 strings +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The concept is that a SYM refers to one of the built-in words and can +// be used in C switch statements. A canon STR is used to identify +// everything else. +// + +inline static const REBYTE *STR_HEAD(REBSTR *str) { + return BIN_HEAD(str); +} + +inline static REBSTR *STR_CANON(REBSTR *str) { + if (GET_SER_INFO(str, STRING_INFO_CANON)) + return str; + return str->misc.canon; +} + +inline static OPT_REBSYM STR_SYMBOL(REBSTR *str) { + REBUPT sym = RIGHT_16_BITS(str->header.bits); + assert(RIGHT_16_BITS(STR_CANON(str)->header.bits) == sym); + return cast(REBSYM, sym); +} + +inline static REBCNT STR_NUM_BYTES(REBSTR *str) { + return SER_LEN(str); // number of bytes in seris is series length, ATM +} + +inline static REBSTR *Canon(REBSYM sym) { + assert(cast(REBCNT, sym) != 0); + assert(cast(REBCNT, sym) < SER_LEN(PG_Symbol_Canons)); + return *SER_AT(REBSTR*, PG_Symbol_Canons, cast(REBCNT, sym)); +} + +inline static REBOOL SAME_STR(REBSTR *s1, REBSTR *s2) { + if (s1 == s2) return TRUE; // !!! does this check speed things up or not? + return LOGICAL(STR_CANON(s1) == STR_CANON(s2)); // canon check, quite fast +} + + + +// +// !!! UNI_XXX: Unicode string series macros !!! - Becoming Deprecated +// + +inline static REBCNT UNI_LEN(REBSER *s) { + assert(SER_WIDE(s) == sizeof(REBUNI)); + return SER_LEN(s); +} + +inline static void SET_UNI_LEN(REBSER *s, REBCNT len) { + assert(SER_WIDE(s) == sizeof(REBUNI)); + SET_SERIES_LEN(s, len); +} + +#define UNI_AT(s,n) \ + SER_AT(REBUNI, (s), (n)) + +#define UNI_HEAD(s) \ + SER_HEAD(REBUNI, (s)) + +#define UNI_TAIL(s) \ + SER_TAIL(REBUNI, (s)) + +#define UNI_LAST(s) \ + SER_LAST(REBUNI, (s)) + +inline static void TERM_UNI(REBSER *s) { + UNI_HEAD(s)[SER_LEN(s)] = 0; +} + +inline static void TERM_UNI_LEN(REBSER *s, REBCNT len) { + SET_SERIES_LEN(s, len); + UNI_HEAD(s)[len] = 0; +} + + +// +// Get a char, from either byte or unicode string: +// + +inline static REBUNI GET_ANY_CHAR(REBSER *s, REBCNT n) { + return BYTE_SIZE(s) ? BIN_HEAD(s)[n] : UNI_HEAD(s)[n]; +} + +inline static void SET_ANY_CHAR(REBSER *s, REBCNT n, REBYTE c) { + if (BYTE_SIZE(s)) + BIN_HEAD(s)[n] = c; + else + UNI_HEAD(s)[n] = c; +} + + + +//=////////////////////////////////////////////////////////////////////////=// +// +// ANY-STRING! (uses `struct Reb_Any_Series`) +// +//=////////////////////////////////////////////////////////////////////////=// + +#define Init_String(v,s) \ + Init_Any_Series((v), REB_STRING, (s)) + +#define Init_File(v,s) \ + Init_Any_Series((v), REB_FILE, (s)) + +#define Init_Email(v,s) \ + Init_Any_Series((v), REB_EMAIL, (s)) + +#define Init_Tag(v,s) \ + Init_Any_Series((v), REB_TAG, (s)) + +#define Init_Url(v,s) \ + Init_Any_Series((v), REB_URL, (s)) + +#define VAL_UNI(v) \ + UNI_HEAD(VAL_SERIES(v)) + +#define VAL_UNI_HEAD(v) \ + UNI_HEAD(VAL_SERIES(v)) + +#define VAL_UNI_AT(v) \ + UNI_AT(VAL_SERIES(v), VAL_INDEX(v)) + +#define VAL_ANY_CHAR(v) \ + GET_ANY_CHAR(VAL_SERIES(v), VAL_INDEX(v)) + + +// Basic string initialization from UTF8. +// +inline static REBSER *Make_UTF8_May_Fail(const char *utf8) +{ + return Append_UTF8_May_Fail( + NULL, + cast(const REBYTE*, utf8), + LEN_BYTES(cast(const REBYTE*, utf8)) + ); +} + + +// Basic string initialization from UTF16. Note: This just assumes two byte +// characters with no decoding at the moment. +// +inline static REBSER *Make_UTF16_May_Fail(REBUNI *utf16) +{ + REBCNT len = Strlen_Uni(utf16); + REBSER *s = Make_Unicode(len); + Append_Uni_Uni(s, utf16, len); + return s; +} \ No newline at end of file diff --git a/src/include/sys-trap.h b/src/include/sys-trap.h new file mode 100644 index 0000000000..b486cc2211 --- /dev/null +++ b/src/include/sys-trap.h @@ -0,0 +1,375 @@ +// +// File: %sys-trap.h +// Summary: "CPU and Interpreter State Snapshot/Restore" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Rebol is settled upon a stable and pervasive implementation baseline of +// ANSI-C (C89). That commitment provides certain advantages. +// +// One of the *disadvantages* is that there is no safe way to do non-local +// jumps with stack unwinding (as in C++). If you've written some code that +// performs a raw malloc and then wants to "throw" via a `longjmp()`, that +// will leak the malloc. +// +// In order to mitigate the inherent failure of trying to emulate stack +// unwinding via longjmp, the macros in this file provide an abstraction +// layer. These allow Rebol to clean up after itself for some kinds of +// "dangling" state--such as manually memory managed series that have been +// made with Make_Series() but never passed to either Free_Series() or +// MANAGE_SERIES(). This covers several potential leaks known-to-Rebol, +// but custom interception code is needed for any generalized resource +// that might be leaked in the case of a longjmp(). +// +// The triggering of the longjmp() is done via "fail", and it's important +// to know the distinction between a "fail" and a "throw". In Rebol +// terminology, a `throw` is a cooperative concept, which does *not* use +// longjmp(), and instead must cleanly pipe the thrown value up through +// the OUT pointer that each function call writes into. The `throw` will +// climb the stack until somewhere in the backtrace, one of the calls +// chooses to intercept the thrown value instead of pass it on. +// +// By contrast, a `fail` is non-local control that interrupts the stack, +// and can only be intercepted by points up the stack that have explicitly +// registered themselves interested. So comparing these two bits of code: +// +// catch [if 1 < 2 [trap [print ["Foo" (throw "Throwing")]]]] +// +// trap [if 1 < 2 [catch [print ["Foo" (fail "Failing")]]]] +// +// In the first case, the THROW is offered to each point up the chain as +// a special sort of "return value" that only natives can examine. The +// `print` will get a chance, the `trap` will get a chance, the `if` will +// get a chance...but only CATCH will take the opportunity. +// +// In the second case, the FAIL is implemented with longjmp(). So it +// doesn't make a return value...it never reaches the return. It offers an +// ERROR! up the stack to native functions that have called PUSH_TRAP() in +// advance--as a way of registering interest in intercepting failures. For +// IF or CATCH or PRINT to have an opportunity, they would need to be changed +// to include a PUSH_TRAP() call. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// NOTE: If you are integrating with C++ and a longjmp crosses a constructed +// object, abandon all hope...UNLESS you use Ren-cpp. It is careful to +// avoid this trap, and you don't want to redo that work. +// +// http://stackoverflow.com/questions/1376085/ +// + + +// "Under FreeBSD 5.2.1 and Mac OS X 10.3, setjmp and longjmp save and restore +// the signal mask. Linux 2.4.22 and Solaris 9, however, do not do this. +// FreeBSD and Mac OS X provide the functions _setjmp and _longjmp, which do +// not save and restore the signal mask." +// +// "To allow either form of behavior, POSIX.1 does not specify the effect of +// setjmp and longjmp on signal masks. Instead, two new functions, sigsetjmp +// and siglongjmp, are defined by POSIX.1. These two functions should always +// be used when branching from a signal handler." +// +// Note: longjmp is able to pass a value (though only an integer on 64-bit +// platforms, and not enough to pass a pointer). This can be used to +// dictate the value setjmp returns in the longjmp case, though the code +// does not currently use that feature. +// +// Also note: with compiler warnings on, it can tell us when values are set +// before the setjmp and then changed before a potential longjmp: +// +// http://stackoverflow.com/q/7721854/211160 +// +// Because of this longjmp/setjmp "clobbering", it's a useful warning to +// have enabled in. One option for suppressing it would be to mark +// a parameter as 'volatile', but that is implementation-defined. +// It is best to use a new variable if you encounter such a warning. +// +#ifdef HAS_POSIX_SIGNAL + #define SET_JUMP(s) \ + sigsetjmp((s), 1) + + #define LONG_JUMP(s,v) \ + siglongjmp((s), (v)) +#else + #define SET_JUMP(s) \ + setjmp(s) + + #define LONG_JUMP(s,v) \ + longjmp((s), (v)) +#endif + + +// SNAP_STATE will record the interpreter state but not include it into +// the chain of trapping points. This is used by PUSH_TRAP but also by +// debug code that just wants to record the state to make sure it balances +// back to where it was. +// +#define SNAP_STATE(s) \ + Snap_State_Core(s) + + +// PUSH_TRAP is a construct which is used to catch errors that have been +// triggered by the Fail_Core() function. This can be triggered by a usage +// of the `fail` pseudo-"keyword" in C code, and in Rebol user code by the +// REBNATIVE(fail). To call the push, you need a `struct Reb_State` to be +// passed which it will write into--which is a black box that clients +// shouldn't inspect. +// +// The routine also takes a pointer-to-a-REBCTX-pointer which represents +// an error. Using the tricky mechanisms of setjmp/longjmp, there will +// be a first pass of execution where the line of code after the PUSH_TRAP +// will see the error pointer as being NULL. If a trap occurs during +// code before the paired DROP_TRAP happens, then the C state will be +// magically teleported back to the line after the PUSH_TRAP with the +// error value now non-null and usable, including put into a REBVAL via +// the `Init_Error()` function. +// +#define PUSH_TRAP(e,s) \ + PUSH_TRAP_CORE((e), (s), TRUE) + + +// PUSH_UNHALTABLE_TRAP is a form of PUSH_TRAP that will receive RE_HALT in +// the same way it would be told about other errors. In a pure C client, +// it would usually be only at the topmost level (e.g. console REPL loop). +// +// It's also necessary at C-to-C++ boundary crossings (as in Ren/C++) even +// if they are not the topmost. This is because C++ needs to know if *any* +// longjmp happens, to keep it from crossing stack frames with constructed +// objects without running their destructors. Once it is done unwinding +// any relevant C++ call frames, it may have to trigger another longjmp IF +// the C++ code was called from other Rebol C code. (This is done in the +// exception handler found in Ren/C++'s %function.hpp) +// +// Note: Despite the technical needs of low-level clients, there is likely +// no reasonable use-case for a user-exposed ability to intercept HALTs in +// Rebol code, for instance with a "TRAP/HALT" construction. +// +#define PUSH_UNHALTABLE_TRAP(e,s) \ + PUSH_TRAP_CORE((e), (s), FALSE) + + +// Core implementation behind PUSH_TRAP and PUSH_UNHALTABLE_TRAP. +// +// Note: The implementation of this macro was chosen stylistically to +// hide the result of the setjmp call. That's because you really can't +// put "setjmp" in arbitrary conditions like `setjmp(...) ? x : y`. That's +// against the rules. So although the preprocessor abuse below is a bit +// ugly, it helps establish that anyone modifying this code later not be +// able to avoid the truth of the limitation: +// +// http://stackoverflow.com/questions/30416403/ +// +// !!! THIS CAN'T BE INLINED due to technical limitations of using setjmp() +// in inline functions (at least in gcc) +// +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=24556 +// +// According to the developers, "This is not a bug as if you inline it, the +// place setjmp goes to could be not where you want to goto." +// +#define PUSH_TRAP_CORE(e,s,haltable) \ + do { \ + assert(Saved_State || (DSP == 0 && FS_TOP == NULL)); \ + Snap_State_Core(s); \ + (s)->last_state = Saved_State; \ + Saved_State = (s); \ + if (haltable) { \ + /* the topmost TRAP must be PUSH_UNHALTABLE_TRAP */ \ + assert((s)->last_state != NULL); \ + } \ + if (!SET_JUMP((s)->cpu_state)) { \ + /* this branch will always be run */ \ + *(e) = NULL; \ + } \ + else { \ + /* this runs if before the DROP_TRAP a longjmp() happens */ \ + if (haltable) { \ + if (Trapped_Helper_Halted(s)) \ + fail ((s)->error); /* proxy the halt up the stack */ \ + else \ + *(e) = (s)->error; \ + } \ + else { \ + (void)Trapped_Helper_Halted(s); /* ignore result */ \ + *(e) = (s)->error; \ + } \ + } \ + } while (0) + + +// If either a haltable or non-haltable TRAP is PUSHed, it must be DROP'd. +// DROP_TRAP_SAME_STACKLEVEL_AS_PUSH has a long and informative name to +// remind you that you must DROP_TRAP from the same scope you PUSH_TRAP +// from. (So do not call PUSH_TRAP in a function, then return from that +// function and DROP_TRAP at another stack level.) +// +// "If the function that called setjmp has exited (whether by return +// or by a different longjmp higher up the stack), the behavior is +// undefined. In other words, only long jumps up the call stack +// are allowed." +// +// http://en.cppreference.com/w/c/program/longjmp +// +// Note: There used to be more aggressive balancing-oriented asserts, making +// this a point where outstanding manuals or guarded values and series would +// have to be balanced. Those seemed to be more irritating than helpful, +// so the asserts have been left to the evaluator's bracketing. +// +inline static void DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(struct Reb_State *s) { + assert(!s->error); + Saved_State = s->last_state; +} + + +// ASSERT_STATE_BALANCED is used to check that the situation modeled in a +// SNAP_STATE has balanced out, without a trap (e.g. it is checked each time +// the evaluator completes a cycle in the debug build) +// +#ifdef NDEBUG + #define ASSERT_STATE_BALANCED(s) NOOP +#else + #define ASSERT_STATE_BALANCED(s) \ + Assert_State_Balanced_Debug((s), __FILE__, __LINE__) +#endif + + +// +// FAIL +// +// The fail() macro implements a form of error which is "trappable" with the +// macros above: +// +// if (Foo_Type(foo) == BAD_FOO) { +// fail (Error_Bad_Foo_Operation(...)); +// +// /* this line will never be reached, because it +// longjmp'd up the stack where execution continues */ +// } +// +// In debug builds, the macro will capture the file and line numbers, and +// add it to the error object itself. +// +// Errors that originate from C code are created via Make_Error, and are +// defined in %errors.r. These definitions contain a formatted message +// template, showing how the arguments will be displayed in FORMing. +// +// NOTE: It's desired that there be a space in `fail (...)` to make it look +// more "keyword-like" and draw attention to the fact it is a `noreturn` call. +// + +#ifdef NDEBUG + // + // We don't want release builds to have to pay for the parameter + // passing cost *or* the string table cost of having a list of all + // the files and line numbers for all the places that originate + // errors... + // + #define fail(error) \ + Fail_Core(error) +#else + #if defined(__cplusplus) && __cplusplus >= 201103L + // + // We can do a bit more checking in the C++ build, for instance to + // make sure you don't pass a RELVAL* into fail(). This could also + // be used by a strict build that wanted to get rid of all the hard + // coded string fail()s, by triggering a compiler error on them. + + template + inline static ATTRIBUTE_NO_RETURN void Fail_Core_Cpp(T *p) { + static_assert( + std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value, + "fail() works on: REBCTX*, REBVAL*, const char*" + ); + Fail_Core(p); + } + + #define fail(error) \ + do { \ + TG_Erroring_C_File = __FILE__; \ + TG_Erroring_C_Line = __LINE__; \ + Fail_Core_Cpp(error); \ + } while (0) + #else + #define fail(error) \ + do { \ + TG_Erroring_C_File = __FILE__; \ + TG_Erroring_C_Line = __LINE__; \ + Fail_Core(error); \ + } while (0) + #endif +#endif + + +//=////////////////////////////////////////////////////////////////////////=// +// +// PANIC (Force System Exit with Diagnostic Info) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Panics are the equivalent of the "blue screen of death" and should never +// happen in normal operation. Generally, it is assumed nothing under the +// user's control could fix or work around the issue, hence the main goal is +// to provide the most diagnostic information possible. +// +// So the best thing to do is to pass in whatever REBVAL* or REBSER* subclass +// (including REBARR*, REBCTX*, REBFUN*...) is the most useful "smoking gun": +// +// if (VAL_TYPE(value) == REB_VOID) +// panic (value); +// +// if (ARR_LEN(array) < 2) +// panic (array); +// +// Both the debug and release builds will spit out diagnostics of the item, +// along with the file and line number of the problem. The diagnostics are +// written in such a way that they give the "more likely to succeed" output +// first, and then get more aggressive to the point of possibly crashing by +// dereferencing corrupt memory which triggered the panic. The debug build +// diagnostics will be more exhaustive, but the release build gives some info. +// +// The most useful argument to panic is going to be a problematic value or +// series vs. a message (especially given that the file and line number are +// included in the report). But if no relevant smoking gun is available, a +// UTF-8 string can also be passed to panic...and it will terminate with that +// as a message: +// +// if (sizeof(foo) != 42) { +// panic ("invalid foo size"); +// +// /* this line will never be reached, because it +// immediately exited the process with a message */ +// } +// +// NOTE: It's desired that there be a space in `panic (...)` to make it look +// more "keyword-like" and draw attention to the fact it is a `noreturn` call. +// +#define panic(v) \ + Panic_Core((v), __FILE__, __LINE__); + +#define panic_at(v,file,line) \ + Panic_Core((v), (file), (line)); diff --git a/src/include/sys-typeset.h b/src/include/sys-typeset.h new file mode 100644 index 0000000000..db3509e50e --- /dev/null +++ b/src/include/sys-typeset.h @@ -0,0 +1,318 @@ +// +// File: %sys-typeset.h +// Summary: {Definitions for Typeset Values} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// A typeset is a collection of up to 62 of the REB_XXX types, implemented as +// a 64-bit bitset. The bits for REB_0 and REB_MAX_VOID can be used for +// special purposes, as these are not actual legal datatypes. +// +// !!! The limit of only being able to hold a set of 62 types is a temporary +// one, as user-defined types will require a different approach. Hence the +// best way to look at the bitset for built-in types is as an optimization +// for type-checking the common parameter cases. +// +// Though available to the user to manipulate directly as a TYPESET!, REBVALs +// of this category have another use in describing the fields of objects +// ("KEYS") or parameters of function frames ("PARAMS"). When used for that +// purpose, they not only list the legal types...but also hold a symbol for +// naming the field or parameter. R3-Alpha made these a special kind of WORD! +// called an "unword", but they lack bindings and have more technically +// in common with the evolving requirements of typesets. +// +// !!! At present, a TYPESET! created with MAKE TYPESET! cannot set the +// internal symbol. Nor can it set the extended flags, though that might +// someday be allowed with a syntax like: +// +// make typeset! [ string! integer!] +// + +enum Reb_Param_Class { + // + // `PARAM_CLASS_LOCAL` is a "pure" local, which will be set to void by + // argument fulfillment. It is indicated by a SET-WORD! in the function + // spec, or by coming after a tag in the function generators. + // + // !!! Initially these were indicated with TYPESET_FLAG_HIDDEN. That + // would allow the PARAM_CLASS to fit in just two bits (if there were + // no debug-purpose PARAM_CLASS_0) and free up a scarce typeset flag. + // But is it the case that hiding and localness should be independent? + // + PARAM_CLASS_LOCAL = 0, + + // `PARAM_CLASS_NORMAL` is cued by an ordinary WORD! in the function spec + // to indicate that you would like that argument to be evaluated normally. + // + // >> foo: function [a] [print [{a is} a] + // + // >> foo 1 + 2 + // a is 3 + // + // Special outlier EVAL/ONLY can be used to subvert this: + // + // >> eval/only :foo 1 + 2 + // a is 1 + // ** Script error: + operator is missing an argument + // + PARAM_CLASS_NORMAL = 0x01, + + // `PARAM_CLASS_HARD_QUOTE` is cued by a GET-WORD! in the function spec + // dialect. It indicates that a single value of content at the callsite + // should be passed through *literally*, without any evaluation: + // + // >> foo: function [:a] [print [{a is} a] + // + // >> foo 1 + 2 + // a is 1 + // + // >> foo (1 + 2) + // a is (1 + 2) + // + PARAM_CLASS_HARD_QUOTE = 0x02, // GET-WORD! in spec + + // `PARAM_CLASS_REFINEMENT` + // + PARAM_CLASS_REFINEMENT = 0x03, + + // `PARAM_CLASS_TIGHT` makes enfixed first arguments "lazy" and other + // arguments will use the DO_FLAG_NO_LOOKAHEAD. + // + // R3-Alpha's notion of infix OP!s changed the way parameters were + // gathered. On the right hand side, the argument was evaluated in a + // special mode in which further infix processing was not done. This + // meant that `1 + 2 * 3`, when fulfilling the 2 for the right side of +, + // would "blind" itself so that it would not chain forward and see the + // `* 3`. This gave rise to a distinct behavior from `1 + multiply 2 3`. + // A similar kind of "tightness" would happen with the left hand side, + // where `add 1 2 * 3` would be aggressive and evaluate it as + // `add 1 (2 * 3)` and not `(add 1 2) * 3`. + // + // Ren-C decouples this property so that it may be applied to any + // parameter, and calls it "tight". By default, however, expressions are + // completed as far as they can be on both the left and right hand side of + // enfixed expressions. + // + PARAM_CLASS_TIGHT = 0x04, + + // PARAM_CLASS_RETURN acts like a pure local, but is pre-filled with a + // definitionally-scoped function value that takes 1 arg and returns it. + // + PARAM_CLASS_RETURN = 0x05, + + // `PARAM_CLASS_SOFT_QUOTE` is cued by a LIT-WORD! in the function spec + // dialect. It quotes with the exception of GROUP!, GET-WORD!, and + // GET-PATH!...which will be evaluated: + // + // >> foo: function ['a] [print [{a is} a] + // + // >> foo 1 + 2 + // a is 1 + // + // >> foo (1 + 2) + // a is 3 + // + // Although possible to implement soft quoting with hard quoting, it is + // a convenient way to allow callers to "escape" a quoted context when + // they need to. + // + // Note: Value chosen for PCLASS_ANY_QUOTE_MASK in common with hard quote + // + PARAM_CLASS_SOFT_QUOTE = 0x06, + + // `PARAM_CLASS_LEAVE` acts like a pure local, but is pre-filled with a + // definitionally-scoped function value that takes 0 args and returns void + // + PARAM_CLASS_LEAVE = 0x07, + + PARAM_CLASS_MAX +}; + +#define PCLASS_ANY_QUOTE_MASK 0x02 + +#define PCLASS_NUM_BITS 3 + + +#ifdef NDEBUG + #define TYPESET_FLAG(n) \ + FLAGIT_LEFT(TYPE_SPECIFIC_BIT + (n)) +#else + #define TYPESET_FLAG(n) \ + (FLAGIT_LEFT(TYPE_SPECIFIC_BIT + (n)) | HEADERIZE_KIND(REB_TYPESET)) +#endif + +// Option flags used with GET_VAL_FLAG(). These describe properties of +// a value slot when it's constrained to the types in the typeset +// + +// Can't be reflected (set with PROTECT/HIDE) or local in spec as `foo:` +// +#define TYPESET_FLAG_HIDDEN TYPESET_FLAG(0) + +// Can't be bound to beyond the current bindings. +// +// !!! This flag was implied in R3-Alpha by TYPESET_FLAG_HIDDEN. However, +// the movement of SELF out of being a hardcoded keyword in the binding +// machinery made it start to be considered as being a by-product of the +// generator, and hence a "userspace" word (like definitional return). +// To avoid disrupting all object instances with a visible SELF, it was +// made hidden...which worked until a bugfix restored the functionality +// of checking to not bind to hidden things. UNBINDABLE is an interim +// solution to separate the property of bindability from visibility, as +// the SELF solution shakes out--so that SELF may be hidden but bind. +// +#define TYPESET_FLAG_UNBINDABLE TYPESET_FLAG(1) + +// !!! is the working name for the property of a function +// argument or local to have its data survive after the call is over. +// Much of the groundwork has been laid to allow this to be specified +// individually for each argument, but the feature currently is "all +// or nothing"--and implementation-wise corresponds to what R3-Alpha +// called CLOSURE!, with the deep-copy-per-call that entails. +// +// Hence if this property is applied, it will be applied to *all* of +// a function's arguments. +// +#define TYPESET_FLAG_DURABLE TYPESET_FLAG(2) + +// !!! This does not need to be on the typeset necessarily. See the +// VARARGS! type for what this is, which is a representation of the +// capture of an evaluation position. The type will also be checked but +// the value will not be consumed. +// +// Note the important distinction, that a variadic parameter and taking +// a VARARGS! type are different things. (A function may accept a +// variadic number of VARARGS! values, for instance.) +// +#define TYPESET_FLAG_VARIADIC TYPESET_FLAG(3) + +// !!! In R3-Alpha, there were only 8 type-specific bits...with the +// remaining bits "reserved for future use". This goes over the line +// with a 9th type-specific bit, which may or may not need review. +// It could just be that more type-specific bits is the future use. + +// Endability is distinct from optional, and it means that a parameter is +// willing to accept being at the end of the input. This means either +// an infix dispatch's left argument is missing (e.g. `do [+ 5]`) or an +// ordinary argument hit the end (e.g. the trick used for `>> help` when +// the arity is 1 usually as `>> help foo`) +// +#define TYPESET_FLAG_ENDABLE TYPESET_FLAG(4) + +// Operations when typeset is done with a bitset (currently all typesets) + +#define VAL_TYPESET_BITS(v) ((v)->payload.typeset.bits) + +#define TYPE_CHECK(v,n) \ + LOGICAL(VAL_TYPESET_BITS(v) & FLAGIT_KIND(n)) + +#define TYPE_SET(v,n) \ + ((VAL_TYPESET_BITS(v) |= FLAGIT_KIND(n)), NOOP) + +#define EQUAL_TYPESET(v,w) \ + (VAL_TYPESET_BITS(v) == VAL_TYPESET_BITS(w)) + + +// Name should be NULL unless typeset in object keylist or func paramlist + +inline static void INIT_TYPESET_NAME(RELVAL *typeset, REBSTR *str) { + assert(IS_TYPESET(typeset)); + typeset->extra.key_spelling = str; +} + +inline static REBSTR *VAL_KEY_SPELLING(const RELVAL *typeset) { + assert(IS_TYPESET(typeset)); + return typeset->extra.key_spelling; +} + +inline static REBSTR *VAL_KEY_CANON(const RELVAL *typeset) { + return STR_CANON(VAL_KEY_SPELLING(typeset)); +} + +inline static OPT_REBSYM VAL_KEY_SYM(const RELVAL *typeset) { + return STR_SYMBOL(VAL_KEY_SPELLING(typeset)); // mirrors canon's symbol +} + +#define VAL_PARAM_SPELLING(p) VAL_KEY_SPELLING(p) +#define VAL_PARAM_CANON(p) VAL_KEY_CANON(p) +#define VAL_PARAM_SYM(p) VAL_KEY_SYM(p) + +inline static enum Reb_Param_Class VAL_PARAM_CLASS(const RELVAL *v) { + assert(IS_TYPESET(v)); + return cast( + enum Reb_Param_Class, + MID_N_BITS(v->header.bits, PCLASS_NUM_BITS) + ); +} + +inline static void INIT_VAL_PARAM_CLASS(RELVAL *v, enum Reb_Param_Class c) { + CLEAR_N_MID_BITS(v->header.bits, PCLASS_NUM_BITS); + v->header.bits |= FLAGBYTE_MID(c); +} + + +// Macros for defining full bit masks + +#define ALL_BITS \ + ((REBCNT)(-1)) + +#ifdef HAS_LL_CONSTS + #define ALL_64 \ + ((REBU64)0xffffffffffffffffLL) +#else + #define ALL_64 \ + ((REBU64)0xffffffffffffffffL) +#endif + + +// !!! R3-Alpha made frequent use of these predefined typesets. In Ren-C +// they have been called into question, as to exactly how copying mechanics +// should work...whether a FUNCTION! should be duplicated when an object +// is made with one in its fields, for instance. + +#define TS_NOT_COPIED \ + (FLAGIT_KIND(REB_IMAGE) \ + | FLAGIT_KIND(REB_VECTOR) \ + | FLAGIT_KIND(REB_PORT)) + +#define TS_STD_SERIES \ + (TS_SERIES & ~TS_NOT_COPIED) + +#define TS_SERIES_OBJ \ + ((TS_SERIES | TS_CONTEXT) & ~TS_NOT_COPIED) + +#define TS_ARRAYS_OBJ \ + ((TS_ARRAY | TS_CONTEXT) & ~TS_NOT_COPIED) + +#define TS_CLONE \ + ((TS_SERIES | FLAGIT_KIND(REB_FUNCTION)) & ~TS_NOT_COPIED) + +#define TS_ANY_WORD \ + (FLAGIT_KIND(REB_WORD) \ + | FLAGIT_KIND(REB_SET_WORD) \ + | FLAGIT_KIND(REB_GET_WORD) \ + | FLAGIT_KIND(REB_REFINEMENT) \ + | FLAGIT_KIND(REB_LIT_WORD) \ + | FLAGIT_KIND(REB_ISSUE)) diff --git a/src/include/sys-value.h b/src/include/sys-value.h old mode 100644 new mode 100755 index 3ca5c1ced0..112e04bdaa --- a/src/include/sys-value.h +++ b/src/include/sys-value.h @@ -1,1132 +1,1506 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Summary: Value and Related Definitions -** Module: sys-value.h -** Author: Carl Sassenrath -** Notes: -** -***********************************************************************/ - -#ifndef VALUE_H -#define VALUE_H - -/*********************************************************************** -** -** REBOL Value Type -** -** This is used for all REBOL values. This is a forward -** declaration. See end of this file for actual structure. -** -***********************************************************************/ - -#pragma pack(4) - -typedef struct Reb_Header { -#ifdef ENDIAN_LITTLE - unsigned type:8; // datatype - unsigned opts:8; // special options - unsigned exts:8; // extensions to datatype - unsigned resv:8; // reserved for future +// +// File: %sys-value.h +// Summary: {Accessor Functions for properties of a Rebol Value} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This file provides basic accessors for value types. Because these +// accessors operate on REBVAL (or RELVAL) pointers, the inline functions need +// the complete struct definition available from all the payload types. +// +// See notes in %sys-rebval.h for the definition of the REBVAL structure. +// +// An attempt is made to group the accessors in sections. Some functions are +// defined in %c-value.c for the sake of the grouping. +// +// While some REBVALs are in C stack variables, most reside in the allocated +// memory block for a Rebol series. The memory block for a series can be +// resized and require a reallocation, or it may become invalid if the +// containing series is garbage-collected. This means that many pointers to +// REBVAL are unstable, and could become invalid if arbitrary user code +// is run...this includes values on the data stack, which is implemented as +// a series under the hood. (See %sys-stack.h) +// +// A REBVAL in a C stack variable does not have to worry about its memory +// address becoming invalid--but by default the garbage collector does not +// know that value exists. So while the address may be stable, any series +// it has in the payload might go bad. Use PUSH_GUARD_VALUE() to protect a +// stack variable's payload, and then DROP_GUARD_VALUE() when the protection +// is not needed. (You must always drop the last guard pushed.) +// +// For a means of creating a temporary array of GC-protected REBVALs, see +// the "chunk stack" in %sys-stack.h. This is used when building function +// argument frames, which means that the REBVAL* arguments to a function +// accessed via ARG() will be stable as long as the function is running. +// + + +//=////////////////////////////////////////////////////////////////////////=// +// +// DEBUG PROBE <== **THIS IS VERY USEFUL** +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The PROBE macro can be used in debug builds to mold a REBVAL much like the +// Rebol `probe` operation. It's actually polymorphic, and if you have +// a REBSER*, REBCTX*, or REBARR* it can be used with those as well. +// +// In order to make it easier to find out where a piece of debug spew is +// coming from, the file and line number will be output as well. +// +// Note: As a convenience, PROBE also flushes the `stdout` and `stderr` in +// case the debug build was using printf() to output contextual information. +// + +#if !defined(NDEBUG) + #define PROBE(v) \ + Probe_Core_Debug((v), __FILE__, __LINE__) +#endif + + +//=////////////////////////////////////////////////////////////////////////=// +// +// TRACKING PAYLOAD <== **THIS IS VERY USEFUL** +// +//=////////////////////////////////////////////////////////////////////////=// +// +// In the debug build, "Trash" cells (NODE_FLAG_FREE) can use their payload to +// store where and when they were initialized. This also applies to some +// datatypes like BLANK!, BAR!, LOGIC!, or void--since they only use their +// header bits, they can also use the payload for this in the debug build. +// +// (Note: The release build does not canonize unused bits of payloads, so +// they are left as random data in that case.) +// +// View this information in the debugging watchlist under the `track` union +// member of a value's payload. It is also reported by panic(). +// + +#if !defined NDEBUG + inline static void Set_Track_Payload_Debug( + RELVAL *v, const char *file, int line + ){ + v->payload.track.filename = file; + v->payload.track.line = line; + v->extra.do_count = TG_Do_Count; + } +#endif + + +#define VAL_ALL_BITS(v) ((v)->payload.all.bits) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// VALUE "KIND" (1 out of 64 different foundational types) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Every value has 6 bits reserved for its VAL_TYPE(). The reason only 6 +// are used is because low-level TYPESET!s are only 64-bits (so they can fit +// into a REBVAL payload, along with a key symbol to represent a function +// parameter). If there were more types, they couldn't be flagged in a +// typeset that fit in a REBVAL under that constraint. +// +// VAL_TYPE() should obviously not be called on uninitialized memory. But +// it should also not be called on an END marker, as those markers only +// guarantee the low bit as having Rebol-readable-meaning. In debug builds, +// this is asserted by VAL_TYPE_Debug. +// + +#define FLAGIT_KIND(t) \ + (cast(REBU64, 1) << (t)) // makes a 64-bit bitflag + +// While inline vs. macro doesn't usually matter much, debug builds won't +// inline this, and it's called *ALL* the time. Since it doesn't repeat its +// argument, it's not worth it to make it a function for slowdown caused. +// Also, don't bother checking using the `cast()` template in C++. +// +// !!! Technically this is wasting two bits in the header, because there are +// only 64 types that fit in a type bitset. Yet the sheer commonness of +// this operation makes bit masking expensive...and choosing the number of +// types based on what fits in a 64-bit mask is not necessarily the most +// future-proof concept in the first place. Use a full byte for speed. +// +#define VAL_TYPE_RAW(v) \ + ((enum Reb_Kind)(RIGHT_8_BITS((v)->header.bits))) + +#ifdef NDEBUG + #define VAL_TYPE(v) \ + VAL_TYPE_RAW(v) +#else + // To help speed up VAL_TYPE_Debug, we push the blank flag into the + // farthest right value bit...on a 32-bit architecture, this is going + // to be the 24th flag...pushing up against the rightmost 8-bits used + // for the value's type. The odds are on any given value this flag will + // not be set, but we still don't completely reserve it. + // + #define BLANK_FLAG_UNREADABLE_DEBUG \ + FLAGIT_LEFT(23) + + inline static enum Reb_Kind VAL_TYPE_Debug( + const RELVAL *v, const char *file, int line + ){ + // VAL_TYPE is called *a lot*, and this makes it a great place to do + // sanity checks in the debug build. But a debug build will not + // inline this function, and makes *no* optimizations. Using no + // stack space e.g. no locals) is ideal, and this front-loaded test + // keeps naive branching implementations from taking > 20% of runtime. + // + if ( + (v->header.bits & ( + NODE_FLAG_END + | NODE_FLAG_CELL + | NODE_FLAG_FREE + | BLANK_FLAG_UNREADABLE_DEBUG + )) == NODE_FLAG_CELL + ){ + return VAL_TYPE_RAW(v); + } + + if (v->header.bits & NODE_FLAG_END) { + printf("VAL_TYPE() called on END marker\n"); + panic_at (v, file, line); + } + + if (NOT(v->header.bits & NODE_FLAG_CELL)) { + printf("VAL_TYPE() called on non-cell\n"); + panic_at (v, file, line); + } + + if (v->header.bits & NODE_FLAG_FREE) { + printf("VAL_TYPE() called on trash cell\n"); + panic_at (v, file, line); + } + + assert(v->header.bits & BLANK_FLAG_UNREADABLE_DEBUG); + + if (VAL_TYPE_RAW(v) == REB_BLANK) { + printf("VAL_TYPE() called on unreadable BLANK!\n"); + panic_at (v, file, line); + } + + // Hopefully rare case... some other type that is using the same + // 24th-from-the-left bit as BLANK_FLAG_UNREADABLE_DEBUG, and it's + // set, but doesn't mean the type is actually unreadable. Avoid + // making this a common case, as it slows the debug build. + // + return VAL_TYPE_RAW(v); + } + + #define VAL_TYPE(v) \ + VAL_TYPE_Debug((v), __FILE__, __LINE__) +#endif + + +//=////////////////////////////////////////////////////////////////////////=// +// +// VALUE FLAGS +// +//=////////////////////////////////////////////////////////////////////////=// +// +// VALUE_FLAG_XXX flags are applicable to all types. Type-specific flags are +// named things like TYPESET_FLAG_XXX or WORD_FLAG_XXX and only apply to the +// type that they reference. Both use these XXX_VAL_FLAG accessors. +// + +#ifdef NDEBUG + #define SET_VAL_FLAGS(v,f) \ + (v)->header.bits |= (f) + + #if defined(__cplusplus) && __cplusplus >= 201103L + // + // In the C++ release build we sanity check that only one bit is set. + // The assert is done at compile-time, you must use a constant flag. + // If you need dynamic flag checking, use GET_VAL_FLAGS even for one. + // + // Note this is not included as a runtime assert because it is costly, + // and it's not included in the debug build because the flags are + // "contaminated" with additional data that's hard to mask out at + // compile-time due to the weirdness of CLEAR_8_RIGHT_BITS. This + // pattern does not catch bad flag checks in asserts. Review. + + template + inline static void SET_VAL_FLAG_cplusplus(RELVAL *v) { + static_assert( + f && (f & (f - 1)) == 0, // only one bit is set + "use SET_VAL_FLAGS() to set multiple bits" + ); + v->header.bits |= f; + } + #define SET_VAL_FLAG(v,f) \ + SET_VAL_FLAG_cplusplus(v) + + template + inline static REBOOL GET_VAL_FLAG_cplusplus(const RELVAL *v) { + static_assert( + f && (f & (f - 1)) == 0, // only one bit is set + "use ANY_VAL_FLAGS() or ALL_VAL_FLAGS() to test multiple bits" + ); + return LOGICAL(v->header.bits & f); + } + #define GET_VAL_FLAG(v,f) \ + GET_VAL_FLAG_cplusplus(v) + #else + #define SET_VAL_FLAG(v,f) \ + SET_VAL_FLAGS((v), (f)) + + #define GET_VAL_FLAG(v, f) \ + LOGICAL((v)->header.bits & (f)) + #endif + + #define ANY_VAL_FLAGS(v,f) \ + LOGICAL(((v)->header.bits & (f)) != 0) + + #define ALL_VAL_FLAGS(v,f) \ + LOGICAL(((v)->header.bits & (f)) == (f)) + + #define CLEAR_VAL_FLAGS(v,f) \ + ((v)->header.bits &= ~(f)) + + #define CLEAR_VAL_FLAG(v,f) \ + CLEAR_VAL_FLAGS((v), (f)) +#else + // For safety in the debug build, all the type-specific flags include a + // type (or type representing a category) as part of the flag. This type + // is checked first, and then masked out to use the single-bit-flag value + // which is intended. + // + // But flag testing routines are called *a lot*, and debug builds do not + // inline functions. So it's worth doing a sketchy macro so this somewhat + // borderline assert doesn't wind up taking up 20% of the debug's runtime. + // + #define CHECK_VALUE_FLAGS_EVIL_MACRO_DEBUG(flags) \ + REBUPT category = RIGHT_8_BITS(flags); \ + assert(kind > REB_0 && kind <= REB_MAX); \ + if (category != REB_0) { \ + if (kind != category) { \ + if (category == REB_WORD) \ + assert(ANY_WORD_KIND(kind)); \ + else if (category == REB_OBJECT) \ + assert(ANY_CONTEXT_KIND(kind)); \ + else \ + assert(FALSE); \ + } \ + CLEAR_8_RIGHT_BITS(flags); \ + } \ + + inline static void SET_VAL_FLAGS(RELVAL *v, REBUPT f) { + enum Reb_Kind kind = VAL_TYPE_RAW(v); + CHECK_VALUE_FLAGS_EVIL_MACRO_DEBUG(f); + v->header.bits |= f; + } + + inline static void SET_VAL_FLAG(RELVAL *v, REBUPT f) { + enum Reb_Kind kind = VAL_TYPE_RAW(v); + CHECK_VALUE_FLAGS_EVIL_MACRO_DEBUG(f); + v->header.bits |= f; + } + + inline static REBOOL GET_VAL_FLAG(const RELVAL *v, REBUPT f) { + enum Reb_Kind kind = VAL_TYPE_RAW(v); + CHECK_VALUE_FLAGS_EVIL_MACRO_DEBUG(f); + return LOGICAL(v->header.bits & f); + } + + inline static REBOOL ANY_VAL_FLAGS(const RELVAL *v, REBUPT f) { + enum Reb_Kind kind = VAL_TYPE_RAW(v); + CHECK_VALUE_FLAGS_EVIL_MACRO_DEBUG(f); + return LOGICAL((v->header.bits & f) != 0); + } + + inline static REBOOL ALL_VAL_FLAGS(const RELVAL *v, REBUPT f) { + enum Reb_Kind kind = VAL_TYPE_RAW(v); + CHECK_VALUE_FLAGS_EVIL_MACRO_DEBUG(f); + return LOGICAL((v->header.bits & f) == f); + } + + inline static void CLEAR_VAL_FLAGS(RELVAL *v, REBUPT f) { + enum Reb_Kind kind = VAL_TYPE_RAW(v); + CHECK_VALUE_FLAGS_EVIL_MACRO_DEBUG(f); + v->header.bits &= ~f; + } + + inline static void CLEAR_VAL_FLAG(RELVAL *v, REBUPT f) { + enum Reb_Kind kind = VAL_TYPE_RAW(v); + CHECK_VALUE_FLAGS_EVIL_MACRO_DEBUG(f); + assert(f && (f & (f - 1)) == 0); // checks that only one bit is set + v->header.bits &= ~f; + } +#endif + +#define NOT_VAL_FLAG(v,f) \ + NOT(GET_VAL_FLAG((v), (f))) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// CELL WRITABILITY AND SETUP +// +//=////////////////////////////////////////////////////////////////////////=// +// +// VAL_RESET_HEADER clears out the header of *most* bits, setting it to a +// new type. +// +// The value is expected to already be "pre-formatted" with the NODE_FLAG_CELL +// bit, so that is left as-is. It is also expected that VALUE_FLAG_STACK has +// been set if the value is stack-based (e.g. on the C stack or in a frame), +// so that is left as-is also. +// +// Asserting writiablity helps avoid very bad catastrophies that might ensue +// if "implicit end markers" could be overwritten. These are the ENDs that +// are actually other bitflags doing double duty inside a data structure, and +// there is no REBVAL storage backing the position. +// +// (A fringe benefit is catching writes to other unanticipated locations.) +// +inline static void VAL_RESET_HEADER_common( // don't call directly + RELVAL *v, + enum Reb_Kind kind, + REBUPT extra_flags +) { + v->header.bits &= CELL_MASK_RESET; + v->header.bits |= HEADERIZE_KIND(kind) | extra_flags; +} + +#ifdef NDEBUG + #define VAL_RESET_HEADER_EXTRA(v,kind,extra) \ + VAL_RESET_HEADER_common((v), (kind), (extra)) + + #define ASSERT_CELL_WRITABLE(v,file,line) \ + NOOP + + // Note no VALUE_FLAG_STACK + #define INIT_CELL(v) \ + (v)->header.bits = NODE_FLAG_NODE | NODE_FLAG_FREE | NODE_FLAG_CELL +#else + inline static void Assert_Cell_Writable( + const RELVAL *v, + const char *file, + int line + ){ + // REBVALs should not be written at addresses that do not match the + // alignment of the processor. Checks modulo the size of an unsigned + // integer the same size as a platform pointer (REBUPT => uintptr_t) + // + assert(cast(REBUPT, v) % sizeof(REBUPT) == 0); + + if (NOT(v->header.bits & NODE_FLAG_CELL)) { + printf("Non-cell passed to writing routine\n"); + panic_at (v, file, line); + } + } + + #define ASSERT_CELL_WRITABLE(v,file,line) \ + Assert_Cell_Writable((v), (file), (line)) + + inline static void VAL_RESET_HEADER_EXTRA_Debug( + RELVAL *v, + enum Reb_Kind kind, + REBUPT extra, + const char *file, + int line + ){ + ASSERT_CELL_WRITABLE(v, file, line); + + // The debug build puts some extra type information onto flags + // which needs to be cleared out. (e.g. WORD_FLAG_BOUND has the bit + // pattern for REB_WORD inside of it, to help make sure that flag + // doesn't get used with things that aren't words). + // + CHECK_VALUE_FLAGS_EVIL_MACRO_DEBUG(extra); + + VAL_RESET_HEADER_common(v, kind, extra); + } + + #define VAL_RESET_HEADER_EXTRA(v,kind,extra) \ + VAL_RESET_HEADER_EXTRA_Debug((v), (kind), (extra), __FILE__, __LINE__) + + inline static void INIT_CELL_Debug( + RELVAL *v, const char *file, int line + ){ + // Note: no VALUE_FLAG_STACK + // + v->header.bits = + NODE_FLAG_NODE | NODE_FLAG_FREE | NODE_FLAG_CELL + | HEADERIZE_KIND(REB_MAX + 1); + Set_Track_Payload_Debug(v, file, line); + } + + #define INIT_CELL(v) \ + INIT_CELL_Debug((v), __FILE__, __LINE__) +#endif + +#define VAL_RESET_HEADER(v,t) \ + VAL_RESET_HEADER_EXTRA((v), (t), 0) + +inline static void VAL_SET_TYPE_BITS(RELVAL *v, enum Reb_Kind kind) { + // + // Note: Only use if you are sure the new type payload is in sync with + // the type and bits (e.g. changing ANY-WORD! to another ANY-WORD!). + // Otherwise the value-specific flags might be misinterpreted. + // + ASSERT_CELL_WRITABLE(v, __FILE__, __LINE__); + CLEAR_8_RIGHT_BITS(v->header.bits); + v->header.bits |= HEADERIZE_KIND(kind); +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// TRASH CELLS +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Trash is a cell (marked by NODE_FLAG_CELL) with NODE_FLAG_FREE set. To +// prevent it from being inspected while it's in an invalid state, VAL_TYPE +// used on a trash cell will assert in the debug build. +// +// The garbage collector is not tolerant of trash. +// + +#ifdef NDEBUG + #define TRASH_CELL_IF_DEBUG(v) \ + NOOP +#else + inline static void Set_Trash_Debug( + RELVAL *v, + const char *file, + int line + ) { + ASSERT_CELL_WRITABLE(v, file, line); + + v->header.bits &= CELL_MASK_RESET; + v->header.bits |= NODE_FLAG_FREE | HEADERIZE_KIND(REB_MAX + 1); + + Set_Track_Payload_Debug(v, file, line); + } + + #define TRASH_CELL_IF_DEBUG(v) \ + Set_Trash_Debug((v), __FILE__, __LINE__) + + inline static REBOOL IS_TRASH_DEBUG(const RELVAL *v) { + assert(v->header.bits & NODE_FLAG_CELL); + if (NOT(v->header.bits & NODE_FLAG_FREE)) + return FALSE; + assert(LEFT_8_BITS(v->header.bits) == TRASH_CELL_BYTE); // bad UTF-8 + assert(VAL_TYPE_RAW(v) == REB_MAX + 1); + return TRUE; + } +#endif + + +//=////////////////////////////////////////////////////////////////////////=// +// +// END marker (not a value type, only writes `struct Reb_Value_Flags`) +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Historically Rebol arrays were always one value longer than their maximum +// content, and this final slot was used for a REBVAL type called END!. +// Like a null terminator in a C string, it was possible to start from one +// point in the series and traverse to find the end marker without needing +// to look at the length (though the length in the series header is maintained +// in sync, also). +// +// Ren-C changed this so that end is not a data type, but a header bit. +// See NODE_FLAG_END for an explanation of this choice--and how it means +// a full cell's worth of size is not needed to terminate. +// +// VAL_TYPE() and many other operations will panic if they are used on an END +// cell. Yet the special unwritable system value END is the size of a REBVAL, +// but does not carry NODE_FLAG_CELL. Since it is a node, it can be more +// useful to return from routines that return REBVAL* than a NULL, because it +// can have its header dereferenced to check its type in a single test... +// as VAL_TYPE_OR_0() will return REB_0 for the system END marker. (It's +// actually possible if you're certain you have a NODE_FLAG_CELL to know that +// the type of an end marker is REB_0, but one can rarely exploit that.) +// + +#ifdef NDEBUG + #define IS_END(v) \ + LOGICAL((v)->header.bits & NODE_FLAG_END) + + inline static void SET_END(RELVAL *v) { + v->header.bits &= CELL_MASK_RESET; // leaves flags _CELL, _NODE, etc. + v->header.bits |= NODE_FLAG_END | HEADERIZE_KIND(REB_0); + } + + // Warning: Only use on valid non-END REBVAL -or- on global END value + // + #define VAL_TYPE_OR_0(v) \ + VAL_TYPE_RAW(v) #else - unsigned resv:8; // reserved for future - unsigned exts:8; // extensions to datatype - unsigned opts:8; // special options - unsigned type:8; // datatype + inline static REBOOL IS_END_Debug( + const RELVAL *v, + const char *file, + int line + ){ + if (v->header.bits & NODE_FLAG_FREE) { + printf("IS_END() called on garbage\n"); + panic_at(v, file, line); + } + + // Do a fast common case. We check for freeness but not cellness, as + // not all END markers are full cells. + // + if ((v->header.bits & (NODE_FLAG_FREE | NODE_FLAG_END)) == 0) + return FALSE; + + if (v->header.bits & NODE_FLAG_END) { + if (v->header.bits & NODE_FLAG_CELL) + assert(VAL_TYPE_RAW(v) == REB_0); + else { + // Can't make any guarantees about what's in the type slot of + // non-cell ENDs, they only commit a bit or two and use the + // rest how they wish! See Init_Endlike_Header() + } + return TRUE; + } + + // Anything that's not an END called by this routine *must* be a cell + // + assert(v->header.bits & NODE_FLAG_CELL); + return FALSE; + } + + #define IS_END(v) \ + IS_END_Debug((v), __FILE__, __LINE__) + + inline static void SET_END_Debug(RELVAL *v, const char *file, int line) { + ASSERT_CELL_WRITABLE(v, file, line); + v->header.bits &= CELL_MASK_RESET; // leaves NODE_FLAG_CELL, etc. + v->header.bits |= NODE_FLAG_END | HEADERIZE_KIND(REB_0); + Set_Track_Payload_Debug(v, file, line); + } + + #define SET_END(v) \ + SET_END_Debug((v), __FILE__, __LINE__) + + inline static enum Reb_Kind VAL_TYPE_OR_0_Debug( + const RELVAL *v, + const char *file, + int line + ){ + if (v->header.bits & NODE_FLAG_END) { + if (v != END) { + printf("VAL_TYPE_OR_0 called on end that isn't -the- END"); + panic_at(v, file, line); + } + return VAL_TYPE_RAW(v); // asserted as REB_0 at startup for END + } + + return VAL_TYPE_Debug(v, file, line); + } + + // Warning: Only use on valid non-END REBVAL -or- on global END value + // + #define VAL_TYPE_OR_0(v) \ + VAL_TYPE_OR_0_Debug((v), __FILE__, __LINE__) #endif -} REBHED; -struct Reb_Value; -typedef struct Reb_Value REBVAL; -typedef struct Reb_Series REBSER; +#define NOT_END(v) \ + NOT(IS_END(v)) + + + +//=////////////////////////////////////////////////////////////////////////=// +// +// VOID CELLS +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Voids are a transient product of evaluation (e.g. the result of `do []`). +// They cannot be stored in BLOCK!s that are seen by the user, and if a +// variable is assigned a void cell then that variable is "unset". +// +// Void is thus not considered to be a "value type", but a bit pattern used to +// mark cells as not containing any value at all. It uses REB_MAX, because +// that is one past the range of valid REB_XXX values in the enumeration +// created for the actual types. +// + +#define VOID_CELL \ + c_cast(const REBVAL*, &PG_Void_Cell[0]) + +#define IS_VOID(v) \ + LOGICAL(VAL_TYPE(v) == REB_MAX_VOID) + +#define Init_Void(v) \ + VAL_RESET_HEADER(v, REB_MAX_VOID) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// BAR! and LIT-BAR! +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The "expression barrier" is denoted by a lone vertical bar `|`. It +// has the special property that literals used directly will be rejected +// as a source for argument fulfillment. BAR! that comes from evaluations +// can be passed as a parameter, however: +// +// append [a b c] | [d e f] print "Hello" ;-- will cause an error +// append [a b c] [d e f] | print "Hello" ;-- is legal +// append [a b c] first [|] ;-- is legal +// append [a b c] '| ;-- is legal +// + +#define BAR_VALUE \ + c_cast(const REBVAL*, &PG_Bar_Value[0]) + +#define Init_Bar(v) \ + VAL_RESET_HEADER((v), REB_BAR) + +#define Init_Lit_Bar(v) \ + VAL_RESET_HEADER((v), REB_LIT_BAR) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// BLANK! +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Unlike a void cell, blank values are inactive. They do not cause errors +// when they are used in situations like the condition of an IF statement. +// Instead they are considered to be false--like the LOGIC! #[false] value. +// So blank is considered to be the other "conditionally false" value. +// +// Only those two values are conditionally false in Rebol, and testing for +// conditional truth and falsehood is frequent. Hence in addition to its +// type, BLANK! also carries a header bit that can be checked for conditional +// falsehood, to save on needing to separately test the type. +// +// In the debug build, it is possible to make an "unreadable" blank. This +// will behave neutrally as far as the garbage collector is concerned, so +// it can be used as a placeholder for a value that will be filled in at +// some later time--spanning an evaluation. But if the special IS_UNREADABLE +// checks are not used, it will not respond to IS_BLANK() and will also +// refuse VAL_TYPE() checks. This is useful anytime a placeholder is needed +// in a slot temporarily where the code knows it's supposed to come back and +// fill in the correct thing later...where the asserts serve as a reminder +// if that fill in never happens. +// + +#define BLANK_VALUE \ + c_cast(const REBVAL*, &PG_Blank_Value[0]) + +#define Init_Blank(v) \ + VAL_RESET_HEADER_EXTRA((v), REB_BLANK, VALUE_FLAG_CONDITIONAL_FALSE) + +#ifdef NDEBUG + #define SET_UNREADABLE_BLANK(v) \ + Init_Blank(v) + + #define IS_BLANK_RAW(v) \ + IS_BLANK(v) + + #define IS_UNREADABLE_IF_DEBUG(v) \ + FALSE + + #define SINK(v) \ + cast(REBVAL*, (v)) +#else + #define SET_UNREADABLE_BLANK(v) \ + VAL_RESET_HEADER_EXTRA((v), REB_BLANK, \ + VALUE_FLAG_CONDITIONAL_FALSE | BLANK_FLAG_UNREADABLE_DEBUG) + + inline static REBOOL IS_BLANK_RAW(const RELVAL *v) { + return LOGICAL(VAL_TYPE_RAW(v) == REB_BLANK); + } + + inline static REBOOL IS_UNREADABLE_IF_DEBUG(const RELVAL *v) { + if (NOT(VAL_TYPE_RAW(v) == REB_BLANK)) + return FALSE; + return LOGICAL(v->header.bits & BLANK_FLAG_UNREADABLE_DEBUG); + } + + // "Sinking" a value is like trashing it in the debug build at the moment + // of knowing that it will ultimately be overwritten. This avoids + // any accidental usage of the target cell's contents before the overwrite + // winds up happening. + // + // It's slightly different than "trashing", because if the node was valid + // before, then it would have been safe for the GC to visit. So this + // doesn't break that invariant...if the node was invalid it stays + // invalid, but if it was valid it is turned into an unreadable blank, + // which overwrites all the cell fields (with tracking info) and will + // trigger errors through VAL_TYPE() if it's used. + // + inline static REBVAL *Sink_Debug( + RELVAL *v, + const char *file, + int line + ) { + ASSERT_CELL_WRITABLE(v, file, line); + + if (NOT(v->header.bits & NODE_FLAG_FREE)) { + VAL_RESET_HEADER_EXTRA_Debug( + v, + REB_BLANK, + VALUE_FLAG_CONDITIONAL_FALSE | BLANK_FLAG_UNREADABLE_DEBUG, + file, + line + ); + } + else { + // already trash, don't need to mess with the header + } + + Set_Track_Payload_Debug(v, file, line); + + return cast(REBVAL*, v); // used by SINK, but not TRASH_CELL_IF_DEBUG + } + + #define SINK(v) \ + Sink_Debug((v), __FILE__, __LINE__) + +#endif -// Value type identifier (generally, should be handled as integer): -#define VAL_TYPE(v) ((v)->flags.flags.type) // get only type, not flags -#define SET_TYPE(v,t) ((v)->flags.flags.type = (t)) // set only type, not flags -#define VAL_SET(v,t) ((v)->flags.header = (t)) // set type, clear all flags -// Note: b-init.c verifies that lower 8 bits of header = flags.type - -// Clear type identifier: -#define SET_END(v) VAL_SET(v, 0) - -// Value option flags: -enum { - OPTS_LINE = 0, // Line break occurs before this value - OPTS_LOCK, // Lock word from modification - OPTS_REVAL, // Reevaluate result value - OPTS_UNWORD, // Not a normal word - OPTS_TEMP, // Temporary flag - variety of uses - OPTS_HIDE, // Hide the word -}; - -#define VAL_OPTS(v) ((v)->flags.flags.opts) -#define VAL_SET_OPT(v,n) SET_FLAG(VAL_OPTS(v), n) -#define VAL_GET_OPT(v,n) GET_FLAG(VAL_OPTS(v), n) -#define VAL_CLR_OPT(v,n) CLR_FLAG(VAL_OPTS(v), n) - -#define VAL_GET_LINE(v) VAL_GET_OPT((v), OPTS_LINE) -#define VAL_SET_LINE(v) VAL_SET_OPT((v), OPTS_LINE) -#define VAL_CLR_LINE(v) VAL_CLR_OPT((v), OPTS_LINE) - -#define VAL_PROTECTED(v) VAL_GET_OPT((v), OPTS_LOCK) - -// Used for datatype-dependent data (e.g. op! stores action!) -#define VAL_GET_EXT(v) ((v)->flags.flags.exts) -#define VAL_SET_EXT(v,n) ((v)->flags.flags.exts = (n)) - -#define IS_SET(v) (VAL_TYPE(v) > REB_UNSET) -#define IS_SCALAR(v) (VAL_TYPE(v) <= REB_DATE) - - -/*********************************************************************** -** -** DATATYPE - Datatype or pseudo-datatype -** -***********************************************************************/ - -typedef struct Reb_Type { - REBINT type; // base type - REBSER *spec; -// REBINT min_type; -// REBINT max_type; -} REBTYP; - -#define VAL_DATATYPE(v) ((v)->data.datatype.type) -#define VAL_TYPE_SPEC(v) ((v)->data.datatype.spec) - -//#define VAL_MIN_TYPE(v) ((v)->data.datatype.min_type) -//#define VAL_MAX_TYPE(v) ((v)->data.datatype.max_type) -#define IS_OF_DATATYPE(v,t) (IS_DATATYPE(v) && (VAL_DATATYPE(v) == (t))) -#define NO_TYPE (0) - - -/*********************************************************************** -** -** NUMBERS - Integer and other simple scalars -** -***********************************************************************/ - -#define SET_UNSET(v) VAL_SET(v, REB_UNSET) - -#define SET_NONE(v) VAL_SET(v, REB_NONE) -#define NONE_VALUE ROOT_NONEVAL - -#define VAL_INT32(v) (REBINT)((v)->data.integer) -#define VAL_INT64(v) ((v)->data.integer) -#define VAL_UNT64(v) ((v)->data.unteger) -#define SET_INTEGER(v,n) VAL_SET(v, REB_INTEGER), ((v)->data.integer) = (n) -#define SET_INT32(v,n) ((v)->data.integer) = (REBINT)(n) - -#define MAX_CHAR 0xffff -#define VAL_CHAR(v) ((v)->data.uchar) -#define SET_CHAR(v,n) VAL_SET(v, REB_CHAR), VAL_CHAR(v) = (REBUNI)(n) - -#define IS_NUMBER(v) (VAL_TYPE(v) == REB_INTEGER || VAL_TYPE(v) == REB_DECIMAL) - - -/*********************************************************************** -** -** DECIMAL, MONEY -- Includes denomination and amount -** -***********************************************************************/ - -#define VAL_DECIMAL(v) ((v)->data.decimal) -#define SET_DECIMAL(v,n) VAL_SET(v, REB_DECIMAL), VAL_DECIMAL(v) = (n) - -typedef deci REBDCI; -#define VAL_DECI(v) ((v)->data.deci) -#define SET_MONEY(v,n) VAL_SET(v, REB_MONEY), VAL_DECI(v) = (n) - -#ifdef not_used -typedef struct Reb_Decimal { - REBDEC number; - REBYTE denom[4]; -} REBMNY; -#define VAL_MONEY(v) ((v)->data.money) -#define VAL_MONEY_DENOM(v) //((v)->data.money.denom) -#define VAL_MONEY_AMOUNT(v) ((v)->data.money.amount) +//=////////////////////////////////////////////////////////////////////////=// +// +// LOGIC! +// +//=////////////////////////////////////////////////////////////////////////=// +// +// A logic can be either true or false. For purposes of optimization, logical +// falsehood is indicated by one of the value option bits in the header--as +// opposed to in the value payload. This means it can be tested quickly, and +// that a single check can test for both BLANK! and logic false. +// +// Conditional truth and falsehood allows an interpretation where a BLANK! +// is a "falsey" value as well. +// + +#define FALSE_VALUE \ + c_cast(const REBVAL*, &PG_False_Value[0]) + +#define TRUE_VALUE \ + c_cast(const REBVAL*, &PG_True_Value[0]) + +#define Init_Logic(v,b) \ + VAL_RESET_HEADER_EXTRA((v), REB_LOGIC, \ + (b) ? 0 : VALUE_FLAG_CONDITIONAL_FALSE) + +#ifdef NDEBUG + #define IS_CONDITIONAL_FALSE(v) \ + GET_VAL_FLAG((v), VALUE_FLAG_CONDITIONAL_FALSE) +#else + inline static REBOOL IS_CONDITIONAL_FALSE_Debug( + const RELVAL *v, const char *file, int line + ){ + if (IS_VOID(v)) { + printf("Conditional true/false test on void\n"); + panic_at (v, file, line); + } + return GET_VAL_FLAG(v, VALUE_FLAG_CONDITIONAL_FALSE); + } + + #define IS_CONDITIONAL_FALSE(v) \ + IS_CONDITIONAL_FALSE_Debug((v), __FILE__, __LINE__) #endif +#define IS_CONDITIONAL_TRUE(v) \ + NOT(IS_CONDITIONAL_FALSE(v)) // macro gets file + line # in debug build + +// Although a BLOCK! value is true, some constructs are safer by not allowing +// literal blocks. e.g. `if [x] [print "this is not safe"`. The evaluated +// bit can let these instances be distinguished. Note that making *all* +// evaluations safe would be limiting, e.g. `foo: any [false-thing []]`. +// +inline static REBOOL IS_CONDITIONAL_TRUE_SAFE(const REBVAL *v) { + if (IS_BLOCK(v)) { + if (GET_VAL_FLAG(v, VALUE_FLAG_UNEVALUATED)) + fail (Error_Block_Conditional_Raw(v)); + + return TRUE; + } + return IS_CONDITIONAL_TRUE(v); +} + +inline static REBOOL VAL_LOGIC(const RELVAL *v) { + assert(IS_LOGIC(v)); + return NOT_VAL_FLAG((v), VALUE_FLAG_CONDITIONAL_FALSE); +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// DATATYPE! +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Note: R3-Alpha's notion of a datatype has not been revisited very much in +// Ren-C. The unimplemented UTYPE! user-defined type concept was removed +// for simplification, pending a broader review of what was needed. +// +// %words.r is arranged so that symbols for types are at the start +// Although REB_0 is 0 and the 0 REBCNT used for symbol IDs is reserved +// for "no symbol"...this is okay, because void is not a value type and +// should not have a symbol. +// +// !!! Consider the naming once all legacy TYPE? calls have been converted +// to TYPE-OF. TYPE! may be a better name, though possibly KIND! would be +// better if user types suggest that TYPE-OF can potentially return some +// kind of context (might TYPE! be an ANY-CONTEXT!, with properties like +// MIN-VALUE and MAX-VALUE, for instance). +// + +#define VAL_TYPE_KIND(v) \ + ((v)->payload.datatype.kind) + +#define VAL_TYPE_SPEC(v) \ + ((v)->payload.datatype.spec) + +#define IS_KIND_SYM(s) \ + ((s) < cast(REBSYM, REB_MAX)) + +inline static enum Reb_Kind KIND_FROM_SYM(REBSYM s) { + assert(IS_KIND_SYM(s)); + return cast(enum Reb_Kind, cast(int, (s))); +} + +#define SYM_FROM_KIND(k) \ + cast(REBSYM, cast(enum Reb_Kind, (k))) + +#define VAL_TYPE_SYM(v) \ + SYM_FROM_KIND((v)->payload.datatype.kind) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// CHAR! +// +//=////////////////////////////////////////////////////////////////////////=// + +#define MAX_CHAR 0xffff + +#define VAL_CHAR(v) \ + ((v)->payload.character) + +inline static void Init_Char(RELVAL *v, REBUNI uni) { + VAL_RESET_HEADER(v, REB_CHAR); + VAL_CHAR(v) = uni; +} + +#define SPACE_VALUE \ + (ROOT_SPACE_CHAR) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// INTEGER! +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Integers in Rebol were standardized to use a compiler-provided 64-bit +// value. This was formally added to the spec in C99, but many compilers +// supported it before that. +// +// !!! 64-bit extensions were added by the "rebolsource" fork, with much of +// the code still written to operate on 32-bit values. Since the standard +// unit of indexing and block length counts remains 32-bit in that 64-bit +// build at the moment, many lingering references were left that operated +// on 32-bit values. To make this clearer, the macros have been renamed +// to indicate which kind of integer they retrieve. However, there should +// be a general review for reasoning, and error handling + overflow logic +// for these cases. +// + +#if defined(NDEBUG) || !defined(__cplusplus) + #define VAL_INT64(v) \ + ((v)->payload.integer) +#else + // allows an assert, but also lvalue: `VAL_INT64(v) = xxx` + // + inline static REBI64 & VAL_INT64(RELVAL *v) { // C++ reference type + assert(IS_INTEGER(v)); + return v->payload.integer; + } + inline static REBI64 VAL_INT64(const RELVAL *v) { + assert(IS_INTEGER(v)); + return v->payload.integer; + } +#endif -/*********************************************************************** -** -** DATE and TIME -** -***********************************************************************/ +inline static void Init_Integer(RELVAL *v, REBI64 i64) { + VAL_RESET_HEADER(v, REB_INTEGER); + v->payload.integer = i64; +} + +#define VAL_INT32(v) \ + cast(REBINT, VAL_INT64(v)) + +#define VAL_UNT32(v) \ + cast(REBCNT, VAL_INT64(v)) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// DECIMAL! and PERCENT! +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Implementation-wise, the decimal type is a `double`-precision floating +// point number in C (typically 64-bit). The percent type uses the same +// payload, and is currently extracted with VAL_DECIMAL() as well. +// +// !!! Calling a floating point type "decimal" appears based on Rebol's +// original desire to use familiar words and avoid jargon. It has however +// drawn criticism from those who don't think it correctly conveys floating +// point behavior, expecting something else. Red has renamed the type +// FLOAT! which may be a good idea. +// + +#if defined(NDEBUG) || !defined(__cplusplus) + #define VAL_DECIMAL(v) \ + ((v)->payload.decimal) +#else + // allows an assert, but also lvalue: `VAL_DECIMAL(v) = xxx` + // + inline static REBDEC & VAL_DECIMAL(RELVAL *v) { // C++ reference type + assert(IS_DECIMAL(v) || IS_PERCENT(v)); + return v->payload.decimal; + } + inline static REBDEC VAL_DECIMAL(const RELVAL *v) { + assert(IS_DECIMAL(v) || IS_PERCENT(v)); + return v->payload.decimal; + } +#endif -typedef struct reb_ymdz { -#ifdef ENDIAN_LITTLE - REBINT zone:7; // +/-15:00 res: 0:15 - REBCNT day:5; - REBCNT month:4; - REBCNT year:16; +inline static void Init_Decimal(RELVAL *v, REBDEC d) { + VAL_RESET_HEADER(v, REB_DECIMAL); + v->payload.decimal = d; +} + +inline static void Init_Percent(RELVAL *v, REBDEC d) { + VAL_RESET_HEADER(v, REB_PERCENT); + v->payload.decimal = d; +} + + +// !!! There was an IS_NUMBER() macro defined in R3-Alpha which only covered +// REB_INTEGER and REB_DECIMAL. But ANY-NUMBER! the typeset included PERCENT! +// so this adds that and gets rid of IS_NUMBER() +// +inline static REBOOL ANY_NUMBER(const RELVAL *v) { + return LOGICAL( + VAL_TYPE(v) == REB_INTEGER + || VAL_TYPE(v) == REB_DECIMAL + || VAL_TYPE(v) == REB_PERCENT + ); +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// MONEY! +// +//=////////////////////////////////////////////////////////////////////////=// +// +// R3-Alpha's MONEY! type is "unitless" currency, such that $10/$10 = $1 +// (and not 1). This is because the feature in Rebol2 of being able to +// store the ISO 4217 code (~15 bits) was not included: +// +// https://en.wikipedia.org/wiki/ISO_4217 +// +// According to @Ladislav: +// +// "The money datatype is neither a bignum, nor a fixpoint arithmetic. +// It actually is unnormalized decimal floating point." +// +// !!! The naming of "deci" used by MONEY! as "decimal" is a confusing overlap +// with DECIMAL!, although that name may be changing also. +// + +inline static deci VAL_MONEY_AMOUNT(const RELVAL *v) { + deci amount; + amount.m0 = v->extra.m0; + amount.m1 = v->payload.money.m1; + amount.m2 = v->payload.money.m2; + amount.s = v->payload.money.s; + amount.e = v->payload.money.e; + return amount; +} + +inline static void Init_Money(RELVAL *v, deci amount) { + VAL_RESET_HEADER(v, REB_MONEY); + v->extra.m0 = amount.m0; + v->payload.money.m1 = amount.m1; + v->payload.money.m2 = amount.m2; + v->payload.money.s = amount.s; + v->payload.money.e = amount.e; +} + + +//=////////////////////////////////////////////////////////////////////////=// +// +// TIME! +// +//=////////////////////////////////////////////////////////////////////////=// + +#if defined(NDEBUG) || !defined(__cplusplus) + #define VAL_NANO(v) \ + (v)->payload.time.nanoseconds #else - REBCNT year:16; - REBCNT month:4; - REBCNT day:5; - REBINT zone:7; // +/-15:00 res: 0:15 + inline static REBI64 VAL_NANO(const RELVAL *v) { + assert(IS_TIME(v) || IS_DATE(v)); + return v->payload.time.nanoseconds; + } + + inline static REBI64 &VAL_NANO(RELVAL *v) { + assert(IS_TIME(v) || IS_DATE(v)); + return v->payload.time.nanoseconds; + } #endif -} REBYMD; -typedef union reb_date { - REBYMD date; - REBCNT bits; -} REBDAT; +#define SECS_TO_NANO(seconds) \ + (cast(REBI64, seconds) * 1000000000L) + +#define MAX_SECONDS \ + ((cast(REBI64, 1) << 31) - 1) + +#define MAX_HOUR \ + (MAX_SECONDS / 3600) + +#define MAX_TIME \ + (cast(REBI64, MAX_HOUR) * HR_SEC) + +#define NANO 1.0e-9 -typedef struct Reb_Time { - REBI64 time; // time in nanoseconds - REBDAT date; -} REBTIM; +#define SEC_SEC \ + cast(REBI64, 1000000000L) -#define VAL_TIME(v) ((v)->data.time.time) -#define TIME_SEC(n) ((REBI64)(n) * 1000000000L) +#define MIN_SEC \ + (60 * SEC_SEC) -#define MAX_SECONDS (((i64)1<<31)-1) -#define MAX_HOUR (MAX_SECONDS / 3600) -#define MAX_TIME ((REBI64)MAX_HOUR * HR_SEC) +#define HR_SEC \ + (60 * 60 * SEC_SEC) -#define NANO 1.0e-9 -#define SEC_SEC ((REBI64)1000000000L) -#define MIN_SEC (60 * SEC_SEC) -#define HR_SEC (60 * 60 * SEC_SEC) +#define SEC_TIME(n) \ + ((n) * SEC_SEC) -#define SEC_TIME(n) ((n) * SEC_SEC) -#define MIN_TIME(n) ((n) * MIN_SEC) -#define HOUR_TIME(n) ((n) * HR_SEC) +#define MIN_TIME(n) \ + ((n) * MIN_SEC) -#define SECS_IN(n) ((n) / SEC_SEC) -#define VAL_SECS(n) (VAL_TIME(n) / SEC_SEC) +#define HOUR_TIME(n) \ + ((n) * HR_SEC) -#define DEC_TO_SECS(n) (i64)(((n) + 5.0e-10) * SEC_SEC) +#define SECS_FROM_NANO(n) \ + ((n) / SEC_SEC) + +#define VAL_SECS(n) \ + (VAL_NANO(n) / SEC_SEC) + +#define DEC_TO_SECS(n) \ + cast(REBI64, ((n) + 5.0e-10) * SEC_SEC) #define SECS_IN_DAY 86400 -#define TIME_IN_DAY (SEC_TIME((i64)SECS_IN_DAY)) -#define NO_TIME MIN_I64 +#define TIME_IN_DAY \ + SEC_TIME(cast(REBI64, SECS_IN_DAY)) + +#define NO_TIME MIN_I64 + +inline static void Init_Time_Nanoseconds(RELVAL *v, REBI64 nanoseconds) { + VAL_RESET_HEADER(v, REB_TIME); + VAL_NANO(v) = nanoseconds; +} -#define MAX_YEAR 0x3fff -#define VAL_DATE(v) ((v)->data.time.date) -#define VAL_YEAR(v) ((v)->data.time.date.date.year) -#define VAL_MONTH(v) ((v)->data.time.date.date.month) -#define VAL_DAY(v) ((v)->data.time.date.date.day) -#define VAL_ZONE(v) ((v)->data.time.date.date.zone) +//=////////////////////////////////////////////////////////////////////////=// +// +// DATE! +// +//=////////////////////////////////////////////////////////////////////////=// + +#define VAL_DATE(v) \ + ((v)->extra.date) + +#define MAX_YEAR 0x3fff + +#define VAL_YEAR(v) \ + ((v)->extra.date.date.year) + +#define VAL_MONTH(v) \ + ((v)->extra.date.date.month) + +#define VAL_DAY(v) \ + ((v)->extra.date.date.day) + +#define VAL_ZONE(v) \ + ((v)->extra.date.date.zone) #define ZONE_MINS 15 -#define ZONE_SECS (ZONE_MINS*60) -#define MAX_ZONE (15 * (60/ZONE_MINS)) - - -/*********************************************************************** -** -** TUPLE -** -***********************************************************************/ - -typedef struct Reb_Tuple { - REBYTE tuple[12]; -} REBTUP; - -#define VAL_TUPLE(v) ((v)->data.tuple.tuple+1) -#define VAL_TUPLE_LEN(v) ((v)->data.tuple.tuple[0]) -#define MAX_TUPLE 10 - - -/*********************************************************************** -** -** PAIR -** -***********************************************************************/ - -#define VAL_PAIR(v) ((v)->data.pair) -#define VAL_PAIR_X(v) ((v)->data.pair.x) -#define VAL_PAIR_Y(v) ((v)->data.pair.y) -#define SET_PAIR(v,x,y) (VAL_SET(v, REB_PAIR),VAL_PAIR_X(v)=(x),VAL_PAIR_Y(v)=(y)) -#define VAL_PAIR_X_INT(v) ROUND_TO_INT((v)->data.pair.x) -#define VAL_PAIR_Y_INT(v) ROUND_TO_INT((v)->data.pair.y) - - -/*********************************************************************** -** -** EVENT -** -***********************************************************************/ - -#define VAL_EVENT_TYPE(v) ((v)->data.event.type) //(VAL_EVENT_INFO(v) & 0xff) -#define VAL_EVENT_FLAGS(v) ((v)->data.event.flags) //((VAL_EVENT_INFO(v) >> 16) & 0xff) -#define VAL_EVENT_WIN(v) ((v)->data.event.win) //((VAL_EVENT_INFO(v) >> 24) & 0xff) -#define VAL_EVENT_MODEL(v) ((v)->data.event.model) -#define VAL_EVENT_DATA(v) ((v)->data.event.data) -#define VAL_EVENT_TIME(v) ((v)->data.event.time) -#define VAL_EVENT_REQ(v) ((v)->data.event.req) -#define VAL_EVENT_SER(v) ((v)->data.event.ser) - -#define IS_EVENT_MODEL(v,f) (VAL_EVENT_MODEL(v) == (f)) - -#define SET_EVENT_INFO(val, type, flags, win) \ - VAL_EVENT_TYPE(val)=type, VAL_EVENT_FLAGS(val)=flags, VAL_EVENT_WIN(val)=win - //VAL_EVENT_INFO(val) = (type | (flags << 16) | (win << 24)) - -#define VAL_EVENT_X(v) ((REBINT) (short) (VAL_EVENT_DATA(v) & 0xffff)) -#define VAL_EVENT_Y(v) ((REBINT) (short) ((VAL_EVENT_DATA(v) >> 16) & 0xffff)) -#define VAL_EVENT_XY(v) (VAL_EVENT_DATA(v)) -#define SET_EVENT_XY(v,x,y) VAL_EVENT_DATA(v) = ((y << 16) | (x & 0xffff)) - -#define VAL_EVENT_KEY(v) (VAL_EVENT_DATA(v) & 0xffff) -#define VAL_EVENT_KCODE(v) ((VAL_EVENT_DATA(v) >> 16) & 0xffff) -#define SET_EVENT_KEY(v,k,c) VAL_EVENT_DATA(v) = ((c << 16) + k) - -#define IS_KEY_EVENT(type) 0 - -#ifdef old_code -#define TO_EVENT_XY(x,y) (((y)<<16)|((x)&0xffff)) -#define SET_EVENT_INFO(v,t,k,c,w,f) ((VAL_FLAGS(v)=(VAL_FLAGS(v)&0x0f)|((f)&0xf0)),\ - (VAL_EVENT_INFO(v)=(((t)&0xff)|(((k)&0xff)<<8)|\ - (((c)&0xff)<<16)|(((w)&0xff)<<24)))) -#endif +#define ZONE_SECS \ + (ZONE_MINS * 60) -/*********************************************************************** -** -*/ struct Reb_Series -/* -** Series header points to data and keeps track of tail and size. -** Additional fields can be used for attributes and GC. Every -** string and block in REBOL uses one of these to permit GC -** and compaction. -** -***********************************************************************/ -{ - REBYTE *data; // series data head - REBCNT tail; // one past end of useful data - REBCNT rest; // total number of units from bias to end - REBINT info; // holds width and flags - union { - REBCNT size; // used for vectors and bitsets - REBSER *series; // MAP datatype uses this - struct { - REBCNT wide:16; - REBCNT high:16; - } area; - }; -#ifdef SERIES_LABELS - REBYTE *label; // identify the series -#endif -}; +#define MAX_ZONE \ + (15 * (60 / ZONE_MINS)) -#define SERIES_TAIL(s) ((s)->tail) -#define SERIES_REST(s) ((s)->rest) -#define SERIES_LEN(s) ((s)->tail + 1) // Includes terminator -#define SERIES_FLAGS(s) ((s)->info) -#define SERIES_WIDE(s) (((s)->info) & 0xff) -#define SERIES_DATA(s) ((s)->data) -#define SERIES_SKIP(s,i) (SERIES_DATA(s) + (SERIES_WIDE(s) * i)) -#define END_FLAG 0x80000000 // Indicates end of block as an index (from DO_NEXT) -#ifdef SERIES_LABELS -#define SERIES_LABEL(s) ((s)->label) -#define SET_SERIES_LABEL(s,l) (((s)->label) = (l)) -#else -#define SERIES_LABEL(s) "-" -#define SET_SERIES_LABEL(s,l) -#endif -// Flag: If wide field is not set, series is free (not used): -#define SERIES_FREED(s) (!SERIES_WIDE(s)) - -// Size in bytes of memory allocated (including bias area): -#define SERIES_TOTAL(s) ((SERIES_REST(s) + SERIES_BIAS(s)) * (REBCNT)SERIES_WIDE(s)) -// Size in bytes of series (not including bias area): -#define SERIES_SPACE(s) (SERIES_REST(s) * (REBCNT)SERIES_WIDE(s)) -// Size in bytes being used, including terminator: -#define SERIES_USED(s) (SERIES_LEN(s) * SERIES_WIDE(s)) - -// Optimized expand when at tail (but, does not reterminate) -#define EXPAND_SERIES_TAIL(s,l) if (SERIES_FITS(s, l)) s->tail += l; else Expand_Series(s, AT_TAIL, l) -#define RESIZE_SERIES(s,l) s->tail = 0; if (!SERIES_FITS(s, l)) Expand_Series(s, AT_TAIL, l); s->tail = 0 -#define RESET_SERIES(s) s->tail = 0; TERM_SERIES(s) -#define RESET_TAIL(s) s->tail = 0 - -// Clear all and clear to tail: -#define CLEAR_SERIES(s) CLEAR(SERIES_DATA(s), SERIES_SPACE(s)) -#define ZERO_SERIES(s) memset(SERIES_DATA(s), 0, SERIES_USED(s)) -#define TERM_SERIES(s) memset(SERIES_SKIP(s, SERIES_TAIL(s)), 0, SERIES_WIDE(s)) - -// Returns space that a series has available (less terminator): -#define SERIES_FULL(s) (SERIES_LEN(s) >= SERIES_REST(s)) -#define SERIES_AVAIL(s) (SERIES_REST(s) - SERIES_LEN(s)) -#define SERIES_FITS(s,n) ((SERIES_TAIL(s) + (REBCNT)(n) + 1) < SERIES_REST(s)) - -// Flag used for extending series at tail: -#define AT_TAIL ((REBCNT)(~0)) // Extend series at tail - -// Is it a byte-sized series? (this works because no other odd size allowed) -#define BYTE_SIZE(s) (((s)->info) & 1) -#define VAL_BYTE_SIZE(v) (BYTE_SIZE(VAL_SERIES(v))) -#define VAL_STR_IS_ASCII(v) (VAL_BYTE_SIZE(v) && !Is_Not_ASCII(VAL_BIN_DATA(v), VAL_LEN(v))) - -// Bias is empty space in front of head: -#define SERIES_BIAS(s) (REBCNT)(SERIES_FLAGS(s) >> 16) -#define MAX_SERIES_BIAS 0x1000 -#define SERIES_SET_BIAS(s,b) (SERIES_FLAGS(s) = (SERIES_FLAGS(s) & 0xffff) | (b << 16)) -#define SERIES_ADD_BIAS(s,b) (SERIES_FLAGS(s) += (b << 16)) -#define SERIES_SUB_BIAS(s,b) (SERIES_FLAGS(s) -= (b << 16)) - -// Series Flags: -enum { - SER_MARK = 1, // Series was found during GC mark scan. - SER_KEEP = 1<<1, // Series is permanent, do not GC it. - SER_LOCK = 1<<2, // Series is locked, do not expand it - SER_EXT = 1<<3, // Series is external (library), do not GC it. - SER_FREE = 1<<4, // mark series as removed - SER_BARE = 1<<5, // Series has no links to GC-able values - SER_PROT = 1<<6, // Series is protected from modification - SER_MON = 1<<7, // Monitoring -}; - -#define SERIES_SET_FLAG(s, f) (SERIES_FLAGS(s) |= ((f) << 8)) -#define SERIES_CLR_FLAG(s, f) (SERIES_FLAGS(s) &= ~((f) << 8)) -#define SERIES_GET_FLAG(s, f) (SERIES_FLAGS(s) & ((f) << 8)) - -#define IS_FREEABLE(s) !SERIES_GET_FLAG(s, SER_MARK|SER_KEEP|SER_EXT|SER_FREE) -#define MARK_SERIES(s) SERIES_SET_FLAG(s, SER_MARK) -#define UNMARK_SERIES(s) SERIES_CLR_FLAG(s, SER_MARK) -#define IS_MARK_SERIES(s) SERIES_GET_FLAG(s, SER_MARK) -#define KEEP_SERIES(s,l) do {SERIES_SET_FLAG(s, SER_KEEP); LABEL_SERIES(s,l);} while(0) -#define IS_EXT_SERIES(s) SERIES_GET_FLAG(s, SER_EXT) -#define LOCK_SERIES(s) SERIES_SET_FLAG(s, SER_LOCK) -#define IS_LOCK_SERIES(s) SERIES_GET_FLAG(s, SER_LOCK) -#define BARE_SERIES(s) SERIES_SET_FLAG(s, SER_BARE) -#define IS_BARE_SERIES(s) SERIES_GET_FLAG(s, SER_BARE) -#define PROTECT_SERIES(s) SERIES_SET_FLAG(s, SER_PROT) -#define UNPROTECT_SERIES(s) SERIES_CLR_FLAG(s, SER_PROT) -#define IS_PROTECT_SERIES(s) SERIES_GET_FLAG(s, SER_PROT) - -#define TRAP_PROTECT(s) if (IS_PROTECT_SERIES(s)) Trap0(RE_PROTECTED) - -#ifdef SERIES_LABELS -#define LABEL_SERIES(s,l) s->label = (l) -#else -#define LABEL_SERIES(s,l) -#endif +//=////////////////////////////////////////////////////////////////////////=// +// +// TUPLE! +// +//=////////////////////////////////////////////////////////////////////////=// +// +// TUPLE! is a Rebol2/R3-Alpha concept to fit up to 7 byte-sized integers +// directly into a value payload without needing to make a series allocation. +// At source level they would be numbers separated by dots, like `1.2.3.4.5`. +// This was mainly applied for IP addresses and RGB/RGBA constants, and +// considered to be a "lightweight"...it would allow PICK and POKE like a +// series, but did not behave like one due to not having a position. +// +// !!! Ren-C challenges the value of the TUPLE! type as defined. Color +// literals are often hexadecimal (where BINARY! would do) and IPv6 addresses +// have a different notation. It may be that `.` could be used for a more +// generalized partner to PATH!, where `a.b.1` would be like a/b/1 +// -#ifdef MEM_STRESS -#define FREE_SERIES(s) SERIES_SET_FLAG(s, SER_FREE) // mark as removed -#define CHECK_MARK(s,d) \ - if (SERIES_GET_FLAG(s, SER_FREE)) Choke(); \ - if (!IS_MARK_SERIES(s)) Mark_Series(s, d); -#else -#define FREE_SERIES(s) -#define CHECK_MARK(s,d) if (!IS_MARK_SERIES(s)) Mark_Series(s, d); -#endif +#define MAX_TUPLE \ + ((sizeof(REBCNT) * 2) - 1) // for same properties on 64-bit and 32-bit -//#define LABEL_SERIES(s,l) s->label = (l) -#define IS_BLOCK_SERIES(s) (SERIES_WIDE(s) == sizeof(REBVAL)) +#define VAL_TUPLE(v) \ + ((v)->payload.tuple.tuple + 1) -// !!! Remove if not used after port: -//#define SERIES_SIDE(s) ((s)->link.side) -//#define SERIES_FRAME(s) ((s)->link.frame) -//#define SERIES_NOT_REBOLS(s) SERIES_SET_FLAG(s, SER_XLIB) +#define VAL_TUPLE_LEN(v) \ + ((v)->payload.tuple.tuple[0]) +#define VAL_TUPLE_DATA(v) \ + ((v)->payload.tuple.tuple) -/*********************************************************************** -** -** SERIES -- Generic series macros -** -***********************************************************************/ +inline static void SET_TUPLE(RELVAL *v, const void *data) { + VAL_RESET_HEADER(v, REB_TUPLE); + memcpy(VAL_TUPLE_DATA(v), data, sizeof(VAL_TUPLE_DATA(v))); +} -#pragma pack() -#include "reb-gob.h" -#pragma pack(4) -typedef struct Reb_Series_Ref -{ - REBSER *series; - REBCNT index; - union { - REBSER *side; // lookaside block for lists/hashes/images - REBINT back; // (Used in DO for stack back linking) -// REBFRM *frame; // (may also be used as frame for binding blocks) - } link; -} REBSRI; - -#define VAL_SERIES(v) ((v)->data.series.series) -#define VAL_INDEX(v) ((v)->data.series.index) -#define VAL_TAIL(v) (VAL_SERIES(v)->tail) -#define VAL_LEN(v) (Val_Series_Len(v)) - -#define VAL_DATA(s) (VAL_BIN_HEAD(s) + (VAL_INDEX(s) * VAL_SERIES_WIDTH(s))) - -#define VAL_BACK(v) ((v)->data.series.link.back) -#define VAL_SERIES_SIDE(v) ((v)->data.series.link.side) -#define VAL_SERIES_FRAME(v) ((v)->data.series.link.frame) -#define VAL_SERIES_WIDTH(v) (SERIES_WIDE(VAL_SERIES(v))) -#define VAL_LIMIT_SERIES(v) if (VAL_INDEX(v) > VAL_TAIL(v)) VAL_INDEX(v) = VAL_TAIL(v) - -#define DIFF_PTRS(a,b) (REBCNT)((REBYTE*)a - (REBYTE*)b) - - -/*********************************************************************** -** -** STRINGS -- All string related values -** -***********************************************************************/ - -#define SET_STRING(v,s) VAL_SERIES(v)=(s), VAL_INDEX(v)=0, VAL_SET(v, REB_STRING) -#define SET_BINARY(v,s) VAL_SERIES(v)=(s), VAL_INDEX(v)=0, VAL_SET(v, REB_BINARY) -#define SET_FILE(v,s) VAL_SERIES(v)=(s), VAL_INDEX(v)=0, VAL_SET(v, REB_FILE) -#define SET_STR_TYPE(t,v,s) VAL_SERIES(v)=(s), VAL_INDEX(v)=0, VAL_SET(v, t) -#define SET_STR_END(s,n) (*STR_SKIP(s,n) = 0) - -// Arg is a binary (byte) series: -#define BIN_HEAD(s) ((REBYTE *)((s)->data)) -#define BIN_DATA(s) ((REBYTE *)((s)->data)) -#define BIN_TAIL(s) (REBYTE*)STR_TAIL(s) -#define BIN_SKIP(s, n) (((REBYTE *)((s)->data))+(n)) -#define BIN_LEN(s) (SERIES_TAIL(s)) - -// Arg is a unicode series: -#define UNI_HEAD(s) ((REBUNI *)((s)->data)) -#define UNI_SKIP(s, n) (((REBUNI *)((s)->data))+(n)) -#define UNI_TAIL(s) (((REBUNI *)((s)->data))+(s)->tail) -#define UNI_LAST(s) (((REBUNI *)((s)->data))+((s)->tail-1)) // make sure tail not zero -#define UNI_LEN(s) (SERIES_TAIL(s)) -#define UNI_TERM(s) (*UNI_TAIL(s) = 0) -#define UNI_RESET(s) (UNI_HEAD(s)[(s)->tail = 0] = 0) - -// Obsolete (remove after Unicode conversion): -#define STR_HEAD(s) ((REBYTE *)((s)->data)) -#define STR_DATA(s) ((REBYTE *)((s)->data)) -#define STR_SKIP(s, n) (((REBYTE *)((s)->data))+(n)) -#define STR_TAIL(s) (((REBYTE *)((s)->data))+(s)->tail) -#define STR_LAST(s) (((REBYTE *)((s)->data))+((s)->tail-1)) // make sure tail not zero -#define STR_LEN(s) (SERIES_TAIL(s)) -#define STR_TERM(s) (*STR_TAIL(s) = 0) -#define STR_RESET(s) (STR_HEAD(s)[(s)->tail = 0] = 0) - -// Arg is a binary value: -#define VAL_BIN(v) BIN_HEAD(VAL_SERIES(v)) -#define VAL_BIN_HEAD(v) BIN_HEAD(VAL_SERIES(v)) -#define VAL_BIN_DATA(v) BIN_SKIP(VAL_SERIES(v), VAL_INDEX(v)) -#define VAL_BIN_SKIP(v,n) BIN_SKIP(VAL_SERIES(v), (n)) -#define VAL_BIN_TAIL(v) BIN_SKIP(VAL_SERIES(v), VAL_SERIES(v)->tail) - -// Arg is a unicode value: -#define VAL_UNI(v) UNI_HEAD(VAL_SERIES(v)) -#define VAL_UNI_HEAD(v) UNI_HEAD(VAL_SERIES(v)) -#define VAL_UNI_DATA(v) UNI_SKIP(VAL_SERIES(v), VAL_INDEX(v)) - -// Get a char, from either byte or unicode string: -#define GET_ANY_CHAR(s,n) (REBUNI)(BYTE_SIZE(s) ? BIN_HEAD(s)[n] : UNI_HEAD(s)[n]) -#define SET_ANY_CHAR(s,n,c) if BYTE_SIZE(s) BIN_HEAD(s)[n]=((REBYTE)c); else UNI_HEAD(s)[n]=((REBUNI)c) -#define GET_CHAR_UNI(f,p,i) (uni ? ((REBUNI*)p)[i] : ((REBYTE*)bp)[i]) - -#define VAL_ANY_CHAR(v) GET_ANY_CHAR(VAL_SERIES(v), VAL_INDEX(v)) - -//#define VAL_STR_LAST(v) STR_LAST(VAL_SERIES(v)) -//#define VAL_MEM_LEN(v) (VAL_TAIL(v) * VAL_SERIES_WIDTH(v)) - - -/*********************************************************************** -** -** IMAGES, QUADS - RGBA -** -***********************************************************************/ - -//typedef struct Reb_ImageInfo -//{ -// REBCNT width; -// REBCNT height; -// REBINT transp; -//} REBIMI; - -#define QUAD_HEAD(s) ((REBYTE *)((s)->data)) -#define QUAD_SKIP(s,n) (((REBYTE *)((s)->data))+(n * 4)) -#define QUAD_TAIL(s) (((REBYTE *)((s)->data))+((s)->tail * 4)) -#define QUAD_LEN(s) (SERIES_TAIL(s)) - -#define IMG_SIZE(s) ((s)->size) -#define IMG_WIDE(s) ((s)->area.wide) -#define IMG_HIGH(s) ((s)->area.high) -#define IMG_DATA(s) ((REBYTE *)((s)->data)) - -#define VAL_IMAGE_HEAD(v) QUAD_HEAD(VAL_SERIES(v)) -#define VAL_IMAGE_TAIL(v) QUAD_SKIP(VAL_SERIES(v), VAL_SERIES(v)->tail) -#define VAL_IMAGE_DATA(v) QUAD_SKIP(VAL_SERIES(v), VAL_INDEX(v)) -#define VAL_IMAGE_BITS(v) ((REBCNT *)VAL_IMAGE_HEAD((v))) -#define VAL_IMAGE_WIDE(v) (IMG_WIDE(VAL_SERIES(v))) -#define VAL_IMAGE_HIGH(v) (IMG_HIGH(VAL_SERIES(v))) -#define VAL_IMAGE_LEN(v) VAL_LEN(v) - -#define SET_IMAGE(v,s) VAL_SET(v, REB_IMAGE);VAL_SERIES(v)=s;VAL_INDEX(v) = 0; - - -//#define VAL_IMAGE_TRANSP(v) (VAL_IMAGE_INFO(v)->transp) -//#define VAL_IMAGE_TRANSP_TYPE(v) (VAL_IMAGE_TRANSP(v)&0xff000000) -//#define VITT_UNKNOWN 0x00000000 -//#define VITT_NONE 0x01000000 -//#define VITT_ALPHA 0x02000000 -//#define VAL_IMAGE_DEPTH(v) ((VAL_IMAGE_INFO(v)>>24)&0x3f) -//#define VAL_IMAGE_TYPE(v) ((VAL_IMAGE_INFO(v)>>30)&3) - -// New Image Datatype defines: -#define TO_COLOR(r,g,b,a) (REBCNT)((a)<<24 | (r)<<16 | (g)<<8 | (b)) - -#define TO_COLOR_TUPLE(t) TO_COLOR(VAL_TUPLE(t)[0], VAL_TUPLE(t)[1], VAL_TUPLE(t)[2], \ - VAL_TUPLE_LEN(t) > 3 ? VAL_TUPLE(t)[3] : 0) - -// Maps color components to correct byte positions for RGBA: -#ifdef ENDIAN_BIG -#define C_A 0 -#define C_R 1 -#define C_G 2 -#define C_B 3 -#else -#define C_B 0 -#define C_G 1 -#define C_R 2 -#define C_A 3 -#endif +//=////////////////////////////////////////////////////////////////////////=// +// +// EVENT! +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Rebol's events are used for the GUI and for network and I/O. They are +// essentially just a union of some structures which are packed so they can +// fit into a REBVAL's payload size. +// +// The available event models are: +// +// * EVM_PORT +// * EVM_OBJECT +// * EVM_DEVICE +// * EVM_CALLBACK +// * EVM_GUI +// -/*********************************************************************** -** -** Logic and Logic Bits -** -***********************************************************************/ +#define VAL_EVENT_TYPE(v) \ + ((v)->payload.event.type) -#define VAL_LOGIC(v) ((v)->data.logic) -#define SET_LOGIC(v,n) VAL_SET(v, REB_LOGIC), VAL_LOGIC(v) = ((n)!=0) //, VAL_LOGIC_WORDS(v)=0 -#define SET_TRUE(v) SET_LOGIC(v, TRUE) // compound statement -#define SET_FALSE(v) SET_LOGIC(v, FALSE) // compound statement -#define IS_FALSE(v) (IS_NONE(v) || (IS_LOGIC(v) && !VAL_LOGIC(v))) -#define IS_TRUE(v) (!IS_FALSE(v)) -#define VAL_I32(v) ((v)->data.logic) // used for handles, etc. +#define VAL_EVENT_FLAGS(v) \ + ((v)->payload.event.flags) +#define VAL_EVENT_WIN(v) \ + ((v)->payload.event.win) -/*********************************************************************** -** -** BIT_SET -- Bit sets -** -***********************************************************************/ +#define VAL_EVENT_MODEL(v) \ + ((v)->payload.event.model) -#define VAL_BITSET(v) VAL_SERIES(v) +#define VAL_EVENT_DATA(v) \ + ((v)->payload.event.data) -#define VAL_BIT_DATA(v) VAL_BIN(v) +#define VAL_EVENT_TIME(v) \ + ((v)->payload.event.time) -#define SET_BIT(d,n) ((d)[(n) >> 3] |= (1 << ((n) & 7))) -#define CLR_BIT(d,n) ((d)[(n) >> 3] &= ~(1 << ((n) & 7))) -#define IS_BIT(d,n) ((d)[(n) >> 3] & (1 << ((n) & 7))) +#define VAL_EVENT_REQ(v) \ + ((v)->extra.eventee.req) +#define VAL_EVENT_SER(v) \ + ((v)->extra.eventee.ser) -/*********************************************************************** -** -** BLOCKS -- Block is a terminated string of values -** -***********************************************************************/ +#define IS_EVENT_MODEL(v,f) \ + (VAL_EVENT_MODEL(v) == (f)) -#define NOT_END(v) (!IS_END(v)) +inline static void SET_EVENT_INFO(RELVAL *val, u8 type, u8 flags, u8 win) { + VAL_EVENT_TYPE(val) = type; + VAL_EVENT_FLAGS(val) = flags; + VAL_EVENT_WIN(val) = win; +} -// Arg is a series: -#define BLK_HEAD(s) ((REBVAL *)((s)->data)) -#define BLK_SKIP(s, n) (((REBVAL *)((s)->data))+(n)) -#define BLK_TAIL(s) (((REBVAL *)((s)->data))+(s)->tail) -#define BLK_LAST(s) (((REBVAL *)((s)->data))+((s)->tail-1)) // make sure tail not zero -#define BLK_LEN(s) (SERIES_TAIL(s)) -#define BLK_TERM(s) SET_END(BLK_TAIL(s)) -#define BLK_RESET(b) (b)->tail = 0, SET_END(BLK_HEAD(b)) +// Position event data -// Arg is a value: -#define VAL_BLK(v) BLK_HEAD(VAL_SERIES(v)) -#define VAL_BLK_DATA(v) BLK_SKIP(VAL_SERIES(v), VAL_INDEX(v)) -#define VAL_BLK_SKIP(v,n) BLK_SKIP(VAL_SERIES(v), (n)) -#define VAL_BLK_TAIL(v) BLK_SKIP(VAL_SERIES(v), VAL_SERIES(v)->tail) -#define VAL_BLK_LEN(v) VAL_LEN(v) -#define VAL_BLK_TERM(v) BLK_TERM(VAL_SERIES(v)) +#define VAL_EVENT_X(v) \ + cast(REBINT, cast(short, VAL_EVENT_DATA(v) & 0xffff)) -#define COPY_VALUES(f,t,l) memcpy(t, f, (l) * sizeof(REBVAL)) +#define VAL_EVENT_Y(v) \ + cast(REBINT, cast(short, (VAL_EVENT_DATA(v) >> 16) & 0xffff)) -#define COPY_BLK_PART(d, s, n) memcpy((d)->data, s, (n) * sizeof(REBVAL)); SERIES_TAIL(d) = n; BLK_TERM(d) +#define VAL_EVENT_XY(v) \ + (VAL_EVENT_DATA(v)) + +inline static void SET_EVENT_XY(RELVAL *v, REBINT x, REBINT y) { + // + // !!! "conversion to u32 from REBINT may change the sign of the result" + // Hence cast. Not clear what the intent is. + // + VAL_EVENT_DATA(v) = cast(u32, ((y << 16) | (x & 0xffff))); +} -#define IS_EMPTY(v) (VAL_INDEX(v) >= VAL_TAIL(v)) +// Key event data +#define VAL_EVENT_KEY(v) \ + (VAL_EVENT_DATA(v) & 0xffff) -/*********************************************************************** -** -** LIST & HASH Block Lookaside buffer -** -***********************************************************************/ +#define VAL_EVENT_KCODE(v) \ + ((VAL_EVENT_DATA(v) >> 16) & 0xffff) -typedef struct Reb_Side { // lookaside series - REBCNT next; // next element - REBCNT past; // prior element -} REBLAB; - -#define LIST_HEAD(s) ((REBLAB *)((s)->data)) -#define LIST_SKIP(s,n) (((REBLAB *)((s)->data))+(n)) - -#define VAL_LIST(v) LIST_HEAD(VAL_SERIES_SIDE(v)) - -#define SET_LIST(v,s,l) VAL_SERIES(v)=(s), VAL_INDEX(v)=0, VAL_SER_LIST(v)=(l), VAL_SET(v, REB_LIST) +inline static void SET_EVENT_KEY(RELVAL *v, REBCNT k, REBCNT c) { + VAL_EVENT_DATA(v) = ((c << 16) + k); +} -/*********************************************************************** -** -** SYMBOLS -- Used only for symbol tables -** -***********************************************************************/ - -typedef struct Reb_Symbol { - REBCNT canon; // Index of the canonical (first) word - REBCNT alias; // Index to next alias form - REBCNT name; // Index into PG_Word_Names string -} REBSYM; +//=////////////////////////////////////////////////////////////////////////=// +// +// IMAGE! +// +//=////////////////////////////////////////////////////////////////////////=// +// +// !!! Ren-C's primary goals are to research and pin down fundamentals, where +// things like IMAGE! would be an extension through a user-defined type +// vs. being in the core. The R3-Alpha code has been kept compiling here +// due to its usage in R3-GUI. +// + +// QUAD=(Red, Green, Blue, Alpha) -// Arg is value: -#define VAL_SYM_NINDEX(v) ((v)->data.symbol.name) -#define VAL_SYM_NAME(v) (STR_HEAD(PG_Word_Names) + VAL_SYM_NINDEX(v)) -#define VAL_SYM_CANON(v) ((v)->data.symbol.canon) -#define VAL_SYM_ALIAS(v) ((v)->data.symbol.alias) - -// Return the CANON value for a symbol number: -#define SYMBOL_TO_CANON(sym) (VAL_SYM_CANON(BLK_SKIP(PG_Word_Table.series, sym))) -// Return the CANON value for a word value: -#define WORD_TO_CANON(w) (VAL_SYM_CANON(BLK_SKIP(PG_Word_Table.series, VAL_WORD_SYM(w)))) - -#define IS_STAR(v) (IS_WORD(v) && VAL_WORD_CANON(v) == SYM__P) - - -/*********************************************************************** -** -** WORDS -- All word related types -** -***********************************************************************/ - -typedef struct Reb_Word { - REBCNT sym; // Index of the word's symbol - REBINT index; // Index of the word in the frame - REBSER *frame; // Frame in which the word is defined -} REBWRD; - -typedef struct Reb_Word_Spec { - REBCNT sym; // Index of the word's symbol (and pad for U64 alignment) - REBU64 typeset; -} REBWRS; - -#define IS_SAME_WORD(v, n) (IS_WORD(v) && VAL_WORD_CANON(v) == n) - -#define VAL_WORD_SYM(v) ((v)->data.word.sym) -#define VAL_WORD_INDEX(v) ((v)->data.word.index) -#define VAL_WORD_FRAME(v) ((v)->data.word.frame) -#define HAS_FRAME(v) VAL_WORD_FRAME(v) - -#define UNBIND(v) VAL_WORD_FRAME(v)=0, VAL_WORD_INDEX(v)=0 - -#define VAL_WORD_CANON(v) VAL_SYM_CANON(BLK_SKIP(PG_Word_Table.series, VAL_WORD_SYM(v))) -#define VAL_WORD_NAME(v) VAL_SYM_NAME(BLK_SKIP(PG_Word_Table.series, VAL_WORD_SYM(v))) -#define VAL_WORD_NAME_STR(v) STR_HEAD(VAL_WORD_NAME(v)) - -// When words are used in frame word lists, fields get a different meaning: -#define VAL_BIND_SYM(v) ((v)->data.wordspec.sym) -#define VAL_BIND_CANON(v) VAL_SYM_CANON(BLK_SKIP(PG_Word_Table.series, VAL_BIND_SYM(v))) //((v)->data.wordspec.index) -#define VAL_BIND_TYPESET(v) ((v)->data.wordspec.typeset) -#define VAL_WORD_FRAME_WORDS(v) VAL_WORD_FRAME(v)->words -#define VAL_WORD_FRAME_VALUES(v) VAL_WORD_FRAME(v)->values - -// Is it the same symbol? Quick check, then canon check: -#define SAME_SYM(a,b) (VAL_WORD_SYM(a)==VAL_BIND_SYM(b)||VAL_WORD_CANON(a)==VAL_BIND_CANON(b)) - -/*********************************************************************** -** -** Frame -- Used to bind words to values. -** -** This type of value is used at the head of a frame block. -** It should appear in no other place. -** -***********************************************************************/ - -typedef struct Reb_Frame { - REBSER *words; - REBSER *spec; -// REBSER *parent; -} REBFRM; - -// Value to frame fields: -#define VAL_FRM_WORDS(v) ((v)->data.frame.words) -#define VAL_FRM_SPEC(v) ((v)->data.frame.spec) -//#define VAL_FRM_PARENT(v) ((v)->data.frame.parent) - -// Word number array (used by Bind_Table): -#define WORDS_HEAD(w) ((REBINT *)(w)->data) -#define WORDS_LAST(w) (((REBINT *)(w)->data)+(w)->tail-1) // (tail never zero) - -// Frame series to frame components: -#define FRM_WORD_SERIES(c) VAL_FRM_WORDS(BLK_HEAD(c)) -#define FRM_WORDS(c) BLK_HEAD(FRM_WORD_SERIES(c)) -#define FRM_VALUES(c) BLK_HEAD(c) -#define FRM_VALUE(c,n) BLK_SKIP(c,(n)) -#define FRM_WORD(c,n) BLK_SKIP(FRM_WORD_SERIES(c),(n)) -#define FRM_WORD_SYM(c,n) VAL_BIND_SYM(FRM_WORD(c,n)) - -#define VAL_FRM_WORD(v,n) BLK_SKIP(FRM_WORD_SERIES(VAL_SERIES(v)),(n)) - -// Object field (series, index): -#define OFV(s,n) BLK_SKIP(s,n) - -#define SET_FRAME(v, s, w) \ - VAL_FRM_SPEC(v) = (s); \ - VAL_FRM_WORDS(v) = (w); \ - VAL_SET(v, REB_FRAME) - -#define SET_SELFLESS(f) VAL_BIND_SYM(FRM_WORDS(f)) = 0 -#define IS_SELFLESS(f) (!VAL_BIND_SYM(FRM_WORDS(f))) - -/*********************************************************************** -** -** OBJECTS - Object Support -** -***********************************************************************/ - -typedef struct Reb_Object { - REBSER *frame; - REBSER *body; // module body -// REBSER *spec; -// REBCNT num; // shortcut for checking error number -} REBOBJ; - -//#define SET_OBJECT(v,s,f) VAL_OBJ_SPEC(v) = (s), VAL_OBJ_FRAME(v) = (f), VAL_SET(v, REB_OBJECT) -#define SET_OBJECT(v,f) VAL_OBJ_FRAME(v) = (f), VAL_SET(v, REB_OBJECT) -#define SET_MODULE(v,f) VAL_OBJ_FRAME(v) = (f), VAL_SET(v, REB_MODULE) - -#define VAL_OBJ_FRAME(v) ((v)->data.object.frame) -#define VAL_OBJ_VALUES(v) FRM_VALUES((v)->data.object.frame) -#define VAL_OBJ_VALUE(v,n) FRM_VALUE((v)->data.object.frame, n) -#define VAL_OBJ_WORDS(v) FRM_WORD_SERIES((v)->data.object.frame) -#define VAL_OBJ_WORD(v,n) BLK_SKIP(VAL_OBJ_WORDS(v), (n)) -//#define VAL_OBJ_SPEC(v) ((v)->data.object.spec) -#define CLONE_OBJECT(c) Copy_Series(c) - -#define VAL_MOD_FRAME(v) ((v)->data.object.frame) -#define VAL_MOD_BODY(v) ((v)->data.object.body) -#define VAL_MOD_SPEC(v) VAL_FRM_SPEC(VAL_OBJ_VALUES(v)) - -#define SET_HANDLE(v,h) VAL_SET(v, REB_HANDLE), VAL_HANDLE(v) = (void*)(h) // a place to put it. - -/*********************************************************************** -** -** PORTS - External series interface -** -***********************************************************************/ - -#define VAL_PORT(v) VAL_OBJ_FRAME(v) -#define SET_PORT(v,s) VAL_SET(v, REB_PORT), VAL_PORT(v) = s - - -/*********************************************************************** -** -** ERRORS - Error values -** -***********************************************************************/ - -typedef struct Reb_Error { - union Reo { - REBSER *object; - REBVAL *value; // RETURN value (also BREAK, THROW) - } reo; - REBCNT num; // (Determines value used below.) - REBCNT sym; // THROW symbol -} REBERR; - -// Value Accessors: -#define VAL_ERR_NUM(v) ((v)->data.error.num) -#define VAL_ERR_OBJECT(v) ((v)->data.error.reo.object) -#define VAL_ERR_VALUE(v) ((v)->data.error.reo.value) -#define VAL_ERR_SYM(v) ((v)->data.error.sym) - -#define IS_THROW(v) (VAL_ERR_NUM(v) < RE_THROW_MAX) -#define IS_BREAK(v) (VAL_ERR_NUM(v) == RE_BREAK) -#define IS_RETURN(v) (VAL_ERR_NUM(v) == RE_RETURN) -#define IS_CONTINUE(v) (VAL_ERR_NUM(v) == RE_CONTINUE) -#define THROWN(v) (IS_ERROR(v) && IS_THROW(v)) - -#define SET_ERROR(v,n,a) VAL_SET(v, REB_ERROR), VAL_ERR_NUM(v)=n, VAL_ERR_OBJECT(v)=a, VAL_ERR_SYM(v)=0 -#define SET_THROW(v,n,a) VAL_SET(v, REB_ERROR), VAL_ERR_NUM(v)=n, VAL_ERR_VALUE(v)=a, VAL_ERR_SYM(v)=0 - -#define VAL_ERR_VALUES(v) ((ERROR_OBJ *)(FRM_VALUES(VAL_ERR_OBJECT(v)))) -#define VAL_ERR_ARG1(v) (&VAL_ERR_VALUES(v)->arg1) -#define VAL_ERR_ARG2(v) (&VAL_ERR_VALUES(v)->arg2) - -// Error Object (frame) Accessors: -#define ERR_VALUES(frame) ((ERROR_OBJ *)FRM_VALUES(frame)) -#define ERR_NUM(frame) VAL_INT32(&ERR_VALUES(frame)->code) - - -/*********************************************************************** -** -** GOBS - Graphic Objects -** -***********************************************************************/ - -typedef struct Reb_Gob { - REBGOB *gob; - REBCNT index; -} REBGBO; - -#define VAL_GOB(v) ((v)->data.gob.gob) -#define VAL_GOB_INDEX(v) ((v)->data.gob.index) -#define SET_GOB(v,g) VAL_SET(v, REB_GOB), VAL_GOB(v)=g, VAL_GOB_INDEX(v)=0 - - -/*********************************************************************** -** -** FUNCTIONS - Natives, actions, operators, and user functions -** -***********************************************************************/ - -typedef int (*REBFUN)(REBVAL *ds); // Native function -typedef int (*REBACT)(REBVAL *ds, REBCNT a); // Action function -typedef void (*REBDOF)(REBVAL *ds); // DO evaltype dispatch function -typedef int (*REBPAF)(REBVAL *ds, REBSER *p, REBCNT a); // Port action func - -typedef void (*ANYFUNC)(void *); -typedef void (*TRYFUNC)(void *); -typedef int (*CMD_FUNC)(REBCNT n, REBSER *args); - -#define REBNATIVE(n) int N_##n(REBVAL *ds) -#define REBTYPE(n) int T_##n(REBVAL *ds, REBCNT action) -#define REBPACT(n) int P_##n(REBVAL *ds) - -typedef struct Reb_Function { - REBSER *spec; // Spec block for function - REBSER *args; // Block of Wordspecs (with typesets) - union Reb_Func_Code { - REBFUN code; - REBSER *body; - REBCNT act; - } func; -} REBFCN; - -#define VAL_FUNC_SPEC(v) ((v)->data.func.spec) // a series -#define VAL_FUNC_SPEC_BLK(v) BLK_HEAD((v)->data.func.spec) -#define VAL_FUNC_ARGS(v) ((v)->data.func.args) -#define VAL_FUNC_WORDS(v) VAL_FUNC_ARGS(v) -#define VAL_FUNC_CODE(v) ((v)->data.func.func.code) -#define VAL_FUNC_BODY(v) ((v)->data.func.func.body) -#define VAL_FUNC_ACT(v) ((v)->data.func.func.act) -#define VAL_FUNC_ARGC(v) SERIES_TAIL((v)->data.func.args) - -typedef struct Reb_Path_Value { - REBVAL *value; // modified - REBVAL *select; // modified - REBVAL *path; // modified - REBVAL *store; // modified (holds constructed values) - REBVAL *setval; // static - REBVAL *orig; // static -} REBPVS; - -enum Path_Eval_Result { - PE_OK, - PE_SET, - PE_USE, - PE_NONE, - PE_BAD_SELECT, - PE_BAD_SET, - PE_BAD_RANGE, - PE_BAD_SET_TYPE -}; - -typedef REBINT (*REBPEF)(REBPVS *pvs); // Path evaluator function - -typedef REBINT (*REBCTF)(REBVAL *a, REBVAL *b, REBINT s); - -/*********************************************************************** -** -** HANDLE -** -***********************************************************************/ - -typedef struct Reb_Handle { - ANYFUNC code; -} REBHAN; - -#define VAL_HANDLE(v) ((v)->data.handle.code) - -/*********************************************************************** -** -** LIBRARY -- External library management structures -** -***********************************************************************/ - -typedef struct Reb_Library { - long handle; // ALPHA wants a long - REBSER *name; - REBCNT id; -} REBLIB; - -#define VAL_LIBRARY(v) (v->data.library) -#define VAL_LIBRARY_HANDLE(v) (v->data.library.handle) -#define VAL_LIBRARY_NAME(v) (v->data.library.name) -#define VAL_LIBRARY_ID(v) (v->data.library.id) - - -/*********************************************************************** -** -** ROUTINE -- External library routine structures -** -***********************************************************************/ - -typedef struct Reb_Routine { - FUNCPTR funcptr; - REBSER *spec; // struct-ptr - REBCNT id; -} REBROT; - -typedef struct Reb_Rot_Info { - REBCNT call_idx; - REBCNT pad1; - REBCNT pad2; -} REBFRO; - -#define VAL_ROUTINE(v) (v->data.routine) -#define VAL_ROUTINE_FUNCPTR(v) (v->data.routine.funcptr) -#define VAL_ROUTINE_SPEC_SER(v) (v->data.routine.spec) -#define VAL_ROUTINE_SPEC(v) ((REBVAL *) (((REBFRO *)BLK_HEAD(VAL_ROUTINE_SPEC_SER(v))) + 1)) -#define VAL_ROUTINE_INFO(v) ((REBFRO *) (((REBFRO *)BLK_HEAD(VAL_ROUTINE_SPEC_SER(v))))) -#define VAL_ROUTINE_ID(v) (v->data.routine.id) - -#define RFRO_CALLIDX(i) ((i)->call_idx) - -typedef struct Reb_Typeset { - REBCNT pad; // Allows us to overlay this type on WORD spec type - REBU64 bits; -} REBTYS; - -#define VAL_TYPESET(v) ((v)->data.typeset.bits) -#define TYPE_CHECK(v,n) ((VAL_TYPESET(v) & ((REBU64)1 << (n))) != (REBU64)0) -#define TYPE_SET(v,n) (VAL_TYPESET(v) |= ((REBU64)1 << (n))) -#define EQUAL_TYPESET(v,w) (VAL_TYPESET(v) == VAL_TYPESET(w)) -#define TYPESET(n) ((REBU64)1 << (n)) - -//#define TYPE_CHECK(v,n) ((VAL_TYPESET(v)[(n)/32] & (1 << ((n)%32))) != 0) -//#define TYPE_SET(v,n) (VAL_TYPESET(v)[(n)/32] |= (1 << ((n)%32))) -//#define EQUAL_TYPESET(v,n) (VAL_TYPESET(v)[0] == VAL_TYPESET(n)[0] && VAL_TYPESET(v)[1] == VAL_TYPESET(n)[1]) - -/*********************************************************************** -** -** STRUCT -- C Structures -** -***********************************************************************/ - -typedef struct Reb_Struct { - REBSER *spec; - REBSER *vals; - REBSER *data; -} REBSTU; - -#define VAL_STRUCT(v) (v->data.structure) -#define VAL_STRUCT_SPEC(v) (v->data.structure.spec) -#define VAL_STRUCT_VALS(v) (v->data.structure.vals) -#define VAL_STRUCT_DATA(v) (v->data.structure.data) -#define VAL_STRUCT_DP(v) (STR_HEAD(VAL_STRUCT_DATA(v))) -#define VAL_STRUCT_LEN(v) (SERIES_TAIL(VAL_STRUCT_DATA(v))) - -/*********************************************************************** -** -** UTYPE - User defined types -** -***********************************************************************/ - -typedef struct Reb_Utype { - REBSER *func; // func object - REBSER *data; // data object -} REBUDT; - -#define VAL_UTYPE_FUNC(v) ((v)->data.utype.func) -#define VAL_UTYPE_DATA(v) ((v)->data.utype.data) - -// All bits of value fields: -typedef struct Reb_All { - REBCNT bits[3]; -} REBALL; - -#define VAL_ALL_BITS(v) ((v)->data.all.bits) - - -/*********************************************************************** -** -*/ struct Reb_Value -/* -** The structure/union for all REBOL values. Most efficient -** if it fits into 16 bytes of memory (but not required). -** -***********************************************************************/ -{ - union Reb_Val_Head { - REBHED flags; - REBCNT header; - } flags; - union Reb_Val_Data { - REBWRD word; - REBSRI series; - REBCNT logic; - REBI64 integer; - REBU64 unteger; - REBDEC decimal; - REBUNI uchar; - REBERR error; - REBTYP datatype; - REBFRM frame; - REBWRS wordspec; - REBTYS typeset; - REBSYM symbol; - REBTIM time; - REBTUP tuple; - REBFCN func; - REBOBJ object; - REBXYF pair; - REBEVT event; - REBLIB library; - REBROT routine; - REBSTU structure; - REBGBO gob; - REBUDT utype; - REBDCI deci; - REBHAN handle; - REBALL all; - } data; -}; - -#define ANY_SERIES(v) (VAL_TYPE(v) >= REB_BINARY && VAL_TYPE(v) <= REB_LIT_PATH) -#define ANY_STR(v) (VAL_TYPE(v) >= REB_STRING && VAL_TYPE(v) <= REB_TAG) -#define ANY_BINSTR(v) (VAL_TYPE(v) >= REB_BINARY && VAL_TYPE(v) <= REB_TAG) -#define ANY_BLOCK(v) (VAL_TYPE(v) >= REB_BLOCK && VAL_TYPE(v) <= REB_LIT_PATH) -#define ANY_WORD(v) (VAL_TYPE(v) >= REB_WORD && VAL_TYPE(v) <= REB_ISSUE) -#define ANY_PATH(v) (VAL_TYPE(v) >= REB_PATH && VAL_TYPE(v) <= REB_LIT_PATH) -#define ANY_FUNC(v) (VAL_TYPE(v) >= REB_NATIVE && VAL_TYPE(v) <= REB_FUNCTION) -#define ANY_EVAL_BLOCK(v) (VAL_TYPE(v) >= REB_BLOCK && VAL_TYPE(v) <= REB_PAREN) -#define ANY_OBJECT(v) (VAL_TYPE(v) >= REB_OBJECT && VAL_TYPE(v) <= REB_PORT) - -#pragma pack() - -#endif // value.h +#define QUAD_LEN(s) \ + SER_LEN(s) + +#define QUAD_HEAD(s) \ + SER_DATA_RAW(s) + +#define QUAD_SKIP(s,n) \ + (QUAD_HEAD(s) + ((n) * 4)) + +#define QUAD_TAIL(s) \ + (QUAD_HEAD(s) + (QUAD_LEN(s) * 4)) + +#define IMG_WIDE(s) \ + ((s)->misc.area.wide) + +#define IMG_HIGH(s) \ + ((s)->misc.area.high) + +#define IMG_DATA(s) \ + SER_DATA_RAW(s) + +#define VAL_IMAGE_HEAD(v) \ + QUAD_HEAD(VAL_SERIES(v)) + +#define VAL_IMAGE_TAIL(v) \ + QUAD_SKIP(VAL_SERIES(v), VAL_LEN_HEAD(v)) + +#define VAL_IMAGE_DATA(v) \ + QUAD_SKIP(VAL_SERIES(v), VAL_INDEX(v)) + +#define VAL_IMAGE_BITS(v) \ + cast(REBCNT*, VAL_IMAGE_HEAD(v)) + +#define VAL_IMAGE_WIDE(v) \ + (IMG_WIDE(VAL_SERIES(v))) +#define VAL_IMAGE_HIGH(v) \ + (IMG_HIGH(VAL_SERIES(v))) + +#define VAL_IMAGE_LEN(v) \ + VAL_LEN_AT(v) + +#define Init_Image(v,s) \ + Init_Any_Series((v), REB_IMAGE, (s)); + +//tuple to image! pixel order bytes +#define TO_PIXEL_TUPLE(t) \ + TO_PIXEL_COLOR(VAL_TUPLE(t)[0], VAL_TUPLE(t)[1], VAL_TUPLE(t)[2], \ + VAL_TUPLE_LEN(t) > 3 ? VAL_TUPLE(t)[3] : 0xff) + +//tuple to RGBA bytes +#define TO_COLOR_TUPLE(t) \ + TO_RGBA_COLOR(VAL_TUPLE(t)[0], VAL_TUPLE(t)[1], VAL_TUPLE(t)[2], \ + VAL_TUPLE_LEN(t) > 3 ? VAL_TUPLE(t)[3] : 0xff) + + +//=////////////////////////////////////////////////////////////////////////=// +// +// GOB! Graphic Object +// +//=////////////////////////////////////////////////////////////////////////=// +// +// !!! The GOB! is a datatype specific to R3-View. Its data is a small +// fixed-size object. It is linked together by series containing more +// GOBs and values, and participates in the garbage collection process. +// +// The monolithic structure of Rebol had made it desirable to take advantage +// of the memory pooling to quickly allocate, free, and garbage collect +// these. With GOB! being moved to an extension, it is not likely that it +// would hook the memory pools directly. +// + +#define VAL_GOB(v) \ + ((v)->payload.gob.gob) + +#define VAL_GOB_INDEX(v) \ + ((v)->payload.gob.index) + +inline static void SET_GOB(RELVAL *v, REBGOB *g) { + VAL_RESET_HEADER(v, REB_GOB); + VAL_GOB(v) = g; + VAL_GOB_INDEX(v) = 0; +} + + +// !!! Because you cannot assign REBVALs to one another (e.g. `*dest = *src`) +// a function is used. The reason that a function is used is because this +// gives more flexibility in decisions based on the destination cell regarding +// whether it is necessary to reify information in the source cell. +// +// That advanced purpose has not yet been implemented, because it requires +// being able to "sniff" a cell for its lifetime. For now it only preserves +// the VALUE_FLAG_STACK bit, without actually doing anything with it. +// +// Interface designed to line up with Derelativize() +// +inline static REBVAL *Move_Value(RELVAL *out, const REBVAL *v) +{ + assert( + ALL_VAL_FLAGS(v, NODE_FLAG_CELL | NODE_FLAG_NODE) + && NOT_VAL_FLAG(v, NODE_FLAG_FREE) + ); + assert(NOT_END(v)); + ASSERT_CELL_WRITABLE(out, __FILE__, __LINE__); + + out->header.bits &= CELL_MASK_RESET; + out->header.bits |= v->header.bits & CELL_MASK_COPY; + + // Note: In theory it would be possible to make payloads that had stack + // lifetimes by default, which would be promoted to GC lifetimes using + // the same kind of logic that the on-demand reification of FRAME!s + // uses. In practice, this would be very difficult to take advantage of + // in C, because it really applies best with things that can live on + // the C stack--and Rebol arrays don't have that form of invocation. + // + out->payload = v->payload; + + if ( + // NOT(v->header.bits & (VALUE_FLAG_BINDABLE | VALUE_FLAG_STACK)) + // || v->extra.binding->header.bits & NODE_FLAG_MANAGED + // + NOT(v->header.bits & VALUE_FLAG_STACK) + ) { + // If the source value isn't the kind of value that can have a + // non-reified binding (e.g. an INTEGER! or STRING!), then it is + // fully specified by definition. + // + // Also, if it is the kind of value that can have a non-reified + // binding but isn't resident on the stack, we know that it must have + // already been reified. + // + // Finally, if it's the kind of thing that can have a non-reified + // binding but it's managed, then that's also fine. + // + out->extra = v->extra; + return KNOWN(out); + } + + // If we get here, the source value is on the stack and has a non-reified + // binding of some kind. Check to see if the target stack level will + // outlive the stack level of the non-reified material in the binding. + + REBCNT bind_depth = 1; // !!! need to determine v's binding stack level + REBCNT out_depth; + if (NOT(out->header.bits & VALUE_FLAG_STACK)) + out_depth = 0; + else + out_depth = 1; // !!! need to determine out's stack level + + if (out_depth >= bind_depth) { + // + // The non-reified binding will outlive the output slot, so there is + // no reason to reify it. + // + out->extra = v->extra; + return KNOWN(out); + } + + // This is the expensive case, we know the binding as-is will not outlive + // the target slot. A reification is necessary. + + // !!! Code is not written yet, but neither are there any actual non + // reified bindings in the wild. + + out->extra = v->extra; + return KNOWN(out); +} + + +// The way globals are currently declared, one cannot use the DECLARE_LOCAL +// macro...because they run through a strange PVAR and TVAR process. +// There would also be no FS_TOP in effect to capture when they are being +// initialized. This is similar to INIT_CELL, but being tracked separately +// because the strategy needs more review. +// +// (In particular, the frame's miscellaneous `f->cell` needs review) +// +#define Prep_Global_Cell(cell) \ + INIT_CELL(cell) + + +// +// Rather than allow a REBVAL to be declared plainly as a local variable in +// a C function, this macro provides a generic "constructor-like" hook. +// See VALUE_FLAG_STACK for the experimental motivation. However, even if +// this were merely a synonym for a plain REBVAL declaration in the release +// build, it provides a useful generic hook into the point of declaration +// of a stack value. +// +// Note: because this will run instructions, a routine should avoid doing a +// DECLARE_LOCAL inside of a loop. It should be at the outermost scope of +// the function. +// +// Note: It sets NODE_FLAG_FREE, so this is a "trash" cell by default. +// +#define DECLARE_LOCAL(name) \ + REBSER name##_pair; \ + *cast(RELVAL*, &name##_pair) = *BLANK_VALUE; /* => tbd: FS_TOP FRAME! */ \ + REBVAL * const name = cast(REBVAL*, &name##_pair) + 1; \ + name->header.bits = (NODE_FLAG_NODE | NODE_FLAG_FREE \ + | NODE_FLAG_CELL | VALUE_FLAG_STACK) diff --git a/src/include/sys-varargs.h b/src/include/sys-varargs.h new file mode 100644 index 0000000000..f9bbb7eb14 --- /dev/null +++ b/src/include/sys-varargs.h @@ -0,0 +1,54 @@ +// +// File: %sys-varargs.h +// Summary: {Definitions for Variadic Value Type} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// A VARARGS! represents a point for parameter gathering inline at the +// callsite of a function. The point is located *after* that function has +// gathered all of its arguments and started running. It is implemented by +// holding a reference to a reified FRAME! series, which allows it to find +// the point of a running evaluation (as well as to safely check for when +// that call is no longer on the stack, and can't provide data.) +// +// A second VARARGS! form is implemented as a thin proxy over an ANY-ARRAY!. +// This mimics the interface of feeding forward through those arguments, to +// allow for "parameter packs" that can be passed to variadic functions. +// +// When the bits of a payload of a VARARGS! are copied from one item to +// another, they are still maintained in sync. TAKE-ing a vararg off of one +// is reflected in the others. This means that the "indexor" position of +// the vararg is located through the frame pointer. If there is no frame, +// then a single element array (the `array`) holds an ANY-ARRAY! value that +// is shared between the instances, to reflect the state. +// + +#ifdef NDEBUG + #define VARARGS_FLAG(n) \ + FLAGIT_LEFT(TYPE_SPECIFIC_BIT + (n)) +#else + #define VARARGS_FLAG(n) \ + (FLAGIT_LEFT(TYPE_SPECIFIC_BIT + (n)) | HEADERIZE_KIND(REB_VARARGS)) +#endif diff --git a/src/include/sys-word.h b/src/include/sys-word.h new file mode 100644 index 0000000000..86a655f0df --- /dev/null +++ b/src/include/sys-word.h @@ -0,0 +1,196 @@ +// +// File: %sys-word.h +// Summary: {Definitions for the ANY-WORD! Datatypes} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The ANY-WORD! is the fundamental symbolic concept of Rebol. It is +// implemented as a REBSTR UTF-8 string (see %sys-string.h), and can act as +// a variable when it is bound specifically to a context (see %sys-context.h) +// or when bound relatively to a function (see %sys-function.h). +// +// For routines that manage binding, see %sys-bind.h. +// +// !!! Today's words are different from ANY-STRING! values. This is because +// they are interned (only one copy of the string data for all instances), +// read-only, use UTF-8 instead of a variable 1 or 2-bytes per character, +// and permit binding. Ren-C intends to pare away these differences, perhaps +// even to the point of allowing mutable WORD!s and bindable STRING!s. This +// is at the idea stage, but is evolving. +// + +#ifdef NDEBUG + #define WORD_FLAG(n) \ + FLAGIT_LEFT(TYPE_SPECIFIC_BIT + (n)) +#else + #define WORD_FLAG(n) \ + (FLAGIT_LEFT(TYPE_SPECIFIC_BIT + (n)) | HEADERIZE_KIND(REB_WORD)) +#endif + +// `WORD_FLAG_BOUND` answers whether a word is bound, but it may be +// relatively bound if `VALUE_FLAG_RELATIVE` is set. In that case, it +// does not have a context pointer but rather a function pointer, that +// must be combined with more information to get the FRAME! where the +// word should actually be looked up. +// +// If VALUE_FLAG_RELATIVE is set, then WORD_FLAG_BOUND must also be set. +// +#define WORD_FLAG_BOUND WORD_FLAG(0) + + +#define IS_WORD_BOUND(v) \ + GET_VAL_FLAG((v), WORD_FLAG_BOUND) + +#define IS_WORD_UNBOUND(v) \ + NOT(IS_WORD_BOUND(v)) + +inline static REBSTR *VAL_WORD_SPELLING(const RELVAL *v) { + assert(ANY_WORD(v)); + return v->payload.any_word.spelling; +} + +inline static REBSTR *VAL_WORD_CANON(const RELVAL *v) { + assert(ANY_WORD(v)); + return STR_CANON(v->payload.any_word.spelling); +} + +inline static OPT_REBSYM VAL_WORD_SYM(const RELVAL *v) { + return STR_SYMBOL(v->payload.any_word.spelling); +} + +inline static const REBYTE *VAL_WORD_HEAD(const RELVAL *v) { + return STR_HEAD(VAL_WORD_SPELLING(v)); // '\0' terminated UTF-8 +} + +inline static void INIT_WORD_CONTEXT(RELVAL *v, REBCTX *context) { + assert(GET_VAL_FLAG(v, WORD_FLAG_BOUND) && context != SPECIFIED); + ENSURE_ARRAY_MANAGED(CTX_VARLIST(context)); + ASSERT_ARRAY_MANAGED(CTX_KEYLIST(context)); + v->extra.binding = CTX_VARLIST(context); +} + +inline static REBCTX *VAL_WORD_CONTEXT(const REBVAL *v) { + assert(GET_VAL_FLAG((v), WORD_FLAG_BOUND)); + return VAL_SPECIFIC(v); +} + +inline static void INIT_WORD_FUNC(RELVAL *v, REBFUN *func) { + assert(GET_VAL_FLAG(v, WORD_FLAG_BOUND)); + v->extra.binding = FUNC_PARAMLIST(func); +} + +inline static REBFUN *VAL_WORD_FUNC(const RELVAL *v) { + assert(GET_VAL_FLAG(v, WORD_FLAG_BOUND)); + return VAL_RELATIVE(v); +} + +inline static void INIT_WORD_INDEX(RELVAL *v, REBCNT i) { + assert(ANY_WORD(v)); + assert(GET_VAL_FLAG((v), WORD_FLAG_BOUND)); + assert(SAME_STR( + VAL_WORD_SPELLING(v), + IS_RELATIVE(v) + ? VAL_KEY_SPELLING(FUNC_PARAM(VAL_WORD_FUNC(v), i)) + : CTX_KEY_SPELLING(VAL_WORD_CONTEXT(KNOWN(v)), i) + )); + v->payload.any_word.index = cast(REBINT, i); +} + +inline static REBCNT VAL_WORD_INDEX(const RELVAL *v) { + assert(ANY_WORD(v)); + REBINT i = v->payload.any_word.index; + assert(i > 0); + return cast(REBCNT, i); +} + +inline static void Unbind_Any_Word(RELVAL *v) { + CLEAR_VAL_FLAGS(v, WORD_FLAG_BOUND | VALUE_FLAG_RELATIVE); +#if !defined(NDEBUG) + v->payload.any_word.index = 0; +#endif +} + +inline static void Init_Any_Word( + RELVAL *out, + enum Reb_Kind kind, + REBSTR *spelling +) { + VAL_RESET_HEADER(out, kind); + + assert(spelling != NULL); + out->payload.any_word.spelling = spelling; + +#if !defined(NDEBUG) + out->payload.any_word.index = 0; +#endif + + assert(ANY_WORD(out)); + assert(IS_WORD_UNBOUND(out)); +} + +#define Init_Word(out,spelling) \ + Init_Any_Word((out), REB_WORD, (spelling)) + +#define Init_Get_Word(out,spelling) \ + Init_Any_Word((out), REB_GET_WORD, (spelling)) + +#define Init_Set_Word(out,spelling) \ + Init_Any_Word((out), REB_SET_WORD, (spelling)) + +#define Init_Lit_Word(out,spelling) \ + Init_Any_Word((out), REB_LIT_WORD, (spelling)) + +#define Init_Refinement(out,spelling) \ + Init_Any_Word((out), REB_REFINEMENT, (spelling)) + +#define Init_Issue(out,spelling) \ + Init_Any_Word((out), REB_ISSUE, (spelling)) + +// Initialize an ANY-WORD! type with a binding to a context. +// +inline static void Init_Any_Word_Bound( + REBVAL *out, + enum Reb_Kind type, + REBSTR *spelling, + REBCTX *context, + REBCNT index +) { + assert(CTX_KEY_CANON(context, index) == STR_CANON(spelling)); + + VAL_RESET_HEADER_EXTRA(out, type, WORD_FLAG_BOUND); + + assert(spelling != NULL); + out->payload.any_word.spelling = spelling; + + INIT_WORD_CONTEXT(out, context); + INIT_WORD_INDEX(out, index); + + assert(ANY_WORD(out)); + assert(IS_WORD_BOUND(out)); +} + +inline static void Canonize_Any_Word(REBVAL *any_word) { + any_word->payload.any_word.spelling = VAL_WORD_CANON(any_word); +} diff --git a/src/include/sys-zlib.h b/src/include/sys-zlib.h index d7c064a5c8..f7f558cf0a 100644 --- a/src/include/sys-zlib.h +++ b/src/include/sys-zlib.h @@ -1,160 +1,281 @@ +// +// Extraction of ZLIB compression and decompression routines +// for REBOL [R3] Language Interpreter and Run-time Environment +// This is a code-generated file. +// +// ZLIB Copyright notice: +// +// (C) 1995-2013 Jean-loup Gailly and Mark Adler +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +// +// Jean-loup Gailly Mark Adler +// jloup@gzip.org madler@alumni.caltech.edu +// +// REBOL is a trademark of REBOL Technologies +// Licensed under the Apache License, Version 2.0 +// +// ********************************************************************** +// +// Title: ZLIB aggregated header file +// Build: A0 +// Date: 29-Sep-2013 +// File: sys-zlib.h +// +// AUTO-GENERATED FILE - Do not modify. (From: make-zlib.r) +// + +#define NO_DUMMY_DECL 1 +#define Z_PREFIX 1 + /* zconf.h -- configuration of the zlib compression library - * Copyright (C) 1995-1998 Jean-loup Gailly. - * For conditions of distribution and use, see copyright notice in zlib.h + * Copyright (C) 1995-2013 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h */ -#ifndef _ZCONF_H -#define _ZCONF_H -#define Z_PREFIX + +/* @(#) $Id$ */ + +#ifndef ZCONF_H +#define ZCONF_H + +// Ren/C - Yes, we want Zlib to be const compliant! +#define ZLIB_CONST + /* * If you *really* need a unique prefix for all types and library functions, * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + * Even better than compiling with -DZ_PREFIX would be to use configure to set + * this permanently in zconf.h using "./configure --zprefix". */ -#ifdef Z_PREFIX -# define deflateInit_ Z_deflateInit_ -# define deflate Z_deflate -# define deflateEnd Z_deflateEnd -# define inflateInit_ Z_inflateInit_ -# define inflate Z_inflate -# define inflateEnd Z_inflateEnd -# define deflateInit2_ Z_deflateInit2_ -# define deflateSetDictionary Z_deflateSetDictionary -# define deflateCopy Z_deflateCopy -# define deflateReset Z_deflateReset -# define deflateParams Z_deflateParams -# define inflateInit2_ Z_inflateInit2_ -# define inflateSetDictionary Z_inflateSetDictionary -# define inflateSync Z_inflateSync -# define inflateSyncPoint Z_inflateSyncPoint -# define inflateReset Z_inflateReset -# define compress Z_compress -# define compress2 Z_compress2 -# define uncompress Z_uncompress -# define adler32 Z_adler32 -# define crc32 Z_crc32 -# define get_crc_table Z_get_crc_table -# define _dist_code Z__dist_code -# define _length_code Z__length_code -# define _tr_align Z__tr_align -# define _tr_flush_block Z__tr_flush_block -# define _tr_init Z__tr_init -# define _tr_stored_block Z__tr_stored_block -# define base_dist Z_base_dist -# define base_length Z_base_length -# define bi_flush Z_bi_flush -# define bi_reverse Z_bi_reverse -# define bi_windup Z_bi_windup -# define bl_order Z_bl_order -# define border Z_border -# define build_bl_tree Z_build_bl_tree -# define build_tree Z_build_tree -# define compress_block Z_compress_block -# define configuration_table Z_configuration_table -# define copy_block Z_copy_block -# define cpdext Z_cpdext -# define cpdist Z_cpdist -# define cplens Z_cplens -# define cplext Z_cplext -# define deflate_slow Z_deflate_slow -# define deflate_stored Z_deflate_stored -# define extra_blbits Z_extra_blbits -# define extra_dbits Z_extra_dbits -# define extra_lbits Z_extra_lbits -# define fill_window Z_fill_window -# define fixed_bd Z_fixed_bd -# define fixed_bl Z_fixed_bl -# define fixed_built Z_fixed_built -# define fixed_mem Z_fixed_mem -# define fixed_td Z_fixed_td -# define fixed_tl Z_fixed_tl -# define flush_pending Z_flush_pending -# define gen_bitlen Z_gen_bitlen -# define gen_codes Z_gen_codes -# define huft_build Z_huft_build -# define inflate_blocks Z_inflate_blocks -# define inflate_blocks_free Z_inflate_blocks_free -# define inflate_blocks_new Z_inflate_blocks_new -# define inflate_blocks_reset Z_inflate_blocks_reset -# define inflate_codes Z_inflate_codes -# define inflate_codes_free Z_inflate_codes_free -# define inflate_codes_new Z_inflate_codes_new -# define inflate_flush Z_inflate_flush -# define inflate_mask Z_inflate_mask -# define inflate_trees_bits Z_inflate_trees_bits -# define inflate_trees_dynamic Z_inflate_trees_dynamic -# define inflate_trees_fixed Z_inflate_trees_fixed -# define init_block Z_init_block -# define lm_init Z_lm_init -# define longest_match Z_longest_match -# define pqdownheap Z_pqdownheap -# define putShortMSB Z_putShortMSB -# define read_buf Z_read_buf -# define scan_tree Z_scan_tree -# define send_all_trees Z_send_all_trees -# define send_bits Z_send_bits -# define send_tree Z_send_tree -# define set_data_type Z_set_data_type -# define static_bl_desc Z_static_bl_desc -# define static_d_desc Z_static_d_desc -# define static_dtree Z_static_dtree -# define static_l_desc Z_static_l_desc -# define static_ltree Z_static_ltree -# define tr_static_init Z_tr_static_init -# define z_errmsg Z_z_errmsg -# define zcalloc Z_zcalloc -# define zcfree Z_zcfree -# define Byte Z_Byte -# define uInt Z_uInt -# define uLong Z_uLong -# define Bytef Z_Bytef -# define charf Z_charf -# define intf Z_intf -# define uIntf Z_uIntf -# define uLongf Z_uLongf -# define voidpf Z_voidpf -# define voidp Z_voidp -#endif -#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) -# define WIN32 -#endif -#if defined(__GNUC__) || defined(WIN32) || defined(__386__) || defined(i386) -# ifndef __32BIT__ -# define __32BIT__ +#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */ +# define Z_PREFIX_SET + +/* all linked symbols */ +# define _dist_code z__dist_code +# define _length_code z__length_code +# define _tr_align z__tr_align +# define _tr_flush_bits z__tr_flush_bits +# define _tr_flush_block z__tr_flush_block +# define _tr_init z__tr_init +# define _tr_stored_block z__tr_stored_block +# define _tr_tally z__tr_tally +# define adler32 z_adler32 +# define adler32_combine z_adler32_combine +# define adler32_combine64 z_adler32_combine64 +# ifndef Z_SOLO +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# endif +# define crc32 z_crc32 +# define crc32_combine z_crc32_combine +# define crc32_combine64 z_crc32_combine64 +# define deflate z_deflate +# define deflateBound z_deflateBound +# define deflateCopy z_deflateCopy +# define deflateEnd z_deflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateInit_ z_deflateInit_ +# define deflateParams z_deflateParams +# define deflatePending z_deflatePending +# define deflatePrime z_deflatePrime +# define deflateReset z_deflateReset +# define deflateResetKeep z_deflateResetKeep +# define deflateSetDictionary z_deflateSetDictionary +# define deflateSetHeader z_deflateSetHeader +# define deflateTune z_deflateTune +# define deflate_copyright z_deflate_copyright +# define get_crc_table z_get_crc_table +# ifndef Z_SOLO +# define gz_error z_gz_error +# define gz_intmax z_gz_intmax +# define gz_strwinerror z_gz_strwinerror +# define gzbuffer z_gzbuffer +# define gzclearerr z_gzclearerr +# define gzclose z_gzclose +# define gzclose_r z_gzclose_r +# define gzclose_w z_gzclose_w +# define gzdirect z_gzdirect +# define gzdopen z_gzdopen +# define gzeof z_gzeof +# define gzerror z_gzerror +# define gzflush z_gzflush +# define gzgetc z_gzgetc +# define gzgetc_ z_gzgetc_ +# define gzgets z_gzgets +# define gzoffset z_gzoffset +# define gzoffset64 z_gzoffset64 +# define gzopen z_gzopen +# define gzopen64 z_gzopen64 +# ifdef _WIN32 +# define gzopen_w z_gzopen_w +# endif +# define gzprintf z_gzprintf +# define gzvprintf z_gzvprintf +# define gzputc z_gzputc +# define gzputs z_gzputs +# define gzread z_gzread +# define gzrewind z_gzrewind +# define gzseek z_gzseek +# define gzseek64 z_gzseek64 +# define gzsetparams z_gzsetparams +# define gztell z_gztell +# define gztell64 z_gztell64 +# define gzungetc z_gzungetc +# define gzwrite z_gzwrite +# endif +# define inflate z_inflate +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define inflateBackInit_ z_inflateBackInit_ +# define inflateCopy z_inflateCopy +# define inflateEnd z_inflateEnd +# define inflateGetHeader z_inflateGetHeader +# define inflateInit2_ z_inflateInit2_ +# define inflateInit_ z_inflateInit_ +# define inflateMark z_inflateMark +# define inflatePrime z_inflatePrime +# define inflateReset z_inflateReset +# define inflateReset2 z_inflateReset2 +# define inflateSetDictionary z_inflateSetDictionary +# define inflateGetDictionary z_inflateGetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateUndermine z_inflateUndermine +# define inflateResetKeep z_inflateResetKeep +# define inflate_copyright z_inflate_copyright +# define inflate_fast z_inflate_fast +# define inflate_table z_inflate_table +# ifndef Z_SOLO +# define uncompress z_uncompress # endif +# define zError z_zError +# ifndef Z_SOLO +# define zcalloc z_zcalloc +# define zcfree z_zcfree +# endif +# define zlibCompileFlags z_zlibCompileFlags +# define zlibVersion z_zlibVersion + +/* all zlib typedefs in zlib.h and zconf.h */ +# define Byte z_Byte +# define Bytef z_Bytef +# define alloc_func z_alloc_func +# define charf z_charf +# define free_func z_free_func +# ifndef Z_SOLO +# define gzFile z_gzFile +# endif +# define gz_header z_gz_header +# define gz_headerp z_gz_headerp +# define in_func z_in_func +# define intf z_intf +# define out_func z_out_func +# define uInt z_uInt +# define uIntf z_uIntf +# define uLong z_uLong +# define uLongf z_uLongf +# define voidp z_voidp +# define voidpc z_voidpc +# define voidpf z_voidpf + +/* all zlib structs in zlib.h and zconf.h */ +# define gz_header_s z_gz_header_s +# define internal_state z_internal_state + #endif + #if defined(__MSDOS__) && !defined(MSDOS) # define MSDOS #endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + /* * Compile with -DMAXSEG_64K if the alloc function cannot allocate more * than 64k bytes at a time (needed on systems with 16-bit int). */ -/* #if defined(MSDOS) && !defined(__32BIT__) */ -/* # define MAXSEG_64K */ -/* #endif */ -/* #ifdef MSDOS */ -/* # define UNALIGNED_OK */ -/* #endif */ -#if (defined(MSDOS) || defined(_WINDOWS) || defined(WIN32)) && !defined(STDC) -# define STDC +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK #endif -#if defined(__STDC__) || defined(__cplusplus) || defined(__OS2__) + +#ifdef __STDC_VERSION__ # ifndef STDC # define STDC # endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC #endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + #ifndef STDC # ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ -# define const +# define const /* note: need a more gentle solution here */ # endif #endif + +#if defined(ZLIB_CONST) && !defined(z_const) +# define z_const const +#else +# define z_const +#endif + /* Some Mac compilers merge all .h files incorrectly: */ -#if defined(__MWERKS__) || defined(applec) ||defined(THINK_C) ||defined(__SC__) +#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) # define NO_DUMMY_DECL #endif -/* Borland C incorrectly complains about missing returns: */ -#if defined(__BORLANDC__) -# define NEED_DUMMY_RETURN -#endif + /* Maximum value for memLevel in deflateInit2 */ #ifndef MAX_MEM_LEVEL # ifdef MAXSEG_64K @@ -163,6 +284,7 @@ # define MAX_MEM_LEVEL 9 # endif #endif + /* Maximum value for windowBits in deflateInit2 and inflateInit2. * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files * created by gzip. (Files created by minigzip can still be extracted by @@ -171,6 +293,7 @@ #ifndef MAX_WBITS # define MAX_WBITS 15 /* 32K LZ77 window */ #endif + /* The memory requirements for deflate are (in bytes): (1 << (windowBits+2)) + (1 << (memLevel+9)) that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) @@ -178,11 +301,14 @@ the default memory requirements from 256K to 128K, compile with make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" Of course this will generally degrade compression (there's no free lunch). + The memory requirements for inflate are (in bytes) 1 << windowBits that is, 32K for windowBits=15 (default value) plus a few kilobytes for small objects. */ + /* Type declarations */ + #ifndef OF /* function prototypes */ # ifdef STDC # define OF(args) args @@ -190,59 +316,109 @@ # define OF(args) () # endif #endif + +#ifndef Z_ARG /* function prototypes for stdarg */ +# if defined(STDC) || defined(Z_HAVE_STDARG_H) +# define Z_ARG(args) args +# else +# define Z_ARG(args) () +# endif +#endif + /* The following definitions for FAR are needed only for MSDOS mixed * model programming (small or medium model with some far allocations). * This was tested only with MSC; for other MSDOS compilers you may have * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, * just define FAR to be empty. */ -#if (defined(M_I86SM) || defined(M_I86MM)) && !defined(__32BIT__) - /* MSC small or medium model */ -# define SMALL_MEDIUM -# ifdef _MSC_VER -# define FAR __far -# else -# define FAR far +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif # endif -#endif -#if defined(__BORLANDC__) && (defined(__SMALL__) || defined(__MEDIUM__)) -# ifndef __32BIT__ +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ # define SMALL_MEDIUM -# define FAR __far +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif # endif #endif -/* Compile with -DZLIB_DLL for Windows DLL support */ -#if (defined(_WINDOWS) || defined(WINDOWS)) && defined(ZLIB_DLL) -# ifdef FAR -# undef FAR + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif # endif -# include -# define ZEXPORT WINAPI -# ifdef WIN32 -# define ZEXPORTVA WINAPIV -# else -# define ZEXPORTVA FAR _cdecl _export +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif # endif -#else -# if defined (__BORLANDC__) && defined (_Windows) && defined (__DLL__) -# define ZEXPORT _export -# define ZEXPORTVA _export -# else -# define ZEXPORT -# define ZEXPORTVA -# endif #endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + #ifndef FAR -# define FAR +# define FAR #endif -//#if !defined(__MACTYPES__) -/* added MacTypes.h test JDJ */ + +#if !defined(__MACTYPES__) typedef unsigned char Byte; /* 8 bits */ -//#endif +#endif typedef unsigned int uInt; /* 16 bits or more */ typedef unsigned long uLong; /* 32 bits or more */ -#if defined(__BORLANDC__) && defined(SMALL_MEDIUM) - /* Borland C/C++ ignores FAR inside typedef */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ # define Bytef Byte FAR #else typedef Byte FAR Bytef; @@ -251,64 +427,401 @@ typedef char FAR charf; typedef int FAR intf; typedef uInt FAR uIntf; typedef uLong FAR uLongf; + #ifdef STDC - typedef void FAR *voidpf; - typedef void *voidp; + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC) +# include +# if (UINT_MAX == 0xffffffffUL) +# define Z_U4 unsigned +# elif (ULONG_MAX == 0xffffffffUL) +# define Z_U4 unsigned long +# elif (USHRT_MAX == 0xffffffffUL) +# define Z_U4 unsigned short +# endif +#endif + +#ifdef Z_U4 + typedef Z_U4 z_crc_t; #else - typedef Byte FAR *voidpf; - typedef Byte *voidp; + typedef unsigned long z_crc_t; +#endif + +#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_UNISTD_H +#endif + +#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_STDARG_H +#endif + +#ifdef STDC +# ifndef Z_SOLO +# include /* for off_t */ +# endif +#endif + +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +# include /* for va_list */ +# endif +#endif + +#ifdef _WIN32 +# ifndef Z_SOLO +# include /* for wchar_t */ +# endif #endif -#if defined(HAVE_UNISTD_H) -#if !defined(TO_WINCE) -# include /* for off_t */ -# include /* for SEEK_* and off_t */ + +/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and + * "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even + * though the former does not conform to the LFS document), but considering + * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as + * equivalently requesting no 64-bit operations + */ +#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1 +# undef _LARGEFILE64_SOURCE #endif -# define z_off_t off_t + +#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H) +# define Z_HAVE_UNISTD_H #endif -#ifndef SEEK_SET +#ifndef Z_SOLO +# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE) +# include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ +# ifdef VMS +# include /* for off_t */ +# endif +# ifndef z_off_t +# define z_off_t off_t +# endif +# endif +#endif + +#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0 +# define Z_LFS64 +#endif + +#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64) +# define Z_LARGE64 +#endif + +#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64) +# define Z_WANT64 +#endif + +#if !defined(SEEK_SET) && !defined(Z_SOLO) # define SEEK_SET 0 /* Seek from beginning of file. */ # define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ #endif + #ifndef z_off_t -# define z_off_t long +# define z_off_t long #endif + +#if !defined(_WIN32) && defined(Z_LARGE64) +# define z_off64_t off64_t +#else +# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO) +# define z_off64_t __int64 +# else +# define z_off64_t z_off_t +# endif +#endif + /* MVS linker does not support external names larger than 8 bytes */ #if defined(__MVS__) -# pragma map(deflateInit_,"DEIN") -# pragma map(deflateInit2_,"DEIN2") -# pragma map(deflateEnd,"DEEND") -# pragma map(inflateInit_,"ININ") -# pragma map(inflateInit2_,"ININ2") -# pragma map(inflateEnd,"INEND") -# pragma map(inflateSync,"INSY") -# pragma map(inflateSetDictionary,"INSEDI") -# pragma map(inflate_blocks,"INBL") -# pragma map(inflate_blocks_new,"INBLNE") -# pragma map(inflate_blocks_free,"INBLFR") -# pragma map(inflate_blocks_reset,"INBLRE") -# pragma map(inflate_codes_free,"INCOFR") -# pragma map(inflate_codes,"INCO") -//# pragma map(inflate_fast,"INFA") -# pragma map(inflate_flush,"INFLU") -# pragma map(inflate_mask,"INMA") -/* # pragma map(inflate_set_dictionary,"INSEDI2") */ -# pragma map(inflate_copyright,"INCOPY") -# pragma map(inflate_trees_bits,"INTRBI") -# pragma map(inflate_trees_dynamic,"INTRDY") -# pragma map(inflate_trees_fixed,"INTRFI") -# pragma map(inflate_trees_free,"INTRFR") -#endif -#endif /* _ZCONF_H */ -//////////////////////////////////////////////////////////////// + #pragma map(deflateInit_,"DEIN") + #pragma map(deflateInit2_,"DEIN2") + #pragma map(deflateEnd,"DEEND") + #pragma map(deflateBound,"DEBND") + #pragma map(inflateInit_,"ININ") + #pragma map(inflateInit2_,"ININ2") + #pragma map(inflateEnd,"INEND") + #pragma map(inflateSync,"INSY") + #pragma map(inflateSetDictionary,"INSEDI") + #pragma map(compressBound,"CMBND") + #pragma map(inflate_table,"INTABL") + #pragma map(inflate_fast,"INFA") + #pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ +/* zutil.h -- internal interface and configuration of the compression library + * Copyright (C) 1995-2013 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id$ */ + +#ifndef ZUTIL_H +#define ZUTIL_H + +#ifdef HAVE_HIDDEN +# define ZLIB_INTERNAL __attribute__((visibility ("hidden"))) +#else +# define ZLIB_INTERNAL +#endif + +// #include "zlib.h" /* In sys-zlib.h (see make-zlib.r) */ + +#if defined(STDC) && !defined(Z_SOLO) +# if !(defined(_WIN32_WCE) && defined(_MSC_VER)) +# include +# endif +# include +# include +#endif + +#ifdef Z_SOLO + typedef long ptrdiff_t; /* guess -- will be caught if guess is wrong */ +#endif + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +typedef unsigned char uch; +typedef uch FAR uchf; +typedef unsigned short ush; +typedef ush FAR ushf; +typedef unsigned long ulg; + +extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ +/* (size given to avoid silly warnings with Visual C++) */ + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ + return (strm->msg = ERR_MSG(err), (err)) +/* To be used only when the state is known to be valid */ + + /* common constants */ + +#ifndef DEF_WBITS +# define DEF_WBITS MAX_WBITS +#endif +/* default windowBits for decompression. MAX_WBITS is for compression only */ + +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +/* default memLevel */ + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ + + /* target dependencies */ + +#if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32)) +# define OS_CODE 0x00 +# ifndef Z_SOLO +# if defined(__TURBOC__) || defined(__BORLANDC__) +# if (__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) + /* Allow compilation with ANSI keywords only enabled */ + void _Cdecl farfree( void *block ); + void *_Cdecl farmalloc( unsigned long nbytes ); +# else +# include +# endif +# else /* MSC or DJGPP */ +# include +# endif +# endif +#endif + +#ifdef AMIGA +# define OS_CODE 0x01 +#endif + +#if defined(VAXC) || defined(VMS) +# define OS_CODE 0x02 +# define F_OPEN(name, mode) \ + fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512") +#endif + +#if defined(ATARI) || defined(atarist) +# define OS_CODE 0x05 +#endif + +#ifdef OS2 +# define OS_CODE 0x06 +# if defined(M_I86) && !defined(Z_SOLO) +# include +# endif +#endif + +#if defined(MACOS) || defined(TARGET_OS_MAC) +# define OS_CODE 0x07 +# ifndef Z_SOLO +# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include /* for fdopen */ +# else +# ifndef fdopen +# define fdopen(fd,mode) NULL /* No fdopen() */ +# endif +# endif +# endif +#endif + +#ifdef TOPS20 +# define OS_CODE 0x0a +#endif + +#ifdef WIN32 +# ifndef __CYGWIN__ /* Cygwin is Unix, not Win32 */ +# define OS_CODE 0x0b +# endif +#endif + +#ifdef __50SERIES /* Prime/PRIMOS */ +# define OS_CODE 0x0f +#endif + +#if defined(_BEOS_) || defined(RISCOS) +# define fdopen(fd,mode) NULL /* No fdopen() */ +#endif + +#if (defined(_MSC_VER) && (_MSC_VER > 600)) && !defined __INTERIX +# if defined(_WIN32_WCE) +# define fdopen(fd,mode) NULL /* No fdopen() */ +# ifndef _PTRDIFF_T_DEFINED + typedef int ptrdiff_t; +# define _PTRDIFF_T_DEFINED +# endif +# else +# define fdopen(fd,type) _fdopen(fd,type) +# endif +#endif + +#if defined(__BORLANDC__) && !defined(MSDOS) + #pragma warn -8004 + #pragma warn -8008 + #pragma warn -8066 +#endif + +/* provide prototypes for these when building zlib without LFS */ +#if !defined(_WIN32) && \ + (!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0) + ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); +#endif + + /* common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef F_OPEN +# define F_OPEN(name, mode) fopen((name), (mode)) +#endif + + /* functions */ + +#if defined(pyr) || defined(Z_SOLO) +# define NO_MEMCPY +#endif +#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__) + /* Use our own functions for small and medium model with MSC <= 5.0. + * You may have to use the same strategy for Borland C (untested). + * The __SC__ check is for Symantec. + */ +# define NO_MEMCPY +#endif +#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY) +# define HAVE_MEMCPY +#endif +#ifdef HAVE_MEMCPY +# ifdef SMALL_MEDIUM /* MSDOS small or medium model */ +# define zmemcpy _fmemcpy +# define zmemcmp _fmemcmp +# define zmemzero(dest, len) _fmemset(dest, 0, len) +# else +# define zmemcpy memcpy +# define zmemcmp memcmp +# define zmemzero(dest, len) memset(dest, 0, len) +# endif +#else + void ZLIB_INTERNAL zmemcpy OF((Bytef* dest, const Bytef* source, uInt len)); + int ZLIB_INTERNAL zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len)); + void ZLIB_INTERNAL zmemzero OF((Bytef* dest, uInt len)); +#endif + +/* Diagnostic functions */ +#ifdef DEBUG +// # include // !!! No in Ren-C release builds + extern int ZLIB_INTERNAL z_verbose; + extern void ZLIB_INTERNAL z_error OF((char *m)); +# define Assert(cond,msg) {if(!(cond)) z_error(msg);} +# define Trace(x) {if (z_verbose>=0) fprintf x ;} +# define Tracev(x) {if (z_verbose>0) fprintf x ;} +# define Tracevv(x) {if (z_verbose>1) fprintf x ;} +# define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;} +# define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +#endif + +#ifndef Z_SOLO + voidpf ZLIB_INTERNAL zcalloc OF((voidpf opaque, unsigned items, + unsigned size)); + void ZLIB_INTERNAL zcfree OF((voidpf opaque, voidpf ptr)); +#endif + +#define ZALLOC(strm, items, size) \ + (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr)) +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + +/* Reverse the bytes in a 32-bit value */ +#define ZSWAP32(q) ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \ + (((q) & 0xff00) << 8) + (((q) & 0xff) << 24)) + +#endif /* ZUTIL_H */ /* zlib.h -- interface of the 'zlib' general purpose compression library - version 1.1.2, March 19th, 1998 - Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler + version 1.2.8, April 28th, 2013 + + Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler + This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. + Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: + 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be @@ -316,90 +829,158 @@ typedef uLong FAR uLongf; 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. + Jean-loup Gailly Mark Adler jloup@gzip.org madler@alumni.caltech.edu + + The data format used by the zlib library is described by RFCs (Request for - Comments) 1950 to 1952 in the files ftp://ds.internic.net/rfc/rfc1950.txt - (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). + Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950 + (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). */ -#ifndef _ZLIB_H -#define _ZLIB_H + +#ifndef ZLIB_H +#define ZLIB_H + +// #include "zconf.h" /* In sys-zlib.h (see make-zlib.r) */ + #ifdef __cplusplus extern "C" { #endif -#define ZLIB_VERSION "1.1.2" -/* - The 'zlib' compression library provides in-memory compression and - decompression functions, including integrity checks of the uncompressed - data. This version of the library supports only one compression method - (deflation) but other algorithms will be added later and will have the same - stream interface. - Compression can be done in a single step if the buffers are large - enough (for example if an input file is mmap'ed), or can be done by - repeated calls of the compression function. In the latter case, the - application must provide more input and/or consume the output + +#define ZLIB_VERSION "1.2.8" +#define ZLIB_VERNUM 0x1280 +#define ZLIB_VER_MAJOR 1 +#define ZLIB_VER_MINOR 2 +#define ZLIB_VER_REVISION 8 +#define ZLIB_VER_SUBREVISION 0 + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed data. + This version of the library supports only one compression method (deflation) + but other algorithms will be added later and will have the same stream + interface. + + Compression can be done in a single step if the buffers are large enough, + or can be done by repeated calls of the compression function. In the latter + case, the application must provide more input and/or consume the output (providing more output space) before each call. - The library also supports reading and writing files in gzip (.gz) format - with an interface similar to that of stdio. - The library does not install any signal handler. The decoder checks - the consistency of the compressed data, so the library should never - crash even in case of corrupted input. + + The compressed data format used by default by the in-memory functions is + the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped + around a deflate stream, which is itself documented in RFC 1951. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio using the functions that start + with "gz". The gzip format is different from the zlib format. gzip is a + gzip wrapper, documented in RFC 1952, wrapped around a deflate stream. + + This library can optionally read and write gzip streams in memory as well. + + The zlib format was designed to be compact and fast for use in memory + and on communications channels. The gzip format was designed for single- + file compression on file systems, has a larger header than zlib to maintain + directory information, and uses a different, slower check method than zlib. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never crash + even in case of corrupted input. */ -typedef uLong (ZEXPORT *check_func) OF((uLong check, const Bytef *buf, uInt len)); + typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); typedef void (*free_func) OF((voidpf opaque, voidpf address)); + struct internal_state; + typedef struct z_stream_s { - Bytef *next_in; /* next input byte */ + z_const Bytef *next_in; /* next input byte */ uInt avail_in; /* number of bytes available at next_in */ - uLong total_in; /* total nb of input bytes read so far */ + uLong total_in; /* total number of input bytes read so far */ + Bytef *next_out; /* next output byte should be put there */ uInt avail_out; /* remaining free space at next_out */ - uLong total_out; /* total nb of bytes output so far */ - char *msg; /* last error message, NULL if no error */ + uLong total_out; /* total number of bytes output so far */ + + z_const char *msg; /* last error message, NULL if no error */ struct internal_state FAR *state; /* not visible by applications */ + alloc_func zalloc; /* used to allocate the internal state */ free_func zfree; /* used to free the internal state */ voidpf opaque; /* private data object passed to zalloc and zfree */ - int data_type; /* best guess about the data type: ascii or binary */ + + int data_type; /* best guess about the data type: binary or text */ uLong adler; /* adler32 value of the uncompressed data */ - check_func checksum; /* reserved for future use */ + uLong reserved; /* reserved for future use */ } z_stream; + typedef z_stream FAR *z_streamp; + +/* + gzip header information passed to and from zlib routines. See RFC 1952 + for more details on the meanings of these fields. +*/ +typedef struct gz_header_s { + int text; /* true if compressed data believed to be text */ + uLong time; /* modification time */ + int xflags; /* extra flags (not used when writing a gzip file) */ + int os; /* operating system */ + Bytef *extra; /* pointer to extra field or Z_NULL if none */ + uInt extra_len; /* extra field length (valid if extra != Z_NULL) */ + uInt extra_max; /* space at extra (only when reading header) */ + Bytef *name; /* pointer to zero-terminated file name or Z_NULL */ + uInt name_max; /* space at name (only when reading header) */ + Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */ + uInt comm_max; /* space at comment (only when reading header) */ + int hcrc; /* true if there was or will be a header crc */ + int done; /* true when done reading gzip header (not used + when writing a gzip file) */ +} gz_header; + +typedef gz_header FAR *gz_headerp; + /* - The application must update next_in and avail_in when avail_in has - dropped to zero. It must update next_out and avail_out when avail_out - has dropped to zero. The application must initialize zalloc, zfree and - opaque before calling the init function. All other fields are set by the - compression library and must not be updated by the application. - The opaque value provided by the application will be passed as the first - parameter for calls of zalloc and zfree. This can be useful for custom - memory management. The compression library attaches no meaning to the + The application must update next_in and avail_in when avail_in has dropped + to zero. It must update next_out and avail_out when avail_out has dropped + to zero. The application must initialize zalloc, zfree and opaque before + calling the init function. All other fields are set by the compression + library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the opaque value. - zalloc must return Z_NULL if there is not enough memory for the object. + + zalloc must return Z_NULL if there is not enough memory for the object. If zlib is used in a multi-threaded application, zalloc and zfree must be thread safe. - On 16-bit systems, the functions zalloc and zfree must be able to allocate - exactly 65536 bytes, but will not be required to allocate more than this - if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, - pointers returned by zalloc for objects of exactly 65536 bytes *must* - have their offset normalized to zero. The default allocation function - provided by this library ensures this (see zutil.c). To reduce memory - requirements and avoid any allocation of 64K objects, at the expense of - compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). - The fields total_in and total_out can be used for statistics or - progress reports. After compression, total_in holds the total size of - the uncompressed data and may be saved for use in the decompressor - (particularly if the decompressor wants to decompress everything in - a single step). + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this if + the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, pointers + returned by zalloc for objects of exactly 65536 bytes *must* have their + offset normalized to zero. The default allocation function provided by this + library ensures this (see zutil.c). To reduce memory requirements and avoid + any allocation of 64K objects, at the expense of compression ratio, compile + the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or progress + reports. After compression, total_in holds the total size of the + uncompressed data and may be saved for use in the decompressor (particularly + if the decompressor wants to decompress everything in a single step). */ + /* constants */ + #define Z_NO_FLUSH 0 -#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_PARTIAL_FLUSH 1 #define Z_SYNC_FLUSH 2 #define Z_FULL_FLUSH 3 #define Z_FINISH 4 -/* Allowed flush values; see deflate() below for details */ +#define Z_BLOCK 5 +#define Z_TREES 6 +/* Allowed flush values; see deflate() and inflate() below for details */ + #define Z_OK 0 #define Z_STREAM_END 1 #define Z_NEED_DICT 2 @@ -409,1051 +990,1656 @@ typedef z_stream FAR *z_streamp; #define Z_MEM_ERROR (-4) #define Z_BUF_ERROR (-5) #define Z_VERSION_ERROR (-6) -/* Return codes for the compression/decompression functions. Negative - * values are errors, positive values are used for special but normal events. +/* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. */ + #define Z_NO_COMPRESSION 0 #define Z_BEST_SPEED 1 #define Z_BEST_COMPRESSION 9 #define Z_DEFAULT_COMPRESSION (-1) /* compression levels */ + #define Z_FILTERED 1 #define Z_HUFFMAN_ONLY 2 +#define Z_RLE 3 +#define Z_FIXED 4 #define Z_DEFAULT_STRATEGY 0 /* compression strategy; see deflateInit2() below for details */ + #define Z_BINARY 0 -#define Z_ASCII 1 +#define Z_TEXT 1 +#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */ #define Z_UNKNOWN 2 -/* Possible values of the data_type field */ +/* Possible values of the data_type field (though see inflate()) */ + #define Z_DEFLATED 8 /* The deflate compression method (the only one supported in this version) */ + #define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + #define zlib_version zlibVersion() /* for compatibility with versions < 1.0.2 */ + + /* basic functions */ -extern const char * ZEXPORT zlibVersion OF((void)); + +ZEXTERN const char * ZEXPORT zlibVersion OF((void)); /* The application can compare zlibVersion and ZLIB_VERSION for consistency. - If the first character differs, the library code actually used is - not compatible with the zlib.h header file used by the application. - This check is automatically made by deflateInit and inflateInit. + If the first character differs, the library code actually used is not + compatible with the zlib.h header file used by the application. This check + is automatically made by deflateInit and inflateInit. */ -/* -extern int ZEXPORT deflateInit OF((z_streamp strm, int level)); - Initializes the internal stream state for compression. The fields - zalloc, zfree and opaque must be initialized before by the caller. - If zalloc and zfree are set to Z_NULL, deflateInit updates them to - use default allocation functions. + +/* +ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. If + zalloc and zfree are set to Z_NULL, deflateInit updates them to use default + allocation functions. + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: - 1 gives best speed, 9 gives best compression, 0 gives no compression at - all (the input data is simply copied a block at a time). - Z_DEFAULT_COMPRESSION requests a default compromise between speed and - compression (currently equivalent to level 6). - deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not - enough memory, Z_STREAM_ERROR if level is not a valid compression level, + 1 gives best speed, 9 gives best compression, 0 gives no compression at all + (the input data is simply copied a block at a time). Z_DEFAULT_COMPRESSION + requests a default compromise between speed and compression (currently + equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if level is not a valid compression level, or Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible - with the version assumed by the caller (ZLIB_VERSION). - msg is set to null if there is no error message. deflateInit does not - perform any compression: this will be done by deflate(). + with the version assumed by the caller (ZLIB_VERSION). msg is set to null + if there is no error message. deflateInit does not perform any compression: + this will be done by deflate(). */ -extern int ZEXPORT deflate OF((z_streamp strm, int flush)); + + +ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); /* deflate compresses as much data as possible, and stops when the input - buffer becomes empty or the output buffer becomes full. It may introduce some - output latency (reading input without producing any output) except when + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when forced to flush. - The detailed semantics are as follows. deflate performs one or both of the + + The detailed semantics are as follows. deflate performs one or both of the following actions: + - Compress more input starting at next_in and update next_in and avail_in - accordingly. If not all input can be processed (because there is not + accordingly. If not all input can be processed (because there is not enough room in the output buffer), next_in and avail_in are updated and processing will resume at this point for the next call of deflate(). + - Provide more output starting at next_out and update next_out and avail_out - accordingly. This action is forced if the parameter flush is non zero. + accordingly. This action is forced if the parameter flush is non zero. Forcing flush frequently degrades the compression ratio, so this parameter - should be set only when necessary (in interactive applications). - Some output may be provided even if flush is not set. - Before the call of deflate(), the application should ensure that at least - one of the actions is possible, by providing more input and/or consuming - more output, and updating avail_in or avail_out accordingly; avail_out - should never be zero before the call. The application can consume the - compressed output when it wants, for example when the output buffer is full - (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK - and with zero avail_out, it must be called again after making room in the - output buffer because there might be more output pending. + should be set only when necessary (in interactive applications). Some + output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming more + output, and updating avail_in or avail_out accordingly; avail_out should + never be zero before the call. The application can consume the compressed + output when it wants, for example when the output buffer is full (avail_out + == 0), or after each call of deflate(). If deflate returns Z_OK and with + zero avail_out, it must be called again after making room in the output + buffer because there might be more output pending. + + Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to + decide how much data to accumulate before producing output, in order to + maximize compression. + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is flushed to the output buffer and the output is aligned on a byte boundary, so - that the decompressor can get all input data available so far. (In particular - avail_in is zero after the call if enough output space has been provided - before the call.) Flushing may degrade compression for some compression - algorithms and so it should be used only when necessary. + that the decompressor can get all input data available so far. (In + particular avail_in is zero after the call if enough output space has been + provided before the call.) Flushing may degrade compression for some + compression algorithms and so it should be used only when necessary. This + completes the current deflate block and follows it with an empty stored block + that is three bits plus filler bits to the next byte, followed by four bytes + (00 00 ff ff). + + If flush is set to Z_PARTIAL_FLUSH, all pending output is flushed to the + output buffer, but the output is not aligned to a byte boundary. All of the + input data so far will be available to the decompressor, as for Z_SYNC_FLUSH. + This completes the current deflate block and follows it with an empty fixed + codes block that is 10 bits long. This assures that enough bytes are output + in order for the decompressor to finish the block before the empty fixed code + block. + + If flush is set to Z_BLOCK, a deflate block is completed and emitted, as + for Z_SYNC_FLUSH, but the output is not aligned on a byte boundary, and up to + seven bits of the current block are held to be written as the next byte after + the next deflate block is completed. In this case, the decompressor may not + be provided enough bits at this point in order to complete decompression of + the data provided so far to the compressor. It may need to wait for the next + block to be emitted. This is for advanced applications that need to control + the emission of deflate blocks. + If flush is set to Z_FULL_FLUSH, all output is flushed as with Z_SYNC_FLUSH, and the compression state is reset so that decompression can restart from this point if previous compressed data has been damaged or if - random access is desired. Using Z_FULL_FLUSH too often can seriously degrade - the compression. + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + compression. + If deflate returns with avail_out == 0, this function must be called again with the same value of the flush parameter and more output space (updated avail_out), until the flush is complete (deflate returns with non-zero - avail_out). + avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that + avail_out is greater than six to avoid repeated flush markers due to + avail_out == 0 on return. + If the parameter flush is set to Z_FINISH, pending input is processed, - pending output is flushed and deflate returns with Z_STREAM_END if there - was enough output space; if deflate returns with Z_OK, this function must be + pending output is flushed and deflate returns with Z_STREAM_END if there was + enough output space; if deflate returns with Z_OK, this function must be called again with Z_FINISH and more output space (updated avail_out) but no - more input data, until it returns with Z_STREAM_END or an error. After - deflate has returned Z_STREAM_END, the only possible operations on the - stream are deflateReset or deflateEnd. - + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the stream + are deflateReset or deflateEnd. + Z_FINISH can be used immediately after deflateInit if all the compression - is to be done in a single step. In this case, avail_out must be at least - 0.1% larger than avail_in plus 12 bytes. If deflate does not return - Z_STREAM_END, then it must be called again as described above. + is to be done in a single step. In this case, avail_out must be at least the + value returned by deflateBound (see below). Then deflate is guaranteed to + return Z_STREAM_END. If not enough output space is provided, deflate will + not return Z_STREAM_END, and it must be called again as described above. + deflate() sets strm->adler to the adler32 checksum of all input read so far (that is, total_in bytes). - deflate() may update data_type if it can make a good guess about - the input data type (Z_ASCII or Z_BINARY). In doubt, the data is considered - binary. This field is only for information purposes and does not affect - the compression algorithm in any manner. + + deflate() may update strm->data_type if it can make a good guess about + the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered + binary. This field is only for information purposes and does not affect the + compression algorithm in any manner. + deflate() returns Z_OK if some progress has been made (more input processed or more output produced), Z_STREAM_END if all input has been consumed and all output has been produced (only when flush is set to Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example - if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible. + if next_in or next_out was Z_NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not + fatal, and deflate() can be called again with more input and more output + space to continue compressing. */ -extern int ZEXPORT deflateEnd OF((z_streamp strm)); + + +ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); /* All dynamically allocated data structures for this stream are freed. - This function discards any unprocessed input and does not flush any - pending output. + This function discards any unprocessed input and does not flush any pending + output. + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state was inconsistent, Z_DATA_ERROR if the stream was freed - prematurely (some input or output was discarded). In the error case, - msg may be set but then points to a static string (which must not be + prematurely (some input or output was discarded). In the error case, msg + may be set but then points to a static string (which must not be deallocated). */ -/* -extern int ZEXPORT inflateInit OF((z_streamp strm)); - Initializes the internal stream state for decompression. The fields + + +/* +ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields next_in, avail_in, zalloc, zfree and opaque must be initialized before by - the caller. If next_in is not Z_NULL and avail_in is large enough (the exact - value depends on the compression method), inflateInit determines the + the caller. If next_in is not Z_NULL and avail_in is large enough (the + exact value depends on the compression method), inflateInit determines the compression method from the zlib header and allocates all data structures accordingly; otherwise the allocation will be deferred to the first call of inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to use default allocation functions. + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough memory, Z_VERSION_ERROR if the zlib library version is incompatible with the - version assumed by the caller. msg is set to null if there is no error - message. inflateInit does not perform any decompression apart from reading - the zlib header if present: this will be done by inflate(). (So next_in and - avail_in may be modified, but next_out and avail_out are unchanged.) + version assumed by the caller, or Z_STREAM_ERROR if the parameters are + invalid, such as a null pointer to the structure. msg is set to null if + there is no error message. inflateInit does not perform any decompression + apart from possibly reading the zlib header if present: actual decompression + will be done by inflate(). (So next_in and avail_in may be modified, but + next_out and avail_out are unused and unchanged.) The current implementation + of inflateInit() does not process any header information -- that is deferred + until inflate() is called. */ -extern int ZEXPORT inflate OF((z_streamp strm, int flush)); + + +ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); /* inflate decompresses as much data as possible, and stops when the input - buffer becomes empty or the output buffer becomes full. It may some - introduce some output latency (reading input without producing any output) - except when forced to flush. - The detailed semantics are as follows. inflate performs one or both of the + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the following actions: + - Decompress more input starting at next_in and update next_in and avail_in - accordingly. If not all input can be processed (because there is not - enough room in the output buffer), next_in is updated and processing - will resume at this point for the next call of inflate(). + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing will + resume at this point for the next call of inflate(). + - Provide more output starting at next_out and update next_out and avail_out - accordingly. inflate() provides as much output as possible, until there - is no more input data or no more space in the output buffer (see below - about the flush parameter). - Before the call of inflate(), the application should ensure that at least - one of the actions is possible, by providing more input and/or consuming - more output, and updating the next_* and avail_* values accordingly. - The application can consume the uncompressed output when it wants, for - example when the output buffer is full (avail_out == 0), or after each - call of inflate(). If inflate returns Z_OK and with zero avail_out, it - must be called again after making room in the output buffer because there - might be more output pending. - If the parameter flush is set to Z_SYNC_FLUSH, inflate flushes as much - output as possible to the output buffer. The flushing behavior of inflate is - not specified for values of the flush parameter other than Z_SYNC_FLUSH - and Z_FINISH, but the current implementation actually flushes as much output - as possible anyway. + accordingly. inflate() provides as much output as possible, until there is + no more input data or no more space in the output buffer (see below about + the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming more + output, and updating the next_* and avail_* values accordingly. The + application can consume the uncompressed output when it wants, for example + when the output buffer is full (avail_out == 0), or after each call of + inflate(). If inflate returns Z_OK and with zero avail_out, it must be + called again after making room in the output buffer because there might be + more output pending. + + The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, Z_FINISH, + Z_BLOCK, or Z_TREES. Z_SYNC_FLUSH requests that inflate() flush as much + output as possible to the output buffer. Z_BLOCK requests that inflate() + stop if and when it gets to the next deflate block boundary. When decoding + the zlib or gzip format, this will cause inflate() to return immediately + after the header and before the first block. When doing a raw inflate, + inflate() will go ahead and process the first block, and will return when it + gets to the end of that block, or when it runs out of data. + + The Z_BLOCK option assists in appending to or combining deflate streams. + Also to assist in this, on return inflate() will set strm->data_type to the + number of unused bits in the last byte taken from strm->next_in, plus 64 if + inflate() is currently decoding the last block in the deflate stream, plus + 128 if inflate() returned immediately after decoding an end-of-block code or + decoding the complete header up to just before the first byte of the deflate + stream. The end-of-block will not be indicated until all of the uncompressed + data from that block has been written to strm->next_out. The number of + unused bits may in general be greater than seven, except when bit 7 of + data_type is set, in which case the number of unused bits will be less than + eight. data_type is set as noted here every time inflate() returns for all + flush options, and so can be used to determine the amount of currently + consumed input in bits. + + The Z_TREES option behaves as Z_BLOCK does, but it also returns when the + end of each deflate block header is reached, before any actual data in that + block is decoded. This allows the caller to determine the length of the + deflate block header for later use in random access within a deflate block. + 256 is added to the value of strm->data_type when inflate() returns + immediately after reaching the end of the deflate block header. + inflate() should normally be called until it returns Z_STREAM_END or an - error. However if all decompression is to be performed in a single step - (a single call of inflate), the parameter flush should be set to - Z_FINISH. In this case all pending input is processed and all pending - output is flushed; avail_out must be large enough to hold all the - uncompressed data. (The size of the uncompressed data may have been saved - by the compressor for this purpose.) The next operation on this stream must - be inflateEnd to deallocate the decompression state. The use of Z_FINISH - is never required, but can be used to inform inflate that a faster routine - may be used for the single inflate() call. - If a preset dictionary is needed at this point (see inflateSetDictionary - below), inflate sets strm-adler to the adler32 checksum of the - dictionary chosen by the compressor and returns Z_NEED_DICT; otherwise - it sets strm->adler to the adler32 checksum of all output produced - so far (that is, total_out bytes) and returns Z_OK, Z_STREAM_END or - an error code as described below. At the end of the stream, inflate() - checks that its computed adler32 checksum is equal to that saved by the - compressor and returns Z_STREAM_END only if the checksum is correct. + error. However if all decompression is to be performed in a single step (a + single call of inflate), the parameter flush should be set to Z_FINISH. In + this case all pending input is processed and all pending output is flushed; + avail_out must be large enough to hold all of the uncompressed data for the + operation to complete. (The size of the uncompressed data may have been + saved by the compressor for this purpose.) The use of Z_FINISH is not + required to perform an inflation in one step. However it may be used to + inform inflate that a faster approach can be used for the single inflate() + call. Z_FINISH also informs inflate to not maintain a sliding window if the + stream completes, which reduces inflate's memory footprint. If the stream + does not complete, either because not all of the stream is provided or not + enough output space is provided, then a sliding window will be allocated and + inflate() can be called again to continue the operation as if Z_NO_FLUSH had + been used. + + In this implementation, inflate() always flushes as much output as + possible to the output buffer, and always uses the faster approach on the + first call. So the effects of the flush parameter in this implementation are + on the return value of inflate() as noted below, when inflate() returns early + when Z_BLOCK or Z_TREES is used, and when inflate() avoids the allocation of + memory for a sliding window when Z_FINISH is used. + + If a preset dictionary is needed after this call (see inflateSetDictionary + below), inflate sets strm->adler to the Adler-32 checksum of the dictionary + chosen by the compressor and returns Z_NEED_DICT; otherwise it sets + strm->adler to the Adler-32 checksum of all output produced so far (that is, + total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described + below. At the end of the stream, inflate() checks that its computed adler32 + checksum is equal to that saved by the compressor and returns Z_STREAM_END + only if the checksum is correct. + + inflate() can decompress and check either zlib-wrapped or gzip-wrapped + deflate data. The header type is detected automatically, if requested when + initializing with inflateInit2(). Any information contained in the gzip + header is not retained, so applications that need that information should + instead use raw inflate, see inflateInit2() below, or inflateBack() and + perform their own processing of the gzip header and trailer. When processing + gzip-wrapped deflate data, strm->adler32 is set to the CRC-32 of the output + producted so far. The CRC-32 is checked against the gzip trailer. + inflate() returns Z_OK if some progress has been made (more input processed or more output produced), Z_STREAM_END if the end of the compressed data has been reached and all uncompressed output has been produced, Z_NEED_DICT if a preset dictionary is needed at this point, Z_DATA_ERROR if the input data was - corrupted (input stream not conforming to the zlib format or incorrect - adler32 checksum), Z_STREAM_ERROR if the stream structure was inconsistent - (for example if next_in or next_out was NULL), Z_MEM_ERROR if there was not - enough memory, Z_BUF_ERROR if no progress is possible or if there was not - enough room in the output buffer when Z_FINISH is used. In the Z_DATA_ERROR - case, the application may then call inflateSync to look for a good - compression block. + corrupted (input stream not conforming to the zlib format or incorrect check + value), Z_STREAM_ERROR if the stream structure was inconsistent (for example + next_in or next_out was Z_NULL), Z_MEM_ERROR if there was not enough memory, + Z_BUF_ERROR if no progress is possible or if there was not enough room in the + output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and + inflate() can be called again with more input and more output space to + continue decompressing. If Z_DATA_ERROR is returned, the application may + then call inflateSync() to look for a good compression block if a partial + recovery of the data is desired. */ -extern int ZEXPORT inflateEnd OF((z_streamp strm)); + + +ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); /* All dynamically allocated data structures for this stream are freed. - This function discards any unprocessed input and does not flush any - pending output. + This function discards any unprocessed input and does not flush any pending + output. + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state - was inconsistent. In the error case, msg may be set but then points to a + was inconsistent. In the error case, msg may be set but then points to a static string (which must not be deallocated). */ + + /* Advanced functions */ + /* The following functions are needed only in some special applications. */ -/* -extern int ZEXPORT deflateInit2 OF((z_streamp strm, - int level, - int method, - int windowBits, - int memLevel, - int strategy)); - This is another version of deflateInit with more compression options. The - fields next_in, zalloc, zfree and opaque must be initialized before by - the caller. - The method parameter is the compression method. It must be Z_DEFLATED in + +/* +ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by the + caller. + + The method parameter is the compression method. It must be Z_DEFLATED in this version of the library. + The windowBits parameter is the base two logarithm of the window size (the size of the history buffer). It should be in the range 8..15 for this - version of the library. Larger values of this parameter result in better - compression at the expense of memory usage. The default value is 15 if + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if deflateInit is used instead. + + windowBits can also be -8..-15 for raw deflate. In this case, -windowBits + determines the window size. deflate() will then generate raw deflate data + with no zlib header or trailer, and will not compute an adler32 check value. + + windowBits can also be greater than 15 for optional gzip encoding. Add + 16 to windowBits to write a simple gzip header and trailer around the + compressed data instead of a zlib wrapper. The gzip header will have no + file name, no extra data, no comment, no modification time (set to zero), no + header crc, and the operating system will be set to 255 (unknown). If a + gzip stream is being written, strm->adler is a crc32 instead of an adler32. + The memLevel parameter specifies how much memory should be allocated - for the internal compression state. memLevel=1 uses minimum memory but - is slow and reduces compression ratio; memLevel=9 uses maximum memory - for optimal speed. The default value is 8. See zconf.h for total memory - usage as a function of windowBits and memLevel. - The strategy parameter is used to tune the compression algorithm. Use the + for the internal compression state. memLevel=1 uses minimum memory but is + slow and reduces compression ratio; memLevel=9 uses maximum memory for + optimal speed. The default value is 8. See zconf.h for total memory usage + as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a - filter (or predictor), or Z_HUFFMAN_ONLY to force Huffman encoding only (no - string match). Filtered data consists mostly of small values with a - somewhat random distribution. In this case, the compression algorithm is - tuned to compress them better. The effect of Z_FILTERED is to force more - Huffman coding and less string matching; it is somewhat intermediate - between Z_DEFAULT and Z_HUFFMAN_ONLY. The strategy parameter only affects - the compression ratio but not the correctness of the compressed output even - if it is not set appropriately. - deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough - memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid - method). msg is set to null if there is no error message. deflateInit2 does - not perform any compression: this will be done by deflate(). + filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match), or Z_RLE to limit match distances to one (run-length + encoding). Filtered data consists mostly of small values with a somewhat + random distribution. In this case, the compression algorithm is tuned to + compress them better. The effect of Z_FILTERED is to force more Huffman + coding and less string matching; it is somewhat intermediate between + Z_DEFAULT_STRATEGY and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as + fast as Z_HUFFMAN_ONLY, but give better compression for PNG image data. The + strategy parameter only affects the compression ratio but not the + correctness of the compressed output even if it is not set appropriately. + Z_FIXED prevents the use of dynamic Huffman codes, allowing for a simpler + decoder for special applications. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if any parameter is invalid (such as an invalid + method), or Z_VERSION_ERROR if the zlib library version (zlib_version) is + incompatible with the version assumed by the caller (ZLIB_VERSION). msg is + set to null if there is no error message. deflateInit2 does not perform any + compression: this will be done by deflate(). */ - -/* extern int ZEXPORT deflateSetDictionary OF((z_streamp strm, */ -/* const Bytef *dictionary, */ -/* uInt dictLength)); */ + +ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); /* Initializes the compression dictionary from the given byte sequence - without producing any compressed output. This function must be called - immediately after deflateInit or deflateInit2, before any call of - deflate. The compressor and decompressor must use exactly the same - dictionary (see inflateSetDictionary). + without producing any compressed output. When using the zlib format, this + function must be called immediately after deflateInit, deflateInit2 or + deflateReset, and before any call of deflate. When doing raw deflate, this + function must be called either before any call of deflate, or immediately + after the completion of a deflate block, i.e. after all input has been + consumed and all output has been delivered when using any of the flush + options Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, or Z_FULL_FLUSH. The + compressor and decompressor must use exactly the same dictionary (see + inflateSetDictionary). + The dictionary should consist of strings (byte sequences) that are likely to be encountered later in the data to be compressed, with the most commonly - used strings preferably put towards the end of the dictionary. Using a + used strings preferably put towards the end of the dictionary. Using a dictionary is most useful when the data to be compressed is short and can be predicted with good accuracy; the data can then be compressed better than with the default empty dictionary. + Depending on the size of the compression data structures selected by deflateInit or deflateInit2, a part of the dictionary may in effect be - discarded, for example if the dictionary is larger than the window size in - deflate or deflate2. Thus the strings most likely to be useful should be - put at the end of the dictionary, not at the front. - Upon return of this function, strm->adler is set to the Adler32 value + discarded, for example if the dictionary is larger than the window size + provided in deflateInit or deflateInit2. Thus the strings most likely to be + useful should be put at the end of the dictionary, not at the front. In + addition, the current implementation of deflate will use at most the window + size minus 262 bytes of the provided dictionary. + + Upon return of this function, strm->adler is set to the adler32 value of the dictionary; the decompressor may later use this value to determine - which dictionary has been used by the compressor. (The Adler32 value + which dictionary has been used by the compressor. (The adler32 value applies to the whole dictionary even if only a subset of the dictionary is - actually used by the compressor.) + actually used by the compressor.) If a raw deflate was requested, then the + adler32 value is not computed and strm->adler is not set. + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a - parameter is invalid (such as NULL dictionary) or the stream state is + parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is inconsistent (for example if deflate has already been called for this stream - or if the compression method is bsort). deflateSetDictionary does not - perform any compression: this will be done by deflate(). + or if not at a block boundary for raw deflate). deflateSetDictionary does + not perform any compression: this will be done by deflate(). */ -extern int ZEXPORT deflateCopy OF((z_streamp dest, - z_streamp source)); + +ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, + z_streamp source)); /* Sets the destination stream as a complete copy of the source stream. + This function can be useful when several compression strategies will be tried, for example when there are several ways of pre-processing the input - data with a filter. The streams that will be discarded should then be freed + data with a filter. The streams that will be discarded should then be freed by calling deflateEnd. Note that deflateCopy duplicates the internal - compression state which can be quite large, so this strategy is slow and - can consume lots of memory. + compression state which can be quite large, so this strategy is slow and can + consume lots of memory. + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not enough memory, Z_STREAM_ERROR if the source stream state was inconsistent - (such as zalloc being NULL). msg is left unchanged in both source and + (such as zalloc being Z_NULL). msg is left unchanged in both source and destination. */ -extern int ZEXPORT deflateReset OF((z_streamp strm)); + +ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); /* This function is equivalent to deflateEnd followed by deflateInit, - but does not free and reallocate all the internal compression state. - The stream will keep the same compression level and any other attributes - that may have been set by deflateInit2. - deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source - stream state was inconsistent (such as zalloc or state being NULL). + but does not free and reallocate all the internal compression state. The + stream will keep the same compression level and any other attributes that + may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being Z_NULL). */ -extern int ZEXPORT deflateParams OF((z_streamp strm, int level, int strategy)); + +ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, + int level, + int strategy)); /* Dynamically update the compression level and compression strategy. The interpretation of level and strategy is as in deflateInit2. This can be used to switch between compression and straight copy of the input data, or - to switch to a different kind of input data requiring a different - strategy. If the compression level is changed, the input available so far - is compressed with the old level (and may be flushed); the new level will - take effect only at the next call of deflate(). + to switch to a different kind of input data requiring a different strategy. + If the compression level is changed, the input available so far is + compressed with the old level (and may be flushed); the new level will take + effect only at the next call of deflate(). + Before the call of deflateParams, the stream state must be set as for - a call of deflate(), since the currently available input may have to - be compressed and flushed. In particular, strm->avail_out must be non-zero. + a call of deflate(), since the currently available input may have to be + compressed and flushed. In particular, strm->avail_out must be non-zero. + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source - stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR - if strm->avail_out was zero. + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR if + strm->avail_out was zero. +*/ + +ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, + int good_length, + int max_lazy, + int nice_length, + int max_chain)); +/* + Fine tune deflate's internal compression parameters. This should only be + used by someone who understands the algorithm used by zlib's deflate for + searching for the best matching string, and even then only by the most + fanatic optimizer trying to squeeze out the last compressed bit for their + specific input data. Read the deflate.c source code for the meaning of the + max_lazy, good_length, nice_length, and max_chain parameters. + + deflateTune() can be called after deflateInit() or deflateInit2(), and + returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + */ + +ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, + uLong sourceLen)); +/* + deflateBound() returns an upper bound on the compressed size after + deflation of sourceLen bytes. It must be called after deflateInit() or + deflateInit2(), and after deflateSetHeader(), if used. This would be used + to allocate an output buffer for deflation in a single pass, and so would be + called before deflate(). If that first deflate() call is provided the + sourceLen input bytes, an output buffer allocated to the size returned by + deflateBound(), and the flush value Z_FINISH, then deflate() is guaranteed + to return Z_STREAM_END. Note that it is possible for the compressed size to + be larger than the value returned by deflateBound() if flush options other + than Z_FINISH or Z_NO_FLUSH are used. +*/ + +ZEXTERN int ZEXPORT deflatePending OF((z_streamp strm, + unsigned *pending, + int *bits)); +/* + deflatePending() returns the number of bytes and bits of output that have + been generated, but not yet provided in the available output. The bytes not + provided would be due to the available output space having being consumed. + The number of bits of output not provided are between 0 and 7, where they + await more bits to join them in order to fill out a full byte. If pending + or bits are Z_NULL, then those values are not set. + + deflatePending returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. + */ + +ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + deflatePrime() inserts bits in the deflate output stream. The intent + is that this function is used to start off the deflate output with the bits + leftover from a previous deflate stream when appending to it. As such, this + function can only be used for raw deflate, and must be used before the first + deflate() call after a deflateInit2() or deflateReset(). bits must be less + than or equal to 16, and that many of the least significant bits of value + will be inserted in the output. + + deflatePrime returns Z_OK if success, Z_BUF_ERROR if there was not enough + room in the internal buffer to insert the bits, or Z_STREAM_ERROR if the + source stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, + gz_headerp head)); +/* + deflateSetHeader() provides gzip header information for when a gzip + stream is requested by deflateInit2(). deflateSetHeader() may be called + after deflateInit2() or deflateReset() and before the first call of + deflate(). The text, time, os, extra field, name, and comment information + in the provided gz_header structure are written to the gzip header (xflag is + ignored -- the extra flags are set according to the compression level). The + caller must assure that, if not Z_NULL, name and comment are terminated with + a zero byte, and that if extra is not Z_NULL, that extra_len bytes are + available there. If hcrc is true, a gzip header crc is included. Note that + the current versions of the command-line version of gzip (up through version + 1.3.x) do not support header crc's, and will report that it is a "multi-part + gzip file" and give up. + + If deflateSetHeader is not used, the default gzip header has text false, + the time set to zero, and os set to 255, with no extra, name, or comment + fields. The gzip header is returned to the default state by deflateReset(). + + deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. */ -/* -extern int ZEXPORT inflateInit2 OF((z_streamp strm, - int windowBits)); - This is another version of inflateInit with an extra parameter. The + +/* +ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The fields next_in, avail_in, zalloc, zfree and opaque must be initialized before by the caller. + The windowBits parameter is the base two logarithm of the maximum window size (the size of the history buffer). It should be in the range 8..15 for - this version of the library. The default value is 15 if inflateInit is used - instead. If a compressed stream with a larger window size is given as - input, inflate() will return with the error code Z_DATA_ERROR instead of - trying to allocate a larger window. - inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough - memory, Z_STREAM_ERROR if a parameter is invalid (such as a negative - memLevel). msg is set to null if there is no error message. inflateInit2 - does not perform any decompression apart from reading the zlib header if - present: this will be done by inflate(). (So next_in and avail_in may be - modified, but next_out and avail_out are unchanged.) -*/ -/* extern int ZEXPORT inflateSetDictionary OF((z_streamp strm, */ -/* const Bytef *dictionary, */ -/* uInt dictLength)); */ + this version of the library. The default value is 15 if inflateInit is used + instead. windowBits must be greater than or equal to the windowBits value + provided to deflateInit2() while compressing, or it must be equal to 15 if + deflateInit2() was not used. If a compressed stream with a larger window + size is given as input, inflate() will return with the error code + Z_DATA_ERROR instead of trying to allocate a larger window. + + windowBits can also be zero to request that inflate use the window size in + the zlib header of the compressed stream. + + windowBits can also be -8..-15 for raw inflate. In this case, -windowBits + determines the window size. inflate() will then process raw deflate data, + not looking for a zlib or gzip header, not generating a check value, and not + looking for any check values for comparison at the end of the stream. This + is for use with other formats that use the deflate compressed data format + such as zip. Those formats provide their own check values. If a custom + format is developed using the raw deflate format for compressed data, it is + recommended that a check value such as an adler32 or a crc32 be applied to + the uncompressed data as is done in the zlib, gzip, and zip formats. For + most applications, the zlib format should be used as is. Note that comments + above on the use in deflateInit2() applies to the magnitude of windowBits. + + windowBits can also be greater than 15 for optional gzip decoding. Add + 32 to windowBits to enable zlib and gzip decoding with automatic header + detection, or add 16 to decode only the gzip format (the zlib format will + return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is a + crc32 instead of an adler32. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller, or Z_STREAM_ERROR if the parameters are + invalid, such as a null pointer to the structure. msg is set to null if + there is no error message. inflateInit2 does not perform any decompression + apart from possibly reading the zlib header if present: actual decompression + will be done by inflate(). (So next_in and avail_in may be modified, but + next_out and avail_out are unused and unchanged.) The current implementation + of inflateInit2() does not process any header information -- that is + deferred until inflate() is called. +*/ + +ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); /* Initializes the decompression dictionary from the given uncompressed byte - sequence. This function must be called immediately after a call of inflate - if this call returned Z_NEED_DICT. The dictionary chosen by the compressor - can be determined from the Adler32 value returned by this call of - inflate. The compressor and decompressor must use exactly the same - dictionary (see deflateSetDictionary). + sequence. This function must be called immediately after a call of inflate, + if that call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the adler32 value returned by that call of inflate. + The compressor and decompressor must use exactly the same dictionary (see + deflateSetDictionary). For raw inflate, this function can be called at any + time to set the dictionary. If the provided dictionary is smaller than the + window and there is already data in the window, then the provided dictionary + will amend what's there. The application must insure that the dictionary + that was used for compression is provided. + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a - parameter is invalid (such as NULL dictionary) or the stream state is + parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the - expected one (incorrect Adler32 value). inflateSetDictionary does not + expected one (incorrect adler32 value). inflateSetDictionary does not perform any decompression: this will be done by subsequent calls of inflate(). */ -extern int ZEXPORT inflateSync OF((z_streamp strm)); -/* - Skips invalid compressed data until a full flush point (see above the - description of deflate with Z_FULL_FLUSH) can be found, or until all - available input is skipped. No output is provided. - inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR - if no more input was provided, Z_DATA_ERROR if no flush point has been found, - or Z_STREAM_ERROR if the stream structure was inconsistent. In the success - case, the application may save the current current value of total_in which - indicates where valid compressed data was found. In the error case, the - application may repeatedly call inflateSync, providing more input each time, - until success or end of the input data. -*/ -extern int ZEXPORT inflateReset OF((z_streamp strm)); + +ZEXTERN int ZEXPORT inflateGetDictionary OF((z_streamp strm, + Bytef *dictionary, + uInt *dictLength)); +/* + Returns the sliding dictionary being maintained by inflate. dictLength is + set to the number of bytes in the dictionary, and that many bytes are copied + to dictionary. dictionary must have enough space, where 32768 bytes is + always enough. If inflateGetDictionary() is called with dictionary equal to + Z_NULL, then only the dictionary length is returned, and nothing is copied. + Similary, if dictLength is Z_NULL, then it is not set. + + inflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the + stream state is inconsistent. +*/ + +ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a possible full flush point (see above + for the description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync searches for a 00 00 FF FF pattern in the compressed data. + All full flush points have this pattern, but not all occurrences of this + pattern are full flush points. + + inflateSync returns Z_OK if a possible full flush point has been found, + Z_BUF_ERROR if no more input was provided, Z_DATA_ERROR if no flush point + has been found, or Z_STREAM_ERROR if the stream structure was inconsistent. + In the success case, the application may save the current current value of + total_in which indicates where valid compressed data was found. In the + error case, the application may repeatedly call inflateSync, providing more + input each time, until success or end of the input data. +*/ + +ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when randomly accessing a large stream. The + first pass through the stream can periodically record the inflate state, + allowing restarting inflate at those points when randomly accessing the + stream. + + inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being Z_NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); /* This function is equivalent to inflateEnd followed by inflateInit, - but does not free and reallocate all the internal decompression state. - The stream will keep attributes that may have been set by inflateInit2. - inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source - stream state was inconsistent (such as zalloc or state being NULL). + but does not free and reallocate all the internal decompression state. The + stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being Z_NULL). */ + +ZEXTERN int ZEXPORT inflateReset2 OF((z_streamp strm, + int windowBits)); +/* + This function is the same as inflateReset, but it also permits changing + the wrap and window size requests. The windowBits parameter is interpreted + the same as it is for inflateInit2. + + inflateReset2 returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being Z_NULL), or if + the windowBits parameter is invalid. +*/ + +ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + This function inserts bits in the inflate input stream. The intent is + that this function is used to start inflating at a bit position in the + middle of a byte. The provided bits will be used before any bytes are used + from next_in. This function should only be used with raw inflate, and + should be used before the first inflate() call after inflateInit2() or + inflateReset(). bits must be less than or equal to 16, and that many of the + least significant bits of value will be inserted in the input. + + If bits is negative, then the input stream bit buffer is emptied. Then + inflatePrime() can be called again to put bits in the buffer. This is used + to clear out bits leftover after feeding inflate a block description prior + to feeding inflate codes. + + inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN long ZEXPORT inflateMark OF((z_streamp strm)); +/* + This function returns two values, one in the lower 16 bits of the return + value, and the other in the remaining upper bits, obtained by shifting the + return value down 16 bits. If the upper value is -1 and the lower value is + zero, then inflate() is currently decoding information outside of a block. + If the upper value is -1 and the lower value is non-zero, then inflate is in + the middle of a stored block, with the lower value equaling the number of + bytes from the input remaining to copy. If the upper value is not -1, then + it is the number of bits back from the current bit position in the input of + the code (literal or length/distance pair) currently being processed. In + that case the lower value is the number of bytes already emitted for that + code. + + A code is being processed if inflate is waiting for more input to complete + decoding of the code, or if it has completed decoding but is waiting for + more output space to write the literal or match data. + + inflateMark() is used to mark locations in the input data for random + access, which may be at bit positions, and to note those cases where the + output of a code may span boundaries of random access blocks. The current + location in the input stream can be determined from avail_in and data_type + as noted in the description for the Z_BLOCK flush parameter for inflate. + + inflateMark returns the value noted above or -1 << 16 if the provided + source stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, + gz_headerp head)); +/* + inflateGetHeader() requests that gzip header information be stored in the + provided gz_header structure. inflateGetHeader() may be called after + inflateInit2() or inflateReset(), and before the first call of inflate(). + As inflate() processes the gzip stream, head->done is zero until the header + is completed, at which time head->done is set to one. If a zlib stream is + being decoded, then head->done is set to -1 to indicate that there will be + no gzip header information forthcoming. Note that Z_BLOCK or Z_TREES can be + used to force inflate() to return immediately after header processing is + complete and before any actual data is decompressed. + + The text, time, xflags, and os fields are filled in with the gzip header + contents. hcrc is set to true if there is a header CRC. (The header CRC + was valid if done is set to one.) If extra is not Z_NULL, then extra_max + contains the maximum number of bytes to write to extra. Once done is true, + extra_len contains the actual extra field length, and extra contains the + extra field, or that field truncated if extra_max is less than extra_len. + If name is not Z_NULL, then up to name_max characters are written there, + terminated with a zero unless the length is greater than name_max. If + comment is not Z_NULL, then up to comm_max characters are written there, + terminated with a zero unless the length is greater than comm_max. When any + of extra, name, or comment are not Z_NULL and the respective field is not + present in the header, then that field is set to Z_NULL to signal its + absence. This allows the use of deflateSetHeader() with the returned + structure to duplicate the header. However if those fields are set to + allocated memory, then the application will need to save those pointers + elsewhere so that they can be eventually freed. + + If inflateGetHeader is not used, then the header information is simply + discarded. The header is always checked for validity, including the header + CRC if present. inflateReset() will reset the process to discard the header + information. The application would need to call inflateGetHeader() again to + retrieve the header from the next gzip stream. + + inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, + unsigned char FAR *window)); + + Initialize the internal stream state for decompression using inflateBack() + calls. The fields zalloc, zfree and opaque in strm must be initialized + before the call. If zalloc and zfree are Z_NULL, then the default library- + derived memory allocation routines are used. windowBits is the base two + logarithm of the window size, in the range 8..15. window is a caller + supplied buffer of that size. Except for special applications where it is + assured that deflate was used with small window sizes, windowBits must be 15 + and a 32K byte window must be supplied to be able to decompress general + deflate streams. + + See inflateBack() for the usage of these routines. + + inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of + the parameters are invalid, Z_MEM_ERROR if the internal state could not be + allocated, or Z_VERSION_ERROR if the version of the library does not match + the version of the header file. +*/ + +typedef unsigned (*in_func) OF((void FAR *, + z_const unsigned char FAR * FAR *)); +typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); + +ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc)); +/* + inflateBack() does a raw inflate with a single call using a call-back + interface for input and output. This is potentially more efficient than + inflate() for file i/o applications, in that it avoids copying between the + output and the sliding window by simply making the window itself the output + buffer. inflate() can be faster on modern CPUs when used with large + buffers. inflateBack() trusts the application to not change the output + buffer passed by the output function, at least until inflateBack() returns. + + inflateBackInit() must be called first to allocate the internal state + and to initialize the state with the user-provided window buffer. + inflateBack() may then be used multiple times to inflate a complete, raw + deflate stream with each call. inflateBackEnd() is then called to free the + allocated state. + + A raw deflate stream is one with no zlib or gzip header or trailer. + This routine would normally be used in a utility that reads zip or gzip + files and writes out uncompressed files. The utility would decode the + header and process the trailer on its own, hence this routine expects only + the raw deflate stream to decompress. This is different from the normal + behavior of inflate(), which expects either a zlib or gzip header and + trailer around the deflate stream. + + inflateBack() uses two subroutines supplied by the caller that are then + called by inflateBack() for input and output. inflateBack() calls those + routines until it reads a complete deflate stream and writes out all of the + uncompressed data, or until it encounters an error. The function's + parameters and return types are defined above in the in_func and out_func + typedefs. inflateBack() will call in(in_desc, &buf) which should return the + number of bytes of provided input, and a pointer to that input in buf. If + there is no input available, in() must return zero--buf is ignored in that + case--and inflateBack() will return a buffer error. inflateBack() will call + out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. out() + should return zero on success, or non-zero on failure. If out() returns + non-zero, inflateBack() will return with an error. Neither in() nor out() + are permitted to change the contents of the window provided to + inflateBackInit(), which is also the buffer that out() uses to write from. + The length written by out() will be at most the window size. Any non-zero + amount of input may be provided by in(). + + For convenience, inflateBack() can be provided input on the first call by + setting strm->next_in and strm->avail_in. If that input is exhausted, then + in() will be called. Therefore strm->next_in must be initialized before + calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called + immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in + must also be initialized, and then if strm->avail_in is not zero, input will + initially be taken from strm->next_in[0 .. strm->avail_in - 1]. + + The in_desc and out_desc parameters of inflateBack() is passed as the + first parameter of in() and out() respectively when they are called. These + descriptors can be optionally used to pass any information that the caller- + supplied in() and out() functions need to do their job. + + On return, inflateBack() will set strm->next_in and strm->avail_in to + pass back any unused input that was provided by the last in() call. The + return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR + if in() or out() returned an error, Z_DATA_ERROR if there was a format error + in the deflate stream (in which case strm->msg is set to indicate the nature + of the error), or Z_STREAM_ERROR if the stream was not properly initialized. + In the case of Z_BUF_ERROR, an input or output error can be distinguished + using strm->next_in which will be Z_NULL only if in() returned an error. If + strm->next_in is not Z_NULL, then the Z_BUF_ERROR was due to out() returning + non-zero. (in() will always be called before out(), so strm->next_in is + assured to be defined if out() returns non-zero.) Note that inflateBack() + cannot return Z_OK. +*/ + +ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); +/* + All memory allocated by inflateBackInit() is freed. + + inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream + state was inconsistent. +*/ + +ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); +/* Return flags indicating compile-time options. + + Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: + 1.0: size of uInt + 3.2: size of uLong + 5.4: size of voidpf (pointer) + 7.6: size of z_off_t + + Compiler, assembler, and debug options: + 8: DEBUG + 9: ASMV or ASMINF -- use ASM code + 10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention + 11: 0 (reserved) + + One-time table building (smaller code, but not thread-safe if true): + 12: BUILDFIXED -- build static block decoding tables when needed + 13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed + 14,15: 0 (reserved) + + Library content (indicates missing functionality): + 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking + deflate code when not needed) + 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect + and decode gzip streams (to avoid linking crc code) + 18-19: 0 (reserved) + + Operation variations (changes in library functionality): + 20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate + 21: FASTEST -- deflate algorithm with only one, lowest compression level + 22,23: 0 (reserved) + + The sprintf variant used by gzprintf (zero is best): + 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format + 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure! + 26: 0 = returns value, 1 = void -- 1 means inferred string length returned + + Remainder: + 27-31: 0 (reserved) + */ + +#ifndef Z_SOLO + /* utility functions */ + /* - The following utility functions are implemented on top of the - basic stream-oriented functions. To simplify the interface, some - default options are assumed (compression level and memory usage, - standard memory allocation functions). The source code of these - utility functions can easily be modified if you need special options. + The following utility functions are implemented on top of the basic + stream-oriented functions. To simplify the interface, some default options + are assumed (compression level and memory usage, standard memory allocation + functions). The source code of these utility functions can be modified if + you need special options. */ -extern int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, - const Bytef *source, uLong sourceLen)); + +ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); /* Compresses the source buffer into the destination buffer. sourceLen is - the byte length of the source buffer. Upon entry, destLen is the total - size of the destination buffer, which must be at least 0.1% larger than - sourceLen plus 12 bytes. Upon exit, destLen is the actual size of the + the byte length of the source buffer. Upon entry, destLen is the total size + of the destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the compressed buffer. - This function can be used to compress a whole file at once if the - input file is mmap'ed. + compress returns Z_OK if success, Z_MEM_ERROR if there was not enough memory, Z_BUF_ERROR if there was not enough room in the output buffer. */ -extern int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, - const Bytef *source, uLong sourceLen, - int level)); + +ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level)); /* - Compresses the source buffer into the destination buffer. The level + Compresses the source buffer into the destination buffer. The level parameter has the same meaning as in deflateInit. sourceLen is the byte - length of the source buffer. Upon entry, destLen is the total size of the - destination buffer, which must be at least 0.1% larger than sourceLen plus - 12 bytes. Upon exit, destLen is the actual size of the compressed buffer. + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough memory, Z_BUF_ERROR if there was not enough room in the output buffer, Z_STREAM_ERROR if the level parameter is invalid. */ -extern int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, - const Bytef *source, uLong sourceLen, int flag)); + +ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); +/* + compressBound() returns an upper bound on the compressed size after + compress() or compress2() on sourceLen bytes. It would be used before a + compress() or compress2() call to allocate the destination buffer. +*/ + +ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); /* Decompresses the source buffer into the destination buffer. sourceLen is - the byte length of the source buffer. Upon entry, destLen is the total - size of the destination buffer, which must be large enough to hold the - entire uncompressed data. (The size of the uncompressed data must have - been saved previously by the compressor and transmitted to the decompressor - by some mechanism outside the scope of this compression library.) - Upon exit, destLen is the actual size of the compressed buffer. - This function can be used to decompress a whole file at once if the - input file is mmap'ed. + the byte length of the source buffer. Upon entry, destLen is the total size + of the destination buffer, which must be large enough to hold the entire + uncompressed data. (The size of the uncompressed data must have been saved + previously by the compressor and transmitted to the decompressor by some + mechanism outside the scope of this compression library.) Upon exit, destLen + is the actual size of the uncompressed buffer. + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough memory, Z_BUF_ERROR if there was not enough room in the output - buffer, or Z_DATA_ERROR if the input data was corrupted. + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. In + the case where there is not enough room, uncompress() will fill the output + buffer with the uncompressed data up to that point. +*/ + + /* gzip file access functions */ + +/* + This library supports reading and writing files in gzip (.gz) format with + an interface similar to that of stdio, using the functions that start with + "gz". The gzip format is different from the zlib format. gzip is a gzip + wrapper, documented in RFC 1952, wrapped around a deflate stream. */ -/* typedef voidp gzFile; */ -/* extern gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); */ + +typedef struct gzFile_s *gzFile; /* semi-opaque gzip file descriptor */ + /* - Opens a gzip (.gz) file for reading or writing. The mode parameter - is as in fopen ("rb" or "wb") but can also include a compression level - ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for - Huffman only compression as in "wb1h". (See the description - of deflateInit2 for more information about the strategy parameter.) +ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); + + Opens a gzip (.gz) file for reading or writing. The mode parameter is as + in fopen ("rb" or "wb") but can also include a compression level ("wb9") or + a strategy: 'f' for filtered data as in "wb6f", 'h' for Huffman-only + compression as in "wb1h", 'R' for run-length encoding as in "wb1R", or 'F' + for fixed code compression as in "wb9F". (See the description of + deflateInit2 for more information about the strategy parameter.) 'T' will + request transparent writing or appending with no compression and not using + the gzip format. + + "a" can be used instead of "w" to request that the gzip stream that will + be written be appended to the file. "+" will result in an error, since + reading and writing to the same gzip file is not supported. The addition of + "x" when writing will create the file exclusively, which fails if the file + already exists. On systems that support it, the addition of "e" when + reading or writing will set the flag to close the file on an execve() call. + + These functions, as well as gzip, will read and decode a sequence of gzip + streams in a file. The append function of gzopen() can be used to create + such a file. (Also see gzflush() for another way to do this.) When + appending, gzopen does not test whether the file begins with a gzip stream, + nor does it look for the end of the gzip streams to begin appending. gzopen + will simply append a gzip stream to the existing file. + gzopen can be used to read a file which is not in gzip format; in this - case gzread will directly read from the file without decompression. - gzopen returns NULL if the file could not be opened or if there was - insufficient memory to allocate the (de)compression state; errno - can be checked to distinguish the two cases (if errno is zero, the - zlib error is Z_MEM_ERROR). */ -/* extern gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); */ -/* - gzdopen() associates a gzFile with the file descriptor fd. File - descriptors are obtained from calls like open, dup, creat, pipe or - fileno (in the file has been previously opened with fopen). - The mode parameter is as in gzopen. - The next call of gzclose on the returned gzFile will also close the - file descriptor fd, just like fclose(fdopen(fd), mode) closes the file - descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). - gzdopen returns NULL if there was insufficient memory to allocate - the (de)compression state. -*/ -/* extern int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); */ -/* - Dynamically update the compression level or strategy. See the description + case gzread will directly read from the file without decompression. When + reading, this will be detected automatically by looking for the magic two- + byte gzip header. + + gzopen returns NULL if the file could not be opened, if there was + insufficient memory to allocate the gzFile state, or if an invalid mode was + specified (an 'r', 'w', or 'a' was not provided, or '+' was provided). + errno can be checked to determine if the reason gzopen failed was that the + file could not be opened. +*/ + +ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +/* + gzdopen associates a gzFile with the file descriptor fd. File descriptors + are obtained from calls like open, dup, creat, pipe or fileno (if the file + has been previously opened with fopen). The mode parameter is as in gzopen. + + The next call of gzclose on the returned gzFile will also close the file + descriptor fd, just like fclose(fdopen(fd, mode)) closes the file descriptor + fd. If you want to keep fd open, use fd = dup(fd_keep); gz = gzdopen(fd, + mode);. The duplicated descriptor should be saved to avoid a leak, since + gzdopen does not close fd if it fails. If you are using fileno() to get the + file descriptor from a FILE *, then you will have to use dup() to avoid + double-close()ing the file descriptor. Both gzclose() and fclose() will + close the associated file descriptor, so they need to have different file + descriptors. + + gzdopen returns NULL if there was insufficient memory to allocate the + gzFile state, if an invalid mode was specified (an 'r', 'w', or 'a' was not + provided, or '+' was provided), or if fd is -1. The file descriptor is not + used until the next gz* read, write, seek, or close operation, so gzdopen + will not detect if fd is invalid (unless fd is -1). +*/ + +ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size)); +/* + Set the internal buffer size used by this library's functions. The + default buffer size is 8192 bytes. This function must be called after + gzopen() or gzdopen(), and before any other calls that read or write the + file. The buffer memory allocation is always deferred to the first read or + write. Two buffers are allocated, either both of the specified size when + writing, or one of the specified size and the other twice that size when + reading. A larger buffer size of, for example, 64K or 128K bytes will + noticeably increase the speed of decompression (reading). + + The new buffer size also affects the maximum length for gzprintf(). + + gzbuffer() returns 0 on success, or -1 on failure, such as being called + too late. +*/ + +ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not opened for writing. */ -/* extern int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); */ + +ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); /* - Reads the given number of uncompressed bytes from the compressed file. - If the input file was not in gzip format, gzread copies the given number - of bytes into the buffer. - gzread returns the number of uncompressed bytes actually read (0 for - end of file, -1 for error). */ -/* extern int ZEXPORT gzwrite OF((gzFile file, const voidp buf, unsigned len)); */ + Reads the given number of uncompressed bytes from the compressed file. If + the input file is not in gzip format, gzread copies the given number of + bytes into the buffer directly from the file. + + After reaching the end of a gzip stream in the input, gzread will continue + to read, looking for another gzip stream. Any number of gzip streams may be + concatenated in the input file, and will all be decompressed by gzread(). + If something other than a gzip stream is encountered after a gzip stream, + that remaining trailing garbage is ignored (and no error is returned). + + gzread can be used to read a gzip file that is being concurrently written. + Upon reaching the end of the input, gzread will return with the available + data. If the error code returned by gzerror is Z_OK or Z_BUF_ERROR, then + gzclearerr can be used to clear the end of file indicator in order to permit + gzread to be tried again. Z_OK indicates that a gzip stream was completed + on the last gzread. Z_BUF_ERROR indicates that the input file ended in the + middle of a gzip stream. Note that gzread does not return -1 in the event + of an incomplete gzip stream. This error is deferred until gzclose(), which + will return Z_BUF_ERROR if the last gzread ended in the middle of a gzip + stream. Alternatively, gzerror can be used before gzclose to detect this + case. + + gzread returns the number of uncompressed bytes actually read, less than + len for end of file, or -1 for error. +*/ + +ZEXTERN int ZEXPORT gzwrite OF((gzFile file, + voidpc buf, unsigned len)); /* Writes the given number of uncompressed bytes into the compressed file. - gzwrite returns the number of uncompressed bytes actually written - (0 in case of error). + gzwrite returns the number of uncompressed bytes written or 0 in case of + error. */ -/* extern int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...)); */ + +ZEXTERN int ZEXPORTVA gzprintf Z_ARG((gzFile file, const char *format, ...)); /* - Converts, formats, and writes the args to the compressed file under - control of the format string, as in fprintf. gzprintf returns the number of - uncompressed bytes actually written (0 in case of error). + Converts, formats, and writes the arguments to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written, or 0 in case of error. The number of + uncompressed bytes written is limited to 8191, or one less than the buffer + size given to gzbuffer(). The caller should assure that this limit is not + exceeded. If it is exceeded, then gzprintf() will return an error (0) with + nothing written. In this case, there may also be a buffer overflow with + unpredictable consequences, which is possible only if zlib was compiled with + the insecure functions sprintf() or vsprintf() because the secure snprintf() + or vsnprintf() functions were not available. This can be determined using + zlibCompileFlags(). */ -/* extern int ZEXPORT gzputs OF((gzFile file, const char *s)); */ + +ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); /* - Writes the given null-terminated string to the compressed file, excluding + Writes the given null-terminated string to the compressed file, excluding the terminating null character. - gzputs returns the number of characters written, or -1 in case of error. + + gzputs returns the number of characters written, or -1 in case of error. +*/ + +ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or a + newline character is read and transferred to buf, or an end-of-file + condition is encountered. If any characters are read or if len == 1, the + string is terminated with a null character. If no characters are read due + to an end-of-file or len < 1, then the buffer is left untouched. + + gzgets returns buf which is a null-terminated string, or it returns NULL + for end-of-file or in case of error. If there was an error, the contents at + buf are indeterminate. */ -/* extern char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); */ + +ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); /* - Reads bytes from the compressed file until len-1 characters are read, or - a newline character is read and transferred to buf, or an end-of-file - condition is encountered. The string is then terminated with a null - character. - gzgets returns buf, or Z_NULL in case of error. + Writes c, converted to an unsigned char, into the compressed file. gzputc + returns the value that was written, or -1 in case of error. */ -/* extern int ZEXPORT gzputc OF((gzFile file, int c)); */ + +ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); /* - Writes c, converted to an unsigned char, into the compressed file. - gzputc returns the value that was written, or -1 in case of error. + Reads one byte from the compressed file. gzgetc returns this byte or -1 + in case of end of file or error. This is implemented as a macro for speed. + As such, it does not do all of the checking the other functions do. I.e. + it does not check to see if file is NULL, nor whether the structure file + points to has been clobbered or not. */ -/* extern int ZEXPORT gzgetc OF((gzFile file)); */ + +ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); /* - Reads one byte from the compressed file. gzgetc returns this byte - or -1 in case of end of file or error. + Push one character back onto the stream to be read as the first character + on the next read. At least one character of push-back is allowed. + gzungetc() returns the character pushed, or -1 on failure. gzungetc() will + fail if c is -1, and may fail if a character has been pushed but not read + yet. If gzungetc is used immediately after gzopen or gzdopen, at least the + output buffer size of pushed characters is allowed. (See gzbuffer above.) + The pushed character will be discarded if the stream is repositioned with + gzseek() or gzrewind(). */ -/* extern int ZEXPORT gzflush OF((gzFile file, int flush)); */ + +ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); /* - Flushes all pending output into the compressed file. The parameter - flush is as in the deflate() function. The return value is the zlib - error number (see function gzerror below). gzflush returns Z_OK if - the flush parameter is Z_FINISH and all output could be flushed. - gzflush should be called only when strictly necessary because it can - degrade compression. + Flushes all pending output into the compressed file. The parameter flush + is as in the deflate() function. The return value is the zlib error number + (see function gzerror below). gzflush is only permitted when writing. + + If the flush parameter is Z_FINISH, the remaining data is written and the + gzip stream is completed in the output. If gzwrite() is called again, a new + gzip stream will be started in the output. gzread() is able to read such + concatented gzip streams. + + gzflush should be called only when strictly necessary because it will + degrade compression if called too often. */ -/* extern z_off_t ZEXPORT gzseek OF((gzFile file, z_off_t offset, int whence)); */ -/* - Sets the starting position for the next gzread or gzwrite on the given - compressed file. The offset represents a number of bytes in the - uncompressed data stream. The whence parameter is defined as in lseek(2); + +/* +ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, + z_off_t offset, int whence)); + + Sets the starting position for the next gzread or gzwrite on the given + compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be - extremely slow. If the file is opened for writing, only forward seeks are + extremely slow. If the file is opened for writing, only forward seeks are supported; gzseek then compresses a sequence of zeroes up to the new starting position. - gzseek returns the resulting offset location as measured in bytes from + + gzseek returns the resulting offset location as measured in bytes from the beginning of the uncompressed stream, or -1 in case of error, in particular if the file is opened for writing and the new starting position would be before the current position. */ -/* extern int ZEXPORT gzrewind OF((gzFile file)); */ + +ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); /* Rewinds the given file. This function is supported only for reading. - gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +/* +ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); + + Returns the starting position for the next gzread or gzwrite on the given + compressed file. This position represents a number of bytes in the + uncompressed data stream, and is zero when starting, even if appending or + reading a gzip stream from the middle of a file using gzdopen(). + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +/* +ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile file)); + + Returns the current offset in the file being read or written. This offset + includes the count of bytes that precede the gzip stream, for example when + appending or when using gzdopen() for reading. When reading, the offset + does not include as yet unused buffered input. This information can be used + for a progress indicator. On error, gzoffset() returns -1. +*/ + +ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +/* + Returns true (1) if the end-of-file indicator has been set while reading, + false (0) otherwise. Note that the end-of-file indicator is set only if the + read tried to go past the end of the input, but came up short. Therefore, + just like feof(), gzeof() may return false even if there is no more data to + read, in the event that the last read request was for the exact number of + bytes remaining in the input file. This will happen if the input file size + is an exact multiple of the buffer size. + + If gzeof() returns true, then the read functions will return no more data, + unless the end-of-file indicator is reset by gzclearerr() and the input file + has grown since the previous end of file was detected. +*/ + +ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); +/* + Returns true (1) if file is being copied directly while reading, or false + (0) if file is a gzip stream being decompressed. + + If the input file is empty, gzdirect() will return true, since the input + does not contain a gzip stream. + + If gzdirect() is used immediately after gzopen() or gzdopen() it will + cause buffers to be allocated to allow reading the file to determine if it + is a gzip file. Therefore if gzbuffer() is used, it should be called before + gzdirect(). + + When writing, gzdirect() returns true (1) if transparent writing was + requested ("wT" for the gzopen() mode), or false (0) otherwise. (Note: + gzdirect() is not needed when writing. Transparent writing must be + explicitly requested, so the application already knows the answer. When + linking statically, using gzdirect() will include all of the zlib code for + gzip file reading and decompression, which may not be desired.) */ -/* extern z_off_t ZEXPORT gztell OF((gzFile file)); */ + +ZEXTERN int ZEXPORT gzclose OF((gzFile file)); /* - Returns the starting position for the next gzread or gzwrite on the - given compressed file. This position represents a number of bytes in the - uncompressed data stream. - gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) + Flushes all pending output if necessary, closes the compressed file and + deallocates the (de)compression state. Note that once file is closed, you + cannot call gzerror with file, since its structures have been deallocated. + gzclose must not be called more than once on the same file, just as free + must not be called more than once on the same allocation. + + gzclose will return Z_STREAM_ERROR if file is not valid, Z_ERRNO on a + file operation error, Z_MEM_ERROR if out of memory, Z_BUF_ERROR if the + last read ended in the middle of a gzip stream, or Z_OK on success. */ -/* extern int ZEXPORT gzeof OF((gzFile file)); */ + +ZEXTERN int ZEXPORT gzclose_r OF((gzFile file)); +ZEXTERN int ZEXPORT gzclose_w OF((gzFile file)); /* - Returns 1 when EOF has previously been detected reading the given - input stream, otherwise zero. + Same as gzclose(), but gzclose_r() is only for use when reading, and + gzclose_w() is only for use when writing or appending. The advantage to + using these instead of gzclose() is that they avoid linking in zlib + compression or decompression code that is not used when only reading or only + writing respectively. If gzclose() is used, then both compression and + decompression code will be included the application when linking to a static + zlib library. */ -/* extern int ZEXPORT gzclose OF((gzFile file)); */ + +ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); /* - Flushes all pending output if necessary, closes the compressed file - and deallocates all the (de)compression state. The return value is the zlib - error number (see function gzerror below). + Returns the error message for the last error which occurred on the given + compressed file. errnum is set to zlib error number. If an error occurred + in the file system and not in the compression library, errnum is set to + Z_ERRNO and the application may consult errno to get the exact error code. + + The application must not modify the returned string. Future calls to + this function may invalidate the previously returned string. If file is + closed, then the string previously returned by gzerror will no longer be + available. + + gzerror() should be used to distinguish errors from end-of-file for those + functions above that do not distinguish those cases in their return values. */ -/* extern const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); */ + +ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); /* - Returns the error message for the last error which occurred on the - given compressed file. errnum is set to zlib error number. If an - error occurred in the file system and not in the compression library, - errnum is set to Z_ERRNO and the application may consult errno - to get the exact error code. + Clears the error and end-of-file flags for file. This is analogous to the + clearerr() function in stdio. This is useful for continuing to read a gzip + file that is being written concurrently. */ + +#endif /* !Z_SOLO */ + /* checksum functions */ + /* These functions are not related to compression but are exported - anyway because they might be useful in applications using the - compression library. + anyway because they might be useful in applications using the compression + library. */ -extern uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); + +ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); /* Update a running Adler-32 checksum with the bytes buf[0..len-1] and - return the updated checksum. If buf is NULL, this function returns - the required initial value for the checksum. - An Adler-32 checksum is almost as reliable as a CRC32 but can be computed - much faster. Usage example: + return the updated checksum. If buf is Z_NULL, this function returns the + required initial value for the checksum. + + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. + + Usage example: + uLong adler = adler32(0L, Z_NULL, 0); + while (read_buffer(buffer, length) != EOF) { adler = adler32(adler, buffer, length); } if (adler != original_adler) error(); */ -/* extern uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); */ + +/* +ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, + z_off_t len2)); + + Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for + each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of + seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. Note + that the z_off_t type (like off_t) is a signed integer. If len2 is + negative, the result has no meaning or utility. +*/ + +ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); /* - Update a running crc with the bytes buf[0..len-1] and return the updated - crc. If buf is NULL, this function returns the required initial value - for the crc. Pre- and post-conditioning (one's complement) is performed - within this function so it shouldn't be done by the application. + Update a running CRC-32 with the bytes buf[0..len-1] and return the + updated CRC-32. If buf is Z_NULL, this function returns the required + initial value for the crc. Pre- and post-conditioning (one's complement) is + performed within this function so it shouldn't be done by the application. + Usage example: + uLong crc = crc32(0L, Z_NULL, 0); + while (read_buffer(buffer, length) != EOF) { crc = crc32(crc, buffer, length); } if (crc != original_crc) error(); */ + +/* +ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); + + Combine two CRC-32 check values into one. For two sequences of bytes, + seq1 and seq2 with lengths len1 and len2, CRC-32 check values were + calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 + check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and + len2. +*/ + + /* various hacks, don't look :) */ + /* deflateInit and inflateInit are macros to allow checking the zlib version * and the compiler's view of z_stream: */ -extern int ZEXPORT deflateInit_ OF((z_streamp strm, int level, - const char *version, int stream_size)); -extern int ZEXPORT inflateInit_ OF((z_streamp strm, - const char *version, int stream_size)); -extern int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, - int windowBits, int memLevel, - int strategy, const char *version, - int stream_size)); -extern int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, +ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, const char *version, int stream_size)); +ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, + int stream_size)); #define deflateInit(strm, level) \ - deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) + deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream)) #define inflateInit(strm) \ - inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) + inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream)) #define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ - (strategy), ZLIB_VERSION, sizeof(z_stream)) + (strategy), ZLIB_VERSION, (int)sizeof(z_stream)) #define inflateInit2(strm, windowBits) \ - inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) -#if !defined(_Z_UTIL_H) && !defined(NO_DUMMY_DECL) -/* struct internal_state {int dummy;}; hack for buggy compilers */ -#endif -extern const char * ZEXPORT zError OF((int err)); -extern int ZEXPORT inflateSyncPoint OF((z_streamp z)); -extern const uLongf * ZEXPORT get_crc_table OF((void)); -#ifdef __cplusplus -} -#endif -#endif /* _ZLIB_H */ -/* zutil.h -- internal interface and configuration of the compression library - * Copyright (C) 1995-1998 Jean-loup Gailly. - * For conditions of distribution and use, see copyright notice in zlib.h + inflateInit2_((strm), (windowBits), ZLIB_VERSION, \ + (int)sizeof(z_stream)) +#define inflateBackInit(strm, windowBits, window) \ + inflateBackInit_((strm), (windowBits), (window), \ + ZLIB_VERSION, (int)sizeof(z_stream)) + +#ifndef Z_SOLO + +/* gzgetc() macro and its supporting function and exposed data structure. Note + * that the real internal state is much larger than the exposed structure. + * This abbreviated structure exposes just enough for the gzgetc() macro. The + * user should not mess with these exposed elements, since their names or + * behavior could change in the future, perhaps even capriciously. They can + * only be used by the gzgetc() macro. You have been warned. */ -/* WARNING: this file should *not* be used by applications. It is - part of the implementation of the compression library and is - subject to change. Applications should only use zlib.h. - */ -#ifndef _Z_UTIL_H -#define _Z_UTIL_H -#ifdef STDC -#if !defined(TO_WINCE) -# include -#endif -# include -# include -#endif -#ifdef NO_ERRNO_H - extern int errno; -#else -#if !defined(TO_WINCE) -# include -#endif -#endif -#ifndef local -#ifdef macintosh -#define local static -#else -# define local /* static */ -#endif -#endif -/* compile with -Dlocal if your debugger can't find static symbols */ -typedef unsigned char uch; -typedef uch FAR uchf; -typedef unsigned short ush; -typedef ush FAR ushf; -typedef unsigned long ulg; -extern const char *z_errmsg[10]; /* indexed by 2-zlib_error */ -/* (size given to avoid silly warnings with Visual C++) */ -#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] -#define ERR_RETURN(strm,err) \ - return (strm->msg = (char*)ERR_MSG(err), (err)) -/* To be used only when the state is known to be valid */ - /* common constants */ -#ifndef DEF_WBITS -# define DEF_WBITS MAX_WBITS -#endif -/* default windowBits for decompression. MAX_WBITS is for compression only */ -#if MAX_MEM_LEVEL >= 8 -# define DEF_MEM_LEVEL 8 +struct gzFile_s { + unsigned have; + unsigned char *next; + z_off64_t pos; +}; +ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */ +#ifdef Z_PREFIX_SET +# undef z_gzgetc +# define z_gzgetc(g) \ + ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : gzgetc(g)) #else -# define DEF_MEM_LEVEL MAX_MEM_LEVEL +# define gzgetc(g) \ + ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : gzgetc(g)) #endif -/* default memLevel */ -#define STORED_BLOCK 0 -#define STATIC_TREES 1 -#define DYN_TREES 2 -/* The three kinds of block type */ -#define MIN_MATCH 3 -#define MAX_MATCH 258 -/* The minimum and maximum match lengths */ -#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ - /* target dependencies */ -#ifdef MSDOS -# define OS_CODE 0x00 -# ifdef __TURBOC__ -# if(__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) - /* Allow compilation with ANSI keywords only enabled */ - void _Cdecl farfree( void *block ); - void *_Cdecl farmalloc( unsigned long nbytes ); -# else -#if !defined(TO_WINCE) -# include + +/* provide 64-bit offset functions if _LARGEFILE64_SOURCE defined, and/or + * change the regular functions to 64 bits if _FILE_OFFSET_BITS is 64 (if + * both are true, the application gets the *64 functions, and the regular + * functions are changed to 64 bits) -- in case these are set on systems + * without large file support, _LFS64_LARGEFILE must also be true + */ +#ifdef Z_LARGE64 + ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); + ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int)); + ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile)); + ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile)); + ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off64_t)); + ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off64_t)); #endif -# endif -# else /* MSC or DJGPP */ -# include + +#if !defined(ZLIB_INTERNAL) && defined(Z_WANT64) +# ifdef Z_PREFIX_SET +# define z_gzopen z_gzopen64 +# define z_gzseek z_gzseek64 +# define z_gztell z_gztell64 +# define z_gzoffset z_gzoffset64 +# define z_adler32_combine z_adler32_combine64 +# define z_crc32_combine z_crc32_combine64 +# else +# define gzopen gzopen64 +# define gzseek gzseek64 +# define gztell gztell64 +# define gzoffset gzoffset64 +# define adler32_combine adler32_combine64 +# define crc32_combine crc32_combine64 # endif +# ifndef Z_LARGE64 + ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); + ZEXTERN z_off_t ZEXPORT gzseek64 OF((gzFile, z_off_t, int)); + ZEXTERN z_off_t ZEXPORT gztell64 OF((gzFile)); + ZEXTERN z_off_t ZEXPORT gzoffset64 OF((gzFile)); + ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); +# endif +#else + ZEXTERN gzFile ZEXPORT gzopen OF((const char *, const char *)); + ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile, z_off_t, int)); + ZEXTERN z_off_t ZEXPORT gztell OF((gzFile)); + ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile)); + ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); #endif -#ifdef OS2 -# define OS_CODE 0x06 -#endif -#ifdef WIN32 /* Window 95 & Windows NT */ -# define OS_CODE 0x0b -#endif -/* #if defined(VAXC) || defined(VMS) */ -/* # define OS_CODE 0x02 */ -/* # define F_OPEN(name, mode) \ */ -/* fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512") */ -/* #endif */ -#ifdef AMIGA -# define OS_CODE 0x01 -#endif -#if defined(ATARI) || defined(atarist) -# define OS_CODE 0x05 -#endif -/* #if defined(MACOS) || defined(TARGET_OS_MAC) */ -/* # define OS_CODE 0x07 */ -/* # ifndef fdopen */ -/* # define fdopen(fd,mode) NULL // No fdopen() */ -/* # endif */ -/* #endif */ -/* #if defined(__MWERKS__) && !defined(fdopen) */ -/* # if __dest_os != __be_os && __dest_os != __win32_os */ -/* # define fdopen(fd,mode) NULL */ -/* # endif */ -/* #endif */ -#ifdef __50SERIES /* Prime/PRIMOS */ -# define OS_CODE 0x0F -#endif -#ifdef TOPS20 -# define OS_CODE 0x0a -#endif -/* #if defined(_BEOS_) || defined(RISCOS) */ -/* # define fdopen(fd,mode) NULL // No fdopen() */ -/* #endif */ -/* #if (defined(_MSC_VER) && (_MSC_VER >= 600)) */ -/* # define fdopen(fd,type) _fdopen(fd,type) */ -/* #endif */ - /* Common defaults */ -#ifndef OS_CODE -# define OS_CODE 0x03 /* assume Unix */ + +#else /* Z_SOLO */ + + ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); + +#endif /* !Z_SOLO */ + +/* hack for buggy compilers */ +#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL) + struct internal_state {int dummy;}; #endif -/* #ifndef F_OPEN */ -/* # define F_OPEN(name, mode) fopen((name), (mode)) */ -/* #endif */ - /* functions */ -/* #ifdef HAVE_STRERROR */ -/* extern char *strerror OF((int)); */ -/* # define zstrerror(errnum) strerror(errnum) */ -/* #else */ -/* # define zstrerror(errnum) "" */ -/* #endif */ -#if defined(pyr) -# define NO_MEMCPY + +/* undocumented functions */ +ZEXTERN const char * ZEXPORT zError OF((int)); +ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp)); +ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table OF((void)); +ZEXTERN int ZEXPORT inflateUndermine OF((z_streamp, int)); +ZEXTERN int ZEXPORT inflateResetKeep OF((z_streamp)); +ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp)); +#if defined(_WIN32) && !defined(Z_SOLO) +ZEXTERN gzFile ZEXPORT gzopen_w OF((const wchar_t *path, + const char *mode)); #endif -#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__) - /* Use our own functions for small and medium model with MSC <= 5.0. - * You may have to use the same strategy for Borland C (untested). - * The __SC__ check is for Symantec. - */ -# define NO_MEMCPY -#endif -#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY) -# define HAVE_MEMCPY -#endif -#ifdef HAVE_MEMCPY -# ifdef SMALL_MEDIUM /* MSDOS small or medium model */ -# define zmemcpy _fmemcpy -# define zmemcmp _fmemcmp -# define zmemzero(dest, len) _fmemset(dest, 0, len) -# else -# define zmemcpy memcpy -# define zmemcmp memcmp -# define zmemzero(dest, len) memset(dest, 0, len) +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +ZEXTERN int ZEXPORTVA gzvprintf Z_ARG((gzFile file, + const char *format, + va_list va)); # endif -#else - extern void zmemcpy OF((Bytef* dest, Bytef* source, uInt len)); - extern int zmemcmp OF((Bytef* s1, Bytef* s2, uInt len)); - extern void zmemzero OF((Bytef* dest, uInt len)); #endif -/* Diagnostic functions */ -/* #ifdef DEBUG */ -/* # include */ -/* extern int z_verbose; */ -/* extern void z_error OF((char *m)); */ -/* # define Assert(cond,msg) {if(!(cond)) z_error(msg);} */ -/* # define Trace(x) {if (z_verbose>=0) fprintf x ;} */ -/* # define Tracev(x) {if (z_verbose>0) fprintf x ;} */ -/* # define Tracevv(x) {if (z_verbose>1) fprintf x ;} */ -/* # define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;} */ -/* # define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;} */ -/* #else */ -/* # define Assert(cond,msg) */ -/* # define Trace(x) */ -/* # define Tracev(x) */ -/* # define Tracevv(x) */ -/* # define Tracec(c,x) */ -/* # define Tracecv(c,x) */ -/* #endif */ -voidpf zcalloc OF((voidpf opaque, unsigned items, unsigned size)); -void zcfree OF((voidpf opaque, voidpf ptr)); -#define ZALLOC(strm, items, size) \ - (*((strm)->zalloc))((strm)->opaque, (items), (size)) -#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr)) -#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} -#if 0 -INLINE voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) { - return (voidpf)calloc(items, size); -} -INLINE void zcfree (voidpf opaque, voidpf ptr) { - free(ptr); + +#ifdef __cplusplus } #endif -#endif /* _Z_UTIL_H */ -#ifndef INFTREES_H -#define INFTREES_H -/* inftrees.h -- header to use inftrees.c - * Copyright (C) 1995-1998 Mark Adler - * For conditions of distribution and use, see copyright notice in zlib.h - */ -/* WARNING: this file should *not* be used by applications. It is - part of the implementation of the compression library and is - subject to change. Applications should only use zlib.h. - */ -/* Huffman code lookup table entry--this entry is four bytes for machines - that have 16-bit pointers (e.g. PC's in the small or medium model). */ -typedef struct inflate_huft_s FAR inflate_huft; -struct inflate_huft_s { - union { - struct { - Byte Exop; /* number of extra bits or operation */ - Byte Bits; /* number of bits in this code or subcode */ - } what; - uInt pad; /* pad structure to a power of 2 (4 bytes for */ - } word; /* 16-bit, 8 bytes for 32-bit int's) */ - uInt base; /* literal, length base, distance base, - or table offset */ -}; -/* Maximum size of dynamic tree. The maximum found in a long but non- - exhaustive search was 1004 huft structures (850 for length/literals - and 154 for distances, the latter actually the result of an - exhaustive search). The actual maximum is not known, but the - value below is more than safe. */ -#define MANY 1440 -extern int inflate_trees_bits OF(( - uIntf *, /* 19 code lengths */ - uIntf *, /* bits tree desired/actual depth */ - inflate_huft * FAR *, /* bits tree result */ - inflate_huft *, /* space for trees */ - z_streamp)); /* for messages */ -extern int inflate_trees_dynamic OF(( - uInt, /* number of literal/length codes */ - uInt, /* number of distance codes */ - uIntf *, /* that many (total) code lengths */ - uIntf *, /* literal desired/actual bit depth */ - uIntf *, /* distance desired/actual bit depth */ - inflate_huft * FAR *, /* literal/length tree result */ - inflate_huft * FAR *, /* distance tree result */ - inflate_huft *, /* space for trees */ - z_streamp)); /* for messages */ -extern int inflate_trees_fixed OF(( - uIntf *, /* literal desired/actual bit depth */ - uIntf *, /* distance desired/actual bit depth */ - inflate_huft * FAR *, /* literal/length tree result */ - inflate_huft * FAR *, /* distance tree result */ - z_streamp)); /* for memory allocation */ -#endif -#ifndef INFBLOCK_H -#define INFBLOCK_H -/* infblock.h -- header to use infblock.c - * Copyright (C) 1995-1998 Mark Adler - * For conditions of distribution and use, see copyright notice in zlib.h - */ -/* WARNING: this file should *not* be used by applications. It is - part of the implementation of the compression library and is - subject to change. Applications should only use zlib.h. - */ -struct inflate_blocks_state; -typedef struct inflate_blocks_state FAR inflate_blocks_statef; -extern inflate_blocks_statef * inflate_blocks_new OF(( - z_streamp z, - check_func c, /* check function */ - uInt w)); /* window size */ -extern int inflate_blocks OF(( - inflate_blocks_statef *, - z_streamp , - int)); /* initial return code */ -extern void inflate_blocks_reset OF(( - inflate_blocks_statef *, - z_streamp , - uLongf *)); /* check value on output */ -extern int inflate_blocks_free OF(( - inflate_blocks_statef *, - z_streamp)); -/* extern void inflate_set_dictionary OF(( */ -/* inflate_blocks_statef *s, */ -/* const Bytef *d, dictionary */ -/* uInt n)); dictionary length */ -extern int inflate_blocks_sync_point OF(( - inflate_blocks_statef *s)); -#endif -#ifndef INFCODES_H -#define INFCODES_H -/* infcodes.h -- header to use infcodes.c - * Copyright (C) 1995-1998 Mark Adler - * For conditions of distribution and use, see copyright notice in zlib.h - */ -/* WARNING: this file should *not* be used by applications. It is - part of the implementation of the compression library and is - subject to change. Applications should only use zlib.h. - */ -struct inflate_codes_state; -typedef struct inflate_codes_state FAR inflate_codes_statef; -extern inflate_codes_statef *inflate_codes_new OF(( - uInt, uInt, - inflate_huft *, inflate_huft *, - z_streamp )); -extern int inflate_codes OF(( - inflate_blocks_statef *, - z_streamp , - int)); -extern void inflate_codes_free OF(( - inflate_codes_statef *, - z_streamp )); -#endif -/* infutil.h -- types and macros common to blocks and codes - * Copyright (C) 1995-1998 Mark Adler - * For conditions of distribution and use, see copyright notice in zlib.h - */ -/* WARNING: this file should *not* be used by applications. It is - part of the implementation of the compression library and is - subject to change. Applications should only use zlib.h. - */ -#ifndef _INFUTIL_H -#define _INFUTIL_H -typedef enum { - TYPE, /* get type bits (3, including end bit) */ - LENS, /* get lengths for stored */ - STORED, /* processing stored block */ - TABLE, /* get table lengths */ - BTREE, /* get bit lengths tree for a dynamic block */ - DTREE, /* get length, distance trees for a dynamic block */ - CODES, /* processing fixed or dynamic block */ - DRY, /* output remaining window bytes */ - DONE, /* finished last block, done */ - BAD} /* got a data error--stuck here */ -inflate_block_mode; -/* inflate blocks semi-private state */ -struct inflate_blocks_state { - /* mode */ - inflate_block_mode mode; /* current inflate_block mode */ - /* mode dependent information */ - union { - uInt left; /* if STORED, bytes left to copy */ - struct { - uInt table; /* table lengths (14 bits) */ - uInt index; /* index into blens (or border) */ - uIntf *blens; /* bit lengths of codes */ - uInt bb; /* bit length tree depth */ - inflate_huft *tb; /* bit length decoding tree */ - } trees; /* if DTREE, decoding info for trees */ - struct { - inflate_codes_statef - *codes; - } decode; /* if CODES, current state */ - } sub; /* submode */ - uInt last; /* true if this block is the last block */ - /* mode independent information */ - uInt bitk; /* bits in bit buffer */ - uLong bitb; /* bit buffer */ - inflate_huft *hufts; /* single malloc for tree space */ - Bytef *window; /* sliding window */ - Bytef *end; /* one byte after sliding window */ - Bytef *read; /* window read pointer */ - Bytef *write; /* window write pointer */ - check_func checkfn; /* check function */ - uLong check; /* check on output */ -}; -/* defines for inflate input/output */ -/* update pointers and return */ -#define UPDBITS {s->bitb=b;s->bitk=k;} -#define UPDIN {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} -#define UPDOUT {s->write=q;} -#define UPDATE {UPDBITS UPDIN UPDOUT} -#define LEAVE {UPDATE return inflate_flush(s,z,r);} -/* get bytes and bits */ -#define LOADIN {p=z->next_in;n=z->avail_in;b=s->bitb;k=s->bitk;} -#define NEEDBYTE {if(n)r=Z_OK;else LEAVE} -#define NEXTBYTE (n--,*p++) -#define NEEDBITS(j) {while(k<(j)){NEEDBYTE;b|=((uLong)NEXTBYTE)<>=(j);k-=(j);} -/* output bytes */ -#define WAVAIL (uInt)(qread?s->read-q-1:s->end-q) -#define LOADOUT {q=s->write;m=(uInt)WAVAIL;} -#define WRAP {if(q==s->end&&s->read!=s->window){q=s->window;m=(uInt)WAVAIL;}} -#define FLUSH {UPDOUT r=inflate_flush(s,z,r); LOADOUT} -#define NEEDOUT {if(m==0){WRAP if(m==0){FLUSH WRAP if(m==0) LEAVE}}r=Z_OK;} -#define OUTBYTE(a) {*q++=(Byte)(a);m--;} -/* load local pointers */ -#define LOAD {LOADIN LOADOUT} -/* masks for lower bits (size given to avoid silly warnings with Visual C++) */ -extern uInt inflate_mask[17]; -/* copy as much as possible from the sliding window to the output area */ -extern int inflate_flush OF(( - inflate_blocks_statef *, - z_streamp , - int)); -//rls struct internal_state {int dummy;}; /* for buggy compilers */ -#endif + +#endif /* ZLIB_H */ /* deflate.h -- internal compression state - * Copyright (C) 1995-1998 Jean-loup Gailly - * For conditions of distribution and use, see copyright notice in zlib.h + * Copyright (C) 1995-2012 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h */ + /* WARNING: this file should *not* be used by applications. It is part of the implementation of the compression library and is subject to change. Applications should only use zlib.h. */ -#ifndef _DEFLATE_H -#define _DEFLATE_H -//rls#include "zutil.h" + +/* @(#) $Id$ */ + +#ifndef DEFLATE_H +#define DEFLATE_H + +// #include "zutil.h" /* In sys-zlib.h (see make-zlib.r) */ + +/* define NO_GZIP when compiling if you want to disable gzip header and + trailer creation by deflate(). NO_GZIP would be used to avoid linking in + the crc code when it is not needed. For shared libraries, gzip encoding + should be left enabled. */ +#ifndef NO_GZIP +# define GZIP +#endif + /* =========================================================================== * Internal compression state. */ + #define LENGTH_CODES 29 /* number of length codes, not counting the special END_BLOCK code */ + #define LITERALS 256 /* number of literal bytes 0..255 */ + #define L_CODES (LITERALS+1+LENGTH_CODES) /* number of Literal or Length codes, including the END_BLOCK code */ + #define D_CODES 30 /* number of distance codes */ + #define BL_CODES 19 /* number of codes used to transfer the bit lengths */ + #define HEAP_SIZE (2*L_CODES+1) /* maximum heap size */ + #define MAX_BITS 15 /* All codes must not exceed MAX_BITS bits */ + +#define Buf_size 16 +/* size of bit buffer in bi_buf */ + #define INIT_STATE 42 +#define EXTRA_STATE 69 +#define NAME_STATE 73 +#define COMMENT_STATE 91 +#define HCRC_STATE 103 #define BUSY_STATE 113 #define FINISH_STATE 666 /* Stream status */ + + /* Data structure describing a single value and its code string. */ typedef struct ct_data_s { union { @@ -1465,37 +2651,47 @@ typedef struct ct_data_s { ush len; /* length of bit string */ } dl; } FAR ct_data; + #define Freq fc.freq #define Code fc.code #define Dad dl.dad #define Len dl.len + typedef struct static_tree_desc_s static_tree_desc; + typedef struct tree_desc_s { ct_data *dyn_tree; /* the dynamic tree */ int max_code; /* largest code with non zero frequency */ static_tree_desc *stat_desc; /* the corresponding static tree */ } FAR tree_desc; + typedef ush Pos; typedef Pos FAR Posf; typedef unsigned IPos; + /* A Pos is an index in the character window. We use short instead of int to * save space in the various tables. IPos is used only for parameter passing. */ + typedef struct internal_state { z_streamp strm; /* pointer back to this zlib stream */ int status; /* as the name implies */ Bytef *pending_buf; /* output still pending */ ulg pending_buf_size; /* size of pending_buf */ Bytef *pending_out; /* next pending byte to output to the stream */ - int pending; /* nb of bytes in the pending buffer */ - int noheader; /* suppress zlib header and adler32 */ - Byte data_type; /* UNKNOWN, BINARY or ASCII */ - Byte method; /* STORED (for zip only) or DEFLATED */ + uInt pending; /* nb of bytes in the pending buffer */ + int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ + gz_headerp gzhead; /* gzip header information to write */ + uInt gzindex; /* where in extra, name, or comment */ + Byte method; /* can only be DEFLATED */ int last_flush; /* value of flush param for previous deflate call */ + /* used by deflate.c: */ + uInt w_size; /* LZ77 window size (32K by default) */ uInt w_bits; /* log2(w_size) (8..16) */ uInt w_mask; /* w_size - 1 */ + Bytef *window; /* Sliding window. Input bytes are read into the second half of the window, * and move to the first half later to keep a dictionary of at least wSize @@ -1505,45 +2701,55 @@ typedef struct internal_state { * the window size to 64K, which is quite useful on MSDOS. * To do: use the user input buffer as sliding window. */ + ulg window_size; /* Actual size of window: 2*wSize, except when the user input buffer * is directly used as sliding window. */ + Posf *prev; /* Link to older string with same hash index. To limit the size of this * array to 64K, this link is maintained only for the last 32K strings. * An index in this array is thus a window index modulo 32K. */ + Posf *head; /* Heads of the hash chains or NIL. */ + uInt ins_h; /* hash index of string to be inserted */ uInt hash_size; /* number of elements in hash table */ uInt hash_bits; /* log2(hash_size) */ uInt hash_mask; /* hash_size-1 */ + uInt hash_shift; /* Number of bits by which ins_h must be shifted at each input * step. It must be such that after MIN_MATCH steps, the oldest * byte no longer takes part in the hash key, that is: * hash_shift * MIN_MATCH >= hash_bits */ + long block_start; /* Window position at the beginning of the current output block. Gets * negative when the window is moved backwards. */ + uInt match_length; /* length of best match */ IPos prev_match; /* previous match */ int match_available; /* set if previous match exists */ uInt strstart; /* start of string to insert */ uInt match_start; /* start of matching string */ uInt lookahead; /* number of valid bytes ahead in window */ + uInt prev_length; /* Length of the best match at previous step. Matches not greater than this * are discarded. This is used in the lazy match evaluation. */ + uInt max_chain_length; /* To speed up deflation, hash chains are never searched beyond this * length. A higher limit improves compression ratio but degrades the * speed. */ + uInt max_lazy_match; /* Attempt to find a better match only when the current match is strictly * smaller than this value. This mechanism is used only for compression @@ -1554,31 +2760,41 @@ typedef struct internal_state { * greater than this length. This saves time but degrades compression. * max_insert_length is used only for compression levels <= 3. */ + int level; /* compression level (1..9) */ int strategy; /* favor or force Huffman coding*/ + uInt good_match; /* Use a faster search when the previous match is longer than this */ + int nice_match; /* Stop searching when current match exceeds this */ + /* used by trees.c: */ - /* Didn't use ct_data typedef below to supress compiler warning */ + /* Didn't use ct_data typedef below to suppress compiler warning */ struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ + struct tree_desc_s l_desc; /* desc. for literal tree */ struct tree_desc_s d_desc; /* desc. for distance tree */ struct tree_desc_s bl_desc; /* desc. for bit length tree */ + ush bl_count[MAX_BITS+1]; /* number of codes at each bit length for an optimal tree */ + int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ int heap_len; /* number of elements in the heap */ int heap_max; /* element of largest frequency */ /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. * The same heap array is used to build all trees. */ + uch depth[2*L_CODES+1]; /* Depth of each subtree used as tie breaker for trees of equal frequency */ + uchf *l_buf; /* buffer for literals or lengths */ + uInt lit_bufsize; /* Size of match buffer for literals/lengths. There are 4 reasons for * limiting lit_bufsize to 64K: @@ -1598,20 +2814,25 @@ typedef struct internal_state { * trees more frequently. * - I can't count above 4 */ + uInt last_lit; /* running index in l_buf */ + ushf *d_buf; /* Buffer for distances. To simplify the code, d_buf and l_buf have * the same number of elements. To use different lengths, an extra flag * array would be necessary. */ + ulg opt_len; /* bit length of current block with optimal trees */ ulg static_len; /* bit length of current block with static trees */ - ulg compressed_len; /* total bit length of compressed file */ uInt matches; /* number of string matches in current block */ - int last_eob_len; /* bit length of EOB code for last block */ + uInt insert; /* bytes at end of window left to insert */ + #ifdef DEBUG - ulg bits_sent; /* bit length of the compressed data */ + ulg compressed_len; /* total bit length of compressed file mod 2^32 */ + ulg bits_sent; /* bit length of compressed data sent mod 2^32 */ #endif + ush bi_buf; /* Output buffer. bits are inserted starting at the bottom (least * significant bits). @@ -1620,42 +2841,64 @@ typedef struct internal_state { /* Number of valid bits in bi_buf. All bits above the last valid bit * are always zero. */ + + ulg high_water; + /* High water mark offset in window for initialized bytes -- bytes above + * this are set to zero in order to avoid memory check warnings when + * longest match routines access bytes past the input. This is then + * updated to the new high water mark. + */ + } FAR deflate_state; + /* Output a byte on the stream. * IN assertion: there is enough room in pending_buf. */ #define put_byte(s, c) {s->pending_buf[s->pending++] = (c);} + + #define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) /* Minimum amount of lookahead, except at the end of the input file. * See deflate.c for comments about the MIN_MATCH+1. */ + #define MAX_DIST(s) ((s)->w_size-MIN_LOOKAHEAD) /* In order to simplify the code, particularly on 16 bit machines, match * distances are limited to MAX_DIST instead of WSIZE. */ + +#define WIN_INIT MAX_MATCH +/* Number of bytes after end of data in window to initialize in order to avoid + memory checker errors from longest match routines */ + /* in trees.c */ -void _tr_init OF((deflate_state *s)); -int _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc)); -ulg _tr_flush_block OF((deflate_state *s, charf *buf, ulg stored_len, - int eof)); -void _tr_align OF((deflate_state *s)); -void _tr_stored_block OF((deflate_state *s, charf *buf, ulg stored_len, - int eof)); +void ZLIB_INTERNAL _tr_init OF((deflate_state *s)); +int ZLIB_INTERNAL _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc)); +void ZLIB_INTERNAL _tr_flush_block OF((deflate_state *s, charf *buf, + ulg stored_len, int last)); +void ZLIB_INTERNAL _tr_flush_bits OF((deflate_state *s)); +void ZLIB_INTERNAL _tr_align OF((deflate_state *s)); +void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf, + ulg stored_len, int last)); + #define d_code(dist) \ ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)]) /* Mapping from a distance to a distance code. dist is the distance - 1 and * must not have side effects. _dist_code[256] and _dist_code[257] are never * used. */ + #ifndef DEBUG /* Inline versions of _tr_tally for speed: */ + #if defined(GEN_TREES_H) || !defined(STDC) - extern uch _length_code[]; - extern uch _dist_code[]; + extern uch ZLIB_INTERNAL _length_code[]; + extern uch ZLIB_INTERNAL _dist_code[]; #else - extern const uch _length_code[]; - extern const uch _dist_code[]; + extern const uch ZLIB_INTERNAL _length_code[]; + extern const uch ZLIB_INTERNAL _dist_code[]; #endif + # define _tr_tally_lit(s, c, flush) \ { uch cc = (c); \ s->d_buf[s->last_lit] = 0; \ @@ -1676,6 +2919,7 @@ void _tr_stored_block OF((deflate_state *s, charf *buf, ulg stored_len, #else # define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c) # define _tr_tally_dist(s, distance, length, flush) \ - flush = _tr_tally(s, distance, length) -#endif + flush = _tr_tally(s, distance, length) #endif + +#endif /* DEFLATE_H */ diff --git a/src/mezz/base-constants.r b/src/mezz/base-constants.r index 03b7c62d23..dd411d5bec 100644 --- a/src/mezz/base-constants.r +++ b/src/mezz/base-constants.r @@ -1,19 +1,19 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Boot Base: Constants and Equates" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Note: { - This code is evaluated just after actions, natives, sysobj, and other lower - levels definitions. This file intializes a minimal working environment - that is used for the rest of the boot. - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Boot Base: Constants and Equates" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Note: { + This code is evaluated just after actions, natives, sysobj, and other lower + levels definitions. This file intializes a minimal working environment + that is used for the rest of the boot. + } ] ; NOTE: The system is not fully booted at this point, so only simple @@ -48,13 +48,12 @@ lf: newline crlf: "^M^J" ;-- Function synonyms: -q: :quit -!: :not +not: :not? +!: :not? min: :minimum max: :maximum abs: :absolute empty?: :tail? ---: :comment -bind?: :bound? rebol.com: http://www.rebol.com diff --git a/src/mezz/base-debug.r b/src/mezz/base-debug.r deleted file mode 100644 index b33a5d2600..0000000000 --- a/src/mezz/base-debug.r +++ /dev/null @@ -1,70 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Boot Base: Debug Functions" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Note: { - This code is evaluated just after actions, natives, sysobj, and other lower - levels definitions. This file intializes a minimal working environment - that is used for the rest of the boot. - } -] - -probe: func [ - {Debug print a molded value and returns that same value.} - value [any-type!] -][ - print mold :value - :value -] - -??: func [ - {Debug print a word, path, or block of such, followed by its molded value.} - 'name "Word, path, and block to obtain values." - /local out -][ - case [ - any [ - word? :name - path? :name - ][ - print ajoin [name ": " mold name: get :name] - ] - block? :name [ - out: make string! 50 - foreach word name [ - either any [ - word? :word - path? :word - ][ - repend out [word ": " mold get word " "] - ][ - repend out [mold word " "] - ] - ] - print out - ] - true [probe :name] - ] - :name -] - -boot-print: func [ - "Prints during boot when not quiet." - data -][ - unless system/options/quiet [print :data] -] - -loud-print: func [ - "Prints during boot when verbose." - data -][ - if system/options/flags/verbose [print :data] -] diff --git a/src/mezz/base-defs.r b/src/mezz/base-defs.r index d302798994..29319e448b 100644 --- a/src/mezz/base-defs.r +++ b/src/mezz/base-defs.r @@ -1,148 +1,256 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Boot Base: Other Definitions" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Note: { - This code is evaluated just after actions, natives, sysobj, and other lower - levels definitions. This file intializes a minimal working environment - that is used for the rest of the boot. - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Boot Base: Other Definitions" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Description: { + This code is evaluated just after actions, natives, sysobj, and + other lower level definitions. This file intializes a minimal working + environment that is used for the rest of the boot. + } + Note: { + Any exported SET-WORD!s must be themselves "top level". This hampers + procedural code here that would like to use tables to avoid repeating + itself. This means variadic approaches have to be used that quote + SET-WORD!s living at the top level, inline after the function call. + } ] -;-- Create the reflector functions (e.g. spec-of, body-of, ...) - -; Must be defined in A108 (no forward refs) -spec-of: -body-of: -words-of: -values-of: -types-of: -title-of: - none - -use [word title] [ - foreach name system/catalog/reflectors [ - word: make word! ajoin [name "-of"] - word: bind/new word 'reflect - title: ajoin ["Returns a copy of the " name " of a " switch/default name [ - spec ["function or module"] - values ["object or module"] - types title ["function"] ; title should include module Title too... - ] ["function, object, or module"]] ; body, words - set word func - reduce [title 'value] - compose [reflect :value (to lit-word! name)] - ] + +; Words for BLANK! and BAR!, for those who don't like symbols + +blank: _ +bar: '| + +; Despite being very "noun-like", HEAD and TAIL have classically been "verbs" +; in Rebol. Ren-C defines the core operations as HEAD-OF and TAIL-OF, but +; establishes quick synonyms. The synonym of LENGTH for LENGTH-OF is a +; controversial idea, and so it is not done until much later in the bootstrap. +; +head: :head-of +tail: :tail-of + +next: specialize 'skip [offset: 1] +back: specialize 'skip [offset: -1] + +unspaced: specialize 'delimit [delimiter: blank] +spaced: specialize 'delimit [delimiter: space] + + +eval proc [ + {Make type testing functions (variadic to quote "top-level" words)} + 'set-word... [set-word! <...>] + + set-word type-name tester meta +][ + while [any-value? set-word: take* set-word...] [ + type-name: append (head clear find (spelling-of set-word) {?}) "!" + tester: typechecker (get bind (to word! type-name) set-word) + set set-word :tester + + ; The TYPECHECKER generator doesn't have make meta information by + ; default, so it leaves it up to the user code. Note REDESCRIBE is + ; not defined yet, so this just makes the meta object directly. + ; + meta: copy system/standard/function-meta + meta/description: form reduce [ + {Returns TRUE if the value is a} type-name + ] + meta/return-type: [logic!] + set-meta :tester meta + ] +] + ; This list consumed by the variadic evaluation, up to the | barrier + ; Each makes a specialization, `XXX: TYPECHECKER XXX!`. A special + ; generator is used vs. something like a specialization of a HAS-TYPE? + ; function...because the generated dispatcher can be more optimized... + ; and type checking is quite common. + ; + blank?: + bar?: + lit-bar?: + logic?: + integer?: + decimal?: + percent?: + money?: + char?: + pair?: + tuple?: + time?: + date?: + word?: + set-word?: + get-word?: + lit-word?: + refinement?: + issue?: + binary?: + string?: + file?: + email?: + url?: + tag?: + bitset?: + image?: + vector?: + block?: + group?: + path?: + set-path?: + get-path?: + lit-path?: + map?: + datatype?: + typeset?: + function?: + varargs?: + object?: + frame?: + module?: + error?: + port?: + gob?: + event?: + handle?: + struct?: + library?: + + ; These typesets are predefined during bootstrap. REDESCRIBE is not + ; defined yet, so decide if it's worth it to add descriptions later + ; e.g. [{Return TRUE if value is } summary {.}] + + any-string?: ;-- "any type of string" + any-word?: ;-- "any type of word" + any-path?: ;-- "any type of path" + any-context?: ;-- "any type of context" + any-number?: ;-- "a number (integer or decimal)" + any-series?: ;-- "any type of series" + any-scalar?: ;-- "any type of scalar" + any-array?: ;-- "a series of Rebol values" +| + + +print: proc [ + "Textually output value (evaluating elements if a block), adds newline" + + value [any-value!] + "Value or BLOCK! literal (BLANK! means print nothing)" + /only + "Do not add a newline, and do not implicitly space items if a block" + /eval + "Allow value to be a block and evaluated (even if not literal)" +; /quote +; "Do not reduce values in blocks" + eval_PRINT ;quote_PRINT +][ + eval_PRINT: eval + eval: :lib/eval + ;quote_PRINT: quote + ;quote: :lib/quote + + if blank? :value [leave] + + write-stdout (either block? :value [ + either any [semiquoted? 'value | eval_PRINT] [ + delimit value either only [blank] [space] + ][ + fail "PRINT called on non-literal block without /EVAL switch" + ] + ][ + form :value ;-- Should this be TO-STRING, or is that MOLD semantics? + ]) + unless only [write-stdout newline] ] -decode-url: none ; set in sys init - -;-- Setup Codecs ------------------------------------------------------------- - -foreach [codec handler] system/codecs [ - if handle? handler [ - ; Change boot handle into object: - codec: set codec make object! [ - entry: handler - title: form reduce ["Internal codec for" codec "media type"] - name: codec - type: 'image! - suffixes: select [ - text [%.txt] - markup [%.html %.htm %.xml %.xsl %.wml %.sgml %.asp %.php %.cgi] - bmp [%.bmp] - gif [%.gif] - jpeg [%.jpg %.jpeg] - png [%.png] - ] codec - ] - ; Media-types block format: [.abc .def type ...] - append append system/options/file-types codec/suffixes codec/name - ] +print-newline: specialize 'write-stdout [value: newline] + + +; PROBE is a good early function to have handy for debugging all the rest (!) +; +probe: func [ + {Debug print a molded value and returns that same value.} + return: [ any-value!] + {Same as the input value.} + value [ any-value!] + {Value to display.} +][ + print mold :value + :value +] + + +eval proc [ + {Make reflector functions (variadic to quote "top-level" words)} + 'set-word... [set-word! <...>] + :divider... [bar! <...>] + 'categories... [string! <...>] + + set-word categories name +][ + while [any-value? set-word: take* set-word...] [ + take* divider... ;-- so it doesn't look like we're setting to a string + categories: take* categories... + + ; extract XXX string from XXX-OF + name: head clear find (spelling-of set-word) {-of} + + set set-word make function! compose/deep [ + [ + (spaced [{Returns a copy of the} name {of a} categories]) + value [any-value!] + ][ + reflect :value (to lit-word! name) + ] + ] + ] ] + spec-of: | {function, object, or module} + body-of: | {function or module} ; %mezz-func.r overwrites + words-of: | {function, object, or module} + values-of: | {object or module} + types-of: | {function} + addr-of: | {struct or callback} + title-of: | {function} ; should work for module +| + + +decode-url: _ ; set in sys init -; Special import case for extensions: -append system/options/file-types switch/default fourth system/version [ - 3 [[%.rx %.dll extension]] ; Windows - 2 [[%.rx %.dylib %.so extension]] ; OS X - 4 7 [[%.rx %.so extension]] ; Other Posix -] [[%.rx extension]] +r3-legacy*: _ ; set in %mezz-legacy.r + +; used only by Ren-C++ as a test of how to patch the lib context prior to +; boot at the higher levels. +test-rencpp-low-level-hook: _ internal!: make typeset! [ - end! unset! frame! handle! + handle! ] immediate!: make typeset! [ - ; Does not include internal datatypes - none! logic! scalar! date! any-word! datatype! typeset! event! + ; Does not include internal datatypes + blank! logic! any-scalar! date! any-word! datatype! typeset! event! ] system/options/result-types: make typeset! [ - immediate! series! bitset! image! object! map! gob! + immediate! any-series! bitset! image! object! map! gob! ] -;-- Create "To-Datatype" conversion functions early in bootstrap: - -any-block?: func [ - "Return TRUE if value is any type of block." - value [any-type!] -][find any-block! type? :value] - -any-string?: func [ - "Return TRUE if value is any type of string." - value [any-type!] -][find any-string! type? :value] - -any-function?: func [ - "Return TRUE if value is any type of function." - value [any-type!] -][find any-function! type? :value] - -any-word?: func [ - "Return TRUE if value is any type of word." - value [any-type!] -][find any-word! type? :value] - -any-path?: func [ - "Return TRUE if value is any type of path." - value [any-type!] -][find any-path! type? :value] - -any-object?: func [ - "Return TRUE if value is any type of object." - value [any-type!] -][find any-object! type? :value] - -number?: func [ - "Return TRUE if value is a number (integer or decimal)." - value [any-type!] -][find number! type? :value] - -series?: func [ - "Return TRUE if value is any type of series." - value [any-type!] -][find series! type? :value] - -scalar?: func [ - "Return TRUE if value is any type of scalar." - value [any-type!] -][find scalar! type? :value] - -true?: func [ - "Returns true if an expression can be used as true." - val ; Note: No [any-type!] - we want unset! to fail. -] [not not :val] - -quote: func [ - "Returns the value passed to it without evaluation." - :value [any-type!] -] [ - :value + +ok?: func [ + "Returns TRUE on all values that are not ERROR!" + value [ any-value!] +][ + not error? :value ] + +; Convenient alternatives for readability +; +neither?: :nand? +both?: :and? diff --git a/src/mezz/base-files.r b/src/mezz/base-files.r index d43a98a39e..9fb1e58b71 100644 --- a/src/mezz/base-files.r +++ b/src/mezz/base-files.r @@ -1,227 +1,207 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Boot Base: File Functions" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Note: { - This code is evaluated just after actions, natives, sysobj, and other lower - levels definitions. This file intializes a minimal working environment - that is used for the rest of the boot. - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Boot Base: File Functions" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Note: { + This code is evaluated just after actions, natives, sysobj, and other lower + levels definitions. This file intializes a minimal working environment + that is used for the rest of the boot. + } ] -info?: func [ - {Returns an info object about a file or url.} - target [file! url!] +info?: function [ + {Returns an info object about a file or url.} + target [file! url!] + /only {for urls, returns 'file or blank} ][ - query target + either file? target [ + query target + ][ + if error? trap [ + t: write target [HEAD] + if only [return 'file] + return make object! [ + name: target + size: t/2 + date: t/3 + type: 'url + ] + ][ + return _ + ] + ] ] exists?: func [ - {Returns the type of a file or URL if it exists, otherwise none.} - target [file! url!] -][ ; Returns 'file or 'dir, or none - select attempt [query target] 'type + {Returns the type of a file or URL if it exists, otherwise blank.} + target [file! url!] +][ ; Returns 'file or 'dir, or blank + either url? target [ + info?/only target + ][ + select attempt [query target] 'type + ] ] -size?: func [ - {Returns the size of a file.} - target [file! url!] +size-of: size?: func [ + {Returns the size of a file.} + target [file! url!] ][ - all [ - target: attempt [query target] - target/size - ] + all [ + target: attempt [info? target] + target/size + ] ] modified?: func [ - {Returns the last modified date of a file.} - target [file! url!] + {Returns the last modified date of a file.} + target [file! url!] ][ - all [ - target: attempt [query target] - target/date - ] + all [ + target: attempt [info? target] + target/date + ] ] -suffix?: func [ - "Return the file suffix of a filename or url. Else, NONE." - path [file! url! string!] +suffix-of: func [ + "Return the file suffix of a filename or url. Else, NONE." + path [file! url! string!] ][ - if all [ - path: find/last path #"." - not find path #"/" - ][to file! path] + to-value if all [ + path: find/last path #"." + not find path #"/" + ][to file! path] ] dir?: func [ - {Returns TRUE if the file or url ends with a slash (or backslash).} - target [file! url!] + {Returns TRUE if the file or url ends with a slash (or backslash).} + target [file! url!] ][ - true? find "/\" last target + true? find "/\" last target ] dirize: func [ - {Returns a copy (always) of the path as a directory (ending slash).} - path [file! string! url!] + {Returns a copy (always) of the path as a directory (ending slash).} + path [file! string! url!] ][ - path: copy path - if slash <> last path [append path slash] - path + path: copy path + if slash <> last path [append path slash] + path ] make-dir: func [ - "Creates the specified directory. No error if already exists." - path [file! url!] - /deep "Create subdirectories too" - /local dirs end created + "Creates the specified directory. No error if already exists." + path [file! url!] + /deep "Create subdirectories too" + /local dirs end created ][ - if empty? path [return path] - if slash <> last path [path: dirize path] - - if exists? path [ - if dir? path [return path] - cause-error 'access 'cannot-open path - ] - - if any [not deep url? path] [ - create path - return path - ] - - ; Scan reverse looking for first existing dir: - path: copy path - dirs: copy [] - while [ - all [ - not empty? path - not exists? path - remove back tail path ; trailing slash - ] - ][ - end: any [find/last/tail path slash path] - insert dirs copy end - clear end - ] - - ; Create directories forward: - created: copy [] - foreach dir dirs [ - path: either empty? path [dir][path/:dir] - append path slash - if error? try [make-dir path] [ - foreach dir created [attempt [delete dir]] - cause-error 'access 'cannot-open path - ] - insert created path - ] - path + if empty? path [return path] + if slash <> last path [path: dirize path] + + if exists? path [ + if dir? path [return path] + cause-error 'access 'cannot-open path + ] + + if any [not deep url? path] [ + create path + return path + ] + + ; Scan reverse looking for first existing dir: + path: copy path + dirs: copy [] + while [ + all [ + not empty? path + not exists? path + remove back tail path ; trailing slash + ] + ][ + end: any [find/last/tail path slash path] + insert dirs copy end + clear end + ] + + ; Create directories forward: + created: copy [] + for-each dir dirs [ + path: either empty? path [dir][path/:dir] + append path slash + if trap? [make-dir path] [ + for-each dir created [attempt [delete dir]] + cause-error 'access 'cannot-open path + ] + insert created path + ] + path ] delete-dir: func [ - {Deletes a directory including all files and subdirectories.} - dir [file! url!] - /local files + {Deletes a directory including all files and subdirectories.} + dir [file! url!] + /local files ][ - if all [ - dir? dir - dir: dirize dir - attempt [files: load dir] - ] [ - foreach file files [delete-dir dir/:file] - ] - attempt [delete dir] + if all [ + dir? dir + dir: dirize dir + attempt [files: load dir] + ] [ + for-each file files [delete-dir dir/:file] + ] + attempt [delete dir] ] script?: func [ - {Checks file, url, or string for a valid script header.} - source [file! url! binary! string!] + {Checks file, url, or string for a valid script header.} + source [file! url! binary! string!] ][ - switch type?/word source [ - file! url! [source: read source] - string! [source: to binary! source] ; Remove this line if FIND-SCRIPT changed to accept string! - ] - find-script source ; Returns binary! + ; !!! to word! necessary as long as OPTIONS_DATATYPE_WORD_STRICT exists + switch to word! type-of source [ + file! url! [source: read source] + string! [source: to binary! source] ; Remove this line if FIND-SCRIPT changed to accept string! + ] + find-script source ; Returns binary! ] file-type?: func [ - "Return the identifying word for a specific file type (or NONE)." - file [file! url!] + "Return the identifying word for a specific file type (or NONE)." + file [file! url!] ][ - if file: find find system/options/file-types suffix? file word! [first file] + to-value if file: find find system/options/file-types suffix-of file word! [ + first file + ] ] split-path: func [ - "Splits and returns directory path and file as a block." - target [file! url! string!] - /local dir pos + "Splits and returns directory path and file as a block." + target [file! url! string!] + /local dir pos ][ - parse/all target [ - [#"/" | 1 2 #"." opt #"/"] end (dir: dirize target) | - pos: any [thru #"/" [end | pos:]] ( - all [empty? dir: copy/part target at head target index? pos dir: %./] - all [find [%. %..] pos: to file! pos insert tail pos #"/"] - ) - ] - reduce [dir pos] + parse target [ + [#"/" | 1 2 #"." opt #"/"] end (dir: dirize target) | + pos: any [thru #"/" [end | pos:]] ( + all [empty? dir: copy/part target at head target index-of pos dir: %./] + all [find [%. %..] pos: to file! pos insert tail pos #"/"] + ) + ] + reduce [dir pos] ] intern: function [ - "Imports (internalize) words and their values from the lib into the user context." - data [block! any-word!] "Word or block of words to be added (deeply)" + "Imports (internalize) words and their values from the lib into the user context." + data [block! any-word!] "Word or block of words to be added (deeply)" ][ - index: 1 + length? usr: system/contexts/user ; optimization - data: bind/new :data usr ; Extend the user context with new words - resolve/only usr lib index ; Copy only the new values into the user context - :data + index: 1 + length-of usr: system/contexts/user ; optimization + data: bind/new :data usr ; Extend the user context with new words + resolve/only usr lib index ; Copy only the new values into the user context + :data ] -load: function [ - {Simple load of a file, URL, or string/binary - minimal boot version.} - source [file! url! string! binary!] - /header {Includes REBOL header object if present} - /all {Load all values, including header (as block)} - ;/unbound {Do not bind the block} -][ - if string? data: case [ - file? source [read source] - url? source [read source] - 'else [source] - ] [data: to binary! data] - - if binary? :data [ - data: transcode data - hdr?: lib/all ['REBOL = first data block? second data] - case [ - header [ - unless hdr? [cause-error 'syntax 'no-header source] - remove data - data/1: attempt [construct/with first data system/standard/header] - ] - all none ; /header overrides /all - hdr? [remove/part data 2] - ] - ; unless unbound [] - data: intern data - - ; If appropriate and possible, return singular data value - unless any [ - all - header - 1 <> length? data - ][data: first data] - ] - - :data -] - -; Reserve these slots near LOAD: -save: -import: - none diff --git a/src/mezz/base-funcs.r b/src/mezz/base-funcs.r index 2b75ec152b..737b3c8f0f 100644 --- a/src/mezz/base-funcs.r +++ b/src/mezz/base-funcs.r @@ -1,123 +1,1171 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Boot Base: Function Constructors" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Note: { - This code is evaluated just after actions, natives, sysobj, and other lower - levels definitions. This file intializes a minimal working environment - that is used for the rest of the boot. - } -] - -func: make function! [[ - ; !!! This is a special minimal FUNC for more efficient boot. Gets replaced later in boot. - {Non-copying function constructor (optimized for boot).} - spec [block!] {Help string (opt) followed by arg words (and opt type and string)} - body [block!] {The body block of the function} -][ - make function! reduce [spec body] -]] - -function: funct: func [ - {Defines a function with all set-words as locals.} - spec [block!] {Help string (opt) followed by arg words (and opt type and string)} - body [block!] {The body block of the function} - /with {Define or use a persistent object (self)} - object [object! block! map!] {The object or spec} - /extern words [block!] {These words are not local} -][ - ; Copy the spec and add /local to the end if not found - unless find spec: copy/deep spec /local [append spec [ - /local ; In a block so the generated source gets the newlines - ]] - ; Make a full copy of the body, to allow reuse of the original - body: copy/deep body - ; Collect all set-words in the body as words to be used as locals, and add - ; them to the spec. Don't include the words already in the spec or object. - insert find/tail spec /local collect-words/deep/set/ignore body either with [ - ; Make our own local object if a premade one is not provided - unless object? object [object: make object! object] - bind body object ; Bind any object words found in the body - ; Ignore the words in the spec and those in the object. The spec needs - ; to be copied since the object words shouldn't be added to the locals. - append append append copy spec 'self words-of object words ; ignore 'self too - ][ - ; Don't include the words in the spec, or any extern words. - either extern [append copy spec words] [spec] - ] - make function! reduce [spec body] + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Boot Base: Function Constructors" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Note: { + This code is evaluated just after actions, natives, sysobj, and other lower + levels definitions. This file intializes a minimal working environment + that is used for the rest of the boot. + } ] +assert: func [ + {Ensure conditions are TRUE? if hooked by debugging (see also: VERIFY)} + + return: [ logic! block!] + {Always returns void unless /QUIET is used to return failing blocks} + conditions [logic! block!] + {Single logic value or block of conditions to evaluate and test TRUE?} + /quiet + {Return failing condition as a BLOCK!, or BLANK! if success} +][ + ; ASSERT has no default implementation, but can be HIJACKed by a debug + ; build with a custom validation or output routine. (Currently there is + ; a default hijacking to set it to be equivalent to VERIFY, late in boot.) +] + +set/lookback quote enfix: proc [ + "Convenience version of SET/LOOKBACK, e.g `+: enfix :add`" + :target [set-word! set-path!] + action [function!] +][ + set/lookback target :action + + ; return value can't currently be given back as enfix, since it is a + ; property of words and not values. Should it be given back at all? +] + +default: enfix func [ + "Set word or path to a default value if it is not set yet or blank." + + return: [any-value!] + :target [set-word! set-path!] + "The word" + value [any-value!] ; not on purpose + "Value to set (blocks and 0-arity functions evaluated)" + + gotten +][ + ; A lookback quoting function that quotes a SET-WORD! on its left is + ; responsible for setting the value if it wants it to change since the + ; SET-WORD! is not actually active. But if something *looks* like an + ; assignment, it's good practice to evaluate the whole expression to + ; the result the SET-WORD! was set to, so `x: y: op z` makes `x = y`. + ; + if all [any-value? gotten: get/opt target | not blank? :gotten] [ + :gotten + ] + else [ + set/opt target if true :value ;-- executed if block or function + ] +] + + does: func [ - {A shortcut to define a function that has no arguments or locals.} - body [block!] {The body block of the function} + {A shortcut to define a function that has no arguments or locals.} + return: [function!] + body [block!] + {The body block of the function} +][ + func [] body ;-- no spec documentation permits any return value +] + + +make-action: func [ + {Internal generator used by FUNCTION and PROCEDURE specializations.} + return: [function!] + generator [function!] + {Arity-2 "lower"-level function generator to use (e.g. FUNC or PROC)} + spec [block!] + {Help string (opt) followed by arg words (and opt type and string)} + body [block!] + {The body block of the function} + + new-spec var other + new-body exclusions locals defaulters statics +][ + exclusions: copy [] + new-spec: make block! length-of spec + new-body: _ + statics: _ + defaulters: _ + var: _ + + ;; dump [spec] + + ; Insert into the spec. This is based on the belief that + ; indefinite duration is a fair user expectation without having to ask. + ; Consider the legitimacy of: + ; + ; foo: function [x] [y: x * 2 | return func [z] [x + y + z] + ; + append new-spec + + ; Gather the SET-WORD!s in the body, excluding the collected ANY-WORD!s + ; that should not be considered. Note that COLLECT is not defined by + ; this point in the bootstrap. + ; + ; !!! REVIEW: ignore self too if binding object? + ; + parse spec [any [ + if (set? 'var) [ + set var: any-word! ( + append exclusions var ;-- exclude args/refines + append new-spec var + ) + | + set other: [block! | string!] ( + append/only new-spec other ;-- spec notes or data type blocks + ) + ] + | + other: + [group!] ( + if not var [ + fail [ + ; spec + ; other + "Default value not paired with argument:" (mold other/1) + ] + ] + unless defaulters [ + defaulters: copy [] + ] + append defaulters compose/deep [ + (to set-word! var) default [(reduce other/1)] + ] + ) + | + (var: void) ;-- everything below this line clears var + fail ;-- failing here means rolling over to next rule () + | + ;-- don't add to new-spec as we already added it + | + + any [set var: word! (other: _) opt set other: group! ( + append new-spec to set-word! var + append exclusions var + if other [ + unless defaulters [ + defaulters: copy [] + ] + append defaulters compose/deep [ + (to set-word! var) default [(reduce other)] + ] + ] + )] + (var: void) ;-- don't consider further GROUP!s or variables + | + ( + unless new-body [ + append exclusions 'self + new-body: copy/deep body + ] + ) + any [ + set other: [word! | path!] ( + other: ensure any-context! get other + bind new-body other + for-each [key val] other [ + append exclusions key + ] + ) + ] + | + any [ + set other: [word! | path!] (append exclusions other) + | + string! ;-- skip over as commentary + ] + | + ; While is a well-known computer science term, it is an + ; un-intuitive word. is Ren-C's preference in mezzanine or + ; official code, relating it to the HAS object constructor. + ; + [ | ] ( + unless statics [ + statics: copy [] + ] + unless new-body [ + append exclusions 'self + new-body: copy/deep body + ] + ) + any [ + set var: word! (other: quote ()) opt set other: group! ( + append exclusions var + append statics compose/only [ + (to set-word! var) (other) + ] + ) + ] + (var: void) + | + end accept + | + other: ( + fail [ + ; spec + ; other + "Invalid spec item:" (mold other/1) + ] + ) + ]] + + locals: collect-words/deep/set/ignore body exclusions + + ;; dump [{before} statics new-spec exclusions] + + if statics [ + statics: has statics + bind new-body statics + ] + + ; !!! The words that come back from COLLECT-WORDS are all WORD!, but we + ; need SET-WORD! to specify pure locals to the generators. Review the + ; COLLECT-WORDS interface to efficiently give this result, as well as + ; a possible COLLECT-WORDS/INTO + ; + for-skip locals 1 [ ;-- FOR-NEXT not specialized yet + append new-spec to set-word! locals/1 + ] + + ;; dump [{after} new-spec defaulters] + + generator new-spec either defaulters [ + append/only defaulters as group! any [new-body body] + ][ + any [new-body body] + ] +] + +;-- These are "redescribed" after REDESCRIBE is created +; +function: specialize :make-action [generator: :func] +procedure: specialize :make-action [generator: :proc] + + +; Functions can be chained, adapted, and specialized--repeatedly. The meta +; information from which HELP is determined can be inherited through links +; in that meta information. Though in order to mutate the information for +; the purposes of distinguishing a derived function, it must be copied. +; +dig-function-meta-fields: function [value [function!]] [ + meta: meta-of :value + + unless meta [ + return construct system/standard/function-meta [ + description: _ + return_type: _ + return_note: _ + parameter-types: make frame! :value + parameter-notes: make frame! :value + ] + ] + + underlying: maybe function! any [ + :meta/specializee + :meta/adaptee + all [block? :meta/chainees | first meta/chainees] + ] + + fields: all [:underlying | dig-function-meta-fields :underlying] + + inherit-frame: function [parent [blank! frame!]] [ + if blank? parent [return blank] + + child: make frame! :value + for-each param child [ + if any-value? select* parent param [ + child/(param): copy parent/(param) + ] + ] + return child + ] + + return construct system/standard/function-meta [ + description: ( + maybe string! any [ + select meta 'description + all [fields | copy fields/description] + ] + ) + return-type: ( + ; + ; !!! The optimized native signals the difference between + ; "undocumented argument" and "no argument at all" with the + ; void vs BLANK! distinction. This routine needs an overhaul and + ; wasn't really written to anticipate the subtlety. But be + ; sensitive to it here. + ; + temp: select meta 'return-type + if all [not set? 'temp | fields | select? fields 'return-type] [ + temp: copy fields/return-type + ] + :temp + ) + return-note: ( + maybe string! any [ + select meta 'return-note + all [fields | copy fields/return-note] + ] + ) + parameter-types: ( + maybe frame! any [ + select meta 'parameter-types + all [fields | inherit-frame :fields/parameter-types] + ] + ) + parameter-notes: ( + maybe frame! any [ + select meta 'parameter-notes + all [fields | inherit-frame :fields/parameter-notes] + ] + ) + ] +] + +redescribe: function [ + {Mutate function description with new title and/or new argument notes.} + + return: [function!] + {The input function, with its description now updated.} + spec [block!] + {Either a string description, or a spec block (without types).} + value [function!] + {(modified) Function whose description is to be updated.} +][ + meta: meta-of :value + notes: _ + + ; For efficiency, objects are only created on demand by hitting the + ; required point in the PARSE. Hence `redescribe [] :foo` will not tamper + ; with the meta information at all, while `redescribe [{stuff}] :foo` will + ; only manipulate the description. + + on-demand-meta: does [ + case/all [ + not meta [ + meta: copy system/standard/function-meta + set-meta :value meta + ] + + not find meta 'description [ + fail [{archetype META-OF doesn't have DESCRIPTION slot} meta] + ] + + not notes: to-value :meta/parameter-notes [ + return () ; specialized or adapted, HELP uses original notes + ] + + not frame? notes [ + fail [{PARAMETER-NOTES in META-OF is not a FRAME!} notes] + ] + + :value != function-of notes [ + fail [{PARAMETER-NOTES in META-OF frame mismatch} notes] + ] + ] + ] + + ; !!! SPECIALIZEE and SPECIALIZEE-NAME will be lost if a REDESCRIBE is + ; done of a specialized function that needs to change more than just the + ; main description. Same with ADAPTEE and ADAPTEE-NAME in adaptations. + ; + ; (This is for efficiency to not generate new keylists on each describe + ; but to reuse archetypal ones. Also to limit the total number of + ; variations that clients like HELP have to reason about.) + ; + on-demand-notes: does [ + on-demand-meta + + if find meta 'parameter-notes [return ()] + + fields: dig-function-meta-fields :value + + meta: blank ;-- need to get a parameter-notes field in the OBJECT! + on-demand-meta ;-- ...so this loses SPECIALIZEE, etc. + + description: meta/description: fields/description + notes: meta/parameter-notes: fields/parameter-notes + types: meta/parameter-types: fields/parameter-types + ] + + unless parse spec [ + opt [ + set description: string! ( + either all [equal? description {} | not meta] [ + ; No action needed (no meta to delete old description in) + ][ + on-demand-meta + meta/description: if not equal? description {} [ + description + ] + ] + ) + ] + any [ + set param: [word! | get-word! | lit-word! | refinement! | set-word!] + + ; It's legal for the redescribe to name a parameter just to + ; show it's there for descriptive purposes without adding notes. + ; But if {} is given as the notes, that's seen as a request + ; to delete a note. + ; + opt [[set note: string!] ( + on-demand-meta + either all [set-word? param | equal? param quote return:] [ + meta/return-note: either equal? note {} [ + _ + ][ + copy note + ] + ][ + if (not equal? note {}) or notes [ + on-demand-notes + + unless find notes to word! param [ + fail [param "not found in frame to describe"] + ] + + actual: first find words-of :value param + unless strict-equal? param actual [ + fail [param {doesn't match word type of} actual] + ] + + notes/(to word! param): if not equal? note {} [note] + ] + ] + )] + ] + ][ + fail [{REDESCRIBE specs should be STRING! and ANY-WORD! only:} spec] + ] + + ; If you kill all the notes then they will be cleaned up. The meta + ; object will be left behind, however. + ; + if all [notes | every [param note] notes [not set? 'note]] [ + meta/parameter-notes: () + ] + + :value ;-- should have updated the meta +] + + +redescribe [ + {Define an action with set-words as locals, that returns a value.} +] :function + +redescribe [ + {Define an action with set-words as locals, that doesn't return a value.} +] :procedure + + +get*: redescribe [ + {Variation of GET which returns void if the source is not set} +]( + specialize 'get [opt: true] +) + +get-value: redescribe [ + {Variation of GET which fails if the value is not set (vs. void or blank)} +]( + chain [ + :get* + | + func [x [ any-value!]] [ + unless set? 'x [ + fail "GET-VALUE requires source variable to be set" + ] + :x + ] + ] +) + +set*: redescribe [ + {Variation of SET where voids are tolerated for unsetting variables.} +]( + specialize 'set [opt: true] +) + +; LOGIC VERSIONS OF CONTROL STRUCTURES +; +; Control structures evaluate to either void (if no branches taken) or the +; last value of any evaluated blocks. This applies to everything from IF +; to CASE to WHILE. The ? versions are tailored to return whether a branch +; was taken at all, and always return either TRUE or FALSE. + +if?: redescribe [ + {Variation of IF which returns TRUE if the branch runs, FALSE if not} +]( + chain [:if | :any-value?] +) + +if*: redescribe [ + {Same as IF/OPT (return void, not blank, if branch evaluates to void)} +]( + specialize 'if [opt: true] +) + +unless?: redescribe [ + {Variation of UNLESS which returns TRUE if the branch runs, FALSE if not} +]( + chain [:unless | :any-value?] +) + +unless*: redescribe [ + {Same as UNLESS/OPT (return void, not blank, if branch evaluates to void)} +]( + specialize 'unless [opt: true] +) + +either*: redescribe [ + {Same as EITHER/OPT (return void, not blank, if branch evaluates to void)} +]( + specialize 'either [opt: true] +) + +while?: redescribe [ + {Variation of WHILE which returns TRUE if the body ever runs, FALSE if not} +]( + chain [:while | :any-value?] +) + +case?: redescribe [ + {Variation of CASE which returns TRUE if any cases run, FALSE if not} +]( + chain [:case | :any-value?] +) + +case*: redescribe [ + {Same as CASE/OPT (return void, not blank, if branch evaluates to void)} +]( + specialize 'case [opt: true] +) + +switch?: redescribe [ + {Variation of SWITCH which returns TRUE if any cases run, FALSE if not} +]( + chain [:switch | :any-value?] +) + +switch*: redescribe [ + {Same as SWITCH/OPT (return void, not blank, if branch evaluates to void)} +]( + specialize 'switch [opt: true] +) + +trap?: redescribe [ + {Variation of TRAP which returns TRUE if an error traps, FALSE if not} +]( + specialize 'trap [?: true] +) + +catch?: redescribe [ + {Variation of CATCH which returns TRUE if a throw is caught, FALSE if not} +]( + specialize 'catch [?: true] +) + +any?: redescribe [ + {Shortcut OR, ignores voids. Unlike plain ANY, forces result to LOGIC!} +]( + chain [:any | :to-value | :true?] +) + +all?: redescribe [ + {Shortcut AND, ignores voids. Unlike plain ALL, forces result to LOGIC!} +]( + chain [:all | :to-value | :true?] +) + +maybe?: redescribe [ + {Check value using tests (match types, TRUE? or FALSE?, filter function)} + ; return: [logic!] ;-- blocks for type changes not supported yet + ; {TRUE if match, FALSE if no match (use MAYBE to pass through value)} +]( + specialize 'maybe [?: true] +) + +find?: redescribe [ + {Variant of FIND that returns TRUE if present and FALSE if not.} +]( + chain [:find | :true?] +) + +select: redescribe [ + {Variant of SELECT* that returns BLANK when not found, instead of void} +]( + chain [:select* | :to-value] +) + +select?: redescribe [ + {Variant of SELECT that returns TRUE if a value was selected, else FALSE.} +]( + chain [:select | :any-value?] +) + +pick: redescribe [ + {Variant of PICK* that returns BLANK! when not found, instead of void} +]( + chain [:pick* | :to-value] +) + +take: redescribe [ + {Variant of TAKE* that will give an error if it can't take, vs. void} +]( + chain [ + :take* + | + func [ + return: [any-value!] + took [ any-value!] + ][ + either set? 'took [ + :took + ][ + fail "Can't TAKE from series end (see TAKE* to get void)" + ] + ] + ] +) + +parse?: redescribe [ + {Variant of PARSE that enforces a TRUE or FALSE result from the rules.} +]( + chain [ + :parse + | + func [x][ + unless logic? :x [ + fail [ + "Rules passed to PARSE? returned non-LOGIC!:" (mold :x) + ] + ] + x + ] + ] +) + +for-next: redescribe [ + "Evaluates a block for each position until the end, using NEXT to skip" +]( + specialize 'for-skip [skip: 1] +) + +for-back: redescribe [ + "Evaluates a block for each position until the start, using BACK to skip" +]( + specialize 'for-skip [skip: -1] +) + +lock-of: redescribe [ + "If value is already locked, return it...otherwise CLONE it and LOCK it." +]( + specialize 'lock [clone: true] +) + + +; To help for discoverability, there is SET-INFIX and INFIX?. However, the +; term can be a misnomer if the function is more advanced, and using the +; "lookback" capabilities in another way. Hence these return descriptive +; errors when people are "outside the bounds" of assurance RE:infixedness. + +arity-of: function [ + "Get the number of fixed parameters (not refinements or refinement args)" + value [any-word! any-path! function!] +][ + if path? :value [fail "arity-of for paths is not yet implemented."] + + unless function? :value [ + value: get value + unless function? :value [return 0] + ] + + if variadic? :value [ + fail "arity-of cannot give reliable answer for variadic functions" + ] + + ; !!! Should willingness to take endability cause a similar error? + ; Arguably the answer tells you an arity that at least it *will* accept, + ; so it's not completely false. + + arity: 0 + for-each param reflect :value 'words [ + if refinement? :param [ + return arity + ] + arity: arity + 1 + ] + arity +] + +nfix?: function [ + n [integer!] + name [string!] + source [any-word! any-path!] +][ + case [ + not lookback? source [false] + equal? n arity: arity-of source [true] + n < arity [ + ; If the queried arity is lower than the arity of the function, + ; assume it's ok...e.g. PREFIX? callers know INFIX? exists (but + ; we don't assume INFIX? callers know PREFIX?/ENDFIX? exist) + false + ] + ] else [ + fail [ + name "used on lookback function with arity" arity + | + "Use LOOKBACK? for generalized (tricky) testing" + ] + ] + + +] + +endfix?: redescribe [ + {TRUE if a no-argument function is SET/LOOKBACK to not allow right infix.} +]( + specialize :nfix? [n: 0 | name: "ENDFIX?"] +) + +postfix?: redescribe [ + {TRUE if an arity 1 function is SET/LOOKBACK to act as postfix.} +]( + specialize :nfix? [n: 1 | name: "POSTFIX?"] +) + +infix?: redescribe [ + {TRUE if an arity 2 function is SET/LOOKBACK to act as infix.} +]( + specialize :nfix? [n: 2 | name: "INFIX?"] +) + + +set-nfix: function [ + return: [function!] + n [integer!] + name [string!] + target [any-word! any-path!] + value [function!] +][ + unless equal? n arity-of :value [ + fail [name "requires arity" n "functions, see SET/LOOKAHEAD"] + ] + set/lookback target :value +] + +set-endfix: redescribe [ + {Convenience wrapper for SET/LOOKBACK that ensures function is arity 0.} +]( + specialize :set-nfix [n: 0 | name: "SET-ENDFIX"] +) + +set-postfix: redescribe [ + {Convenience wrapper for SET/LOOKBACK that ensures a function is arity 1.} +]( + specialize :set-nfix [n: 1 | name: "SET-POSTFIX"] +) + +set-infix: redescribe [ + {Convenience wrapper for SET/LOOKBACK that ensures a function is arity 2.} +]( + specialize :set-nfix [n: 2 | name: "SET-INFIX"] +) + + +lambda: function [ + {Convenience variadic wrapper for FUNC and FUNCTION constructors} + + return: [function!] + :args [ word! path! block!] + {Block of argument words, or a single word (passed via LIT-WORD!)} + :body [any-value! <...>] + {Block that serves as the body or variadic elements for the body} + /only + {Use FUNC and do not run locals-gathering on the body} +][ + f: either only [:func] [:function] + + f ( + :args then [to block! args] else [[]] + )( + if block? first body [ + take body + ] else [ + make block! body + ] + ) +] + +left-bar: func [ + {Expression barrier that evaluates to left side but executes right.} + return: [ any-value!] + {Evaluative result of `left`.} + left [ any-value!] + {A single complete expression on the left.} + right [ any-value! <...>] + {Any number of expressions on the right.} + :look [any-value! <...>] ][ - make function! copy/deep reduce [[] body] + ; !!! Should this fail if left is END? How would it tell the difference + ; between left being void or end, is that exposed with SEMIQUOTED? + + loop-until [ + while [bar? first look] [take look] ;-- want to feed past BAR!s + take* right ;-- a void eval or an end both give back void here + tail? look + ] + :left ] +right-bar: func [ + {Expression barrier that evaluates to first expression on right.} + return: [ any-value!] + {Evaluative result of first of the right expressions.} + left [ any-value!] + {A single complete expression on the left.} + right [ any-value! <...>] + {Any number of expressions on the right.} + :look [any-value! <...>] +][ + ; !!! This could fail if `tail? right`, but should it? Might make + ; COMPOSE situations less useful, e.g. `compose [thing |> (may-be-void)]` + + also ( + ; We want to make sure `1 |> | 2 3 4` is void, not BAR! + ; + either* bar? first look [void] [take* right] + )( + loop-until [ + while [bar? first look] [take look] + take* right ;-- a void eval or an end both give back void here + tail? look + ] + ) +] + + +once-bar: func [ + {Expression barrier that's willing to only run one expression after it} + return: [ any-value!] + left [ any-value!] + right [ any-value! <...>] + :lookahead [any-value! <...>] + look: +][ + also take right ( + unless any [ + tail? right + | + '|| = look: take lookahead ;-- hack...recognize selfs + ][ + fail [ + "|| expected single expression, found residual of" :look + ] + ] + ) +] + + use: func [ - {Defines words local to a block.} - vars [block! word!] {Local word(s) to the block} - body [block!] {Block to evaluate} -][ ; !!Needs the R3 equivalent of the [throw] function attribute in the created closure! - apply make closure! reduce [to block! vars copy/deep body] [] + {Defines words local to a block.} + return: [ any-value!] + vars [block! word!] {Local word(s) to the block} + body [block!] {Block to evaluate} +][ + ; We are building a FUNC out of the body that was passed to us, and that + ; body may have RETURN words with bindings in them already that we do + ; not want to disturb with the definitional bindings in the new code. + ; So that means either using MAKE FUNCTION! (which wouldn't disrupt + ; RETURN bindings) or using the more friendly FUNC and ` return` + ; (they do the same thing, just FUNC is arity-2) + ; + ; is used so that the data for the locals will still be + ; available if any of the words leak out and are accessed after the + ; execution is finished. + ; + eval func compose [ (vars) return] body ] -object: func [ - {Defines a unique object.} - blk [block!] {Object words and values (modified)} +; Shorthand helper for CONSTRUCT (similar to DOES for FUNCTION). +; +has: func [ + "Defines an object with just a body...no spec and no parent." + body [block!] ;-- !!! name checked as `body` vs `vars` by r2r3-future.r + "Object words and values (bindings modified)" + /only + "Values are kept as-is" ][ - make object! append blk none + construct/(all [only 'only]) [] body ] module: func [ - "Creates a new module." - spec [block!] "The header block of the module (modified)" - body [block!] "The body block of the module (modified)" - /mixin "Mix in words from other modules" - words [object!] "Words collected into an object" + "Creates a new module." + spec [block! object!] "The header block of the module (modified)" + body [block!] "The body block of the module (modified)" + /mixin "Mix in words from other modules" + mixins [object!] "Words collected into an object" + /local hidden w mod ][ - make module! unbind/deep reduce pick [[spec body] [spec body words]] not mixin + mixins: to-value :mixins + + ; !!! Is it a good idea to mess with the given spec and body bindings? + ; This was done by MODULE but not seemingly automatically by MAKE MODULE! + ; + unbind/deep body + + ; Convert header block to standard header object: + ; + if block? :spec [ + unbind/deep spec + spec: attempt [construct/only system/standard/header :spec] + ] + + ; Historically, the Name: and Type: fields would tolerate either LIT-WORD! + ; or WORD! equally well. This is because it used R3-Alpha's CONSTRUCT, + ; (which was non-evaluative by default, unlike Ren-C's construct) but + ; without the /ONLY switch. In that mode, it decayed LIT-WORD! to WORD!. + ; To try and standardize the variance, Ren-C does not accept LIT-WORD! + ; in these slots. + ; + ; !!! Although this is a goal, it creates some friction. Backing off of + ; it temporarily. + ; + if lit-word? spec/name [ + spec/name: as word! spec/name + ;fail ["Ren-C module Name:" (spec/name) "must be WORD!, not LIT-WORD!"] + ] + if lit-word? spec/type [ + spec/type: as word! spec/type + ;fail ["Ren-C module Type:" (spec/type) "must be WORD!, not LIT-WORD!"] + ] + + ; Validate the important fields of header: + ; + ; !!! This should be an informative error instead of asserts! + ; + for-each [var types] [ + spec object! + body block! + mixins [object! blank!] + spec/name [word! blank!] + spec/type [word! blank!] + spec/version [tuple! blank!] + spec/options [block! blank!] + ][ + do compose/only [ensure (types) (var)] ;-- names to show if fails + ] + + ; In Ren-C, MAKE MODULE! acts just like MAKE OBJECT! due to the generic + ; facility for SET-META. + + mod: make module! 7 ; arbitrary starting size + + if find spec/options 'extension [ + append mod 'lib-base ; specific runtime values MUST BE FIRST + ] + + unless spec/type [spec/type: 'module] ; in case not set earlier + + ; Collect 'export keyword exports, removing the keywords + if find body 'export [ + unless block? select spec 'exports [ + join spec ['exports make block! 10] + ] + + ; Note: 'export overrides 'hidden, silently for now + parse body [while [ + to 'export remove skip opt remove 'hidden opt + [ + set w any-word! ( + unless find spec/exports w: to word! w [ + append spec/exports w + ] + ) + | + set w block! ( + append spec/exports collect-words/ignore w spec/exports + ) + ] + ] to end] + ] + + ; Collect 'hidden keyword words, removing the keywords. Ignore exports. + hidden: _ + if find body 'hidden [ + hidden: make block! 10 + ; Note: Exports are not hidden, silently for now + parse body [while [ + to 'hidden remove skip opt + [ + set w any-word! ( + unless find select spec 'exports w: to word! w [ + append hidden w] + ) + | + set w block! ( + append hidden collect-words/ignore w select spec 'exports + ) + ] + ] to end] + ] + + ; Add hidden words next to the context (performance): + if block? hidden [bind/new hidden mod] + + if block? hidden [protect/hide/words hidden] + + set-meta mod spec + + ; Add exported words at top of context (performance): + if block? select spec 'exports [bind/new spec/exports mod] + + either find spec/options 'isolate [ + ; + ; All words of the module body are module variables: + ; + bind/new body mod + + ; The module keeps its own variables (not shared with system): + ; + if object? mixins [resolve mod mixins] + + comment [resolve mod sys] ; no longer done -Carl + + resolve mod lib + ][ + ; Only top level defined words are module variables. + ; + bind/only/set body mod + + ; The module shares system exported variables: + ; + bind body lib + + comment [bind body sys] ; no longer done -Carl + + if object? mixins [bind body mixins] + ] + + bind body mod ;-- redundant? + do body + + ;print ["Module created" spec/name spec/version] + mod ] + cause-error: func [ - "Causes an immediate error throw with the provided information." - err-type [word!] - err-id [word!] - args -][ - ; Make sure it's a block: - args: compose [(:args)] - ; Filter out functional values: - forall args [ - if any-function? first args [ - change/only args spec-of first args - ] - ] - ; Build and throw the error: - do make error! [ - type: err-type - id: err-id - arg1: first args - arg2: second args - arg3: third args - ] -] - -default: func [ - "Set a word to a default value if it hasn't been set yet." - 'word [word! set-word! lit-word!] "The word (use :var for word! values)" - value "The value" ; unset! not allowed on purpose -][ - unless all [value? word not none? get word] [set word :value] :value -] - -secure: func ['d] [boot-print "SECURE is disabled"] + "Causes an immediate error throw with the provided information." + err-type [word!] + err-id [word!] + args +][ + ; Make sure it's a block: + args: compose [(:args)] + + ; Filter out functional values: + for-next args [ + if function? first args [ + change/only args meta-of first args + ] + ] + + ; Build and raise the error: + do make error! [ + type: err-type + id: err-id + arg1: first args + arg2: second args + arg3: third args + ] +] + + +fail: function [ + {Interrupts execution by reporting an error (a TRAP can intercept it).} + reason [error! string! block!] + "ERROR! value, message string, or failure spec" + /where + "Specify an originating location other than the FAIL itself" + location [frame! any-word!] + "Frame or parameter at which to indicate the error originated" +][ + ; By default, make the originating frame the FAIL's frame + ; + unless where [location: context-of 'reason] + + ; Ultimately we might like FAIL to use some clever error-creating dialect + ; when passed a block, maybe something like: + ; + ; fail [ {The key} key-name: key {is invalid}] + ; + ; That could provide an error ID, the format message, and the values to + ; plug into the slots to make the message...which could be extracted from + ; the error if captured (e.g. error/id and `error/key-name`. Another + ; option would be something like: + ; + ; fail/with [{The key} :key-name {is invalid}] [key-name: key] + ; + case [ + error? reason [ + error: reason + ] + string? reason [ + error: make error! reason + ] + block? reason [ + error: make error! spaced reason + ] + ] + + ; !!! Does SET-LOCATION-OF-ERROR need to be a native? + ; + set-location-of-error error location + + ; Raise error to the nearest TRAP up the stack (if any) + ; + do error +] + + +ensure: function [ + {Pass through a value only if it matches types (or TRUE?/FALSE? state)} + return: [ any-value!] + test [function! datatype! typeset! block! logic!] + arg [ any-value!] +][ + case* [ + void? temp: maybe test :arg [ + assert [any [void? :arg | false? :arg]] + + ; The test passed but we want to avoid an accidental usage like + ; `if ensure [logic!] some-false-thing [...]` where the test + ; passed but the passthru gets used conditionally. So FALSE? + ; things are converted to void. + ; + () + ] + blank? :temp [ + fail/where [ + "ENSURE expected arg to match" (test) + ] 'arg + ] + true [ + assert [all [true? :temp | :arg = :temp]] + :temp + ] + ] +] diff --git a/src/mezz/base-infix.r b/src/mezz/base-infix.r new file mode 100644 index 0000000000..a1af2522cd --- /dev/null +++ b/src/mezz/base-infix.r @@ -0,0 +1,220 @@ +REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Infix operator symbol definitions" + Rights: { + Copyright 2012 REBOL Technologies + Copyright 2012-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0. + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + When a variable is set to a function value with SET, there is an option + to designate that particular binding as /LOOKBACK. This means that + when the function is invoked through that variable, its first argument + will come from the left hand side--before the invoking WORD!. + + If the function has two parameters, this gives the effect of what + Rebol2 called an "OP!" (infix operator). However, Ren-C's choice to + make this a separate flag to SET means it does not require a new + datatype. Any FUNCTION! of any arity can be used, and it will just + get its first argument from the left, with the rest from the right. + + This file sets up the common "enfixed" operators. + } +] + +; Due to Rebol's complex division of lexical space, operations like `<` have +; needed special rules in the scanner code. These rules may have permitted +; use of the WORD! form, but made the SET-WORD! forms illegal (e.g. `<:`). +; +; Ren-C allows more of these things, but if they were used in this file it +; could not be read by R3-Alpha; which is used to process this file for +; bootstrap. So it brings the operators into existence in %b-init.c in +; the function Add_Lib_Keys_R3Alpha_Cant_Make(). +; +; These hacks are used to get the properly bound WORD!s. Note that FIRST, +; LOAD, INTERN etc. are not in the definition order at this point...so +; PICK MAKE BLOCK! is used. +; +; Note also the unsets for these at the bottom of the file for "cleanliness." + +lt: (pick [<] 1) +lteq: (pick [<=] 1) +gt: (pick [>] 1) +gteq: (pick [>=] 1) +dv: (pick [/] 1) ;-- "slash" is the character #"/" +dvdv: (pick [//] 1) +should-be-empty-tag: (pick [<>] 1) + +right-arrow: bind (pick make block! "->" 1) context-of 'lambda +left-arrow: bind (pick make block! "<-" 1) context-of 'lambda +left-flag: bind (pick make block! "<|" 1) context-of 'lambda +right-flag: bind (pick make block! "|>" 1) context-of 'lambda + + +; While Ren-C has no particular concept of "infix OP!s" as a unique datatype, +; a function which is arity-2 and bound lookback to a variable acts similarly. +; Yet the default is to obey the same lookahead rules as prefix operations +; historically applied. Also, the left hand argument will be evaluated as +; complete an expression as it can. +; +; The annotation is long-term likely a legacy-only property, which +; requests as *minimal* a complete expression on a slot as possible. So if +; you have SOME-INFIX with tight parameters on the left and the right it +; would see: +; +; add 1 2 some-infix add 1 2 + 10 +; +; and interpret it as: +; +; add 1 (2 some-infix add 1 2) + 10 +; +; Whereas if the arguments were not tight, it would see this as: +; +; (add 1 2) some-infix (add 1 2 + 10) +; +; For the moment while the features settle, the operators "in the box" are +; all wrapped to behave with tight left and right arguments. Long term the +; feature is theorized to be unnecessary. +; + ++: enfix tighten :add +-: enfix tighten :subtract +*: enfix tighten :multiply +**: enfix tighten :power + +set/lookback dv tighten :divide +set/lookback dvdv tighten :remainder + +=: enfix tighten :equal? +=?: enfix tighten :same? + +==: enfix tighten :strict-equal? +!=: enfix tighten :not-equal? +!==: enfix tighten :strict-not-equal? + +set/lookback should-be-empty-tag tighten :not-equal? + +set/lookback lt tighten :lesser? +set/lookback lteq tighten :lesser-or-equal? + +set/lookback gt tighten :greater? +set/lookback gteq tighten :greater-or-equal? + +and: enfix tighten :and? +or: enfix tighten :or? +xor: enfix tighten :xor? +nor: enfix tighten :nor? + +nand: enfix tighten :nand? +and*: enfix tighten :and~ +or+: enfix tighten :or~ +xor+: enfix tighten :xor~ + + +; Postfix operator for asking the most existential question of Rebol...is it +; a Rebol value at all? (non-void) +; +; !!! Originally in Rebol2 and R3-Alpha, ? was a synonym for HELP, which seems +; wasteful for the language as a whole when it's easy enough to type HELP. +; Postfix was not initially considered, because there was no ability of +; enfixed operators to force the left hand side of expressions to be as +; maximal as possible. Hence `while [take blk ?] [...]` would ask if blk was +; void, not `take blk`. So it was tried as a prefix operator, which wound +; up looking somewhat junky...now it's being tried as working postfix. + +?: enfix :any-value? + + +; ELSE is an experiment to try and allow `if condition [...] else [...]` +; Its left hand side is a "normal" parameter, not a "tight" one, so that is +; interpreted as `(if condition [...]) else [...]`, as opposed to seen as +; `if condition ([...] else [...])`. It leverages the default behavior of +; IF to return void only when the condition is not taken (if the branch +; happens to evaluate to void, it will become a BLANK!, unless IF/OPT or IF* +; are used) +; +else: enfix redescribe [ + "Evaluate the branch if the left hand side expression is void" +]( + func [ + return: [any-value!] + prior [ any-value!] + branch [ any-value!] + ][ + either void? :prior :branch [:prior] + ] +) + +else*: enfix redescribe [ + "Would be the same as ELSE/OPT, if infix functions dispatched from paths" +]( + func [ + return: [ any-value!] + prior [ any-value!] + branch [ any-value!] + ][ + either* void? :prior :branch [:prior] + ] +) + + +; THEN is a complement to ELSE, running only if its left hand side is not void +; +then: enfix redescribe [ + "Evaluate the branch if the left hand side expression is not void" +]( + func [ + return: [ any-value!] + prior [ any-value!] + branch [ any-value!] + ][ + if any-value? :prior :branch + ] +) + +then*: enfix redescribe [ + "Would be the same as THEN/OPT, if infix functions dispatched from paths" +]( + func [ + return: [ any-value!] + prior [ any-value!] + branch [ any-value!] + ][ + if* any-value? :prior :branch + ] +) + +; Lambdas are experimental quick function generators via a symbol +; +set/lookback right-arrow :lambda +set/lookback left-arrow (specialize :lambda [only: true]) + + +; These usermode expression-barrier like constructs may not necessarily use +; their left-hand arguments...however by being enfixed and not having +; first args, they are able to force complete expressions to their left. + +set/lookback left-flag :left-bar +set/lookback right-flag :right-bar +||: enfix :once-bar + + +; Clean up the words used to hold things that can't be made SET-WORD!s (or +; perhaps even words) in R3-Alpha + +lt: +lteq: +gt: +gteq: +dv: +dvdv: +should-be-empty-tag: +right-arrow: +left-arrow: +left-flag: +right-flag: + () diff --git a/src/mezz/base-series.r b/src/mezz/base-series.r index 4c3693326d..a0059e0871 100644 --- a/src/mezz/base-series.r +++ b/src/mezz/base-series.r @@ -1,47 +1,174 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Boot Base: Series Functions" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Note: { - This code is evaluated just after actions, natives, sysobj, and other lower - levels definitions. This file intializes a minimal working environment - that is used for the rest of the boot. - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Boot Base: Series Functions" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Note: { + This code is evaluated just after actions, natives, sysobj, and other lower + levels definitions. This file intializes a minimal working environment + that is used for the rest of the boot. + } ] -repend: func [ - "Appends a reduced value to a series and returns the series head." - series [series! port! map! gob! object! bitset!] {Series at point to insert (modified)} - value {The value to insert} - /part {Limits to a given length or position} - length [number! series! pair!] - /only {Inserts a series as a series} - /dup {Duplicates the insert a specified number of times} - count [number! pair!] +first: redescribe [ + {Returns the first value of a series.} +]( + specialize 'pick [picker: 1] +) + +first+: func [ + {Return the FIRST of a series then increment the series index.} + return: [ any-value!] + 'word [word!] "Word must refer to a series" + /local prior ][ - apply :append [series reduce :value part length only dup count] + also (pick prior: get word 1) (set word next prior) ] -join: func [ - "Concatenates values." - value "Base value" - rest "Value or block of values" +second: redescribe [ + {Returns the second value of a series.} +]( + specialize 'pick [picker: 2] +) + +third: redescribe [ + {Returns the third value of a series.} +]( + specialize 'pick [picker: 3] +) + +fourth: redescribe [ + {Returns the fourth value of a series.} +]( + specialize 'pick [picker: 4] +) + +fifth: redescribe [ + {Returns the fifth value of a series.} +]( + specialize 'pick [picker: 5] +) + +sixth: redescribe [ + {Returns the sixth value of a series.} +]( + specialize 'pick [picker: 6] +) + +seventh: redescribe [ + {Returns the seventh value of a series.} +]( + specialize 'pick [picker: 7] +) + +eighth: redescribe [ + {Returns the eighth value of a series.} +]( + specialize 'pick [picker: 8] +) + +ninth: redescribe [ + {Returns the ninth value of a series.} +]( + specialize 'pick [picker: 9] +) + +tenth: redescribe [ + {Returns the tenth value of a series.} +]( + specialize 'pick [picker: 10] +) + +last: func [ + {Returns the last value of a series.} + return: [ any-value!] + value [any-series! tuple! gob!] + len ][ - value: either series? :value [copy value] [form :value] - repend value :rest + case* [ ;-- returns , can't use "blankifying" convention + + any-series? value [pick back tail value 1] + tuple? value [pick value length-of value] + gob? value [ + ; The C code effectively used 'pick value t' with: + ; + ; t = GOB_PANE(VAL_GOB(val)) ? GOB_LEN(VAL_GOB(val)) : 0; + ; VAL_GOB_INDEX(val) = 0; + ; + ; Try getting same result with what series does. :-/ + + pick back tail value 1 + ] + 'else [ + ; C code said "let the action throw the error", but by virtue + ; of type checking this case should not happen. + ; + pick value 0 + ] + ] ] -reform: func [ - "Forms a reduced block and returns a string." - value "Value to reduce and form" - ;/with "separator" +; +; !!! End of functions that used to be natives, now mezzanine +; + + +repend: redescribe [ + "APPEND a reduced value to a series." +]( + adapt 'append [ + if set? 'value [ + value: reduce :value + ] + ] +) + + +; REPEND very literally does what it says, which is to reduce the argument +; and call APPEND. This is not necessarily the most useful operation. +; Note that `x: 10 | repend [] 'x` would give you `[x]` in R3-Alpha +; and not 10. The new JOIN (temporarily ADJOIN) and JOIN-OF operations +; can take more license with their behavior if it makes the function more +; convenient, and not be beholden to the behavior that the name REPEND would +; seem to suggest. +; +join: func [ ;-- renamed to ADJOIN in %sys-start.r for user context, temporary + "Concatenates values to the end of a series." + return: [any-series! port! map! gob! object! module! bitset!] + series [any-series! port! map! gob! object! module! bitset!] + value [ any-value!] ][ - form reduce :value + case [ + block? :value [repend series :value] + group? :value [ + fail/where "Can't JOIN a GROUP! onto a series (use APPEND)." + ] + function? :value [ + fail/where "Can't JOIN a FUNCTION! onto a series (use APPEND)." + ] + ] else [ + append/only series :value ;-- paths, words, not in block + ] ] + +join-of: redescribe [ + "Concatenates values to the end of a copy of a series." +]( + adapt 'join [ + series: copy series + ] +) + +append-of: redescribe [ + "APPEND variation that copies the input series first." +]( + adapt 'append [ + series: copy series + ] +) diff --git a/src/mezz/boot-files.r b/src/mezz/boot-files.r index 51dafb6908..c5459209d1 100644 --- a/src/mezz/boot-files.r +++ b/src/mezz/boot-files.r @@ -1,54 +1,56 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Boot: System Contexts" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Note: "Used by tools/make-boot.r" + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Boot: System Contexts" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Note: "Used by tools/make-boot.r" ] ;-- base: low-level boot in lib context: [ - %base-constants.r - %base-funcs.r - %base-series.r - %base-files.r - %base-debug.r - %base-defs.r + %base-constants.r + %base-defs.r + %base-funcs.r + %base-infix.r + %base-series.r + %base-files.r ] ;-- sys: low-level sys context: [ - %sys-base.r - %sys-ports.r - %sys-codec.r ; export to lib! - %sys-load.r - %sys-start.r + %sys-base.r + %sys-ports.r + %sys-codec.r ; export to lib! + %sys-load.r + %sys-start.r ] ;-- lib: mid-level lib context: [ - %mezz-types.r - %mezz-func.r - %mezz-debug.r - %mezz-control.r - %mezz-save.r - %mezz-series.r - %mezz-files.r - %mezz-shell.r - %mezz-math.r - %mezz-help.r ; move dump-obj! - %mezz-banner.r - %mezz-colors.r - %mezz-tail.r + %mezz-types.r + %mezz-func.r + %mezz-debug.r + %mezz-control.r + %mezz-save.r + %mezz-series.r + %mezz-files.r + %mezz-shell.r + %mezz-math.r + %mezz-help.r ; move dump-obj! + %mezz-colors.r + %mezz-tail.r + %mezz-legacy.r ] ;-- protocols: [ - %prot-http.r + ;moved to file-base and loaded by host-start.r + ;%prot-http.r + ;%prot-tls.r ] diff --git a/src/mezz/dial-draw.r b/src/mezz/dial-draw.r deleted file mode 100644 index ec3f785709..0000000000 --- a/src/mezz/dial-draw.r +++ /dev/null @@ -1,125 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL Internal Dialect: Draw Commands (SVG)" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Note: "Modification requires recompiling affected source files." -] - -system/dialects/draw: context [ - - type-spec: [block!] - - ;-- DRAW Commands: - - anti-alias: [logic!] - arc: [;shared with SHAPE command - pair! pair! decimal! decimal! word! - decimal! word! - ] - arrow: [tuple! pair!] - box: [pair! pair! decimal!] - circle: [pair! decimal! decimal!] - clip: [pair! pair! logic!] - curve: [* pair!] ;shared with SHAPE command - effect: [pair! pair! block!] - ellipse: [pair! pair!] - fill-pen: [tuple! image! logic!] - fill-rule: [word!] - gamma: [decimal!] - grad-pen: [word! word! pair! logic! decimal! decimal! decimal! decimal! decimal! block!] - invert-matrix: [] - image: [image! tuple! word! word! integer! integer! integer! integer! * pair!] - image-filter: [word! word! decimal!] - line: [* pair!] ;shared with SHAPE command - line-cap: [word!] - line-join: [word!] - line-pattern: [logic! tuple! * decimal!] - line-width: [decimal! word!] - matrix: [block!] - pen: [tuple! image! logic!] - polygon: [* pair!] - push: [block!] - reset-matrix: [] - rotate: [decimal!] - scale: [decimal! decimal!] - shape: [block!] - skew: [decimal!] - spline: [integer! word! * pair!] - text: [word! pair! pair! block!] - transform: [decimal! pair! decimal! decimal! pair!] - translate: [pair!] - triangle: [pair! pair! pair! tuple! tuple! tuple! decimal!] - - ;-- SHAPE Commands - ;arc is shared - close: [] - curv: [* pair!] - ;curve is shared - hline: [decimal!] - ;line is shared - move: [* pair!] - qcurv: [pair!] - qcurve: [* pair!] - vline: [decimal!] - - ;-- DRAW Options: - - ; FILL-PEN - radial: - conic: - diamond: - linear: - diagonal: - cubic: - - ; FILL-RULE - non-zero: - even-odd: - - ; IMAGE - border: - - ; IMAGE-FILTER - nearest: - bilinear: - bicubic: - gaussian: - resample: - - ; LINE-CAP - butt: - square: - rounded: - - ; LINE-JOIN - miter: - miter-bevel: - round: - bevel: - - ;LINE-WIDTH - fixed: - - ; SPLINE & ARC & TEXT - closed: - - ; GRADIENT - normal: - repeat: - reflect: - - ;SHAPE ARC - large: - sweep: - - ;TEXT - vectorial: - none -] diff --git a/src/mezz/dial-effect.r b/src/mezz/dial-effect.r deleted file mode 100644 index 4adedfe957..0000000000 --- a/src/mezz/dial-effect.r +++ /dev/null @@ -1,66 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL Internal Dialect: Graphic Effects" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Note: "Modification requires recompiling affected source files." -] - -system/dialects/effect: context [ - - type-spec: [] - - add: [image! image!] - alphamul: [image! integer!] - aspect: [image! word! word! decimal!] - blur: [image!] - colorify: [image! tuple! integer!] - colorize: [image! tuple!] - convolve: [image! block! decimal! integer! logic!] - contrast: [image! integer!] - crop: [image! pair! pair!] - difference: [image! image! tuple!] - emboss: [image!] - extend: [image! pair! pair!] - fit: [image! word! word! decimal!] - flip: [image! pair!] - gradcol: [image! pair! tuple! tuple!] - gradient: [image! pair! tuple! tuple!] - gradmul: [image! pair! tuple! tuple!] - grayscale: [image!] - hsv: [image! tuple!] - invert: [image!] - key: [image! tuple!] - luma: [image! integer!] - mix: [image! image!] - multiply: [image! image! tuple! integer!] - reflect: [image! pair!] - rotate: [image! integer!] - shadow: [image! pair! pair! tuple! decimal! word!] - sharpen: [image!] - tile: [image! pair!] - tile-view: [image!] - tint: [image! integer!] - -;not yet -comment { - clip: [] -} - ;-- EFFECTS Options: - - ;SHADOW option - only: - - ;FIT options - nearest: - bilinear: - bicubic: - gaussian: - resample: none -] diff --git a/src/mezz/dial-text.r b/src/mezz/dial-text.r deleted file mode 100644 index 26b1a6ecdb..0000000000 --- a/src/mezz/dial-text.r +++ /dev/null @@ -1,42 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL Internal Dialect: Rich Text" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Note: "Modification requires recompiling affected source files." -] - -system/dialects/text: context [ - - type-spec: [string! tuple!] - - bold: [logic!] - italic: [logic!] - underline: [logic!] - font: [object!] - para: [object!] - size: [integer!] - shadow: [pair! tuple! integer!] - scroll: [pair!] - drop: [integer!] - anti-alias: [logic!] - newline: [] - caret: [object!] - center: [] - left: [] - right: [] - - ; Aliases - b: - i: - u: - nl: - none - -] diff --git a/src/mezz/mezz-banner.r b/src/mezz/mezz-banner.r deleted file mode 100644 index ff12d947f1..0000000000 --- a/src/mezz/mezz-banner.r +++ /dev/null @@ -1,83 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Mezzanine: Startup Banner" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } -] - -make-banner: func [ - "Build startup banner." - fmt /local str star spc a b s -][ - if string? fmt [return fmt] ; aleady built - str: make string! 200 - star: append/dup make string! 74 #"*" 74 - spc: format ["**" 70 "**"] "" - parse fmt [ - some [ - [ - set a string! (s: format ["** " 68 "**"] a) - | '= set a [string! | word! | set-word!] [ - b: - path! (b: get b/1) - | word! (b: get b/1) - | block! (b: reform b/1) - | string! (b: b/1) - ] - (s: format ["** " 11 55 "**"] reduce [a b]) - | '* (s: star) - | '- (s: spc) - ] - (append append str s newline) - ] - ] - str -] - -sys/boot-banner: make-banner [ - * - - - "REBOL 3.0 [Alpha Test]" - - - = Copyright: [system/build/year "REBOL Technologies"] - = "" "All rights reserved." - = Website: "www.REBOL.com" - - - = Version: system/version - = Platform: system/platform - = Build: system/build - = Warning: "For testing purposes only. Use at your own risk." - - - = Language: system/locale/language* - = Locale: system/locale/locale* - = Home: [to-local-file system/options/home] - - - * -] - -sys/boot-help: -{Important notes: - - * Sandbox and security are not available. - * Direct access to TCP HTTP required (no proxies). - * Default web browser must be available. - -Special functions: - - Chat - open DevBase developer forum/BBS - Docs - open DocBase document wiki (web) - Bugs - open CureCode bug database (web) - Demo - run demo launcher (from rebol.com) - Help - show built-in help information - Upgrade - check for newer releases - Changes - what's new about this version (web) -} - -;print make-banner boot-banner halt -;print boot-help diff --git a/src/mezz/mezz-colors.r b/src/mezz/mezz-colors.r index 878f66f7dd..e65b6436de 100644 --- a/src/mezz/mezz-colors.r +++ b/src/mezz/mezz-colors.r @@ -1,63 +1,63 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL View: Standard Colors" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL View: Standard Colors" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } ] -black: 0.0.0 -coal: 64.64.64 -gray: 128.128.128 -pewter: 170.170.170 -silver: 192.192.192 -snow: 240.240.240 -white: 255.255.255 +black: 0.0.0 +coal: 64.64.64 +gray: 128.128.128 +pewter: 170.170.170 +silver: 192.192.192 +snow: 240.240.240 +white: 255.255.255 -blue: 0.0.255 -green: 0.255.0 -red: 255.0.0 +blue: 0.0.255 +green: 0.255.0 +red: 255.0.0 -cyan: 0.255.255 -magenta: 255.0.255 -yellow: 255.255.0 +cyan: 0.255.255 +magenta: 255.0.255 +yellow: 255.255.0 -yello: 255.240.120 ; selection yellow -navy: 0.0.128 -leaf: 0.128.0 -teal: 0.128.128 -maroon: 128.0.0 -olive: 128.128.0 -purple: 128.0.128 +yello: 255.240.120 ; selection yellow +navy: 0.0.128 +leaf: 0.128.0 +teal: 0.128.128 +maroon: 128.0.0 +olive: 128.128.0 +purple: 128.0.128 -orange: 255.150.10 -oldrab: 72.72.16 -brown: 139.69.19 -coffee: 76.26.0 -sienna: 160.82.45 -crimson: 220.20.60 -violet: 72.0.90 -brick: 178.34.34 -pink: 255.164.200 -gold: 255.205.40 -tan: 222.184.135 -beige: 255.228.196 -ivory: 255.255.240 -linen: 250.240.230 -khaki: 179.179.126 -rebolor: 142.128.110 -wheat: 245.222.129 -aqua: 40.100.130 -forest: 0.48.0 -water: 80.108.142 -papaya: 255.80.37 -sky: 164.200.255 -mint: 100.136.116 +orange: 255.150.10 +oldrab: 72.72.16 +brown: 139.69.19 +coffee: 76.26.0 +sienna: 160.82.45 +crimson: 220.20.60 +violet: 72.0.90 +brick: 178.34.34 +pink: 255.164.200 +gold: 255.205.40 +tan: 222.184.135 +beige: 255.228.196 +ivory: 255.255.240 +linen: 250.240.230 +khaki: 179.179.126 +rebolor: 142.128.110 +wheat: 245.222.129 +aqua: 40.100.130 +forest: 0.48.0 +water: 80.108.142 +papaya: 255.80.37 +sky: 164.200.255 +mint: 100.136.116 -reblue: 38.58.108 +reblue: 38.58.108 base-color: 200.200.200 diff --git a/src/mezz/mezz-control.r b/src/mezz/mezz-control.r index 6c04558d65..e804e0db52 100644 --- a/src/mezz/mezz-control.r +++ b/src/mezz/mezz-control.r @@ -1,28 +1,31 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Mezzanine: Control" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Mezzanine: Control" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } ] launch: func [ - {Runs a script as a separate process; return immediately.} - script [file! string! none!] "The name of the script" - /args arg [string! block! none!] "Arguments to the script" - /wait "Wait for the process to terminate" - /local exe + {Runs a script as a separate process; return immediately.} + script [file! string! blank!] "The name of the script" + /args arg [string! block! blank!] "Arguments to the script" + /wait "Wait for the process to terminate" ][ - if file? script [script: to-local-file clean-path script] - exe: to-local-file system/options/boot + if file? script [script: to-local-file clean-path script] + args: reduce [to-local-file system/options/boot script] + unless void? :arg [append args arg] + either wait [call/wait args] [call args] +] - ; Quote everything, just in case it has spaces: - args: to-string reduce [{"} exe {" "} script {" }] - if arg [append args arg] - either wait [call/wait args] [call args] +wrap: func [ + "Evaluates a block, wrapping all set-words as locals." + body [block!] "Block to evaluate" +][ + do bind/copy/set body make object! 0 ] diff --git a/src/mezz/mezz-debug.r b/src/mezz/mezz-debug.r index dafe7e775f..818263fb21 100644 --- a/src/mezz/mezz-debug.r +++ b/src/mezz/mezz-debug.r @@ -1,93 +1,261 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Mezzanine: Debug" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Mezzanine: Debug" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } ] -dt: delta-time: function [ - {Delta-time - returns the time it takes to evaluate the block.} - block [block!] +; !!! Set up ASSERT as having a user-mode implementation matching VERIFY. +; Rather than using the implementation of verify directly, this helps to +; show people a pattern for implementing their own assert. +; +; This should really only be done in debug modes. R3-Alpha did not have the +; idea of running in release mode at all, so there is some code that depends +; on the side-effects of an assert...but it's nice to have the distinction +; available so that people can add tests. +; +; This ASSERT has basic features of being able to treat issues as instructions +; for enablement. By default, if an issue label is used, then an assert +; will not run unless e.g. `assert/meta [#heavy-checks on]` is performed. +; The same is available for TAG!, except the default is that a tagged assert +; will be run unless disabled. +; +; In order to facilitate reuse and chaining, the assert can be told not to +; actually report its error, but to give the failing assert back as a block! +; +; As a special enhancement, there is an understanding of the specific +; construct of `assert [x = blah blah blah]` which will report what x +; actually was. This is only an example of what is possible with a true +; assert or logging dialect. + +live-asserts-map: make map! [] + +assert-debug: function [ + return: [ any-value!] + conditions [logic! block!] + {Conditions to check (or meta instructions if /META)} + /quiet + {Return void on success or a BLOCK! of the failure condition if failed} + /meta + {Block is enablement and disablement, e.g. [#heavy-checks on]} +][ + if meta [ + rules: [any [ + any bar! + set option: [issue! | tag!] + set value: [word! | logic!] + ( + if word? value [value: get value] + + unless logic? value [ + fail ["switch must be LOGIC! true or false for" option] + ] + + either value [ + either tag? option [ + remove/map live-asserts-map option ; enable implicit + ][ + live-asserts-map/(option): true ; must be explicit + ] + ][ + either issue? option [ + remove/map live-asserts-map option ; disable implicit + ][ + live-asserts-map/(option): false ; must be explicit + ] + ] + ) + ]] + + unless parse conditions rules [ + fail [ + "/META options must be pairs, e.g. [#heavy-checks on]" + conditions + ] + ] + return () + ] + + failure-helper: procedure [ + expr [logic! block!] + {The failing expression (or just FALSE if a LOGIC!)} + bad-result [ any-value!] + {What the FALSE? or void that triggered failure was} + return + ][ + if quiet [ + ; + ; Due to return this imports the return from ASSERT-DEBUG + ; overall. This result is not going to be very useful for a + ; plain FALSE return, and a proper logging mechanism would need + ; some information about the source location of failure. + ; + return expr ;-- due to ` return` + ] + + fail [ + "Assertion condition returned" + (case [ + (not set? 'bad-result) "void" + | + (blank? bad-result) "blank" + | + (false? bad-result) "false" + ]) + ":" + expr + ] + ] + + either logic? conditions [ + if not conditions [ + failure-helper false false + ] + ][ + ; Otherwise it's a block! + active: true + until [tail? conditions] [ + if option: maybe [issue! tag!] :conditions/1 [ + unless active: select live-asserts-map option [ + ; + ; if not found in the map, go with default behavior. + ; (disabled for #named tests, enabled for ) + ; + active: tag? option + ] + ] + + result: do/next conditions quote pos: + if active and any [not set? 'result | not :result] [ + failure-helper (copy/part conditions pos) :result + ] + + conditions: pos ;-- move expression position and continue + + ; including BAR!s in the failure report looks messy + while [bar? :conditions/1] [conditions: next conditions] + ] + ] + + return if quiet [true] ;-- void is return default +] + + +; !!! If a debug mode were offered, you'd want to be able to put back ASSERT +; in such a way as to cost basically nothing. +; +; !!! Note there is a layering problem, in that if people make a habit of +; hijacking ASSERT, and it's used in lower layer implementations, it could +; recurse. e.g. if file I/O writing used ASSERT, and you added a logging +; feature via HIJACK that wrote to a file. Implications of being able to +; override a system-wide assert in this way should be examined, and perhaps +; copies of the function made at layer boundaries. +; +native-assert: hijack 'assert :assert-debug + + +delta-time: function [ + {Delta-time - returns the time it takes to evaluate the block.} + block [block!] ][ - start: stats/timer - do block - stats/timer - start + start: stats/timer + do block + stats/timer - start ] -dp: delta-profile: func [ - {Delta-profile of running a specific block.} - block [block!] - /local start end +delta-profile: func [ + {Delta-profile of running a specific block.} + block [block!] + /local start end ][ - start: values-of stats/profile - do block - end: values-of stats/profile - foreach num start [ - change end end/1 - num - end: next end - ] - start: make system/standard/stats [] - set start head end - start + start: values-of stats/profile + do block + end: values-of stats/profile + for-each num start [ + change end end/1 - num + end: next end + ] + start: construct system/standard/stats [] + set start head end + start ] speed?: function [ - "Returns approximate speed benchmarks [eval cpu memory file-io]." - /no-io "Skip the I/O test" - /times "Show time for each test" + "Returns approximate speed benchmarks [eval cpu memory file-io]." + /no-io "Skip the I/O test" + /times "Show time for each test" +][ + result: copy [] + for-each block [ + [ + loop 100'000 [ + ; measure more than just loop func + ; typical load: 1 set, 2 data, 1 op, 4 trivial funcs + x: 1 * index-of back next "x" + x: 1 * index-of back next "x" + x: 1 * index-of back next "x" + x: 1 * index-of back next "x" + ] + calc: [100'000 / secs / 100] ; arbitrary calc + ][ + tmp: make binary! 500'000 + insert/dup tmp "abcdefghij" 50000 + loop 10 [ + random tmp + decompress compress tmp + ] + calc: [(length-of tmp) * 10 / secs / 1900] + ][ + repeat n 40 [ + change/dup tmp to-char n 500'000 + ] + calc: [(length-of tmp) * 40 / secs / 1024 / 1024] + ][ + unless no-io [ + write file: %tmp-junk.txt "" ; force security request before timer + tmp: make string! 32000 * 5 + insert/dup tmp "test^/" 32000 + loop 100 [ + write file tmp + read file + ] + delete file + calc: [(length-of tmp) * 100 * 2 / secs / 1024 / 1024] + ] + ] + ][ + secs: now/precise + calc: 0 + recycle + do block + secs: to decimal! difference now/precise secs + append result to integer! do calc + if times [append result secs] + ] + result +] + +net-log: func [txt /C /S][txt] + +net-trace: procedure [ + "Switch between using a no-op or a print operation for net-tracing" + val [logic!] ][ - result: copy [] - foreach block [ - [ - loop 100'000 [ - ; measure more than just loop func - ; typical load: 1 set, 2 data, 1 op, 4 trivial funcs - x: 1 * index? back next "x" - x: 1 * index? back next "x" - x: 1 * index? back next "x" - x: 1 * index? back next "x" - ] - calc: [100'000 / secs / 100] ; arbitrary calc - ][ - tmp: make binary! 500'000 - insert/dup tmp "abcdefghij" 50000 - loop 10 [ - random tmp - decompress compress tmp - ] - calc: [(length? tmp) * 10 / secs / 1900] - ][ - repeat n 40 [ - change/dup tmp to-char n 500'000 - ] - calc: [(length? tmp) * 40 / secs / 1024 / 1024] - ][ - unless no-io [ - write file: %tmp-junk.txt "" ; force security request before timer - tmp: make string! 32000 * 5 - insert/dup tmp "test^/" 32000 - loop 100 [ - write file tmp - read file - ] - delete file - calc: [(length? tmp) * 100 * 2 / secs / 1024 / 1024] - ] - ] - ][ - secs: now/precise - calc: 0 - recycle - do block - secs: to decimal! difference now/precise secs - append result to integer! do calc - if times [append result secs] - ] - result + either val [ + hijack 'net-log func [txt /C /S][ + if c [print/only "C: "] + if s [print/only "S: "] + print/eval txt + txt + ] + print "Net-trace is now on" + ][ + hijack 'net-log func [txt /C /S][txt] + ] ] diff --git a/src/mezz/mezz-files.r b/src/mezz/mezz-files.r index 570d293980..475e5725c1 100644 --- a/src/mezz/mezz-files.r +++ b/src/mezz/mezz-files.r @@ -1,193 +1,274 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Mezzanine: File Related" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Mezzanine: File Related" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } ] -clean-path: func [ - "Returns new directory path with //, . and .. processed." - file [file! url! string!] - /only "Do not prepend current directory" - /dir "Add a trailing / if missing" - /local out cnt f + +clean-path: function [ + "Returns new directory path with `//` `.` and `..` processed." + file [file! url! string!] + /only + "Do not prepend current directory" + /dir + "Add a trailing / if missing" ][ - case [ - any [only not file? file] [file: copy file] - #"/" = first file [ - ++ file - out: next what-dir - while [ - all [ - #"/" = first file - f: find/tail out #"/" - ] - ][ - ++ file - out: f - ] - file: append clear out file - ] - file: append what-dir file - ] - - if all [dir not dir? file] [append file #"/"] - - out: make file length? file ; same datatype - cnt: 0 ; back dir counter - - parse/all reverse file [ - some [ - ;pp: (?? pp) - "../" (++ cnt) - | "./" - | #"/" (if any [not file? file #"/" <> last out] [append out #"/"]) - | copy f [to #"/" | to end] ( - either cnt > 0 [ - -- cnt - ][ - unless find ["" "." ".."] to string! f [append out f] - ] - ) - ] - ] - - if all [#"/" = last out #"/" <> last file] [remove back tail out] - reverse out + file: case [ + any [only | not file? file] [ + copy file + ] + #"/" = first file [ + file: next file + out: next what-dir + while [ + all [ + #"/" = first file + f: find/tail out #"/" + ] + ][ + file: next file + out: f + ] + append clear out file + ] + ] else [ + append what-dir file + ] + + if all [dir | not dir? file] [append file #"/"] + + out: make type-of file length-of file ; same datatype + cnt: 0 ; back dir counter + + parse reverse file [ + some [ + "../" (++ cnt) + | "./" + | #"/" ( + if any [not file? file | #"/" <> last out] [append out #"/"] + ) + | copy f [to #"/" | to end] ( + either cnt > 0 [ + -- cnt + ][ + unless find ["" "." ".."] as string! f [append out f] + ] + ) + ] + ] + + if all [#"/" = last out | #"/" <> last file] [remove back tail out] + reverse out ] + input: function [ - {Inputs a string from the console. New-line character is removed.} -; /hide "Mask input with a * character" + {Inputs a string from the console. New-line character is removed.} + return: [string!] +; /hide +; "Mask input with a * character" ][ - if any [ - not port? system/ports/input - not open? system/ports/input - ][ - system/ports/input: open [scheme: 'console] - ] - line: to-string read system/ports/input - trim/with line newline - line + if any [ + not port? system/ports/input + not open? system/ports/input + ][ + system/ports/input: open [scheme: 'console] + ] + + line: to-string read system/ports/input + trim/with line newline + line ] -ask: func [ - "Ask the user for input." - question [series!] "Prompt to user" - /hide "mask input with *" + +ask: function [ + "Ask the user for input." + return: [string!] + question [any-series!] + "Prompt to user" + /hide + "mask input with *" ][ - prin question - trim either hide [input/hide] [input] + print/only either block? question [spaced question] [question] + trim either hide [input/hide] [input] ] -confirm: func [ - "Confirms a user choice." - question [series!] "Prompt to user" - /with choices [string! block!] - /local response + +confirm: function [ + "Confirms a user choice." + return: [logic!] + question [any-series!] + "Prompt to user" + /with + choices [string! block!] ][ - if all [block? choices 2 < length? choices] [ - cause-error 'script 'invalid-arg mold choices - ] - response: ask question - unless with [choices: [["y" "yes"] ["n" "no"]]] - case [ ; returned - empty? choices [true] - string? choices [if find/match response choices [true]] - 2 > length? choices [if find/match response first choices [true]] - find first choices response [true] - find second choices response [false] - ] + if all [block? choices | 2 < length-of choices] [ + cause-error 'script 'invalid-arg mold choices + ] + + response: ask question + + unless with [choices: [["y" "yes"] ["n" "no"]]] + + case [ + empty? choices [true] + string? choices [find?/match response choices] + 2 > length-of choices [find?/match response first choices] + find? first choices response [true] + find? second choices response [false] + ] ] -list-dir: func [ - "Print contents of a directory (ls)." - 'path [file! word! path! string! unset!] "Accepts %file, :variables, and just words (as dirs)" - /l "Line of info format" - /f "Files only" - /d "Dirs only" -; /t "Time order" - /r "Recursive" - /i indent - /local files save-dir info + +list-dir: procedure [ + "Print contents of a directory (ls)." + 'path [ file! word! path! string!] + "Accepts %file, :variables, and just words (as dirs)" + /l "Line of info format" + /f "Files only" + /d "Dirs only" +; /t "Time order" + /r "Recursive" + /i "Indent" + indent ][ - save-dir: what-dir - switch type?/word :path [ - unset! [] ; Stay here - file! [change-dir path] - string! [change-dir to-rebol-file path] - word! path! [change-dir to-file path] - ] - if r [l: true] - unless l [l: make string! 62] ; approx width - unless indent [indent: ""] - files: attempt [read %./] - if not files [print ["Not found:" :path] change-dir save-dir exit] - foreach file files [ - if any [ - all [f dir? file] - all [d not dir? file] - ][continue] - either string? l [ - append l file - append/dup l #" " 15 - remainder length? l 15 - if greater? length? l 60 [print l clear l] - ][ - info: get query file - change info second split-path info/1 - printf [indent 16 -8 #" " 24 #" " 6] info - if all [r dir? file] [ - list-dir/l/r/i :file join indent " " - ] - ] - ] - if all [string? l not empty? l] [print l] - change-dir save-dir - exit + indent: default "" + + save-dir: what-dir + + unless file? save-dir [ + fail ["No directory listing protocol registered for" save-dir] + ] + + switch type-of :path [ + _ [] ; Stay here + :file! [change-dir path] + :string! [change-dir to-rebol-file path] + :word! :path! [change-dir to-file path] + ] + + if r [l: true] + unless l [l: make string! 62] ; approx width + + if not (files: attempt [read %./]) [ + print ["Not found:" :path] + change-dir save-dir + leave + ] + + for-each file files [ + if any [ + all [f | dir? file] + all [d | not dir? file] + ][continue] + + either string? l [ + append l file + append/dup l #" " 15 - remainder length-of l 15 + if greater? length-of l 60 [print l clear l] + ][ + info: get query file + change info second split-path info/1 + printf [indent 16 -8 #" " 24 #" " 6] info + if all [r | dir? file] [ + list-dir/l/r/i :file join-of indent " " + ] + ] + ] + + if all [string? l | not empty? l] [print l] + + change-dir save-dir ] -undirize: func [ - {Returns a copy of the path with any trailing "/" removed.} - path [file! string! url!] + +undirize: function [ + {Returns a copy of the path with any trailing "/" removed.} + return: [file! string! url!] + path [file! string! url!] ][ - path: copy path - if #"/" = last path [clear back tail path] - path + path: copy path + if #"/" = last path [clear back tail path] + path ] -in-dir: func [ - "Evaluate a block while in a directory." - dir [file!] "Directory to change to (changed back after)" - block [block!] "Block to evaluate" - /local old-dir -] [ - old-dir: what-dir - change-dir dir - also do block change-dir old-dir -] ; You don't want the block to be done if the change-dir fails, for safety. - -to-relative-file: func [ - "Returns the relative portion of a file if in a subdirectory, or the original if not." - file [file! string!] "File to check (local if string!)" - /no-copy "Don't copy, just reference" - /as-rebol "Convert to REBOL-style filename if not" - /as-local "Convert to local-style filename if not" -] [ - either string? file [ ; Local file - ; Note: to-local-file drops trailing / in R2, not in R3 - ; if tmp: find/match file to-local-file what-dir [file: next tmp] - file: any [find/match file to-local-file what-dir file] - if as-rebol [file: to-rebol-file file no-copy: true] - ] [ - file: any [find/match file what-dir file] - if as-local [file: to-local-file file no-copy: true] - ] - unless no-copy [file: copy file] - file + +in-dir: function [ + "Evaluate a block while in a directory." + return: [ any-value!] + dir [file!] + "Directory to change to (changed back after)" + block [block!] + "Block to evaluate" +][ + old-dir: what-dir + change-dir dir + + ; You don't want the block to be done if the change-dir fails, for safety. + + also do block change-dir old-dir +] + + +to-relative-file: function [ + "Returns relative portion of a file if in subdirectory, original if not." + return: [file! string!] + file [file! string!] + "File to check (local if string!)" + /no-copy + "Don't copy, just reference" + /as-rebol + "Convert to REBOL-style filename if not" + /as-local + "Convert to local-style filename if not" +][ + either string? file [ ; Local file + ; Note: to-local-file drops trailing / in R2, not in R3 + ; if tmp: find/match file to-local-file what-dir [file: next tmp] + file: any [find/match file to-local-file what-dir | file] + if as-rebol [ + file: to-rebol-file file + no-copy: true + ] + ][ + file: any [find/match file what-dir | file] + if as-local [ + file: to-local-file file + no-copy: true + ] + ] + + unless no-copy [file: copy file] + + file +] + + +; !!! Probably should not be in the "core" mezzanine. But to make it easier +; for people who seem to be unable to let go of the tabbing/CR past, this +; helps them turn their files into sane ones :-/ +; +; http://www.rebol.com/r3/docs/concepts/scripts-style.html#section-4 +; +detab-file: procedure [ + "detabs a disk file" + filename [file!] +][ + write filename detab to string! read filename +] + +; temporary location +set-net: procedure [ + {sets the system/user/identity email smtp pop3 esmtp-usr esmtp-pass fqdn} + bl [block!] +][ + if 6 <> length-of bl [fail "Needs all 6 parameters for set-net"] + set words-of system/user/identity bl ] diff --git a/src/mezz/mezz-func.r b/src/mezz/mezz-func.r index f1c65e47fc..3996882ca3 100644 --- a/src/mezz/mezz-func.r +++ b/src/mezz/mezz-func.r @@ -1,80 +1,143 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Mezzanine: Function Helpers" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Mezzanine: Function Helpers" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } ] -clos: func [ - {Defines a closure function.} - spec [block!] {Help string (opt) followed by arg words (and opt type and string)} - body [block!] {The body block of the function} -][ - make closure! copy/deep reduce [spec body] -] -closure: func [ - {Defines a closure function with all set-words as locals.} - spec [block!] {Help string (opt) followed by arg words (and opt type and string)} - body [block!] {The body block of the function} - /with {Define or use a persistent object (self)} - object [object! block! map!] {The object or spec} - /extern words [block!] {These words are not local} +map: func [ + {Make a map value (hashed associative block).} + val ][ - ; Copy the spec and add /local to the end if not found - unless find spec: copy/deep spec /local [append spec [ - /local ; In a block so the generated source gets the newlines - ]] - ; Make a full copy of the body, to allow reuse of the original - body: copy/deep body - ; Collect all set-words in the body as words to be used as locals, and add - ; them to the spec. Don't include the words already in the spec or object. - insert find/tail spec /local collect-words/deep/set/ignore body either with [ - ; Make our own local object if a premade one is not provided - unless object? object [object: make object! object] - bind body object ; Bind any object words found in the body - ; Ignore the words in the spec and those in the object. The spec needs - ; to be copied since the object words shouldn't be added to the locals. - append append append copy spec 'self words-of object words ; ignore 'self too - ][ - ; Don't include the words in the spec, or any extern words. - either extern [append copy spec words] [spec] - ] - make closure! reduce [spec body] + make map! :val ] -has: func [ - {A shortcut to define a function that has local variables but no arguments.} - vars [block!] {List of words that are local to the function} - body [block!] {The body block of the function} -][ - make function! reduce [head insert copy/deep vars /local copy/deep body] -] -context: func [ - {Defines a unique object.} - blk [block!] {Object words and values (modified)} +body-of: function [ + value [any-value!] ][ - make object! blk -] + body: reflect :value 'body + unless function? :value [return body] -map: func [ - {Make a map value (hashed associative block).} - val -][ - make map! :val -] + ; FUNCTION! has a number of special tricks for its implementation, where + ; the body information is not what you could just pass to MAKE FUNCTION! + ; and get equivalent behavior. The goal of this usermode code is to + ; build simulated equivalants that *could* be passed to MAKE FUNCTION!. + ; + switch func-class-of :value [ + 1 [ + ; Native. The actual "body of" is a function pointer, which + ; is currently rendered as a HANDLE!. + ; + ; !!! Near-term-future feature: native bodies able to provide + ; equivalent user-mode code, if provided via native/body -task: func [ - {Creates a task.} - spec [block!] {Name or spec block} - body [block!] {The body block of the task} -][ - make task! copy/deep reduce [spec body] + remark: [ + comment {Native code, implemented in C (this body is fake)} + ] + + either block? body [ + body: compose [ + (remark) + (body) + ] + ][ + body: compose [ + (remark) + do-native (body) <...> + ] + ] + + body + ] + + 2 [ + ; Usermode-written function (like this one is!) via MAKE FUNCTION! + ; so just give back the body as-is. + ; + ; Note: The body given back here may be fake if it's a PROC or + ; FUNC...though that level of fakeness is more tightly integrated + ; into the dispatch than the other fakes here. It's needed for + ; efficient definitional returns, but pains were taken to ensure + ; that this could indeed be done by equivalent user mode code. + + body + ] + + 3 [ + ; Action. Currently action bodies are numbers, because the + ; `switch` statement in C that implements type-specific actions + ; isn't able to switch on the function's identity (via paramlist) + + compose [ + comment {Type-Specific action method (internal, ATM)} + do-action (body) <...> + ] + ] + + 4 [ + ; Command. These are a historical extension mechanism, used to + ; make native routines that are built with the extension API + ; (as opposed to Ren-C). + + compose [ + comment {Rebol Lib (RL_Api) Extension (made by make-command)} + do-command (body) <...> + ] + ] + + 5 [ + ; FFI Routine...likely to become user function. + + compose [ + comment {FFI Bridge to C Function (via make-routine)} + do-routine (body) <...> + ] + ] + + 6 [ + ; FFI Callback...likely to be folded in as an internal mechanism + ; in the FFI for calling ordinary user functions. + + append copy [ + comment {FFI C thunk for Rebol Function (via make-callback)} + ] body + ] + + 7 [ + ; Function Specialization. These are partially (or fully) filled + ; frames that EVAL automatically, by being stuffed in FUNCTION! + ; + ; Currently the low-level spec of these functions is just a single + ; element series of the name of what is specialized. + ; + ; !!! It would be possible to inject commentary information on + ; the specialized fields for what they meant, by taking it that + ; from the original function's spec and putting it inline with + ; the specialization assignment. + + spec-with-word: reflect :value 'spec + assert [word? first spec-with-word] + + compose [ + comment ( + spaced [ + {Specialization of} + first spec-with-word + {(this body is fake)} + ] + ) + eval (body) <...> + ] + ] + + (fail "Unknown function class") + ] ] diff --git a/src/mezz/mezz-help.r b/src/mezz/mezz-help.r index e14ca7c599..7c4b7ad5b3 100644 --- a/src/mezz/mezz-help.r +++ b/src/mezz/mezz-help.r @@ -1,512 +1,735 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Mezzanine: Help" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Mezzanine: Help" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } ] -; MOVE THIS INTERNAL FUNC: + +; !!! R3-Alpha labeled this "MOVE THIS INTERNAL FUNC" but it is actually used +; to search for patterns in HELP when you type in something that isn't bound, +; so it uses that as a string pattern. Review how to better factor that +; (as part of a general help review) +; dump-obj: function [ - "Returns a block of information about an object or port." - obj [object! port!] - /match "Include only those that match a string or datatype" pat + "Returns a block of information about an object or port." + obj [object! port!] + /match "Include only those that match a string or datatype" pat ][ - clip-str: func [str] [ - ; Keep string to one line. - trim/lines str - if (length? str) > 45 [str: append copy/part str 45 "..."] - str - ] - - form-val: func [val] [ - ; Form a limited string from the value provided. - if any-block? :val [return reform ["length:" length? val]] - if image? :val [return reform ["size:" val/size]] - if datatype? :val [return get in spec-of val 'title] - if any-function? :val [ - return clip-str any [title-of :val mold spec-of :val] - ] - if object? :val [val: words-of val] - if typeset? :val [val: to-block val] - if port? :val [val: reduce [val/spec/title val/spec/ref]] - if gob? :val [return reform ["offset:" val/offset "size:" val/size]] - clip-str mold :val - ] - - form-pad: func [val size] [ - ; Form a value with fixed size (space padding follows). - val: form val - insert/dup tail val #" " size - length? val - val - ] - - ; Search for matching strings: - out: copy [] - wild: all [string? pat find pat "*"] - - foreach [word val] obj [ - type: type?/word :val - str: either find [function! closure! native! action! op! object!] type [ - reform [word mold spec-of :val words-of :val] - ][ - form word - ] - if any [ - not match - all [ - not unset? :val - either string? :pat [ - either wild [ - tail? any [find/any/match str pat pat] - ][ - find str pat - ] - ][ - all [ - datatype? get :pat - type = :pat - ] - ] - ] - ][ - str: form-pad word 15 - append str #" " - append str form-pad type 10 - ((length? str) - 15) - append out reform [ - " " str - if type <> 'unset! [form-val :val] - newline - ] - ] - ] - out + clip-str: func [str] [ + ; Keep string to one line. + trim/lines str + if (length str) > 48 [str: append copy/part str 45 "..."] + str + ] + + form-val: func [val [any-value!]] [ + ; Form a limited string from the value provided. + if any-block? :val [return spaced ["length:" length-of val]] + if image? :val [return spaced ["size:" val/size]] + if datatype? :val [return form val] + if function? :val [ + return clip-str any [title-of :val mold spec-of :val] + ] + if object? :val [val: words-of val] + if typeset? :val [val: to-block val] + if port? :val [val: reduce [val/spec/title val/spec/ref]] + if gob? :val [return spaced ["offset:" val/offset "size:" val/size]] + clip-str mold :val + ] + + form-pad: func [val size] [ + ; Form a value with fixed size (space padding follows). + val: form val + insert/dup tail val #" " size - length-of val + val + ] + + ; Search for matching strings: + collect [ + wild: all [set? 'pat | string? pat | find pat "*"] + + for-each [word val] obj [ + type: type-of :val + + str: either maybe [function! object!] :type [ + spaced [word _ mold spec-of :val _ words-of :val] + ][ + form word + ] + + if any [ + not match + all [ + not void? :val + either string? :pat [ + either wild [ + tail? any [find/any/match str pat pat] + ][ + find str pat + ] + ][ + all [ + datatype? get :pat + type = get :pat + ] + ] + ] + ][ + str: form-pad word 15 + append str #" " + append str form-pad type 10 - ((length-of str) - 15) + keep spaced [ + " " str + if type [form-val :val] + newline + ] + ] + ] + ] ] -?: help: func [ - "Prints information about words and values." - 'word [any-type!] - /doc "Open web browser to related documentation." - /local value args item type-name types tmp print-args + +dump: proc [ + {Show the name of a value (or block of expressions) with the value itself} + :value + + dump-one dump-val clip-string item ][ - if unset? get/any 'word [ - print trim/auto { - Use HELP or ? to see built-in info: + clip-string: function [str len][ + either len < length-of str [ + delimit [ copy/part str len - 3 "..." ] _ + ][ + str + ] + ] + + dump-val: function [val][ + either object? val [ + unspaced [ + "make object! [" | + dump-obj val | "]" + ] + ][ + clip-string mold val system/options/dump-size + ] + ] + + dump-one: proc [item][ + case [ + string? item [ + print ["---" clip-string item system/options/dump-size "---"] ;-- label it + ] + + word? item [ + print [to set-word! item "=>" dump-val get item] + ] + + path? item [ + print [to set-path! item "=>" dump-val get item] + ] + + group? item [ + trap/with [ + print [item "=>" mold eval item] + ] func [error] [ + print [item "=!!!=>" mold error] + ] + ] + ] else [ + fail [ + "Item not WORD!, PATH!, or GROUP! in DUMP." item + ] + ] + ] + + either block? value [ + for-each item value [dump-one item] + ][ + dump-one value + ] +] - help insert - ? insert - To search within the system, use quotes: +spec-of: function [ + {Generate a block which could be used as a "spec block" from a function.} - ? "insert" + value [function!] +][ + meta: maybe object! meta-of :value + + specializee: maybe function! select meta 'specializee + adaptee: maybe function! select meta 'specializee + original-meta: maybe object! any [ + all [:specializee | meta-of :specializee] + all [:adaptee | meta-of :adaptee] + ] + + spec: copy [] + + if description: maybe string! any [ + select meta 'description + select original-meta 'description + ][ + append spec description + new-line back spec true + ] + + return-type: maybe block! any [ + select meta 'return-type + select original-meta 'return-type + ] + return-note: maybe string! any [ + select meta 'return-note + select original-meta 'return-note + ] + if return-type or return-note [ + append spec quote return: + if return-type [append/only spec return-type] + if return-note [append spec return-note] + ] + + types: maybe frame! any [ + select meta 'parameter-types + select original-meta 'parameter-types + ] + notes: maybe frame! any [ + select meta 'parameter-notes + select original-meta 'parameter-notes + ] + + for-each param words-of :value [ + append spec param + if any [type: select types param] [append/only spec type] + if any [note: select notes param] [append spec note] + ] + + return spec +] - To browse online web documents: - help/doc insert +title-of: function [ + {Extracts a summary of a value's purpose from its "meta" information.} - To view words and values of a context or object: + value [any-value!] +][ + switch type-of :value [ + :function! [ + all [ + object? meta: meta-of :value + string? description: select meta 'description + copy description + ] + ] + + :datatype! [ + spec: spec-of value + assert [string? spec] ;-- !!! Consider simplifying "type specs" + spec/title + ] + + (blank) + ] +] - ? lib - the runtime library - ? self - your user context - ? system - the system object - ? system/options - special settings +browse: procedure [ + "stub function for browse* in extensions/process/ext-process-init.reb" + location [url! file! blank!] +][ + print "Browse needs redefining" +] - To see all words of a specific datatype: +help: procedure [ + "Prints information about words and values (if no args, general help)." + 'word [ any-value!] + /doc "Open web browser to related documentation." +][ + if not set? 'word [ + ; + ; Was just `>> help` or `do [help]` or similar. + ; Print out generic help message. + ; + print trim/auto copy { + Use HELP to see built-in info: - ? native! - ? function! - ? datatype! + help insert - Other debug functions: + To search within the system, use quotes: - docs - open browser to web documentation - ?? - display a variable and its value - probe - print a value (molded) - source func - show source code of func - trace - trace evaluation steps - what - show a list of known functions - why? - explain more about last error (via web) + help "insert" - Other information: + To browse online web documents: - chat - open DevBase developer forum/BBS - docs - open DocBase document wiki website - bugs - open CureCore bug database website - demo - run demo launcher (from rebol.com) - about - see general product info - upgrade - check for newer versions - changes - show changes for recent version - install - install (when applicable) - license - show user license - usage - program cmd line options - } - exit - ] + help/doc insert -; Word completion: -; -; The command line can perform word -; completion. Type a few chars and press TAB -; to complete the word. If nothing happens, -; there may be more than one word that -; matches. Press TAB again to see choices. -; -; Local filenames can also be completed. -; Begin the filename with a %. -; -; Other useful functions: -; -; about - see general product info -; usage - view program options -; license - show terms of user license -; source func - view source of a function -; upgrade - updates your copy of REBOL -; -; More information: http://www.rebol.com/docs.html - - ; If arg is an undefined word, just make it into a string: - if all [word? :word not value? :word] [word: mold :word] - - ; Open the web page for it? - if all [ - doc - word? :word - any [any-function? get :word datatype? get :word] - ][ - item: form :word - either any-function? get :word [ - foreach [a b] [ ; need a better method ! - "!" "-ex" - "?" "-q" - "*" "-mul" - "+" "-plu" - "/" "-div" - "=" "-eq" - "<" "-lt" - ">" "-gt" - ][replace/all item a b] - tmp: http://www.rebol.com/r3/docs/functions/ - ][ - tmp: http://www.rebol.com/r3/docs/datatypes/ - remove back tail item ; the ! - ] - browse join tmp [item ".html"] - ] - - ; If arg is a string or datatype! word, search the system: - if any [string? :word all [word? :word datatype? get :word]] [ - if all [word? :word datatype? get :word] [ - value: spec-of get :word - print [ - mold :word "is a datatype" newline - "It is defined as" either find "aeiou" first value/title ["an"] ["a"] value/title newline - "It is of the general type" value/type newline - ] - ] - if any [:word = 'unset! not value? :word] [exit] - types: dump-obj/match lib :word - sort types - if not empty? types [ - print ["Found these related words:" newline types] - exit - ] - if all [word? :word datatype? get :word] [ - print ["No values defined for" word] - exit - ] - print ["No information on" word] - exit - ] - - ; Print type name with proper singular article: - type-name: func [value] [ - value: mold type? :value - clear back tail value - join either find "aeiou" first value ["an "]["a "] value - ] - - ; Print literal values: - if not any [word? :word path? :word][ - print [mold :word "is" type-name :word] - exit - ] - - ; Get value (may be a function, so handle with ":") - either path? :word [ - if any [ - error? set/any 'value try [get :word] ;try reduce [to-get-path word] - not value? 'value - ][ - print ["No information on" word "(path has no value)"] - exit - ] - ][ - value: get :word - ] - unless any-function? :value [ - prin [uppercase mold word "is" type-name :value "of value: "] - print either any [object? value port? value] [print "" dump-obj value][mold :value] - exit - ] - - ; Must be a function... - ; If it has refinements, strip them: - ;if path? :word [word: first :word] - - ;-- Print info about function: - prin "USAGE:^/^-" - - args: words-of :value - clear find args /local - either op? :value [ - print [args/1 word args/2] - ][ - print [uppercase mold word args] - ] - - print ajoin [ - newline "DESCRIPTION:" newline - tab any [title-of :value "(undocumented)"] newline - tab uppercase mold word " is " type-name :value " value." - ] - - unless args: find spec-of :value any-word! [exit] - clear find args /local - - ;-- Print arg lists: - print-args: func [label list /extra /local str] [ - if empty? list [exit] - print label - foreach arg list [ - str: ajoin [tab arg/1] - if all [extra word? arg/1] [insert str tab] - if arg/2 [append append str " -- " arg/2] - if all [arg/3 not refinement? arg/1] [ - repend str [" (" arg/3 ")"] - ] - print str - ] - ] - - use [argl refl ref b v] [ - argl: copy [] - refl: copy [] - ref: b: v: none - - parse args [ - any [string! | block!] - any [ - set word [refinement! (ref: true) | any-word!] - (append/only either ref [refl][argl] b: reduce [word none none]) - any [set v block! (b/3: v) | set v string! (b/2: v)] - ] - ] - - print-args "^/ARGUMENTS:" argl - print-args/extra "^/REFINEMENTS:" refl - ] - - exit ; return unset -] + To view words and values of a context or object: -about: func [ - "Information about REBOL" -][ - print make-banner sys/boot-banner -] + help lib - the runtime library + help self - your user context + help system - the system object + help system/options - special settings -; --cgi (-c) Load CGI utiliy module and modes + To see all words of a specific datatype: -usage: func [ - "Prints command-line arguments." -][ - print trim/auto { - Command line usage: + help object! + help function! + help datatype! - REBOL |options| |script| |arguments| + Other debug functions: - Standard options: + docs - open browser to web documentation + dump - display a variable and its value + probe - print a value (molded) + source func - show source code of func + trace - trace evaluation steps + what - show a list of known functions + why? - explain more about last error (via web) - --args data Explicit arguments to script (quoted) - --do expr Evaluate expression (quoted) - --help (-?) Display this usage information - --script file Explicit script filename - --version tuple Script must be this version or greater + Other information: - Special options: + bugs - open GitHub issues website + chat - open GitHub developer forum + about - see general product info + upgrade - check for newer versions + changes - show changelog (TBD) + install - install (when applicable) + license - show user license + usage - program cmd line options + } + leave + ] - --boot level Valid levels: base sys mods - --debug flags For user scripts (system/options/debug) - --halt (-h) Leave console open when script is done - --import file Import a module prior to script - --quiet (-q) No startup banners or information - --secure policy Can be: none allow ask throw quit - --trace (-t) Enable trace mode during boot - --verbose Show detailed startup information + ;docs - open DocBase document wiki website + ;demo - run demo launcher (from rebol.com) - Other quick options: - -s No security - +s Full security - -v Display version only (then quit) +; Word completion: +; +; The command line can perform word +; completion. Type a few chars and press TAB +; to complete the word. If nothing happens, +; there may be more than one word that +; matches. Press TAB again to see choices. +; +; Local filenames can also be completed. +; Begin the filename with a %. +; +; Other useful functions: +; +; about - see general product info +; usage - view program options +; license - show terms of user license +; source func - view source of a function +; upgrade - updates your copy of REBOL +; +; More information: http://www.rebol.com/docs.html + + if all [word? :word | blank? context-of word] [ + print [word "is an unbound WORD!"] + leave + ] + + if all [word? :word | not set? word] [ + print [word "is bound to a context, but has no value."] + leave + ] + + ; Open the web page for it? + if all [ + doc + word? :word + any [function? get :word datatype? get :word] + ][ + item: form :word + browse join-of + either function? get :word [ + for-each [a b] [ ; need a better method ! + "!" "-ex" + "?" "-q" + "*" "-mul" + "+" "-plu" + "/" "-div" + "=" "-eq" + "<" "-lt" + ">" "-gt" + "|" "-bar" + ][replace/all item a b] + tmp: %.MD + https://github.com/gchiu/reboldocs/blob/master/ + ][ + remove back tail item ; the ! + tmp: %.html + http://www.rebol.com/r3/docs/datatypes/ + ] + [item tmp] + ] + + if all [word? :word | set? :word | datatype? get :word] [ + types: dump-obj/match make lib system/contexts/user :word + if not empty? types [ + print ["Found these" (uppercase form word) "words:" newline types] + ] else [ + print [word {is a datatype}] + ] + leave + ] + + ; If arg is a string, search the system: + if string? :word [ + types: dump-obj/match make lib system/contexts/user :word + sort types + if not empty? types [ + print ["Found these related words:" newline types] + leave + ] + print ["No information on" word] + leave + ] + + ; Print type name with proper singular article: + type-name: func [value [any-value!]] [ + value: mold type-of :value + clear back tail value + spaced [(either find "aeiou" first value ["an"]["a"]) value] + ] + + ; Print literal values: + if not any [word? :word path? :word][ + print [mold :word "is" type-name :word] + leave + ] + + ; Functions are not infix in Ren-C, only bindings of words to infix, so + ; we have to read the infixness off of the word before GETting it. + + ; Get value (may be a function, so handle with ":") + either path? :word [ + print ["!!! NOTE: Infix testing not currently supported for paths !!!"] + lookback: false + if any [ + error? set/opt 'value trap [get :word] ;trap reduce [to-get-path word] + not set? 'value + ][ + print ["No information on" word "(path has no value)"] + leave + ] + ][ + lookback: lookback? :word + value: get :word + ] + + unless function? :value [ + print/only spaced [ + (uppercase mold word) "is" (type-name :value) "of value: " + ] + print unspaced collect [ + either maybe [object! port!] value [ + keep newline + keep dump-obj value + ][ + keep mold value + ] + ] + leave + ] + + ; Must be a function... + ; If it has refinements, strip them: + ;if path? :word [word: first :word] + + space4: unspaced [space space space space] ;-- use instead of tab + + ;-- Print info about function: + print "USAGE:" + + args: _ ;-- plain arguments + refinements: _ ;-- refinements and refinement arguments + + parse words-of :value [ + copy args any [word! | get-word! | lit-word! | issue!] + copy refinements any [ + refinement! | word! | get-word! | lit-word! | issue! + ] + ] + + ; Output exemplar calling string, e.g. LEFT + RIGHT or FOO A B C + ; !!! Should refinement args be shown for lookback case?? + ; + either lookback [ + print [space4 args/1 (uppercase mold word) next args] + ][ + print [space4 (uppercase mold word) args refinements] + ] + + ; Dig deeply, but try to inherit the most specific meta fields available + ; + fields: dig-function-meta-fields :value + + description: fields/description + return-type: :fields/return-type + return-note: fields/return-note + types: fields/parameter-types + notes: fields/parameter-notes + + ; For reporting what kind of function this is, don't dig at all--just + ; look at the meta information of the function being asked about + ; + meta: meta-of :value + all [ + original-name: maybe word! ( + any [ + select meta 'specializee-name + select meta 'adaptee-name + ] + ) + original-name: uppercase mold original-name + ] + + specializee: maybe function! select meta 'specializee + adaptee: maybe function! select meta 'adaptee + chainees: maybe block! select meta 'chainees + + classification: case [ + :specializee [ + either original-name [ + spaced [{a specialization of} original-name] + ][ + {a specialized function} + ] + ] + + :adaptee [ + either original-name [ + spaced [{an adaptation of} original-name] + ][ + {an adapted function} + ] + ] + + :chainees [ + {a chained function} + ] + ] else {a function} + + print-newline + + print [ + "DESCRIPTION:" + | + space4 (any [description | "(undocumented)"]) + | + space4 (uppercase mold word) {is} classification {.} + ] + + print-args: procedure [list /indent-words] [ + for-each param list [ + note: maybe string! select notes to-word param + type: maybe [block! any-word!] select types to-word param + + ;-- parameter name and type line + either all [type | not refinement? param] [ + print/only [space4 param space "[" type "]" newline] + ][ + print/only [space4 param newline] + ] + + if note [ + print/only [space4 space4 note newline] + ] + ] + ] + + either blank? :return-type [ + ; If it's a PROCEDURE, saying "RETURNS: void" would waste space + ][ + ; For any return besides "always void", always say something about + ; the return value...even if just to say it's undocumented. + ; + print-newline + print ["RETURNS:" (if set? 'return-type [mold return-type])] + either return-note [ + print/only [space4 return-note newline] + ][ + if not set? 'return-type [ + print/only [space4 "(undocumented)" newline] + ] + ] + ] + + unless empty? args [ + print-newline + print "ARGUMENTS:" + print-args args + ] + + unless empty? refinements [ + print-newline + print "REFINEMENTS:" + print-args/indent-words refinements + ] +] - Examples: - REBOL script.r - REBOL -s script.r - REBOL script.r 10:30 test@example.com - REBOL --do "watch: on" script.r - } -] +; !!! MAKE is used here to deliberately avoid the use of an abstraction, +; because of the adaptation of SOURCE to be willing to take an index that +; indicates the caller's notion of a stack frame. (So `source 3` would +; give the source of the function they saw labeled as 3 in BACKTRACE.) +; +; The problem is that if FUNCTION is implemented using its own injection of +; unknown stack levels, it's not possible to count how many stack levels +; the call to source itself introduced. +; +; !!! This is fairly roundabout and probably should just make users type +; `source backtrace 5` or similar. Being left as-is for the talking point +; of how to implement functions which want to do this kind of thing. +; +source: make function! [[ + "Prints the source code for a function." + 'arg [integer! word! path! function! tag!] + {If integer then the function backtrace for that index is shown} -license: func [ - "Prints the REBOL/core license agreement." + f: name: ; pure locals ][ - print system/license -] - -source: func [ - "Prints the source code for a word." - 'word [word! path!] + case [ + tag? :arg [ + f: copy "unknown tag" + for-each location words-of system/locale/library [ + if location: select load get location arg [ + f: location/1 + break + ] + ] + ] + maybe [word! path!] :arg [ + name: arg + f: get :arg + ] + + integer? :arg [ + name: unspaced ["backtrace-" arg] + + ; We add two here because we assume the caller meant to be + ; using as point of reference what BACKTRACE would have told + ; *them* that index 1 was... not counting when SOURCE and this + ; nested CASE is on the stack. + ; + ; !!! A maze of questions are opened by this kind of trick, + ; which are beyond the scope of this comment. + + ; The usability rule for backtraces is that 0 is the number + ; given to a breakpoint if it's the top of the stack (after + ; backtrace removes itself from consideration). If running + ; SOURCE when under a breakpoint, the rule will not apply... + ; hence the numbering will start at 1 and the breakpoint is + ; now 3 deep in the stack (after SOURCE+CASE). Yet the + ; caller is asking about 1, 2, 3... or even 0 for what they + ; saw in the backtrace as the breakpoint. + ; + ; This is an interim convoluted answer to how to resolve it, + ; which would likely be done better with a /relative refinement + ; to backtrace. Before investing in that, some usability + ; experience just needs to be gathered, so compensate. + ; + f: function-of backtrace ( + 1 ; if BREAKPOINT, compensate differently (it's called "0") + + 1 ; CASE + + 1 ; SOURCE + ) + f: function-of backtrace ( + arg + ; if breakpoint there, bump 0 up to a 1, 1 to a 2, etc. + + (either :f == :breakpoint [1] [0]) + + 1 ; CASE + + 1 ; SOURCE + ) + + unless :f [ + print ["Stack level" arg "does not exist in backtrace"] + ] + ] + ] else [ + name: "anonymous" + f: :arg + ] + + case [ + function? :f [ + print unspaced [mold name ":" space mold :f] + ] + any [string? :f url? :f][ + print f + ] + true [ + print [name "is a" mold type-of :f "and not a FUNCTION!"] + ] + ] + () ;-- return nothing, as with a PROCEDURE +]] + + +what: procedure [ + {Prints a list of known functions.} + 'name [ word! lit-word!] + "Optional module name" + /args + "Show arguments not titles" ][ - if not value? word [print [word "undefined"] exit] - print head insert mold get word reduce [word ": "] - exit + list: make block! 400 + size: 0 + + ctx: any [select system/modules :name | lib] + + for-each [word val] ctx [ + if function? :val [ + arg: either args [ + arg: words-of :val + clear find arg /local + mold arg + ][ + title-of :val + ] + append list reduce [word arg] + size: max size length-of to-string word + ] + ] + + vals: make string! size + for-each [word arg] sort/skip list 2 [ + append/dup clear vals #" " size + print [head change vals word | :arg] + ] ] -what: func [ - {Prints a list of known functions.} - 'name [word! lit-word! unset!] "Optional module name" - /args "Show arguments not titles" - /local ctx vals arg list size -][ - list: make block! 400 - size: 0 - - ctx: any [select system/modules :name lib] - - foreach [word val] ctx [ - if any-function? :val [ - arg: either args [ - arg: words-of :val - clear find arg /local - mold arg - ][ - title-of :val - ] - append list reduce [word arg] - size: max size length? word - ] - ] - - vals: make string! size - foreach [word arg] sort/skip list 2 [ - append/dup clear vals #" " size - print [head change vals word any [arg ""]] - ] - exit -] pending: does [ - comment "temp function" - print "Pending implementation." -] - -say-browser: does [ - comment "temp function" - print "Opening web browser..." + comment "temp function" + print "Pending implementation." ] -upgrade: function [ - "Check for newer versions (update REBOL)." -][ - print "Fetching upgrade check ..." - if error? err: try [do http://www.rebol.com/r3/upgrade.r none][ - either err/id = 'protocol [print "Cannot upgrade from web."][do err] - ] - exit -] -chat: function [ - "Open REBOL DevBase forum/BBS." -][ - print "Fetching chat..." - if error? err: try [do http://www.rebol.com/r3/chat.r none][ - either err/id = 'protocol [print "Cannot load chat from web."][do err] - ] - exit -] - -docs: func [ - "Browse on-line documentation." -][ - say-browser - browse http://www.rebol.com/r3/docs - exit -] - -bugs: func [ - "View bug database." -][ - say-browser - browse http://curecode.org/rebol3/ - exit +say-browser: does [ + comment "temp function" + print "Opening web browser..." ] -changes: func [ - "What's new about this version." -][ - say-browser - browse http://www.rebol.com/r3/changes.html - exit -] -why?: func [ - "Explain the last error in more detail." - 'err [word! path! error! none! unset!] "Optional error value" +bugs: proc [ + "View bug database." ][ - case [ - unset? :err [err: none] - word? err [err: get err] - path? err [err: get err] - ] - - either all [ - error? err: any [:err system/state/last-error] - err/type ; avoids lower level error types (like halt) - ][ - say-browser - err: lowercase ajoin [err/type #"-" err/id] - browse join http://www.rebol.com/r3/docs/errors/ [err ".html"] - ][ - print "No information is available." - ] - exit + say-browser + browse https://github.com/metaeducation/ren-c/issues ] -demo: function [ - "Run R3 demo." -][ - print "Fetching demo..." - if error? err: try [do http://www.rebol.com/r3/demo.r none][ - either err/id = 'protocol [print "Cannot load demo from web."][do err] - ] - exit -] -load-gui: function [ - "Download current GUI module from web. (Temporary)" +chat: proc [ + "Open REBOL/ren-c developers chat forum" ][ - print "Fetching GUI..." - either error? data: try [load http://www.rebol.com/r3/gui.r][ - either data/id = 'protocol [print "Cannot load GUI from web."][do err] - ][ - do data - ] - exit + say-browser + browse http://chat.stackoverflow.com/rooms/291/rebol ] diff --git a/src/mezz/mezz-legacy.r b/src/mezz/mezz-legacy.r new file mode 100644 index 0000000000..6351ddd415 --- /dev/null +++ b/src/mezz/mezz-legacy.r @@ -0,0 +1,1299 @@ +REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Mezzanine: Legacy compatibility" + Homepage: https://trello.com/b/l385BE7a/porting-guide + Rights: { + Copyright 2012-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Description: { + These definitions attempt to create a compatibility mode for Ren-C, + so that it operates more like R3-Alpha. + + Some "legacy" definitions (like `foreach` as synonym of `for-each`) + are enabled by default, and may remain indefinitely. Other changes + may be strictly incompatible: words have been used for different + purposes, or variations in natives of the same name. Hence it is + necessary to "re-skin" the environment, by running: + + do + + (Dispatch for this from DO is in the DO* function of %sys-base.r) + + This statement will do nothing in older Rebols, since executing a + tag evaluates to just a tag. + + Though as much of the compatibility bridge as possible is sought to + be implemented in user code, some flags affect the executable behavior + of the evaluator. To avoid interfering with the native performance of + Ren-C, THESE ARE ONLY ENABLED IN DEBUG BUILDS. Be aware of that. + + Legacy mode is intended to assist in porting efforts to Ren-C, and to + exercise the abilities of the language to "flex". It is not intended + as a "supported" operating mode. Contributions making it work more + seamlessly are welcome, but scheduling of improvements to the legacy + mode are on a strictly "as-needed" basis. + } + Notes: { + At present it is a one-way street. Once `do ` is run, + there is no clean "shutdown" of legacy mode to go back to plain Ren-C. + + The current trick will modify the user context directly, and is not + module-based...so you really are sort of "backdating" the system + globally. A more selective version that turns features on and off + one at a time to ease porting is needed, perhaps like: + + do/args [ + new-do: off + question-marks: on + ] + } +] + +; This identifies if r3-legacy mode is has been turned on, useful mostly +; to avoid trying to turn it on twice. +; +r3-legacy-mode: off + + +; Ren-C *prefers* the use of GROUP! to PAREN!, both likely to remain legal. +; https://trello.com/c/ANlT44nH +; +paren?: :group? +paren!: :group! +to-paren: :to-group + + +; CONSTRUCT (arity 2) and HAS (arity 1) have arisen as the OBJECT!-making +; routines, parallel to FUNCTION (arity 2) and DOES (arity 1). By not being +; nouns like CONTEXT and OBJECT, they free up those words for other usages. +; For legacy support, both CONTEXT and OBJECT are just defined to be HAS. +; +; Note: Historically OBJECT was essentially a synonym for CONTEXT with the +; ability to tolerate a spec of `[a:]` by transforming it to `[a: none]. +; The tolerance of ending with a set-word has been added to CONSTRUCT+HAS +; so this distinction is no longer required. +; +context: object: :has + + +; General renamings away from non-LOGIC!-ending-in-?-functions +; https://trello.com/c/DVXmdtIb +; +index?: :index-of +offset?: :offset-of +sign?: :sign-of +suffix?: :suffix-of + +comment [ + ; !!! Less common cases still linger as question mark routines that + ; don't return LOGIC!, and they seem like they need greater rethinking in + ; general. What replaces them (for ones that are kept) might be new. + ; + encoding?: _ + file-type?: _ + speed?: _ + why?: _ + info?: _ + exists?: _ +] + + +; Semi-controversial choice to take a noun to avoid "lengthening LENGTH?" +; https://trello.com/c/4OT7qvdu +; Due to controversy, LENGTH-OF is used in the mezzanine +; +length: length?: :length-of + + +; FOREACH isn't being taken for anything else, may stay a built-in synonym +; https://trello.com/c/cxvHGNha +; +foreach: :for-each + + +; FOR-NEXT lets you switch series (unlike FORALL), see also FOR-BACK +; https://trello.com/c/StCADPIB +; +forall: :for-next +forskip: :for-skip + + +; Both in user code and in the C code, good to avoid BLOCK! vs. ANY-BLOCK! +; https://trello.com/c/lCSdxtux +; +any-block!: :any-array! +any-block?: :any-array? + + +; Similarly to the BLOCK! and ANY-BLOCK! problem for understanding the inside +; and outside of the system, ANY-CONTEXT! is a better name for the superclass +; of OBJECT!, ERROR!, PORT! and (likely to be killed) MODULE! + +any-object!: :any-context! +any-object?: :any-context? + + +; Typesets containing ANY- helps signal they are not concrete types +; https://trello.com/c/d0Nw87kp +; +number!: :any-number! +number?: :any-number? +scalar!: :any-scalar! +scalar?: :any-scalar? +series!: :any-series! +series?: :any-series? + + +; ANY-TYPE! is ambiguous with ANY-DATATYPE! +; https://trello.com/c/1jTJXB0d +; +; It is not legal for user-facing typesets to include the idea of containing +; a void type or optionality. Hence, ANY-TYPE! cannot include void. The +; notion of tolerating optionality must be encoded outside a typeset (Note +; that `find any-type! ()` didn't work in R3-Alpha, either.) +; +; The r3-legacy mode FUNC and FUNCTION explicitly look for ANY-TYPE! and +; replaces it with any-value! in the function spec. +; +any-type!: any-value! + + +; BIND? and BOUND? didn't fit the naming convention of returning LOGIC! if +; they end in a question mark. Also, CONTEXT-OF is more explicit about the +; type of the return result, which makes it more useful than BINDING-OF or +; BIND-OF as a name. (Result can be an ANY-CONTEXT!, including FRAME!) +; +bound?: :context-of +bind?: :context-of + + +; !!! Technically speaking all frames should be "selfless" in the sense that +; the system does not have a particular interest in the word "self" as +; applied to objects. Generators like OBJECT may choose to establish a +; self-bearing protocol. +; +selfless?: func [context [any-context!]] [ + fail {selfless? no longer has meaning (all frames are "selfless")} +] + +unset!: func [dummy:] [ + fail/where [ + {UNSET! is not a datatype in Ren-C.} + {You can test with VOID? (), but the TYPE-OF () is a NONE! *value*} + {So NONE? TYPE-OF () will be TRUE.} + ] 'dummy +] + +unset?: func [dummy:] [ + fail/where [ + {UNSET? is reserved in Ren-C for future use} + {(Will mean VOID? GET, like R3-Alpha VALUE?, only for WORDs/PATHs} + {Use VOID? for a similar test, but be aware there is no UNSET! type} + {If running in mode, old UNSET? meaning is available} + ] 'dummy +] + +value?: func [dummy:] [ + fail/where [ + {VALUE? is reserved in Ren-C for future use} + {(It will be a shorthand for ANY-VALUE! a.k.a. NOT VOID?)} + {SET? is similar to R3-Alpha VALUE?--but SET? only takes words} + {If running in mode, old VALUE? meaning is available.} + ] 'dummy +] + +none-of: :none ;-- reduce mistakes for now by renaming NONE out of the way + +none?: none!: none: func [dummy:] [ + fail/where [ + {NONE is reserved in Ren-C for future use} + {(It will act like NONE-OF, e.g. NONE [a b] => ALL [not a not b])} + {_ is now a "BLANK! literal", with BLANK? test and BLANK the word.} + {If running in mode, old NONE meaning is available.} + ] 'dummy +] + +type?: func [dummy:] [ + fail/where [ + {TYPE? is reserved in Ren-C for future use} + {(Though not fixed in stone, it may replace DATATYPE?)} + {TYPE-OF is the current replacement, with no TYPE-OF/WORD} + {Use soft quotes, e.g. SWITCH TYPE-OF 1 [:INTEGER! [...]]} + {If running in mode, old TYPE? meaning is available.} + ] 'dummy +] + +found?: func [dummy:] [ + fail/where [ + {FOUND? is deprecated in Ren-C, see chained function FIND?} + {FOUND? is available if running in mode.} + ] 'dummy +] + +op?: func [dummy:] [ + fail/where [ + {OP? can't work in Ren-C because there are no "infix FUNCTION!s"} + {"infixness" is a proerty of a word binding, made via SET/LOOKBACK} + {See: LOOKBACK?, INFIX?, PREFIX?, ENDFIX?} + ] 'dummy +] + + +; The legacy PRIN construct is replaced by PRINT/ONLY SPACED +; +prin: procedure [ + "Print spaced w/no added terminal line break, reducing blocks." + + value [ any-value!] +][ + print/only/eval either block? :value [spaced :value] [:value] +] + +; Common debug abbreviations that should be console-only (if anything) +; +dt: :delta-time +dp: :delta-profile + + +; AJOIN is a kind of ugly name for making an unspaced string from a block. +; REFORM is nonsensical looking. Ren-C has UNSPACED and SPACED. +; +ajoin: :unspaced +reform: :spaced + + + +; REJOIN in R3-Alpha meant "reduce and join"; the idea of cumulative joining +; in Ren-C already implies reduction of the appended data. JOIN-ALL is a +; friendlier name, suggesting joining with the atomic root type of the first +; reduced element. +; +; JOIN-ALL is not exactly the same as REJOIN; and it is not used as often +; because UNSPACED can be used for strings, with AS allowing aliasing of the +; data as other string types (`as tag! unspaced [...]` will not create a copy +; of the series data the way TO TAG! would). While REJOIN is tolerant of +; cases like `rejoin [() () ()]` producing an empty block, this makes a +; void in JOIN-ALL...but that is a common possibility. +; +rejoin: chain [ + :join-all + | + func [v [ any-series!]] [ + either set? 'v [:v] [copy []] + ] +] + + +; SET has a refinement called /ANY which doesn't communicate as well in the +; Ren-C world as OPT. OPT is the marker on functions to mark parameters as +; optional...OPT is the function to convert NONE! to UNSET! while passing +; all else through. It has a narrower and more communicative focus of purpose +; than /ANY does (also ANY is a very common function with a very different +; meaning and sense) +; +lib-set: :set ; overwriting lib/set for now +set: function [ + {Sets a word, path, block of words, or context to specified value(s).} + + return: [ any-value!] + {Just chains the input value (unmodified)} + target [blank! any-word! any-path! block! any-context!] + {Word, block of words, path, or object to be set (modified)} + value [ any-value!] + "Value or block of values" + /only + {If target and value are blocks, set each item to the same value} + /opt + "Treat void values as unsetting the target instead of an error" + /some + {Blank values (or values past end of block) are not set.} + /lookback + {If value is a function, then make the bound word dispatch infix} + /any + "Deprecated legacy synonym for /opt" +][ + set_ANY: any + any: :lib/any + set_OPT: opt + opt: :lib/opt + set_SOME: some + some: :lib/some + + apply 'lib-set [ + target: either any-context? target [words-of target] [target] + value: :value + only: only + opt: any? [set_ANY set_OPT] + some: set_SOME + lookback: lookback + ] +] + + +; This version of get supports the legacy /ANY switch. +; +; Historical GET in Rebol allowed any type that wasn't UNSET!. If you said +; something like `get 1` this would be passed through as `1`. Both Ren-C and +; Red have removed that feature, it is not carried forward in legacy at this +; time. +; +lib-get: :get +get: function [ + {Gets the value of a word or path, or values of a context.} + return: [ any-value!] + source [blank! any-word! any-path! any-context! block!] + "Word, path, context to get" + /opt + "Return void if no value instead of blank" + /any + "Deprecated legacy synonym for /OPT" +][ + any_GET: any + any: :lib/any + opt_GET: opt + opt: :lib/opt + + either* any-context? source [ + ; + ; In R3-Alpha, this was the vars of the context put into a BLOCK!: + ; + ; >> get make object! [[a b][a: 10 b: 20]] + ; == [10 20] + ; + ; Presumes order, and has strange semantics. Was written as native + ; code but is expressible more flexibily in usermode as getting the + ; WORDS-OF block, which covers things like hidden fields etc. + + apply 'lib-get [ + source: words-of source + opt: any? [any_GET opt_GET] ;-- will error if voids found + ] + ][ + apply 'lib-get [ + source: source + opt: any? [any_GET opt_GET] + ] + ] +] + + +; TRAP makes more sense as parallel-to-CATCH, /WITH makes more sense too +; https://trello.com/c/IbnfBaLI +; +try: func [ + {Tries to DO a block and returns its value or an error.} + return: [ any-value!] + block [block!] + /except + "On exception, evaluate code" + code [block! function!] +][ + either* except [trap/with block :code] [trap block] +] + + +; R3-Alpha's APPLY had a historically brittle way of handling refinements, +; based on their order in the function definition. e.g. the following would +; be how to say saying `APPEND/ONLY/DUP A B 2`: +; +; apply :append [a b none none true true 2] +; +; Ren-C's default APPLY construct is based on evaluating a block of code in +; the frame of a function before running it. This allows refinements to be +; specified as TRUE or FALSE and the arguments to be assigned by name. It +; also accepts a WORD! or PATH! as the function argument which it fetches, +; which helps it deliver a better error message labeling the applied function +; (instead of the stack frame appearing "anonymous"): +; +; apply 'append [ +; series: a +; value: b +; only: true +; dup: true +; count: true +; ] +; +; For most usages this is better, though it has the downside of becoming tied +; to the names of parameters at the callsite. One might not want to remember +; those, or perhaps just not want to fail if the names are changed. +; +; This implementation of R3-ALPHA-APPLY is a stopgap compatibility measure for +; the positional version. It shows that such a construct could be written in +; userspace--even implementing the /ONLY refinement. This is hoped to be a +; "design lab" for figuring out what a better positional apply might look like. +; +r3-alpha-apply: function [ + "Apply a function to a reduced block of arguments." + + return: [ any-value!] + action [function!] + "Function value to apply" + block [block!] + "Block of args, reduced first (unless /only)" + /only + "Use arg values as-is, do not reduce the block" +][ + frame: make frame! :action + params: words-of :action + using-args: true + + until [tail? block] [ + arg: either* only [ + also block/1 (block: next block) + ][ + do/next block 'block + ] + + either refinement? params/1 [ + using-args: set (in frame params/1) true? :arg + ][ + if using-args [ + set* (in frame params/1) :arg + ] + ] + + params: next params + ] + + comment [ + ; + ; Too many arguments was not a problem for R3-alpha's APPLY, it would + ; evaluate them all even if not used by the function. It may or + ; may not be better to have it be an error. + ; + unless tail? block [ + fail "Too many arguments passed in R3-ALPHA-APPLY block." + ] + ] + + do frame ;-- voids are optionals +] + +; In Ren-C, FUNCTION's variables have indefinite extent (aka ), and +; the body is specifically bound to those variables. (There is no dynamic +; binding in Ren-C) +; +closure: func [ + return: [ any-value!] + spec + body +][ + function compose [ + return: [ any-value!] + (spec) + ] body +] + +; FUNC variables are not durable by default, it must be specified explicitly. +; +clos: func [ + "Defines a closure function." + spec [block!] + {Help string (opt) followed by arg words (and opt type and string)} + body [block!] + "The body block of the function" +][ + func compose [ (spec)] body +] + +closure!: :function! +closure?: :function? + +; All other function classes are also folded into the one FUNCTION! type ATM. + +any-function!: :function! +any-function?: :function? + +native!: function! +native?: func [f [ any-value!]] [ + all [function? :f | 1 = func-class-of :f] +] + +;-- If there were a test for user-written functions, what would it be called? +;-- it would be function class 2 ATM + +action!: function! +action?: func [f [ any-value!]] [ + all [function? :f | 3 = func-class-of :f] +] + +command!: function! +command?: func [f [ any-value!]] [ + all [function? :f | 4 = func-class-of :f] +] + +routine!: function! +routine?: func [f [ any-value!]] [ + all [function? :f | 5 = func-class-of :f] +] + +callback!: function! +callback?: func [f [ any-value!]] [ + all [function? :f | 6 = func-class-of :f] +] + + +; In Ren-C, MAKE for OBJECT! does not use the "type" slot for parent +; objects. You have to use the arity-2 CONSTRUCT to get that behavior. +; Also, MAKE OBJECT! does not do evaluation--it is a raw creation, +; and requires a format of a spec block and a body block. +; +; Because of the commonality of the alternate interpretation of MAKE, this +; bridges until further notice. +; +; Also: bridge legacy calls to MAKE ROUTINE!, MAKE COMMAND!, and MAKE CALLBACK! +; while still letting ROUTINE!, COMMAND!, and CALLBACK! be valid to use in +; typesets invokes the new variadic behavior. This can only work if the +; source literally wrote out `make routine!` vs an expression that evaluated +; to the routine! datatype (for instance) but should cover most cases. +; +lib-make: :make +make: function [ + "Constructs or allocates the specified datatype." + return: [any-value!] + :lookahead [any-value! <...>] + type [ any-value! <...>] + "The datatype or an example value" + def [ any-value! <...>] + "Attributes or size of the new value (modified)" +][ + switch first lookahead [ + callback! [ + verify [function! = take type] + def: ensure block! take def + ffi-spec: ensure block! first def + action: ensure function! reduce second def + return make-callback :action ffi-spec + ] + routine! [ + verify [function! = take type] + def: ensure block! take def + ffi-spec: ensure block! first def + lib: ensure [integer! library!] reduce second def + if integer? lib [ ;-- interpreted as raw function pointer + return make-routine-raw lib ffi-spec + ] + name: ensure string! third def + return make-routine lib name ffi-spec + ] + command! [ + verify [function! = take type] + def: ensure block! take def + return make-command def + ] + ] + + type: take type + def: take def + + case [ + all [ + :type = object! + block? :def + not block? first def + ][ + ; + ; MAKE OBJECT! [x: ...] vs. MAKE OBJECT! [[spec][body]] + ; This old style did evaluation. Must use a generator + ; for that in Ren-C. + ; + return has :def + ] + + any [ + object? :type | struct? :type | gob? :type + ][ + ; + ; For most types in Rebol2 and R3-Alpha, MAKE VALUE [...] + ; was equivalent to MAKE TYPE-OF VALUE [...]. But with + ; objects, MAKE SOME-OBJECT [...] would interpret the + ; some-object as a parent. This must use a generator + ; in Ren-C. + ; + ; The STRUCT!, GOB!, and EVENT! types had a special 2-arg + ; variation as well, which is bridged here. + ; + return construct :type :def + ] + ] + + ; R3-Alpha would accept an example value of the type in the first slot. + ; This is of questionable utility. + ; + unless datatype? :type [ + type: type-of :type + ] + + if all [find any-array! :type | any-array? :def] [ + ; + ; MAKE BLOCK! of a BLOCK! was changed in Ren-C to be + ; compatible with the construction syntax, so that it lets + ; you combine existing array data with an index used for + ; aliasing. It is no longer a synonym for TO ANY-ARRAY! + ; that makes a copy of the data at the source index and + ; changes the type. (So use TO if you want that.) + ; + return to :type :def + ] + + lib-make :type :def +] + + +; To invoke this function, use `do ` instead of calling it +; directly, as that will be a no-op in older Rebols. Notice the word +; is defined in sys-base.r, as it needs to be visible pre-Mezzanine +; +; !!! There are a lot of SET-WORD!s in this routine inside an object append. +; So it's a good case study of how one can get a very large number of +; locals if using FUNCTION. Study. +; +set 'r3-legacy* func [ if-flags] [ + + if r3-legacy-mode [return blank] + + ; NOTE: these flags only work in debug builds. A better availability + ; test for the functionality is needed, as these flags may be expired + ; at different times on a case-by-case basis. + ; + ; (We don't flip these switches until after the above functions have been + ; created, so that the shims can use Ren-C features like word-valued + ; refinements/etc.) + ; + do in system/options [ + lit-word-decay: true + broken-case-semantics: true + exit-functions-only: true + refinements-blank: true + no-switch-evals: true + no-switch-fallthrough: true + forever-64-bit-ints: true + print-forms-everything: true + break-with-overrides: true + none-instead-of-voids: true + dont-exit-natives: true + paren-instead-of-group: true + get-will-get-anything: true + no-reduce-nested-print: true + unlocked-source: true + ] + + append system/contexts/user compose [ + + ; UNSET! as a reified type does not exist in Ren-C. There is still + ; a "void" state as the result of `do []` or just `()`, and it can be + ; passed around transitionally. Yet this "meta" result cannot be + ; stored in blocks. + ; + ; Over the longer term, UNSET? should be something that takes a word + ; or path to tell whether a variable is unset... but that is reserved + ; for NOT SET? until legacy is adapted. + ; + unset?: (:void?) + + ; Result from TYPE-OF () is a NONE!, so this should allow one to write + ; `unset! = type-of ()`. Also, a NONE! value in a typeset spec is + ; used to indicate a willingness to tolerate optional arguments, so + ; `foo: func [x [unset! integer!] x][...]` should work in legacy mode + ; for making an optional x argument. + ; + ; Note that with this definition, `datatype? unset!` will fail. + ; + unset!: _ + + ; NONE is reserved for NONE-OF in the future + ; + none: (:blank) + none!: (:blank!) + none?: (:blank?) + + ; The bizarre VALUE? function would look up words, return TRUE if they + ; were set and FALSE if not. All other values it returned TRUE. The + ; parameter was not optional, so you couldn't say `value?`. + ; + value?: (func [ + {If a word, return whether word is set...otherwise TRUE} + value + ][ + either any-word? :value [set? value] [true] + ]) + + ; Note that TYPE?/WORD is less necessary since SWITCH can soft quote + ; https://trello.com/c/fjJb3eR2 + ; + type?: (function [ + "Returns the datatype of a value ." + value [ any-value!] + /word + ][ + case [ + not word [type-of :value] + + not set? 'value [ + quote unset! ;-- https://trello.com/c/rmsTJueg + ] + + blank? :value [ + quote none! ;-- https://trello.com/c/vJTaG3w5 + ] + + all [ + group? :value + system/options/paren-instead-of-group + ][ + quote paren! ;-- https://trello.com/c/ANlT44nH + ] + ] else [ + to-word type-of :value + ] + ]) + + found?: (func [ + "Returns TRUE if value is not NONE." + value + ][ + not blank? :value + ]) + + ; These words do NOT inherit the infixed-ness, and you simply cannot + ; set things infix through a plain set-word. We have to do this + ; after the words are appended to the object. + + and: _ + + or: _ + + xor: _ + + apply: (:r3-alpha-apply) + + ; Adapt the TO ANY-WORD! case for GROUP! to give back the + ; word PAREN! (not the word GROUP!) + ; + to: (adapt 'to [ + if all [ + :value = group! + system/options/paren-instead-of-group + find any-word! type + ][ + value: "paren!" ;-- twist it into a string conversion + ] + ]) + + ; Not contentious, but trying to excise this ASAP + funct: (:function) + + op?: (func [ + "OP? behavior which just always returns FALSE" + value [ any-value!] + ][ + false + ]) + + ; R3-Alpha and Rebol2's DO was effectively variadic. If you gave it + ; a function, it could "reach out" to grab arguments from after the + ; call. While Ren-C permits this in variadic functions, the system + ; functions should be "well behaved" and there will even likely be + ; a security setting to turn variadics off (system-wide or per module) + ; + ; https://trello.com/c/YMAb89dv + ; + ; This legacy bridge is variadic to achieve the result. + ; + do: (function [ + {Evaluates a block of source code (variadic bridge)} + + return: [ any-value!] + source [ blank! block! group! string! binary! url! file! tag! + error! function! + ] + normals [any-value! <...>] + {Normal variadic parameters if function ( only)} + 'softs [any-value! <...>] + {Soft-quote variadic parameters if function ( only)} + :hards [any-value! <...>] + {Hard-quote variadic parameters if function ( only)} + /args + {If value is a script, this will set its system/script/args} + arg + "Args passed to a script (normally a string)" + /next + {Do next expression only, return it, update block variable} + var [word! blank!] + "Variable updated with new block position" + ][ + next_DO: next + next: :lib/next + + either function? :source [ + code: reduce [:source] + params: words-of :source + for-next params [ + append code switch type-of params/1 [ + :word! [take normals] + :lit-word! [take softs] + :get-word! [take hards] + :set-word! [()] ;-- unset appends nothing (for local) + :refinement! [break] + (fail ["bad param type" params/1]) + ] + ] + lib/do code + ][ + apply :lib/do [ + source: :source + if args: args [ + arg: :arg + ] + if next: next_DO [ + var: :var + ] + ] + ] + ]) + + ; Ren-C's default is a "lookback" that can see the SET-WORD! to its + ; left and examine it. `x: default 10` instead of `default 'x 10`, + ; with the same effect. + ; + default: (func [ + "Set a word to a default value if it hasn't been set yet." + 'word [word! set-word! lit-word!] + "The word (use :var for word! values)" + value "The value" ; void not allowed on purpose + ][ + unless all [set? word | not blank? get word] [set word :value] :value + ]) + + ; Ren-C removed the "simple parse" functionality, which has been + ; superseded by SPLIT. For the legacy parse implementation, add + ; it back in (more or less) by delegating to split. + ; + ; Also, as an experiment Ren-C has been changed so that a successful + ; parse returns the input, while an unsuccessful one returns blank. + ; Historically PARSE returned LOGIC!, this restores that behavior. + ; + parse: (function [ + "Parses a string or block series according to grammar rules." + + input [any-series!] + "Input series to parse" + rules [block! string! blank!] + "Rules (string! is , use SPLIT)" + /case + "Uses case-sensitive comparison" + /all + "Ignored refinement for " + ][ + case_PARSE: case + case: :lib/case + + comment [all_PARSE: all] ;-- Not used + all: :lib/all + + case [ + blank? rules [ + split input charset reduce [tab space cr lf] + ] + + string? rules [ + split input to-bitset rules + ] + ] else [ + lib/parse/(all [case_PARSE 'case]) input rules + ] + ]) + + ; There is a feature in R3-Alpha, used by R3-GUI, which allows an + ; unusual syntax for capturing series positions (like a REPEAT or + ; FORALL) with a SET-WORD! in the loop words block: + ; + ; >> a: [1 2 3] + ; >> foreach [s: i] a [print ["s:" mold s "i:" i]] + ; + ; s: [1 2 3] i: 1 + ; s: [2 3] i: 2 + ; s: [3] i: 3 + ; + ; This feature was removed from Ren-C due to it not deemed to be + ; "Quality", adding semantic questions and complexity to the C loop + ; implementation. (e.g. `foreach [a:] [...] [print "infinite loop"]`) + ; That interferes with the goal of "modify with confidence" and + ; simplicity. + ; + ; This shim function implements the behavior in userspace. Should it + ; arise that MAP-EACH is used similarly in a legacy scenario then the + ; code could be factored and shared, but it is not likely that the + ; core construct will be supporting this in FOR-EACH or EVERY. + ; + ; Longer-term, a rich LOOP dialect like Lisp's is planned: + ; + ; http://www.gigamonkeys.com/book/loop-for-black-belts.html + ; + foreach: (function [ + "Evaluates a block for value(s) in a series w/ 'extra'." + + 'vars [word! block!] + "Word or block of words to set each time (local)" + data [any-series! any-context! map! blank!] + "The series to traverse" + body [block!] + "Block to evaluate each time" + ][ + if any [ + not block? vars + lib/for-each item vars [ + if set-word? item [break/with false] + true + ] + ][ + ; a normal FOREACH + return lib/for-each :vars data body + ] + + ; Otherwise it's a weird FOREACH. So handle a block containing at + ; least one set-word by doing a transformation of the code into + ; a while loop. + ; + use :vars [ + position: data + until [tail? position] compose [ + (collect [ + every item vars [ + case [ + set-word? item [ + keep compose [(item) position] + ] + word? item [ + keep compose [ + (to-set-word :item) position/1 + position: next position + ] + ] + ] else [ + fail "non SET-WORD?/WORD? in FOREACH vars" + ] + ] + ]) + (body) + ] + ] + ]) + + ; REDUCE has been changed to evaluate single-elements if those + ; elements do not require arguments (so effectively a more limited + ; form of EVAL). The old behavior was to just pass through non-blocks + ; + reduce: (function [ + {Evaluates expressions and returns multiple results.} + value + /into + {Output results into a series with no intermediate storage} + target [any-block!] + ][ + unless block? :value [return :value] + + apply :lib/reduce [ + | value: :value + | if into: into [target: :target] + ] + ]) + + ; because reduce has been changed but lib/reduce is not in legacy + ; mode, this means the repend and join function semantics are + ; different. This snapshots their implementation. + + repend: (function [ + "Appends a reduced value to a series and returns the series head." + series [series! port! map! gob! object! bitset!] + {Series at point to insert (modified)} + value + {The value to insert} + /part + {Limits to a given length or position} + limit [number! series! pair!] + /only + {Inserts a series as a series} + /dup + {Duplicates the insert a specified number of times} + count [number! pair!] + ][ + ;-- R3-alpha REPEND with block behavior called out + ; + apply :append [ + | series: series + | value: either block? :value [reduce :value] [value] + | if part: part [limit: limit] + | only: only + | if dup: dup [count: count] + ] + ]) + + join: (function [ + "Concatenates values." + value "Base value" + rest "Value or block of values" + ][ + ;-- double-inline of R3-alpha `repend value :rest` + ; + apply :append [ + | series: either series? :value [copy value] [form :value] + | value: either block? :rest [reduce :rest] [rest] + ] + ]) + + ??: (:dump) + + ; To be on the safe side, the PRINT in the box won't do evaluations on + ; blocks unless the literal argument itself is a block + ; + print: (specialize 'print [eval: true]) + + ; QUIT now takes /WITH instead of /RETURN + ; + quit: (function [ + {Stop evaluating and return control to command shell or calling script.} + + /with + {Yield a result (mapped to an integer if given to shell)} + value [any-value!] + "See: http://en.wikipedia.org/wiki/Exit_status" + /return + "(deprecated synonym for /WITH)" + return-value + ][ + apply 'lib/quit [ + with: any? [with | return] + value: case [with [value] return [return-value]] + ] + ]) + + ; R3-Alpha would tolerate blocks in the first position, which were + ; a feature in Rebol2. e.g. `func [[throw catch] x y][...]`. Ren-C + ; does not allow this. Also, policy requires a RETURN: annotation to + ; say if one returns functions or void in Ren-C--there was no such + ; requirement in R3-Alpha. + ; + ; Also, ANY-TYPE! must be expressed as ANY-VALUE! in Ren-C, + ; since typesets cannot contain no-type. + ; + func: (func [ + {FUNC } + return: [function!] + spec [block!] + body [block!] + ][ + if block? first spec [spec: next spec] + + if find spec [[any-type!]] [ + spec: copy/deep spec + replace/all spec [[any-type!]] [[ any-value!]] + ] + + lib/func compose [ + return: [ any-value!] + (spec) + ] body + ]) + + ; The shift in Ren-C is to remove the refinements from FUNCTION. + ; Previously /WITH is now handles as the tag + ; /EXTERN then takes over the tag + ; + function: (func [ + {FUNCTION } + return: [function!] + spec [block!] + body [block!] + /with + {Define or use a persistent object (self)} + object [object! block! map!] + {The object or spec} + /extern + {Provide explicit list of external words} + words [block!] + {These words are not local.} + ][ + if block? first spec [spec: next spec] + + if find spec [[any-type!]] [ + spec: copy/deep spec + replace/all spec [[any-type!]] [[ any-value!]] + ] + + if block? :object [object: has object] + + lib/function compose [ + return: [ any-value!] + (spec) + (if with [reduce [ object]]) + (if extern []) + (:words) + ] body + ]) + + ; In Ren-C, HAS is the arity-1 parallel to OBJECT as arity-2 (similar + ; to the relationship between DOES and FUNCTION). In Rebol2 and + ; R3-Alpha it just broke out locals into their own block when they + ; had no arguments. + ; + has: (func [ + {Shortcut for function with local variables but no arguments.} + return: [function!] + vars [block!] {List of words that are local to the function} + body [block!] {The body block of the function} + ][ + func (head insert copy vars /local) body + ]) + + ; CONSTRUCT is now the generalized arity-2 object constructor. What + ; was previously known as CONSTRUCT can be achieved with the /ONLY + ; parameter to CONSTRUCT or to HAS. + ; + ; !!! There's was code inside of Rebol which called "Scan_Net_Header()" + ; in order to produce a block out of a STRING! or a BINARY! here. + ; That has been moved to scan-net-header, and there was not presumably + ; other code that used the feature. + ; + construct: (func [ + "Creates an object with scant (safe) evaluation." + + spec [block!] + "Specification (modified)" + /with + "Create from a default object" + object [object!] + "Default object" + /only + "Values are kept as-is" + ][ + apply :lib/construct [ + | spec: either with [object] [[]] + | body: spec + + ; It may be necessary to do *some* evaluation here, because + ; things like loading module headers would tolerate [x: 'foo] + ; as well as [x: foo] for some fields. + ; + | only: true + ] + ]) + + ; There were several different strata of equality checks, and one was + ; EQUIV? as well as NOT-EQUIV?. With changes to make comparisons + ; inside the system indifferent to binding (unless SAME? is used), + ; these have been shaken up instead focusing on getting more + ; foundational comparisons working. Red does not have EQUIV?, for + ; example, and few could tell you what it was. + ; + ; These aren't the same but may work in some cases. :-/ + ; + equiv?: (:equal?) + not-equiv?: (:not-equal?) + + ; BREAK/RETURN had a lousy name to start with (return from what?), but + ; was axed to give loops a better interface contract: + ; + ; https://trello.com/c/uPiz2jLL/ + ; + ; New features of WITH: https://trello.com/c/cOgdiOAD + ; + lib-break: :break ; overwriting lib/break for now + break: (func [ + {Exit the current iteration of a loop and stop iterating further.} + + /return ;-- Overrides RETURN! + {(deprecated: use THROW+CATCH)} + value [any-value!] + ][ + if return [ + fail [ + "BREAK/RETURN temporarily not implemented in " + "see https://trello.com/c/uPiz2jLL/ for why it was" + "removed. It could be accomplished in the compatibility" + "layer by climbing the stack via the DEBUG API and" + "looking for loops to EXIT, but this will all change with" + "the definitional BREAK and CONTINUE so it seems not" + "worth it. Use THROW and CATCH instead (available in" + "R3-Alpha) to subvert the loop return value." + ] + ] + + lib-break + ]) + ] + + ; set-infix on PATH! instead of WORD! is still TBD + ; + set-infix (bind 'and system/contexts/user) :and* + set-infix (bind 'or system/contexts/user) :or+ + set-infix (bind 'xor system/contexts/user) :xor+ + + if-flags: func [flags [block!] body [block!]] [ + for-each flag flags [ + if system/options/(flag) [return do body] + ] + ] + + ; + ; The Ren-C invariant for control constructs that don't run their cases + ; is to return VOID, not a "NONE!" (BLANK!) as in R3-Alpha. We assume + ; that converting void results from these operations gives compatibility, + ; and if it doesn't it's likealy a bigger problem because you can't put + ; "unset! literals" (voids) into blocks in the first place. + ; + ; So make a lot of things like `first: (chain [:first :to-value])` + ; + if-flags [none-instead-of-voids] [ + for-each word [ + if unless either case + while foreach loop repeat forall forskip + ][ + append system/contexts/user compose [ + (to-set-word word) + (chain compose [(to-get-word word) :to-value]) + ] + ] + ] + + ; SWITCH had several behavior changes--it evaluates GROUP! and GET-WORD! + ; and GET-PATH!--and values "fall out" the bottom if there isn't a match + ; (and the last item isn't a block). + ; + ; We'll assume these cases are rare in porting, but swap in + ; SWITCH for a routine that will FAIL if the cases come up. A sufficiently + ; motivated individual could then make a compatibility construct, but + ; probably would rather just change it so their code runs faster. :-/ + ; + if-flags [no-switch-evals no-switch-fallthrough][ + append system/contexts/user compose [ + switch: ( + chain [ + adapt 'switch [use [last-was-block] [ + last-was-block: false + for-next cases [ + if maybe [get-word! get-path! group!] cases/1 [ + fail [{SWITCH non- evaluates} (cases/1)] + ] + if block? cases/1 [ + last-was-block: true + ] + ] + unless last-was-block [ + fail [{SWITCH non-} last cases {"fallout"}] + ] + ]] + | + :to-value + ] + ) + ]] + + r3-legacy-mode: on + return blank +] diff --git a/src/mezz/mezz-math.r b/src/mezz/mezz-math.r index f648978990..0c5c2d74c5 100644 --- a/src/mezz/mezz-math.r +++ b/src/mezz/mezz-math.r @@ -1,92 +1,267 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Mezzanine: Math" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Mezzanine: Math" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } ] -mod: func [ - "Compute a nonnegative remainder of A divided by B." - ; In fact the function tries to find the remainder, - ; that is "almost non-negative" - ; Example: 0.15 - 0.05 - 0.1 // 0.1 is negative, - ; but it is "almost" zero, i.e. "almost non-negative" - [catch] - a [number! money! time!] - b [number! money! time!] "Must be nonzero." - /local r -] [ - ; Compute the smallest non-negative remainder - all [negative? r: a // b r: r + b] - ; Use abs a for comparisons - a: abs a - ; If r is "almost" b (i.e. negligible compared to b), the - ; result will be r - b. Otherwise the result will be r - either all [a + r = (a + b) positive? r + r - b] [r - b] [r] +pi: 3.14159265358979323846 + + +; ++ and -- were previously used to take a quoted word and increment +; it. They were ordinary prefix operations + +++: func [ + {Increment an integer or series index. Return its prior value.} + 'word [word!] "Integer or series variable" + /local prior +][ + also (prior: get word) ( + set word either series? prior [next prior] [prior + 1] + ) +] + +--: func [ + {Decrement an integer or series index. Return its prior value.} + 'word [word!] "Integer or series variable" + /local prior +][ + also (prior: get word) ( + set word either series? prior [back prior] [prior - 1] + ) +] + + +mod: function [ + "Compute a nonnegative remainder of A divided by B." + a [any-number! money! time!] + b [any-number! money! time!] + "Must be nonzero." +][ + ; This function tries to find the remainder that is "almost non-negative" + ; Example: 0.15 - 0.05 - 0.1 // 0.1 is negative, + ; but it is "almost" zero, i.e. "almost non-negative" + + ; Compute the smallest non-negative remainder + all [negative? r: a // b | r: r + b] + ; Use abs a for comparisons + a: abs a + ; If r is "almost" b (i.e. negligible compared to b), the + ; result will be r - b. Otherwise the result will be r + either all [(a + r) = (a + b) | positive? (r + r) - b] [r - b] [r] ] -modulo: func [ - {Wrapper for MOD that handles errors like REMAINDER. Negligible values (compared to A and B) are rounded to zero.} - ;[catch] - a [number! money! time!] - b [number! money! time!] "Absolute value will be used" - /local r + +modulo: function [ + {Wrapper for MOD that handles errors like REMAINDER.} + return: + {Negligible values (compared to A and B) are rounded to zero} + a [any-number! money! time!] + b [any-number! money! time!] + "Absolute value will be used" ] [ - ; Coerce B to a type compatible with A - any [number? a b: make a b] - ; Get the "accurate" MOD value - r: mod a abs b - ; If the MOD result is "near zero", w.r.t. A and B, - ; return 0--the "expected" result, in human terms. - ; Otherwise, return the result we got from MOD. - either any [a - r = a r + b = b] [make r 0] [r] + ; Coerce B to a type compatible with A + any [any-number? a b: make a b] + ; Get the "accurate" MOD value + r: mod a abs b + ; If the MOD result is "near zero", w.r.t. A and B, + ; return 0--the "expected" result, in human terms. + ; Otherwise, return the result we got from MOD. + either any [(a - r) = a | (r + b) = b] [make r 0] [r] ] -sign?: func [ - "Returns sign of number as 1, 0, or -1 (to use as multiplier)." - number [number! money! time!] + +sign-of: func [ + "Returns sign of number as 1, 0, or -1 (to use as multiplier)." + number [any-number! money! time!] ][ - case [ - positive? number [1] - negative? number [-1] - true [0] - ] + case [ + positive? number [1] + negative? number [-1] + ] else 0 ] + minimum-of: func [ - {Finds the smallest value in a series} - series [series!] {Series to search} - /skip {Treat the series as records of fixed size} - size [integer!] - /local spot + {Finds the smallest value in a series} + series [any-series!] {Series to search} + /skip {Treat the series as records of fixed size} + size [integer!] + /local spot ][ - size: any [size 1] - if 1 > size [cause-error 'script 'out-of-range size] - spot: series - forskip series size [ - if lesser? first series first spot [spot: series] - ] - spot + size: any [size 1] + if 1 > size [cause-error 'script 'out-of-range size] + spot: series + for-skip series size [ + if lesser? first series first spot [spot: series] + ] + spot ] maximum-of: func [ - {Finds the largest value in a series} - series [series!] {Series to search} - /skip {Treat the series as records of fixed size} - size [integer!] - /local spot + {Finds the largest value in a series} + series [any-series!] {Series to search} + /skip {Treat the series as records of fixed size} + size [integer!] + /local spot +][ + size: any [:size 1] + if 1 > size [cause-error 'script 'out-of-range size] + spot: series + for-skip series size [ + if greater? first series first spot [spot: series] + ] + spot +] + + +; A simple iterative implementation; returns 1 for negative +; numbers. FEEL FREE TO IMPROVE THIS! +; +factorial: func [n [integer!] /local res] [ + if n < 2 [return 1] + res: 1 + ; should avoid doing the loop for i = 1... + repeat i n [res: res * i] +] + + +; This MATH implementation is from Gabrielle Santilli circa 2001, found +; via http://www.rebol.org/ml-display-thread.r?m=rmlXJHS. It implements the +; much-requested (by new users) idea of * and / running before + and - in +; math expressions. Expanded to include functions. +; +math: function [ + {Process expression taking "usual" operator precedence into account.} + + expr [block!] + {Block to evaluate} + /only + {Translate operators to their prefix calls, but don't execute} + + ; !!! This creation of static rules helps avoid creating those rules + ; every time, but has the problem that the references to what should + ; be locals are bound to statics as well (e.g. everything below which + ; is assigned with BLANK! really should be relatively bound to the + ; function, so that it will refer to the specific call.) It's not + ; technically obvious how to do that, not the least of the problem is + ; that statics are currently a usermode feature...and injecting relative + ; binding information into something that's not the function body itself + ; isn't implemented. + + + + slash (to-lit-word first [ / ]) + + expr-val (_) + + expr-op (_) + + expression ([ + term (expr-val: term-val) + any [ + ['+ (expr-op: 'add) | '- (expr-op: 'subtract)] + term (expr-val: compose [(expr-op) (expr-val) (term-val)]) + ] + ]) + + term-val (_) + + term-op (_) + + term ([ + pow (term-val: power-val) + any [ + ['* (term-op: 'multiply) | slash (term-op: 'divide)] + pow (term-val: compose [(term-op) (term-val) (power-val)]) + ] + ]) + + power-val (_) + + pow ([ + unary (power-val: unary-val) + opt ['** unary (power-val: compose [power (power-val) (unary-val)])] + ]) + + unary-val (_) + + pre-uop (_) + + post-uop (_) + + unary ([ + (post-uop: pre-uop: []) + opt ['- (pre-uop: 'negate)] + primary + opt ['! (post-uop: 'factorial)] + (unary-val: compose [(post-uop) (pre-uop) (prim-val)]) + ]) + + prim-val (_) + + primary ([ + set prim-val any-number! + | set prim-val [word! | path!] (prim-val: reduce [prim-val]) + ; might be a funtion call, looking for arguments + any [ + nested-expression (append prim-val take nested-expr-val) + ] + | and group! into nested-expression (prim-val: take nested-expr-val) + ]) + + p-recursion (_) + + nested-expr-val ([]) + + save-vars (func [][ + p-recursion: reduce [ + :p-recursion :expr-val :expr-op :term-val :term-op :power-val :unary-val + :pre-uop :post-uop :prim-val + ] + ]) + + restore-vars (func [][ + set [ + p-recursion expr-val expr-op term-val term-op power-val unary-val + pre-uop post-uop prim-val + ] p-recursion + ]) + + nested-expression ([ + ;all of the static variables have to be saved + (save-vars) + expression + ( + ; This rule can be recursively called as well, + ; so result has to be passed via a stack + insert/only nested-expr-val expr-val + restore-vars + ) + ; vars could be changed even it failed, so restore them and fail + | (restore-vars) fail + + ]) ][ - size: any [size 1] - if 1 > size [cause-error 'script 'out-of-range size] - spot: series - forskip series size [ - if greater? first series first spot [spot: series] - ] - spot + clear nested-expr-val + res: either parse expr expression [expr-val] [blank] + + either only [res] [ + ret: reduce res + unless all [ + 1 = length-of ret + any-number? ret/1 + ][ + fail unspaced [ + "Cannot be REDUCED to a number(" mold ret ") :" mold res + ] + ] + ret/1 + ] ] diff --git a/src/mezz/mezz-save.r b/src/mezz/mezz-save.r index 5fa9119596..6c5000ddbc 100644 --- a/src/mezz/mezz-save.r +++ b/src/mezz/mezz-save.r @@ -1,134 +1,179 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Mezzanine: Save" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Issues: { - Is MOLD Missing a terminating newline? -CS - Add MOLD/options -CS - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Mezzanine: Save" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Issues: { + Is MOLD Missing a terminating newline? -CS + Add MOLD/options -CS + } ] mold64: function [ - "Temporary function to mold binary base 64." ; fix the need for this! -CS - data + "Temporary function to mold binary base 64." ; fix the need for this! -CS + data ][ - base: system/options/binary-base - system/options/binary-base: 64 - data: mold :data - system/options/binary-base: :base - data + base: system/options/binary-base + system/options/binary-base: 64 + data: mold :data + system/options/binary-base: :base + data ] save: function [ - {Saves a value, block, or other data to a file, URL, binary, or string.} - where [file! url! binary! string! none!] {Where to save (suffix determines encoding)} - value {Value(s) to save} - /header {Provide a REBOL header block (or output non-code datatypes)} - header-data [block! object! logic!] {Header block, object, or TRUE (header is in value)} - /all {Save in serialized format} - /length {Save the length of the script content in the header} - /compress {Save in a compressed format or not} - method [logic! word!] "true = compressed, false = not, 'script = encoded string" + {Saves a value, block, or other data to a file, URL, binary, or string.} + where [file! url! binary! string! blank!] + {Where to save (suffix determines encoding)} + value {Value(s) to save} + /header + {Provide a REBOL header block (or output non-code datatypes)} + header-data [block! object! logic!] + {Header block, object, or TRUE (header is in value)} + /all ;-- renamed to `all_SAVE` to avoid ambiguity with native + {Save in serialized format} + /length ;-- renamed to `length_SAVE` to avoid ambiguity with native + {Save the length of the script content in the header} + /compress + {Save in a compressed format or not} + method [logic! word!] + {true = compressed, false = not, 'script = encoded string} ][ - ;-- Special datatypes use codecs directly (e.g. PNG image file): - if lib/all [ - not header ; User wants to save value as script, not data file - any [file? where url? where] - type: file-type? where - ][ ; We have a codec: - return write where encode type :value ; will check for valid type - ] - - ;-- Compressed scripts and script lengths require a header: - if any [length method] [ - header: true - header-data: any [header-data []] - ] - - ;-- Handle the header object: - if header-data [ - ; TRUE indicates the header is the first value in the block: - if header-data = true [ - header-data: any [ - lib/all [ - block? :value - first+ value ; the header (do not use TAKE) - ] - [] ; empty header - ] - ] - - ; Make it an object if it's not already (ok to ignore overhead): - header-data: either object? :header-data [ - trim :header-data ; clean out the words set to none - ][ - construct :header-data ; standard/header intentionally not used - ] - - if compress [ ; Make the header option match - case [ - not method [remove find select header-data 'options 'compress] - not block? select header-data 'options [ - repend header-data ['options copy [compress]] - ] - not find header-data/options 'compress [ - append header-data/options 'compress - ] - ] - ] - - if length [ - append header-data [length: #[true]] ; any true? value will work - ] - - unless compress: true? find select header-data 'options 'compress [method: none] - length: true? select header-data 'length - header-data: body-of header-data - ] - - ; (Maybe /all should be the default? See CureCode.) - data: either all [mold/all/only :value] [mold/only :value] - append data newline ; mold does not append a newline? Nope. - - case/all [ - ; Checksum uncompressed data, if requested - tmp: find header-data 'checksum [change next tmp checksum/secure data: to-binary data] - ; Compress the data if necessary - compress [data: lib/compress data] - ; File content is encoded as base-64: - method = 'script [data: mold64 data] - not binary? data [data: to-binary data] - length [change find/tail header-data 'length length? data] - header-data [insert data ajoin ['REBOL #" " mold header-data newline]] - ] - case [ - file? where [write where data] ; WRITE converts to UTF-8, saves overhead - url? where [write where data] ; But some schemes don't support it - none? where [data] ; just return the UTF-8 binary - 'else [insert tail where data] ; string! or binary!, insert data - ] -] + ; Recover common natives for words used as refinements. + all_SAVE: all + length_SAVE: length + all: :lib/all + length: :lib/length + + ; Default `method` and `header-data` to blank + method: to-value :method + header-data: to-value :header-data + + ;-- Special datatypes use codecs directly (e.g. PNG image file): + if all [ + not header ; User wants to save value as script, not data file + any [file? where url? where] + type: file-type? where + ][ + ; We have a codec. Will check for valid type. + return write where encode type :value + ] + + ;-- Compressed scripts and script lengths require a header: + if any [length_SAVE method] [ + header: true + header-data: any [header-data []] + ] + + ;-- Handle the header object: + if header-data [ + ; TRUE indicates the header is the first value in the block: + if header-data = true [ + header-data: any [ + all [ + block? :value + first+ value ; the header (do not use TAKE) + ] + [] ; empty header + ] + ] + + ; Make it an object if it's not already (ok to ignore overhead): + header-data: either object? :header-data [ + ; clean out the words set to blank + trim :header-data + ][ + ; standard/header intentionally not used + has/only :header-data + ] + + if compress [ ; Make the header option match + case [ + not method [ + remove find select header-data 'options 'compress + ] + not block? select header-data 'options [ + join header-data ['options copy [compress]] + ] + not find header-data/options 'compress [ + append header-data/options 'compress + ] + ] + ] + + if length_SAVE [ + ; any true? value will work, but this uses #[true]. (Notation + ; is to help realize this is a *mention*, not *usage* of length.) + append header-data reduce [(quote length:) (true)] + ] + + unless compress: find? (select header-data 'options) 'compress [ + method: _ + ] + + length_SAVE: maybe integer! select header-data 'length + header-data: body-of header-data + ] + + ; !!! Maybe /all should be the default? See #2159 + data: either all_SAVE [mold/all/only :value] [ + mold/only :value + ] + + ; mold does not append a newline? Nope. + append data newline + + case/all [ + tmp: find header-data 'checksum [ + ; Checksum uncompressed data, if requested + change next tmp checksum/secure data: to-binary data + ] + + compress [ + ; Compress the data if necessary + data: lib/compress data + ] + + method = 'script [ + data: mold64 data ; File content is encoded as base-64 + ] + + not binary? data [ + data: to-binary data + ] + + length_SAVE [ + change find/tail header-data 'length (length-of data) + ] + + header-data [ + insert data unspaced [{REBOL} space (mold header-data) newline] + ] + ] + + case [ + file? where [ + ; WRITE converts to UTF-8, saves overhead + write where data + ] + + url? where [ + ; !!! Comment said "But some schemes don't support it" + ; Presumably saying that the URL scheme does not support UTF-8 (?) + write where data + ] -#test [ - data: [1 1.2 10:20 "test" user@example.com [sub block]] - probe to string! save none [] - probe to string! save none data - probe to string! save/header none data [title: "my code"] - probe to string! save/compress none [] true - probe to string! save/compress none data true - probe to string! save/compress none data 'script - probe to string! save/header/compress none data [title: "my code"] true - probe to string! save/header/compress none data [title: "my code"] 'script - probe to string! save/header none data [title: "my code" options: [compress]] - probe to string! save/header/compress none data [title: "my code" options: [compress]] none - probe to string! save/header none data [title: "my code" checksum: true] - halt - ; more needed + blank? where [ + ; just return the UTF-8 binary + data + ] + ] else [ + ; string! or binary!, insert data + insert tail where data + ] ] diff --git a/src/mezz/mezz-secure.r b/src/mezz/mezz-secure.r index ac7f3303cc..34d80f8e5c 100644 --- a/src/mezz/mezz-secure.r +++ b/src/mezz/mezz-secure.r @@ -1,207 +1,216 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL Mezzanine: Security" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL Mezzanine: Security" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } ] -secure: function/with [ - "Set security policies (use SECURE help for more information)." - 'policy [word! lit-word! block! unset!] "Set single or multiple policies (or HELP)" +secure: function [ + "Set security policies (use SECURE help for more information)." + return: [ any-value!] + 'policy [ word! lit-word! block!] + "Set single or multiple policies (or HELP)" + + + + ; Permanent values and sub-functions of SECURE: + + acts ([allow ask throw quit]) + + assert-policy ( + func [tst kind arg] [ + unless tst [cause-error 'access 'security-error reduce [kind arg]] + ] + ) + + make-policy (function [ + ; Build the policy tuple used by lower level code. + target ; "For special cases: eval, memory" + pol ; word number or block + ][ + ; Special cases: [eval 100000] + if find [eval memory] target [ + assert-policy any-number? pol target pol + limit-usage target pol ; pol is a number here + return 3.3.3 ; always quit + ] + ; The set all case: [file allow] + if word? pol [ + n: find acts pol + assert-policy n target pol + return (index-of n) - 1 * 1.1.1 + ] + ; Detailed case: [file [allow read throw write]] + flags: 0.0.0 + assert-policy block? pol target pol + for-each [act perm] pol [ + n: find acts act + assert-policy n target act + m: select [read 1.0.0 write 0.1.0 execute 0.0.1] perm + assert-policy m target perm + flags: (index-of n) - 1 * m or+ flags + ] + flags + ]) + + set-policy (function [ + ; Set the policy as tuple or block: + target + pol + pol-obj + ][ + case [ + file? target [ + val: to-local-file/full target + ; This string must have OS-local encoding, because + ; the check is done at a lower level of I/O. + if system/version/4 != 3 [val: to binary! val] + target: 'file + ] + url? target [val: target target: 'net] + ] + old: select pol-obj target + assert-policy old target pol + either val [ + ; Convert tuple to block if needed: + if tuple? old [old: reduce [target old]] + remove/part find old val 2 ; can be in list only once + insert old reduce [val pol] + ][ + old: pol + ] + set in pol-obj target old + ]) + + word-policy (function [pol][ + ; Convert lower-level policy tuples to words: + if all [pol/1 = pol/2 pol/2 = pol/3][ + return pick acts 1 + pol/1 + ] + blk: make block! 4 + n: 1 + for-each act [read write execute] [ + join blk [pick acts 1 + pol/:n act] + ++ n + ] + blk + ]) + ] append bind [ - "Two funcs bound to private system/state/policies with protect/hide after." - set-policies: func [p] [set 'policies p] - get-policies: func [] [copy/deep policies] + "Two funcs bound to private system/state/policies with protect/hide after." + set-policies: func [p] [set 'policies p] + get-policies: func [] [copy/deep policies] ] system/state [ - if unset? :policy [policy: 'help] - - if policy = 'none [policy: 'allow] ; note: NONE is a word here (like R2) - - pol-obj: get-policies ; a deep copy - - if policy = 'help [ - print "You can set policies for:" - foreach [target pol] pol-obj [print [" " target]] - print "These can be set to:" - foreach [t d] [ - allow "no security" - ask "ask user for permission" - throw "throw as an error" - quit "exit the program immediately" - file "a file path" - url "a file path" - other "other value, such as integer" - ] [print [" " t "-" d]] - print "Settings for read, write, and execute are also available." - print "Type: help/doc secure for detailed documentation and examples." - exit - ] - - if policy = 'query [ - out: make block! 2 * length? pol-obj - foreach [target pol] pol-obj [ - case [ - ; file 0.0.0 (policies) - tuple? pol [repend out [target word-policy pol]] - ; file [allow read quit write] - block? pol [ - foreach [item pol] pol [ - if binary? item [item: to-string item] ; utf-8 decode - if string? item [item: to-rebol-file item] - repend out [item word-policy pol] - ] - ] - ] - ] - new-line/skip out on 2 - return out - ] - - ; Check if SECURE is secured: - if pol-obj/secure <> 0.0.0 [ - if pol-obj/secure == 'throw [cause-error 'access 'security :policy] - quit/now/return 101 ; an arbitrary code - ] - - ; Bulk-set all policies: - if word? policy [ - n: make-policy 'all policy - foreach word words-of pol-obj [set word n] - set-policies pol-obj - exit - ] - - ; Set each policy target separately: - foreach [target pol] policy [ - assert/type [target [word! file! url!] pol [block! word! integer!]] - set-policy target make-policy target pol pol-obj - ] - - ; ADD: check for policy level reductions! - set-policies pol-obj - exit -][ - ; Permanent values and sub-functions of SECURE: - - acts: [allow ask throw quit] - - assert-policy: func [tst kind arg] [unless tst [cause-error 'access 'security-error reduce [kind arg]]] - - make-policy: func [ - ; Build the policy tuple used by lower level code. - target ; "For special cases: eval, memory" - pol ; word number or block - /local n m flags - ][ - ; Special cases: [eval 100000] - if find [eval memory] target [ - assert-policy number? pol target pol - limit-usage target pol ; pol is a number here - return 3.3.3 ; always quit - ] - ; The set all case: [file allow] - if word? pol [ - n: find acts pol - assert-policy n target pol - return (index? n) - 1 * 1.1.1 - ] - ; Detailed case: [file [allow read throw write]] - flags: 0.0.0 - assert-policy block? pol target pol - foreach [act perm] pol [ - n: find acts act - assert-policy n target act - m: select [read 1.0.0 write 0.1.0 execute 0.0.1] perm - assert-policy m target perm - flags: (index? n) - 1 * m or flags - ] - flags - ] - - set-policy: func [ - ; Set the policy as tuple or block: - target - pol - pol-obj - /local val old - ][ - case [ - file? target [ - val: to-local-file/full target - ; This string must have OS-local encoding, because - ; the check is done at a lower level of I/O. - if system/version/4 != 3 [val: to binary! val] - target: 'file - ] - url? target [val: target target: 'net] - ] - old: select pol-obj target - assert-policy old target pol - either val [ - ; Convert tuple to block if needed: - if tuple? old [old: reduce [target old]] - remove/part find old val 2 ; can be in list only once - insert old reduce [val pol] - ][ - old: pol - ] - set in pol-obj target old - ] - - word-policy: func [pol /local blk n][ - ; Convert lower-level policy tuples to words: - if all [pol/1 = pol/2 pol/2 = pol/3][ - return pick acts 1 + pol/1 - ] - blk: make block! 4 - n: 1 - foreach act [read write execute] [ - repend blk [pick acts 1 + pol/:n act] - ++ n - ] - blk - ] + if void? :policy [policy: 'help] + + if policy = 'none [policy: 'allow] ; note: NONE is a word here (like R2) + + pol-obj: get-policies ; a deep copy + + if policy = 'help [ + print "You can set policies for:" + for-each [target pol] pol-obj [print [" " target]] + print "These can be set to:" + for-each [t d] [ + allow "no security" + ask "ask user for permission" + throw "throw as an error" + quit "exit the program immediately" + file "a file path" + url "a file path" + other "other value, such as integer" + ] [print [space space t "-" d]] + print "Settings for read, write, and execute are also available." + print "Type: help/doc secure for detailed documentation and examples." + return () + ] + + if policy = 'query [ + out: make block! 2 * length-of pol-obj + for-each [target pol] pol-obj [ + case [ + ; file 0.0.0 (policies) + tuple? pol [join out [target word-policy pol]] + ; file [allow read quit write] + block? pol [ + for-each [item pol] pol [ + if binary? item [item: to-string item] ; utf-8 decode + if string? item [item: to-rebol-file item] + join out [item word-policy pol] + ] + ] + ] + ] + new-line/skip out on 2 + return out + ] + + ; Check if SECURE is secured: + if pol-obj/secure <> 0.0.0 [ + if pol-obj/secure == 'throw [cause-error 'access 'security :policy] + quit/now/return 101 ; an arbitrary code + ] + + ; Bulk-set all policies: + if word? policy [ + n: make-policy 'all policy + for-each word words-of pol-obj [set word n] + set-policies pol-obj + return () + ] + + ; Set each policy target separately: + for-each [target pol] policy [ + ensure [word! file! url!] target + ensure [block! word! integer!] pol + set-policy target make-policy target pol pol-obj + ] + + ; ADD: check for policy level reductions! + set-policies pol-obj + return () ] -unless system/options/flags/secure-min [ - ; Remove all other access to the policies: - protect/hide in system/state 'policies + +unless system/options/secure == 'allow [ + ; Remove all other access to the policies: + protect/hide in system/state 'policies ] protect-system-object: func [ - "Protect the system object and selected sub-objects." + "Protect the system object and selected sub-objects." ][ - protect 'system - protect system - - "full protection:" - protect/words/deep [ - system/catalog - ;system/standard - system/dialects - ;system/intrinsic - ] - - "mild protection:" - protect/words [ - system/license - system/contexts - ] - - unprotect/words [ - system/script - ;system/schemes - ;system/ports ; should not be modified, fix this - system/options ; some are modified by scripts - system/view ; should not be modified! - ] + protect 'system + protect system + + "full protection:" + protect/words/deep [ + system/catalog + ;system/standard + system/dialects + ;system/intrinsic + ] + + "mild protection:" + protect/words [ + system/license + system/contexts + ] + + unprotect/words [ + system/script + ;system/schemes + ;system/ports ; should not be modified, fix this + system/options ; some are modified by scripts + system/view ; should not be modified! + ] ] diff --git a/src/mezz/mezz-series.r b/src/mezz/mezz-series.r index 31a8ac8888..02602aba55 100644 --- a/src/mezz/mezz-series.r +++ b/src/mezz/mezz-series.r @@ -1,518 +1,739 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Mezzanine: Series Helpers" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Mezzanine: Series Helpers" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } ] -empty?: make :tail? [ - [ - {Returns TRUE if empty or NONE, or for series if index is at or beyond its tail.} - series [series! object! gob! port! bitset! map! none!] - ] -] - -offset?: func [ - "Returns the offset between two series positions." - series1 [series!] - series2 [series!] +empty?: func [ + {Returns TRUE if empty or NONE, or for series if index is at or beyond its tail.} + series [any-series! object! gob! port! bitset! map! blank!] ][ - subtract index? series2 index? series1 + tail? series ] -found?: func [ - "Returns TRUE if value is not NONE." - value + +offset-of: func [ + "Returns the offset between two series positions." + series1 [any-series!] + series2 [any-series!] ][ - not none? :value + subtract index-of series2 index-of series1 ] + last?: single?: func [ - "Returns TRUE if the series length is 1." - series [series! port! map! tuple! bitset! object! gob! any-word!] + "Returns TRUE if the series length is 1." + series [any-series! port! map! tuple! bitset! object! gob! any-word!] ][ - 1 = length? series + 1 = length-of series ] + extend: func [ - "Extend an object, map, or block type with word and value pair." - obj [object! map! block! paren!] {object to extend (modified)} - word [any-word!] - val + "Extend an object, map, or block type with word and value pair." + obj [object! map! block! group!] {object to extend (modified)} + word [any-word!] + val ][ - if :val [append obj reduce [to-set-word word :val]] - :val + if :val [append obj reduce [to-set-word word :val]] + :val ] -rejoin: func [ - "Reduces and joins a block of values." - block [block!] "Values to reduce and join" - ;/with "separator" + +join-all: function [ + "Reduces and appends a block of values together." + return: [ any-series!] + "Will be the type of the first non-void series produced by evaluation" + block [block!] + "Values to join together" + position ][ - if empty? block: reduce block [return block] - append either series? first block [copy first block][ - form first block - ] next block + forever [ + if tail? block [return ()] + unless void? base: do/next block 'block [break] + ] + + ; !!! It isn't especially compelling that `join-of 3 "hello"` gives you + ; `3hello`; defaulting to a string doesn't make obviously more sense than + ; `[3 "hello"]` when using a series operation. However, so long as + ; JOIN-OF is willing to do so, it will be legal to do it here. + ; + join-of base block ] + remold: func [ - {Reduces and converts a value to a REBOL-readable string.} - value {The value to reduce and mold} - /only {For a block value, mold only its contents, no outer []} - /all {Mold in serialized format} - /flat {No indentation} + {Reduces and converts a value to a REBOL-readable string.} + value {The value to reduce and mold} + /only {For a block value, mold only its contents, no outer []} + /all {Mold in serialized format} + /flat {No indentation} ][ - apply :mold [reduce :value only all flat] + all_REMOLD: all + all: :lib/all + + mold/(all [only 'only])/(all [all_REMOLD 'all])/(all [flat 'flat]) + reduce :value ] -charset: func [ - "Makes a bitset of chars for the parse function." - chars [string! block! binary! char! integer!] - /length "Preallocate this many bits" - len [integer!] "Must be > 0" + +charset: function [ + "Makes a bitset of chars for the parse function." + chars [string! block! binary! char! integer!] + /length "Preallocate this many bits" + len [integer!] "Must be > 0" ][ - either length [append make bitset! len chars] [make bitset! chars] + ;-- CHARSET function historically has a refinement called /LENGTH, that + ;-- is used to preallocate bits. Yet the LENGTH? function has been + ;-- changed to use just the word LENGTH. We could change this to + ;-- /CAPACITY SIZE or something similar, but keep it working for now. + ;-- + length_CHARSET: length ; refinement passed in + unset 'length ; helps avoid overlooking the ambiguity + + either length_CHARSET [append make bitset! len chars] [make bitset! chars] ] + array: func [ - "Makes and initializes a series of a given size." - size [integer! block!] "Size or block of sizes for each dimension" - /initial "Specify an initial value for all elements" - value "Initial value (will be called each time if a function)" - /local block rest + "Makes and initializes a series of a given size." + size [integer! block!] "Size or block of sizes for each dimension" + /initial "Specify an initial value for all elements" + value "Initial value (will be called each time if a function)" + /local block rest ][ - if block? size [ - if tail? rest: next size [rest: none] - unless integer? set/any 'size first size [ - cause-error 'script 'expect-arg reduce ['array 'size type? :size] - ] - ] - block: make block! size - case [ - block? rest [ - loop size [block: insert/only block array/initial rest :value] - ] - series? :value [ - loop size [block: insert/only block copy/deep value] - ] - any-function? :value [ ; So value can be a thunk :) - loop size [block: insert/only block value] ; Called every time - ] - insert/dup block value size - ] - head block + if block? size [ + if tail? rest: next size [rest: _] + unless integer? size: first size [ + cause-error 'script 'expect-arg reduce ['array 'size type-of :size] + ] + ] + block: make block! size + case [ + block? :rest [ + loop size [block: insert/only block array/initial rest :value] + ] + any-series? :value [ + loop size [block: insert/only block copy/deep value] + ] + function? :value [ ; So value can be a thunk :) + loop size [block: insert/only block value] ; Called every time + ] + ] else [ + insert/dup block either initial [value][_] size + ] + head block ] -replace: func [ - "Replaces a search value with the replace value within the target series." - target [series!] "Series to replace within (modified)" - search "Value to be replaced (converted if necessary)" - replace "Value to replace with (called each time if a function)" - /all "Replace all occurrences" ;!!! Note ALL is redefined in here! - /case "Case-sensitive replacement" ;!!! Note CASE is redefined in here! - /tail "Return target after the last replacement position" ;!!! Note TAIL is redefined in here! - /local save-target len value pos do-break + +replace: function [ + "Replaces a search value with the replace value within the target series." + target [any-series!] "Series to replace within (modified)" + pattern "Value to be replaced (converted if necessary)" + replacement "Value to replace with (called each time if a function)" + + ; !!! Note these refinments alias ALL, CASE, TAIL natives! + /all "Replace all occurrences" + /case "Case-sensitive replacement" + /tail "Return target after the last replacement position" + + ; Consider adding an /any refinement to use find/any, once that works. ][ - save-target: target - ; If target is a string but search is not, make search a string (except for bitset). - ; If target is a binary but search is not, make search a binary (except for bitset). - ; If target is a bitset, or a block and search is not a block, len = 1 - len: lib/case [ - bitset? :search 1 - any-string? target [ - if any [not any-string? :search tag? :search] [search: form :search] - length? :search - ] - binary? target [ - unless binary? :search [search: to-binary :search] ; Must be convertable - length? :search - ] - any-block? :search [length? :search] - true 1 - ] - ; /all and /case checked before the while, /tail after - do-break: unless all [:break] ; Will be none if not /all, a noop - while pick [ - [pos: find target :search] - [pos: find/case target :search] - ] not case [ - (value: replace pos) ; The replace argument can be a function - target: change/part pos :value len - do-break - ] - either tail [target] [save-target] + all_REPLACE: all + all: :lib/all + case_REPLACE: case + case: :lib/case + tail_REPLACE: tail + tail: :lib/tail + + save-target: target + + ; !!! These conversions being missing seems a problem with FIND the native + ; as a holdover from pre-open-source Rebol when mezzanine development + ; had no access to source (?). Correct answer is likely to fix FIND: + ; + ; >> find "abcdef" + ; >> == "cdef" ; should probably be NONE! + ; + ; >> find "abf" + ; == "cde>f" ; should be "f" + ; + ; Note that if a FORM actually happens inside of FIND, it could wind up + ; happening repeatedly in the /ALL case if that happens. + + len: case [ + ; leave bitset patterns as-is regardless of target type, len = 1 + bitset? :pattern 1 + + any-string? target [ + unless string? :pattern [pattern: form :pattern] + length-of :pattern + ] + + binary? target [ + ; Target is binary, pattern is not, make pattern a binary + unless binary? :pattern [pattern: to-binary :pattern] + length-of :pattern + ] + + any-block? :pattern [length-of :pattern] + ] else 1 + + while [pos: find/(all [case_REPLACE 'case]) target :pattern] [ + ; apply replacement if function, or drops pos if not + ; the parens quarantine function invocation to maximum arity of 1 + (value: replacement pos) + + target: change/part pos :value len + + unless all_REPLACE [break] + ] + + either tail_REPLACE [target] [save-target] ] -; We need to consider adding an /any refinement to use find/any, once that works. - -reword: func [ - "Make a string or binary based on a template and substitution values." - source [any-string! binary!] "Template series with escape sequences" - values [map! object! block!] "Keyword literals and value expressions" - /case "Characters are case-sensitive" ;!!! Note CASE is redefined in here! - /only "Use values as-is, do not reduce the block, insert block values" - /escape "Choose your own escape char(s) or [begin end] delimiters" - char [char! any-string! binary! block! none!] {Default "$"} - /into "Insert into a buffer instead (returns position after insert)" - output [any-string! binary!] "The buffer series (modified)" - /local char-end vals word wtype cword out fout rule a b w v + + +reword: function [ + "Make a string or binary based on a template and substitution values." + + source [any-string! binary!] + "Template series with escape sequences" + values [map! object! block!] + "Keyword literals and value expressions" + /case + "Characters are case-sensitive" ;!!! Note CASE is redefined in here! + /escape + "Choose your own escape char(s) or [prefix suffix] delimiters" + delimiters [blank! char! any-string! word! binary! block!] + {Default "$"} + ; Note: since blank is being taken deliberately, it's not possible + ; to use the defaulting feature, e.g. () + /into + "Insert into a buffer instead (returns position after insert)" + output [any-string! binary!] + "The buffer series (modified)" + + + + ; Note: this list should be the same as above with delimiters, with + ; BLOCK! excluded. + ; + delimiter-types ( + make typeset! [blank! char! any-string! word! binary!] + ) + keyword-types ( + make typeset! [blank! char! any-string! integer! word! binary!] + ) ][ - assert/type [local none!] ; Prevent locals injection - unless into [output: make source length? source] - ; Determine the datatype to convert the keywords to internally - ; Case-sensitive map keys must be binary, tags are special-cased by parse - wtype: lib/case [case binary! tag? source string! 'else type? source] - ; Determine the escape delimiter(s), if any - lib/case/all [ - not escape [char: "$"] - block? char [ - ; Have to use parse here because ASSERT/type is broken - rule: [char! | any-string! | binary!] - unless parse c: char [set char rule set char-end opt rule] [ - cause-error 'script 'invalid-arg reduce [c] - ] - ] - char? char [char: to wtype char] - char? char-end [char-end: to wtype char-end] - ] - lib/case [ - ; Check whether values is a map of the kind we can use internally - all [ - map? values ; Must be a map to use series keys with no dups - empty? char-end ; If we have char-end, it gets appended to the keys - foreach [w v] values [ - ; Key types must match wtype and no unset values allowed - if any [unset? :v wtype <> type? :w] [break/return false] - true - ] - ] [vals: values] ; Success, so use it - ; Otherwise, convert keywords to wtype and eliminate duplicates and empties - ; Last duplicate keyword wins; empty keywords, unset or none vals removed - ; Any trailing delimiter is added to the end of the key for convenience - all [ - vals: make map! length? values ; Make a new map internally - not only block? values ; Should we evaluate value expressions? - ] [ - while [not tail? values] [ - w: first+ values ; Keywords are not evaluated - set/any 'v do/next values 'values - if any [set-word? :w lit-word? :w] [w: to word! :w] - lib/case [ - wtype = type? :w none - wtype <> binary! [w: to wtype :w] - any-string? :w [w: to binary! :w] - 'else [w: to binary! to string! :w] - ] - unless empty? w [ - unless empty? char-end [w: append copy w char-end] - poke vals w unless unset? :v [:v] - ] - ] - ] - 'else [ ; /only doesn't apply, just assign raw values - foreach [w v] values [ ; foreach can be used on all values types - if any [set-word? :w lit-word? :w] [w: to word! :w] - lib/case [ - wtype = type? :w none - wtype <> binary! [w: to wtype :w] - any-string? :w [w: to binary! :w] - 'else [w: to binary! to string! :w] - ] - unless empty? w [ - unless empty? char-end [w: append copy w char-end] - poke vals w unless unset? :v [:v] - ] - ] - ] - ] - ; Construct the reword rule - word: make block! 2 * length? vals - foreach w vals [word: reduce/into [w '|] word] - word: head remove back word - ; Convert keyword if the type doesn't match - cword: pick [(w: to wtype w)] wtype <> type? source - set/any [out: fout:] pick [ - [ ; Convert to string if type combination needs it - (output: insert output to string! copy/part a b) - (output: insert output to string! a) - ][ ; ... otherwise just insert it directly - (output: insert/part output a b) - (output: insert output a) - ] - ] or~ tag? source and~ binary? source not binary? output - escape: [ - copy w word cword out ( - output: insert output lib/case [ - block? v: select vals w [either only [v] :v] - any-function? :v [apply :v [:b]] - 'else :v - ] - ) a: - ] - rule: either empty? char [ - ; No starting escape string, use TO multi - [a: any [to word b: [escape | skip]] to end fout] - ][ - ; Starting escape string defined, use regular TO - if wtype <> type? char [char: to wtype char] - [a: any [to char b: char [escape | none]] to end fout] - ] - either case [parse/case source rule] [parse source rule] - ; Return end of output with /into, head otherwise - either into [output] [head output] + case_REWORD: case + case: :lib/case + + unless into [output: make (type-of source) length-of source] + + prefix: _ + suffix: _ + case [ + not set? 'delimiters [ + prefix: "$" + ] + + block? delimiters [ + unless (parse delimiters [ + set prefix delimiter-types + set suffix opt delimiter-types + ])[ + fail ["Invalid /ESCAPE delimiter block" delimiters] + ] + ] + + true [ + assert [maybe? delimiter-types prefix] + prefix: delimiters + ] + ] + + ; MAKE MAP! will create a map with no duplicates from the input if it + ; is a BLOCK!. This might be better with stricter checking, in case + ; later keys overwrite earlier ones and obscure the invalidity of the + ; earlier keys (or perhaps MAKE MAP! itself should disallow duplicates) + ; + ; !!! To be used as keys, any series in the block will have to be LOCK'd. + ; This could either be done with copies of the keys in the block, or + ; locking them directly. For now, the whole block is locked before the + ; MAKE MAP! call. + ; + if block? values [ + values: make map! lock values + ] + + ; The keyword matching rule is a series of [OR'd | clauses], where each + ; clause has GROUP! code in it to remember which keyword matched, which + ; it stores in this variable. It's necessary to know the exact form of + ; the matched keyword in order to look it up in the values MAP!, as trying + ; to figure this out based on copying data out of the source series would + ; need to do a lot of reverse-engineering of the types. + ; + match: _ + + ; Note that the enclosing rule has to account for `prefix` and `suffix`, + ; this just matches the keywords themselves, setting `match` if one did. + ; + any-keyword-rule: collect [ + for-each [keyword value] values [ + unless maybe? keyword-types keyword [ + fail ["Invalid keyword type:" keyword] + ] + + keep reduce [ + ; Rule for matching the keyword in the PARSE. Although it + ; is legal to search for BINARY! in ANY-STRING! and vice + ; versa due to UTF-8 conversion, keywords can also be WORD!, + ; and neither `parse "abc" [abc]` nor `parse "abc" ['abc]` + ; will work...so the keyword must be string converted for + ; the purposes of this rule. + ; + either maybe? [integer! word!] keyword [ + to-string keyword + ][ + keyword + ] + + ; GROUP! execution code for remembering which keyword matched. + ; We want the actual keyword as-is in the MAP! key, not any + ; variation modified to + ; + ; Note also that getting to this point doesn't mean a full + ; match necessarily happened, as the enclosing rule may have + ; a `suffix` left to take into account. + ; + as group! compose [match: quote (keyword)] + ] + + keep [ + | + ] + ] + keep 'fail ;-- add failure if no match, instead of removing last | + ] + + ; Note that `any-keyword-rule` will look something like: + ; + ; [ + ; "keyword1" (match: quote keyword1) + ; | "keyword2" (match: quote keyword2) + ; | fail + ; ] + + ; To be used in a parse rule, words must be turned into strings, though + ; it would be nice if they didn't have to be, e.g. + ; + ; parse "abc" [quote abc] => true + ; + ; Integers have to be converted also. + ; + if maybe [integer! word!] prefix [prefix: to-string prefix] + if maybe [integer! word!] suffix [suffix: to-string suffix] + + rule: [ + ; Begin marking text to copy verbatim to output + a: + + any [ + ; Seek to the prefix. Note that the prefix may be BLANK!, in + ; which case this is a no-op. + ; + to prefix + + ; End marking text to copy verbatim to output + b: + + ; Consume the prefix (again, this could be a no-op, which means + ; there's no guarantee we'll be at the start of a match for + ; an `any-keyword-rule` + ; + prefix + + [ + [ + any-keyword-rule suffix ( + ; + ; Output any leading text before the prefix was seen + ; + output: insert/part output a b + + v: select values match + output: insert output case [ + function? :v [v :match] + block? :v [do :v] + true [:v] + ] + ) + + ; Restart mark of text to copy verbatim to output + a: + ] + | + ; Because we might not be at the head of an any-keyword rule + ; failure to find a match at this point needs to SKIP to keep + ; the ANY rule scanning forward. + ; + skip + ] + ] + + ; Seek to end, just so rule succeeds + ; + to end + + ; Finalize the output, such that any remainder is transferred verbatim + ; + (output: insert output a) + ] + + unless parse/(all [case_REWORD 'case]) source rule [ + fail "Unexpected error in REWORD's parse rule, should not happen." + ] + + ; Return end of output with /into, head otherwise + ; + either into [output] [head output] ] -; It's big, it's complex, but it works. Placeholder for a native. + move: func [ - "Move a value or span of values in a series." - source [series!] "Source series (modified)" - offset [integer!] "Offset to move by, or index to move to" - /part "Move part of a series" - length [integer!] "The length of the part to move" - /skip "Treat the series as records of fixed size" ;; SKIP redefined - size [integer!] "Size of each record" - /to "Move to an index relative to the head of the series" ;; TO redefined + "Move a value or span of values in a series." + source [any-series!] "Source series (modified)" + offset [integer!] "Offset to move by, or index to move to" + /part "Move part of a series" + limit [integer!] "The length of the part to move" + /skip "Treat the series as records of fixed size" ;; SKIP redefined + size [integer!] "Size of each record" + /to "Move to an index relative to the head of the series" ;; TO redefined ][ - unless length [length: 1] - if skip [ - if 1 > size [cause-error 'script 'out-of-range size] - offset: either to [offset - 1 * size + 1] [offset * size] - length: length * size - ] - part: take/part source length - insert either to [at head source offset] [ - lib/skip source offset - ] part + unless limit [limit: 1] + if skip [ + if 1 > size [cause-error 'script 'out-of-range size] + offset: either to [offset - 1 * size + 1] [offset * size] + limit: limit * size + ] + part: take/part source limit + insert either to [at head source offset] [ + lib/skip source offset + ] part ] + extract: func [ - "Extracts a value from a series at regular intervals." - series [series!] - width [integer!] "Size of each entry (the skip)" - /index "Extract from an offset position" - pos "The position(s)" [number! logic! block!] - /default "Use a default value instead of none" - value "The value to use (will be called each time if a function)" - /into "Insert into a buffer instead (returns position after insert)" - output [series!] "The buffer series (modified)" - /local len val + "Extracts a value from a series at regular intervals." + series [any-series!] + width [integer!] "Size of each entry (the skip)" + /index "Extract from an offset position" + pos "The position(s)" [any-number! logic! block!] + /default "Use a default value instead of blank" + value "The value to use (will be called each time if a function)" + /into "Insert into a buffer instead (returns position after insert)" + output [any-series!] "The buffer series (modified)" + /local len val ][ ; Default value is "" for any-string! output - if zero? width [return any [output make series 0]] ; To avoid an infinite loop - len: either positive? width [ ; Length to preallocate - divide length? series width ; Forward loop, use length - ][ - divide index? series negate width ; Backward loop, use position - ] - unless index [pos: 1] - either block? pos [ - unless parse pos [some [number! | logic!]] [cause-error 'Script 'invalid-arg reduce [pos]] - unless output [output: make series len * length? pos] - if all [not default any-string? output] [value: copy ""] - forskip series width [forall pos [ - if none? set/any 'val pick series pos/1 [set/any 'val value] - output: insert/only output :val - ]] - ][ - unless output [output: make series len] - if all [not default any-string? output] [value: copy ""] - forskip series width [ - if none? set/any 'val pick series pos [set/any 'val value] - output: insert/only output :val - ] - ] - either into [output] [head output] + if zero? width [return any [output make series 0]] ; To avoid an infinite loop + len: either positive? width [ ; Length to preallocate + divide (length-of series) width ; Forward loop, use length + ][ + divide index-of series negate width ; Backward loop, use position + ] + unless index [pos: 1] + either block? pos [ + unless parse pos [some [any-number! | logic!]] [cause-error 'Script 'invalid-arg reduce [pos]] + if void? :output [output: make series len * length-of pos] + if all [not default any-string? output] [value: copy ""] + for-skip series width [for-next pos [ + if void? val: pick series pos/1 [val: value] + output: insert/only output :val + ]] + ][ + if void? :output [output: make series len] + if all [not default any-string? output] [value: copy ""] + for-skip series width [ + if void? val: pick series pos [val: value] + output: insert/only output :val + ] + ] + either into [output] [head output] ] + alter: func [ - "Append value if not found, else remove it; returns true if added." - series [series! port! bitset!] {(modified)} - value - /case "Case-sensitive comparison" + "Append value if not found, else remove it; returns true if added." + + series [any-series! port! bitset!] {(modified)} + value + /case + "Case-sensitive comparison" ][ - if bitset? series [ - return either find series :value [ - remove/part series :value false - ][ - append series :value true - ] - ] - found? unless remove ( - either case [find/case series :value] [find series :value] - ) [append series :value] + case_ALTER: case + case: :lib/case + + if bitset? series [ + return either find series :value [ + remove/part series :value false + ][ + append series :value true + ] + ] + unless? remove (find/(all [case_ALTER ['case]]) series :value) [ + append series :value ;-- returns true if this branch runs, false if not + ] ] -collect: func [ - "Evaluates a block, storing values via KEEP function, and returns block of collected values." - body [block!] "Block to evaluate" - /into "Insert into a buffer instead (returns position after insert)" - output [series!] "The buffer series (modified)" + +collect-with: func [ + "Evaluate body, and return block of values collected via keep function." + + return: [block!] + 'name [word! lit-word!] + "Name to which keep function will be assigned ( if word!)" + body [block!] + "Block to evaluate" + /into + "Insert into a buffer instead (returns position after insert)" + output [any-series!] + "The buffer series (modified)" + + keeper: ;-- local ][ - unless output [output: make block! 16] - do func [keep] body func [value [any-type!] /only] [ - output: apply :insert [output :value none none only] - :value - ] - either into [output] [head output] + output: any [:output make block! 16] + + keeper: func [ + return: [ any-value!] + value [ any-value!] + /only + ][ + output: insert/(all [only 'only]) output :value + :value + ] + + either word? name [ + ; + ; A word `name` indicates that the body is not already bound to + ; that word. FUNC does binding and variable creation so let it + ; do the work. + ; + eval func compose [(name) [function!] return] body :keeper + ][ + ; A lit-word `name` indicates that the word for the keeper already + ; exists. Set the variable and DO the body bound as-is. + ; + set name :keeper + do body + ] + + either into [output] [head output] ] + +; Classic version of COLLECT which assumes that the word you want to use +; is KEEP, and that the body needs to be deep copied and rebound (via FUNC) +; to a new variable to hold the keeping function. +; +collect: specialize :collect-with [name: 'keep] + + format: function [ - "Format a string according to the format dialect." - rules {A block in the format dialect. E.g. [10 -10 #"-" 4]} - values - /pad p + "Format a string according to the format dialect." + rules {A block in the format dialect. E.g. [10 -10 #"-" 4]} + values + /pad p ][ - p: any [p #" "] - unless block? :rules [rules: reduce [:rules]] - unless block? :values [values: reduce [:values]] - - ; Compute size of output (for better mem usage): - val: 0 - foreach rule rules [ - if word? :rule [rule: get rule] - val: val + switch/default type?/word :rule [ - integer! [abs rule] - string! [length? rule] - char! [1] - ][0] - ] - - out: make string! val - insert/dup out p val - - ; Process each rule: - foreach rule rules [ - if word? :rule [rule: get rule] - switch type?/word :rule [ - integer! [ - pad: rule - val: form first+ values - clear at val 1 + abs rule - if negative? rule [ - pad: rule + length? val - if negative? pad [out: skip out negate pad] - pad: length? val - ] - change out :val - out: skip out pad ; spacing (remainder) - ] - string! [out: change out rule] - char! [out: change out rule] - ] - ] - - ; Provided enough rules? If not, append rest: - if not tail? values [append out values] - head out + p: any [:p #" "] + unless block? :rules [rules: reduce [:rules]] + unless block? :values [values: reduce [:values]] + + ; Compute size of output (for better mem usage): + val: 0 + for-each rule rules [ + if word? :rule [rule: get rule] + + val: val + (switch type-of :rule [ + :integer! [abs rule] + :string! [length-of rule] + :char! [1] + ] else 0) + ] + + out: make string! val + insert/dup out p val + + ; Process each rule: + for-each rule rules [ + if word? :rule [rule: get rule] + + switch type-of :rule [ + :integer! [ + pad: rule + val: form first+ values + clear at val 1 + abs rule + if negative? rule [ + pad: rule + length-of val + if negative? pad [out: skip out negate pad] + pad: length-of val + ] + change out :val + out: skip out pad ; spacing (remainder) + ] + :string! [out: change out rule] + :char! [out: change out rule] + ] + ] + + ; Provided enough rules? If not, append rest: + if not tail? values [append out values] + head out ] -printf: func [ - "Formatted print." - fmt "Format" - val "Value or block of values" + +printf: proc [ + "Formatted print." + fmt "Format" + val "Value or block of values" ][ - print format :fmt :val + print format :fmt :val ] -split: func [ - "Split a series into pieces; fixed or variable size, fixed number, or at delimiters" - series [series!] "The series to split" - dlm [block! integer! char! bitset! any-string!] "Split size, delimiter(s), or rule(s)." - /into "If dlm is an integer, split into n pieces, rather than pieces of length n." - /local size piece-size count mk1 mk2 res fill-val add-fill-val + +split: function [ + "Split series in pieces: fixed/variable size, fixed number, or delimited" + + series [any-series!] + "The series to split" + dlm [block! integer! char! bitset! any-string!] + "Split size, delimiter(s), or rule(s)." + /into + "If dlm is integer, split in n pieces rather than pieces of length n." ][ - either all [block? dlm parse dlm [some integer!]] [ - map-each len dlm [ - either positive? len [ - copy/part series series: skip series len - ] [ - series: skip series negate len - ; return unset so that nothing is added to output - () - ] - ] - ][ - size: dlm ; alias for readability - res: collect [ - parse/all series case [ - all [integer? size into] [ - if size < 1 [cause-error 'Script 'invalid-arg size] - count: size - 1 - piece-size: to integer! round/down divide length? series size - if zero? piece-size [piece-size: 1] - [ - count [copy series piece-size skip (keep/only series)] - copy series to end (keep/only series) - ] - ] - integer? dlm [ - if size < 1 [cause-error 'Script 'invalid-arg size] - [any [copy series 1 size skip (keep/only series)]] - ] - 'else [ ; = any [bitset? dlm any-string? dlm char? dlm] - [any [mk1: some [mk2: dlm break | skip] (keep/only copy/part mk1 mk2)]] - ] - ] - ] - ;-- Special processing, to handle cases where the spec'd more items in - ; /into than the series contains (so we want to append empty items), - ; or where the dlm was a char/string/charset and it was the last char - ; (so we want to append an empty field that the above rule misses). - fill-val: does [copy either any-block? series [[]] [""]] - add-fill-val: does [append/only res fill-val] - case [ - all [integer? size into] [ - ; If the result is too short, i.e., less items than 'size, add - ; empty items to fill it to 'size. - ; We loop here, because insert/dup doesn't copy the value inserted. - if size > length? res [ - loop (size - length? res) [add-fill-val] - ] - ] - ; integer? dlm [ - ; ] - 'else [ ; = any [bitset? dlm any-string? dlm char? dlm] - ; If the last thing in the series is a delimiter, there is an - ; implied empty field after it, which we add here. - case [ - bitset? dlm [ - ; ATTEMPT is here because LAST will return NONE for an - ; empty series, and finding none in a bitest is not allowed. - if attempt [find dlm last series] [add-fill-val] - ] - char? dlm [ - if dlm = last series [add-fill-val] - ] - string? dlm [ - if all [ - find series dlm - empty? find/last/tail series dlm - ] [add-fill-val] - ] - ] - ] - ] - - res - ] + either all [block? dlm | parse dlm [some integer!]] [ + map-each len dlm [ + either positive? len [ + copy/part series series: skip series len + ][ + series: skip series negate len + continue ;-- don't add to output + ] + ] + ][ + size: dlm ; alias for readability + + res: collect [ + parse series (case [ + all [integer? size | into] [ + if size < 1 [cause-error 'Script 'invalid-arg size] + count: size - 1 + piece-size: ( + to integer! round/down divide length-of series size + ) + if zero? piece-size [piece-size: 1] + [ + count [copy series piece-size skip (keep/only series)] + copy series to end (keep/only series) + ] + ] + integer? dlm [ + if size < 1 [cause-error 'Script 'invalid-arg size] + [any [copy series 1 size skip (keep/only series)]] + ] + ] else [ + ; !!! It appears from the tests that dlm is allowed to be a + ; block, in which case it acts as a parse rule. At least, + ; there was a test that uses the feature. This would not + ; apply to parse rules that were all integers, e.g. [1 1 1], + ; since those style blocks are handled by the other branch. + ; + assert [maybe [bitset! any-string! char! block!] dlm] + [ + any [mk1: some [mk2: dlm break | skip] ( + keep/only copy/part mk1 mk2 + )] + ] + ]) + ] + + ; Special processing, to handle cases where the spec'd more items in + ; /into than the series contains (so we want to append empty items), + ; or where the dlm was a char/string/charset and it was the last char + ; (so we want to append an empty field that the above rule misses). + ; + fill-val: does [copy either any-block? series [[]] [""]] + add-fill-val: does [append/only res fill-val] + case [ + all [integer? size | into] [ + ; + ; If the result is too short, i.e., less items than 'size, add + ; empty items to fill it to 'size. + ; + ; We loop here as insert/dup doesn't copy the value inserted. + ; + if size > length-of res [ + loop (size - length-of res) [add-fill-val] + ] + ] + integer? dlm [] + ] + else [ + assert [maybe [bitset! any-string! char! block!] dlm] + + ; If the last thing in the series is a delimiter, there is an + ; implied empty field after it, which we add here. + ; + case [ + bitset? dlm [ + ; + ; ATTEMPT is here because LAST will return void for an + ; empty series, and FIND of void is not allowed. + ; + if attempt [find dlm last series] [add-fill-val] + ] + + char? dlm [ + if dlm = last series [add-fill-val] + ] + + string? dlm [ + if all [ + find series dlm + empty? find/last/tail series dlm + ] [add-fill-val] + ] + + block? dlm [ + ;-- nothing was here. + ] + ] + ] + + + res + ] ] -find-all: func [ - "Find all occurrences of a value within a series (allows modification)." - 'series [word!] "Variable for block, string, or other series" - value - body [block!] "Evaluated for each occurrence" - /local orig result + +find-all: function [ + "Find all occurrences of a value within a series (allows modification)." + + 'series [word!] + "Variable for block, string, or other series" + value + body [block!] + "Evaluated for each occurrence" ][ - assert [series? orig: get series] - set/any 'result while [set series find get series :value] [ - do body - ++ (series) - ] - unless get series [set series orig] - :result + verify [any-series? orig: get series] + while [any [set series find get series :value (set series orig false)]] [ + do body + ++ (series) + ] ] diff --git a/src/mezz/mezz-shell.r b/src/mezz/mezz-shell.r index d26ef7c02d..ce1fee26ee 100644 --- a/src/mezz/mezz-shell.r +++ b/src/mezz/mezz-shell.r @@ -1,40 +1,47 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Mezzanine: Shell-like Command Functions" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Mezzanine: Shell-like Command Functions" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } ] -ls: :list-dir -pwd: :what-dir -rm: :delete -mkdir: :make-dir +ls: :list-dir +pwd: :what-dir + +rm: does [ + fail "Use DELETE, not RM (Rebol REMOVE is different, shell dialect coming)" +] + +mkdir: :make-dir cd: func [ - "Change directory (shell shortcut function)." - 'path [file! word! path! unset! string!] "Accepts %file, :variables and just words (as dirs)" + "Change directory (shell shortcut function)." + 'path [ file! word! path! string!] + "Accepts %file, :variables and just words (as dirs)" ][ - switch type?/word :path [ - unset! [print what-dir] - file! [change-dir path] - string! [change-dir to-rebol-file path] - word! path! [change-dir to-file path] - ] + switch type-of :path [ + _ [print what-dir] + :file! [change-dir path] + :string! [change-dir to-rebol-file path] + :word! :path! [change-dir to-file path] + ] ] more: func [ - "Print file (shell shortcut function)." - 'file [file! word! path! string!] "Accepts %file and also just words (as file names)" + "Print file (shell shortcut function)." + 'file [file! word! path! string!] + "Accepts %file and also just words (as file names)" ][ - print deline to-string read switch type?/word :file [ - file! [file] - string! [to-rebol-file file] - word! path! [to-file file] - ] + ; !!! to-word necessary as long as OPTIONS_DATATYPE_WORD_STRICT exists + print deline to-string read switch to-word type-of :file [ + file! [file] + string! [to-rebol-file file] + word! path! [to-file file] + ] ] diff --git a/src/mezz/mezz-tail.r b/src/mezz/mezz-tail.r index b135e93b7f..81b7b7e6f8 100644 --- a/src/mezz/mezz-tail.r +++ b/src/mezz/mezz-tail.r @@ -1,26 +1,14 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Mezzanine: End of Mezz" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Mezzanine: End of Mezz" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } ] -funco: :func ; save it for expert usage - -; Final FUNC definition: -func: funco [ - {Defines a user function with given spec and body.} - spec [block!] {Help string (opt) followed by arg words (and opt type and string)} - body [block!] {The body block of the function} -][ - make function! copy/deep reduce [spec body] ; (now it deep copies) -] - -; Quick test runner (temporary): -t: does [do %test.r] +;-- used to finalize the definition of FUNC, now native diff --git a/src/mezz/mezz-types.r b/src/mezz/mezz-types.r index 2edb04ce8a..d52e093edf 100644 --- a/src/mezz/mezz-types.r +++ b/src/mezz/mezz-types.r @@ -1,38 +1,55 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Mezzanine: To-Type Helpers" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Mezzanine: To-Type Helpers" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } ] -; These must be listed now, because there is no longer a global context for mezz functions: -; Are we sure we really want all these?? -Carl A108 -to-logic: to-integer: to-decimal: to-percent: to-money: to-char: to-pair: -to-tuple: to-time: to-date: to-binary: to-string: to-file: to-email: to-url: to-tag: -to-bitset: to-image: to-vector: to-block: to-paren: +; !!! Carl wrote "Are we sure we really want all these?" as a comment here. +; Discussion of eliminating the TO-XXX functions in favor of TO XXX! resolved +; to say that many people prefer them...and that they also may serve a point +; by showing a list of legal conversion types in the help. They also could +; have refinements giving slightly different abilities than the default +; unrefined TO XXX! behavior would give. + +; These must be listed explicitly in order for the words to be collected +; as legal "globals" for the mezzanine context (otherwise SET would fail) + +; Note that TO-INTEGER and TO-STRING are currently their own natives with +; additional refinements, and thus should not be overwritten here + +to-logic: to-decimal: to-percent: to-money: to-char: to-pair: +to-tuple: to-time: to-date: to-binary: to-file: to-email: to-url: to-tag: +to-bitset: to-image: to-vector: to-block: to-group: to-path: to-set-path: to-get-path: to-lit-path: to-map: to-datatype: to-typeset: to-word: to-set-word: to-get-word: to-lit-word: to-refinement: to-issue: to-command: to-closure: to-function: to-object: to-module: to-error: to-port: to-gob: to-event: - none + blank ; Auto-build the functions for the above TO-* words. use [word] [ - foreach type system/catalog/datatypes [ - ; The list above determines what will be made here: - if in lib word: make word! head remove back tail ajoin ["to-" type] [ - ; Add doc line only if this build has autodocs: - set in lib :word func either string? first spec-of :make [ - reduce [reform ["Converts to" form type "value."] 'value] - ][ - [value] - ] compose [to (type) :value] - ] - ] + for-each type system/catalog/datatypes [ + word: make word! head remove back tail unspaced ["to-" type] + + ; The list above determines what will be made here, but we must not + ; overwrite any NATIVE! implementations. (e.g. TO-INTEGER is a + ; native with a refinement for interpreting as unsigned.) + + if all [ + word: in lib word + blank? get word + ][ + set word make function! compose/deep [ + [(spaced ["Converts to" form type "value."]) value] + [to (type) :value] + ] + ] + ] ] diff --git a/src/mezz/prot-http.r b/src/mezz/prot-http.r index 21f5b5d800..4b5cf88675 100644 --- a/src/mezz/prot-http.r +++ b/src/mezz/prot-http.r @@ -1,522 +1,749 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 HTTP protocol scheme" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Name: 'http - Type: 'module - Version: 0.1.0 - File: %prot-http.r - Purpose: { - This program defines the HTTP protocol scheme for REBOL 3. - } - Author: "Gabriele Santilli" - Date: 22-Jun-2007 + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 HTTP protocol scheme" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Name: http + Type: module + File: %prot-http.r + Version: 0.1.47 + Purpose: { + This program defines the HTTP protocol scheme for REBOL 3. + } + Author: ["Gabriele Santilli" "Richard Smolak"] + Date: 26-Nov-2012 + History: [ + 8-Oct-2015 {Modified by @GrahamChiu to return an error object with + the info object when manual redirect required} + ] ] -sync-op: func [port body /local state] [ - unless port/state [open port port/state/close?: yes] - state: port/state - state/awake: :read-sync-awake - do body - if state/state = 'ready [do-request port] - unless port? wait [state/connection port/spec/timeout] [http-error "Timeout"] - body: copy port - if state/close? [close port] - body +digit: charset [#"0" - #"9"] +alpha: charset [#"a" - #"z" #"A" - #"Z"] +idate-to-date: function [date [string!]] [ + either parse date [ + 5 skip + copy day: 2 digit + space + copy month: 3 alpha + space + copy year: 4 digit + space + copy time: to space + space + copy zone: to end + ][ + if zone = "GMT" [zone: copy "+0"] + to date! unspaced [day "-" month "-" year "/" time zone] + ][ + blank + ] ] -read-sync-awake: func [event [event!] /local error] [ - switch/default event/type [ - connect ready [ - do-request event/port - false - ] - done [ - true - ] - close [ - true - ] - error [ - error: event/port/state/error - event/port/state/error: none - do error - ] - ] [ - false - ] + +sync-op: function [port body] [ + unless port/state [ + open port + port/state/close?: yes + ] + + state: port/state + state/awake: :read-sync-awake + + do body + + if state/state = 'ready [do-request port] + + ; Wait in a WHILE loop so the timeout cannot occur during 'reading-data + ; state. The timeout should be triggered only when the response from + ; the other side exceeds the timeout value. + ; + until [find [ready close] state/state] [ + unless port? wait [state/connection port/spec/timeout] [ + fail make-http-error "Timeout" + ] + if state/state = 'reading-data [ + read state/connection + ] + ] + + body: copy port + + if state/close? [close port] + + either port/spec/debug [ + state/connection/locals + ][ + body + ] +] + +read-sync-awake: function [event [event!]] [ + switch event/type [ + connect + ready [ + do-request event/port + false + ] + done [ + true + ] + close [ + true + ] + error [ + error: event/port/state/error + event/port/state/error: _ + fail error + ] + ] else false ] -http-awake: func [event /local port http-port state awake res] [ - port: event/port - http-port: port/locals - state: http-port/state - if any-function? :http-port/awake [state/awake: :http-port/awake] - awake: :state/awake - switch/default event/type [ - read [ - awake make event! [type: 'read port: http-port] - check-response http-port - ] - wrote [ - awake make event! [type: 'wrote port: http-port] - state/state: 'reading-headers - read port - false - ] - lookup [open port false] - connect [ - state/state: 'ready - awake make event! [type: 'connect port: http-port] - ] - close [ - res: switch state/state [ - ready [ - awake make event! [type: 'close port: http-port] - ] - doing-request reading-headers [ - state/error: make-http-error "Server closed connection" - awake make event! [type: 'error port: http-port] - ] - reading-data [ - either any [integer? state/info/headers/content-length state/info/headers/transfer-encoding = "chunked"] [ - state/error: make-http-error "Server closed connection" - awake make event! [type: 'error port: http-port] - ] [ - any [ - awake make event! [type: 'done port: http-port] - awake make event! [type: 'close port: http-port] - ] - ] - ] - ] - close http-port - res - ] - ] [true] + +http-awake: function [event] [ + port: event/port + http-port: port/locals + state: http-port/state + if function? :http-port/awake [state/awake: :http-port/awake] + awake: :state/awake + switch event/type [ + read [ + awake make event! [type: 'read port: http-port] + check-response http-port + ] + wrote [ + awake make event! [type: 'wrote port: http-port] + state/state: 'reading-headers + read port + false + ] + lookup [open port false] + connect [ + state/state: 'ready + awake make event! [type: 'connect port: http-port] + ] + close [ + res: switch state/state [ + ready [ + awake make event! [type: 'close port: http-port] + ] + doing-request reading-headers [ + state/error: make-http-error "Server closed connection" + awake make event! [type: 'error port: http-port] + ] + reading-data [ + either any [ + integer? state/info/headers/content-length + state/info/headers/transfer-encoding = "chunked" + ][ + state/error: make-http-error "Server closed connection" + awake make event! [type: 'error port: http-port] + ] [ + ;set state to CLOSE so the WAIT loop in 'sync-op can be interrupted --Richard + state/state: 'close + any [ + awake make event! [type: 'done port: http-port] + awake make event! [type: 'close port: http-port] + ] + ] + ] + ] + close http-port + res + ] + ] else true ] + make-http-error: func [ - "Make an error for the HTTP protocol" - message [string! block!] + "Make an error for the HTTP protocol" + msg [string! block!] + /inf obj + /otherhost new-url [url!] ] [ - if block? message [message: ajoin message] - make error! [ - type: 'Access - id: 'Protocol - arg1: message - ] -] -http-error: func [ - "Throw an error for the HTTP protocol" - message [string! block!] -] [ - do make-http-error message + ; cannot call it "message" because message is the error template. :-/ + ; hence when the error is created it has message defined as blank, and + ; you have to overwrite it if you're doing a custom template, e.g. + ; + ; make error! [message: ["the" :animal "has claws"] animal: "cat"] + ; + ; A less keyword-y solution is being pursued, however this error template + ; name of "message" existed before. It's just that the object creation + ; with derived fields in the usual way wasn't working, so you didn't + ; know. Once it was fixed, the `message` variable name here caused + ; a conflict where the error had no message. + + if block? msg [msg: unspaced msg] + case [ + inf [ + make error! [ + type: 'Access + id: 'Protocol + arg1: msg + arg2: obj + ] + ] + otherhost [ + make error! [ + type: 'Access + id: 'Protocol + arg1: msg + arg3: new-url + ] + ] + ] else [ + make error! [ + type: 'Access + id: 'Protocol + arg1: msg + ] + ] ] + make-http-request: func [ - "Create an HTTP request (returns string!)" - method [word! string!] "E.g. GET, HEAD, POST etc." - target [file! string!] {In case of string!, no escaping is performed (eg. useful to override escaping etc.). Careful!} - headers [block!] "Request headers (set-word! string! pairs)" - content [any-string! binary! none!] {Request contents (Content-Length is created automatically). Empty string not exactly like none.} - /local result + "Create an HTTP request (returns string!)" + method [word! string!] "E.g. GET, HEAD, POST etc." + target [file! string!] + {In case of string!, no escaping is performed.} + {(eg. useful to override escaping etc.). Careful!} + headers [block!] "Request headers (set-word! string! pairs)" + content [any-string! binary! blank!] + {Request contents (Content-Length is created automatically).} + {Empty string not exactly like blank.} + /local result ] [ - result: rejoin [ - uppercase form method #" " - either file? target [next mold target] [target] - " HTTP/1.0" CRLF - ] - foreach [word string] headers [ - repend result [mold word #" " string CRLF] - ] - if content [ - content: to binary! content - repend result ["Content-Length: " length? content CRLF] - ] - append result CRLF - result: to binary! result - if content [append result content] - result + result: unspaced [ + uppercase form method space + either file? target [next mold target] [target] + space "HTTP/1.0" CRLF + ] + for-each [word string] headers [ + join result [mold word space string CRLF] + ] + if content [ + content: to binary! content + join result ["Content-Length:" space (length-of content) CRLF] + ] + append result CRLF + result: to binary! result + if content [append result content] + result ] do-request: func [ - "Perform an HTTP request" - port [port!] - /local spec info + "Perform an HTTP request" + port [port!] + /local spec info req ] [ - spec: port/spec - info: port/state/info - spec/headers: body-of make make object! [ - Accept: "*/*" - Accept-Charset: "utf-8" - Host: either spec/port-id <> 80 [ - rejoin [form spec/host #":" spec/port-id] - ] [ - form spec/host - ] - User-Agent: "REBOL" - ] spec/headers - port/state/state: 'doing-request - info/headers: info/response-line: info/response-parsed: port/data: - info/size: info/date: info/name: none - write port/state/connection - make-http-request spec/method to file! any [spec/path %/] - spec/headers spec/content + spec: port/spec + info: port/state/info + spec/headers: body-of construct has [ + Accept: "*/*" + Accept-Charset: "utf-8" + Host: either not find [80 443] spec/port-id [ + unspaced [form spec/host ":" spec/port-id] + ] [ + form spec/host + ] + User-Agent: "REBOL" + ] spec/headers + port/state/state: 'doing-request + info/headers: info/response-line: info/response-parsed: port/data: + info/size: info/date: info/name: blank + write port/state/connection + req: make-http-request spec/method any [spec/path %/] + spec/headers spec/content + net-log/C to string! req ] -parse-write-dialect: func [port block /local spec] [ - spec: port/spec - parse block [[set block word! (spec/method: block) | (spec/method: 'post)] - opt [set block [file! | url!] (spec/path: block)] [set block block! (spec/headers: block) | (spec/headers: [])] [set block [any-string! | binary!] (spec/content: block) | (spec/content: none)] - ] +parse-write-dialect: func [port block /local spec debug] [ + spec: port/spec + parse block [ + opt [ 'headers ( spec/debug: true ) ] + [set block word! (spec/method: block) | (spec/method: 'post)] + opt [set block [file! | url!] (spec/path: block)] + [set block block! (spec/headers: block) | (spec/headers: [])] + [ + set block [any-string! | binary!] (spec/content: block) + | (spec/content: blank) + ] + ] ] -check-response: func [port /local conn res headers d1 d2 line info state awake spec] [ - state: port/state - conn: state/connection - info: state/info - headers: info/headers - line: info/response-line - awake: :state/awake - spec: port/spec - if all [ - not headers - d1: find conn/data crlfbin - d2: find/tail d1 crlf2bin - ] [ - info/response-line: line: to string! copy/part conn/data d1 - info/headers: headers: construct/with d1 http-response-headers - info/name: to file! any [spec/path %/] - if headers/content-length [info/size: headers/content-length: to integer! headers/content-length] - if headers/last-modified [info/date: attempt [to date! headers/last-modified]] - remove/part conn/data d2 - state/state: 'reading-data - ] - unless headers [ - read conn - return false - ] - res: false - unless info/response-parsed [ - ;?? line - parse/all line [ - "HTTP/1." [#"0" | #"1"] some #" " [ - #"1" (info/response-parsed: 'info) - | - #"2" [["04" | "05"] (info/response-parsed: 'no-content) - | (info/response-parsed: 'ok) - ] - | - #"3" [ - "03" (info/response-parsed: 'see-other) - | - "04" (info/response-parsed: 'not-modified) - | - "05" (info/response-parsed: 'use-proxy) - | (info/response-parsed: 'redirect) - ] - | - #"4" [ - "01" (info/response-parsed: 'unauthorized) - | - "07" (info/response-parsed: 'proxy-auth) - | (info/response-parsed: 'client-error) - ] - | - #"5" (info/response-parsed: 'server-error) - ] - | (info/response-parsed: 'version-not-supported) - ] - ] - switch/all info/response-parsed [ - ok [ - either spec/method = 'head [ - state/state: 'ready - res: awake make event! [type: 'done port: port] - unless res [res: awake make event! [type: 'ready port: port]] - ] [ - res: check-data port - if all [not res state/state = 'ready] [ - res: awake make event! [type: 'done port: port] - unless res [res: awake make event! [type: 'ready port: port]] - ] - ] - ] - redirect see-other [ - either spec/method = 'head [ - state/state: 'ready - res: awake make event! [type: 'custom port: port code: 0] - ] [ - res: check-data port - ] - if all [not res state/state = 'ready] [ - either all [ - any [ - find [get head] spec/method - all [ - info/response-parsed = 'see-other - spec/method: 'get - ] - ] - in headers 'Location - ] [ - res: do-redirect port headers/location - ] [ - state/error: make-http-error "Redirect requires manual intervention" - res: awake make event! [type: 'error port: port] - ] - ] - ] - unauthorized client-error server-error proxy-auth [ - either spec/method = 'head [ - state/state: 'ready - ] [ - check-data port - ] - ] - unauthorized [ - state/error: make-http-error "Authentication not supported yet" - res: awake make event! [type: 'error port: port] - ] - client-error server-error [ - state/error: make-http-error ["Server error: " line] - res: awake make event! [type: 'error port: port] - ] - not-modified [state/state: 'ready - res: awake make event! [type: 'done port: port] - unless res [res: awake make event! [type: 'ready port: port]] - ] - use-proxy [ - state/state: 'ready - state/error: make-http-error "Proxies not supported yet" - res: awake make event! [type: 'error port: port] - ] - proxy-auth [ - state/error: make-http-error "Authentication and proxies not supported yet" - res: awake make event! [type: 'error port: port] - ] - no-content [ - state/state: 'ready - res: awake make event! [type: 'done port: port] - unless res [res: awake make event! [type: 'ready port: port]] - ] - info [ - info/headers: info/response-line: info/response-parsed: port/data: none - state/state: 'reading-headers - read conn - ] - version-not-supported [ - state/error: make-http-error "HTTP response version not supported" - res: awake make event! [type: 'error port: port] - close port - ] - ] - res +check-response: func [port /local conn res headers d1 d2 line info state awake spec body] [ + state: port/state + conn: state/connection + info: state/info + headers: info/headers + line: info/response-line + awake: :state/awake + spec: port/spec + if all [ + not headers + d1: find conn/data crlfbin + d2: find/tail d1 crlf2bin + ] [ + info/response-line: line: to string! copy/part conn/data d1 + + ; !!! In R3-Alpha, CONSTRUCT/WITH allowed passing in data that could + ; be a STRING! or a BINARY! which would be interpreted as an HTTP/SMTP + ; header. The code that did it was in a function Scan_Net_Header(), + ; that has been extracted into a completely separate native. It + ; should really be rewritten as user code with PARSE here. + ; + assert [binary? d1] + d1: scan-net-header d1 + + info/headers: headers: construct/only http-response-headers d1 + info/name: to file! any [spec/path %/] + if headers/content-length [ + info/size: + headers/content-length: + to-integer/unsigned headers/content-length + ] + if headers/last-modified [ + info/date: attempt [idate-to-date headers/last-modified] + ] + remove/part conn/data d2 + state/state: 'reading-data + if quote (txt) <> last body-of :net-log [ ; net-log is in active state + print "Dumping Webserver headers and body" + net-log/S info + if trap? [ + body: to string! conn/data + dump body + ][print ajoin ["S: " length-of conn/data " binary bytes in buffer ..."]] + ] + ] + unless headers [ + read conn + return false + ] + res: false + unless info/response-parsed [ + ;?? line + parse line [ + "HTTP/1." [#"0" | #"1"] some #" " [ + #"1" (info/response-parsed: 'info) + | + #"2" [["04" | "05"] (info/response-parsed: 'no-content) + | (info/response-parsed: 'ok) + ] + | + #"3" [ + "03" (info/response-parsed: 'see-other) + | + "04" (info/response-parsed: 'not-modified) + | + "05" (info/response-parsed: 'use-proxy) + | (info/response-parsed: 'redirect) + ] + | + #"4" [ + "01" (info/response-parsed: 'unauthorized) + | + "07" (info/response-parsed: 'proxy-auth) + | (info/response-parsed: 'client-error) + ] + | + #"5" (info/response-parsed: 'server-error) + ] + | (info/response-parsed: 'version-not-supported) + ] + ] + if all [logic? spec/debug true? spec/debug] [ + spec/debug: info + ] + switch/all info/response-parsed [ + ok [ + either spec/method = 'head [ + state/state: 'ready + res: awake make event! [type: 'done port: port] + unless res [res: awake make event! [type: 'ready port: port]] + ] [ + res: check-data port + if all [not res state/state = 'ready] [ + res: awake make event! [type: 'done port: port] + unless res [res: awake make event! [type: 'ready port: port]] + ] + ] + ] + redirect see-other [ + either spec/method = 'head [ + state/state: 'ready + res: awake make event! [type: 'custom port: port code: 0] + ] [ + res: check-data port + unless open? port [ + ;NOTE some servers(e.g. yahoo.com) don't supply content-data in the redirect header so the state/state can be left in 'reading-data after check-data call + ;I think it is better to check if port has been closed here and set the state so redirect sequence can happen. --Richard + state/state: 'ready + ] + ] + if all [not res state/state = 'ready] [ + either all [ + any [ + find [get head] spec/method + all [ + info/response-parsed = 'see-other + spec/method: 'get + ] + ] + in headers 'Location + ] [ + res: do-redirect port headers/location + ] [ + state/error: make-http-error/inf "Redirect requires manual intervention" info + res: awake make event! [type: 'error port: port] + ] + ] + ] + unauthorized client-error server-error proxy-auth [ + either spec/method = 'head [ + state/state: 'ready + ] [ + check-data port + ] + ] + unauthorized [ + state/error: make-http-error "Authentication not supported yet" + res: awake make event! [type: 'error port: port] + ] + client-error server-error [ + state/error: make-http-error ["Server error: " line] + res: awake make event! [type: 'error port: port] + ] + not-modified [state/state: 'ready + res: awake make event! [type: 'done port: port] + unless res [res: awake make event! [type: 'ready port: port]] + ] + use-proxy [ + state/state: 'ready + state/error: make-http-error "Proxies not supported yet" + res: awake make event! [type: 'error port: port] + ] + proxy-auth [ + state/error: make-http-error "Authentication and proxies not supported yet" + res: awake make event! [type: 'error port: port] + ] + no-content [ + state/state: 'ready + res: awake make event! [type: 'done port: port] + unless res [res: awake make event! [type: 'ready port: port]] + ] + info [ + info/headers: _ + info/response-line: _ + info/response-parsed: _ + port/data: _ + state/state: 'reading-headers + read conn + ] + version-not-supported [ + state/error: make-http-error "HTTP response version not supported" + res: awake make event! [type: 'error port: port] + close port + ] + ] + res ] crlfbin: #{0D0A} crlf2bin: #{0D0A0D0A} crlf2: to string! crlf2bin http-response-headers: context [ - Content-Length: - Transfer-Encoding: - Last-Modified: none + Content-Length: _ + Transfer-Encoding: _ + Last-Modified: _ ] do-redirect: func [port [port!] new-uri [url! string! file!] /local spec state] [ - spec: port/spec - state: port/state - if #"/" = first new-uri [ - new-uri: to url! ajoin [spec/scheme "://" spec/host new-uri] - ] - new-uri: construct/with decode-url new-uri port/scheme/spec - if new-uri/scheme <> 'http [ - state/error: make-http-error {Redirect to a protocol different from HTTP not supported} - return state/awake make event! [type: 'error port: port] - ] - either all [ - new-uri/host = spec/host - new-uri/port-id = spec/port-id - ] [ - spec/path: new-uri/path - do-request port - false - ] [ - state/error: make-http-error "Redirect to other host - requires custom handling" - state/awake make event! [type: 'error port: port] - ] + spec: port/spec + state: port/state + if #"/" = first new-uri [ + new-uri: as url! unspaced [spec/scheme "://" spec/host new-uri] + ] + new-uri: decode-url new-uri + unless find new-uri 'port-id [ + switch new-uri/scheme [ + 'https [append new-uri [port-id: 443]] + 'http [append new-uri [port-id: 80]] + ] + ] + new-uri: construct/only port/scheme/spec new-uri + unless find [http https] new-uri/scheme [ + state/error: make-http-error {Redirect to a protocol different from HTTP or HTTPS not supported} + return state/awake make event! [type: 'error port: port] + ] + either all [ + new-uri/host = spec/host + new-uri/port-id = spec/port-id + ] [ + spec/path: new-uri/path + ;we need to reset tcp connection here before doing a redirect + close port/state/connection + open port/state/connection + do-request port + false + ] [ + state/error: make-http-error/otherhost + "Redirect to other host - requires custom handling" + as url! unspaced [new-uri/scheme "://" new-uri/host new-uri/path] + state/awake make event! [type: 'error port: port] + ] ] check-data: func [port /local headers res data out chunk-size mk1 mk2 trailer state conn] [ - state: port/state - headers: state/info/headers - conn: state/connection - res: false - case [ - headers/transfer-encoding = "chunked" [ - data: conn/data - out: port/data: make binary! length? data - until [ - either parse/all data [ - copy chunk-size some hex-digits thru crlfbin mk1: to end - ] [ - chunk-size: to integer! to issue! chunk-size - either chunk-size = 0 [ - if parse/all mk1 [ - crlfbin (trailer: "") to end | copy trailer to crlf2bin to end - ] [ - trailer: construct trailer - append headers body-of trailer - state/state: 'ready - res: state/awake make event! [type: 'custom port: port code: 0] - clear data - ] - true - ] [ - either parse/all mk1 [ - chunk-size skip mk2: crlfbin to end - ] [ - insert/part tail out mk1 mk2 - remove/part data skip mk2 2 - empty? data - ] [ - true - ] - ] - ] [ - true - ] - ] - unless state/state = 'ready [read conn] - ] - integer? headers/content-length [ - port/data: conn/data - either headers/content-length <= length? port/data [ - state/state: 'ready - conn/data: make binary! 32000 - res: state/awake make event! [type: 'custom port: port code: 0] - ] [ - read conn - ] - ] - true [ - port/data: conn/data - read conn - ] - ] - res + state: port/state + headers: state/info/headers + conn: state/connection + res: false + case [ + headers/transfer-encoding = "chunked" [ + data: conn/data + ;clear the port data only at the beginning of the request --Richard + unless port/data [port/data: make binary! length-of data] + out: port/data + loop-until [ + either parse data [ + copy chunk-size some hex-digits thru crlfbin mk1: to end + ] [ + ; The chunk size is in the byte stream as ASCII chars + ; forming a hex string. ISSUE! can decode that. + chunk-size: ( + to-integer/unsigned to issue! to string! chunk-size + ) + + either chunk-size = 0 [ + if parse mk1 [ + crlfbin (trailer: "") to end | copy trailer to crlf2bin to end + ] [ + trailer: has/only trailer + append headers body-of trailer + state/state: 'ready + res: state/awake make event! [type: 'custom port: port code: 0] + clear data + ] + true + ] [ + either parse mk1 [ + chunk-size skip mk2: crlfbin to end + ] [ + insert/part tail out mk1 mk2 + remove/part data skip mk2 2 + empty? data + ] [ + true + ] + ] + ] [ + true + ] + ] + unless state/state = 'ready [ + ; + ; Awaken WAIT loop to prevent timeout when reading big data. + ; + res: true + ] + ] + integer? headers/content-length [ + port/data: conn/data + either headers/content-length <= length-of port/data [ + state/state: 'ready + conn/data: make binary! 32000 + res: state/awake make event! [ + type: 'custom + port: port + code: 0 + ] + ][ + ; Awaken WAIT loop to prevent timeout when reading big data. + ; + res: true + ] + ] + ] else [ + port/data: conn/data + either state/info/response-parsed = 'ok [ + ; + ; Awaken WAIT loop to prevent timeout when reading big data. + ; + res: true + ][ + ; On other response than OK read all data asynchronously + ; (assuming the data are small). + ; + read conn + ] + ] + + res ] + hex-digits: charset "1234567890abcdefABCDEF" sys/make-scheme [ - name: 'http - title: "HyperText Transport Protocol v1.1" - spec: make system/standard/port-spec-net [ - path: %/ - method: 'get - headers: [] - content: none - timeout: 15 - ] - info: make system/standard/file-info [ - response-line: - response-parsed: - headers: none - ] - actor: [ - read: func [ - port [port!] - ] [ - either any-function? :port/awake [ - unless open? port [cause-error 'Access 'not-open port/spec/ref] - if port/state/state <> 'ready [http-error "Port not ready"] - port/state/awake: :port/awake - do-request port - port - ] [ - sync-op port [] - ] - ] - write: func [ - port [port!] - value - ] [ - unless any [block? :value binary? :value any-string? :value] [value: form :value] - unless block? value [value: reduce [[Content-Type: "application/x-www-form-urlencoded; charset=utf-8"] value]] - either any-function? :port/awake [ - unless open? port [cause-error 'Access 'not-open port/spec/ref] - if port/state/state <> 'ready [http-error "Port not ready"] - port/state/awake: :port/awake - parse-write-dialect port value - do-request port - port - ] [ - sync-op port [parse-write-dialect port value] - ] - ] - open: func [ - port [port!] - /local conn - ] [ - if port/state [return port] - if none? port/spec/host [http-error "Missing host address"] - port/state: context [ - state: 'inited - connection: - error: none - close?: no - info: make port/scheme/info [type: 'file] - awake: :port/awake - ] - port/state/connection: conn: make port! [ - scheme: 'tcp - host: port/spec/host - port-id: port/spec/port-id - ref: rejoin [tcp:// host ":" port-id] - ] - conn/awake: :http-awake - conn/locals: port - open conn - port - ] - open?: func [ - port [port!] - ] [ - found? all [port/state open? port/state/connection] - ] - close: func [ - port [port!] - ] [ - if port/state [ - close port/state/connection - port/state/connection/awake: none - port/state: none - ] - port - ] - copy: func [ - port [port!] - ] [ - either all [port/spec/method = 'head port/state] [ - reduce bind [name size date] port/state/info - ] [ - if port/data [copy port/data] - ] - ] - query: func [ - port [port!] - /local error state - ] [ - if state: port/state [ - either error? error: state/error [ - state/error: none - error - ] [ - state/info - ] - ] - ] - length?: func [ - port [port!] - ] [ - either port/data [length? port/data] [0] - ] - ] + name: 'http + title: "HyperText Transport Protocol v1.1" + + spec: construct system/standard/port-spec-net [ + path: %/ + method: 'get + headers: [] + content: _ + timeout: 15 + debug: _ + ] + + info: construct system/standard/file-info [ + response-line: + response-parsed: + headers: _ + ] + + actor: [ + read: func [ + port [port!] + /lines + /string + foo + ][ + foo: either function? :port/awake [ + unless open? port [ + cause-error 'Access 'not-open port/spec/ref + ] + unless port/state/state = 'ready [ + fail make-http-error "Port not ready" + ] + port/state/awake: :port/awake + do-request port + ][ + sync-op port [] + ] + if lines or string [ + ; !!! When READ is called on an http PORT! (directly or + ; indirectly) it bounces its parameters to this routine. To + ; avoid making an error this tolerates the refinements but the + ; actual work of breaking the buffer into lines is done in the + ; generic code so it will apply to all ports. The design + ; from R3-Alpha for ports (and "actions" in general), was + ; rather half-baked, so this should all be rethought. + ] + return foo + ] + + write: func [ + port [port!] + value + ][ + unless any [block? :value binary? :value any-string? :value] [ + value: form :value + ] + unless block? value [ + value: reduce [ + [Content-Type: + "application/x-www-form-urlencoded; charset=utf-8" + ] + value + ] + ] + either function? :port/awake [ + unless open? port [ + cause-error 'Access 'not-open port/spec/ref + ] + unless port/state/state = 'ready [ + fail make-http-error "Port not ready" + ] + port/state/awake: :port/awake + parse-write-dialect port value + do-request port + port + ][ + sync-op port [parse-write-dialect port value] + ] + ] + + open: func [ + port [port!] + conn + ][ + if port/state [return port] + unless port/spec/host [ + fail make-http-error "Missing host address" + ] + port/state: has [ + state: 'inited + connection: _ + error: _ + close?: no + info: construct port/scheme/info [type: 'file] + awake: :port/awake + ] + port/state/connection: conn: make port! compose [ + scheme: ( + to lit-word! either port/spec/scheme = 'http ['tcp]['tls] + ) + host: port/spec/host + port-id: port/spec/port-id + ref: join-all [tcp:// host ":" port-id] + ] + conn/awake: :http-awake + conn/locals: port + open conn + port + ] + + open?: func [ + port [port!] + ][ + all? [port/state open? port/state/connection] + ] + + close: func [ + port [port!] + ][ + if port/state [ + close port/state/connection + port/state/connection/awake: _ + port/state: _ + ] + port + ] + + copy: func [ + port [port!] + ][ + either all [port/spec/method = 'head port/state] [ + reduce bind [name size date] port/state/info + ][ + if port/data [copy port/data] + ] + ] + + query: func [ + port [port!] + error state + ][ + if state: port/state [ + either error? error: state/error [ + state/error: _ + error + ][ + state/info + ] + ] + ] + + length-of: func [ + port [port!] + ][ + ; actor is not an object!, so this isn't a recursive length call + either port/data [length-of port/data] [0] + ] + ] ] + +sys/make-scheme/with [ + name: 'https + title: "Secure HyperText Transport Protocol v1.1" + spec: construct spec [ + port-id: 443 + ] +] 'http diff --git a/src/mezz/prot-tls.r b/src/mezz/prot-tls.r new file mode 100644 index 0000000000..d92b1833da --- /dev/null +++ b/src/mezz/prot-tls.r @@ -0,0 +1,1388 @@ +REBOL [ + Title: "REBOL 3 TLSv1.0 protocol scheme" + Name: tls + Type: module + Author: "Richard 'Cyphre' Smolak" + Version: 0.6.1 + Todo: { + -cached sessions + -automagic cert data lookup + -add more cipher suites + -server role support + -SSL3.0, TLS1.1/1.2 compatibility + -cert validation + } +] + +; +; These are the currently supported cipher suites. (Additional possibilities +; would be DSA, 3DES, ECDH, ECDHE, ECDSA, SHA256, SHA384...) +; +; https://testssl.sh/openssl-rfc.mapping.html +; +cipher-suites: has [ + TLS_RSA_WITH_RC4_128_MD5: #{00 04} + TLS_RSA_WITH_RC4_128_SHA: #{00 05} + TLS_RSA_WITH_AES_128_CBC_SHA: #{00 2F} + TLS_RSA_WITH_AES_256_CBC_SHA: #{00 35} + TLS_DHE_DSS_WITH_AES_128_CBC_SHA: #{00 32} + TLS_DHE_DSS_WITH_AES_256_CBC_SHA: #{00 38} + TLS_DHE_RSA_WITH_AES_128_CBC_SHA: #{00 33} + TLS_DHE_RSA_WITH_AES_256_CBC_SHA: #{00 39} +] + + +; +; SUPPORT FUNCTIONS +; + +debug: (comment [:print] blank) + +emit: func [ + ctx [object!] + code [block! binary!] +][ + join ctx/msg code +] + +to-bin: func [ + val [integer!] + width [integer!] +][ + skip tail to binary! val negate width +] + +make-tls-error: func [ + "Make an error for the TLS protocol" + message [string! block!] +][ + if block? message [message: unspaced message] + make error! [ + type: 'Access + id: 'Protocol + arg1: message + ] +] + + +; +; ASN.1 FORMAT PARSER CODE +; +; ASN.1 is similar in purpose and use to protocol buffers and Apache Thrift, +; which are also interface description languages for cross-platform data +; serialization. Like those languages, it has a schema (in ASN.1, called a +; "module"), and a set of encodings, typically type-length-value encodings. +; +; https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One +; + +parse-asn: function [ + data [binary!] + + + + universal-tags ([ + eoc + boolean + integer + bit-string + octet-string + null + object-identifier + object-descriptor + external + real + enumerated + embedded-pdv + utf8string + relative-oid + undefined + undefined + sequence + set + numeric-string + printable-string + t61-string + videotex-string + ia5-string + utc-time + generalized-time + graphic-string + visible-string + general-string + universal-string + character-string + bmp-string + ]) + + class-types ([universal application context-specific private]) +][ + result: make block! 16 + mode: 'type + + while [d: first data] [ + switch mode [ + type [ + constructed?: not zero? (d and* 32) + class: pick class-types 1 + shift d -6 + + switch class [ + universal [ + tag: pick universal-tags 1 + (d and* 31) + ] + context-specific [ + tag: class + val: d and* 31 + ] + ] + mode: 'size + ] + + size [ + size: d and* 127 + unless zero? (d and* 128) [ + ; long form + ln: size + size: to-integer/unsigned copy/part next data size + data: skip data ln + ] + either zero? size [ + append/only result compose/deep [ + (tag) [ + (either constructed? ["constructed"] ["primitive"]) + (index-of data) + (size) + _ + ] + ] + mode: 'type + ][ + mode: 'value + ] + ] + + value [ + switch class [ + universal [ + val: copy/part data size + append/only result compose/deep [ + (tag) [ + (either constructed? ["constructed"] ["primitive"]) + (index-of data) + (size) + (either constructed? [blank] [val]) + ] + ] + if constructed? [ + poke second last result 4 + parse-asn val + ] + ] + + context-specific [ + append/only result compose/deep [(tag) [(val) (size)]] + parse-asn copy/part data size + ] + ] + + data: skip data size - 1 + mode: 'type + ] + ] + + data: next data + ] + result +] + + +; +; PROTOCOL STATE HANDLING +; + +get-next-read-state: function [ + ctx [object!] + + + + read-proto-states ([ + client-hello [server-hello] + server-hello [certificate] + certificate [server-hello-done server-key-exchange] + server-key-exchange [server-hello-done] + server-hello-done [#complete] + finished [change-cipher-spec alert] + change-cipher-spec [encrypted-handshake] + encrypted-handshake [application #complete] + application [application alert #complete] + alert [#complete] + close-notify [alert] + ]) +][ + select/only/skip read-proto-states ctx/protocol-state 2 +] + + +get-next-write-state: function [ + ctx [object!] + + + + write-proto-states ([ + server-hello-done [client-key-exchange] + client-key-exchange [change-cipher-spec] + change-cipher-spec [finished] + encrypted-handshake [application] + application [application alert] + alert [close-notify] + close-notify _ + ]) +][ + select/only/skip write-proto-states ctx/protocol-state 2 +] + + +update-proto-state: function [ + ctx [object!] + new-state [word!] + /write-state +][ + debug [ctx/protocol-state "->" new-state write-state] + either any [ + blank? ctx/protocol-state + all [ + next-state: either write-state [ + get-next-write-state ctx + ][ + get-next-read-state ctx + ] + + find next-state new-state + ] + ][ + debug ["new-state:" new-state] + ctx/protocol-state: new-state + ][ + fail "invalid protocol state" + ] +] + +; +; TLS PROTOCOL CODE +; + +client-hello: function [ + ctx [object!] +][ + ; generate client random struct + ; + ctx/client-random: to-bin to-integer difference now/precise 1-Jan-1970 4 + random/seed now/time/precise + loop 28 [append ctx/client-random (random/secure 256) - 1] + + cs-data: join-all values-of cipher-suites + + beg: length-of ctx/msg + emit ctx [ + #{16} ; protocol type (22=Handshake) + ctx/version ; protocol version (3|1 = TLS1.0) + #{00 00} ; length of SSL record data + #{01} ; protocol message type (1=ClientHello) + #{00 00 00} ; protocol message length + ctx/version ; max supported version by client (TLS1.0) + ctx/client-random ; 4 bytes gmt unix time + 28 random bytes + #{00} ; session ID length + to-bin length-of cs-data 2 ; cipher suites length + cs-data ; cipher suites list + #{01} ; compression method length + #{00} ; no compression + ] + + ; set the correct msg lengths + ; + change at ctx/msg beg + 7 to-bin len: length-of at ctx/msg beg + 10 3 + change at ctx/msg beg + 4 to-bin len + 4 2 + + append clear ctx/handshake-messages copy at ctx/msg beg + 6 + + return ctx/msg +] + + +client-key-exchange: function [ + ctx [object!] +][ + switch ctx/key-method [ + rsa [ + ; generate pre-master-secret + ctx/pre-master-secret: copy ctx/version + random/seed now/time/precise + loop 46 [append ctx/pre-master-secret (random/secure 256) - 1] + + ; encrypt pre-master-secret + rsa-key: rsa-make-key + rsa-key/e: ctx/pub-exp + rsa-key/n: ctx/pub-key + + ; supply encrypted pre-master-secret to server + key-data: rsa ctx/pre-master-secret rsa-key + ] + + dhe-dss + dhe-rsa [ + ; generate public/private keypair + dh-generate-key ctx/dh-key + + ; supply the client's public key to server + key-data: ctx/dh-key/pub-key + + ; generate pre-master-secret + ctx/pre-master-secret: dh-compute-key ctx/dh-key ctx/dh-pub + ] + ] + + beg: length-of ctx/msg + emit ctx [ + #{16} ; protocol type (22=Handshake) + ctx/version ; protocol version (3|1 = TLS1.0) + #{00 00} ; length of SSL record data + #{10} ; message type (16=ClientKeyExchange) + #{00 00 00} ; protocol message length + to-bin length-of key-data 2 ; length of the key (2 bytes) + key-data + ] + + ; set the correct msg lengths + change at ctx/msg beg + 7 to-bin len: length-of at ctx/msg beg + 10 3 + change at ctx/msg beg + 4 to-bin len + 4 2 + + ; make all secure data + make-master-secret ctx ctx/pre-master-secret + + make-key-block ctx + + ; update keys + ctx/client-mac-key: copy/part ctx/key-block ctx/hash-size + ctx/server-mac-key: copy/part skip ctx/key-block ctx/hash-size ctx/hash-size + ctx/client-crypt-key: copy/part skip ctx/key-block 2 * ctx/hash-size ctx/crypt-size + ctx/server-crypt-key: copy/part skip ctx/key-block (2 * ctx/hash-size) + ctx/crypt-size ctx/crypt-size + + if ctx/block-size [ + ctx/client-iv: copy/part skip ctx/key-block 2 * (ctx/hash-size + ctx/crypt-size) ctx/block-size + ctx/server-iv: copy/part skip ctx/key-block (2 * (ctx/hash-size + ctx/crypt-size)) + ctx/block-size ctx/block-size + ] + + append ctx/handshake-messages copy at ctx/msg beg + 6 + + return ctx/msg +] + + +change-cipher-spec: function [ + ctx [object!] +][ + emit ctx [ + #{14} ; protocol type (20=ChangeCipherSpec) + ctx/version ; protocol version (3|1 = TLS1.0) + #{00 01} ; length of SSL record data + #{01} ; CCS protocol type + ] + return ctx/msg +] + + +encrypted-handshake-msg: function [ + ctx [object!] + message [binary!] +][ + plain-msg: message + message: encrypt-data/type ctx message #{16} + emit ctx [ + #{16} ; protocol type (22=Handshake) + ctx/version ; protocol version (3|1 = TLS1.0) + to-bin length-of message 2 ; length of SSL record data + message + ] + append ctx/handshake-messages plain-msg + return ctx/msg +] + + +application-data: function [ + ctx [object!] + message [binary! string!] +][ + message: encrypt-data ctx to binary! message + emit ctx [ + #{17} ; protocol type (23=Application) + ctx/version ; protocol version (3|1 = TLS1.0) + to-bin length-of message 2 ; length of SSL record data + message + ] + return ctx/msg +] + + +alert-close-notify: function [ + ctx [object!] +][ + message: encrypt-data ctx #{0100} ; close notify + emit ctx [ + #{15} ; protocol type (21=Alert) + ctx/version ; protocol version (3|1 = TLS1.0) + to-bin length-of message 2 ; length of SSL record data + message + ] + return ctx/msg +] + + +finished: function [ + ctx [object!] +][ + ctx/seq-num-w: 0 + who-finished: either ctx/server? ["server finished"] ["client finished"] + + return join-all [ + #{14} ; protocol message type (20=Finished) + #{00 00 0c} ; protocol message length (12 bytes) + + prf ctx/master-secret who-finished join-all [ + checksum/method ctx/handshake-messages 'md5 + checksum/method ctx/handshake-messages 'sha1 + ] 12 + ] +] + + +encrypt-data: function [ + ctx [object!] + data [binary!] + /type + msg-type [binary!] "application data is default" +][ + data: join-all [ + data + ; MAC code + mac: checksum/method/key join-all [ + to-bin ctx/seq-num-w 8 ; sequence number (64-bit int) + any [:msg-type #{17}] ; msg type + ctx/version ; version + to-bin length-of data 2 ; msg content length + data ; msg content + ] ctx/hash-method decode 'text ctx/client-mac-key + ] + + if ctx/block-size [ + ; add the padding data in CBC mode + padding: ctx/block-size - ((1 + (length-of data)) // ctx/block-size) + len: 1 + padding + append data head insert/dup make binary! len to-bin padding 1 len + ] + + switch ctx/crypt-method [ + rc4 [ + unless ctx/encrypt-stream [ + ctx/encrypt-stream: rc4/key ctx/client-crypt-key + ] + rc4/stream ctx/encrypt-stream data + ] + aes [ + unless ctx/encrypt-stream [ + ctx/encrypt-stream: aes/key ctx/client-crypt-key ctx/client-iv + ] + data: aes/stream ctx/encrypt-stream data + ] + ] + + return data +] + + +decrypt-data: function [ + ctx [object!] + data [binary!] +][ + switch ctx/crypt-method [ + rc4 [ + unless ctx/decrypt-stream [ + ctx/decrypt-stream: rc4/key ctx/server-crypt-key + ] + rc4/stream ctx/decrypt-stream data + ] + aes [ + unless ctx/decrypt-stream [ + ctx/decrypt-stream: aes/key/decrypt ctx/server-crypt-key ctx/server-iv + ] + data: aes/stream ctx/decrypt-stream data + ] + ] + + return data +] + + +parse-protocol: function [ + data [binary!] + + + + protocol-types ([ + 20 change-cipher-spec + 21 alert + 22 handshake + 23 application + ]) +][ + unless proto: select protocol-types data/1 [ + fail "unknown/invalid protocol type" + ] + return context [ + type: proto + version: pick [ssl-v3 tls-v1.0 tls-v1.1] data/3 + 1 + size: to-integer/unsigned copy/part at data 4 2 + messages: copy/part at data 6 size + ] +] + + +parse-messages: function [ + ctx [object!] + proto [object!] + + + + message-types ([ + 0 hello-request + 1 client-hello + 2 server-hello + 11 certificate + 12 server-key-exchange + 13 certificate-request + 14 server-hello-done + 15 certificate-verify + 16 client-key-exchange + 20 finished + ]) + + alert-descriptions ([ + 0 "Close notify" + 10 "Unexpected message" + 20 "Bad record MAC" + 21 "Decryption failed" + 22 "Record overflow" + 30 "Decompression failure" + 40 "Handshake failure - no supported cipher suite available on server" + 41 "No certificate" + 42 "Bad certificate" + 43 "Unsupported certificate" + 44 "Certificate revoked" + 45 "Certificate expired" + 46 "Certificate unknown" + 47 "Illegal parameter" + 48 "Unknown CA" + 49 "Access denied" + 50 "Decode error" + 51 "Decrypt error" + 60 "Export restriction" + 70 "Protocol version" + 71 "Insufficient security" + 80 "Internal error" + 90 "User cancelled" + 100 "No renegotiation" + 110 "Unsupported extension" + ]) + + ; The structure has a field called LENGTH, so when a FUNCTION! is used + ; that field is picked up. + ; + length +][ + result: make block! 8 + data: proto/messages + + if ctx/encrypted? [ + change data decrypt-data ctx data + debug ["decrypting..."] + if ctx/block-size [ + ; deal with padding in CBC mode + data: copy/part data ( + ((length-of data) - 1) - (to-integer/unsigned last data) + ) + debug ["depadding..."] + ] + debug ["data:" data] + ] + debug [ctx/seq-num-r ctx/seq-num-w "READ <--" proto/type] + + unless proto/type = 'handshake [ + if proto/type = 'alert [ + if proto/messages/1 > 1 [ + ; fatal alert level + fail any [select alert-descriptions data/2 "unknown"] + ] + ] + update-proto-state ctx proto/type + ] + + switch proto/type [ + alert [ + append result reduce [ + context [ + level: any [pick [warning fatal] data/1 'unknown] + description: any [select alert-descriptions data/2 "unknown"] + ] + ] + ] + + handshake [ + while [not tail? data] [ + msg-type: select message-types data/1 + + update-proto-state ctx either ctx/encrypted? ['encrypted-handshake] [msg-type] + + len: to-integer/unsigned copy/part at data 2 3 + append result switch msg-type [ + server-hello [ + msg-content: copy/part at data 7 len + + msg-obj: context [ + type: msg-type + version: pick [ssl-v3 tls-v1.0 tls-v1.1] data/6 + 1 + length: len + server-random: copy/part msg-content 32 + session-id: copy/part at msg-content 34 msg-content/33 + cipher-suite: copy/part at msg-content 34 + msg-content/33 2 + compression-method-length: first at msg-content 36 + msg-content/33 + compression-method: + either compression-method-length = 0 [blank] [ + copy/part + at msg-content 37 + msg-content/33 + compression-method-length + ] + ] + ctx/cipher-suite: msg-obj/cipher-suite + + switch ctx/cipher-suite (reduce in cipher-suites [ + TLS_RSA_WITH_RC4_128_SHA [ + ctx/key-method: 'rsa + ctx/crypt-method: 'rc4 + ctx/crypt-size: 16 + ctx/hash-method: 'sha1 + ctx/hash-size: 20 + ] + TLS_RSA_WITH_RC4_128_MD5 [ + ctx/key-method: 'rsa + ctx/crypt-method: 'rc4 + ctx/crypt-size: 16 + ctx/hash-method: 'md5 + ctx/hash-size: 16 + ] + TLS_RSA_WITH_AES_128_CBC_SHA [ + ctx/key-method: 'rsa + ctx/crypt-method: 'aes + ctx/crypt-size: 16 + ctx/block-size: 16 + ctx/iv-size: 16 + ctx/hash-method: 'sha1 + ctx/hash-size: 20 + ] + TLS_RSA_WITH_AES_256_CBC_SHA [ + ctx/key-method: 'rsa + ctx/crypt-method: 'aes + ctx/crypt-size: 32 + ctx/block-size: 16 + ctx/iv-size: 16 + ctx/hash-method: 'sha1 + ctx/hash-size: 20 + ] + TLS_DHE_DSS_WITH_AES_128_CBC_SHA [ + ctx/key-method: 'dhe-dss + ctx/crypt-method: 'aes + ctx/crypt-size: 16 + ctx/block-size: 16 + ctx/iv-size: 16 + ctx/hash-method: 'sha1 + ctx/hash-size: 20 + ] + TLS_DHE_DSS_WITH_AES_256_CBC_SHA [ + ctx/key-method: 'dhe-dss + ctx/crypt-method: 'aes + ctx/crypt-size: 32 + ctx/block-size: 16 + ctx/iv-size: 16 + ctx/hash-method: 'sha1 + ctx/hash-size: 20 + ] + TLS_DHE_RSA_WITH_AES_128_CBC_SHA [ + ctx/key-method: 'dhe-rsa + ctx/crypt-method: 'aes + ctx/crypt-size: 16 + ctx/block-size: 16 + ctx/iv-size: 16 + ctx/hash-method: 'sha1 + ctx/hash-size: 20 + ] + TLS_DHE_RSA_WITH_AES_256_CBC_SHA [ + ctx/key-method: 'dhe-rsa + ctx/crypt-method: 'aes + ctx/crypt-size: 32 + ctx/block-size: 16 + ctx/iv-size: 16 + ctx/hash-method: 'sha1 + ctx/hash-size: 20 + ] + ]) else [ + fail [ + "This TLS scheme doesn't support ciphersuite:" + (mold ctx/cipher-suite) + ] + ] + + ctx/server-random: msg-obj/server-random + msg-obj + ] + + certificate [ + msg-content: copy/part at data 5 len + msg-obj: context [ + type: msg-type + length: len + certificates-length: to-integer/unsigned copy/part msg-content 3 + certificate-list: make block! 4 + while [not tail? msg-content] [ + if 0 < clen: to-integer/unsigned copy/part skip msg-content 3 3 [ + append certificate-list copy/part at msg-content 7 clen + ] + msg-content: skip msg-content 3 + clen + ] + ] + ; no cert validation - just set it to be used + ctx/certificate: parse-asn msg-obj/certificate-list/1 + + switch ctx/key-method [ + rsa [ + ; get the public key and exponent (hardcoded for now) + ctx/pub-key: parse-asn next +; ctx/certificate/1/sequence/4/1/sequence/4/6/sequence/4/2/bit-string/4 + ctx/certificate/1/sequence/4/1/sequence/4/7/sequence/4/2/bit-string/4 + ctx/pub-exp: ctx/pub-key/1/sequence/4/2/integer/4 + ctx/pub-key: next ctx/pub-key/1/sequence/4/1/integer/4 + ] + ] else [ + ; for DH cipher suites the certificate is used + ; just for signing the key exchange data + ] + msg-obj + ] + + server-key-exchange [ + switch ctx/key-method [ + dhe-dss dhe-rsa [ + msg-content: copy/part at data 5 len + msg-obj: context [ + type: msg-type + length: len + p-length: to-integer/unsigned copy/part msg-content 2 + p: copy/part at msg-content 3 p-length + g-length: to-integer/unsigned copy/part at msg-content 3 + p-length 2 + g: copy/part at msg-content 3 + p-length + 2 g-length + ys-length: to-integer/unsigned copy/part at msg-content 3 + p-length + 2 + g-length 2 + ys: copy/part at msg-content 3 + p-length + 2 + g-length + 2 ys-length + signature-length: to-integer/unsigned copy/part at msg-content 3 + p-length + 2 + g-length + 2 + ys-length 2 + signature: copy/part at msg-content 3 + p-length + 2 + g-length + 2 + ys-length + 2 signature-length + ] + + ctx/dh-key: dh-make-key + ctx/dh-key/p: msg-obj/p + ctx/dh-key/g: msg-obj/g + ctx/dh-pub: msg-obj/ys + + ; TODO: the signature sent by server should be verified using DSA or RSA algorithm to be sure the dh-key params are safe + msg-obj + ] + ] else [ + fail "Server-key-exchange message sent illegally." + ] + ] + + server-hello-done [ + context [ + type: msg-type + length: len + ] + ] + + client-hello [ + msg-content: copy/part at data 7 len + context [ + type: msg-type + version: pick [ssl-v3 tls-v1.0 tls-v1.1] data/6 + 1 + length: len + content: msg-content + ] + ] + + finished [ + ctx/seq-num-r: 0 + msg-content: copy/part at data 5 len + who-finished: either ctx/server? [ + "client finished" + ][ + "server finished" + ] + if (msg-content <> + prf ctx/master-secret who-finished join-all [ + checksum/method + ctx/handshake-messages 'md5 + checksum/method ctx/handshake-messages 'sha1 + ] 12 + )[ + fail "Bad 'finished' MAC" + ] + + debug "FINISHED MAC verify: OK" + + context [ + type: msg-type + length: len + content: msg-content + ] + ] + ] + + append ctx/handshake-messages copy/part data len + 4 + + skip-amount: either ctx/encrypted? [ + mac: copy/part skip data len + 4 ctx/hash-size + + mac-check: checksum/method/key join-all [ + to-bin ctx/seq-num-r 8 ; 64-bit sequence number + #{16} ; msg type + ctx/version ; version + to-bin len + 4 2 ; msg content length + copy/part data len + 4 + ] ctx/hash-method decode 'text ctx/server-mac-key + + if mac <> mac-check [ + fail "Bad handshake record MAC" + ] + + 4 + ctx/hash-size + ][ + 4 + ] + + data: skip data (len + skip-amount) + ] + ] + + change-cipher-spec [ + ctx/encrypted?: true + append result context [ + type: 'ccs-message-type + ] + ] + + application [ + append result msg-obj: context [ + type: 'app-data + content: copy/part data (length-of data) - ctx/hash-size + ] + len: length-of msg-obj/content + mac: copy/part skip data len ctx/hash-size + mac-check: checksum/method/key join-all [ + to-bin ctx/seq-num-r 8 ; sequence number (64-bit int in R3) + #{17} ; msg type + ctx/version ; version + to-bin len 2 ; msg content length + msg-obj/content ; content + ] ctx/hash-method decode 'text ctx/server-mac-key + + if mac <> mac-check [ + fail "Bad application record MAC" + ] + ] + ] + + ctx/seq-num-r: ctx/seq-num-r + 1 + return result +] + + +parse-response: function [ + ctx [object!] + msg [binary!] +][ + proto: parse-protocol msg + messages: parse-messages ctx proto + + if empty? messages [ + fail "unknown/invalid protocol message" + ] + + proto/messages: messages + + debug [ + "processed protocol type:" proto/type + "messages:" length-of proto/messages + ] + + unless tail? skip msg proto/size + 5 [ + fail "invalid length of response fragment" + ] + + return proto +] + + +prf: function [ + secret [binary!] + label [string! binary!] + seed [binary!] + output-length [integer!] +][ + len: length-of secret + mid: to integer! (.5 * (len + either odd? len [1] [0])) + + s-1: copy/part secret mid + s-2: copy at secret mid + either odd? len [0] [1] + + seed: join-all [#{} label seed] + + p-md5: copy #{} + a: seed ; A(0) + while [output-length > length-of p-md5] [ + a: checksum/method/key a 'md5 decode 'text s-1 ; A(n) + append p-md5 checksum/method/key join-all [a seed] 'md5 decode 'text s-1 + + ] + + p-sha1: copy #{} + a: seed ; A(0) + while [output-length > length-of p-sha1] [ + a: checksum/method/key a 'sha1 decode 'text s-2 ; A(n) + append p-sha1 checksum/method/key join-all [a seed] 'sha1 decode 'text s-2 + ] + return ((copy/part p-md5 output-length) xor+ copy/part p-sha1 output-length) +] + + +make-key-block: function [ + ctx [object!] +][ + ctx/key-block: prf + ctx/master-secret + "key expansion" + join-all [ctx/server-random ctx/client-random] + ( + (ctx/hash-size + ctx/crypt-size) + + (either ctx/block-size [ctx/iv-size] [0]) + ) * 2 +] + + +make-master-secret: function [ + ctx [object!] + pre-master-secret [binary!] +][ + ctx/master-secret: prf + pre-master-secret + "master secret" + join-all [ctx/client-random ctx/server-random] + 48 +] + + +do-commands: function [ + ctx [object!] + commands [block!] + /no-wait +][ + clear ctx/msg + parse commands [ + some [ + set cmd: [ + 'client-hello (client-hello ctx) + | 'client-key-exchange (client-key-exchange ctx) + | 'change-cipher-spec (change-cipher-spec ctx) + | 'finished (encrypted-handshake-msg ctx finished ctx) + | 'application set arg: [string! | binary!] + (application-data ctx arg) + | 'close-notify (alert-close-notify ctx) + ] ( + debug [ctx/seq-num-r ctx/seq-num-w "WRITE -->" cmd] + ctx/seq-num-w: ctx/seq-num-w + 1 + update-proto-state/write-state ctx cmd + ) + ] + ] + debug ["writing bytes:" length-of ctx/msg] + ctx/resp: copy [] + write ctx/connection ctx/msg + + unless no-wait [ + unless port? wait [ctx/connection 30] [fail "port timeout"] + ] + ctx/resp +] + + +; +; TLS SCHEME +; + + +tls-init: procedure [ + ctx [object!] +][ + ctx/seq-num-r: 0 + ctx/seq-num-w: 0 + ctx/protocol-state: _ + ctx/encrypted?: false + + switch ctx/crypt-method [ + rc4 [ + if ctx/encrypt-stream [ + ctx/encrypt-stream: rc4/stream ctx/encrypt-stream blank + ] + if ctx/decrypt-stream [ + ctx/decrypt-stream: rc4/stream ctx/decrypt-stream blank + ] + ] + ] +] + + +tls-read-data: function [ + ctx [object!] + port-data [binary!] +][ + debug ["tls-read-data:" length-of port-data "bytes"] + data: append ctx/data-buffer port-data + clear port-data + + while [5 = length-of copy/part data 5] [ + len: 5 + to-integer/unsigned copy/part at data 4 2 + + debug ["reading bytes:" len] + + fragment: copy/part data len + + if len > length-of fragment [ + debug [ + "incomplete fragment:" + "read" length-of fragment "of" len "bytes" + ] + break + ] + + debug ["received bytes:" length-of fragment | "parsing response..."] + + append ctx/resp parse-response ctx fragment + + next-state: get-next-read-state ctx + + debug ["State:" ctx/protocol-state "-->" next-state] + + data: skip data len + + if all [tail? data | find next-state #complete] [ + debug [ + "READING FINISHED" + length-of head ctx/data-buffer + index-of data + same? tail ctx/data-buffer data + ] + clear ctx/data-buffer + return true + ] + ] + + debug ["CONTINUE READING..."] + clear change ctx/data-buffer data + return false +] + + +tls-awake: function [event [event!]] [ + debug ["TLS Awake-event:" event/type] + port: event/port + tls-port: port/locals + tls-awake: :tls-port/awake + + if all [ + tls-port/state/protocol-state = 'application + not port/data + ][ + ; reset the data field when interleaving port r/w states + tls-port/data: _ + ] + + switch event/type [ + lookup [ + open port + tls-init tls-port/state + insert system/ports/system make event! [ + type: 'lookup + port: tls-port + ] + return false + ] + + connect [ + do-commands tls-port/state [client-hello] + + if tls-port/state/resp/1/type = 'handshake [ + do-commands tls-port/state [ + client-key-exchange + change-cipher-spec + finished + ] + ] + insert system/ports/system make event! [ + type: 'connect + port: tls-port + ] + return false + ] + + wrote [ + switch tls-port/state/protocol-state [ + close-notify [ + return true + ] + application [ + insert system/ports/system make event! [ + type: 'wrote + port: tls-port + ] + return false + ] + ] + read port + return false + ] + + read [ + debug [ + "Read" length-of port/data + "bytes proto-state:" tls-port/state/protocol-state + ] + + complete?: tls-read-data tls-port/state port/data + application?: false + + for-each proto tls-port/state/resp [ + switch proto/type [ + application [ + for-each msg proto/messages [ + if msg/type = 'app-data [ + unless tls-port/data [ + tls-port/data: clear tls-port/state/port-data + ] + append tls-port/data msg/content + application?: true + msg/type: _ + ] + ] + ] + alert [ + for-each msg proto/messages [ + if msg/description = "Close notify" [ + do-commands tls-port/state [close-notify] + insert system/ports/system make event! [ + type: 'read + port: tls-port + ] + return true + ] + ] + ] + ] + ] + + debug ["data complete?:" complete? "application?:" application?] + + either application? [ + insert system/ports/system make event! [ + type: 'read + port: tls-port + ] + ][ + read port + ] + return complete? + ] + + close [ + insert system/ports/system make event! [ + type: 'close + port: tls-port + ] + return true + ] + ] + + close port + fail ["Unexpected TLS event:" (event/type)] +] + + +sys/make-scheme [ + name: 'tls + title: "TLS protocol v1.0" + spec: construct system/standard/port-spec-net [] + actor: [ + read: func [ + port [port!] + /local + resp data msg + ][ + debug ["READ" open? port/state/connection] + read port/state/connection + return port + ] + + write: func [port [port!] value [ any-value!]] [ + if find [encrypted-handshake application] port/state/protocol-state [ + do-commands/no-wait port/state compose [ + application (value) + ] + return port + ] + ] + + open: func [port [port!] /local conn] [ + if port/state [return port] + + unless port/spec/host [ + fail make-tls-error "Missing host address" + ] + + port/state: context [ + data-buffer: make binary! 32000 + port-data: make binary! 32000 + resp: _ + + version: #{03 01} ; protocol version used + + server?: false + + protocol-state: _ + + key-method: + + hash-method: + hash-size: + + crypt-method: + crypt-size: + block-size: + iv-size: + + cipher-suite: blank + + + client-crypt-key: + client-mac-key: + client-iv: + server-crypt-key: + server-mac-key: + server-iv: blank + + seq-num-r: 0 + seq-num-w: 0 + + msg: make binary! 4096 + + ; all messages from Handshake records except "HelloRequest" + ; + handshake-messages: make binary! 4096 + + encrypted?: false + + client-random: server-random: pre-master-secret: master-secret: + key-block: + certificate: pub-key: pub-exp: + dh-key: dh-pub: blank + + encrypt-stream: decrypt-stream: blank + + connection: _ + ] + + port/state/connection: conn: make port! [ + scheme: 'tcp + host: port/spec/host + port-id: port/spec/port-id + ref: join-all [tcp:// host ":" port-id] + ] + + port/data: port/state/port-data + + conn/awake: :tls-awake + conn/locals: port + open conn + port + ] + + open?: func [port [port!]] [ + all? [port/state open? port/state/connection] + ] + + close: func [port [port!] /local ctx] [ + unless port/state [return port] + + close port/state/connection + + ; The symmetric ciphers used by TLS are able to encrypt chunks of + ; data one at a time. It keeps the progressive state of the + ; encryption process in the -stream variables, which under the + ; hood are memory-allocated items stored as a HANDLE!. + ; + ; Calling the encryption functions with BLANK! as the data to + ; input will assume you are done, and will free the handle. + ; + ; !!! Is there a good reason for not doing this with an ordinary + ; OBJECT! containing a BINARY! ? + ; + switch port/state/crypt-method [ + rc4 [ + if port/state/encrypt-stream [ + port/state/encrypt-stream: _ ;-- will be GC'd + ] + if port/state/decrypt-stream [ + port/state/decrypt-stream: _ ;-- will be GC'd + ] + ] + aes [ + if port/state/encrypt-stream [ + port/state/encrypt-stream: _ ;-- will be GC'd + ] + if port/state/decrypt-stream [ + port/state/decrypt-stream: _ ;-- will be GC'd + ] + ] + ] + + debug "TLS/TCP port closed" + port/state/connection/awake: blank + port/state: blank + port + ] + + copy: func [port [port!]] [ + if port/data [copy port/data] + ] + + query: func [port [port!]] [ + all [port/state query port/state/connection] + ] + + length-of: func [port [port!]] [ + ; actor is not an object!, so this isn't a recursive length call + either port/data [length-of port/data] [0] + ] + ] +] diff --git a/src/mezz/sys-base.r b/src/mezz/sys-base.r index 37485dc167..cb32803c18 100644 --- a/src/mezz/sys-base.r +++ b/src/mezz/sys-base.r @@ -1,214 +1,235 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Boot Sys: Top Context Functions" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Context: sys - Note: { - Follows the BASE lib init that provides a basic set of functions - to be able to evaluate this code. - - The boot binding of this module is SYS then LIB deep. - Any non-local words not found in those contexts WILL BE - UNBOUND and will error out at runtime! - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Boot Sys: Top Context Functions" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Context: sys + Note: { + Follows the BASE lib init that provides a basic set of functions + to be able to evaluate this code. + + The boot binding of this module is SYS then LIB deep. + Any non-local words not found in those contexts WILL BE + UNBOUND and will error out at runtime! + } ] -;-- SYS context definition begins here -- -; WARNING: ORDER DEPENDENT part of context (accessed from C code) - -native: none ; for boot only -action: none ; for boot only - -do*: func [ - {SYS: Called by system for DO on datatypes that require special handling.} - value [file! url! string! binary!] - /args "If value is a script, this will set its system/script/args" - arg "Args passed to a script (normally a string)" - /next "Do next expression only, return it, update block variable" - var [word!] "Variable updated with new block position" - /local data file spec dir hdr scr mod? -][ - ; This code is only called for urls, files, and strings. - ; DO of functions, blocks, paths, and other do-able types is done in the - ; native, and this code is not called. - ; Note that DO of file path evaluates in the directory of the target file. - ; Files, urls and modules evaluate as scripts, other strings don't. - ; Note: LOAD/header returns a block with the header object in the first - ; position, or will cause an error. No exceptions, not even for - ; directories or media. - ; Currently, load of URL has no special block forms. - - ; Load the data, first so it will error before change-dir - data: load/header/type value 'unbound ; unbound so DO-NEEDS runs before INTERN - ; Get the header and advance 'data to the code position - hdr: first+ data ; object or none - ; data is a block! here, with the header object in the first position back - mod?: 'module = select hdr 'type - - either all [string? value not mod?] [ - ; Return result without script overhead - do-needs hdr ; Load the script requirements - if empty? data [if var [set var data] exit] ; Shortcut return empty - intern data ; Bind the user script - catch/quit either var [[do/next data var]] [data] - ][ ; Otherwise we are in script mode - - ; Do file in directory if necessary - dir: none ; in case of /local hack - if all [file? value file: find/last/tail value slash] [ - dir: what-dir ; save the current directory for later restoration - change-dir copy/part value file - ] - - ; Make the new script object - scr: system/script ; and save old one - system/script: make system/standard/script [ - title: select hdr 'title - header: hdr - parent: :scr - path: what-dir - args: :arg - ] - - ; Print out the script info - boot-print [ - pick ["Module:" "Script:"] mod? mold select hdr 'title - "Version:" select hdr 'version - "Date:" select hdr 'date - ] - - also - ; Eval the block or make the module, returned - either mod? [ ; Import the module and set the var - spec: reduce [hdr data do-needs/no-user hdr] - also import catch/quit [make module! spec] - if var [set var tail data] - ][ - do-needs hdr ; Load the script requirements - intern data ; Bind the user script - catch/quit either var [[do/next data var]] [data] - ] - ; Restore system/script and the dir - all [system/script: :scr dir change-dir dir] - ] -] - -make-module*: func [ - "SYS: Called by system on MAKE of MODULE! datatype." - spec [block!] "As [spec-block body-block opt-mixins-object]" - /local body obj mixins hidden w +; It is desirable to express the logic of PRINT as user code, but it is +; also desirable to use PRINT from the C code. This should likely be +; optimized as a native, but is easier to explore at the moment like this. +; +print*: :print + + +;-- If the host wants to know if a script or module is loaded, e.g. to +; print out a message. (Printing directly from this code would be +; presumptuous.) +; +script-pre-load-hook: _ + + +; DO of functions, blocks, paths, and other do-able types is done directly by +; C code in REBNATIVE(do). But that code delegates to this Rebol function +; for ANY-STRING! and BINARY! types (presumably because it would be laborious +; to express as C). +; +do*: function [ + {SYS: Called by system for DO on datatypes that require special handling.} + return: [ any-value!] + source [file! url! string! binary! tag!] + {Files, urls and modules evaluate as scripts, other strings don't.} + args [logic!] + "Positional workaround of /ARGS" + arg [any-value!] + "Args passed as system/script/args to a script (normally a string)" + next [logic!] + "Positional workaround of /NEXT" + var [blank! word!] + "If do next expression only, variable updated with new block position" + only [logic!] + "Do not catch quits...propagate them." ][ - set [spec body mixins] spec - - ; Convert header block to standard header object: - if block? :spec [ - spec: attempt [construct/with :spec system/standard/header] - ] - - ; Validate the important fields of header: - assert/type [ - spec object! - body block! - mixins [object! none!] - spec/name [word! none!] - spec/type [word! none!] - spec/version [tuple! none!] - spec/options [block! none!] - ] - - ; Module is an object during its initialization: - obj: make object! 7 ; arbitrary starting size - - if find spec/options 'extension [ - append obj 'lib-base ; specific runtime values MUST BE FIRST - ] - - unless spec/type [spec/type: 'module] ; in case not set earlier - - ; Collect 'export keyword exports, removing the keywords - if find body 'export [ - unless block? select spec 'exports [repend spec ['exports make block! 10]] - ; Note: 'export overrides 'hidden, silently for now - parse body [while [to 'export remove skip opt remove 'hidden opt [ - set w any-word! ( - unless find spec/exports w: to word! w [append spec/exports w] - ) | - set w block! (append spec/exports collect-words/ignore w spec/exports) - ]] to end] - ] - - ; Add exported words at top of context (performance): - if block? select spec 'exports [bind/new spec/exports obj] - - ; Collect 'hidden keyword words, removing the keywords. Ignore exports. - hidden: none - if find body 'hidden [ - hidden: make block! 10 - ; Note: Exports are not hidden, silently for now - parse body [while [to 'hidden remove skip opt [ - set w any-word! ( - unless find select spec 'exports w: to word! w [append hidden w] - ) | - set w block! (append hidden collect-words/ignore w select spec 'exports) - ]] to end] - ] - - ; Add hidden words next to the context (performance): - if block? hidden [bind/new hidden obj] - - either find spec/options 'isolate [ - ; All words of the module body are module variables: - bind/new body obj - ; The module keeps its own variables (not shared with system): - if object? mixins [resolve obj mixins] - ;resolve obj sys -- no longer done -Carl - resolve obj lib - ][ - ; Only top level defined words are module variables. - bind/only/set body obj - ; The module shares system exported variables: - bind body lib - ;bind body sys -- no longer done -Carl - if object? mixins [bind body mixins] - ] - - bind body obj - if block? hidden [protect/hide/words hidden] - obj: to module! reduce [spec obj] - do body - - ;print ["Module created" spec/name spec/version] - obj + next_DO*: next + next: :lib/next + + ; !!! These were refinements on the original DO* which were called from + ; the system using positional order. Under the Ren-C model you cannot + ; select refinements positionally, nor can you pass "void" cells in + ; a variadic invocation (because variadics may be reified to blocks which + ; are user-exposed, and arrays with voids in them are only allowed for + ; cases like the internal varlist of objects). + ; + ; It would be *possible* to keep these going as refinements and have the + ; system build a path to make a call, but this is easier. + ; + if not args [unset 'args] + if not next_DO* [unset 'var] + + ; !!! DEMONSTRATION OF CONCEPT... this translates a tag into a URL!, but + ; it should be using a more "official" URL instead of on individuals + ; websites. There should also be some kind of local caching facility. + ; + if tag? source [ + if source = [ + ; Special compatibility tag... Rebol2 and R3-Alpha will ignore the + ; DO of a , so this is a no-op in them. + ; + return r3-legacy* ;-- calls function defined in %mezz-legacy.r + ] + + ; Convert value into a URL! + source: switch source load rebol/locale/library/utilities + else [ + fail [ + {Module} source {not in rebol/locale/library} + ] + ] + ] + + ; Note that DO of file path evaluates in the directory of the target file. + ; + original-path: what-dir + original-script: _ + + finalizer: func [ + value [ any-value!] + name [any-value!] ;-- can be a FUNCTION! + return + ][ + ; Restore system/script and the dir if they were changed + + if original-script [system/script: original-script] + if original-path [change-dir original-path] + + either :name = :quit [ + if only [ + quit/with :value ;-- "rethrow" the QUIT if DO/ONLY + ] + ][ + assert [:name = blank] + ] + + return :value ;-- returns from DO* not FINALIZER, due to return + ] + + ; If a file is being mentioned as a DO location and the "current path" + ; is a URL!, then adjust the source to be a URL! based from that path. + ; + if all [url? original-path | file? source] [ + source: join-of original-path source + ] + + ; Load the code (do this before CHANGE-DIR so if there's an error in the + ; LOAD it will trigger before the failure of changing the working dir) + ; It is loaded as UNBOUND so that DO-NEEDS runs before INTERN. + ; + code: ensure block! (load/header/type source 'unbound) + + ; LOAD/header returns a block with the header object in the first + ; position, or will cause an error. No exceptions, not even for + ; directories or media. "Load of URL has no special block forms." <-- ??? + ; + ; !!! Should the header always be locked by LOAD? + ; + hdr: lock to-value ensure [object! blank!] first code + is-module: 'module = select hdr 'type + code: next code + + either all [string? source | not is-module] [ + ; + ; Return result without "script overhead" (e.g. don't change the + ; working directory to the base of the file path supplied) + ; + do-needs hdr ; Load the script requirements + intern code ; Bind the user script + result: catch/quit/with [ + ; + ; The source string may have been mutable or immutable, but the + ; loaded code is not locked for this case. So this works: + ; + ; do "append {abc} {de}" + ; + do/next code :var ;-- If var is void, /NEXT is revoked + ] :finalizer + ][ + ; Otherwise we are in script mode. When we run a script, the + ; "current" directory is changed to the directory of that script. + ; This way, relative path lookups to find dependent files will look + ; relative to the script. + ; + ; We want this behavior for both FILE! and for URL!, which means + ; that the "current" path may become a URL!. This can be processed + ; with change-dir commands, but it will be protocol dependent as + ; to whether a directory listing would be possible (HTTP does not + ; define a standard for that) + ; + if all [ + maybe? [file! url!] source + file: find/last/tail source slash + ][ + change-dir copy/part source file + ] + + ; Also in script mode, the code is immutable by default. + ; + ; !!! Note that this does not currently protect the code from binding + ; changes, and it gets INTERNed below, or by "module/mixin" (?!) + ; + lock code + + ; Make the new script object + original-script: system/script ; and save old one + system/script: construct system/standard/script [ + title: select hdr 'title + header: hdr + parent: :original-script + path: what-dir + args: to-value :arg + ] + + all [ + set? 'script-pre-load-hook + | + script-pre-load-hook is-module hdr ;-- chance to print it out + ] + + ; Eval the block or make the module, returned + either is-module [ ; Import the module and set the var + result: import catch/quit/with [ + module/mixin hdr code (opt do-needs/no-user hdr) + ] :finalizer + if next_DO* [set var tail code] + ][ + do-needs hdr ; Load the script requirements + intern code ; Bind the user script + result: catch/quit/with [ + do/next code :var ;-- If var is void, /NEXT is revoked + ] :finalizer + ] + ] + + finalizer :result blank ] -; MOVE some of these to SYSTEM? -boot-banner: ajoin ["REBOL 3.0 A" system/version/3 " " system/build newline] -boot-help: "Boot-sys level - no extra features." -boot-host: none ; any host add-ons to the lib (binary) -boot-mezz: none ; built-in mezz code (put here on boot) -boot-prot: none ; built-in boot protocols -boot-exts: none ; boot extension list - export: func [ - "Low level export of values (e.g. functions) to lib." - words [block!] "Block of words (already defined in local context)" + "Low level export of values (e.g. functions) to lib." + words [block!] "Block of words (already defined in local context)" ][ - foreach word words [repend lib [word get word]] + for-each word words [join lib [word get word]] ] assert-utf8: function [ - "If binary data is UTF-8, returns it, else throws an error." - data [binary!] + "If binary data is UTF-8, returns it, else throws an error." + data [binary!] ][ - unless find [0 8] tmp: utf? data [ ; Not UTF-8 - cause-error 'script 'no-decode ajoin ["UTF-" abs tmp] - ] - data + unless find [0 8] tmp: utf? data [ ; Not UTF-8 + cause-error 'script 'no-decode unspaced ["UTF-" abs tmp] + ] + data ] diff --git a/src/mezz/sys-codec.r b/src/mezz/sys-codec.r index 37f5ef4b8f..d5ef35e866 100644 --- a/src/mezz/sys-codec.r +++ b/src/mezz/sys-codec.r @@ -1,61 +1,139 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Boot Sys: Encoder and Decoder" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Context: sys - Note: { - The boot binding of this module is SYS then LIB deep. - Any non-local words not found in those contexts WILL BE - UNBOUND and will error out at runtime! - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Boot Sys: Encoder and Decoder" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Context: sys + Note: { + The boot binding of this module is SYS then LIB deep. + Any non-local words not found in those contexts WILL BE + UNBOUND and will error out at runtime! + } ] + +; This function is called by the C function Register_Codec(), but can +; also be called by user code. +; +; !!! There should also be an unregister-codec* +; +register-codec*: func [ + name [word!] + {Descriptive name of the codec.} + suffixes [file! block!] + {File extension or block of file extensions the codec processes} + identify? [function! blank!] + decode [function! blank!] + encode [function! blank!] + codec +][ + unless block? suffixes [suffixes: reduce [suffixes]] + + codec: construct [] compose/only [ + name: quote (name) + + ; !!! There was a "type:" field here before, which was always set to + ; IMAGE!. Should the argument types of the encode function be cached + ; here, or be another parameter, or...? + + suffixes: (suffixes) + identify?: quote (:identify?) + decode: quote (:decode) + encode: quote (:encode) + ] + + append system/codecs reduce [(to set-word! name) codec] + + ; Media-types block format: [.abc .def type ...] + ; !!! Should be a map, with blocks of codecs on collisions + ; + append append system/options/file-types suffixes (bind name system/codecs) + + return codec +] + + +; Special import case for extensions: +append system/options/file-types (switch fourth system/version [ + 3 [ + [%.rx %.dll extension] ; Windows + ] + 2 [ + [%.rx %.dylib %.so extension] ; OS X + ] + 4 + 7 [ + [%.rx %.so extension] ; Other Posix + ] +] else [ + [%.rx extension] +]) + + decode: function [ - {Decodes a series of bytes into the related datatype (e.g. image!).} - type [word!] {Media type (jpeg, png, etc.)} - data [binary!] {The data to decode} + {Decodes a series of bytes into the related datatype (e.g. image!).} + + type [word!] + {Media type (jpeg, png, etc.)} + data [binary!] + {The data to decode} ][ - unless all [ - cod: select system/codecs type - data: do-codec cod/entry 'decode data - ][ - cause-error 'access 'no-codec type - ] - data + unless all [ + cod: select system/codecs type + f: :cod/decode + (data: f data) + ][ + cause-error 'access 'no-codec type + ] + data ] + encode: function [ - {Encodes a datatype (e.g. image!) into a series of bytes.} - type [word!] {Media type (jpeg, png, etc.)} - data [image! binary! string!] {The data to encode} - /options opts [block!] {Special encoding options} + {Encodes a datatype (e.g. image!) into a series of bytes.} + + return: [binary!] + type [word!] + {Media type (jpeg, png, etc.)} + data + {The data to encode} + /options + {Special encoding options} + opts [block!] ][ - unless all [ - cod: select system/codecs type - data: do-codec cod/entry 'encode data - ][ - cause-error 'access 'no-codec type - ] - data + unless all [ + cod: select system/codecs type + f: :cod/encode + (data: f data) + ][ + cause-error 'access 'no-codec type + ] + data ] -encoding?: function [ - "Returns the media codec name for given binary data. (identify)" - data [binary!] + +encoding-of: function [ + "Returns the media codec name for given binary data. (identify)" + + return [word!] + data [binary!] ][ - foreach [name codec] system/codecs [ - if do-codec codec/entry 'identify data [ - return name - ] - ] - none + for-each [name codec] system/codecs [ + if all [ + f: :codec/identify? + (f data) + ][ + return name + ] + ] + blank ] -export [decode encode encoding?] + +export [decode encode encoding-of] diff --git a/src/mezz/sys-load.r b/src/mezz/sys-load.r index 02fe8b7ff0..10625df81c 100644 --- a/src/mezz/sys-load.r +++ b/src/mezz/sys-load.r @@ -1,749 +1,1094 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Boot Sys: Load, Import, Modules" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Context: sys - Note: { - The boot binding of this module is SYS then LIB deep. - Any non-local words not found in those contexts WILL BE - UNBOUND and will error out at runtime! - - These functions are kept in a single file because they - are inter-related. - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Boot Sys: Load, Import, Modules" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Context: sys + Note: { + The boot binding of this module is SYS then LIB deep. + Any non-local words not found in those contexts WILL BE + UNBOUND and will error out at runtime! + + These functions are kept in a single file because they + are inter-related. + + The fledgling module system in R3-Alpha was never widely used or + tested, but there's a description page here: + + http://www.rebol.com/r3/docs/concepts/modules-defining.html + } ] ; BASICS: +; ; Code gets loaded in two ways: ; 1. As user code/data - residing in user context ; 2. As module code/data - residing in its own context -; Module loading can be delayed. This allows special modules like CGI, protocols, -; or HTML formatters to be available, but not require extra space. +; +; Module loading can be delayed. This allows special modules like CGI, +; protocols, or HTML formatters to be available, but not require extra space. ; The system/modules list holds modules for fully init'd modules, otherwise it -; holds their headers, along with the binary or block that will be used to init them. +; holds their headers, along with the binary or block that will be used to +; init them. intern: function [ - "Imports (internalizes) words/values from the lib into the user context." - data [block! any-word!] "Word or block of words to be added (deeply)" + "Imports (internalizes) words/values from the lib into the user context." + data [block! any-word!] "Word or block of words to be added (deeply)" ][ - index: 1 + length? usr: system/contexts/user ; for optimization below (index for resolve) - data: bind/new :data usr ; Extend the user context with new words - resolve/only usr lib index ; Copy only the new values into the user context - :data + ; for optimization below (index for resolve) + index: 1 + length-of usr: system/contexts/user + + ; Extend the user context with new words + data: bind/new :data usr + + ; Copy only the new values into the user context + resolve/only usr lib index + + :data ] + bind-lib: func [ - "Bind only the top words of the block to the lib context (used to load mezzanines)." - block [block!] + "Bind only the top words of the block to the lib context (mezzanine load)." + block [block!] ][ - bind/only/set block lib ; Note: not bind/new ! - bind block lib - block + bind/only/set block lib ; Note: not bind/new ! + bind block lib + block ] + export-words: func [ - "Exports the words of a context into both the system lib and user contexts." - ctx [module! object!] "Module context" - words [block! none!] "The exports words block of the module" + "Exports words of a context into both the system lib and user contexts." + ctx [module! object!] + "Module context" + words [block! blank!] + "The exports words block of the module" ][ - if words [ - resolve/extend/only lib ctx words ; words already set in lib are not overriden - resolve/extend/only system/contexts/user lib words ; lib, because of above - ] + if words [ + ; words already set in lib are not overriden + resolve/extend/only lib ctx words + + ; lib, because of above + resolve/extend/only system/contexts/user lib words + ] ] + mixin?: func [ - "Returns TRUE if module is a mixin with exports." - mod [module! object!] "Module or spec header" + "Returns TRUE if module is a mixin with exports." + mod [module! object!] "Module or spec header" ][ - ; Note: Unnamed modules DO NOT default to being mixins. - if module? mod [mod: spec-of mod] ; Get the header object - true? all [ - find select mod 'options 'private - ; If there are no exports, there's no difference - block? select mod 'exports - not empty? select mod 'exports - ] + ; Note: Unnamed modules DO NOT default to being mixins. + if module? mod [mod: meta-of mod] ; Get the header object + all? [ + find select mod 'options 'private + ; If there are no exports, there's no difference + block? select mod 'exports + not empty? select mod 'exports + ] ] -load-header: function/with [ - "Loads script header object and body binary (not loaded)." - source [binary! string!] "Source code (string! will be UTF-8 encoded)" - /only "Only process header, don't decompress or checksum body" - /required "Script header is required" -][ - ; This function decodes the script header from the script body. - ; It checks the header 'checksum and 'compress and 'content options, - ; and supports length-specified or script-in-a-block embedding. - ; - ; It will set the 'content field to the binary source if 'content is true. - ; The 'content will be set to the source at the position of the beginning - ; of the script header, skipping anything before it. For multi-scripts it - ; doesn't copy the portion of the content that relates to the current - ; script, or at all, so be careful with the source data you get. - ; - ; If the 'compress option is set then the body will be decompressed. - ; Binary vs. script encoded compression will be autodetected. The - ; header 'checksum is compared to the checksum of the decompressed binary. - ; - ; Normally, returns the header object, the body text (as binary), and the - ; the end of the script or script-in-a-block. The end position can be used - ; to determine where to stop decoding the body text. After the end is the - ; rest of the binary data, which can contain anything you like. This can - ; support multiple scripts in the same binary data, multi-scripts. - ; - ; If not /only and the script is embedded in a block and not compressed - ; then the body text will be a decoded block instead of binary, to avoid - ; the overhead of decoding the body twice. - ; - ; Syntax errors are returned as words: - ; no-header - ; bad-header - ; bad-checksum - ; bad-compress - ; - ; Note: set/any and :var used - prevent malicious code errors. - ; Commented assert statements are for documentation and testing. - ; - case/all [ - binary? source [tmp: assert-utf8 source] - string? source [tmp: to binary! source] - not data: script? tmp [ ; no script header found - return either required ['no-header] [reduce [none tmp tail tmp]] - ] - set/any [key: rest:] transcode/only data none ; get 'rebol keyword - set/any [hdr: rest:] transcode/next/error rest none ; get header block - not block? :hdr [return 'no-header] ; header block is incomplete - not attempt [hdr: construct/with :hdr system/standard/header][return 'bad-header] - not any [block? :hdr/options none? :hdr/options][return 'bad-header] - not any [binary? :hdr/checksum none? :hdr/checksum][return 'bad-checksum] - find hdr/options 'content [repend hdr ['content data]] ; as of start of header - 13 = rest/1 [rest: next rest] ; skip CR - 10 = rest/1 [rest: next rest] ; skip LF - integer? tmp: select hdr 'length [end: skip rest tmp] - not end [end: tail data] - only [return reduce [hdr rest end]] ; decompress and checksum not done - sum: hdr/checksum none ;[print sum] ; none saved to simplify later code - :key = 'rebol [ ; regular script, binary or script encoded compression supported - case [ - find hdr/options 'compress [ - rest: any [find rest non-ws rest] ; skip whitespace after header - unless rest: any [ ; automatic detection of compression type - attempt [decompress/part rest end] ; binary compression - attempt [decompress first transcode/next rest] ; script encoded - ] [return 'bad-compress] - if all [sum sum != checksum/secure rest] [return 'bad-checksum] - ] ; else assumed not compressed - all [sum sum != checksum/secure/part rest end] [return 'bad-checksum] - ] - ] - ;assert/type [rest [binary!]] none - :key != 'rebol [ ; block-embedded script, only script compression, hdr/length ignored - tmp: rest ; saved for possible checksum calc later - rest: skip first set [data: end:] transcode/next data 2 ; decode embedded script - case [ - find hdr/options 'compress [ ; script encoded only - unless rest: attempt [decompress first rest] [return 'bad-compress] - if all [sum sum != checksum/secure rest] [return 'bad-checksum] - ] - all [sum sum != checksum/secure/part tmp back end] [return 'bad-checksum] - ] - ] - ;assert/type [rest [block! binary!]] none - ] - ;assert/type [hdr object! rest [binary! block!] end binary!] - ;assert/type [hdr/checksum [binary! none!] hdr/options [block! none!]] - reduce [hdr rest end] -][ - non-ws: make bitset! [not 1 - 32] -] -load-ext-module: function [ - "Loads an extension module from an extension object." - ext [object!] "Extension object (from LOAD-EXTENSION, modified)" - ;/local -- don't care if cmd-index and command are defined local -][ - ; for ext obj: help system/standard/extensions - assert/type [ext/lib-base handle! ext/lib-boot binary!] ; Just in case - - if word? set [hdr: code:] load-header/required ext/lib-boot [ - cause-error 'syntax hdr ext ; word returned is error code - ] - ;assert/type [hdr object! hdr/options [block! none!] code [binary! block!]] - - loud-print ["Extension:" select hdr 'title] - unless hdr/options [hdr/options: make block! 1] - append hdr/options 'extension ; So make module! special cases it - hdr/type: 'module ; So load and do special case it - ext/lib-boot: none ; So it doesn't show up in the source - tmp: body-of ext ; Special extension words - - ; Define default extension initialization if needed: - ; It is overridden when extension provides it's own COMMAND func. - unless :ext/command [ - append tmp [ - cmd-index: 0 - command: func [ - "Define a new command for an extension." - args [integer! block!] - ][ - ; (contains module-local variables) - make command! reduce [args self ++ cmd-index] - ] - protect/hide/words [cmd-index command] - ] - ] - - ; Convert the code to a block if not already: - unless block? code [code: to block! code] - insert code tmp ; Extension object fields and values must be first! - reduce [hdr code] ; ready for make module! -] +load-header: function [ + "Loads script header object and body binary (not loaded)." + source [binary! string!] + "Source code (string! will be UTF-8 encoded)" + /only + "Only process header, don't decompress or checksum body" + /required + "Script header is required" + + -load-boot-exts: function [ - "INIT: Load boot-based extensions." + non-ws (make bitset! [not 1 - 32]) ][ - loud-print "Loading boot extensions..." - - ext-objs: [] - - foreach [spec caller] boot-exts [ - append ext-objs load-extension/dispatch spec caller - ] - - foreach ext ext-objs [ - case/all [ - word? set [hdr: code:] load-header/only/required ext/lib-boot [ - cause-error 'syntax hdr ext ; word returned is error code - ] - not word? :hdr/name [hdr/name: none] - not any [hdr/name find hdr/options 'private] [ - hdr/options: append any [hdr/options make block! 1] 'private - ] - delay: all [hdr/name find hdr/options 'delay] [mod: reduce [hdr ext]] ; load it later - not delay [hdr: spec-of mod: make module! load-ext-module ext] - ; NOTE: This will error out if the code contains commands but - ; no extension dispatcher (call) has been provided. - hdr/name [reduce/into [hdr/name mod if hdr/checksum [copy hdr/checksum]] system/modules] - ] - case [ - not module? mod none - not block? select hdr 'exports none - empty? hdr/exports none - find hdr/options 'private [ - resolve/extend/only system/contexts/user mod hdr/exports ; full export to user - ] - 'else [export-words mod hdr/exports] - ] - ] - set 'load-boot-exts 'done ; only once + ; This function decodes the script header from the script body. + ; It checks the header 'checksum and 'compress and 'content options, + ; and supports length-specified or script-in-a-block embedding. + ; + ; It will set the 'content field to the binary source if 'content is true. + ; The 'content will be set to the source at the position of the beginning + ; of the script header, skipping anything before it. For multi-scripts it + ; doesn't copy the portion of the content that relates to the current + ; script, or at all, so be careful with the source data you get. + ; + ; If the 'compress option is set then the body will be decompressed. + ; Binary vs. script encoded compression will be autodetected. The + ; header 'checksum is compared to the checksum of the decompressed binary. + ; + ; Normally, returns the header object, the body text (as binary), and the + ; the end of the script or script-in-a-block. The end position can be used + ; to determine where to stop decoding the body text. After the end is the + ; rest of the binary data, which can contain anything you like. This can + ; support multiple scripts in the same binary data, multi-scripts. + ; + ; If not /only and the script is embedded in a block and not compressed + ; then the body text will be a decoded block instead of binary, to avoid + ; the overhead of decoding the body twice. + ; + ; Syntax errors are returned as words: + ; no-header + ; bad-header + ; bad-checksum + ; bad-compress + ; + ; Note: set and :var used - prevent malicious code errors. + ; Commented assert statements are for documentation and testing. + ; + end: _ ;-- locals are now unset by default, added after that change + + case/all [ + binary? source [tmp: assert-utf8 source] + + string? source [tmp: to binary! source] + + not data: script? tmp [ ; no script header found + return either required ['no-header] [reduce [_ tmp tail tmp]] + ] + + ; get 'rebol keyword + set* [key: rest:] transcode/only data blank + + ; get header block + set* [hdr: rest:] transcode/next/relax rest blank + + not block? :hdr [ + ; header block is incomplete + return 'no-header + ] + + not attempt [hdr: construct/only system/standard/header :hdr] [ + return 'bad-header + ] + + not any [block? :hdr/options blank? :hdr/options] [ + return 'bad-header + ] + + not any [binary? :hdr/checksum blank? :hdr/checksum] [ + return 'bad-checksum + ] + + find hdr/options 'content [ + join hdr ['content data] ; as of start of header + ] + + 13 = rest/1 [rest: next rest] ; skip CR + 10 = rest/1 [rest: next rest] ; skip LF + + integer? tmp: select hdr 'length [ + end: skip rest tmp + ] + + not end [end: tail data] + + only [ + ; decompress and checksum not done + return reduce [hdr rest end] + ] + + sum: hdr/checksum [ + ; blank saved to simplify later code + blank ;[print sum] + ] + + :key = 'rebol [ + ; regular script, binary or script encoded compression supported + case [ + find hdr/options 'compress [ + ; skip whitespace after header + rest: any [find rest non-ws | rest] + + ; automatic detection of compression type + unless rest: any [ + attempt [ + ; binary compression + decompress/part rest end + ] + attempt [ + ; script encoded + decompress first transcode/next rest + ] + ][ + return 'bad-compress + ] + + if all [sum | sum != checksum/secure rest] [ + return 'bad-checksum + ] + ] ; else assumed not compressed + + all [sum | sum != checksum/secure/part rest end] [ + return 'bad-checksum + ] + ] + ] + + :key != 'rebol [ + ; block-embedded script, only script compression, ignore hdr/length + + tmp: ensure binary! rest ; saved for possible checksum calc later + + ; decode embedded script + rest: skip first set [data: end:] transcode/next data 2 + + case [ + find hdr/options 'compress [ ; script encoded only + unless rest: attempt [decompress first rest] [ + return 'bad-compress + ] + + if all [sum sum != checksum/secure rest] [ + return 'bad-checksum + ] + ] + all [sum sum != checksum/secure/part tmp back end] [ + return 'bad-checksum + ] + ] + ] + + ] + + ensure [binary! blank!] hdr/checksum + ensure [block! blank!] hdr/options + + ; Return a BLOCK! with 3 elements in it + ; + return reduce [ + ensure object! hdr + ensure [binary! block!] rest + ensure binary! end + ] ] + read-decode: function [ - "Reads code/data from source or DLL, decodes it, returns result (binary, block, image,...)." - source [file! url!] "Source or block of sources?" - type [word! none!] "File type, or NONE for binary raw data" + "Reads code/data from source or DLL, decodes it, returns result." + source [file! url!] + "Source (binary, block, image,...) or block of sources?" + type [word! blank!] + "File type, or NONE for binary raw data" ][ - either type = 'extension [ ; DLL-based extension - ; Try to load it (will fail if source is a url) - data: load-extension source ; returns an object or throws an error - ][ - data: read source ; can be string, binary, block - if find system/options/file-types type [data: decode type :data] ; e.g. not 'unbound - ] - data + either type = 'extension [ + ; DLL-based extension, try to load it (will fail if source is a url) + ; `load-extension` returns an object or throws an error + data: load-extension source + ][ + data: read source ; can be string, binary, block + if find system/options/file-types type [ + ; e.g. not 'unbound + data: decode type :data + ] + ] + data ] + load: function [ - {Loads code or data from a file, URL, string, or binary.} - source [file! url! string! binary! block!] {Source or block of sources} - /header {Result includes REBOL header object (preempts /all)} - /all {Load all values (does not evaluate REBOL header)} - /type {Override default file-type; use NONE to always load as code} - ftype [word! none!] "E.g. text, markup, jpeg, unbound, etc." + {Loads code or data from a file, URL, string, or binary.} + source [file! url! string! binary! block!] + {Source or block of sources} + /header + {Result includes REBOL header object (preempts /all)} + /all ;-- renamed to all_LOAD to avoid conflict with ALL native + {Load all values (does not evaluate REBOL header)} + /type + {Override default file-type; use NONE to always load as code} + ftype [word! blank!] + "E.g. text, markup, jpeg, unbound, etc." ] [ - ; WATCH OUT: for ALL and NEXT words! They are local. - - ; NOTES: - ; Note that code/data can be embedded in other datatypes, including - ; not just text, but any binary data, including images, etc. The type - ; argument can be used to control how the raw source is converted. - ; Pass a /type of none or 'unbound if you want embedded code or data. - ; Scripts are normally bound to the user context, but no binding will - ; happen for a module or if the /type is 'unbound. This allows the result - ; to be handled properly by DO (keeping it out of user context.) - ; Extensions will still be loaded properly if /type is 'unbound. - ; Note that IMPORT has its own loader, and does not use LOAD directly. - ; /type with anything other than 'extension disables extension loading. - - assert/type [local none!] ; easiest way to protect against /local hacks - - case/all [ - header [all: none] - - ;-- Load multiple sources? - block? source [ - return map-each item source [apply :load [:item header all type ftype]] - ] - - ;-- What type of file? Decode it too: - any [file? source url? source] [ - sftype: file-type? source - ftype: case [ - lib/all ['unbound = ftype 'extension = sftype] [sftype] - type [ftype] - 'else [sftype] - ] - data: read-decode source ftype - ] - none? data [data: source] - - ;-- Is it not source code? Then return it now: - any [block? data not find [0 extension unbound] any [ftype 0]][ ; due to make-boot issue with #[none] - return data ; directory, image, txt, markup, etc. - ] - - ;-- Try to load the header, handle error: - not all [ - set [hdr: data:] either object? data [load-ext-module data] [load-header data] - if word? hdr [cause-error 'syntax hdr source] - ] - ; data is binary or block now, hdr is object or none - - ;-- Convert code to block, insert header if requested: - not block? data [data: to block! data] - header [insert data hdr] - - ;-- Bind code to user context: - not any [ - 'unbound = ftype - 'module = select hdr 'type - find select hdr 'options 'unbound - ][data: intern data] - - ;-- If appropriate and possible, return singular data value: - not any [ - all - header - empty? data - 1 < length? data - ][data: first data] - ] - :data + ; Rename the /all refinement out of the way and put back lib/all (safer!) + all_LOAD: all + all: :lib/all + + file: line: void + + ; NOTES: + ; Note that code/data can be embedded in other datatypes, including + ; not just text, but any binary data, including images, etc. The type + ; argument can be used to control how the raw source is converted. + ; Pass a /type of blank or 'unbound if you want embedded code or data. + ; Scripts are normally bound to the user context, but no binding will + ; happen for a module or if the /type is 'unbound. This allows the result + ; to be handled properly by DO (keeping it out of user context.) + ; Extensions will still be loaded properly if /type is 'unbound. + ; Note that IMPORT has its own loader, and does not use LOAD directly. + ; /type with anything other than 'extension disables extension loading. + + case/all [ + header [all_LOAD: _] + + ;-- Load multiple sources? + block? source [ + return map-each item source [ + load/type/(all [header 'header])/(all [all_LOAD 'all]) + item :ftype + ] + ] + + ;-- What type of file? Decode it too: + maybe? [file! url!] source [ + file: source + line: 1 + + sftype: file-type? source + ftype: case [ + all [:ftype = 'unbound | :sftype = 'extension] [sftype] + type [ftype] + ] else [ + sftype + ] + data: read-decode source ftype + if sftype = 'extension [return data] + ] + + void? :data [data: source] + + ;-- Is it not source code? Then return it now: + any [block? data | not find [0 extension unbound] any [:ftype 0]] [ + ; !!! "due to make-boot issue with #[none]" <-- What? + return data ; directory, image, txt, markup, etc. + ] + + ;-- Try to load the header, handle error: + not all_LOAD [ + set [hdr: data:] either object? data [ + load-ext-module data + ][ + load-header data + ] + if word? hdr [cause-error 'syntax hdr source] + ] + not set? 'hdr [hdr: _] + ; data is binary or block now, hdr is object or blank + + ;-- Convert code to block, insert header if requested: + not block? data [ + if string? data [ + data: to binary! data ;-- !!! inefficient, might be UTF8 + ] + assert [binary? data] + data: transcode/file/line data :file :line + take/last data ;-- !!! always the residual, a #{}... why? + ] + + header [insert data hdr] + + ;-- Bind code to user context: + not any [ + 'unbound = :ftype ;-- may be void + | + 'module = select hdr 'type + | + find select hdr 'options 'unbound + ][ + data: intern data + ] + + ;-- If appropriate and possible, return singular data value: + not any [ + all_LOAD + header + empty? data + 1 < length-of data + ][ + data: first data + ] + ] + + :data ] + do-needs: function [ - "Process the NEEDS block of a program header. Returns unapplied mixins." - needs [block! object! tuple! none!] "Needs block, header or version" - /no-share "Force module to use its own non-shared global namespace" - /no-lib "Don't export to the runtime library" - /no-user "Don't export to the user context (mixins returned)" - /block "Return all the imported modules in a block, instead" + "Process the NEEDS block of a program header. Returns unapplied mixins." + needs [block! object! tuple! blank!] + "Needs block, header or version" + /no-share + "Force module to use its own non-shared global namespace" + /no-lib + "Don't export to the runtime library" + /no-user + "Don't export to the user context (mixins returned)" + /block + "Return all the imported modules in a block, instead" +][ + ; NOTES: + ; This is a low-level function and its use and return values reflect that. + ; In user mode, the mixins are applied by IMPORT, so they don't need to + ; be returned. In /no-user mode the mixins are collected into an object + ; and returned, if the object isn't empty. This object can then be passed + ; to MAKE module! to be applied there. The /block option returns a block + ; of all the modules imported, not any mixins - this is for when IMPORT + ; is called with a Needs block. + + case/all [ + ; If it's a header object: + object? needs [needs: select needs 'needs] ; (protected) + + blank? needs [return blank] + + ; If simple version number check: + tuple? needs [ + case [ + needs > system/version [ + cause-error 'syntax 'needs reduce ['core needs] + ] + + 3 >= length-of needs [ ; no platform id + blank + ] + + (needs and* 0.0.0.255.255) + != (system/version and* 0.0.0.255.255) [ + cause-error 'syntax 'needs reduce ['core needs] + ] + ] + return blank + ] + + ; If it's an inline value, put it in a block: + not block? needs [needs: reduce [needs]] + + empty? needs [return blank] + ] + + ; Parse the needs dialect [source ] + mods: make block! length-of needs + name: vers: hash: _ + unless parse needs [ + here: + opt [opt 'core set vers tuple! (do-needs vers)] + any [ + here: + set name [word! | file! | url!] + set vers opt tuple! + set hash opt binary! + (join mods [name vers hash]) + ] + ][ + cause-error 'script 'invalid-arg here + ] + + ; Temporary object to collect exports of "mixins" (private modules). + ; Don't bother if returning all the modules in a block, or if in user mode. + if all [no-user not block] [ + ; Minimal length since it may persist later + mixins: make object! 0 + ] + + ; Import the modules: + mods: map-each [name vers hash] mods [ + ; Import the module + mod: apply 'import [ + module: name + + version: true + ver: opt vers + + check: true + sum: opt hash + + no-share: no-share + no-lib: no-lib + no-user: no-user + ] + + ; Collect any mixins into the object (if we are doing that) + if all [any-value? :mixins | mixin? mod] [ + resolve/extend/only mixins mod select meta-of mod 'exports + ] + mod + ] + + case [ + block [mods] ; /block: return block of modules + not empty? to-value :mixins [mixins] ; else return mixins, if any + ] +] + + +load-ext-module: function [ + spec [binary!] "Spec for the module" + impl [handle!] "Native function implementation array" + error-base [integer! blank!] "error base for the module" + /unloadable + /no-lib + /no-user ][ - ; NOTES: - ; This is a low-level function and its use and return values reflect that. - ; In user mode, the mixins are applied by IMPORT, so they don't need to - ; be returned. In /no-user mode the mixins are collected into an object - ; and returned, if the object isn't empty. This object can then be passed - ; to MAKE module! to be applied there. The /block option returns a block - ; of all the modules imported, not any mixins - this is for when IMPORT - ; is called with a Needs block. - - case/all [ - ; If it's a header object: - object? needs [set/any 'needs select needs 'needs] ; (protected) - none? needs [return none] - ; If simple version number check: - tuple? :needs [ - case [ - needs > system/version [cause-error 'syntax 'needs reduce ['core needs]] - 3 >= length? needs none ; no platform id - (needs and 0.0.0.255.255) != (system/version and 0.0.0.255.255) [ - cause-error 'syntax 'needs reduce ['core needs] ; must match - ] - ] - return none - ] - ; If it's an inline value, put it in a block: - not block? :needs [needs: reduce [:needs]] - empty? needs [return none] - ] - - ; Parse the needs dialect [source |version| |checksum-hash|] - mods: make block! length? needs - name: vers: hash: none - unless parse needs [ - here: - opt [opt 'core set vers tuple! (do-needs vers)] - any [ - here: - set name [word! | file! | url!] - set vers opt tuple! - set hash opt binary! - (repend mods [name vers hash]) - ] - ][ - cause-error 'script 'invalid-arg here - ] - - ; Temporary object to collect exports of "mixins" (private modules). - ; Don't bother if returning all the modules in a block, or if in user mode. - if all [no-user not block] [mixins: make object! 0] ; Minimal length since it may persist later - - ; Import the modules: - mods: map-each [name vers hash] mods [ - ; Import the module - mod: apply :import [name true? vers vers true? hash hash no-share no-lib no-user] - ; Collect any mixins into the object (if we are doing that) - if all [mixins mixin? mod] [resolve/extend/only mixins mod select spec-of mod 'exports] - mod - ] - - case [ - block [mods] ; /block: return block of modules - not empty? mixins [mixins] ; else return mixins, if any - ] + code: load/header decompress spec + hdr: take code + tmp-ctx: make object! [ + native: function [ + return: [function!] + spec + /export + "this refinement is ignored here" + /body + code [block!] + "Equivalent rebol code" + + index (-1) + ] compose [ + index: index + 1 + f: load-native/(all [body 'body])/(all [unloadable 'unloadable]) spec (impl) index :code + :f + ] + ] + mod: make module! (length-of code) / 2 + set-meta mod hdr + if errors: find code to set-word! 'errors [ + eo: construct make object! [ + code: error-base + type: lowercase spaced [hdr/name "error"] + ] second errors + append system/catalog/errors reduce [to set-word! hdr/name eo] + remove/part errors 2 + ] + bind/only/set code mod + bind hdr/exports mod + bind code tmp-ctx + if w: in mod 'words [protect/hide w] + do code + + if hdr/name [ + reduce/into [ + hdr/name mod either hdr/checksum [copy hdr/checksum][blank] + ] system/modules + ] + + case [ + not module? mod [blank] + + not block? select hdr 'exports [blank] + + empty? hdr/exports [blank] + + find hdr/options 'private [ + ; full export to user + unless no-user [ + resolve/extend/only system/contexts/user mod hdr/exports + ] + ] + ] else [ + unless no-lib [ + resolve/extend/only system/contexts/lib mod hdr/exports + ] + unless no-user [ + resolve/extend/only system/contexts/user mod hdr/exports + ] + ] + + mod ] + load-module: function [ - {Loads a module (from a file, URL, binary, etc.) and inserts it into the system module list.} - source [word! file! url! string! binary! module! block!] {Source or block of sources} - /version ver [tuple!] "Module must be this version or greater" - /check sum [binary!] "Match checksum (must be set in header)" - /no-share "Force module to use its own non-shared global namespace" - /no-lib "Don't export to the runtime library (lib)" - /import "Do module import now, overriding /delay and 'delay option" - /as name [word!] "New name for the module (not valid for reloads)" - /delay "Delay module init until later (ignored if source is module!)" + {Loads a module and inserts it into the system module list.} + source [word! file! url! string! binary! module! block!] + {Source (file, URL, binary, etc.) or block of sources} + /version ver [tuple!] + "Module must be this version or greater" + /check sum [binary!] + "Match checksum (must be set in header)" + /no-share + "Force module to use its own non-shared global namespace" + /no-lib + "Don't export to the runtime library (lib)" + /import + "Do module import now, overriding /delay and 'delay option" + /as + name [word!] + "New name for the module (not valid for reloads)" + /delay + "Delay module init until later (ignored if source is module!)" ][ - ; NOTES: - ; This is a variation of LOAD that is used by IMPORT. Unlike LOAD, the module init - ; may be delayed. The module may be stored as binary or as an unbound block, then - ; init'd later, as needed. - ; - ; The checksum applies to the uncompressed binary source of the body, and - ; is calculated in LOAD-HEADER if the 'checksum header field is set. - ; A copy of the checksum is saved in the system modules list for security. - ; /no-share and /delay are ignored for module! source because it's too late. - ; A name is required for all imported modules, delayed or not; /as can be - ; specified for unnamed modules. If you don't want to name it, don't import. - ; If source is a module that is loaded already, /as name is an error. - ; - ; Returns block of name, and either built module or none if delayed. - ; Returns none if source is word and no module of that name is loaded. - ; Returns none if source is file/url and read or load-extension fails. - - assert/type [local none!] ; easiest way to protect against /local hacks - if import [delay: none] ; /import overrides /delay - - ; Process the source, based on its type - case [ - word? source [ ; loading the preloaded - case/all [ - as [cause-error 'script 'bad-refine /as] ; no renaming - ; Return none if no module of that name found - not tmp: find/skip system/modules source 3 [return none] - set [mod: modsum:] next tmp none ; get the module - ;assert/type [mod [module! block!] modsum [binary! none!]] none - ; If no further processing is needed, shortcut return - all [not version not check any [delay module? :mod]] [ - return reduce [source if module? :mod [mod]] - ] - ] - ] - binary? source [data: source] - string? source [data: to binary! source] - any [file? source url? source] [ - tmp: file-type? source - case [ ; Return none if read or load-extension fails - not tmp [unless attempt [data: read source] [return none]] - tmp = 'extension [ ; special processing for extensions - ; load-extension also fails for url! - unless attempt [ext: load-extension source] [return none] - data: ext/lib-boot ; save for checksum before it's unset - case [ - import [set [hdr: code:] load-ext-module ext] - word? set [hdr: tmp:] load-header/only/required data [ - cause-error 'syntax hdr source ; word is error code - ] - not any [delay delay: true? find hdr/options 'delay] [ - set [hdr: code:] load-ext-module ext ; import now - ] - ] - if hdr/checksum [modsum: copy hdr/checksum] - ] - 'else [cause-error 'access 'no-script source] ; needs better error - ] - ] - module? source [ ; see if the same module is already in the list - if tmp: find/skip next system/modules mod: source 3 [ - if as [cause-error 'script 'bad-refine /as] ; already imported - if all [ ; not /version, not /check, same as top module of that name - not version not check same? mod select system/modules pick tmp 0 - ] [return copy/part back tmp 2] - set [mod: modsum:] tmp - ] - ] - block? source [ - if any [version check as] [cause-error 'script 'bad-refines none] - data: make block! length? source - unless parse source [ - any [ - tmp: - set name opt set-word! - set mod [word! | module! | file! | url! | string! | binary!] - set ver opt tuple! - set sum opt binary! ; ambiguous - (repend data [mod ver sum if name [to word! name]]) - ] - ] [cause-error 'script 'invalid-arg tmp] - return map-each [mod ver sum name] source [ - apply :load-module [ - mod true? ver ver true? sum sum no-share no-lib import true? name name delay - ] - ] - ] - ] - - case/all [ - ; Get info from preloaded or delayed modules - module? mod [ - delay: no-share: none hdr: spec-of mod - assert/type [hdr/options [block! none!]] - ] - block? mod [set/any [hdr: code:] mod] - ; module/block mod used later for override testing - - ; Get and process the header - not hdr [ - ; Only happens for string, binary or non-extension file/url source - set [hdr: code:] load-header/required data - case [ - word? hdr [cause-error 'syntax hdr source] - import none ; /import overrides 'delay option - not delay [delay: true? find hdr/options 'delay] - ] - if hdr/checksum [modsum: copy hdr/checksum] - ] - no-share [hdr/options: append any [hdr/options make block! 1] 'isolate] - - ; Unify hdr/name and /as name - name [hdr/name: name] ; rename /as name - not name [set/any 'name :hdr/name] - all [not no-lib not word? :name] [ ; requires name for full import - ; Unnamed module can't be imported to lib, so /no-lib here - no-lib: true ; Still not /no-lib in IMPORT - ; But make it a mixin and it will be imported directly later - unless find hdr/options 'private [ - hdr/options: append any [hdr/options make block! 1] 'private - ] - ] - - not tuple? set/any 'modver :hdr/version [modver: 0.0.0] ; get version - - ; See if it's there already, or there is something more recent - all [ - override?: not no-lib ; set to false later if existing module is used - set [name0: mod0: sum0:] pos: find/skip system/modules name 3 - ] [ - ; Get existing module's info - case/all [ - module? :mod0 [hdr0: spec-of mod0] ; final header - block? :mod0 [hdr0: first mod0] ; cached preparsed header - ;assert/type [name0 word! hdr0 object! sum0 [binary! none!]] none - not tuple? set/any 'ver0 :hdr0/version [ver0: 0.0.0] - ] - ; Compare it to the module we want to load - case [ - same? mod mod0 [override?: not any [delay module? mod]] ; here already - module? mod0 [ ; premade module - pos: none ; just override, don't replace - if ver0 >= modver [ ; it's at least as new, use it instead - mod: mod0 hdr: hdr0 code: none - modver: ver0 modsum: sum0 - override?: false - ] - ] - ; else is delayed module - ver0 > modver [ ; and it's newer, use it instead - mod: none set [hdr code] mod0 - modver: ver0 modsum: sum0 - ext: if object? code [code] ; delayed extension - override?: not delay ; stays delayed if /delay - ] - ] - ] - not module? mod [mod: none] ; don't need/want the block reference now - - ; Verify /check and /version - all [check sum !== modsum] [cause-error 'access 'invalid-check module] - all [version ver > modver] [cause-error 'syntax 'needs reduce [name ver]] - - ; If no further processing is needed, shortcut return - all [not override? any [mod delay]] [return reduce [name mod]] - - ; If /delay, save the intermediate form - delay [mod: reduce [hdr either object? ext [ext] [code]]] - - ; Else not /delay, make the module if needed - not mod [ ; not prebuilt or delayed, make a module - case/all [ - find hdr/options 'isolate [no-share: true] ; in case of delay - object? code [ ; delayed extension - set [hdr: code:] load-ext-module code - hdr/name: name ; in case of delayed rename - if all [no-share not find hdr/options 'isolate] [ - hdr/options: append any [hdr/options make block! 1] 'isolate - ] - ] - binary? code [code: to block! code] - ] - assert/type [hdr object! code block!] - mod: reduce [hdr code do-needs/no-user hdr] - mod: catch/quit [make module! mod] - ] - - all [not no-lib override?] [ - case/all [ - pos [pos/2: mod pos/3: modsum] ; replace delayed module - not pos [reduce/into [name mod modsum] system/modules] - all [module? mod not mixin? hdr block? select hdr 'exports] [ - resolve/extend/only lib mod hdr/exports ; no-op if empty - ] - ] - ] - ] - - reduce [name if module? mod [mod]] + as_LOAD_MODULE: :as + as: :lib/as + + ; NOTES: + ; + ; This is a variation of LOAD that is used by IMPORT. Unlike LOAD, the + ; module init may be delayed. The module may be stored as binary or as an + ; unbound block, then init'd later, as needed. + ; + ; The checksum applies to the uncompressed binary source of the body, and + ; is calculated in LOAD-HEADER if the 'checksum header field is set. + ; A copy of the checksum is saved in the system modules list for security. + ; /no-share and /delay are ignored for module! source because it's too late. + ; A name is required for all imported modules, delayed or not; /as can be + ; specified for unnamed modules. If you don't want to name it, don't import. + ; If source is a module that is loaded already, /as name is an error. + ; + ; Returns block of name, and either built module or blank if delayed. + ; Returns blank if source is word and no module of that name is loaded. + ; Returns blank if source is file/url and read or load-extension fails. + + if import [delay: _] ; /import overrides /delay + + ; Process the source, based on its type + case [ + word? source [ ; loading the preloaded + case/all [ + as_LOAD_MODULE [ + cause-error 'script 'bad-refine /as ; no renaming + ] + + ; Return blank if no module of that name found + not tmp: find/skip system/modules source 3 [return blank] + + ; get the module + set [mod: modsum:] next tmp [blank] + + [ + ensure [module! block!] mod + ensure [binary! blank!] modsum + ] + + ; If no further processing is needed, shortcut return + all [not version | not check | any [delay module? :mod]] [ + return reduce [source if module? :mod [mod]] + ] + ] + ] + binary? source [data: source] + string? source [data: to binary! source] + + any [file? source | url? source] [ + tmp: file-type? source + case [ ; Return blank if read or load-extension fails + not tmp [ + unless attempt [data: read source] [return blank] + ] + + tmp = 'extension [ + fail "Use LOAD or LOAD-EXTENSION to load an extension" + ] + ] else [ + cause-error 'access 'no-script source ; needs better error + ] + ] + + module? source [ + ; see if the same module is already in the list + if tmp: find/skip next system/modules mod: source 3 [ + if as_LOAD_MODULE [ + ; already imported + cause-error 'script 'bad-refine /as + ] + + if all [ + ; not /version, not /check, same as top module of that name + not version + not check + same? mod select system/modules pick tmp 0 + ][ + return copy/part back tmp 2 + ] + + set [mod: modsum:] tmp + ] + ] + + block? source [ + if any [version check as] [ + cause-error 'script 'bad-refines blank + ] + + data: make block! length-of source + + unless parse source [ + any [ + tmp: + set name opt set-word! + set mod [word! | module! | file! | url! | string! | binary!] + set ver opt tuple! + set sum opt binary! ; ambiguous + (join data [mod ver sum if name [to word! name]]) + ] + ][ + cause-error 'script 'invalid-arg tmp + ] + + return map-each [mod ver sum name] source [ + apply 'load-module [ + source: mod + version: version + ver: :ver + check: :check + sum: :sum + as: true + name: opt name + no-share: no-share + no-lib: no-lib + import: import + delay: delay + ] + ] + ] + ] + + case/all [ + ; Get info from preloaded or delayed modules + void? :mod [mod: _] + module? mod [ + delay: no-share: _ hdr: meta-of mod + ensure [block! blank!] hdr/options + ] + block? mod [set* [hdr: code:] mod] + + ; module/block mod used later for override testing + + ; Get and process the header + void? :hdr [ + ; Only happens for string, binary or non-extension file/url source + set [hdr: code:] load-header/required data + case [ + word? hdr [cause-error 'syntax hdr source] + import blank ; /import overrides 'delay option + not delay [delay: true? find hdr/options 'delay] + ] + if hdr/checksum [modsum: copy hdr/checksum] + ] + no-share [ + hdr/options: append any [hdr/options make block! 1] 'isolate + ] + + ; Unify hdr/name and /as name + any-value? :name [hdr/name: name] ; rename /as name + void? :name [name: :hdr/name] + all [not no-lib not word? :name] [ ; requires name for full import + ; Unnamed module can't be imported to lib, so /no-lib here + no-lib: true ; Still not /no-lib in IMPORT + + ; But make it a mixin and it will be imported directly later + unless find hdr/options 'private [ + hdr/options: append any [hdr/options make block! 1] 'private + ] + ] + not tuple? set* 'modver :hdr/version [ + modver: 0.0.0 ; get version + ] + + ; See if it's there already, or there is something more recent + all [ + ; set to false later if existing module is used + override?: not no-lib + + set [name0: mod0: sum0:] pos: find/skip system/modules name 3 + ] [ + ; Get existing module's info + case/all [ + module? :mod0 [hdr0: meta-of mod0] ; final header + block? :mod0 [hdr0: first mod0] ; cached preparsed header + + [ + ensure word! name0 + ensure object! hdr0 + ensure [binary! blank!] sum0 + ] + + not tuple? ver0: :hdr0/version [ver0: 0.0.0] + ] + + ; Compare it to the module we want to load + case [ + same? mod mod0 [ + override?: not any [delay module? mod] ; here already + ] + + module? mod0 [ + ; premade module + pos: _ ; just override, don't replace + if ver0 >= modver [ + ; it's at least as new, use it instead + mod: mod0 | hdr: hdr0 | code: _ + modver: ver0 | modsum: sum0 + override?: false + ] + ] + + ; else is delayed module + ver0 > modver [ ; and it's newer, use it instead + mod: _ set [hdr code] mod0 + modver: ver0 | modsum: sum0 + ext: all [(object? code) code] ; delayed extension + override?: not delay ; stays delayed if /delay + ] + ] + ] + + not module? mod [ + mod: _ ; don't need/want the block reference now + ] + + ; Verify /check and /version + all [check sum !== modsum] [ + cause-error 'access 'invalid-check module + ] + all [version ver > modver] [ + cause-error 'syntax 'needs reduce [name ver] + ] + + ; If no further processing is needed, shortcut return + all [not override? any [mod delay]] [return reduce [name mod]] + + ; If /delay, save the intermediate form + delay [mod: reduce [hdr either object? ext [ext] [code]]] + + ; Else not /delay, make the module if needed + not mod [ + ; not prebuilt or delayed, make a module + case/all [ + find hdr/options 'isolate [no-share: true] ; in case of delay + + object? code [ ; delayed extension + set [hdr: code:] load-ext-module code + hdr/name: name ; in case of delayed rename + if all [no-share not find hdr/options 'isolate] [ + hdr/options: append any [hdr/options make block! 1] 'isolate + ] + ] + + binary? code [code: to block! code] + ] + + ensure object! hdr + ensure block! code + + mod: catch/quit [ + module/mixin hdr code (opt do-needs/no-user hdr) + ] + ] + + all [not no-lib override?] [ + unless any-value? :modsum [modsum: _] + case/all [ + pos [pos/2: mod pos/3: modsum] ; replace delayed module + + not pos [reduce/into [name mod modsum] system/modules] + + all [module? mod not mixin? hdr block? select hdr 'exports] [ + resolve/extend/only lib mod hdr/exports ; no-op if empty + ] + ] + ] + ] + + reduce [name if module? mod [mod]] ] + import: function [ - "Imports a module; locate, load, make, and setup its bindings." - module [word! file! url! string! binary! module! block!] - /version ver [tuple!] "Module must be this version or greater" - /check sum [binary!] "Match checksum (must be set in header)" - /no-share "Force module to use its own non-shared global namespace" - /no-lib "Don't export to the runtime library (lib)" - /no-user "Don't export to the user context" - ; See also: sys/make-module*, sys/load-module, sys/do-needs + ; See also: sys/make-module*, sys/load-module, sys/do-needs + + "Imports a module; locate, load, make, and setup its bindings." + module [word! file! url! string! binary! module! block! tag!] + /version ver [tuple!] + "Module must be this version or greater" + /check sum [binary!] + "Match checksum (must be set in header)" + /no-share + "Force module to use its own non-shared global namespace" + /no-lib + "Don't export to the runtime library (lib)" + /no-user + "Don't export to the user context" ][ - ; If it's a needs dialect block, call DO-NEEDS/block: - if block? module [ - assert [not version not check] ; these can only apply to one module - return apply :do-needs [module no-share no-lib no-user /block] - ] - ; Note: IMPORT block! returns a block of all the modules imported. - - ; Try to load and check the module. - set [name: mod:] apply :load-module [module version ver check sum no-share no-lib /import] - - case [ - mod none ; success! - word? module [ - ; Module (as word!) is not loaded already, so let's try to find it. - file: append to file! module system/options/default-suffix - foreach path system/options/module-paths [ - if set [name: mod:] apply :load-module [ - path/:file version ver check sum no-share no-lib /import /as module - ] [break] - ] - ] - any [file? module url? module] [ - cause-error 'access 'cannot-open reduce [module "not found or not valid"] - ] - ] - - unless mod [cause-error 'access 'cannot-open reduce [module "module not found"]] - - ; Do any imports to the user context that are necessary. - ; The lib imports were handled earlier by LOAD-MODULE. - case [ - ; Do nothing if /no-user or no exports. - no-user none - not block? exports: select hdr: spec-of mod 'exports none - empty? exports none - ; If it's a private module (mixin), we must add *all* of its exports to user. - any [no-lib find select hdr 'options 'private] [ ; /no-lib causes private - resolve/extend/only system/contexts/user mod exports - ] - ; Unless /no-lib its exports are in lib already, so just import what we need. - not no-lib [resolve/only system/contexts/user lib exports] - ] - - mod ; module! returned + if tag? module [ + if trap? [ + module: first tmp: select load rebol/locale/library/modules module + ][ + cause-error 'access 'cannot-open reduce + either blank? tmp [ + [module "module not found in system/locale/library/modules"] + ][ + [module "error occurred in loading module from system/locale/library/modules"] + ] + ] + ] + ; If it's a needs dialect block, call DO-NEEDS/block: + if block? module [ + assert [not version not check] ; these can only apply to one module + return apply 'do-needs [ + needs: module + no-share: :no-share + no-lib: :no-lib + no-user: :no-user + block: true + ] + ] + + ; Note: IMPORT block! returns a block of all the modules imported. + + ; Try to load and check the module. + ; !!! the original code said /import, not conditional on refinement + set [name: mod:] apply 'load-module [ + source: module + version: version + ver: :ver + check: check + sum: :sum + no-share: no-share + no-lib: no-lib + import: true + ] + + case [ + mod blank ; success! + + word? module [ + ; Module (as word!) is not loaded already, so let's try to find it. + file: append to file! module system/options/default-suffix + + for-each path system/options/module-paths [ + if set [name: mod:] ( + apply 'load-module [ + source: path/:file + version: version + ver: :ver + check: check + sum: :sum + no-share: :no-share + no-lib: :no-lib + import: true + ] + ) [ + break + ] + ] + ] + + any [file? module | url? module] [ + cause-error 'access 'cannot-open reduce [module "not found or not valid"] + ] + ] + + unless mod [ + cause-error 'access 'cannot-open reduce [module "module not found"] + ] + + ; Do any imports to the user context that are necessary. + ; The lib imports were handled earlier by LOAD-MODULE. + case [ + ; Do nothing if /no-user or no exports. + no-user blank + not block? exports: select hdr: meta-of mod 'exports blank + empty? exports blank + + any [ + no-lib + find select hdr 'options 'private ; /no-lib causes private + ][ + ; It's a private module (mixin) + ; we must add *all* of its exports to user + + resolve/extend/only system/contexts/user mod exports + ] + + ; Unless /no-lib its exports are in lib already + ; ...so just import what we need. + not no-lib [resolve/only system/contexts/user lib exports] + ] + + mod ; module! returned ] -export [load import] - -#test [ -test: [ - [ - write %test-emb.r {123^/[REBOL [title: "embed"] 1 2 3]^/123^/} - [1 2 3] = xload/header/type %test-emb.r 'unbound - ] -][ ; General function: - [[1 2 3] = xload ["1" "2" "3"]] - [[] = xload " "] - [1 = xload "1"] - [[1] = xload "[1]"] - [[1 2 3] = xload "1 2 3"] - [[1 2 3] = xload/type "1 2 3" none] - [[1 2 3] = xload "rebol [] 1 2 3"] - [ - d: xload/header "rebol [] 1 2 3" - all [object? first d [1 2 3] = next d] - ] - [[rebol [] 1 2 3] = xload/all "rebol [] 1 2 3"] - - ; File variations: - [equal? read %./ xload %./] - [ - write %test.txt s: "test of text" - s = xload %test.txt - ] - [ - write %test.html "

test

" - [

"test"

] = xload %test.html - ] - [ - save %test2.r 1 - 1 = xload %test1.r - ] - [ - save %test2.r [1 2] - [1 2] = xload %test2.r - ] - [ - save/header %test.r [1 2 3] [title: "Test"] - [1 2 3] = xload %test.r - ] - [ - save/header %test-checksum.r [1 2 3] [checksum: true] - ;print read/string %test-checksum.r - [1 2 3] = xload %test-checksum.r - ] - [ - save/header %test-checksum.r [1 2 3] [checksum: true compress: true] - ;print read/string %test-checksum.r - [1 2 3] = xload %test-checksum.r - ] - [ - save/header %test-checksum.r [1 2 3] [checksum: script compress: true] - ;print read/string %test-checksum.r - [1 2 3] = xload %test-checksum.r - ] - [ - write %test-emb.r {123^/[REBOL [title: "embed"] 1 2 3]^/123^/} - [1 2 3] = probe xload/header %test-emb.r - ] + +load-extension: function [ + file [file! handle!] "library file or handle to init function in the builtin extension" + /no-user "Do not export to the user context" + /no-lib "Do not export to the lib context" +][ + + ext: load-extension-helper file + ;print ["ext:" mold ext] + if locked? ext [; already loaded + return ext + ] + case [ + string? ext/script [ + script: load/header ext/script + ] + binary? ext/script [ + script: decompress ext/script + script: load/header script + ] + ] else [ + ; ext/script should ALWAYS be set by the extension but if it's not, + ; do not fail, because failing to load a builtin extension could + ; cause the interpreter to fail to boot + ; + script: reduce [construct system/standard/header []] + ] + + ext/script: _ ;clear the startup script to save memory + ext/header: take script + modules: make block! 1 + for-each [spec impl error-base] ext/modules [ + append modules apply 'load-ext-module [ + spec: spec + impl: impl + error-base: error-base + unloadable: true + no-user: no-user + no-lib: no-lib + ] + ] + + ext/modules: modules + if blank? ext/header/type [ + ext/header/type: 'extension + ] + + lock ext/header + lock ext + + append system/extensions ext + + ;run the startup script + do script + + ext ] -foreach t test [print either do t ['ok] [join "FAILED:" mold t] print ""] -halt + + +unload-extension: procedure [ + ext [object!] "extension object" +][ + + ;sanity checking + unless locked? ext [ + fail "Extension is not locked" + ] + unless all [ + library? ext/lib-base + file? ext/lib-file + ][ + fail "Can't unload a builtin extension" + ] + + remove find system/extensions ext + for-each m ext/modules [ + remove/part back find system/modules m 3 + ;print ["words-of m:" words-of m] + for-each w words-of m [ + v: get w + if all [function? :v 1 = func-class-of :v] [ + unload-native :v + ] + ] + ] + unload-extension-helper ext ] + + +export [load import load-extension unload-extension] diff --git a/src/mezz/sys-ports.r b/src/mezz/sys-ports.r index 8f2e73d871..7519b483f7 100644 --- a/src/mezz/sys-ports.r +++ b/src/mezz/sys-ports.r @@ -1,272 +1,364 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Boot Sys: Port and Scheme Functions" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Context: sys - Note: { - The boot binding of this module is SYS then LIB deep. - Any non-local words not found in those contexts WILL BE - UNBOUND and will error out at runtime! - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Boot Sys: Port and Scheme Functions" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Context: sys + Note: { + The boot binding of this module is SYS then LIB deep. + Any non-local words not found in those contexts WILL BE + UNBOUND and will error out at runtime! + } ] -make-port*: func [ - "SYS: Called by system on MAKE of PORT! port from a scheme." - spec [file! url! block! object! word! port!] "port specification" - /local name scheme port +make-port*: function [ + "SYS: Called by system on MAKE of PORT! port from a scheme." + + spec [file! url! block! object! word! port!] + "port specification" ][ - ; The first job is to identify the scheme specified: - case [ - file? spec [ - name: pick [dir file] dir? spec - spec: join [ref:] spec - ] - url? spec [ - spec: repend decode-url spec [to set-word! 'ref spec] - name: select spec to set-word! 'scheme - ] - block? spec [ - name: select spec to set-word! 'scheme - ] - object? spec [ - name: get in spec 'scheme - ] - word? spec [ - name: spec - spec: [] - ] - port? spec [ - name: port/scheme/name - spec: port/spec - ] - true [ - return none - ] - ] - - ; Get the scheme definition: - unless all [ - word? name - scheme: get in system/schemes name - ][cause-error 'access 'no-scheme name] - - ; Create the port with the correct scheme spec: - port: make system/standard/port [] - port/spec: make any [scheme/spec system/standard/port-spec-head] spec - port/spec/scheme: name - port/scheme: scheme - - ; Defaults: - port/actor: get in scheme 'actor ; avoid evaluation - port/awake: any [get in port/spec 'awake :scheme/awake] - unless port/spec/ref [port/spec/ref: spec] - unless port/spec/title [port/spec/title: scheme/title] - port: to port! port - - ; Call the scheme-specific port init. Note that if the - ; scheme has not yet been initialized, it can be done - ; at this time. - if in scheme 'init [scheme/init port] - port + ; The first job is to identify the scheme specified: + case [ + file? spec [ + name: pick [dir file] dir? spec + spec: join-of [ref:] spec + ] + url? spec [ + spec: join decode-url spec [to set-word! 'ref spec] + name: select spec to set-word! 'scheme + ] + block? spec [ + name: select spec to set-word! 'scheme + ] + object? spec [ + name: get in spec 'scheme + ] + word? spec [ + name: spec + spec: [] + ] + port? spec [ + name: port/scheme/name + spec: port/spec + ] + ] else [ + return blank + ] + + ; Get the scheme definition: + unless all [ + any [word? name lit-word? name] + scheme: get in system/schemes to word! name + ][cause-error 'access 'no-scheme name] + + ; Create the port with the correct scheme spec: + port: construct system/standard/port [] + port/spec: construct any [scheme/spec system/standard/port-spec-head] spec + port/spec/scheme: name + port/scheme: scheme + + ; Defaults: + port/actor: get in scheme 'actor ; avoid evaluation + port/awake: any [get in port/spec 'awake | :scheme/awake] + unless port/spec/ref [port/spec/ref: spec] + unless port/spec/title [port/spec/title: scheme/title] + port: to port! port + + ; Call the scheme-specific port init. Note that if the + ; scheme has not yet been initialized, it can be done + ; at this time. + if in scheme 'init [scheme/init port] + port ] -*parse-url: make object! [ - digit: make bitset! "0123456789" - digits: [1 5 digit] - alpha-num: make bitset! [#"a" - #"z" #"A" - #"Z" #"0" - #"9"] - scheme-char: insert copy alpha-num "+-." - path-char: insert copy alpha-num "/=+-_.;:&$@%*',~?| []()^"" ; !!! note: space allowed - user-char: insert copy alpha-num "=+-_.;&$%*,'#|" - pass-char: complement make bitset! "^/ ^-@" - s1: s2: none ; in R3, input datatype is preserved - these are now URL strings! - out: [] - emit: func ['w v] [reduce/into [to set-word! w if :v [to string! :v]] tail out] - - rules: [ - ; Scheme://user-host-part - [ - ; scheme name: [//] - copy s1 some scheme-char ":" opt "//" ; we allow it - (reduce/into [to set-word! 'scheme to lit-word! to string! s1] tail out) - - ; optional user [:pass] - opt [ - copy s1 some user-char - opt [#":" copy s2 to #"@" (emit pass s2)] - #"@" (emit user s1) - ] - - ; optional host [:port] - opt [ - copy s1 any user-char - opt [#":" copy s2 digits (compose/into [port-id: (to integer! s2)] tail out)] - (unless empty? s1 [attempt [s1: to tuple! s1] emit host s1]) - ] - ] - - ; optional path - opt [copy s1 some path-char (emit path s1)] - - ; optional bookmark - opt [#"#" copy s1 some path-char (emit tag s1)] - ] - - decode-url: func ["Decode a URL according to rules of sys/*parse-url." url] [ - --- "This function is bound in the context of sys/*parse-url." - out: make block! 8 - parse/all url rules - out - ] +*parse-url: has [ + digit: make bitset! "0123456789" + digits: [1 5 digit] + alpha-num: make bitset! [#"a" - #"z" #"A" - #"Z" #"0" - #"9"] + scheme-char: insert copy alpha-num "+-." + path-char: insert copy alpha-num "!/=+-_.;:&$@%*',~?| []()^"" ; !!! note: space allowed + user-char: insert copy alpha-num "=+-_.;&$@%*,'#|" + pass-char: complement make bitset! "^/ ^-@" + s1: s2: _ ; in R3, input datatype is preserved - these are now URL strings! + out: [] + emit: func ['w v] [reduce/into [to set-word! w if :v [to string! :v]] tail out] + + rules: [ + ; Scheme://user-host-part + [ + ; scheme name: [//] + copy s1 some scheme-char ":" opt "//" ; we allow it + (reduce/into [to set-word! 'scheme to lit-word! to string! s1] tail out) + + ; optional user [:pass] + opt [ + copy s1 some user-char + opt [#":" copy s2 to #"@" (emit pass s2)] + #"@" (emit user s1) + ] + + ; optional host [:port] + opt [ + copy s1 any user-char + opt [ + #":" copy s2 digits ( + compose/into [ + port-id: (to-integer/unsigned s2) + ] tail out + ) + ] ( + ; Note: This code has historically attempted to convert + ; the host name into a TUPLE!, and if it succeeded it + ; considers this to represent an IP address lookup vs. + ; a DNS lookup. A basis for believing this will work can + ; come from RFC-1738: + ; + ; "The rightmost domain label will never start with a + ; digit, though, which syntactically distinguishes all + ; domain names from the IP addresses." + ; + ; This suggests that as long as a TUPLE! conversion will + ; never allow non-numeric characters it can work, though + ; giving a confusing response to looking up "1" to come + ; back and say "1.0.0 cannot be found", because that is + ; the result of `make tuple! "1"`. + ; + ; !!! This code was also broken in R3-Alpha, because the + ; captured content in PARSE of a URL! was a URL! and not + ; a STRING!, and so the attempt to convert `s1` to TUPLE! + ; would always fail. Ren-C permits this conversion. + + unless empty? trim s1 [ + attempt [s1: to tuple! s1] + emit host s1 + ] + ) + ] + ] + + ; optional path + opt [copy s1 some path-char (emit path s1)] + + ; optional bookmark + opt [#"#" copy s1 some path-char (emit tag s1)] + ] + + decode-url: func ["Decode a URL according to rules of sys/*parse-url." url] [ + --- "This function is bound in the context of sys/*parse-url." + out: make block! 8 + parse url rules + out + ] ] -decode-url: none ; used by sys funcs, defined above, set below +decode-url: _ ; used by sys funcs, defined above, set below ;-- Native Schemes ----------------------------------------------------------- -make-scheme: func [ - "INIT: Make a scheme from a specification and add it to the system." - def [block!] "Scheme specification" - /with 'scheme "Scheme name to use as base" - /local actor +make-scheme: function [ + "Make a scheme from a specification and add it to the system." + def [block!] + "Scheme specification" + /with + 'base-name + "Scheme name to use as base" ][ - with: either with [get in system/schemes scheme][system/standard/scheme] - unless with [cause-error 'access 'no-scheme scheme] - - def: make with def - ;print ["Scheme:" def/name] - unless def/name [cause-error 'access 'no-scheme-name def] - set-scheme def - - ; If actor is block build a non-contextual actor object: - if block? :def/actor [ - actor: make object! (length? def/actor) / 4 - foreach [name func* args body] def/actor [ ; (maybe PARSE is better here) - name: to word! name ; bug!!! (should not be necessary?) - repend actor [name func args body] - ] - def/actor: actor - ] - - append system/schemes reduce [def/name def] + with: either with [get in system/schemes base-name][system/standard/scheme] + unless with [cause-error 'access 'no-scheme base-name] + + scheme: construct with def + unless scheme/name [cause-error 'access 'no-scheme-name scheme] + + ; If actor is block build a non-contextual actor object: + if block? :scheme/actor [ + actor: make object! (length-of scheme/actor) / 4 + for-each [name func* args body] scheme/actor [ + ; !!! Comment here said "Maybe PARSE is better here", though + ; knowing would depend on understanding precisely what the goal + ; is in only allowing FUNC vs. alternative function generators. + assert [ + set-word? name + func* = 'func + block? args + block? body + ] + append actor reduce [ + name (func args body) ; add function! to object! w/name + ] + ] + scheme/actor: actor + ] + + unless maybe [object! handle!] :scheme/actor [ + fail ["Scheme actor" :scheme/name "can't be" type-of :scheme/actor] + ] + + append system/schemes reduce [scheme/name scheme] ] init-schemes: func [ - "INIT: Init system native schemes and ports." + "INIT: Init system native schemes and ports." ][ - loud-print "Init schemes" - - sys/decode-url: lib/decode-url: :sys/*parse-url/decode-url - - system/schemes: make object! 10 - - make-scheme [ - title: "System Port" - name: 'system - awake: func [ - sport "System port (State block holds events)" - ports "Port list (Copy of block passed to WAIT)" - /local event port waked - ][ - waked: sport/data ; The wake list (pending awakes) - - ; Process all events (even if no awake ports). - ; Do only 8 events at a time (to prevent polling lockout). - loop 8 [ - unless event: take sport/state [break] - port: event/port - if wake-up port event [ - ; Add port to wake list: - ;print ["==System-waked:" port/spec/ref] - unless find waked port [append waked port] - ] - ] - - ; No wake ports (just a timer), return now. - unless block? ports [return none] - - ; Are any of the requested ports awake? - forall ports [ - if find waked first ports [return true] - ] - - false ; keep waiting - ] - init: func [port] [ - ;;print ["Init" title] - port/data: copy [] ; The port wake list - ] - ] - - make-scheme [ - title: "Console Access" - name: 'console - ] - - make-scheme [ - title: "Callback Event Functions" - name: 'callback - awake: func [event] [ - do-callback event - true - ] - ] - - make-scheme [ - title: "File Access" - name: 'file - info: system/standard/file-info ; for C enums - init: func [port /local path] [ - if url? port/spec/ref [ - parse port/spec/ref [thru #":" 0 2 slash path:] - append port/spec compose [path: (to file! path)] - ] - ] - ] - - make-scheme/with [ - title: "File Directory Access" - name: 'dir - ] 'file - - make-scheme [ - title: "GUI Events" - name: 'event - awake: func [event] [ - print ["Default GUI event/awake:" event/type] - true - ] - ] - - make-scheme [ - title: "DNS Lookup" - name: 'dns - spec: system/standard/port-spec-net - awake: func [event] [print event/type true] - ] - - make-scheme [ - title: "TCP Networking" - name: 'tcp - spec: system/standard/port-spec-net - info: system/standard/net-info ; for C enums - awake: func [event] [print ['TCP-event event/type] true] - ] - - make-scheme [ - title: "Clipboard" - name: 'clipboard - ] - - system/ports/system: open [scheme: 'system] - system/ports/input: open [scheme: 'console] - system/ports/callback: open [scheme: 'callback] - - init-schemes: 'done ; only once + sys/decode-url: lib/decode-url: :sys/*parse-url/decode-url + + system/schemes: make object! 10 + + make-scheme [ + title: "System Port" + name: 'system + actor: get-event-actor-handle + awake: func [ + sport "System port (State block holds events)" + ports "Port list (Copy of block passed to WAIT)" + /only + /local event event-list n-event port waked + ][ + waked: sport/data ; The wake list (pending awakes) + + if only [ + unless block? ports [return blank] ;short cut for a pause + ] + + ; Process all events (even if no awake ports). + n-event: 0 + event-list: sport/state + until [empty? event-list][ + if n-event > 8 [break] ; Do only 8 events at a time (to prevent polling lockout). + event: first event-list + port: event/port + either any [ + not only + find ports port + ][ + remove event-list ;avoid event overflow caused by wake-up recursively calling into wait + if wake-up port event [ + ; Add port to wake list: + ;print ["==System-waked:" port/spec/ref] + unless find waked port [append waked port] + ] + n-event: n-event + 1 + ][ + event-list: next event-list + ] + ] + + ; No wake ports (just a timer), return now. + unless block? ports [return blank] + + ; Are any of the requested ports awake? + for-next ports [ + if port: find waked first ports [return true] + ] + + false ; keep waiting + ] + init: proc [port] [ + ;;print ["Init" title] + port/data: copy [] ; The port wake list + ] + ] + + make-scheme [ + title: "Console Access" + name: 'console + actor: get-console-actor-handle + ] + + make-scheme [ + title: "File Access" + name: 'file + actor: get-file-actor-handle + info: system/standard/file-info ; for C enums + init: proc [port /local path] [ + if url? port/spec/ref [ + parse port/spec/ref [thru #":" 0 2 slash path:] + append port/spec compose [path: (to file! path)] + ] + ] + ] + + make-scheme/with [ + title: "File Directory Access" + name: 'dir + actor: get-dir-actor-handle + ] 'file + + make-scheme [ + title: "GUI Events" + name: 'event + actor: get-event-actor-handle + awake: func [event] [ + print ["Default GUI event/awake:" event/type] + true + ] + ] + + make-scheme [ + title: "DNS Lookup" + name: 'dns + actor: get-dns-actor-handle + spec: system/standard/port-spec-net + awake: func [event] [print event/type true] + ] + + make-scheme [ + title: "TCP Networking" + name: 'tcp + actor: get-tcp-actor-handle + spec: system/standard/port-spec-net + info: system/standard/net-info ; for C enums + awake: func [event] [print ['TCP-event event/type] true] + ] + + make-scheme [ + title: "UDP Networking" + name: 'udp + actor: get-udp-actor-handle + spec: system/standard/port-spec-net + info: system/standard/net-info ; for C enums + awake: func [event] [print ['UDP-event event/type] true] + ] + + make-scheme [ + title: "Clipboard" + name: 'clipboard + actor: get-clipboard-actor-handle + ] + + if 4 == fourth system/version [ + make-scheme [ + title: "Signal" + name: 'signal + actor: get-signal-actor-handle + spec: system/standard/port-spec-signal + ] + ] + + make-scheme [ + title: "Serial Port" + name: 'serial + actor: get-serial-actor-handle + spec: system/standard/port-spec-serial + init: proc [port /local path speed] [ + if url? port/spec/ref [ + parse port/spec/ref + [thru #":" 0 2 slash copy path [to slash | end] skip copy speed to end] + if speed: trap [to-integer/unsigned speed] [ + port/spec/speed: speed + ] + port/spec/path: to file! path + ] + ] + ] + + system/ports/system: open [scheme: 'system] + system/ports/input: open [scheme: 'console] + + init-schemes: 'done ; only once ] diff --git a/src/mezz/sys-start.r b/src/mezz/sys-start.r index dd39f599ab..268fc6a2cc 100644 --- a/src/mezz/sys-start.r +++ b/src/mezz/sys-start.r @@ -1,180 +1,89 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "REBOL 3 Boot Sys: Startup" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Context: sys - Note: { - The boot binding of this module is SYS then LIB deep. - Any non-local words not found in those contexts WILL BE - UNBOUND and will error out at runtime! - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "REBOL 3 Boot Sys: Startup" + Rights: { + Copyright 2012 REBOL Technologies + Copyright 2012-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Context: sys + Note: { + The Startup_Core() function in %b-init.c is supposed to be a fairly + minimal startup, to get the system running. For instance, it does + not do any command-line processing...as the host program might not + even *have* a command line. It just gets basic things set up like + the garbage collector and other interpreter services. + + Not much of that work can be delegated to Rebol routines, because + the evaluator can't run for a lot of that setup time. But at the + end of Startup_Core() when the evaluator is ready, it runs this + routine for any core initialization code which can reasonably be + delegated to Rebol. + + After this point, it is expected that further initialization be done + by the host. That includes the mentioned command-line processing, + which due to this layering can be done with PARSE. + } ] -start: func [ - "INIT: Completes the boot sequence. Loads extras, handles args, security, scripts." - /local file tmp script-path script-args code -] bind [ ; context is: system/options - - ;** Note ** We need to make this work for lower boot levels too! - - ;-- DEBUG: enable these lines for debug or related testing - loud-print ["Starting... boot level:" boot-level] - ;trace 1 - ;crash-here ; test error handling (undefined word) - - boot-level: any [boot-level 'full] - start: 'done ; only once - init-schemes ; only once - - ;-- Print minimal identification banner if needed: - if all [ - not quiet - any [flags/verbose flags/usage flags/help] - ][ - boot-print boot-banner ; basic boot banner only - ] - if any [do-arg script] [quiet: true] - - ;-- Set up option/paths for /path, /boot, /home, and script path (for SECURE): - path: dirize any [path home] - home: dirize home - ;if slash <> first boot [boot: clean-path boot] ;;;;; HAVE C CODE DO IT PROPERLY !!!! - home: file: first split-path boot - if file? script [ ; Get the path (needed for SECURE setup) - script-path: split-path script - case [ - slash = first first script-path [] ; absolute - %./ = first script-path [script-path/1: path] ; curr dir - 'else [insert first script-path path] ; relative - ] - ] - - ;-- Convert command line arg strings as needed: - script-args: args ; save for below - foreach [opt act] [ - args [parse args ""] - do-arg block! - debug block! - secure word! - import [to-rebol-file import] - version tuple! - ][ - set opt attempt either block? act [act][ - [all [get opt to get act get opt]] - ] - ] - ; version, import, secure are all of valid type or none - - if flags/verbose [print self] - - ;-- Boot up the rest of the run-time environment: - ; NOTE: this can still be split up into more boot-levels !!! - ; For example: mods, plus, host, and full - if boot-level [ - load-boot-exts - loud-print "Init mezz plus..." - - do bind-lib boot-mezz - boot-mezz: 'done - - foreach [spec body] boot-prot [module spec body] - ;do bind-lib boot-prot - ;boot-prot: 'done - - ;-- User is requesting usage info: - if flags/help [lib/usage quiet: true] - - ;-- Print fancy banner (created by mezz plus): - if any [ - flags/verbose - not any [quiet script do-arg] - ][ - boot-print boot-banner - ] - if boot-host [ - loud-print "Init host code..." - do load boot-host - boot-host: none - ] - ] - - ;-- Setup SECURE configuration (a NO-OP for min boot) - lib/secure (case [ - flags/secure [secure] - flags/secure-min ['allow] - flags/secure-max ['quit] - file? script [compose [file throw (file) [allow read] (first script-path) allow]] - 'else [compose [file throw (file) [allow read] %. allow]] ; default - ]) - - ;-- Evaluate rebol.r script: - loud-print ["Checking for rebol.r file in" file] - if exists? file/rebol.r [do file/rebol.r] ; bug#706 - - ;-- Make the user's global context: - tmp: make object! 320 - append tmp reduce ['system :system] - system/contexts/user: tmp - - ;boot-print ["Checking for user.r file in" file] - ;if exists? file/user.r [do file/user.r] - - boot-print "" - - ;if :lib/secure [protect-system-object] - - ; Import module? - if import [lib/import import] - - ;-- Evaluate: --do "some code" if found - if do-arg [ - do intern do-arg - unless script [quit/now] - ] - - ;-- Evaluate script argument? - either file? script [ - ; !!! Would be nice to use DO for this section. !!! - ; NOTE: We can't use DO here because it calls the code it does with CATCH/quit - ; and we shouldn't catch QUIT in the top-level script, we should just quit. - ; script-path holds: [dir file] for script - assert/type [script-path [block!] script-path/1 [file!] script-path/2 [file!]] - ; /path dir is where our script gets started. - change-dir first script-path - either exists? second script-path [ - boot-print ["Evaluating:" script] - code: load/header/type second script-path 'unbound - ; update system/script (Make into a function?) - system/script: make system/standard/script [ - title: select first code 'title - header: first code - parent: none - path: what-dir - args: script-args - ] - either 'module = select first code 'type [ - code: reduce [first+ code code] - if object? tmp: do-needs/no-user first code [append code tmp] - import make module! code - ][ - do-needs first+ code - do intern code - ] - if flags/halt [lib/halt] - ] [ - cause-error 'access 'no-script script - ] - ][ - boot-print boot-help - ] - - exit - -] system/options +finish-init-core: procedure [ + "Completes the boot sequence for Ren-C core." + boot-mezz [block!] + {Mezzanine code loaded as part of the boot block in Startup_Core()} +][ + ; Remove the reference through which this function we are running is + ; found, so it's invisible to the user and can't run again (but leave + ; a hint that it's in the process of running vs. just unsetting it) + ; + finish-init-core: 'running + + ; Make the user's global context. Remove functions whose names are being + ; retaken for new functionality--to be kept this way during a deprecation + ; period. Ther lib definitions are left as-is, however, since the new + ; definitions are required by SYS and LIB code itself. + ; + tmp: make object! 320 + append tmp reduce [ + 'system :system + + 'adjoin (get 'join) + 'join (func [dummy1 dummy2] [ + fail/where [ + {JOIN is reserved in Ren-C for future use} + {(It will act like R3's REPEND, which has a slight difference} + {from APPEND of a REDUCE'd value: it only reduces blocks).} + {Use ADJOIN for the future JOIN, JOIN-OF for non-mutating.} + {If in mode, old JOIN meaning is available.} + ] 'dummy1 + ]) + + 'while-not (get 'until) + 'until (func [dummy] [ + fail/where [ + {UNTIL is reserved in Ren-C for future use} + {(It will be arity-2 and act like WHILE [NOT ...] [...])} + {Use LOOP-UNTIL for the single arity form, and see also} + {LOOP-WHILE for the arity-1 form of WHILE.} + {If in mode, old UNTIL meaning is available.} + ] 'dummy + ]) + ] + system/contexts/user: tmp + + ; It was a stated goal at one point that it should be possible to protect + ; the entire system object and still run the interpreter. This was + ; commented out, so the state of that feature is unknown. + ; + comment [if :lib/secure [protect-system-object]] + + ; The mezzanine is currently considered part of what Startup_Core() will + ; initialize for all clients. + ; + do bind-lib boot-mezz + + finish-init-core: 'done +] diff --git a/src/mezz/view-funcs.r b/src/mezz/view-funcs.r deleted file mode 100644 index 5f8d432614..0000000000 --- a/src/mezz/view-funcs.r +++ /dev/null @@ -1,248 +0,0 @@ -REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "View - windowing system basic API" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } -] - -; The View system handles windowing, events, popups, requestors, and modal -; operations. Normally VID provides the contents for these, but users are -; also allowed to build and display their own windows directly. - -system/standard/font: context [ - name: "arial" - style: none - size: 12 - color: 0.0.0 - offset: 2x2 - space: 0x0 - shadow: none -] - -system/standard/para: context [ - origin: 2x2 - margin: 2x2 - indent: 0x0 - tabs: 40 - wrap?: true - scroll: 0x0 - align: 'left - valign: 'top -] - -view: func [ - "Displays a window view." - window [gob! block! object!] "Window gob, VID face, or VID layout block" - /options opts [block!] "Window options spec block" - /no-wait "Return immediately. Do not wait and process events." - /as-is "Leave window as is. Do not add a parent gob." - /local screen tmp xy -][ - if not screen: system/view/screen-gob [return none] - - ; Convert option block to a map: - opts: make map! any [reduce/no-set opts []] - case/all [ - no-wait [opts/no-wait: true] - as-is [opts/as-is: true] -; options [append opts reduce/no-set opts] - ] - - ; GOB based view: - if gob? window [ - ; Build the window: - unless opts/as-is [ - tmp: window - tmp/offset: 0x0 - window: make gob! [size: tmp/size] - append window tmp - ] - ; Set optional background: - if any [opts/color opts/draw] [ - insert window make gob! append copy [ - size: window/size - offset: 0x0 - ] pick [ - [draw: opts/draw] - [color: opts/color] - ] block? opts/draw - ] - ; Set up default handler, if user did not provide one: - unless opts/handler [ - handle-events [ - name: 'view-default - priority: 50 - handler: func [event] [ - print ["view-event:" event/type event/offset] - if switch event/type [ - close [true] - key [event/key = escape] - ][ - unhandle-events self - unview event/window - quit - ] - show event/window - none ; we handled it - ] - ] - ] - ] - - ; VID-layout based view: - if block? window [ - window: layout/background window any [opts/draw opts/color] - ] - - ; VID-face based view: - if object? window [ - ; Build the window: - window: append make gob! [ - data: window - size: window/size - ] window/gob - window/flags: [resize] ; may be overwritten below - ] - - ; Window title: - window/text: any [opts/title window/text "REBOL: untitled"] - - ;!!! Add later: use script title - once modules provide that - - ; Other options: - if opts/offset [ - ; 'Center is allowed: - if word? opts/offset [ - opts/offset: either opts/offset = 'center [screen/size - window/size / 2][100x100] - ] - window/offset: opts/offset - ] - if opts/owner [window/owner: opts/owner] - if opts/flags [window/flags: opts/flags] - if opts/handler [handle-events opts/handler] - - ; Add the window to the screen. If it is already there, this action - ; will move it to the top: - unless window = screen [append screen window] - - ; Open or refresh the window: - show window - - ; Wait for the event port to tell us we can return: - if all [ - not opts/no-wait - 1 = length? screen - ][ - do-events - ] - - ; Return window (which may have been created here): - window -] - -unview: func [ - "Closes a window view." - window [object! gob! word! none!] "Window face or GOB. 'all for all. none for last" - /local screen -][ - screen: system/view/screen-gob - case [ - object? window [window: window/gob/parent] - window = 'all [show clear screen exit] - not window [window: last screen] ; NONE is ok - ] - remove find screen window ; none ok - show window ; closes it, none ok -] - -base-handler: context [ - name: 'no-name - priority: 0 -] - -handle-events: func [ - "Adds a handler to the view event system." - handler [block!] - /local sys-hand -][ - handler: make base-handler handler - sys-hand: system/view/event-port/locals/handlers - ; Insert by priority: - unless foreach [here: hand] sys-hand [ - if handler/priority > hand/priority [ - insert here handler - break/return true - ] - ][ - append sys-hand handler - ] - handler -] - -unhandle-events: func [ - "Removes a handler from the view event system." - handler [object!] -][ - remove find system/view/event-port/locals/handlers handler - exit -] - -handled-events?: func [ - "Returns event handler object matching a given name." - name -][ - foreach hand system/view/event-port/locals/handlers [ - if hand/name = name [return hand] - ] - none -] - -do-events: func [ - "Waits for window events. Returns when all windows are closed." -][ - wait system/view/event-port -] - -init-view-system: func [ - "Initialize the View subsystem." - /local ep -][ - ; The init function called here resides in this module - init system/view/screen-gob: make gob! [text: "Top Gob"] - - ;update the metrics object (temp - will become mezz later) - foreach w words-of system/view/metrics [ - set in system/view/metrics w gui-metric w - ] - - ; Already initialized? - if system/view/event-port [exit] - - ; Open the event port: - ep: open [scheme: 'event] - system/view/event-port: ep - - ; Create block of event handlers: - ep/locals: context [handlers: copy []] - - ; Global event handler for view system: - ep/awake: func [event /local h] [ - h: event/port/locals/handlers - while [ ; (no binding needed) - all [event not tail? h] - ][ - ; Handlers should return event in order to continue. - event: h/1/handler event - h: next h - ] - tail? system/view/screen-gob - ] -] - -init-view-system diff --git a/src/os/dev-dns.c b/src/os/dev-dns.c index 50a64d9407..09713a8ef8 100644 --- a/src/os/dev-dns.c +++ b/src/os/dev-dns.c @@ -1,214 +1,210 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Device: DNS access -** Author: Carl Sassenrath -** Purpose: Calls local DNS services for domain name lookup. -** Notes: -** See MS WSAAsyncGetHost* details regarding multiple requests. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ +// +// File: %dev-dns.c +// Summary: "Device: DNS access" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Calls local DNS services for domain name lookup. +// +// See MS WSAAsyncGetHost* details regarding multiple requests. +// #include #include #include +#include #include "reb-host.h" -#include "host-lib.h" #include "sys-net.h" +#include "reb-net.h" extern DEVICE_CMD Init_Net(REBREQ *); // Share same init extern DEVICE_CMD Quit_Net(REBREQ *); +extern i32 Request_Size_Net(REBREQ *); // Share same request struct extern void Signal_Device(REBREQ *req, REBINT type); -#ifdef HAS_ASYNC_DNS -// Async DNS requires a window handle to signal completion (WSAASync) -extern HWND Event_Handle; -#endif -/*********************************************************************** -** -*/ DEVICE_CMD Open_DNS(REBREQ *sock) -/* -***********************************************************************/ +// +// Open_DNS: C +// +DEVICE_CMD Open_DNS(REBREQ *sock) { - SET_OPEN(sock); - return DR_DONE; + SET_OPEN(sock); + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Close_DNS(REBREQ *sock) -/* -** Note: valid even if not open. -** -***********************************************************************/ +// +// Close_DNS: C +// +// Note: valid even if not open. +// +DEVICE_CMD Close_DNS(REBREQ *req) { - // Terminate a pending request: -#ifdef HAS_ASYNC_DNS - if (GET_FLAG(sock->flags, RRF_PENDING)) { - CLR_FLAG(sock->flags, RRF_PENDING); - if (sock->handle) WSACancelAsyncRequest(sock->handle); - } -#endif - if (sock->net.host_info) OS_Free(sock->net.host_info); - sock->net.host_info = 0; - sock->handle = 0; - SET_CLOSED(sock); - return DR_DONE; // Removes it from device's pending list (if needed) + // Terminate a pending request: + struct devreq_net *sock = DEVREQ_NET(req); + + if (sock->host_info) OS_FREE(sock->host_info); + sock->host_info = 0; + req->requestee.handle = 0; + SET_CLOSED(req); + return DR_DONE; // Removes it from device's pending list (if needed) } -/*********************************************************************** -** -*/ DEVICE_CMD Read_DNS(REBREQ *sock) -/* -** Initiate the GetHost request and return immediately. -** Note the temporary results buffer (must be freed later). -** -***********************************************************************/ +// +// Read_DNS: C +// +// Initiate the GetHost request and return immediately. +// Note the temporary results buffer (must be freed later by the caller). +// +// !!! R3-Alpha used WSAAsyncGetHostByName and WSAAsyncGetHostByName to do +// non-blocking DNS lookup on Windows. These functions are deprecated, since +// they do not have IPv6 equivalents...so applications that want asynchronous +// lookup are expected to use their own threads and call getnameinfo(). +// +// !!! R3-Alpha was written to use the old non-reentrant form in POSIX, but +// glibc2 implements _r versions. +// +DEVICE_CMD Read_DNS(REBREQ *req) { - void *host; -#ifdef HAS_ASYNC_DNS - HANDLE handle; -#else - HOSTENT *he; -#endif - - host = OS_Make(MAXGETHOSTSTRUCT); // be sure to free it - -#ifdef HAS_ASYNC_DNS - if (!GET_FLAG(sock->modes, RST_REVERSE)) // hostname lookup - handle = WSAAsyncGetHostByName(Event_Handle, WM_DNS, sock->data, host, MAXGETHOSTSTRUCT); - else - handle = WSAAsyncGetHostByAddr(Event_Handle, WM_DNS, (char*)&(sock->net.remote_ip), 4, AF_INET, host, MAXGETHOSTSTRUCT); - - if (handle != 0) { - sock->net.host_info = host; - sock->handle = handle; - return DR_PEND; // keep it on pending list - } -#else - // Use old-style blocking DNS (mainly for testing purposes): - if (GET_FLAG(sock->modes, RST_REVERSE)) { - he = gethostbyaddr((char*)&sock->net.remote_ip, 4, AF_INET); - if (he) { - sock->net.host_info = host; //??? - sock->data = he->h_name; - SET_FLAG(sock->flags, RRF_DONE); - return DR_DONE; - } - } - else { - he = gethostbyname(sock->data); - if (he) { - sock->net.host_info = host; // ?? who deallocs? - COPY_MEM((char*)&(sock->net.remote_ip), (char *)(*he->h_addr_list), 4); //he->h_length); - SET_FLAG(sock->flags, RRF_DONE); - return DR_DONE; - } - } -#endif - - OS_Free(host); - sock->net.host_info = 0; - - sock->error = GET_ERROR; - //Signal_Device(sock, EVT_ERROR); - return DR_ERROR; // Remove it from pending list + struct devreq_net *sock = DEVREQ_NET(req); + char *host = OS_ALLOC_N(char, MAXGETHOSTSTRUCT); + + HOSTENT *he; + if (GET_FLAG(req->modes, RST_REVERSE)) { + // 93.184.216.34 => example.com + he = gethostbyaddr( + cast(char*, &sock->remote_ip), 4, AF_INET + ); + if (he != NULL) { + sock->host_info = host; //??? + req->common.data = b_cast(he->h_name); + SET_FLAG(req->flags, RRF_DONE); + return DR_DONE; + } + } + else { + // example.com => 93.184.216.34 + he = gethostbyname(s_cast(req->common.data)); + if (he != NULL) { + sock->host_info = host; // ?? who deallocs? + memcpy(&sock->remote_ip, *he->h_addr_list, 4); //he->h_length); + SET_FLAG(req->flags, RRF_DONE); + return DR_DONE; + } + } + + OS_FREE(host); + sock->host_info = NULL; + + switch (h_errno) { + case HOST_NOT_FOUND: // The specified host is unknown + case NO_ADDRESS: // (or NO_DATA) name is valid but has no IP + // + // The READ should return a blank in these cases, vs. raise an + // error, for convenience in handling. + // + SET_FLAG(req->flags, RRF_DONE); + return DR_DONE; + + case NO_RECOVERY: // A nonrecoverable name server error occurred + case TRY_AGAIN: // Temporary error on authoritative name server + break; + + default: + assert(FALSE); + } + + req->error = GET_ERROR; + //Signal_Device(req, EVT_ERROR); + return DR_ERROR; // Remove it from pending list } -/*********************************************************************** -** -*/ DEVICE_CMD Poll_DNS(REBREQ *dr) -/* -** Check for completed DNS requests. These are marked with -** RRF_DONE by the windows message event handler (dev-event.c). -** Completed requests are removed from the pending queue and -** event is signalled (for awake dispatch). -** -***********************************************************************/ +// +// Poll_DNS: C +// +// Check for completed DNS requests. These are marked with +// RRF_DONE by the windows message event handler (dev-event.c). +// Completed requests are removed from the pending queue and +// event is signalled (for awake dispatch). +// +DEVICE_CMD Poll_DNS(REBREQ *dr) { - REBDEV *dev = (REBDEV*)dr; // to keep compiler happy - REBREQ **prior = &dev->pending; - REBREQ *req; - REBOOL change = FALSE; - HOSTENT *host; - - // Scan the pending request list: - for (req = *prior; req; req = *prior) { - - // If done or error, remove command from list: - if (GET_FLAG(req->flags, RRF_DONE)) { // req->error may be set - *prior = req->next; - req->next = 0; - CLR_FLAG(req->flags, RRF_PENDING); - - if (!req->error) { // success! - host = (HOSTENT*)req->net.host_info; - if (GET_FLAG(req->modes, RST_REVERSE)) - req->data = host->h_name; - else - COPY_MEM((char*)&(req->net.remote_ip), (char *)(*host->h_addr_list), 4); //he->h_length); - Signal_Device(req, EVT_READ); - } - else - Signal_Device(req, EVT_ERROR); - change = TRUE; - } - else prior = &req->next; - } - - return change; + REBDEV *dev = (REBDEV*)dr; // to keep compiler happy + REBREQ **prior = &dev->pending; + REBREQ *req; + REBOOL change = FALSE; + HOSTENT *host; + + // Scan the pending request list: + for (req = *prior; req; req = *prior) { + + // If done or error, remove command from list: + if (GET_FLAG(req->flags, RRF_DONE)) { // req->error may be set + *prior = req->next; + req->next = 0; + CLR_FLAG(req->flags, RRF_PENDING); + + if (!req->error) { // success! + host = cast(HOSTENT*, DEVREQ_NET(req)->host_info); + if (GET_FLAG(req->modes, RST_REVERSE)) + req->common.data = b_cast(host->h_name); + else + memcpy(&(DEVREQ_NET(req)->remote_ip), *host->h_addr_list, 4); //he->h_length); + Signal_Device(req, EVT_READ); + } + else + Signal_Device(req, EVT_ERROR); + change = TRUE; + } + else prior = &req->next; + } + + return change ? 1 : 0; // DEVICE_CMD implicitly returns i32 } /*********************************************************************** ** -** Command Dispatch Table (RDC_ enum order) +** Command Dispatch Table (RDC_ enum order) ** ***********************************************************************/ static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = { - Init_Net, // Shared init - called only once - Quit_Net, // Shared - Open_DNS, - Close_DNS, - Read_DNS, - 0, // write - Poll_DNS, + Request_Size_Net, + Init_Net, // Shared init - called only once + Quit_Net, // Shared + Open_DNS, + Close_DNS, + Read_DNS, + 0, // write + Poll_DNS }; -DEFINE_DEV(Dev_DNS, "DNS", 1, Dev_Cmds, RDC_MAX, 0); +DEFINE_DEV(Dev_DNS, "DNS", 1, Dev_Cmds, RDC_MAX); diff --git a/src/os/dev-net.c b/src/os/dev-net.c index 6eab425888..5906fb4b76 100644 --- a/src/os/dev-net.c +++ b/src/os/dev-net.c @@ -1,620 +1,703 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Device: TCP/IP network access -** Author: Carl Sassenrath -** Purpose: Supports TCP and UDP (but not raw socket modes.) -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ +// +// File: %dev-net.c +// Summary: "Device: TCP/IP network access" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Supports TCP and UDP (but not raw socket modes.) +// +//=////////////////////////////////////////////////////////////////////////=// +// -#include #include #include -#include "reb-host.h" -#include "host-lib.h" #include "sys-net.h" +#ifdef IS_ERROR +#undef IS_ERROR //winerror.h defines this, so undef it to avoid the warning +#endif +#include "sys-core.h" + +#include "reb-net.h" +#include "reb-evtypes.h" + #if (0) -#define WATCH1(s,a) printf(s, a) -#define WATCH2(s,a,b) printf(s, a, b) -#define WATCH4(s,a,b,c,d) printf(s, a, b, c, d) + #define WATCH1(s,a) printf(s, a) + #define WATCH2(s,a,b) printf(s, a, b) + #define WATCH4(s,a,b,c,d) printf(s, a, b, c, d) #else -#define WATCH1(s,a) -#define WATCH2(s,a,b) -#define WATCH4(s,a,b,c,d) + #define WATCH1(s,a) + #define WATCH2(s,a,b) + #define WATCH4(s,a,b,c,d) #endif void Signal_Device(REBREQ *req, REBINT type); DEVICE_CMD Listen_Socket(REBREQ *sock); -#ifdef TO_WIN32 -extern HWND Event_Handle; // For WSAAsync API +#ifdef TO_WINDOWS + extern HWND Event_Handle; // For WSAAsync API #endif +// Prevent sendmsg/write raising SIGPIPE the TCP socket is closed: +// https://stackoverflow.com/questions/108183/how-to-prevent-sigpipes-or-handle-them-properly +// Linux does not support SO_NOSIGPIPE +// +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif /*********************************************************************** ** -** Local Functions +** Local Functions ** ***********************************************************************/ -static void Set_Addr(SOCKAI *sa, long ip, int port) +static void Set_Addr(struct sockaddr_in *sa, long ip, int port) { - // Set the IP address and port number in a socket_addr struct. - sa->sin_family = AF_INET; - sa->sin_addr.s_addr = ip; //htonl(ip); NOTE: REBOL stays in network byte order - sa->sin_port = htons((unsigned short)port); + // Set the IP address and port number in a socket_addr struct. + memset(sa, 0, sizeof(*sa)); + sa->sin_family = AF_INET; + sa->sin_addr.s_addr = ip; //htonl(ip); NOTE: REBOL stays in network byte order + sa->sin_port = htons((unsigned short)port); } -static void Get_Local_IP(REBREQ *sock) +static void Get_Local_IP(struct devreq_net *sock) { - // Get the local IP address and port number. - // This code should be fast and never fail. - SOCKAI sa; - int len = sizeof(sa); - - getsockname(sock->socket, (struct sockaddr *)&sa, &len); - sock->net.local_ip = sa.sin_addr.s_addr; //htonl(ip); NOTE: REBOL stays in network byte order - sock->net.local_port = ntohs(sa.sin_port); + // Get the local IP address and port number. + // This code should be fast and never fail. + struct sockaddr_in sa; + socklen_t len = sizeof(sa); + + getsockname(AS_REBREQ(sock)->requestee.socket, cast(struct sockaddr *, &sa), &len); + sock->local_ip = sa.sin_addr.s_addr; //htonl(ip); NOTE: REBOL stays in network byte order + sock->local_port = ntohs(sa.sin_port); } -static REBOOL Nonblocking_Mode(SOCKET sock) +static REBOOL Set_Sock_Options(SOCKET sock) { - // Set non-blocking mode. Return TRUE if no error. + // Prevent sendmsg/write raising SIGPIPE the TCP socket is closed: + // https://stackoverflow.com/questions/108183/how-to-prevent-sigpipes-or-handle-them-properly +#if defined(SO_NOSIGPIPE) + int on = 1; + if (setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)) < 0) { + return FALSE; + } +#endif + + // Set non-blocking mode. Return TRUE if no error. #ifdef FIONBIO - long mode = 1; - return !IOCTL(sock, FIONBIO, &mode); + unsigned long mode = 1; + return NOT(IOCTL(sock, FIONBIO, &mode)); #else - int flags; - flags = fcntl(sock, F_GETFL, 0); - flags |= O_NONBLOCK; - //else flags &= ~O_NONBLOCK; - fcntl(sock, F_SETFL, flags); - return TRUE; + int flags; + flags = fcntl(sock, F_GETFL, 0); + flags |= O_NONBLOCK; + //else flags &= ~O_NONBLOCK; + return LOGICAL(fcntl(sock, F_SETFL, flags) >= 0); #endif } -/*********************************************************************** -** -*/ DEVICE_CMD Init_Net(REBREQ *dr) -/* -** Intialize networking libraries and related interfaces. -** This function will be called prior to any socket functions. -** -***********************************************************************/ +// +// Init_Net: C +// +// Intialize networking libraries and related interfaces. +// This function will be called prior to any socket functions. +// +DEVICE_CMD Init_Net(REBREQ *dr) { - REBDEV *dev = (REBDEV*)dr; // just to keep compiler happy -#ifdef TO_WIN32 - WSADATA wsaData; - // Initialize Windows Socket API with given VERSION. - // It is ok to call twice, as long as WSACleanup twice. - if (WSAStartup(0x0101, &wsaData)) return DR_ERROR; + REBDEV *dev = (REBDEV*)dr; // just to keep compiler happy +#ifdef TO_WINDOWS + WSADATA wsaData; + // Initialize Windows Socket API with given VERSION. + // It is ok to call twice, as long as WSACleanup twice. + if (WSAStartup(0x0101, &wsaData)) return DR_ERROR; #endif - SET_FLAG(dev->flags, RDF_INIT); - return DR_DONE; + SET_FLAG(dev->flags, RDF_INIT); + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Quit_Net(REBREQ *dr) -/* -** Close and cleanup networking libraries and related interfaces. -** -***********************************************************************/ +// +// Quit_Net: C +// +// Close and cleanup networking libraries and related interfaces. +// +DEVICE_CMD Quit_Net(REBREQ *dr) { - REBDEV *dev = (REBDEV*)dr; // just to keep compiler happy -#ifdef TO_WIN32 - if (GET_FLAG(dev->flags, RDF_INIT)) WSACleanup(); + REBDEV *dev = (REBDEV*)dr; // just to keep compiler happy +#ifdef TO_WINDOWS + if (GET_FLAG(dev->flags, RDF_INIT)) WSACleanup(); #endif - CLR_FLAG(dev->flags, RDF_INIT); - return DR_DONE; + CLR_FLAG(dev->flags, RDF_INIT); + return DR_DONE; } -/*********************************************************************** -** -*/ int Host_Address(char *hostname, char *hostaddr) -/* -** Simple lookup of a host address. -** The hostaddr must be at least 16 bytes in size (IPv6). -** This is a synchronous function and blocks during access. -** -** On success, returns length of address. -** On failure, returns 0. -** -** Current version is IPv4 only. -** -***********************************************************************/ + +// +// Open_Socket: C +// +// Setup a socket with the specified protocol and bind it to +// the related transport service. +// +// Returns 0 on success. +// On failure, error code is OS local. +// +// Note: This is an intialization procedure and no actual +// connection is made at this time. The IP address and port +// number are not needed, only the type of service required. +// +// After usage: +// Close_Socket() - to free OS allocations +// +DEVICE_CMD Open_Socket(REBREQ *sock) +{ + int type; + int protocol; + long result; + + sock->error = 0; + sock->state = 0; // clear all flags + + // Setup for correct type and protocol: + if (GET_FLAG(sock->modes, RST_UDP)) { + type = SOCK_DGRAM; + protocol = IPPROTO_UDP; + } + else { // TCP is default + type = SOCK_STREAM; + protocol = IPPROTO_TCP; + } + + // Bind to the transport service, return socket handle or error: + result = (int)socket(AF_INET, type, protocol); + + // Failed, get error code (os local): + if (result == BAD_SOCKET) { + sock->error = GET_ERROR; + return DR_ERROR; + } + + sock->requestee.socket = result; + SET_FLAG(sock->state, RSM_OPEN); + + // Set socket to non-blocking async mode: + if (!Set_Sock_Options(sock->requestee.socket)) { + sock->error = GET_ERROR; + return DR_ERROR; + } + + return DR_DONE; +} + + +// +// Close_Socket: C +// +// Close a socket. +// +// Returns 0 on success. +// On failure, error code is OS local. +// +DEVICE_CMD Close_Socket(REBREQ *req) { - struct hostent *he; + struct devreq_net *sock = DEVREQ_NET(req); + req->error = 0; + + if (GET_FLAG(req->state, RSM_OPEN)) { - if (!(he = gethostbyname(hostname))) return DR_DONE; + req->state = 0; // clear: RSM_OPEN, RSM_CONNECT - COPY_MEM(hostaddr, (char *)(*he->h_addr_list), he->h_length); + // If DNS pending, abort it: + if (sock->host_info) { // indicates DNS phase active + OS_FREE(sock->host_info); + req->requestee.socket = req->length; // Restore TCP socket (see Lookup) + } - return he->h_length; + if (CLOSE_SOCKET(req->requestee.socket)) { + req->error = GET_ERROR; + return DR_ERROR; + } + } + + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Open_Socket(REBREQ *sock) -/* -** Setup a socket with the specified protocol and bind it to -** the related transport service. -** -** Returns 0 on success. -** On failure, error code is OS local. -** -** Note: This is an intialization procedure and no actual -** connection is made at this time. The IP address and port -** number are not needed, only the type of service required. -** -** After usage: -** Close_Socket() - to free OS allocations -** -***********************************************************************/ +// +// Lookup_Socket: C +// +// Initiate the GetHost request and return immediately. +// This is very similar to the DNS device. +// The request will pend until the main event handler gets WM_DNS. +// Note the temporary results buffer (must be freed later). +// Note we use the sock->requestee.handle for the DNS handle. During use, +// we store the TCP socket in the length field. +// +DEVICE_CMD Lookup_Socket(REBREQ *req) { - int type; - int protocol; - long result; - - sock->error = 0; - sock->state = 0; // clear all flags - - // Setup for correct type and protocol: - if (GET_FLAG(sock->modes, RST_UDP)) { - type = SOCK_DGRAM; - protocol = IPPROTO_UDP; - } - else { // TCP is default - type = SOCK_STREAM; - protocol = IPPROTO_TCP; - } - - // Bind to the transport service, return socket handle or error: - result = (int)socket(AF_INET, type, protocol); - - // Failed, get error code (os local): - if (result == BAD_SOCKET) { - sock->error = GET_ERROR; - return DR_ERROR; - } - - sock->socket = result; - SET_FLAG(sock->state, RSM_OPEN); - - // Set socket to non-blocking async mode: - if (!Nonblocking_Mode(sock->socket)) { - sock->error = GET_ERROR; - return DR_ERROR; - } - - return DR_DONE; + struct devreq_net *sock = DEVREQ_NET(req); + sock->host_info = NULL; // no allocated data + + // !!! R3-Alpha would use asynchronous DNS API on Windows, but that API + // was not supported by IPv6, and developers are encouraged to use normal + // socket APIs with their own threads. + + HOSTENT *host = gethostbyname(s_cast(req->common.data)); + if (host != NULL) { + memcpy(&sock->remote_ip, *host->h_addr_list, 4); //he->h_length); + CLR_FLAG(req->flags, RRF_DONE); + Signal_Device(req, EVT_LOOKUP); + return DR_DONE; + } + + req->error = GET_ERROR; + //Signal_Device(req, EVT_ERROR); + return DR_ERROR; // Remove it from pending list } -/*********************************************************************** -** -*/ DEVICE_CMD Close_Socket(REBREQ *sock) -/* -** Close a socket. -** -** Returns 0 on success. -** On failure, error code is OS local. -** -***********************************************************************/ +// +// Connect_Socket: C +// +// Connect a socket to a service. +// Only required for connection-based protocols (e.g. not UDP). +// The IP address must already be resolved before calling. +// +// This function is asynchronous. It will return immediately. +// You can call this function again to check the pending connection. +// +// The function will return: +// =0: connection succeeded (or already is connected) +// >0: in-progress, still trying +// <0: error occurred, no longer trying +// +// Before usage: +// Open_Socket() -- to allocate the socket +// +DEVICE_CMD Connect_Socket(REBREQ *req) { - sock->error = 0; + int result; + struct sockaddr_in sa; + struct devreq_net *sock = DEVREQ_NET(req); - if (GET_FLAG(sock->state, RSM_OPEN)) { + if (GET_FLAG(req->modes, RST_LISTEN)) + return Listen_Socket(req); - sock->state = 0; // clear: RSM_OPEN, RSM_CONNECT + if (GET_FLAG(req->state, RSM_CONNECT)) return DR_DONE; // already connected - // If DNS pending, abort it: - if (sock->net.host_info) { // indicates DNS phase active -#ifdef HAS_ASYNC_DNS - if (sock->handle) WSACancelAsyncRequest(sock->handle); -#endif - OS_Free(sock->net.host_info); - sock->socket = sock->length; // Restore TCP socket (see Lookup) - } + if (GET_FLAG(req->modes, RST_UDP)) { + CLR_FLAG(req->state, RSM_ATTEMPT); + SET_FLAG(req->state, RSM_CONNECT); + Get_Local_IP(sock); + Signal_Device(req, EVT_CONNECT); + return DR_DONE; // done + } - if (CLOSE_SOCKET(sock->socket)) { - sock->error = GET_ERROR; - return DR_ERROR; - } - } + Set_Addr(&sa, sock->remote_ip, sock->remote_port); + result = connect( + req->requestee.socket, cast(struct sockaddr *, &sa), sizeof(sa) + ); - return DR_DONE; -} + if (result != 0) result = GET_ERROR; + WATCH2("connect() error: %d - %s\n", result, strerror(result)); -/*********************************************************************** -** -*/ DEVICE_CMD Lookup_Socket(REBREQ *sock) -/* -** Initiate the GetHost request and return immediately. -** This is very similar to the DNS device. -** The request will pend until the main event handler gets WM_DNS. -** Note the temporary results buffer (must be freed later). -** Note we use the sock->handle for the DNS handle. During use, -** we store the TCP socket in the length field. -** -***********************************************************************/ -{ -#ifdef TO_WIN32 - HANDLE handle; -#endif - HOSTENT *host; - -#ifdef HAS_ASYNC_DNS - // Check if we are polling for completion: - if (host = (HOSTENT*)(sock->net.host_info)) { - // The windows main event handler will change this when it gets WM_DNS event: - if (!GET_FLAG(sock->flags, RRF_DONE)) return DR_PEND; // still waiting - CLR_FLAG(sock->flags, RRF_DONE); - if (!sock->error) { // Success! - host = (HOSTENT*)sock->net.host_info; - COPY_MEM((char*)&(sock->net.remote_ip), (char *)(*host->h_addr_list), 4); //he->h_length); - Signal_Device(sock, EVT_LOOKUP); - } - else - Signal_Device(sock, EVT_ERROR); - OS_Free(host); // free what we allocated earlier - sock->socket = sock->length; // Restore TCP socket saved below - sock->net.host_info = 0; - return DR_DONE; - } - - // Else, make the lookup request: - host = OS_Make(MAXGETHOSTSTRUCT); // be sure to free it - handle = WSAAsyncGetHostByName(Event_Handle, WM_DNS, sock->data, (char*)host, MAXGETHOSTSTRUCT); - if (handle != 0) { - sock->net.host_info = host; - sock->length = sock->socket; // save TCP socket temporarily - sock->handle = handle; - return DR_PEND; // keep it on pending list - } - OS_Free(host); -#else - // Use old-style blocking DNS (mainly for testing purposes): - host = gethostbyname(sock->data); - sock->net.host_info = 0; // no allocated data - - if (host) { - COPY_MEM((char*)&(sock->net.remote_ip), (char *)(*host->h_addr_list), 4); //he->h_length); - CLR_FLAG(sock->flags, RRF_DONE); - Signal_Device(sock, EVT_LOOKUP); - return DR_DONE; - } + switch (result) { + + case 0: // no error + case NE_ISCONN: + // Connected, set state: + CLR_FLAG(req->state, RSM_ATTEMPT); + SET_FLAG(req->state, RSM_CONNECT); + Get_Local_IP(sock); + Signal_Device(req, EVT_CONNECT); + return DR_DONE; // done + +#ifdef TO_WINDOWS + case NE_INVALID: // Corrects for Microsoft bug #endif + case NE_WOULDBLOCK: + case NE_INPROGRESS: + case NE_ALREADY: + // Still trying: + SET_FLAG(req->state, RSM_ATTEMPT); + return DR_PEND; + + default: + // An error happened: + CLR_FLAG(req->state, RSM_ATTEMPT); + req->error = result; + //Signal_Device(req, EVT_ERROR); + return DR_ERROR; + } +} - sock->error = GET_ERROR; - //Signal_Device(sock, EVT_ERROR); - return DR_ERROR; // Remove it from pending list + +// +// Transfer_Socket: C +// +// Write or read a socket (for connection-based protocols). +// +// This function is asynchronous. It will return immediately. +// You can call this function again to check the pending connection. +// +// The mode is RSM_RECEIVE or RSM_SEND. +// +// The function will return: +// =0: succeeded +// >0: in-progress, still trying +// <0: error occurred, no longer trying +// +// Before usage: +// Open_Socket() +// Connect_Socket() +// Verify that RSM_CONNECT is true +// Setup the sock->common.data and sock->length +// +// Note that the mode flag is cleared by the caller, not here. +// +DEVICE_CMD Transfer_Socket(REBREQ *req) +{ + int result; + long len; + struct sockaddr_in remote_addr; + socklen_t addr_len = sizeof(remote_addr); + struct devreq_net *sock = DEVREQ_NET(req); + int mode = (req->command == RDC_READ ? RSM_RECEIVE : RSM_SEND); + + if (!GET_FLAG(req->state, RSM_CONNECT) + &&!GET_FLAG(req->modes, RST_UDP)) { + req->error = -18; + return DR_ERROR; + } + + SET_FLAG(req->state, mode); + + // Limit size of transfer: + len = MIN(req->length - req->actual, MAX_TRANSFER); + + if (mode == RSM_SEND) { + // If host is no longer connected: + Set_Addr(&remote_addr, sock->remote_ip, sock->remote_port); + result = sendto( + req->requestee.socket, + s_cast(req->common.data), len, + MSG_NOSIGNAL, // Flags + cast(struct sockaddr*, &remote_addr), addr_len + ); + WATCH2("send() len: %d actual: %d\n", len, result); + + if (result >= 0) { + req->common.data += result; + req->actual += result; + if (req->actual >= req->length) { + Signal_Device(req, EVT_WROTE); + return DR_DONE; + } + SET_FLAG(req->flags, RRF_ACTIVE); /* notify OS_WAIT of activity */ + return DR_PEND; + } + // if (result < 0) ... + } + else { + result = recvfrom( + req->requestee.socket, + s_cast(req->common.data), len, + 0, // Flags + cast(struct sockaddr*, &remote_addr), &addr_len + ); + WATCH2("recv() len: %d result: %d\n", len, result); + + if (result > 0) { + if (GET_FLAG(req->modes, RST_UDP)) { + sock->remote_ip = remote_addr.sin_addr.s_addr; + sock->remote_port = ntohs(remote_addr.sin_port); + } + req->actual = result; + Signal_Device(req, EVT_READ); + return DR_DONE; + } + if (result == 0) { // The socket gracefully closed. + req->actual = 0; + CLR_FLAG(req->state, RSM_CONNECT); // But, keep RRF_OPEN true + Signal_Device(req, EVT_CLOSE); + return DR_DONE; + } + // if (result < 0) ... + } + + // Check error code: + result = GET_ERROR; + WATCH2("get error: %d %s\n", result, strerror(result)); + if (result == NE_WOULDBLOCK) return DR_PEND; // still waiting + + WATCH4("ERROR: recv(%d %x) len: %d error: %d\n", req->requestee.socket, req->common.data, len, result); + // A nasty error happened: + req->error = result; + //Signal_Device(req, EVT_ERROR); + return DR_ERROR; } -/*********************************************************************** -** -*/ DEVICE_CMD Connect_Socket(REBREQ *sock) -/* -** Connect a socket to a service. -** Only required for connection-based protocols (e.g. not UDP). -** The IP address must already be resolved before calling. -** -** This function is asynchronous. It will return immediately. -** You can call this function again to check the pending connection. -** -** The function will return: -** =0: connection succeeded (or already is connected) -** >0: in-progress, still trying -** <0: error occurred, no longer trying -** -** Before usage: -** Open_Socket() -- to allocate the socket -** -***********************************************************************/ +// +// Listen_Socket: C +// +// Setup a server (listening) socket (TCP or UDP). +// +// Before usage: +// Open_Socket(); +// Set local_port to desired port number. +// +// Use this instead of Connect_Socket(). +// +DEVICE_CMD Listen_Socket(REBREQ *req) { - int result; - SOCKAI sa; + int result; + int len = 1; + struct sockaddr_in sa; + struct devreq_net *sock = DEVREQ_NET(req); - if (GET_FLAG(sock->modes, RST_LISTEN)) - return Listen_Socket(sock); + // make sure ACCEPT queue is empty + // initialized in p-net.c + assert(req->common.sock == NULL); - if (GET_FLAG(sock->state, RSM_CONNECT)) return DR_DONE; // already connected + // Setup socket address range and port: + Set_Addr(&sa, INADDR_ANY, sock->local_port); - Set_Addr(&sa, sock->net.remote_ip, sock->net.remote_port); - result = connect(sock->socket, (struct sockaddr *)&sa, sizeof(sa)); + // Allow listen socket reuse: + result = setsockopt( + req->requestee.socket, SOL_SOCKET, SO_REUSEADDR, + cast(char*, &len), sizeof(len) + ); - if (result != 0) result = GET_ERROR; + if (result) { +lserr: + req->error = GET_ERROR; + return DR_ERROR; + } - WATCH2("connect() error: %d - %s\n", result, strerror(result)); + // Bind the socket to our local address: + result = bind( + req->requestee.socket, cast(struct sockaddr *, &sa), sizeof(sa) + ); + if (result) goto lserr; - switch (result) { + SET_FLAG(req->state, RSM_BIND); - case 0: // no error - case NE_ISCONN: - // Connected, set state: - CLR_FLAG(sock->state, RSM_ATTEMPT); - SET_FLAG(sock->state, RSM_CONNECT); - Get_Local_IP(sock); - Signal_Device(sock, EVT_CONNECT); - return DR_DONE; // done + // For TCP connections, setup listen queue: + if (!GET_FLAG(req->modes, RST_UDP)) { + result = listen(req->requestee.socket, SOMAXCONN); + if (result) goto lserr; + SET_FLAG(req->state, RSM_LISTEN); + } -#ifdef TO_WIN32 - case NE_INVALID: // Corrects for Microsoft bug -#endif - case NE_WOULDBLOCK: - case NE_INPROGRESS: - case NE_ALREADY: - // Still trying: - SET_FLAG(sock->state, RSM_ATTEMPT); - return DR_PEND; - - default: - // An error happened: - CLR_FLAG(sock->state, RSM_ATTEMPT); - sock->error = result; - //Signal_Device(sock, EVT_ERROR); - return DR_ERROR; - } + Get_Local_IP(sock); + req->command = RDC_CREATE; // the command done on wakeup + + return DR_PEND; } -/*********************************************************************** -** -*/ DEVICE_CMD Transfer_Socket(REBREQ *sock) -/* -** Write or read a socket (for connection-based protocols). -** -** This function is asynchronous. It will return immediately. -** You can call this function again to check the pending connection. -** -** The mode is RSM_RECEIVE or RSM_SEND. -** -** The function will return: -** =0: succeeded -** >0: in-progress, still trying -** <0: error occurred, no longer trying -** -** Before usage: -** Open_Socket() -** Connect_Socket() -** Verify that RSM_CONNECT is true -** Setup the sock->data and sock->length -** -** Note that the mode flag is cleared by the caller, not here. -** -***********************************************************************/ + +// +// Modify_Socket: C +// +// !!! R3-Alpha had no RDC_MODIFY commands. Some way was needed to get +// multicast setting through to the platform-specific port code, and this +// method was chosen. Eventually, the ports *themselves* should be extension +// modules instead of in core, and then there won't be concern about the +// mixture of port dispatch code with platform code. +// +DEVICE_CMD Modify_Socket(REBREQ *sock) { - int result; - long len; - int mode = (sock->command == RDC_READ ? RSM_RECEIVE : RSM_SEND); - - if (!GET_FLAG(sock->state, RSM_CONNECT)) { - sock->error = -18; - return DR_ERROR; - } - - SET_FLAG(sock->state, mode); - - // Limit size of transfer: - len = MIN(sock->length, MAX_TRANSFER); - - if (mode == RSM_SEND) { - // If host is no longer connected: - result = send(sock->socket, sock->data, len, 0); - WATCH2("send() len: %d actual: %d\n", len, result); - - if (result >= 0) { - sock->data += result; - sock->actual += result; - if (sock->actual >= sock->length) { - Signal_Device(sock, EVT_WROTE); - return DR_DONE; - } - return DR_PEND; - } - // if (result < 0) ... - } - else { - result = recv(sock->socket, sock->data, len, 0); - WATCH2("recv() len: %d result: %d\n", len, result); - - if (result > 0) { - sock->actual = result; - Signal_Device(sock, EVT_READ); - return DR_DONE; - } - if (result == 0) { // The socket gracefully closed. - sock->actual = 0; - CLR_FLAG(sock->state, RSM_CONNECT); // But, keep RRF_OPEN true - Signal_Device(sock, EVT_CLOSE); - return DR_DONE; - } - // if (result < 0) ... - } - - // Check error code: - result = GET_ERROR; - WATCH2("get error: %d %s\n", result, strerror(result)); - if (result == NE_WOULDBLOCK) return DR_PEND; // still waiting - - WATCH4("ERROR: recv(%d %x) len: %d error: %d\n", sock->socket, sock->data, len, result); - // A nasty error happened: - sock->error = result; - //Signal_Device(sock, EVT_ERROR); - return DR_ERROR; -} + assert(sock->command == RDC_MODIFY); + REBFRM *frame_ = cast(REBFRM*, sock->common.data); + int result = 0; -/*********************************************************************** -** -*/ DEVICE_CMD Listen_Socket(REBREQ *sock) -/* -** Setup a server (listening) socket (TCP or UDP). -** -** Before usage: -** Open_Socket(); -** Set local_port to desired port number. -** -** Use this instead of Connect_Socket(). -** -***********************************************************************/ -{ - int result; - int len = 1; - SOCKAI sa; + switch (sock->flags) { + case 3171: { + INCLUDE_PARAMS_OF_SET_UDP_MULTICAST; - // Setup socket address range and port: - Set_Addr(&sa, INADDR_ANY, sock->net.local_port); + UNUSED(ARG(port)); // implicit from sock, which caller extracted - // Allow listen socket reuse: - result = setsockopt(sock->socket, SOL_SOCKET, SO_REUSEADDR, (char*)(&len), sizeof(len)); - if (result) { -lserr: - sock->error = GET_ERROR; - return DR_ERROR; - } + if (!GET_FLAG(sock->modes, RST_UDP)) { // !!! other checks? + sock->error = -18; + return DR_ERROR; + } + + struct ip_mreq mreq; + memcpy(&mreq.imr_multiaddr.s_addr, VAL_TUPLE(ARG(group)), 4); + memcpy(&mreq.imr_interface.s_addr, VAL_TUPLE(ARG(member)), 4); - // Bind the socket to our local address: - result = bind(sock->socket, (struct sockaddr *)&sa, sizeof(sa)); - if (result) goto lserr; + result = setsockopt( + sock->requestee.socket, + IPPROTO_IP, + REF(drop) ? IP_DROP_MEMBERSHIP : IP_ADD_MEMBERSHIP, + cast(char*, &mreq), + sizeof(mreq) + ); - SET_FLAG(sock->state, RSM_BIND); + break; } - // For TCP connections, setup listen queue: - if (!GET_FLAG(sock->modes, RST_UDP)) { - result = listen(sock->socket, SOMAXCONN); - if (result) goto lserr; - SET_FLAG(sock->state, RSM_LISTEN); - } + case 2365: { + INCLUDE_PARAMS_OF_SET_UDP_TTL; - Get_Local_IP(sock); - sock->command = RDC_CREATE; // the command done on wakeup + UNUSED(ARG(port)); // implicit from sock, which caller extracted - return DR_PEND; + if (!GET_FLAG(sock->modes, RST_UDP)) { // !!! other checks? + sock->error = -18; + return DR_ERROR; + } + + int ttl = VAL_INT32(ARG(ttl)); + result = setsockopt( + sock->requestee.socket, + IPPROTO_IP, + IP_TTL, + cast(char*, &ttl), + sizeof(ttl) + ); + + break; } + + default: + // not return DR_ERROR? Is failing here ok? + // + fail ("Unknown socket MODIFY operation"); + } + + if (result < 0) { + sock->error = result; + return DR_ERROR; + } + + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Accept_Socket(REBREQ *sock) -/* -** Accept an inbound connection on a TCP listen socket. -** -** The function will return: -** =0: succeeded -** >0: in-progress, still trying -** <0: error occurred, no longer trying -** -** Before usage: -** Open_Socket(); -** Set local_port to desired port number. -** Listen_Socket(); -** -***********************************************************************/ +// +// Accept_Socket: C +// +// Accept an inbound connection on a TCP listen socket. +// +// The function will return: +// =0: succeeded +// >0: in-progress, still trying +// <0: error occurred, no longer trying +// +// Before usage: +// Open_Socket(); +// Set local_port to desired port number. +// Listen_Socket(); +// +DEVICE_CMD Accept_Socket(REBREQ *req) { - SOCKAI sa; - REBREQ *news; - int len = sizeof(sa); - int result; - extern void Attach_Request(REBREQ **prior, REBREQ *req); - - // Accept a new socket, if there is one: - result = accept(sock->socket, (struct sockaddr *)&sa, &len); - - if (result == BAD_SOCKET) { - result = GET_ERROR; - if (result == NE_WOULDBLOCK) return DR_PEND; - sock->error = result; - //Signal_Device(sock, EVT_ERROR); - return DR_ERROR; - } - - // To report the new socket, the code here creates a temporary - // request and copies the listen request to it. Then, it stores - // the new values for IP and ports and links this request to the - // original via the sock->data. - news = MAKE_NEW(*news); // Be sure to deallocate it - CLEARS(news); -// *news = *sock; - news->device = sock->device; - - SET_OPEN(news); - SET_FLAG(news->state, RSM_OPEN); - SET_FLAG(news->state, RSM_CONNECT); - - news->socket = result; - news->net.remote_ip = sa.sin_addr.s_addr; //htonl(ip); NOTE: REBOL stays in network byte order - news->net.remote_port = ntohs(sa.sin_port); - Get_Local_IP(news); - - //Nonblocking_Mode(news->socket); ???Needed? - - Attach_Request((REBREQ**)&sock->data, news); - Signal_Device(sock, EVT_ACCEPT); - - // Even though we signalled, we keep the listen pending to - // accept additional connections. - return DR_PEND; + struct sockaddr_in sa; + struct devreq_net *news; + socklen_t len = sizeof(sa); + int result; + extern void Attach_Request(REBREQ **prior, REBREQ *req); + struct devreq_net *sock = DEVREQ_NET(req); + + // Accept a new socket, if there is one: + result = accept(req->requestee.socket, cast(struct sockaddr *, &sa), &len); + + if (result == BAD_SOCKET) { + result = GET_ERROR; + if (result == NE_WOULDBLOCK) return DR_PEND; + req->error = result; + //Signal_Device(sock, EVT_ERROR); + return DR_ERROR; + } + + if (!Set_Sock_Options(result)) { + req->error = GET_ERROR; + //Signal_Device(sock, EVT_ERROR); + return DR_ERROR; + } + + // To report the new socket, the code here creates a temporary + // request and copies the listen request to it. Then, it stores + // the new values for IP and ports and links this request to the + // original via the sock->common.data. + news = OS_ALLOC_ZEROFILL(struct devreq_net); +// *news = *sock; + news->devreq.device = req->device; + + SET_OPEN(news); + SET_FLAG(news->devreq.state, RSM_OPEN); + SET_FLAG(news->devreq.state, RSM_CONNECT); + + news->devreq.requestee.socket = result; + news->remote_ip = sa.sin_addr.s_addr; //htonl(ip); NOTE: REBOL stays in network byte order + news->remote_port = ntohs(sa.sin_port); + Get_Local_IP(news); + + // There could be mulitple connections to be accepted. + // Queue them at common.sock + Attach_Request(cast(REBREQ**, &AS_REBREQ(sock)->common.sock), AS_REBREQ(news)); + + Signal_Device(req, EVT_ACCEPT); + + // Even though we signalled, we keep the listen pending to + // accept additional connections. + return DR_PEND; } +i32 Request_Size_Net(REBREQ *sock) +{ + UNUSED(sock); + return sizeof(struct devreq_net); +} /*********************************************************************** ** -** Command Dispatch Table (RDC_ enum order) +** Command Dispatch Table (RDC_ enum order) ** ***********************************************************************/ static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = { - Init_Net, - Quit_Net, - Open_Socket, - Close_Socket, - Transfer_Socket, // Read - Transfer_Socket, // Write - 0, // poll - Connect_Socket, - 0, // query - 0, // modify - Accept_Socket, // Create - 0, // delete - 0, // rename - Lookup_Socket + Request_Size_Net, + Init_Net, + Quit_Net, + Open_Socket, + Close_Socket, + Transfer_Socket, // Read + Transfer_Socket, // Write + 0, // poll + Connect_Socket, + 0, // query + Modify_Socket, // modify + Accept_Socket, // Create + 0, // delete + 0, // rename + Lookup_Socket }; -DEFINE_DEV(Dev_Net, "TCP/IP Network", 1, Dev_Cmds, RDC_MAX, sizeof(REBREQ)); +DEFINE_DEV(Dev_Net, "TCP/IP Network", 1, Dev_Cmds, RDC_MAX); diff --git a/src/os/encap.reb b/src/os/encap.reb new file mode 100644 index 0000000000..eeabefeab1 --- /dev/null +++ b/src/os/encap.reb @@ -0,0 +1,1300 @@ +REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Host Script and Resource Embedding Services ('encapping')" + Rights: { + Copyright 2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Description: { + Encapping grafts data into an already-compiled executable, to add + resources to it "after the fact". Note that there are different + executable formats used on different operating systems, each with a + header that tells the operating system how to interpret the file: + + Linux: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format + Windows: https://en.wikipedia.org/wiki/Portable_Executable + OS X: https://en.wikipedia.org/wiki/Mach-O + + A "naive" form of adding data into an executable is to append the + data at the tail, which generally does not affect operation: + + http://stackoverflow.com/a/5801598/211160 + + This is a common approach, yet it has some flaws. e.g. on Linux, + running the `strip` command will see the added data as irrelevant, + and remove it. Other manipulations like adding an icon resource may + add the icon resource data to the end. There are other things, like + executable compression (although some executable compressors are aware + of this well-known embedding tactic, and account for it). + + It may be reasonable to say that it is the burden of those doing + executable manipulations to de-encap it, do the modification, and then + re-encap the executable. But all things being equal, it's desirable + to find ways to slipstream the information into the "valid/known" + resource logic of the OS. + + This can be done with OS-specific tools or system calls, but the + advantage of writing it standalone as Rebol is that it reduces the + dependencies. It allows encapping of executables built on a platform + different than the one you are running on. So attempts are made + here to manipulate the published formats with Rebol code itself. + + For formats not supported currently by the encapper, the simple + appending strategy is used. + } +] + + +; +; https://en.wikipedia.org/wiki/Executable_and_Linkable_Format +; +; The ELF format contains named data sections, and the encap payload is +; injected as one of these sections (with a specific name). Injecting or +; modifying a section requires updating file offsets in affected headers. +; +; Note: since section headers are fixed-size, the names for the sections are +; zero-terminated strings which are themselves stored in a section. This +; can be any section (specified in the header as `e_shstrndx`), so processing +; names requires a pre-pass to find it, hence is a little bit convoluted. +; +elf-format: context [ + encap-section-name: ".rebol.encap.1" + + ; (E)LF overall header properties read or written during parse + + EI_CLASS: _ + EI_DATA: _ + EI_VERSION: _ + bits: _ ; 32 or 64 + endian: _ ; 'little or 'big + e_phoff: _ ; Offset of program header table start. + e_phnum: _ ; Number of entries in the section header table. + e_phentsize: _ ; Size of a program header table entry. + e_shoff: _ ; Offset of section header table start. + e_shnum: _ ; Number of entries in the section header table. + e_shentsize: _ ; Size of a section header table entry. + e_shstrndx: _ ; section header index with section names. + + ; (P)rogram Header properties read or written during parse + + p_type: _ + p_offset: _ + p_filesz: _ + + ; (S)ection (H)eader properties extracted during parse + + sh_name: _ ; .shstrtab section offset w/this section's name + sh_type: _ + sh_flags: _ + sh_addr: _ + sh_offset: _ + sh_size: _ + sh_link: _ + sh_info: _ + sh_addralign: _ + sh_entsize: _ + + begin: _ ; Capture position in the series + + ; When parsing a binary header, the properties are either 'read or 'write + ; In the current update pattern, a read phase is followed by tweaking + ; the desired parameters, then seeking back and doing a write phase. + ; For safety, the mode is reset to blank after each rule, to force being + ; explicit at the callsites. + ; + mode: _ + handler: function [name [word!] num-bytes [integer!]] [ + assert [ + binary? begin | num-bytes <= length begin + | find [read write] mode + ] + + either mode = 'read [ + bin: copy/part begin num-bytes + if endian = 'little [reverse bin] + set name (to-integer/unsigned bin) + ][ + val: ensure integer! get name + bin: skip (tail to-binary val) (negate num-bytes) ;-- big endian + if endian = 'little [reverse bin] + change begin bin + ] + ] + + header-rule: [ + #{7F} "ELF" + set EI_CLASS skip (bits: either EI_CLASS = 1 [32] [64]) + set EI_DATA skip (endian: either EI_DATA = 1 ['little] ['big]) + set EI_VERSION skip (assert [EI_VERSION = 1]) + skip ; EI_OSABI + skip ; EI_ABIVERSION + 7 skip ; EI_PAD + 2 skip ; e_type + 2 skip ; e_machine + 4 skip ; e_version + [ + if (bits = 32) [ + 4 skip ; e_entry + begin: 4 skip (handler 'e_phoff 4) + begin: 4 skip (handler 'e_shoff 4) + ] + | + if (bits = 64) [ + 8 skip ; e_entry + begin: 8 skip (handler 'e_phoff 8) + begin: 8 skip (handler 'e_shoff 8) + ] + ] + 4 skip ; e_flags + 2 skip ; e_ehsize + begin: 2 skip (handler 'e_phentsize 2) + begin: 2 skip (handler 'e_phnum 2) + begin: 2 skip (handler 'e_shentsize 2) + begin: 2 skip (handler 'e_shnum 2) + begin: 2 skip (handler 'e_shstrndx 2) + + (mode: _) + ] + + program-header-rule: [ + begin: 4 skip (handler 'p_type 4) + [ + if (bits = 32) [ + begin: 4 skip (handler 'p_offset 4) + 4 skip ; p_vaddr + 4 skip ; p_paddr + begin: 4 skip (handler 'p_filesz 4) + 4 skip ; p_memsz + ] + | + if (bits = 64) [ + 4 skip ; p_flags, different position in 64-bit + begin: 8 skip (handler 'p_offset 8) + 8 skip ; p_vaddr + 8 skip ; p_paddr + begin: 8 skip (handler 'p_filesz 8) + 8 skip ; p_memsz + ] + ] + [ + if (bits = 32) [ + 4 skip ; p_flags, different position in 32-bit + 4 skip ; p_align + ] + | + if (bits = 64) [ + 8 skip ; p_align + ] + ] + + (mode: _) + ] + + section-header-rule: [ + begin: 4 skip (handler 'sh_name 4) + begin: 4 skip (handler 'sh_type 4) + [ + if (bits = 32) [ + begin: 4 skip (handler 'sh_flags 4) + begin: 4 skip (handler 'sh_addr 4) + begin: 4 skip (handler 'sh_offset 4) + begin: 4 skip (handler 'sh_size 4) + ] + | + if (bits = 64) [ + begin: 8 skip (handler 'sh_flags 8) + begin: 8 skip (handler 'sh_addr 8) + begin: 8 skip (handler 'sh_offset 8) + begin: 8 skip (handler 'sh_size 8) + ] + ] + begin: 4 skip (handler 'sh_link 4) + begin: 4 skip (handler 'sh_info 4) + [ + if (bits = 32) [ + begin: 4 skip (handler 'sh_addralign 4) + begin: 4 skip (handler 'sh_entsize 4) + ] + | + if (bits = 64) [ + begin: 8 skip (handler 'sh_addralign 8) + begin: 8 skip (handler 'sh_entsize 8) + ] + ] + + (mode: _) + ] + + find-section: function [ + return: [blank! integer!] + {The index of the section header with encap (sh_xxx vars set)} + name [string!] + section-headers [binary!] + string-section [binary!] + + self + ][ + index: 0 + parse section-headers [ + (assert [integer? e_shnum]) + e_shnum [ ; the number of times to apply the rule + (mode: 'read) section-header-rule + ( + name-start: skip string-section sh_name + name-end: ensure binary! find name-start #{00} + section-name: to-string copy/part name-start name-end + if name = section-name [ + return index ;-- sh_offset, sh_size, etc. are set + ] + index: index + 1 + ) + ] + ] + return blank + ] + + update-offsets: procedure [ + {Adjust headers to account for insertion or removal of data @ offset} + + executable [binary!] + offset [integer!] + delta [integer!] + + self + ][ + assert [e_phoff < offset] ;-- program headers are before any changes + unless parse skip executable e_phoff [ + e_phnum [ + (mode: 'read) pos: program-header-rule + (if p_offset >= offset [p_offset: p_offset + delta]) + (mode: 'write) :pos program-header-rule + ] + to end + ][ + fail "Error updating offsets in program headers" + ] + + assert [e_shoff >= offset] ;-- section headers are after any changes + unless parse skip executable e_shoff [ + e_shnum [ + (mode: 'read) pos: section-header-rule + (if sh_offset >= offset [sh_offset: sh_offset + delta]) + (mode: 'write) :pos section-header-rule + ] + to end + ][ + fail "Error updating offsets in section headers" + ] + ] + + update-embedding: procedure [ + executable [binary!] + {Executable to be mutated to either add or update an embedding} + embedding [binary!] + + self + ][ + ; Up front, let's check to see if the executable has data past the + ; tail or not--which indicates some other app added data using the + ; simple concatenation method of "poor man's encap" + ; + section-header-tail: e_shoff + (e_shnum * e_shentsize) + case [ + section-header-tail = length executable [ + print "Executable has no appended data past ELF image size" + ] + section-header-tail > length executable [ + print [ + "Executable has" + (length executable) - section-header-tail + "bytes of extra data past the formal ELF image size" + ] + ] + true [ + fail "Section header table in ELF binary is corrupt" + ] + ] + + ; The string names of the sections are themselves stored in a section, + ; (at index `e_shstrndx`) + ; + string-header-offset: e_shoff + (e_shstrndx * e_shentsize) + unless parse skip executable string-header-offset [ + (mode: 'read) section-header-rule to end + ][ + fail "Error finding string section in ELF binary" + ] + + string-section-offset: sh_offset + string-section-size: sh_size + + ; Now that we have the string section, we can go through the + ; section names and see if there's any match for an existing encap + ; + section-index: ( + find-section + encap-section-name + skip executable e_shoff ; section headers + skip executable string-section-offset ; section offset + ) + + either section-index [ + ; + ; There's already an embedded section, and we're either going to + ; grow it or shrink it. We don't have to touch the string table, + ; though we might wind up displacing it (if the embedded section + ; somehow got relocated from being the last) + ; + print [ + "Embedded section exists [" + "index:" section-index + "offset:" sh_offset + "size:" sh_size + "]" + ] + + old-size: sh_size + new-size: length embedding + + ; Update the size of the embedded section in it's section header + ; + parse skip executable e_shoff + (section-index * e_shentsize) [ + (sh_size: new-size) + (mode: 'write) section-header-rule + ] + + ; Adjust all the program and section header offsets that are + ; affected by this movement + ; + delta: new-size - old-size + print ["Updating embedding by delta of" delta "bytes."] + (update-offsets + executable + (sh_offset + old-size) ; offset of change + delta ; amount of change + ) + + ; With offsets adjusted, delete old embedding, and insert the new + ; + remove/part (skip executable sh_offset) old-size + insert (skip executable sh_offset) embedding + + ; We moved the section headers at the tail of the file, which are + ; pointed to by the main ELF header. Updated after branch. + ; + e_shoff: e_shoff + delta + ][ + print "No existing embedded section was found, adding one." + + ; ADD STRING TABLE ENTRY + + ; Loop through all the section and program headers that will be + ; affected by an insertion (could be 0 if string table is the + ; last section, could be all of them if it's the first). Update + ; their offsets to account for the string table insertion, but + ; don't actually move any data in `executable` yet. + ; + (update-offsets + executable + (string-section-offset + string-section-size) + (1 + length encap-section-name) ; include null terminator + ) + + ; Update string table size in its corresponding header. + ; + unless parse skip executable string-header-offset [ + (mode: 'read) pos: section-header-rule + ( + assert [sh_offset = string-section-offset] + sh_size: sh_size + (1 + length encap-section-name) + ) + (mode: 'write) :pos section-header-rule + to end + ][ + fail "Error updating string table size in string header" + ] + + ; MAKE NEW SECTION TO BE THE LAST SECTION + + ; Start by cloning the string table section, and assume that its + ; fields will be mostly okay for the platform. + ; + (new-section-header: copy/part + (skip executable string-header-offset) e_shentsize) + + ; Tweak the fields of the copy to be SHT_NOTE, which is used for + ; miscellaneous program-specific purposes, and hence not touched + ; by strip...it is also not mapped into memory. + ; + unless parse new-section-header [ + ( + sh_name: string-section-size ; w.r.t string-section-offset + sh_type: 7 ; SHT_NOTE + sh_flags: 0 + sh_size: length embedding + sh_offset: e_shoff + (1 + length encap-section-name) + ) + (mode: 'write) section-header-rule + to end + ][ + fail "Error creating new section for the embedded data" + ] + + ; Append new header to the very end of the section headers. This + ; may or may not be the actual end of the executable. It will + ; affect no ELF offsets, just the `e_shnum`. + ; + insert (skip executable section-header-tail) new-section-header + + ; Do the insertion of the data for the embedding itself. Since + ; we're adding it right where the old section headers used to + ; start, this only affects `e_shoff`. + ; + insert (skip executable e_shoff) embedding + + ; Now do the string table insertion, which all the program and + ; section headers were already adjusted to account for. + ; + (insert + (skip executable string-section-offset + string-section-size) + (join-of (to-binary encap-section-name) #{00}) + ) + + ; We added a section (so another section header to account for), + ; + e_shnum: e_shnum + 1 + + ; We expanded the string table and added the embedding, so the + ; section header table offset has to be adjusted. + ; + e_shoff: ( + e_shoff + + (length embedding) + + (1 + length encap-section-name) + ) + + ; (main header write is done after the branch.) + ] + + unless parse executable [ + (mode: 'write) header-rule to end + ][ + fail "Error updating the ELF header" + ] + ] + + get-embedding: function [ + return: [binary! blank!] + file [file!] + + self + ][ + header-data: read/part file 64 ; 64-bit size, 32-bit is smaller + + if not parse header-data [(mode: 'read) header-rule to end] [ + return blank + ] + + section-headers-data: + read/seek/part file e_shoff (e_shnum * e_shentsize) + + ; The string names of the sections are themselves stored in a section, + ; (at index `e_shstrndx`) + ; + unless parse skip section-headers-data (e_shstrndx * e_shentsize) [ + (mode: 'read) section-header-rule to end + ][ + fail "Error finding string section in ELF binary" + ] + + string-section-data: read/seek/part file sh_offset sh_size + + ; Now that we have the string section, we can go through the + ; section names and see if there's any match for an existing encap + ; + if not section-index: ( + find-section + encap-section-name + section-headers-data + string-section-data + )[ + return blank + ] + + return read/seek/part file sh_offset sh_size + ] +] + +; The Portable Executable (PE) format is a file format for executables, object +; code, DLLs, FON Font files, and others used in 32-bit and 64-bit versions of +; Windows operating systems. +; +; The official specification is at: +; https://msdn.microsoft.com/en-us/library/windows/desktop/ms680547(v=vs.85).aspx +; +pe-format: context [ + encap-section-name: ".rebolE" ;limited to 8 bytes + + b1: b2: b3: b4: b5: b6: b7: b8: u16: u32: u64: uintptr: _ + err: _ + fail-at: _ + + byte: complement charset [] + u16-le: [copy b1 byte copy b2 byte + (u16: (shift to-integer/unsigned b2 8) + or+ to-integer/unsigned b1)] + u32-le: [copy b1 byte copy b2 byte + copy b3 byte copy b4 byte + (u32: (shift to-integer/unsigned b4 24) + or+ (shift to-integer/unsigned b3 16) + or+ (shift to-integer/unsigned b2 8) + or+ to-integer/unsigned b1)] + u64-le: [copy b1 byte copy b2 byte + copy b3 byte copy b4 byte + copy b5 byte copy b6 byte + copy b7 byte copy b8 byte + (u64: (shift to-integer/unsigned b8 56) + or+ (shift to-integer/unsigned b7 48) + or+ (shift to-integer/unsigned b3 40) + or+ (shift to-integer/unsigned b5 32) + or+ (shift to-integer/unsigned b4 24) + or+ (shift to-integer/unsigned b3 16) + or+ (shift to-integer/unsigned b2 8) + or+ to-integer/unsigned b1)] + + uintptr-le: + uintptr-32-le: [u32-le (uintptr: u32)] + uintptr-64-le: [u64-le (uintptr: u64)] + + gen-rule: function [ + "Collect all set-words in @rule and make an object out of them and save it in @name" + rule [block!] + 'name [word!] + /skip + words [word! block!] "Do not collect these words" + + word + skips + def + find-a-word + ][ + find-a-word: proc [ + word [any-word!] + ][ + unless any [ + find? words to word! word + find? def to set-word! word + ][ + append def reduce [to set-word! word] + ] + ] + + either skip [ + if word? words [ + words: reduce [words] + ] + if locked? words [ + words: copy words + ] + append words [err] + ][ + words: [err] + ] + + def: make block! 1 + group-rule: [ + any [ + set word set-word! + (find-a-word word) + | and block! into block-rule ;recursively look into the array + | skip + ] + ] + block-rule: [ + any [ + and group! into group-rule + | and block! into block-rule + | ['copy | 'set] set word word! (find-a-word word) + | skip + ] + ] + + parse rule block-rule + + ;dump def + set name make object! append def _ + bind rule get name + ] + + DOS-header: _ + pos: _ + + DOS-header-rule: gen-rule [ + ["MZ" | fail-at: (err: 'missing-dos-signature) fail] + u16-le (last-size: u16) + u16-le (n-blocks: u16) + u16-le (n-reloc: u16) + u16-le (header-size: u16) + u16-le (min-alloc: u16) + u16-le (max-alloc: u16) + u16-le (ss: u16) + u16-le (sp: u16) + u16-le (checksum: u16) + u16-le (ip: u16) + u16-le (cs: u16) + u16-le (reloc-pos: u16) + u16-le (n-overlay: u16) + copy reserved1 4 u16-le + u16-le (oem-id: u16) + u16-le (oem-info: u16) + copy reserved2 10 u16-le + u32-le (e-lfanew: u32) + ] DOS-header + + PE-header-rule: [ + "PE^@^@" | fail-at: (err: 'missing-PE-signature) fail + ] + + COFF-header: _ + COFF-header-rule: gen-rule/skip [ + and [ + #{4c01} (machine: 'i386) + | #{6486} (machine: 'x86-64 uintptr-le: uintptr-64-le) + | #{6201} (machine: 'MIPS-R3000) + | #{6801} (machine: 'MIPS-R10000) + | #{6901} (machine: 'MIPS-le-WCI-v2) + | #{8301} (machine: 'old-alpha-AXP) + | #{8401} (machine: 'alpha-AXP) + | #{0002} (machine: 'IA64 uintptr-le: uintptr-64-le) + | #{6602} (machine: 'MIPS16) + ] + u16-le (machine-value: u16) + pos: u16-le ( + number-of-sections: u16 + number-of-sections-offset: (index-of pos) - 1 + ) + u32-le (time-date-stamp: u32) + u32-le (pointer-to-symbol-table: u32) + u32-le (number-of-symbols: u32) + u16-le (size-of-optional-headers: u16) + u16-le (chracteristics: u16) + ] COFF-header 'uintptr-le + + data-directories: make block! 16 + sections: make block! 8 + PE-optional-header: _ + + PE-optional-header-rule: gen-rule [ + and [#{0b01} (signature: 'exe-32) + | #{0b02} (signature: 'exe-64) + | #{0701} (signature: 'ROM) + | fail-at: (err: 'missing-image-signature) fail + ] + u16-le (signature-value: u16) + copy major-linker-version byte + copy minor-linker-version byte + u32-le (size-of-code: u32) + u32-le (size-of-initialized-data: u32) + u32-le (size-of-uninialized-data: u32) + u32-le (address-of-entry-point: u32) + u32-le (code-base: u32) + u32-le (data-base: u32) + u32-le (image-base: u32 + if signature = 'exe-64 [ + image-base: code-base or+ shift image-base 32 + code-base: _ + ]) + u32-le (section-alignment: u32) + u32-le (file-alignment: u32) + u16-le (major-OS-version: u16) + u16-le (minor-OS-version: u16) + u16-le (major-image-version: u16) + u16-le (minor-image-version: u16) + u16-le (major-subsystem-version: u16) + u16-le (minor-subsystem-version: u16) + u32-le (win32-version-value: u32) + pos: u32-le (image-size: u32 + image-size-offset: (index-of pos) - 1) + u32-le (size-of-headers: u32) + u32-le (checksum: u32) + and [ + #{0000} (subsystem: 'unknown) + | #{0100} (subsystem: 'native) + | #{0200} (subsystem: 'Widnows-GUI) + | #{0300} (subsystem: 'Windows-CUI) + | #{0500} (subsystem: 'OS2-CUI) + | #{0700} (subsystem: 'POSIX-CUI) + | #{0900} (subsystem: 'Widnows-CE-GUI) + | #{1000} (subsystem: 'EFI-application) + | #{1100} (subsystem: 'EFI-boot-service-driver) + | #{1200} (subsystem: 'EFI-runtime-driver) + | #{1300} (subsystem: 'EFI-ROM) + | #{1400} (subsystem: 'Xbox) + | #{1600} (subsystem: 'Windows-Boot-application) + | fail-at: (err: 'unrecoginized-subsystem) fail + ] + u16-le (subsystem-value: u16) + u16-le (dll-characteristics: u16) + uintptr-le (size-of-stack-reserve: uintptr) + uintptr-le (size-of-stack-commit: uintptr) + uintptr-le (size-of-heap-reserve: uintptr) + uintptr-le (size-of-heap-commit: uintptr) + u32-le (loader-flags: u32) + u32-le (number-of-RVA-and-sizes: u32) + ] PE-optional-header + + data-directory: _ + data-directory-rule: gen-rule [ + u32-le (RVA: u32) + u32-le (size: u32) + (append data-directories copy data-directory) + ] data-directory + + section: _ + section-rule: gen-rule [ + copy name [8 byte] + u32-le (virtual-size: u32) + u32-le (virtual-offset: u32) + u32-le (physical-size: u32) + u32-le (physical-offset: u32) + copy reserved [12 byte] + u32-le (flags: u32) + (append sections copy section) + ] section + + garbage: _ + start-of-section-header: _ + end-of-section-header: _ + + exe-rule: [ + DOS-header-rule pos: (garbage: DOS-header/e-lfanew + 1 - index-of pos) + garbage skip + PE-header-rule + COFF-header-rule + PE-optional-header-rule + PE-optional-header/number-of-RVA-and-sizes data-directory-rule + start-of-section-header: + COFF-header/number-of-sections section-rule + end-of-section-header: + ] + size-of-section-header: 40 ;size of one entry + + to-u32-le: func [ + i [integer!] + ][ + reverse skip (to binary! i) 4 + ] + + to-u16-le: func [ + i [integer!] + ][ + reverse skip (to binary! i) 6 + ] + + align-to: function [ + offset [integer!] + align [integer!] + ][ + either zero? rem: offset // align [ + offset + ][ + offset + align - rem + ] + ] + + reset: does [ + err: _ + fail-at: _ + start-of-section-header: + end-of-section-header: + garbage: _ + ;DOS-header: _ + pos: _ + ;PE-optional-header: _ + clear sections + clear data-directories + ] + + parse-exe: function [ + exe-data [binary!] + ][ + reset + parse exe-data exe-rule + if err [ + fail unspaced ["err: " err ", at: " copy/part fail-at 16] + ] + true + ] + + update-section-header: procedure [ + pos [binary!] + section [object!] + ][ + change pos new-section: rejoin [ + copy/part (head insert/dup tail to binary! copy section/name #{00} 8) 8 ;name, must be 8-byte long + to-u32-le section/virtual-size + to-u32-le section/virtual-offset + to-u32-le section/physical-size + to-u32-le section/physical-offset + copy/part (head insert/dup tail to binary! copy section/reserved #{00} 12) 12 ;reserved, must be 12-byte long + either binary? section/flags [section/flags][to-u32-le section/flags] + ] + + ;dump new-section + assert [size-of-section-header = length new-section] + ] + + add-section: function [ + "Add a new section to the exe, modify in place" + exe-data [binary!] + section-name [string!] + section-data [binary!] + ][ + parse-exe exe-data + + ;dump DOS-header + ;dump PE-optional-header + + ;check if there's section name conflicts + for-each sec sections [ + if section-name = to string! trim/with sec/name #{00} [ + fail unspaced ["There is already a section named " section-name ":^/" mold sec] + ] + ] + + ;print ["Section headers end at:" index-of end-of-section-header] + sort/compare sections func [a b][a/physical-offset < b/physical-offset] + secs: sections + first-section-by-phy-offset: secs/1 + for-next secs [ + unless zero? secs/1/physical-offset [ + first-section-by-phy-offset: secs/1 + break + ] + ] + ;dump first-section-by-phy-offset + gap: first-section-by-phy-offset/physical-offset - (index-of end-of-section-header) + if gap < size-of-section-header [ + fail "Not enough room for a new section header" + ] + + ; increment the "number of sections" + change skip exe-data COFF-header/number-of-sections-offset + to-u16-le (COFF-header/number-of-sections + 1) + + last-section-by-phy-offset: sections/(COFF-header/number-of-sections) + ;dump last-section-by-phy-offset + + sort/compare sections func [a b][a/virtual-offset < b/virtual-offset] + last-section-by-virt-offset: sections/(COFF-header/number-of-sections) + last-virt-offset: align-to last-section-by-virt-offset/virtual-offset + last-section-by-virt-offset/virtual-size 4096 + + new-section-size: align-to (length section-data) PE-optional-header/file-alignment;physical size + new-section-offset: last-section-by-phy-offset/physical-offset + last-section-by-phy-offset/physical-size + assert [zero? new-section-offset // PE-optional-header/file-alignment] + + ; set the image size + ; image size has to be mulitple of section-alignment + change skip exe-data PE-optional-header/image-size-offset + to-u32-le align-to (PE-optional-header/image-size + new-section-size) PE-optional-header/section-alignment + + ; add a new section header + new-section-header: make section [ + name: section-name + virtual-size: length section-data + virtual-offset: last-virt-offset + physical-size: new-section-size + physical-offset: new-section-offset + flags: #{40000040} ; initialized read-only exe-data + ] + + update-section-header end-of-section-header new-section-header + + ;print ["current exe-data length" length exe-data] + if new-section-offset > length exe-data [ + print "Last section has been truncated, filling with garbage" + insert/dup garbage: copy #{} #{00} (new-section-offset - length exe-data) + print ["length of filler:" length garbage] + append exe-data garbage + ] + + if new-section-size > length section-data [ + insert/dup garbage: copy #{} #{00} (new-section-size - length section-data) + section-data: join-of to binary! section-data garbage + ] + + assert [zero? (length section-data) // PE-optional-header/file-alignment] + + ; add the section + case [ + new-section-offset < length exe-data [ + print ["There's extra exe-data at the end"] + insert (skip exe-data new-section-offset) section-data + ] + new-section-offset = length exe-data [ + print ["Appending exe-data"] + append exe-data section-data + ] + ] else [ + fail "Last section has been truncated" + ] + + head exe-data + ] + + find-section: function [ + "Find a section to the exe" + exe-data [binary!] + section-name [string!] + /header "Return only the section header" + /data "Return only the section data" + ][ + trap/with [ + parse-exe exe-data + ][ + ;print ["Failed to parse exe:" err] + return _ + ] + + ;check if there's section name conflicts + target-sec: _ + for-each sec sections [ + if section-name = to string! trim/with sec/name #{00} [ + target-sec: sec + break + ] + ] + unless target-sec [ + ;fail spaced ["Couldn't find the section" section-name] + return _ + ] + + case [ + header [ + target-sec + ] + data [ + copy/part (skip exe-data target-sec/physical-offset) target-sec/physical-size + ] + 'else [ + reduce [ + target-sec + copy/part (skip exe-data target-sec/physical-offset) target-sec/physical-size + ] + ] + ] + ] + + update-section: function [ + exe-data [binary!] + section-name [string!] + section-data [binary!] + ][ + target-sec: find-section/header exe-data section-name; this will parse exe-data + ;dump target-sec + if blank? target-sec [ + return add-section exe-data section-name section-data + ] + new-section-size: align-to (length section-data) PE-optional-header/file-alignment + section-size-diff: new-section-size - target-sec/physical-size + unless zero? section-size-diff [ + new-image-size: to-u32-le align-to (PE-optional-header/image-size + section-size-diff) PE-optional-header/section-alignment + if new-image-size != PE-optional-header/image-size [ + change skip exe-data PE-optional-header/image-size-offset new-image-size + ] + ] + pos: start-of-section-header + for-each sec sections [ + if sec/physical-offset > target-sec/physical-size [ + ;update the offset affected sections + sec/physical-offset: sec/physical-offset + section-size-diff + update-section-header pos sec + ] + pos: skip pos size-of-section-header + ] + remove/part pos: skip exe-data target-sec/physical-offset target-sec/physical-size + if new-section-size > length section-data [;padding with #{00} + insert/dup garbage: copy #{} #{00} (new-section-size - length section-data) + section-data: join-of to binary! section-data garbage + ] + insert pos section-data + + also head exe-data + reset + ] + + remove-section: function [ + exe-data [binary!] + section-name [string!] + ][ + target-sec: find-section/header exe-data section-name; this will parse exe-data + ;dump target-sec + + ;dump COFF-header + ;dump PE-optional-header + ; decrement the "number of sections" + change skip exe-data COFF-header/number-of-sections-offset + to-u16-le (COFF-header/number-of-sections - 1) + + image-size-diff: align-to target-sec/physical-size PE-optional-header/section-alignment + unless zero? image-size-diff [ + change skip exe-data PE-optional-header/image-size-offset + to-u32-le (PE-optional-header/image-size - image-size-diff) + ] + + pos: start-of-section-header + for-each sec sections [ + print to string! sec/name + ;dump sec + case [ + sec/physical-offset = target-sec/physical-offset [ + assert [sec/name = target-sec/name] + ;target sec, replace with all #{00} + change pos head (insert/dup copy #{} #{00} size-of-section-header) + ; do not skip @pos, so that the next section will overwrite this one if it's not the last section + ] + sec/physical-offset > target-sec/physical-offset [ + ;update the offset affected sections + sec/physical-offset: sec/physical-offset - target-sec/physical-size + update-section-header pos sec + pos: skip pos size-of-section-header + ] + 'else [;unchanged + pos: skip pos size-of-section-header + ] + ] + ] + + unless target-sec/physical-offset + 1 = index-of pos [ + ;if the section to remove is not the last section, the last section + ;must have moved forward, so erase the old section + change pos head (insert/dup copy #{} #{00} size-of-section-header) + ] + + remove/part skip exe-data target-sec/physical-offset target-sec/physical-size + + also head exe-data + reset + ] + + update-embedding: specialize 'update-section [section-name: encap-section-name] + get-embedding: function [ + return: [binary! blank!] + file [file!] + ][ + ;print ["Geting embedded from" mold file] + exe-data: read file + also find-section/data exe-data encap-section-name + reset + ] +] + +generic-format: context [ + signature: to-binary "ENCAP000" + sig-length: (length signature) + + update-embedding: procedure [ + executable [binary!] + {Executable to be mutated to either add or update an embedding} + embedding [binary!] + + self + ][ + embed-size: length embedding + + ; The executable we're looking at is already encapped if it ends with + ; the encapping signature. + ; + sig-location: skip tail executable (negate length signature) + case [ + sig-location = signature [ + print "Binary contains encap version 0 data block." + + size-location: skip sig-location -8 + embed-size: to-integer/unsigned copy/part size-location 8 + print ["Existing embedded data is" embed-size "bytes long."] + + print ["Trimming out existing embedded data."] + clear skip size-location (negate embed-size) + + print ["Trimmed executable size is" length executable] + ] + true [ + print "Binary contains no pre-existing encap data block" + ] + ] + + while [0 != modulo (length executable) 4096] [ + append executable #{00} + ] then [ + print ["Executable padded to" length executable "bytes long."] + ] else [ + print ["No padding of executable length required."] + ] + + append executable embedding + + size-as-binary: to-binary length embedding + assert [8 = length size-as-binary] + append executable size-as-binary + + append executable signature + ] + + get-embedding: function [ + return: [binary! blank!] + file [file!] + + self + ][ + info: query file + + test-sig: read/seek/part file (info/size - sig-length) sig-length + + if test-sig != signature [return blank] + + embed-size: to-integer/unsigned ( + read/seek/part file (info/size - sig-length - 8) 8 + ) + + embed: read/seek/part file ( + info/size - sig-length - 8 - embed-size + ) embed-size + + return embed + ] +] + + +encap: function [ + return: [file!] + {Path location of the resulting output} + spec [file! block!] + {Single script to embed, directory to zip with main.reb, or dialect} + /rebol + {Specify a path to a Rebol to encap instead of using the current one} + in-rebol-path +][ + if block? spec [ + fail "The spec dialect for encapping has not been defined yet" + ] + + in-rebol-path: default [system/options/boot] + either ".exe" = base-name: skip tail in-rebol-path -4 [ + out-rebol-path: join-of + copy/part in-rebol-path (index-of base-name) - 1 + "-encap.exe" + ][ + out-rebol-path: join-of in-rebol-path "-encap" + ] + + print ["Encapping from original executable:" in-rebol-path] + + executable: read in-rebol-path + + print ["Original executable is" length executable "bytes long."] + + single-script: not dir? spec + + either single-script [ + embed: read spec + print ["New embedded resource size is" length embed "bytes long."] + + compressed: compress embed + ][ + compressed: copy #{} + zip/deep/verbose compressed spec + ] + + print ["Compressed resource is" length compressed "bytes long."] + + ; !!! Renaming the single file "main.reb" and zipping it would probably + ; be better, but the interface for zip doesn't allow you to override the + ; actual names of disk files at this moment. Just signal which it is. + ; + either single-script [ + insert compressed 0 ;-- signal a single file encap + ][ + insert compressed 1 ;-- signal a zipped encap + ] + + print ["Extending compressed resource by one byte for zipped/not signal"] + + case [ + parse executable [ + (elf-format/mode: 'read) elf-format/header-rule to end + ][ + print "ELF format found" + elf-format/update-embedding executable compressed + ] + pe-format/parse-exe executable [ + print "PE format found" + pe-format/update-embedding executable compressed + ] + true [ + print "Unidentified executable format, using naive concatenation." + + generic-format/update-embedding executable compressed + ] + ] + + print ["Writing executable with encap, size, signature to" out-rebol-path] + + write out-rebol-path executable + + print ["Output executable written with total size" length executable] + + ; !!! Currently only test the extraction for single-file, easier. + ; + if all [single-script | embed != extracted: get-encap out-rebol-path] [ + print ["Test extraction size:" length extracted] + print ["Embedded bytes" mold embed] + print ["Extracted bytes" mold extracted] + + fail "Test extraction of embedding did not match original data." + ] + + return out-rebol-path +] + + +get-encap: function [ + return: [blank! binary! block!] + {Blank if no encapping found, binary if single file, block if archive} + rebol-path [file!] + {The executable to search for the encap information in} +][ + trap/with [ + read/part rebol-path 1 + ] func [e return] [ + print ["Can't check for embedded code in Rebol path:" rebol-path] + return blank + ] + + unless compressed-data: any [ + elf-format/get-embedding rebol-path + | + pe-format/get-embedding rebol-path + | + generic-format/get-embedding rebol-path + ][ + return blank + ] + + switch compressed-data/1 [ + 0 [ + return decompress next compressed-data + ] + 1 [ + block: copy [] + unzip/quiet block next compressed-data + return block + ] + ] else [ + fail ["Unknown embedding signature byte:" compressed-data/1] + ] +] diff --git a/src/os/generic/host-gob.c b/src/os/generic/host-gob.c new file mode 100644 index 0000000000..0c744e423a --- /dev/null +++ b/src/os/generic/host-gob.c @@ -0,0 +1,76 @@ +// +// File: %host-gob.c +// Summary: "GOB Hostkit Facilities" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// @HostileFork doesn't particularly like the way GOB! is done, +// and feels it's an instance of a more general need for external +// types that participate in Rebol's type system and garbage +// collector. For now these routines are kept together here. +// + +#ifndef __cplusplus + // See feature_test_macros(7) + // This definition is redundant under C++ + #define _GNU_SOURCE +#endif + +#include +#include +#include +#include /* Obtain O_* constant definitions */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "reb-host.h" + + +#ifndef REB_CORE +REBSER* Gob_To_Image(REBGOB *gob); +#endif + +// +// OS_GOB_To_Image: C +// +// Render a GOB into an image. Returns an image or zero if +// it cannot be done. +// +REBVAL *OS_GOB_To_Image(REBGOB *gob) +{ +#if (defined REB_CORE) + UNUSED(gob); + return 0; +#else + return Gob_To_Image(gob); +#endif +} diff --git a/src/os/generic/host-memory.c b/src/os/generic/host-memory.c new file mode 100644 index 0000000000..588380ce0b --- /dev/null +++ b/src/os/generic/host-memory.c @@ -0,0 +1,93 @@ +// +// File: %host-memory.c +// Summary: "Host Memory Allocator" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// See notes about OS_ALLOC and OS_FREE in make-os-ext.r +// + +#include +#include + +#include "reb-host.h" + + +// +// OS_Alloc_Mem: C +// +// Allocate memory of given size. +// +// This is necessary because some environments may use their +// own specific memory allocation (e.g. private heaps). +// +void *OS_Alloc_Mem(size_t size) +{ +#ifdef NDEBUG + return malloc(size); +#else + { + // We skew the return pointer so we don't return exactly at + // the malloc point, to prevent free() from being used directly + // on an address acquired from OS_Alloc_Mem. And because + // Rebol Core uses the same trick (but stores a size), we + // write a known garbage value into that size to warn you that + // you are FREE()ing something you should OS_FREE(). + // + // A 64-bit size is used in order to maintain a 64-bit alignment + // (potentially a lesser alignment guarantee than malloc()) + + // (If you copy this code, choose another "magic number".) + + void *ptr = malloc(size + sizeof(REBI64)); + *cast(REBI64 *, ptr) = -1020; + return cast(char *, ptr) + sizeof(REBI64); + } +#endif +} + + +// +// OS_Free_Mem: C +// +// Free memory allocated in this OS environment. (See OS_Alloc_Mem) +// +void OS_Free_Mem(void *mem) +{ +#ifdef NDEBUG + free(mem); +#else + { + char *ptr = cast(char *, mem) - sizeof(REBI64); + if (*cast(REBI64 *, ptr) != -1020) { + OS_CRASH( + cb_cast("OS_Free_Mem() mismatched with allocator!"), + cb_cast("Did you mean to use FREE() instead of OS_FREE()?") + ); + } + free(ptr); + } +#endif +} diff --git a/src/os/host-args.c b/src/os/host-args.c deleted file mode 100644 index 34161a46fb..0000000000 --- a/src/os/host-args.c +++ /dev/null @@ -1,283 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Command line argument processing -** Author: Carl Sassenrath -** Caution: OS independent -** Purpose: -** Parses command line arguments and options, storing them -** in a structure to be used by the REBOL library. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ - -#include -#include - -#include "reb-config.h" -#include "reb-c.h" -#include "reb-args.h" - -#define ARG_BUF_SIZE 1024 - -extern int OS_Get_Current_Dir(REBCHR **lp); - -// REBOL Option --Words: - -const struct {const char *word; const int flag;} arg_words[] = { - // Keep in Alpha order! - {"args", RO_ARGS | RO_EXT}, - {"boot", RO_BOOT | RO_EXT}, - {"cgi", RO_CGI | RO_QUIET}, - {"debug", RO_DEBUG | RO_EXT}, - {"do", RO_DO | RO_EXT}, - {"halt", RO_HALT}, - {"help", RO_HELP}, - {"import", RO_IMPORT | RO_EXT}, - {"quiet", RO_QUIET}, - {"script", RO_SCRIPT | RO_EXT}, - {"secure", RO_SECURE | RO_EXT}, - {"trace", RO_TRACE}, - {"verbose", RO_VERBOSE}, - {"version", RO_VERSION | RO_EXT}, - {"", 0}, -}; - -// REBOL Option -Characters (in alpha sorted order): - -const struct arg_chr {const char cflg; const int flag;} arg_chars[] = { - {'?', RO_HELP}, - {'V', RO_VERS}, - {'b', RO_BOOT | RO_EXT}, - {'c', RO_CGI | RO_QUIET}, - {'h', RO_HALT}, - {'q', RO_QUIET}, - {'s', RO_SECURE_MIN}, - {'t', RO_TRACE}, - {'v', RO_VERS}, - {'w', RO_NO_WINDOW}, - {'\0', 0}, -}; - -// REBOL Option +Characters: - -const struct arg_chr arg_chars2[] = { - {'s', RO_SECURE_MAX}, - {'\0', 0}, -}; - - -/*********************************************************************** -** -*/ static int find_option_word(REBCHR *word) -/* -** Scan options, return flag bits, else zero. -** -***********************************************************************/ -{ - int n; - int i; - char buf[16]; - - // Some shells will pass us the line terminator. Ignore it. - if (word[0] == '\r' || word[0] == '\n') return RO_IGNORE; - - FROM_OS_STR(buf, word, 15); - - for (i = 0; arg_words[i].flag; i++) { - n = strncmp(buf, arg_words[i].word, 15); // correct (bytes) - if (n < 0) break; - if (n == 0) return arg_words[i].flag; - } - return 0; -} - - -/*********************************************************************** -** -*/ static int find_option_char(REBCHR chr, const struct arg_chr list[]) -/* -** Scan option char flags, return flag bits, else zero. -** -***********************************************************************/ -{ - int i; - - // Some shells will pass us the line terminator. Ignore it. - if (chr == '\r' || chr == '\n') return RO_IGNORE; - - for (i = 0; list[i].flag; i++) { - if (chr < list[i].cflg) break; - if (chr == list[i].cflg) return list[i].flag; - } - return 0; -} - - -/*********************************************************************** -** -*/ static int Get_Ext_Arg(int flag, REBARGS *rargs, REBCHR *arg) -/* -** Get extended argument field. -** -***********************************************************************/ -{ - if (arg && arg[1] == (REBCHR)'-') return flag; - - flag &= ~RO_EXT; - - switch (flag) { - - case RO_VERSION: - rargs->version = arg; - break; - - case RO_SCRIPT: - rargs->script = arg; - break; - - case RO_ARGS: - rargs->args = arg; - break; - - case RO_DO: - rargs->do_arg = arg; - break; - - case RO_DEBUG: - rargs->debug = arg; - break; - - case RO_SECURE: - rargs->secure = arg; - break; - - case RO_IMPORT: - rargs->import = arg; - break; - - case RO_BOOT: - rargs->boot = arg; - break; - } - - return flag; -} - - -/*********************************************************************** -** -*/ void Parse_Args(int argc, REBCHR **argv, REBARGS *rargs) -/* -** Parse REBOL's command line arguments, setting options -** and values in the provided args structure. -** -***********************************************************************/ -{ - REBCHR *arg; - REBCHR *args = 0; // holds trailing args - int flag; - int i; - - CLEARS(rargs); - - // First arg is path to execuable (on most systems): - if (argc > 0) rargs->exe_path = *argv; - - OS_Get_Current_Dir(&rargs->home_dir); - - // Parse each argument: - for (i = 1; i < argc ; i++) { - arg = argv[i]; - if (arg == 0) continue; // shell bug - if (*arg == '-') { - if (arg[1] == '-') { - // --option words - flag = find_option_word(arg+2); - if (flag & RO_EXT) { - flag = Get_Ext_Arg(flag, rargs, (i+1 >= argc) ? 0 : argv[i+1]); - if ((flag & RO_EXT) == 0) i++; // used it - else flag &= ~RO_EXT; - } - if (!flag) flag = RO_HELP; - rargs->options |= flag; - } - else { - // -x option chars - while (*++arg) { - flag = find_option_char(*arg, arg_chars); - if (flag & RO_EXT) { - flag = Get_Ext_Arg(flag, rargs, (i+1 >= argc) ? 0 : argv[i+1]); - if ((flag & RO_EXT) == 0) i++; // used it - else flag &= ~RO_EXT; - } - if (!flag) flag = RO_HELP; - rargs->options |= flag; - } - } - } - else if (*arg == '+') { - // +x option chars - while (*++arg) { - flag = find_option_char(*arg, arg_chars2); - if (flag & RO_EXT) { - flag = Get_Ext_Arg(flag, rargs, (i+1 >= argc) ? 0 : argv[i+1]); - if ((flag & RO_EXT) == 0) i++; // used it - else flag &= ~RO_EXT; - } - if (!flag) flag = RO_HELP; - rargs->options |= flag; - } - } - else { - // script filename - if (!rargs->script) - rargs->script = arg; - else { - int len; - if (!args) { - args = MAKE_STR(ARG_BUF_SIZE); - args[0] = 0; - } - len = ARG_BUF_SIZE - LEN_STR(args) - 2; // space remaining - JOIN_STR(args, arg, len); - JOIN_STR(args, TXT(" "), 1); - } - } - } - - if (args) { - args[LEN_STR(args)-1] = 0; // remove trailing space - Get_Ext_Arg(RO_ARGS, rargs, args); - } -} - - - diff --git a/src/os/host-console.r b/src/os/host-console.r new file mode 100644 index 0000000000..6f1e047ccb --- /dev/null +++ b/src/os/host-console.r @@ -0,0 +1,378 @@ +REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Host Console (Rebol's Read-Eval-Print-Loop, ie. REPL)" + Rights: { + Copyright 2016-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Description: { + This implements a simple interactive command line. It gathers strings + to be executed but does not actually run them with DO--instead it + returns a block to C code which does the actual execution. The + reason it is done this way is to avoid having Rebol CONSOLE stack frames + hanging around when debug commands are executed (e.g. one does not + want BACKTRACE to see a FOREVER [] loop of gathering input, or the DO) + + Though not implemented in C as the R3-Alpha CONSOLE was, it still relies + upon INPUT to receive lines. INPUT reads lines from the "console + port", which is C code that is linked to STDTERM on POSIX and the + Win32 Console API on Windows. Thus, the ability to control the cursor + and use it to page through line history is still a "black box" at + that layer. + } +] + + +; The ECHO routine has to collaborate specifically with the console, because +; it is often desirable to capture the input only, the output only, or both. +; +; !!! The features that tie the echo specifically to the console would be +; things like ECHO INPUT, e.g.: +; +; https://github.com/red/red/issues/2487 +; +; They are not implemented yet, but ECHO is moved here to signify the known +; issue that the CONSOLE must collaborate specifically with ECHO to achieve +; this. +; +echo: procedure [ + {Copies console I/O to a file.} + + 'instruction [file! string! block! word!] + {File or template with * substitution, or command: [ON OFF RESET].} + + + target ([%echo * %.txt]) + form-target + sub ("") + old-input (copy :input) + old-write-stdout (copy :write-stdout) + hook-in + hook-out + logger + ensure-echo-on + ensure-echo-off +][ + ; Sample "interesting" feature, be willing to form the filename by filling + ; in the blank with a substitute string you can change. + ; + form-target: default [func [return: [file!]] [ + either block? target [ + as file! unspaced replace (copy target) '* ( + either empty? sub [[]] [unspaced ["-" sub]] + ) + ][ + target + ] + ]] + + logger: default [func [value][ + write/append form-target either char? value [to-string value][value] + value + ]] + + ; Installed hook; in an ideal world, WRITE-STDOUT would not exist and + ; would just be WRITE, so this would be hooking WRITE and checking for + ; STDOUT or falling through. Note WRITE doesn't take CHAR! right now. + ; + hook-out: default [proc [ + value [string! char! binary!] + {Text to write, if a STRING! or CHAR! is converted to OS format} + ][ + old-write-stdout value + logger value + ]] + + ; It looks a bit strange to look at a console log without the input + ; being included too. Note that hooking the input function doesn't get + ; the newlines, has to be added. + ; + hook-in: default [ + chain [ + :old-input + | + func [value] [ + logger value + logger newline + value ;-- hook still needs to return the original value + ] + ] + ] + + ensure-echo-on: default [does [ + ; + ; Hijacking is a NO-OP if the functions are the same. + ; (this is indicated by a BLANK! return vs a FUNCTION!) + ; + hijack 'write-stdout 'hook-out + hijack 'input 'hook-in + ]] + + ensure-echo-off: default [does [ + ; + ; Restoring a hijacked function with its original will + ; remove any overhead and be as fast as it was originally. + ; + hijack 'write-stdout 'old-write-stdout + hijack 'input 'old-input + ]] + + case [ + word? instruction [ + switch instruction [ + on [ensure-echo-on] + off [ensure-echo-off] + reset [ + delete form-target + write/append form-target "" ;-- or just have it not exist? + ] + ] else [ + word: to-uppercase word + fail [ + "Unknown ECHO command, not [ON OFF RESET]" + | + unspaced ["Use ECHO (" word ") to force evaluation"] + ] + ] + ] + + string? instruction [ + sub: instruction + ensure-echo-on + ] + + any [block? instruction | file? instruction] [ + target: instruction + ensure-echo-on + ] + ] +] + + +host-console: function [ + {Implements one Print-and-Read step of a Read-Eval-Print-Loop (REPL).} + + return: [block! error!] + {Code to run or syntax error in the string input that tried to LOAD} + + last-result [ any-value!] + {The result from the last time HOST-CONSOLE ran to display (if any)} + + last-failed [logic!] + {TRUE if the last-result is an ERROR! that FAILed vs just a result} + + focus-level [blank! integer!] + {If at a breakpoint, the integer index of how deep the stack was} + + focus-frame [blank! frame!] + {If at a breakpoint, the function frame where the breakpoint was hit} + + + + RE_SCAN_INVALID (2000) + RE_SCAN_MISSING (2001) + RE_SCAN_EXTRA (2002) + RE_SCAN_MISMATCH (2003) +][ + ; CONSOLE is an external object for skinning the behaviour & appearance + ; + ; /cycle - updates internal counter and print greeting on first rotation (ie. once) + ; + repl: system/console + repl/cycle + + source: copy {} ;-- source code potentially built of multiple lines + + ; The LOADed and bound code. It's initialized to empty block so that if + ; there is no input text (just newline at a prompt) , it will be treated + ; as DO []. + ; + code: copy [] + + ; Output the last evaluation result if there was one. MOLD it unless it + ; was an actual error that FAILed. + ; + case [ + not set? 'last-result [ + ; Do nothing + ] + + last-failed [ + assert [error? :last-result] + repl/print-error last-result + + unless system/state/last-error [ + repl/print-info "Note: use WHY for more error information" + ] + + system/state/last-error: last-result + ] + ] else [ + repl/last-result: mold :last-result + repl/print-result + ] + + repl/print-gap + + ; If a debug frame is in focus then show it in the prompt, e.g. + ; as `if:|4|>>` to indicate stack frame 4 is being examined, and + ; it was an `if` statement...so it will be used for binding (you + ; can examine the condition and branch for instance) + ; + if focus-frame [ + if label-of focus-frame [ + print/only [label-of focus-frame ":"] + ] + + print/only ["|" focus-level "|"] + ] + + repl/print-prompt + + forever [ ;-- gather potentially multi-line input + + line: repl/input-hook input ;-- pre-processor hook + if empty? line [ + ; + ; if empty line, result is whatever's in `code`, even ERROR! + ; + break + ] + + append source line + + trap/with [ + ; + ; Note that LOAD/ALL makes BLOCK! even for a single item, + ; e.g. `load/all "word"` => `[word]` + ; + code: load/all source + assert [block? code] + + ] func [error] [ + ; + ; If loading the string gave back an error, check to see if it + ; was the kind of error that comes from having partial input + ; (RE_SCAN_MISSING). If so, CONTINUE and read more data until + ; it's complete (or until an empty line signals to just report + ; the error as-is) + ; + ; Save the error even if it's a "needs continuation" error, in + ; case the next input is an empty line. That makes the error get + ; reported, stopping people from getting trapped in a loop. + ; + ; !!! Note that this is a bit unnatural, but it's similar to what + ; Ren Garden does (though it takes two lines). It should not be + ; applied to input that is pasted as text from another source, + ; and arguably this could be disruptive to multi-line strings even + ; if being entered in the CONSOLE + ; + code: error + + if error/code = RE_SCAN_MISSING [ + ; + ; !!! Error message tells you what's missing, not what's open + ; and needs to be closed. Invert the symbol. + ; + unclosed: switch error/arg1 [ + "}" ["{"] + ")" ["("] + "]" ["["] + ] + + if set? 'unclosed [ + print/only [unclosed space space space] + append source newline + continue + ] else [ + ; + ; Could be an unclosed double quote (unclosed tag?) which + ; more input on a new line cannot legally close ATM + ; + ] + ] + ] + + break ;-- Exit FOREVER if no additional input to be gathered + ] + + if not error? code [ + assert [block? code] + + ; If we're focused on a debug frame, try binding into it + ; + if focus-frame [ + bind code focus-frame + ] + + ; There is a question of how it should be decided whether the code + ; in the CONSOLE should be locked as read-only or not. It may be a + ; configuration switch, as it also may be an option for a module or + ; a special type of function which does not lock its source. + ; + lock code + + if all [1 = length-of code | shortcut: select repl/shortcuts code/1] [ + ; + ; One word shortcuts. Built-ins are: + ; + ; q => quit + ; + if all [bound? code/1 | set? code/1] [ + ; + ; Help confused user who might not know about the shortcut not + ; panic by giving them a message. Reduce noise for the casual + ; shortcut by only doing so a bound variable exists. + ; + repl/print-warning [ + (uppercase to-string code/1) + "interpreted by console as:" form shortcut + ] + repl/print-warning [ + "use" form to-get-word code/1 "to get variable." + ] + ] + code: shortcut + ] + ] + + code: repl/dialect-hook code + return code +] + + +why: procedure [ + "Explain the last error in more detail." + 'err [ word! path! error! blank!] "Optional error value" +][ + case [ + not set? 'err [err: _] + word? err [err: get err] + path? err [err: get err] + ] + + either all [ + error? err: any [:err system/state/last-error] + err/type ; avoids lower level error types (like halt) + ][ + say-browser + err: lowercase unspaced [err/type #"-" err/id] + browse join-of http://www.rebol.com/r3/docs/errors/ [err ".html"] + ][ + print "No information is available." + ] +] + + +upgrade: procedure [ + "Check for newer versions." +][ + ; Should this be a console-detected command, like Q, or is it meaningful + ; to define this as a function you could call from code? + ; + do +] diff --git a/src/os/host-device.c b/src/os/host-device.c index 457a5f3bf4..51934a0982 100644 --- a/src/os/host-device.c +++ b/src/os/host-device.c @@ -1,67 +1,60 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Device management and command dispatch -** Author: Carl Sassenrath -** Caution: OS independent -** Purpose: -** This module implements a device management system for -** REBOL devices and tracking their I/O requests. -** It is intentionally kept very simple (makes debugging easy!) -** -** Special note: -** This module is parsed for function declarations used to -** build prototypes, tables, and other definitions. To change -** function arguments requires a rebuild of the REBOL library. -** -** Design comments: -** 1. Not a lot of devices are needed (dozens, not hundreds). -** 2. Devices are referenced by integer (index into device table). -** 3. A single device can support multiple requests. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ +// +// File: %host-device.c +// Summary: "Device management and command dispatch" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// OS independent +// +// This module is parsed for function declarations used to +// build prototypes, tables, and other definitions. To change +// function arguments requires a rebuild of the REBOL library. +// +// This module implements a device management system for +// REBOL devices and tracking their I/O requests. +// It is intentionally kept very simple (makes debugging easy!) +// +// 1. Not a lot of devices are needed (dozens, not hundreds). +// 2. Devices are referenced by integer (index into device table). +// 3. A single device can support multiple requests. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include #include #include "reb-host.h" -#include "host-lib.h" /*********************************************************************** ** -** REBOL Device Table +** REBOL Device Table ** -** The table most be in same order as the RDI_ enums. -** Table is in polling priority order. +** The table most be in same order as the RDI_ enums. +** Table is in polling priority order. ** ***********************************************************************/ @@ -70,414 +63,474 @@ extern REBDEV Dev_File; extern REBDEV Dev_Event; extern REBDEV Dev_Net; extern REBDEV Dev_DNS; -#ifndef MIN_OS + +#ifdef TO_WINDOWS extern REBDEV Dev_Clipboard; #endif +// There should be a better decoupling of these devices so the core +// does not need to know about them... +#if defined(TO_WINDOWS) || defined(TO_LINUX) +extern REBDEV Dev_Serial; +#endif + +#ifdef HAS_POSIX_SIGNAL +extern REBDEV Dev_Signal; +#endif + REBDEV *Devices[RDI_LIMIT] = { - 0, - &Dev_StdIO, - 0, - &Dev_File, - &Dev_Event, - &Dev_Net, - &Dev_DNS, -#ifndef MIN_OS - &Dev_Clipboard, + 0, + &Dev_StdIO, + 0, + &Dev_File, + &Dev_Event, + &Dev_Net, + &Dev_DNS, +#ifdef TO_WINDOWS + &Dev_Clipboard, +#else + 0, +#endif + +#if defined(TO_WINDOWS) || defined(TO_LINUX) + &Dev_Serial, +#else + NULL, +#endif + +#ifdef HAS_POSIX_SIGNAL + &Dev_Signal, #endif - 0, + 0, }; static int Poll_Default(REBDEV *dev) { - // The default polling function for devices. - // Retries pending requests. Return TRUE if status changed. - REBREQ **prior = &dev->pending; - REBREQ *req; - REBOOL change = FALSE; - int result; - - for (req = *prior; req; req = *prior) { - - // Call command again: - if (req->command < RDC_MAX) - result = dev->commands[req->command](req); - else { - result = -1; // invalid command, remove it - req->error = ((REBCNT)-1); - } - - // If done or error, remove command from list: - if (result <= 0) { - *prior = req->next; - req->next = 0; - CLR_FLAG(req->flags, RRF_PENDING); - change = TRUE; - } - else prior = &req->next; - } - - return change; + // The default polling function for devices. + // Retries pending requests. Return TRUE if status changed. + REBREQ **prior = &dev->pending; + REBREQ *req; + REBOOL change = FALSE; + int result; + + for (req = *prior; req; req = *prior) { + + // Call command again: + if (req->command < RDC_MAX) { + CLR_FLAG(req->flags, RRF_ACTIVE); + result = dev->commands[req->command](req); + } else { + result = -1; // invalid command, remove it + req->error = ((REBCNT)-1); + } + + // If done or error, remove command from list: + if (result <= 0) { + *prior = req->next; + req->next = 0; + CLR_FLAG(req->flags, RRF_PENDING); + change = TRUE; + } else { + prior = &req->next; + if (GET_FLAG(req->flags, RRF_ACTIVE)) { + change = TRUE; + } + } + } + + return change ? 1 : 0; } -/*********************************************************************** -** -*/ void Attach_Request(REBREQ **node, REBREQ *req) -/* -** Attach a request to a device's pending or accept list. -** Node is a pointer to the head pointer of the req list. -** -***********************************************************************/ +// +// Attach_Request: C +// +// Attach a request to a device's pending or accept list. +// Node is a pointer to the head pointer of the req list. +// +void Attach_Request(REBREQ **node, REBREQ *req) { - REBREQ *r; + REBREQ *r; #ifdef special_debug - if (req->device == 5) - Debug_Fmt("Attach: %x %x %x %x", req, req->device, req->port, req->next); + if (req->device == 5) { + printf("Attach: %x %x %x %x\n", + req, req->device, req->port, req->next + ); + fflush(stdout); + } #endif - // See if its there, and get last req: - for (r = *node; r; r = *node) { - if (r == req) return; // already in list - node = &r->next; - } + // See if its there, and get last req: + for (r = *node; r; r = *node) { + if (r == req) return; // already in list + node = &r->next; + } - // Link the new request to end: - *node = req; - req->next = 0; - SET_FLAG(req->flags, RRF_PENDING); + // Link the new request to end: + *node = req; + req->next = 0; + SET_FLAG(req->flags, RRF_PENDING); } -/*********************************************************************** -** -*/ void Detach_Request(REBREQ **node, REBREQ *req) -/* -** Detach a request to a device's pending or accept list. -** If it is not in list, then no harm done. -** -***********************************************************************/ +// +// Detach_Request: C +// +// Detach a request to a device's pending or accept list. +// If it is not in list, then no harm done. +// +void Detach_Request(REBREQ **node, REBREQ *req) { - REBREQ *r; + REBREQ *r; #ifdef special_debug - if (req->device == 5) - Debug_Fmt("Detach= n: %x r: %x p: %x %x", *node, req, req->port, &req->next); + if (req->device == 5) { + printf("Detach= n: %x r: %x p: %x %x\n", + *node, req, req->port, &req->next); + fflush(stdout); + } #endif - // See if its there, and get last req: - for (r = *node; r; r = *node) { + // See if its there, and get last req: + for (r = *node; r; r = *node) { #ifdef special_debug - if (req->device == 5) - Debug_Fmt("Detach: r: %x n: %x", r, r->next); + if (req->device == 5) { + printf("Detach: r: %x n: %x\n", r, r->next); + fflush(stdout); + } #endif - if (r == req) { - *node = req->next; - req->next = 0; - CLR_FLAG(req->flags, RRF_PENDING); - return; - } - node = &r->next; - } + if (r == req) { + *node = req->next; + req->next = 0; + CLR_FLAG(req->flags, RRF_PENDING); + return; + } + node = &r->next; + } } -/*********************************************************************** -** -*/ void Done_Device(int handle, int error) -/* -** Given a handle mark the related request as done. -** (Used by DNS device). -** -***********************************************************************/ +extern void Done_Device(REBUPT handle, int error); + +// +// Done_Device: C +// +// Given a handle mark the related request as done. +// (Used by DNS device). +// +void Done_Device(REBUPT handle, int error) { - REBINT d; - REBDEV *dev; - REBREQ **prior; - REBREQ *req; - - for (d = RDI_NET; d <= RDI_DNS; d++) { - dev = Devices[d]; - prior = &dev->pending; - // Scan the pending requests, mark the one we got: - for (req = *prior; req; req = *prior) { - if ((int)(req->handle) == handle) { - req->error = error; // zero when no error - SET_FLAG(req->flags, RRF_DONE); - return; - } - prior = &req->next; - } - } + REBINT d; + REBDEV *dev; + REBREQ **prior; + REBREQ *req; + + for (d = RDI_NET; d <= RDI_DNS; d++) { + dev = Devices[d]; + prior = &dev->pending; + // Scan the pending requests, mark the one we got: + for (req = *prior; req; req = *prior) { + if (cast(REBUPT, req->requestee.handle) == handle) { + req->error = error; // zero when no error + SET_FLAG(req->flags, RRF_DONE); + return; + } + prior = &req->next; + } + } } -/*********************************************************************** -** -*/ void Signal_Device(REBREQ *req, REBINT type) -/* -** Generate a device event to awake a port on REBOL. -** -***********************************************************************/ +// +// Signal_Device: C +// +// Generate a device event to awake a port on REBOL. +// +void Signal_Device(REBREQ *req, REBINT type) { - REBEVT evt; + REBEVT evt; - CLEARS(&evt); + CLEARS(&evt); - evt.type = (REBYTE)type; - evt.model = EVM_DEVICE; - evt.req = req; - if (type == EVT_ERROR) evt.data = req->error; + evt.type = (REBYTE)type; + evt.model = EVM_DEVICE; + evt.eventee.req = req; + if (type == EVT_ERROR) evt.data = req->error; - RL_Event(&evt); // (returns 0 if queue is full, ignored) + RL_Event(&evt); // (returns 0 if queue is full, ignored) } -/*********************************************************************** -** -*/ int OS_Call_Device(REBINT device, REBCNT command) -/* -** Shortcut for non-request calls to device. -** -** Init - Initialize any device-related resources (e.g. libs). -** Quit - Cleanup any device-related resources. -** Make - Create and initialize a request for a device. -** Free - Free a device request structure. -** Poll - Poll device for activity. -** -***********************************************************************/ +// +// OS_Call_Device: C +// +// Shortcut for non-request calls to device. +// +// Init - Initialize any device-related resources (e.g. libs). +// Quit - Cleanup any device-related resources. +// Make - Create and initialize a request for a device. +// Free - Free a device request structure. +// Poll - Poll device for activity. +// +int OS_Call_Device(REBINT device, REBCNT command) { - REBDEV *dev; - - // Validate device: - if (device >= RDI_MAX || !(dev = Devices[device])) - return -1; + REBDEV *dev; + REBREQ req; + + // Validate device: + if (device >= RDI_MAX || !(dev = Devices[device])) + return -1; + + // Validate command: + if (command > dev->max_command || dev->commands[command] == 0) + return -2; + + // Do command, return result: + /* fake a request, not all fields are set */ + req.device = device; + req.command = command; + return dev->commands[command](&req); +} - // Validate command: - if (command > dev->max_command || dev->commands[command] == 0) - return -2; - // Do command, return result: - return dev->commands[command]((REBREQ*)dev); +// +// OS_Do_Device: C +// +// Tell a device to perform a command. Non-blocking in many +// cases and will attach the request for polling. +// +// Returns: +// =0: for command success +// >0: for command still pending +// <0: for command error +// +int OS_Do_Device(REBREQ *req, REBCNT command) +{ + REBDEV *dev; + REBINT result; + + req->error = 0; // A94 - be sure its cleared + + // Validate device: + if (req->device >= RDI_MAX || !(dev = Devices[req->device])) { + req->error = RDE_NO_DEVICE; + return -1; + } + + // Confirm device is initialized. If not, return an error or init + // it if auto init option is set. + if (!GET_FLAG(dev->flags, RDF_INIT)) { + if (GET_FLAG(dev->flags, RDO_MUST_INIT)) { + req->error = RDE_NO_INIT; + return -1; + } + if (!dev->commands[RDC_INIT] || !dev->commands[RDC_INIT]((REBREQ*)dev)) + SET_FLAG(dev->flags, RDF_INIT); + } + + // Validate command: + if (command > dev->max_command || dev->commands[command] == 0) { + req->error = RDE_NO_COMMAND; + return -1; + } + + // Do the command: + req->command = command; + result = dev->commands[command](req); + + // If request is pending, attach it to device for polling: + if (result > 0) Attach_Request(&dev->pending, req); + else if (dev->pending) { + Detach_Request(&dev->pending, req); // often a no-op + if (result == DR_ERROR && GET_FLAG(req->flags, RRF_ALLOC)) { // not on stack + Signal_Device(req, EVT_ERROR); + } + } + + return result; } -/*********************************************************************** -** -*/ int OS_Do_Device(REBREQ *req, REBCNT command) -/* -** Tell a device to perform a command. Non-blocking in many -** cases and will attach the request for polling. -** -** Returns: -** =0: for command success -** >0: for command still pending -** <0: for command error -** -***********************************************************************/ +// +// OS_Devreq_Size: C +// +// Ask the Devreq size for the device +// +int OS_Devreq_Size(int device) { - REBDEV *dev; - REBINT result; - - req->error = 0; // A94 - be sure its cleared - - // Validate device: - if (req->device >= RDI_MAX || !(dev = Devices[req->device])) { - req->error = RDE_NO_DEVICE; - return -1; - } - - // Confirm device is initialized. If not, return an error or init - // it if auto init option is set. - if (!GET_FLAG(dev->flags, RDF_INIT)) { - if (GET_FLAG(dev->flags, RDO_MUST_INIT)) { - req->error = RDE_NO_INIT; - return -1; - } - if (!dev->commands[RDC_INIT] || !dev->commands[RDC_INIT]((REBREQ*)dev)) - SET_FLAG(dev->flags, RDF_INIT); - } - - // Validate command: - if (command > dev->max_command || dev->commands[command] == 0) { - req->error = RDE_NO_COMMAND; - return -1; - } - - // Do the command: - req->command = command; - result = dev->commands[command](req); - - // If request is pending, attach it to device for polling: - if (result > 0) Attach_Request(&dev->pending, req); - else if (dev->pending) { - Detach_Request(&dev->pending, req); // often a no-op - if (result == DR_ERROR && GET_FLAG(req->flags, RRF_ALLOC)) { // not on stack - Signal_Device(req, EVT_ERROR); - } - } - - return result; + REBDEV *dev; + + // Validate device: + if (device >= RDI_MAX || !(dev = Devices[device])) + return 0; + + return dev->commands[RDC_DEVREQ_SIZE](NULL); } -/*********************************************************************** -** -*/ REBREQ *OS_Make_Devreq(int device) -/* -***********************************************************************/ + +// +// OS_Make_Devreq: C +// +REBREQ *OS_Make_Devreq(int device) { - REBDEV *dev; - REBREQ *req; - int size; - - // Validate device: - if (device >= RDI_MAX || !(dev = Devices[device])) - return 0; - - size = dev->req_size ? dev->req_size : sizeof(REBREQ); - req = OS_Make(size); - CLEARS(req); - SET_FLAG(req->flags, RRF_ALLOC); - req->clen = size; - req->device = device; - - return req; + REBDEV *dev; + + // Validate device: + if (device >= RDI_MAX || !(dev = Devices[device])) + return 0; + + i32 size = OS_Devreq_Size(device); + REBREQ *req = cast (REBREQ *, OS_ALLOC_MEM(size)); + memset(req, 0, size); + SET_FLAG(req->flags, RRF_ALLOC); + req->device = device; + + return req; } -/*********************************************************************** -** -*/ int OS_Abort_Device(REBREQ *req) -/* -** Ask device to abort prior request. -** -***********************************************************************/ +// +// OS_Abort_Device: C +// +// Ask device to abort prior request. +// +int OS_Abort_Device(REBREQ *req) { - REBDEV *dev; + REBDEV *dev; - if ((dev = Devices[req->device]) != 0) Detach_Request(&dev->pending, req); - return 0; + if ((dev = Devices[req->device]) != 0) Detach_Request(&dev->pending, req); + return 0; } -/*********************************************************************** -** -*/ int OS_Poll_Devices(void) -/* -** Poll devices for activity. -** -** Returns count of devices that changed status. -** -** Devices with pending lists will be called to see if -** there is a change in status of those requests. If so, -** those devices are allowed to change the state of those -** requests or call-back into special REBOL functions -** (e.g. Add_Event for GUI) to invoke special actions. -** -***********************************************************************/ +// +// OS_Poll_Devices: C +// +// Poll devices for activity. +// +// Returns count of devices that changed status. +// +// Devices with pending lists will be called to see if +// there is a change in status of those requests. If so, +// those devices are allowed to change the state of those +// requests or call-back into special REBOL functions +// (e.g. Add_Event for GUI) to invoke special actions. +// +int OS_Poll_Devices(void) { - int d; - int cnt = 0; - REBDEV *dev; - //int cc = 0; - - //printf("Polling Devices\n"); - - // Check each device: - for (d = 0; d < RDI_MAX; d++) { - dev = Devices[d]; - if (dev && (dev->pending || GET_FLAG(dev->flags, RDO_AUTO_POLL))) { - // If there is a custom polling function, use it: - if (dev->commands[RDC_POLL]) { - if (dev->commands[RDC_POLL]((REBREQ*)dev)) cnt++; - } - else { - if (Poll_Default(dev)) cnt++; - } - } - //if (cc != cnt) {printf("dev=%s ", dev->title); cc = cnt;} - } - - return cnt; + int d; + int cnt = 0; + REBDEV *dev; + //int cc = 0; + + //printf("Polling Devices\n"); + + // Check each device: + for (d = 0; d < RDI_MAX; d++) { + dev = Devices[d]; + if (dev && (dev->pending || GET_FLAG(dev->flags, RDO_AUTO_POLL))) { + // If there is a custom polling function, use it: + if (dev->commands[RDC_POLL]) { + if (dev->commands[RDC_POLL]((REBREQ*)dev)) cnt++; + } + else { + if (Poll_Default(dev)) cnt++; + } + } + //if (cc != cnt) {printf("dev=%s ", dev->title); cc = cnt;} + } + + return cnt; } -/*********************************************************************** -** -*/ int OS_Quit_Devices(int flags) -/* -** Terminate all devices in preparation to quit. -** -** Allows devices to perform cleanup and resource freeing. -** -** Set flags to zero for now. (May later be used to indicate -** a device query check or a brute force quit.) -** -** Returns: 0 for now. -** -***********************************************************************/ +// +// OS_Quit_Devices: C +// +// Terminate all devices in preparation to quit. +// +// Allows devices to perform cleanup and resource freeing. +// +// Set flags to zero for now. (May later be used to indicate +// a device query check or a brute force quit.) +// +// Returns: 0 for now. +// +int OS_Quit_Devices(int flags) { - int d; - REBDEV *dev; + UNUSED(flags); - for (d = RDI_MAX-1; d >= 0; d--) { - dev = Devices[d]; - if (dev && GET_FLAG(dev->flags, RDF_INIT) && dev->commands[RDC_QUIT]) { - dev->commands[RDC_QUIT]((REBREQ*)dev); - } - } + int d; + for (d = RDI_MAX - 1; d >= 0; d--) { + REBDEV *dev = Devices[d]; + if (dev && GET_FLAG(dev->flags, RDF_INIT) && dev->commands[RDC_QUIT]) { + dev->commands[RDC_QUIT](cast(REBREQ*, dev)); + } + } - return 0; + return 0; } -/*********************************************************************** -** -*/ REBINT OS_Wait(REBCNT millisec, REBCNT res) -/* -** Check if devices need attention, and if not, then wait. -** The wait can be interrupted by a GUI event, otherwise -** the timeout will wake it. -** -** Res specifies resolution. (No wait if less than this.) -** -** Returns: -** -1: Devices have changed state. -** 0: past given millsecs -** 1: wait in timer -** -** The time it takes for the devices to be scanned is -** subtracted from the timer value. -** -***********************************************************************/ +// +// OS_Wait: C +// +// Check if devices need attention, and if not, then wait. +// The wait can be interrupted by a GUI event, otherwise +// the timeout will wake it. +// +// Res specifies resolution. (No wait if less than this.) +// +// Returns: +// -1: Devices have changed state. +// 0: past given millsecs +// 1: wait in timer +// +// The time it takes for the devices to be scanned is +// subtracted from the timer value. +// +REBINT OS_Wait(REBCNT millisec, REBCNT res) { - REBREQ req; // OK: QUERY below does not store it - REBCNT delta; - i64 base; + REBREQ req; // OK: QUERY below does not store it + REBCNT delta; + i64 base; + + // printf("OS_Wait %d\n", millisec); - // printf("OS_Wait %d\n", millisec); + base = OS_Delta_Time(0, 0); // start timing - base = OS_Delta_Time(0, 0); // start timing + // Setup for timing: + CLEARS(&req); + req.device = RDI_EVENT; - // Setup for timing: - CLEARS(&req); - req.device = RDI_EVENT; + OS_Reap_Process(-1, NULL, 0); - // Let any pending device I/O have a chance to run: - if (OS_Poll_Devices()) return -1; + // Let any pending device I/O have a chance to run: + if (OS_Poll_Devices()) return -1; - // Nothing, so wait for period of time - delta = (REBCNT)OS_Delta_Time(base, 0)/1000 + res; - if (delta >= millisec) return 0; - millisec -= delta; // account for time lost above - req.length = millisec; + // Nothing, so wait for period of time + delta = (REBCNT)OS_Delta_Time(base, 0)/1000 + res; + if (delta >= millisec) return 0; + millisec -= delta; // account for time lost above + req.length = millisec; - // printf("Wait: %d ms\n", millisec); - OS_Do_Device(&req, RDC_QUERY); // wait for timer or other event + // printf("Wait: %d ms\n", millisec); + OS_Do_Device(&req, RDC_QUERY); // wait for timer or other event - return 1; // layer above should check delta again + return 1; // layer above should check delta again +} + + + +// +// Request_Size_Rebreq: C +// +i32 Request_Size_Rebreq(REBREQ *req) +{ + UNUSED(req); + return sizeof(REBREQ); //no special fields } diff --git a/src/os/host-ext-test.c b/src/os/host-ext-test.c deleted file mode 100644 index 1888ab2236..0000000000 --- a/src/os/host-ext-test.c +++ /dev/null @@ -1,234 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Test for Embedded Extension Modules -** Author: Carl Sassenrath -** Purpose: -** Provides test code for extensions that can be easily -** built and run in the host-kit. Not part of release, -** but can be used as an example. -** See: http://www.rebol.com/r3/docs/concepts/extensions-embedded.html -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ - -#include -#include -#include - -#include "reb-host.h" -#include "host-lib.h" - -RL_LIB *RL; // Link back to reb-lib from embedded extensions - -char *RX_Spec = - "REBOL [\n" - "Title: {Test of Embedded Extension}\n" - "Name: ext-test\n" - "Type: module\n" - "Options: [boot extension]\n" - "Exports: [xtest]\n" - "]\n" - - "xarg0: command [{return zero}]\n" - "xarg1: command [{return first arg} arg]\n" - "xarg2: command [{return second arg} arg1 arg2]\n" - "xword0: command [{return system word from internal string}]\n" - "xword1: command [{return word from string} str [string!]]\n" - "xobj1: command [{return obj field value} obj [object!] field [word! lit-word!]]\n" - "calls: command [{test sync callback} context [object!] word [word!]]\n" - "calla: command [{test async callback} context [object!] word [word!]]\n" - "img0: command [{return 10x20 image}]\n" - "cec0: command [{test command context struct} blk [block!]]\n" - "cec1: command [{returns cec.index value or -1 if no cec}]\n" - - "a: b: c: none\n" - "xtest: does [\n" - "foreach blk [\n" - "[xarg0]\n" - "[xarg1 111]\n" - "[xarg1 1.1]\n" - "[xarg1 {test}]\n" - "[xarg1 [1 2 3]]\n" - "[xarg1 10-Sep-2010]\n" - "[xarg2 111 222]\n" - "[xword0]\n" - "[xword1 {system}]\n" - "[xobj1 system 'version]\n" - - // We just use this context as example. Normally, it would be - // your own object that has your special functions within it. - "[calls lib 'negate]\n" - "[calls lib 'sine]\n" - "[calla lib 'print]\n" - "[img0]\n" - "[c: do-commands [a: xarg0 b: xarg1 333 xobj1 system 'version] reduce [a b c]]\n" - "[cec0 [a: cec1 b: cec1 c: cec1] reduce [a b c]]\n" - "][\n" - "print [{test:} mold blk]\n" - "prin { } \n" - //"replace {x} {x} {y}\n" - "probe do blk\n" - "]\n" - "wait 0.1 ; let async events happen\n" - "exit\n" - "]\n" - //"print {^/Loaded resident extension: Test - type xtest^/^/}\n" -; - - -REBCNT Test_Sync_Callback(REBSER *obj, REBCNT word, RXIARG *result) -{ - RXICBI cbi; - RXIARG args[4]; - REBCNT n; - - // These can be on the stack, because it's synchronous. - CLEAR(&cbi, sizeof(cbi)); - CLEAR(&args[0], sizeof(args)); - cbi.obj = obj; - cbi.word = word; - cbi.args = args; - - // Pass a single integer arg to the callback function: - RXI_COUNT(args) = 1; - RXI_TYPE(args, 1) = RXT_INTEGER; - - args[1].int64 = 123; - - n = RL_CALLBACK(&cbi); - - *result = cbi.result; - return n; -} - - -REBCNT Test_Async_Callback(REBSER *obj, REBCNT word) -{ - RXICBI *cbi; - RXIARG *args; - REBCNT n; - - // These cannot be on the stack, because they are used - // when the callback happens later. - cbi = MAKE_NEW(*cbi); - CLEAR(cbi, sizeof(cbi)); - args = MAKE_MEM(sizeof(RXIARG) * 4); - CLEAR(args, sizeof(RXIARG) * 4); - cbi->obj = obj; - cbi->word = word; - cbi->args = args; - SET_FLAG(cbi->flags, RXC_ASYNC); - - // Pass a single integer arg to the callback function: - RXI_COUNT(args) = 1; - RXI_TYPE(args, 1) = RXT_INTEGER; - - args[1].int64 = 123; - - n = RL_CALLBACK(cbi); // result is in cbi struct, if wanted - - return n; -} - - -RXIEXT int RX_Call(int cmd, RXIFRM *frm, REBCEC *ctx) { - REBYTE *str; - - switch (cmd) { - - case 0: - RXA_INT64(frm, 1) = 0; - RXA_TYPE(frm, 1) = RXT_INTEGER; - break; - - case 1: - break; // same as arg - - case 2: - RXA_INT64(frm, 1) = RXA_INT64(frm, 2); - RXA_TYPE(frm, 1) = RXA_TYPE(frm, 2); - break; - - case 3: - RXA_WORD(frm, 1) = RL_MAP_WORD("system"); //?? is frame always long enough?? - RXA_TYPE(frm, 1) = RXT_WORD; - break; - - case 4: - RL_GET_STRING(RXA_SERIES(frm, 1), 0, (void*)(&str)); // latin-1 only for test - RXA_WORD(frm, 1) = RL_MAP_WORD(str); - RXA_TYPE(frm, 1) = RXT_WORD; - break; - - case 5: - RXA_TYPE(frm, 1) = RL_GET_FIELD(RXA_OBJECT(frm, 1), RXA_WORD(frm, 2), &RXA_ARG(frm, 1)); - break; - - case 6: - RXA_TYPE(frm, 1) = Test_Sync_Callback(RXA_OBJECT(frm, 1), RXA_WORD(frm, 2), &RXA_ARG(frm, 1)); - break; - - case 7: - RXA_LOGIC(frm, 1) = Test_Async_Callback(RXA_OBJECT(frm, 1), RXA_WORD(frm, 2)); - RXA_TYPE(frm, 1) = RXT_LOGIC; - break; - - case 8: - RXA_TYPE(frm, 1) = RXT_IMAGE; - RXA_SERIES(frm, 1) = RL_MAKE_IMAGE(2, 3); - break; - - case 9: - { - REBCEC cec; - cec.envr = 0; - cec.block = RXA_SERIES(frm, 1); - cec.index = 0; - RL_DO_COMMANDS(RXA_SERIES(frm, 1), 0, &cec); - } - return RXR_UNSET; - - case 10: - RXA_INT64(frm, 1) = (i64)(ctx ? ctx->index : -1); - RXA_TYPE(frm, 1) = RXT_INTEGER; - break; - - default: - return RXR_NO_COMMAND; - } - return RXR_VALUE; -} - - -void Init_Ext_Test(void) -{ - RL = RL_Extend(&RX_Spec[0], &RX_Call); -} diff --git a/src/os/host-main.c b/src/os/host-main.c index 501aaa91c0..f656f961b3 100644 --- a/src/os/host-main.c +++ b/src/os/host-main.c @@ -1,198 +1,1083 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Host environment main entry point -** Note: OS independent -** Author: Carl Sassenrath -** Purpose: -** Provides the outer environment that calls the REBOL lib. -** This module is more or less just an example and includes -** a very simple console prompt. -** -************************************************************************ -** -** WARNING to PROGRAMMERS: -** -** This open source code is strictly managed to maintain -** source consistency according to our standards, not yours. -** -** 1. Keep code clear and simple. -** 2. Document odd code, your reasoning, or gotchas. -** 3. Use our source style for code, indentation, comments, etc. -** 4. It must work on Win32, Linux, OS X, BSD, big/little endian. -** 5. Test your code really well before submitting it. -** -***********************************************************************/ +// +// File: %host-main.c +// Summary: "Host environment main entry point" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// %host-main.c is the original entry point for the open-sourced R3-Alpha. +// Depending on whether it was POSIX or Windows, it would define either a +// `main()` or `WinMain()`, and implemented a very rudimentary console. +// +// On POSIX systems it uses to implement line editing: +// +// http://pubs.opengroup.org/onlinepubs/7908799/xbd/termios.html +// +// On Windows it uses the Console API: +// +// https://msdn.microsoft.com/en-us/library/ms682087.aspx +// +// !!! Originally %host-main.c was a client of the %reb-host.h (RL_Api). It +// did not have access to things like the definition of a REBVAL or a REBSER. +// The sparse and convoluted nature of the RL_Api presented an awkward +// barrier, and the "sample console" stagnated as a result. +// +// In lieu of a suitable "abstracted" variant of the core services--be that +// an evolution of RL_Api or otherwise--the console now links directly +// against the Ren-C core. This provides full access to the routines and +// hooks necessary to evolve the console if one were interested. (The GUI +// inteface Ren Garden is the flagship console for Ren-C, so that is where +// most investment will be made.) +// #include -#include #include +#include #ifdef _WIN32 -#include + // + // On Windows it is required to include , and defining the + // _WIN32_WINNT constant to 0x0501 specifies the minimum targeted version + // is Windows XP. This is the earliest platform API still supported by + // Visual Studio 2015: + // + // https://msdn.microsoft.com/en-us/library/6sehtctf.aspx + // + // R3-Alpha used 0x0500, indicating a minimum target of Windows 2000. No + // Windows-XP-specific dependencies were added in Ren-C, but the version + // was bumped to avoid compilation errors in the common case. + // + // !!! Note that %sys-core.h includes as well if building + // for windows. The redundant inclusion should not create a problem. + // (So better to do the inclusion just to test that it doesn't.) + // + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x0501 + #include + + // Put any dependencies that include here + // + /* #include "..." */ + /* #include "..." */ + + // Undefine the Windows version of IS_ERROR to avoid compiler warning + // when Rebol redefines it. (Rebol defines IS_XXX for all datatypes.) + // + #undef IS_ERROR + #undef max + #undef min +#else + #include // needed for SIGINT, SIGTERM, SIGHUP #endif -#define OS_LIB_TABLE // include the host-lib dispatch table -#include "reb-host.h" // standard host include files -#include "host-lib.h" // OS host library (dispatch table) +#include "sys-core.h" +#include "sys-ext.h" +#include "tmp-boot-extensions.h" -#ifdef CUSTOM_STARTUP -#include "host-init.h" -#endif +EXTERN_C void RL_Version(REBYTE vers[]); +EXTERN_C void RL_Shutdown(REBOOL clean); +EXTERN_C void RL_Escape(); -/**********************************************************************/ +EXTERN_C REBOL_HOST_LIB Host_Lib_Init; -#define PROMPT_STR ">> " -#define RESULT_STR "== " -REBARGS Main_Args; +// The initialization done by RL_Init() is intended to be as basic as possible +// in order to get the Rebol series/values/array functions ready to be run. +// Once that's ready, the rest of the initialization can take advantage of +// a working evaluator. This includes PARSE to process the command line +// parameters, or PRINT to output boot banners. +// +// The %make-host-init.r file takes the %host-start.r script and turns it +// into a compressed binary C literal. That literal can be LOADed and +// executed to return the HOST-START function, which takes the command line +// arguments as an array of STRING! and handles it from there. +// +#include "tmp-host-start.inc" -#ifdef TO_WIN32 -HINSTANCE App_Instance = 0; -#endif + +const REBYTE halt_str[] = "[escape]"; +const REBYTE breakpoint_str[] = + "** Breakpoint Hit (see BACKTRACE, DEBUG, and RESUME)\n"; +const REBYTE interrupted_str[] = + "** Execution Interrupted (see BACKTRACE, DEBUG, and RESUME)\n"; #ifndef REB_CORE -extern void Init_Windows(void); -extern void Init_Graphics(void); +EXTERN_C void Init_Windows(void); +EXTERN_C void OS_Init_Graphics(void); +EXTERN_C void OS_Destroy_Graphics(void); #endif -//#define TEST_EXTENSIONS -#ifdef TEST_EXTENSIONS -extern void Init_Ext_Test(void); // see: host-ext-test.c + +#ifdef TO_WINDOWS + EXTERN_C HINSTANCE App_Instance; + HINSTANCE App_Instance = 0; #endif + // Host bare-bones stdio functs: extern void Open_StdIO(void); -extern void Put_Str(char *buf); -extern REBYTE *Get_Str(); +extern void Close_StdIO(void); +extern void Put_Str(const REBYTE *buf); + + +/* coverity[+kill] */ +void Host_Crash(const char *reason) { + OS_CRASH(cb_cast("REBOL Host Failure"), cb_cast(reason)); +} + + +// Current stack level displayed in the REPL, where bindings are assumed to +// be made for evaluations. So if the prompt reads `[3]>>`, and a string +// of text is typed in to be loaded as code, that code will be bound to +// the user context, then the lib context, then to the variables of whatever +// function is located at stack level 3. +// +extern REBCNT HG_Stack_Level; +REBCNT HG_Stack_Level = 1; + +REBVAL HG_Host_Repl; + + +// The DEBUG command is a host-specific "native", which modifies state that +// is specific to controlling variables and behaviors in the REPL. Since +// the core itself seeks to avoid having any UI and only provide evaluation +// services, C code for DEBUG must either be within the host, or the DEBUG +// native would need to implement an abstract protocol that could make +// callbacks into the host. +// +// A standard or library might evolve so that every host does not reimplement +// the debug logic. However, much of the debugging behavior depends on the +// nature of the host (textual vs. GUI), as well as being able to modify +// state known to the host and not the core. So for the moment, DEBUG is +// implemented entirely in the host...while commands like BREAKPOINT have +// their implementation in the core with a callback to the host to implement +// the host-specific portion. +// +// !!! Can the REBNATIVE with source-in-comment declaration style be +// something that non-core code can use, vs. this handmade variant? +// +const REBYTE N_debug_spec[] = + " {Dialect for interactive debugging, see documentation for details}" + " 'value [_ integer! frame! function! block!]" + " {Stack level to inspect or dialect block, or enter debug mode}" + ""; +REB_R N_debug(REBFRM *frame_) { + PARAM(1, value); // no automatic INCLUDE_PARAMS_OF_XXX for manual native + + REBVAL *value = ARG(value); + + if (IS_VOID(value)) { + // + // e.g. just `>> debug` and [enter] in the console. Ideally this + // would shift the REPL into a mode where all commands issued were + // assumed to be in the debug dialect, similar to Ren Garden's + // modalities like `debug>>`. + // + Debug_Fmt("Sorry, there is no debug>> 'mode' yet in the console."); + goto modify_with_confidence; + } + + if (IS_INTEGER(value) || IS_FRAME(value) || IS_FUNCTION(value)) { + REBFRM *frame; + + // We pass TRUE here to account for an extra stack level... the one + // added by DEBUG itself, which presumably should not count. + // + if (!(frame = Frame_For_Stack_Level(&HG_Stack_Level, value, TRUE))) + fail (value); + + Init_Block(D_OUT, Make_Where_For_Frame(frame)); + return R_OUT; + } + + assert(IS_BLOCK(value)); + + Debug_Fmt( + "Sorry, but the `debug [...]` dialect is not defined yet.\n" + "Change the stack level (integer!, frame!, function!)\n" + "Or try out these commands:\n" + "\n" + " BREAKPOINT, RESUME, BACKTRACE\n" + ); + +modify_with_confidence: + Debug_Fmt( + "(Note: Ren-C is 'modify-with-confidence'...so just because a debug\n" + "feature you want isn't implemented doesn't mean you can't add it!)\n" + ); + + return R_BLANK; +} + + +// +// Do_Code() +// +// This is a version of a routine that was offered by the RL_Api, which has +// been expanded here in order to permit the necessary customizations for +// interesting REPL behavior w.r.t. binding, error handling, and response +// to throws. +// +// !!! Now that this code has been moved into the host, the convoluted +// integer-return-scheme can be eliminated and the code integrated more +// clearly into the surrounding calls. +// +int Do_Code( + int *exit_status, + REBVAL *out, + const REBVAL *code, + REBOOL at_breakpoint +) { + assert(IS_BLOCK(code)); + + struct Reb_State state; + REBCTX *error; + + // Breakpoint REPLs are nested, and we may wish to jump out of them to + // the topmost level via a HALT. However, all other errors need to be + // confined, so that if one is doing evaluations during the pause of + // a breakpoint an error doesn't "accidentally resume" by virtue of + // jumping the stack out of the REPL. + // + // The topmost layer REPL, however, needs to catch halts in order to + // keep control and not crash out. + // + if (at_breakpoint) + PUSH_TRAP(&error, &state); + else + PUSH_UNHALTABLE_TRAP(&error, &state); + +// The first time through the following code 'error' will be NULL, but... +// `fail` can longjmp here, so 'error' won't be NULL *if* that happens! + + if (error) { + if (ERR_NUM(error) == RE_HALT) { + assert(!at_breakpoint); + return -1; // !!! Revisit hardcoded # + } + + Init_Error(out, error); + return -cast(REBINT, ERR_NUM(error)); + } + + if (Do_At_Throws(out, VAL_ARRAY(code), VAL_INDEX(code), SPECIFIED)) { + if (at_breakpoint) { + if ( + IS_FUNCTION(out) + && VAL_FUNC_DISPATCHER(out) == &N_resume + ) { + // + // This means we're done with the embedded REPL. We want to + // resume and may be returning a piece of code that will be + // run by the finishing BREAKPOINT command in the target + // environment. + // + // We'll never return a halt, so we reuse -1 (in this very + // temporary scheme built on the very clunky historical REPL, + // which will not last much longer...fingers crossed.) + // + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + CATCH_THROWN(out, out); + *exit_status = -1; + return -1; + } + + if ( + IS_FUNCTION(out) + && VAL_FUNC_DISPATCHER(out) == &N_quit + ) { + // + // It would be frustrating if the system did not respond to + // a QUIT and forced you to do `resume/with [quit]`. So + // this is *not* caught, rather passed back up with the + // special -2 status code. + // + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + CATCH_THROWN(out, out); + *exit_status = -2; + return -2; + } + } + else { + // We are at the top level REPL, where we catch QUIT + // + if ( + IS_FUNCTION(out) + && VAL_FUNC_DISPATCHER(out) == &N_quit + ) { + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + CATCH_THROWN(out, out); + *exit_status = Exit_Status_From_Value(out); + return -2; // Revisit hardcoded # + } + } + + fail (Error_No_Catch_For_Throw(out)); + } + + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + + return 0; +} + + +void Host_Repl( + int *exit_status, + REBVAL *out, + REBOOL at_breakpoint +) { + REBOOL last_failed = FALSE; + Init_Void(out); + + DECLARE_LOCAL (level); + DECLARE_LOCAL (frame); + Init_Blank(level); + Init_Blank(frame); + + PUSH_GUARD_VALUE(frame); + + while (TRUE) { + int do_result; + + if (at_breakpoint) { + // + // If we're stopped at a breakpoint, then the REPL has a + // modality to it of "which stack level you are examining". + // The DEBUG command can change this, so at the moment it + // has to be refreshed each time an evaluation is performed. + + Init_Integer(level, HG_Stack_Level); + + REBFRM *f = Frame_For_Stack_Level(NULL, level, FALSE); + assert(f); + + Init_Any_Context( + frame, + REB_FRAME, + Context_For_Frame_May_Reify_Managed(f) + ); + } + const REBOOL fully = TRUE; // error if not all arguments consumed + + // Generally speaking, we do not want the trace level to apply to the + // REPL execution itself. + // + REBINT Save_Trace_Level = Trace_Level; + REBINT Save_Trace_Depth = Trace_Depth; + Trace_Level = 0; + Trace_Depth = 0; + + DECLARE_LOCAL (code_or_error); + if (Apply_Only_Throws( + code_or_error, // where return value of HOST-REPL is saved + fully, + &HG_Host_Repl, // HOST-REPL function to run + out, // last-result (always void first run through loop) + last_failed ? TRUE_VALUE : FALSE_VALUE, // last-failed + level, // focus-level + frame, // focus-frame + END + )) { + // The REPL should not execute anything that should throw. + // Determine graceful way of handling if it does. + // + panic (code_or_error); + } + + Trace_Level = Save_Trace_Level; + Trace_Depth = Save_Trace_Depth; + + if (IS_ERROR(code_or_error)) { + do_result = -cast(int, ERR_NUM(VAL_CONTEXT(code_or_error))); + Move_Value(out, code_or_error); + } + else if (IS_BLOCK(code_or_error)) + do_result = Do_Code( + exit_status, out, code_or_error, at_breakpoint + ); + else + panic (code_or_error); + + // NOTE: Although the operation has finished at this point, it may + // be that a Ctrl-C set up a pending FAIL, which will be triggered + // during output below. See the PUSH_UNHALTABLE_TRAP in the caller. + + if (do_result == -1) { + // + // If we're inside a breakpoint, this actually means "resume", + // because Do_Code doesn't do any error trapping if we pass + // in `at_breakpoint = TRUE`. Hence any HALT longjmp would + // have bypassed this, so the -1 signal is reused (for now). + // + if (at_breakpoint) + goto cleanup_and_return; + + // !!! The "Halt" status is communicated via -1, but + // is not an actual valid "error value". It cannot be + // created by user code, and the fact that it is done + // via the error mechanism is an "implementation detail". + // + Put_Str(halt_str); + last_failed = FALSE; + + // The output value will be an END marker on halt, to signal the + // unusability of the interrupted result. + // + Init_Void(out); + } + else if (do_result == -2) { + // + // Command issued a purposeful QUIT or EXIT, exit_status + // contains status. Assume nothing was pushed on stack + // + goto cleanup_and_return; + } + else if (do_result < -2) { + last_failed = TRUE; + assert(IS_ERROR(out)); + } + else { + // Result will be printed by next loop + // + assert(do_result == 0); + last_failed = FALSE; + } + } + +cleanup_and_return: + DROP_GUARD_VALUE(frame); + return; +} + + +// +// Host_Breakpoint_Quitting_Hook() +// +// This hook is registered with the core as the function that gets called +// when a breakpoint triggers. +// +// There are only two options for leaving the hook. One is to return TRUE +// and thus signal a QUIT, where `instruction` is the value to quit /WITH. +// The other choice is to return FALSE, where `instruction` is a purposefully +// constructed "resume instruction". +// +// (Note: See remarks in the implementation of `REBNATIVE(resume)` for the +// format of resume instructions. But generally speaking, the host does not +// need to know the details, as this represents a protocol that is supposed +// to only be between BREAKPOINT and RESUME. So the host just needs to +// bubble up the argument to a throw that had the RESUME native's name on it, +// when that type of throw is caught.) +// +// The ways in which a breakpoint hook can be exited are constrained in +// order to "sandbox" it somewhat. Though a nested REPL may be invoked in +// response to a breakpoint--as is done here--continuation should be done +// purposefully vs. "accidentally resuming" just because a FAIL or a THROW +// happened. One does not want to hit a breakpoint, then mistype a variable +// name and trigger an error that does a longjmp that effectively cancels +// the interactive breakpoint session! +// +// Hence RESUME and QUIT should be the only ways to get out of the breakpoint. +// Note that RESUME/DO provides a loophole, where it's possible to run code +// that performs a THROW or FAIL which is not trapped by the sandbox. +// +REBOOL Host_Breakpoint_Quitting_Hook( + REBVAL *instruction_out, + REBOOL interrupted +) { + // Notify the user that the breakpoint or interruption was hit. + // + if (interrupted) + Put_Str(interrupted_str); + else + Put_Str(breakpoint_str); + + // We save the stack level from before, so that we can put it back when + // we resume. Each new breakpoint nesting hit will default to debugging + // stack level 1...e.g. the level that called breakpoint. + // + REBCNT old_stack_level = HG_Stack_Level; + + DECLARE_LOCAL (level); + Init_Integer(level, 1); + + if (Frame_For_Stack_Level(NULL, level, FALSE) != NULL) + HG_Stack_Level = 1; + else + HG_Stack_Level = 0; // Happens if you just type "breakpoint" + + // Spawn nested REPL. + // + int exit_status; + Host_Repl(&exit_status, instruction_out, TRUE); + + // Restore stack level, which is presumably still valid (there shouldn't + // have been any way to "delete levels from the stack above" while we + // were nested). + // + // !!! It might be nice if the prompt had a way of conveying that you were + // in nested breaks, and give the numberings of them adjusted: + // + // |14|6|1|>> ... + // + // Or maybe that's TMI? + // + HG_Stack_Level = old_stack_level; + + // We get -1 for RESUME and -2 for QUIT, under the current convoluted + // scheme of return codes. + // + // !!! Eliminate return codes now that RL_Api dependence is gone and + // speak in terms of the REBVALs themselves. + // + assert(exit_status == -1 || exit_status == -2); + return LOGICAL(exit_status == -2); +} + + +// Register host-specific DEBUG native in user and lib contexts. (See +// notes on N_debug regarding why the C code implementing DEBUG is in +// the host and not part of Rebol Core.) +// +void Init_Debug_Extension(void) { + const REBYTE debug_utf8[] = "debug"; + REBSTR *debug_name = Intern_UTF8_Managed(debug_utf8, LEN_BYTES(debug_utf8)); -void Host_Crash(REBYTE *reason) { - OS_Crash("REBOL Host Failure", reason); + REBCTX *user_context = VAL_CONTEXT(Get_System(SYS_CONTEXTS, CTX_USER)); + if ( + 0 == Find_Canon_In_Context(Lib_Context, STR_CANON(debug_name), TRUE) && + 0 == Find_Canon_In_Context(user_context, STR_CANON(debug_name), TRUE) + ) { + REBSTR *filename = Canon(SYM___ANONYMOUS__); + REBARR *spec_array = Scan_UTF8_Managed( + N_debug_spec, LEN_BYTES(N_debug_spec), filename + ); + DECLARE_LOCAL (spec); + Init_Block(spec, spec_array); + Bind_Values_Deep(ARR_HEAD(spec_array), Lib_Context); + + REBFUN *debug_native = Make_Function( + Make_Paramlist_Managed_May_Fail(spec, MKF_KEYWORDS), + &N_debug, + NULL, // no underlying function, this is fundamental + NULL // not providing a specialization + ); + + Move_Value( + Append_Context(Lib_Context, 0, debug_name), + FUNC_VALUE(debug_native) + ); + Move_Value( + Append_Context(user_context, 0, debug_name), + FUNC_VALUE(debug_native) + ); + } + else { + // It's already there--e.g. someone added REBNATIVE(debug). Assert + // about it in the debug build, otherwise don't add the host version. + // + assert(FALSE); + } } +#ifdef TO_WINDOWS + +// +// This is the callback passed to `SetConsoleCtrlHandler()`. +// +BOOL WINAPI Handle_Break(DWORD dwCtrlType) +{ + switch(dwCtrlType) { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + RL_Escape(); + return TRUE; // TRUE = "we handled it" + + case CTRL_CLOSE_EVENT: + // + // !!! Theoretically the close event could confirm that the user + // wants to exit, if there is possible unsaved state. As a UI + // premise this is probably less good than persisting the state + // and bringing it back. + // + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + // + // They pushed the close button, did a shutdown, etc. Exit. + // + // !!! Review arbitrary "100" exit code here. + // + OS_EXIT(100); + return TRUE; // TRUE = "we handled it" + + default: + return FALSE; // FALSE = "we didn't handle it" + } +} + +BOOL WINAPI Handle_Nothing(DWORD dwCtrlType) +{ + UNUSED(dwCtrlType); + return TRUE; +} + +#else + +// +// Hook registered via `signal()`. +// +static void Handle_Signal(int sig) +{ + UNUSED(sig); + RL_Escape(); +} + +#endif + + + /*********************************************************************** ** ** MAIN ENTRY POINT ** -** Win32 args: -** inst: current instance of the application (app handle) -** prior: always NULL (use a mutex for single inst of app) -** cmd: command line string (or use GetCommandLine) -** show: how app window is to be shown (e.g. maximize, minimize, etc.) +** Win32 args: +** inst: current instance of the application (app handle) +** prior: always NULL (use a mutex for single inst of app) +** cmd: command line string (or use GetCommandLine) +** show: how app window is to be shown (e.g. maximize, minimize, etc.) ** -** Win32 return: -** If the function succeeds, terminating when it receives a WM_QUIT -** message, it should return the exit value contained in that -** message's wParam parameter. If the function terminates before -** entering the message loop, it should return zero. +** Win32 return: +** If the function succeeds, terminating when it receives a WM_QUIT +** message, it should return the exit value contained in that +** message's wParam parameter. If the function terminates before +** entering the message loop, it should return zero. ** ** Posix args: as you would expect in C. ** Posix return: ditto. ** -***********************************************************************/ +*/ +/***********************************************************************/ -#ifdef TO_WIN32 -int WINAPI WinMain(HINSTANCE inst, HINSTANCE prior, LPSTR cmd, int show) -#else -int main(int argc, char **argv) -#endif +// Using a main entry point for a console program (as opposed to WinMain) +// so that we can connect to the console. See the StackOverflow question +// "Can one executable be both a console and a GUI application": +// +// http://stackoverflow.com/questions/493536/ +// +// int WINAPI WinMain(HINSTANCE inst, HINSTANCE prior, LPSTR cmd, int show) + +int main(int argc, char **argv_ansi) { - REBYTE vers[8]; - REBYTE *line; - REBINT n; - -#ifdef TO_WIN32 // In Win32 get args manually: - int argc; - REBCHR **argv; - // Fetch the win32 unicoded program arguments: - argv = CommandLineToArgvW(GetCommandLineW(), &argc); - App_Instance = inst; -#endif + // Must be done before an console I/O can occur. Does not use reb-lib, + // so this device should open even if there are other problems. + // + Open_StdIO(); - Host_Lib = &Host_Lib_Init; + Host_Lib = &Host_Lib_Init; + RL_Init(Host_Lib); - Parse_Args(argc, (REBCHR **)argv, &Main_Args); + // While running the Rebol initialization code, we don't want any special + // Ctrl-C handling... leave it to the OS (which would likely terminate + // the process). But once it's done, set up the interrupt handler. + // + // Note: Once this was done in Open_StdIO, but it's less opaque to do it + // here (since there are already platform-dependent #ifdefs to handle the + // command line arguments) + // +#ifdef TO_WINDOWS + SetConsoleCtrlHandler(Handle_Break, TRUE); +#else + // SIGINT is the interrupt, usually tied to "Ctrl-C" + // + signal(SIGINT, Handle_Signal); - vers[0] = 5; // len - RL_Version(&vers[0]); + // SIGTERM is sent on "polite request to end", e.g. default unix `kill` + // + signal(SIGTERM, Handle_Signal); - // Must be done before an console I/O can occur. Does not use reb-lib, - // so this device should open even if there are other problems. - Open_StdIO(); // also sets up interrupt handler + // SIGHUP is sent on a hangup, e.g. user's terminal disconnected + // + signal(SIGHUP, Handle_Signal); - // Initialize the REBOL library (reb-lib): - if (!CHECK_STRUCT_ALIGN) Host_Crash("Incompatible struct alignment"); - if (!Host_Lib) Host_Crash("Missing host lib"); - // !!! Second part will become vers[2] < RL_REV on release!!! - if (vers[1] != RL_VER || vers[2] != RL_REV) Host_Crash("Incompatible reb-lib DLL"); - n = RL_Init(&Main_Args, Host_Lib); - if (n == 1) Host_Crash("Host-lib wrong size"); - if (n == 2) Host_Crash("Host-lib wrong version/checksum"); + // SIGQUIT is used to terminate a program in a way that is designed to + // debug it, e.g. a core dump. Receiving SIGQUIT is a case where + // program exit functions like deletion of temporary files may be + // skipped to provide more state to analyze in a debugging scenario. + // + // -- no handler -#ifndef REB_CORE - Init_Windows(); - Init_Graphics(); + // SIGKILL is the impolite signal for shutdown; cannot be hooked/blocked #endif -#ifdef TEST_EXTENSIONS - Init_Ext_Test(); + // With basic initialization done, we want to turn the platform-dependent + // argument strings into a block of Rebol strings as soon as possible. + // That way the command line argument processing can be taken care of by + // PARSE instead of C code! + // + REBARR *argv = Make_Array(argc); + +#ifdef TO_WINDOWS + UNUSED(argv_ansi); + + // + // Were we using WinMain we'd be getting our arguments in Unicode, but + // since we're using an ordinary main() we do not. However, this call + // lets us slip out and pick up the arguments in Unicode form. + // + wchar_t **argv_utf16 = cast( + wchar_t**, CommandLineToArgvW(GetCommandLineW(), &argc) + ); + int i = 0; + for (; i < argc; ++i) { + if (argv_utf16[i] == NULL) + continue; // shell bug + + static_assert_c(sizeof(REBUNI) == sizeof(wchar_t)); + + Init_String( + Alloc_Tail_Array(argv), + Make_UTF16_May_Fail(cast(REBUNI*, argv_utf16[i])) + ); + } +#else + // Assume no wide character support, and just take the ANSI C args, which + // should ideally be in UTF8 + // + int i = 0; + for (; i < argc; ++i) { + if (argv_ansi[i] == NULL) + continue; // shell bug + + Init_String( + Alloc_Tail_Array(argv), Make_UTF8_May_Fail(argv_ansi[i]) + ); + } #endif -// Call sys/start function. If a compressed script is provided, it will be -// decompressed, stored in system/options/boot-host, loaded, and evaluated. -// Returns: 0: ok, -1: error, 1: bad data. -#ifdef CUSTOM_STARTUP - // For custom startup, you can provide compressed script code here: - n = RL_Start((REBYTE *)(&Reb_Init_Code[0]), REB_INIT_SIZE, 0); // TRUE on halt + // !!! Register EXPERIMENTAL breakpoint hook. Note that %host-main.c is + // not really expected to stick around as the main REPL... + // + PG_Breakpoint_Quitting_Hook = &Host_Breakpoint_Quitting_Hook; + + // !!! Note that the first element of the argv_value block is used to + // initialize system/options/boot by the startup code. The real way to + // get the path to the executable varies by OS, and should either be + // passed in independently (with no argv[0]) or substituted in the first + // element of the array: + // + // http://stackoverflow.com/a/933996/211160 + // + DECLARE_LOCAL (argv_value); + Init_Block(argv_value, argv); + PUSH_GUARD_VALUE(argv_value); + +#ifdef TO_WINDOWS + // no console, we must be the child process + if (GetStdHandle(STD_OUTPUT_HANDLE) == 0) + { + App_Instance = GetModuleHandle(NULL); + } +#ifdef REB_CORE + else //use always the console for R3/core + { + // GetWindowsLongPtr support 32 & 64 bit windows + App_Instance = (HINSTANCE)GetWindowLongPtr(GetConsoleWindow(), GWLP_HINSTANCE); + } #else - n = RL_Start(0, 0, 0); + //followinng R3/view code behaviors when compiled as: + //-"console app" mode: stdio redirection works but blinking console window during start + //-"GUI app" mode stdio redirection doesn't work properly, no blinking console window during start + else if (argc > 1) // we have command line args + { + // GetWindowsLongPtr support 32 & 64 bit windows + App_Instance = (HINSTANCE)GetWindowLongPtr(GetConsoleWindow(), GWLP_HINSTANCE); + } + else // no command line args but a console - launch child process so GUI is initialized and exit + { + DWORD dwCreationFlags = CREATE_DEFAULT_ERROR_MODE | DETACHED_PROCESS; + STARTUPINFO startinfo; + PROCESS_INFORMATION procinfo; + ZeroMemory(&startinfo, sizeof(startinfo)); + startinfo.cb = sizeof(startinfo); + if (!CreateProcess(NULL, argv[0], NULL, NULL, FALSE, dwCreationFlags, NULL, NULL, &startinfo, &procinfo)) + MessageBox(0, L"CreateProcess() failed :(", L"", 0); + exit(0); + } +#endif //REB_CORE +#endif //TO_WINDOWS + + // Common code for console & GUI version +#ifndef REB_CORE + Init_Windows(); + OS_Init_Graphics(); +#endif // REB_CORE + + Init_Debug_Extension(); + + struct Reb_State state; + REBCTX *error; + + PUSH_UNHALTABLE_TRAP(&error, &state); + +// The first time through the following code 'error' will be NULL, but... +// `fail` can longjmp here, so 'error' won't be NULL *if* that happens! + + int exit_status; + + volatile REBOOL finished; // without volatile, gets "clobbered" warning + + Prep_Global_Cell(&HG_Host_Repl); + Init_Blank(&HG_Host_Repl); + + if (error != NULL) { + // + // We want to avoid doing I/O directly from the C code of the host, + // and let that go through WRITE-STDOUT. Hence any part of the + // startup that can error should be TRAP'd by the startup code itself + // and handled or PRINT'd in some way. + // + // The exception is a halt with Ctrl-C, which can currently only be + // handled by C code that ran PUSH_UNHALTABLE_TRAP(). + // + if (ERR_NUM(error) != RE_HALT) + panic (error); + + exit_status = 128; // http://stackoverflow.com/questions/1101957/ + finished = TRUE; + } + else { + REBSER *startup = Decompress( + &Reb_Init_Code[0], + REB_INIT_SIZE, + -1, + FALSE, + FALSE + ); + if (startup == NULL) + panic ("Can't decompress %host-start.r linked into executable"); + + const char *host_start_utf8 = "host-start.r"; + REBSTR *host_start_filename = Intern_UTF8_Managed( + cb_cast(host_start_utf8), strlen(host_start_utf8) + ); + REBARR *array = Scan_UTF8_Managed( + BIN_HEAD(startup), BIN_LEN(startup), host_start_filename + ); + + // Bind the REPL and startup code into the lib context. + // + // !!! It's important not to load the REPL into user, because since it + // uses routines like PRINT to do it's I/O you (probably) don't want + // the REPL to get messed up if PRINT is redefined--for instance. It + // should probably have its own context, which would entail a copy of + // every word in lib that it uses, but that mechanic hasn't been + // fully generalized--and might not be the right answer anyway. + // + // Only add top-level words to the `lib' context + Bind_Values_Set_Midstream_Shallow(ARR_HEAD(array), Lib_Context); + + // Bind all words to the `lib' context, but not adding any new words + Bind_Values_Deep(ARR_HEAD(array), Lib_Context); + + // The new policy for source code in Ren-C is that it loads read only. + // This didn't go through the LOAD Rebol function (should it? it + // never did before.) For now, use simple binding but lock it. + // + Deep_Freeze_Array(array); + + DECLARE_LOCAL (code); + Init_Block(code, array); + + DECLARE_LOCAL (host_start); + if ( + Do_Code(&exit_status, host_start, code, FALSE) + != 0 + ){ + panic (startup); // just loads functions, shouldn't QUIT or error + } + + Free_Series(startup); + + DECLARE_LOCAL (ext_value); + Init_Blank(ext_value); + LOAD_BOOT_EXTENSIONS(ext_value); + + if (!IS_FUNCTION(host_start)) + panic (host_start); // should not be able to error + + const REBOOL fully = TRUE; // error if not all arguments are consumed + + DECLARE_LOCAL(exec_path); + REBCHR *path; + REBINT path_len = OS_GET_CURRENT_EXEC(&path); + if (path_len < 0){ + Init_Blank(exec_path); + } else { + Init_File(exec_path, + To_REBOL_Path(path, path_len, (OS_WIDE ? PATH_OPT_UNI_SRC : 0)) + ); + OS_FREE(path); + } + + DECLARE_LOCAL (result); + if (Apply_Only_Throws( + result, + fully, + host_start, // startup function, implicit GC guard + exec_path, // path to executable file, implicit GC guard + argv_value, // argv parameter, implicit GC guard + ext_value, + END + )) { + if ( + IS_FUNCTION(result) + && VAL_FUNC_DISPATCHER(result) == &N_quit + ) { + CATCH_THROWN(result, result); + exit_status = Exit_Status_From_Value(result); + + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + + SHUTDOWN_BOOT_EXTENSIONS(); + Shutdown_Core(); + OS_EXIT(exit_status); + DEAD_END; + } + + fail (Error_No_Catch_For_Throw(result)); + } + + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + + // HOST-START returns either an integer exit code or a blank if the + // behavior should be to fall back to the REPL. + // + if (IS_FUNCTION(result)) { + finished = FALSE; + Move_Value(&HG_Host_Repl, result); + } + else if (IS_INTEGER(result)) { + finished = TRUE; + exit_status = VAL_INT32(result); + } + else + panic (result); // no other legal return values for now + } + + DROP_GUARD_VALUE(argv_value); + + PUSH_GUARD_VALUE(&HG_Host_Repl); // might be blank + + // Although the REPL routine does a PUSH_UNHALTABLE_TRAP in order to + // catch any errors or halts, it then has to report those errors when + // that trap is engaged. So imagine it's in the process of trapping an + // error and prints out a very long one, and the user wants to interrupt + // the error report with a Ctrl-C...but there's not one in effect. + // + // This loop institutes a top-level trap whose only job is to catch the + // interrupts that occur during overlong error reports inside the REPL. + // + + while (NOT(finished)) { + // The DECLARE_LOCAL is here and not outside the loop + // due to wanting to avoid "longjmp clobbering" warnings + // (seen in optimized builds on Android). + // + DECLARE_LOCAL (value); + SET_END(value); + PUSH_GUARD_VALUE(value); // !!! Out_Value expects value to be GC safe + + struct Reb_State state; + REBCTX *error; + + PUSH_UNHALTABLE_TRAP(&error, &state); + + // The first time through the following code 'error' will be NULL, but... + // `fail` can longjmp here, so 'error' won't be NULL *if* that happens! + + if (error) { + // + // If a HALT happens and manages to get here, just go set up the + // trap again and call into the REPL again. (It wasn't an + // evaluation error because those have their own traps, it was a + // halt that happened during output.) + // + if (ERR_NUM(error) != RE_HALT) { + #ifdef NDEBUG + // do something sensible in release builds here that does not + // crash. + #else + // A non-halting error may be in the process of delivery, + // when a pending Ctrl-C gets processed. This causes the + // printing machinery to complain, since there's no trap + // state set up to handle it. Since we're crashing anyway, + // unregister the Ctrl-C handler. We also need to register + // a no op handler, to prevent the error test from getting + // cut off by Windows default Ctrl-C behavior. + // + // !!! Is this necessary on linux too? On Windows the case + // to cause it would be Ctrl-C during an ASK to cancel it, and + // then a Ctrl-C after that. + // + #ifdef TO_WINDOWS + SetConsoleCtrlHandler(Handle_Break, FALSE); // unregister + SetConsoleCtrlHandler(Handle_Nothing, TRUE); // register + #endif + + CLR_SIGNAL(SIG_HALT); + panic(error); + #endif + } + } + else { + Host_Repl(&exit_status, value, FALSE); + + finished = TRUE; + + DROP_TRAP_SAME_STACKLEVEL_AS_PUSH(&state); + } + + DROP_GUARD_VALUE(value); + } + + DROP_GUARD_VALUE(&HG_Host_Repl); + + SHUTDOWN_BOOT_EXTENSIONS(); + + OS_QUIT_DEVICES(0); + +#ifndef REB_CORE + OS_Destroy_Graphics(); #endif - // Console line input loop (just an example, can be improved): - if ( - !(Main_Args.options & RO_CGI) - && ( - !Main_Args.script // no script was provided - || n < 0 // script halted or had error - || Main_Args.options & RO_HALT // --halt option - ) - ){ - n = 0; // reset error code (but should be able to set it below too!) - while (TRUE) { - Put_Str(PROMPT_STR); - if ((line = Get_Str())) { - RL_Do_String(line, 0, 0); - RL_Print_TOS(0, RESULT_STR); - OS_Free(line); - } - else break; // EOS - } - } - - //OS_Call_Device(RDI_STDIO, RDC_CLOSE); - OS_Quit_Devices(0); - - // A QUIT does not exit this way, so the only valid return code is zero. - return 0; -} + Close_StdIO(); + // No need to do a "clean" shutdown, as we are about to exit the process + // (Note: The debug build runs through the clean shutdown anyway!) + // + RL_Shutdown(FALSE); + + return exit_status; +} diff --git a/src/os/host-start.r b/src/os/host-start.r new file mode 100644 index 0000000000..6ff072e4b3 --- /dev/null +++ b/src/os/host-start.r @@ -0,0 +1,864 @@ +REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Command line processing and startup code called by %host-main.c" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Description: { + Codebases using the Rebol interpreter can vary widely, and might not + have command line arguments or user interface at all. + + This is a beginning attempt to factor out what used to be in + R3-Alpha's %sys-start.r and executed by RL_Start(). By making the + Startup_Core() routine more lightweight, it's possible to get the system + up to a point where it's possible to use Rebol code to do things like + command-line processing. + + Still more factoring should be possible, so that different executables + (R3/Core, R3/View, Ren Garden) might reuse large parts of the + initialization, if they need to do things in common. + } +] + +; These used to be loaded by the core, but prot-tls depends on crypt, thus it +; needs to be loaded after crypt. It was not an issue when crypt was builtin. +; But when it's converted to a module, and loaded by load-boot-exts, it breaks +; the dependency of prot-tls. +; +; Moving protocol loading from core to host fixes the problem. +; +; This should be initialized by make-host-init.r, but set a default just in +; case +host-prot: default _ + +boot-print: procedure [ + "Prints during boot when not quiet." + data + /eval +][ + eval_BOOT_PRINT: eval + eval: :lib/eval + + unless system/options/quiet [ + print/(all [any [eval_BOOT_PRINT | semiquoted? 'data] 'eval]) :data + ] +] + +loud-print: procedure [ + "Prints during boot when verbose." + data + /eval +][ + eval_BOOT_PRINT: eval + eval: :lib/eval + + if system/options/verbose [ + print/(all [any [eval_BOOT_PRINT | semiquoted? 'data] 'eval]) :data + ] +] + +make-banner: function [ + "Build startup banner." + fmt [block!] +][ + str: make string! 200 + star: append/dup make string! 74 #"*" 74 + spc: format ["**" 70 "**"] "" + parse fmt [ + some [ + [ + set a: string! (s: format ["** " 68 "**"] a) + | '= set a: [string! | word! | set-word!] [ + b: + path! (b: get b/1) + | word! (b: get b/1) + | block! (b: spaced b/1) + | string! (b: b/1) + ] + (s: format ["** " 11 55 "**"] reduce [a b]) + | '* (s: star) + | '- (s: spc) + ] + (append append str s newline) + ] + ] + return str +] + + +boot-banner: [ + * + - + "REBOL 3.0 (Ren-C branch)" + - + = Copyright: "2012 REBOL Technologies" + = Copyright: "2012-2017 Rebol Open Source Contributors" + = "" "Apache 2.0 License, see LICENSE." + = Website: "http://github.com/metaeducation/ren-c" + - + = Version: system/version + = Platform: system/platform + = Build: system/build + = Commit: system/commit + - + = Language: system/locale/language* + = Locale: system/locale/locale* + = Home: system/options/home + = Resources: system/options/resources + = Console: system/console/name + - + * +] + +about: procedure [ + "Information about REBOL" +][ + print make-banner boot-banner +] + + +; The usage instructions should be automatically generated from a table, +; the same table used to generate parse rules for the command line processing. +; +; There has been some talk about generalizing command-line argument handling +; in a way that a module can declare what its arguments and types are, much +; like an ordinary FUNCTION!, and all the proxying is handled for the user. +; Work done on the dialect here could be shared in common. +; +usage: procedure [ + "Prints command-line arguments." +][ +; --cgi (-c) Load CGI utiliy module and modes +; --version tuple Script must be this version or greater +; Perhaps add --reqired version-tuple for above TBD + + print trim/auto copy { + Command line usage: + + REBOL [options] [script] [arguments] + + Standard options: + + --do expr Evaluate expression (quoted) + --help (-?) Display this usage information + --script file Implicitly provide script to run + --version (-v) Display version only (then quit) + -- End of options (not implemented) + + Special options: + + --about Prints full banner of information when console starts + --debug flags For user scripts (system/options/debug) + --halt (-h) Leave console open when script is done + --import file Import a module prior to script + --quiet (-q) No startup banners or information + --resources dir Manually set where Rebol resources directory lives + --secure policy Can be: none allow ask throw quit + --suppress "" Suppress any found start-up scripts Use "*" to suppress all. + --trace (-t) Enable trace mode during boot + --verbose Show detailed startup information + + Other quick options: + + -s No security + +s Full security + -qs Quiet and secure (combining other switches not implemented yet) + + Examples: + + REBOL script.reb + REBOL -s script.reb + REBOL script.reb 10:30 test@example.com + REBOL --do "print 1 + 1" + #!/sbin/REBOL -cs + + Console (no script/arguments or Standard option used): + + REBOL + REBOL -q --about --suppress "%rebol.reb %user.reb" + } +] + +boot-welcome: +{Welcome to the Rebol console. For more information please type in the commands below: + + HELP - For starting information + ABOUT - Information about your Rebol + CHANGES - What's different about this version} + +license: procedure [ + "Prints the REBOL/core license agreement." +][ + print system/license +] + +load-boot-exts: function [ + "INIT: Load boot-based extensions." + boot-exts [block! blank!] +][ + loud-print "Loading boot extensions..." + + ;loud-print ["boot-exts:" mold boot-exts] + for-each [init quit] boot-exts [ + load-extension init + ] + + boot-exts: 'done + set 'load-boot-exts 'done ; only once +] + +host-script-pre-load: procedure [ + {Code registered as a hook when a module or script are loaded} + is-module [logic!] + hdr [blank! object!] + {Header object (will be blank for DO of BINARY! with no header)} +][ + ; Print out the script info + boot-print [ + (either is-module "Module:" "Script:") select hdr 'title + "Version:" opt select hdr 'version + "Date:" opt select hdr 'date + ] +] + + +host-start: function [ + "Loads extras, handles args, security, scripts." + return: [integer! function!] + {If integer, host should exit with that status; else a CONSOLE FUNCTION!} + exec-path [file! blank!] + {Path to the executable file} + argv [block!] + {Raw command line argument block received by main() as STRING!s} + boot-exts [block! blank!] + {Extensions (modules) loaded at boot} + host-prot + + o (system/options) ;-- shorthand since options are often read/written +][ + ; Currently there is just one monolithic "initialize all schemes", e.g. + ; FILE:// and HTTP:// and CONSOLE:// -- this will need to be broken down + ; into finer granularity. Formerly all of them were loaded at the end + ; of Startup_Core(), but one small step is to push the decision into the + ; host...which loads them all, but should be more selective. + ; + sys/init-schemes + + ; The text codecs should also probably be extensions as well. But the + ; old Register_Codec() function was C code taking up space in %b-init.c + ; so this at least allows that function to be deleted...the registration + ; as an extension would also be done like this in user-mode. + ; + (sys/register-codec* + 'text + %.txt + :identify-text? + :decode-text + :encode-text) + + (sys/register-codec* + 'utf-16le + %.txt + :identify-utf16le? + :decode-utf16le + :encode-utf16le) + + (sys/register-codec* + 'utf-16be + %.txt + :identify-utf16be? + :decode-utf16be + :encode-utf16be) + + system/product: 'core + + ; + ; helper functions + ; + die: func [ + {A gracefully way to FAIL during startup} + reason [string!] + {Error message} + /error e [error!] + {Error object, shown if --verbose option used} + return + ][ + print "Startup encountered an error!" + print ["**" reason] + if error [ + print either o/verbose [e] ["!! use --verbose for more detail"] + ] + return 1 + ] + + to-dir: function [ + {Convert string path to absolute dir! path} + return: [blank! file!] + {Blank if not found} + dir [string!] + ][ + if empty? dir [return _] + dir: clean-path/dir to-rebol-file dir + all [exists? dir | dir] + ] + + get-home-path: function [ + {Return HOME path (e.g. $HOME on *nix)} + return: [blank! file!] + {Blank if not found} + ][ + home: attempt [ + any [ + get-env 'HOME + ; join-of could fail because it doesn't accept blank + attempt [join-of get-env 'HOMEDRIVE get-env 'HOMEPATH] + ] + ] + + if blank? home [return _] + to-dir home + ] + + get-resources-path: function [ + {Return platform specific resources path.} + return: [blank! file!] + {Blank if not found} + ][ + ;; lives under systems/options/home + + path: join-of o/home switch/default system/platform/1 [ + 'Windows [%REBOL/] + ][ + %.rebol/ ;; default *nix (covers Linux, MacOS (OS X) and Unix) + ] + + all [exists? path | path] + ] + + ; Set system/users/home (users HOME directory) + ; Set system/options/home (ditto) + ; Set system/options/resources (users Rebol resource directory) + ; NB. Above can be overridden by --home option + ; TBD - check perms are correct (SECURITY) + all [ + home-dir: get-home-path ;; _ if doesn't exist + system/user/home: o/home: home-dir + resources-dir: get-resources-path ;; _ if doesn't exist + o/resources: resources-dir + ] + + sys/script-pre-load-hook: :host-script-pre-load + + do-string: _ ;-- will be set if a string is given with --do + + quit-when-done: _ ;-- by default run CONSOLE + + ; Process the option syntax out of the command line args in order to get + ; the intended arguments. TAKEs each option string as it goes so the + ; array remainder can act as the args. + + either tail? argv [ + if file? exec-path [ + o/boot: exec-path + o/bin: first split-path o/boot + ] + ][ + either file? exec-path [ + o/boot: exec-path + take argv ;consume argv[0] anyway + ][ ;-- on most systems, argv[0] is the exe path + o/boot: clean-path to-rebol-file take argv + ] + o/bin: first split-path o/boot + ] + + param-or-die: func [ + {Take --option argv and then check if param arg is present, else die} + switch-arg [string!] {Command-line option (switch) used} + ][ + take argv + any [ + first argv + die join-all [switch-arg { parameter missing}] + ] + ] + + until [tail? argv] [ + + is-option: parse/case argv/1 [ + + ["--" end] ( + ; Double-dash means end of command line arguments, and the + ; rest of the arguments are going to be positional. In + ; Rebol's case, that means a file to run and its arguments + ; (if anything following). + ; + ; Make the is-option rule fail, but take the "--" away so + ; it isn't treated as the name of a script to run! + ; + take argv + ) fail + | + "--about" end ( + o/about: true ;; show full banner (ABOUT) on startup + ) + | + ["--cgi" | "-c"] end ( + o/quiet: true + o/cgi: true + ) + | + "--debug" end ( + ;-- was coerced to BLOCK! before, but what did this do? + ; + o/debug: to logic! param-or-die "DEBUG" + ) + | + "--do" end ( + o/quiet: true ;-- don't print banner, just run code string + do-string: param-or-die "DO" + quit-when-done: default true ;-- override blank, not false + ) + | + ["--halt" | "-h"] end ( + quit-when-done: false ;-- overrides true + ) + | + ["--help" | "-?"] end ( + usage + quit-when-done: default true + ) + | + "--import" end ( + lib/import to-rebol-file param-or-die "IMPORT" + ) + | + ["--quiet" | "-q"] end ( + o/quiet: true + ) + | + "-qs" end ( + ; !!! historically you could combine switches when used with + ; a single dash, but this feature should be part of a better + ; thought out implementation. For now, any historically + ; significant combinations (e.g. used in make-make.r) will + ; be supported manually. This is "quiet unsecure" + ; + o/quiet: true + o/secure: 'allow + ) + | + "-cs" end ( + ; every tutorial on Rebol CGI shows these flags. + o/secure: 'allow + o/quiet: true + o/cgi: true + ) + | + "--resources" end ( + if resource-dir: to-dir param-or-die "RESOURCES" [ + ;; dir exists so will override earlier automated settings + o/resources: resource-dir + ] + else [die "RESOURCES directory not found"] + ) + | + "--suppress" end ( + param: param-or-die "SUPPRESS" + o/suppress: if param == "*" [ + ;; suppress all known start-up files + [%rebol.reb %user.reb %console-skin.reb] + ] else [ + to-block param + ] + ) + | + "--secure" end ( + o/secure: to word! param-or-die "SECURE" + if o/secure != 'allow [ + die "SECURE is disabled (never finished for R3-Alpha)" + ] + ) + | + "-s" end ( + o/secure: 'allow ;-- "secure-min" + ) + | + "+s" end ( + o/secure: 'quit ;-- "secure-max" + die "SECURE is disabled (never finished for R3-Alpha)" + ) + | + "--script" end ( + o/script: param-or-die "SCRIPT" + quit-when-done: default true ;-- overrides blank, not false + ) + | + ["-t" | "--trace"] end ( + trace on ;-- did they mean trace just the script/DO code? + ) + | + "--verbose" end ( + o/verbose: true + ) + | + ["-v" | "-V" | "--version"] end ( + boot-print ["Rebol 3" system/version] ;-- version tuple + quit-when-done: default true + ) + | + "-w" end ( + ;-- No window; not currently applicable + ) + | + [copy cli-option: [["--" | "-" | "+"] to end ]] ( + die join-all [ + "Unknown command line option: " cli-option + newline + {!! For a full list of command-line options use: --help} + ] + ) + ] + + if not is-option [break] + + take argv + ] + + ; As long as there was no `--script` pased on the command line explicitly, + ; the first item after the options is implicitly the script. + ; + if all [not o/script | not tail? argv] [ + o/script: to file! take argv + quit-when-done: default true + ] + + ; Whatever is left is the positional arguments, available to the script. + ; + o/args: argv ;-- whatever's left is positional args + + + boot-embedded: get-encap system/options/boot + + if any [boot-embedded o/script] [o/quiet: true] + + ;-- Set option/paths for /path, /boot, /home, and script path (for SECURE) + o/path: what-dir ;dirize any [o/path o/home] + + ;-- !!! this was commented out. Is it important? + comment [ + if slash <> first o/boot [o/boot: clean-path o/boot] + ] + + if file? o/script [ ; Get the path (needed for SECURE setup) + script-path: split-path o/script + case [ + slash = first first script-path [] ; absolute + %./ = first script-path [script-path/1: o/path] ; curr dir + ] else [ + insert first script-path o/path ; relative + ] + ] + + ;-- Convert command line arg strings as needed: + script-args: o/args ; save for below + + ; version, import, secure are all of valid type or blank + + + load-boot-exts boot-exts + + for-each [spec body] host-prot [module spec body] + host-prot: 'done + + ;-- Setup SECURE configuration (a NO-OP for min boot) + ;; Note: After refactoring `file` was removed from above. + ;; file (below) -> o/bin (would have been same) + +comment [ + lib/secure (case [ + o/secure [ + o/secure + ] + file? o/script [ + compose [file throw (file) [allow read] (first script-path) allow] + ] + ] else [ + compose [file throw (file) [allow read] %. allow] ; default + ]) +] + + ; + ; start-up scripts, o/loaded tracks which ones are loaded (with full path) + ; + + ;-- Evaluate rebol.reb script: + unless find o/suppress %rebol.reb [ + loud-print ["Checking for rebol.reb file in" o/bin] + if exists? o/bin/rebol.reb [ ; bug#706 ?? + trap/with [ + do o/bin/rebol.reb + append o/loaded o/bin/rebol.reb + loud-print ["Finished evaluating script:" o/bin/rebol.reb] + ] func [error] [ + die/error "Error found in rebol.reb script" error + ] + ] + ] + + ;-- Evaluate user.reb script: + if all [ + o/resources + not find o/suppress %user.reb + ][ + loud-print ["Checking for user.reb file in" o/resources] + if exists? o/resources/user.reb [ + trap/with [ + ; + ; ideally this would query permissions to make sure RESOURCES + ; is owner writable only + ; + do o/resources/user.reb + append o/loaded o/resources/user.reb + loud-print ["Finished evaluating script:" o/resources/user.reb] + ] func [error] [ + die/error "Error found in user.reb script" error + ] + ] + ] + + unless blank? boot-embedded [ + case [ + binary? boot-embedded [ ; single script + code: load/header/type boot-embedded 'unbound + ] + block? boot-embedded [ + ; + ; The encapping is an embedded zip archive. get-encap did + ; the unzipping into a block, and this information must be + ; made available somehow. It shouldn't be part of the "core" + ; but just responsibility of the host that supports encap + ; based loading. + ; + o/encap: boot-embedded + + main: select boot-embedded %main.reb + unless binary? main [ + die "Could not find %main.reb in encapped zip file" + ] + code: load/header/type main 'unbound + ] + ] else [ + die "Bad embedded boot data (not a BLOCK! or a BINARY!)" + ] + + ;boot-print ["executing embedded script:" mold code] + system/script: construct system/standard/script [ + title: select first code 'title + header: first code + parent: _ + path: what-dir + args: script-args + ] + either 'module = select first code 'type [ + code: reduce [first+ code code] + if object? tmp: sys/do-needs/no-user first code [append code tmp] + import do compose [module (code)] + ][ + sys/do-needs first+ code + do intern code + ] + quit ;ignore user script and "--do" argument + ] + + ; Evaluate any script argument, e.g. `r3 test.r` or `r3 --script test.r` + ; + either file? o/script [ + trap/with [ + do/only o/script ;-- /ONLY so QUIT/WITH exit code bubbles out + ] func [error return] [ + print error + return 1 + ] + ] + host-start: 'done + + ; Evaluate the DO string, e.g. `r3 --do "print {Hello}"` + ; + if do-string [ + trap/with [ + do/only do-string ;-- /ONLY so QUIT/WITH exit code bubbles out + ] func [error return] [ + print error + return 1 + ] + ] + + if quit-when-done [return 0] + + ; Start CONSOLE if got this far. + ; + ; Instantiate console! object into system/console for skinning. This + ; object can be updated %console-skin.reb if in system/options/resources + ; + ; See /os/host-console.r where this object is called from + ; + + loud-print "Starting console..." + loud-print "" + proto-skin: make console! [] + skin-error: _ + + if all [ + skin-file: %console-skin.reb + not find o/suppress skin-file + o/resources + exists? skin-file: join-of o/resources skin-file + ][ + trap/with [ + new-skin: do load skin-file + + ;; if loaded skin returns console! object then use as prototype + if all [ + object? new-skin + true? select new-skin 'repl ;; quacks like a REPL so assume its a console! + ][ + proto-skin: new-skin + proto-skin/updated?: true + proto-skin/name: any [proto-skin/name "updated"] + ] + + proto-skin/loaded?: true + proto-skin/name: any [proto-skin/name "loaded"] + append o/loaded skin-file + + ] func [error] [ + skin-error: error ;; show error later if --verbose + proto-skin/name: "error" + ] + ] + + proto-skin/name: any [proto-skin/name | "default"] + system/console: proto-skin + + ; + ; banner time + ; + if o/about [ + ;-- print fancy boot banner + ; + boot-print make-banner boot-banner + ] else [ + boot-print [ + "Rebol 3 (Ren/C branch)" + mold compose [version: (system/version) build: (system/build)] + newline + ] + ] + + boot-print boot-welcome + + ; verbose console skinning messages + loud-print [newline {Console skinning:} newline] + if skin-error [ + loud-print [ + { Error loading console skin -} skin-file | | + skin-error | | + { Fix error and restart CONSOLE} + ] + ] else [ + loud-print [ + space space + either/only proto-skin/loaded? {Loaded skin} {Skin does not exist} + "-" skin-file + unspaced ["(CONSOLE " unless/only proto-skin/updated? {not } "updated)"] + ] + ] + + ; Rather than have the host C code look up the CONSOLE function by name, it + ; is returned as a function value from calling the start. It's a bit of + ; a hack, and might be better with something like the SYS_FUNC table that + ; lets the core call Rebol code. + ; + return :host-console +] + + +; Define console! object for skinning - stub for elsewhere? +; + +console!: make object! [ + name: _ + repl: true ;-- used to identify this as a console! object (quack!) + loaded?: false ;-- if true then this is a loaded (external) skin + updated?: false ;-- if true then console! object found in loaded skin + counter: 0 + last-result: _ ;-- last evaluated result (sent by HOST-CONSOLE) + + ; Called on every line of input by HOST-CONSOLE in %os/host-console.r + ; + cycle: does [ + if zero? ++ counter [print-greeting] ;-- only load skin on first cycle + counter + ] + + ;; APPEARANCE (can be overridden) + + prompt: {>> } + result: {== } + warning: {!! } + error: {** } ;; not used yet + info: to-string #{e29398} ;; info sign! + greeting: _ + print-prompt: proc [] [print/only prompt] + print-result: proc [] [print unspaced [result last-result]] + print-warning: proc [s] [print unspaced [warning reduce s]] + print-error: proc [e] [print e] + print-info: proc [s] [print [info space space reduce s]] + print-greeting: proc [] [boot-print greeting] + print-gap: proc [] [print-newline] + + ;; BEHAVIOR (can be overridden) + + input-hook: func [ + {Receives line input, parse/transform, send back to CONSOLE eval} + s + ][ + s + ] + + dialect-hook: func [ + {Receives code block, parse/transform, send back to CONSOLE eval} + s + ][ + s + ] + + shortcuts: make object! compose/deep [ + q: [quit] + list-shortcuts: [print system/console/shortcuts] + changes: [ + browse (join-all [ + https://github.com/metaeducation/ren-c/blob/master/CHANGES.md# + join-all ["" system/version/1 system/version/2 system/version/3] + ]) + ] + ] + + ;; HELPERS (could be overridden!) + + add-shortcut: proc [ + {Add/Change console shortcut} + name [any-word!] + {shortcut name} + block [block!] + {command(s) expanded to} + ][ + extend shortcuts name block + ] +] diff --git a/src/os/host-stdio.c b/src/os/host-stdio.c index 5b86cb0505..37488b3574 100644 --- a/src/os/host-stdio.c +++ b/src/os/host-stdio.c @@ -1,169 +1,111 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Simple helper functions for host-side standard I/O -** Author: Carl Sassenrath -** Caution: OS independent -** Purpose: -** Interfaces to the stdio device for standard I/O on the host. -** All stdio within REBOL uses UTF-8 encoding so the functions -** shown here operate on UTF-8 bytes, regardless of the OS. -** The conversion to wide-chars for OSes like Win32 is done in -** the StdIO Device code. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ +// +// File: %host-stdio.c +// Summary: "Simple helper functions for host-side standard I/O" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// OS independent +// +// Interfaces to the stdio device for standard I/O on the host. +// All stdio within REBOL uses UTF-8 encoding so the functions +// shown here operate on UTF-8 bytes, regardless of the OS. +// The conversion to wide-chars for OSes like Win32 is done in +// the StdIO Device code. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include #include #include "reb-host.h" -#include "host-lib.h" -void Host_Crash(REBYTE *reason); +void Host_Crash(const char *reason); // Temporary globals: (either move or remove?!) REBREQ Std_IO_Req; static REBYTE *inbuf; static REBCNT inbuf_len = 32*1024; -static REBYTE *Get_Next_Line() -{ - REBYTE *bp = inbuf; - REBYTE *out; - REBCNT len; - - // Scan for line terminator or end: - for (bp = inbuf; *bp != CR && *bp != LF && *bp != 0; bp++); - - // If found, copy the line and remove it from buffer: - if (*bp) { - if (*bp == CR && bp[1] == LF) bp++; - len = bp - inbuf; - out = OS_Make(len + 2); - COPY_BYTES(out, inbuf, len+1); - out[len+1] = 0; - MOVE_MEM(inbuf, bp+1, 1+strlen(bp+1)); - return out; - } - - return 0; // more input needed -} -static int Fetch_Buf() +// +// Open_StdIO: C +// +// Open REBOL's standard IO device. This same device is used +// by both the host code and the R3 DLL itself. +// +// This must be done before any other initialization is done +// in order to output banners or errors. +// +void Open_StdIO(void) { - REBCNT len = strlen(inbuf); + CLEARS(&Std_IO_Req); + Std_IO_Req.device = RDI_STDIO; - Std_IO_Req.data = inbuf + len; - Std_IO_Req.length = inbuf_len - len - 1; - Std_IO_Req.actual = 0; + OS_Do_Device(&Std_IO_Req, RDC_OPEN); - OS_Do_Device(&Std_IO_Req, RDC_READ); + if (Std_IO_Req.error) Host_Crash("stdio open"); - // If error, don't crash, just ignore it: - if (Std_IO_Req.error) return 0; //Host_Crash("stdio read"); - - // Terminate (LF) last line? - if (len > 0 && Std_IO_Req.actual == 0) { - inbuf[len++] = LF; - inbuf[len] = 0; - return TRUE; - } - - // Null terminate buffer: - len = Std_IO_Req.actual; - Std_IO_Req.data[len] = 0; - return len > 0; + inbuf = OS_ALLOC_N(REBYTE, inbuf_len); + inbuf[0] = 0; } -/*********************************************************************** -** -*/ void Open_StdIO(void) -/* -** Open REBOL's standard IO device. This same device is used -** by both the host code and the R3 DLL itself. -** -** This must be done before any other initialization is done -** in order to output banners or errors. -** -***********************************************************************/ +// +// Close_StdIO: C +// +// Complement to Open_StdIO() +// +void Close_StdIO(void) { - CLEARS(&Std_IO_Req); - Std_IO_Req.clen = sizeof(Std_IO_Req); - Std_IO_Req.device = RDI_STDIO; - - OS_Do_Device(&Std_IO_Req, RDC_OPEN); - - if (Std_IO_Req.error) Host_Crash("stdio open"); - - inbuf = OS_Make(inbuf_len); - inbuf[0] = 0; + OS_FREE(inbuf); } -/*********************************************************************** -** -*/ REBYTE *Get_Str() -/* -** Get input of a null terminated UTF-8 string. -** Divides the input into lines. -** Buffers multiple lines if needed. -** Returns NULL on end of stream. -** -***********************************************************************/ +// +// Put_Str: C +// +// Outputs a null terminated UTF-8 string. +// If buf is larger than StdIO Device allows, error out. +// OS dependent line termination must be done prior to call. +// +void Put_Str(const REBYTE *buf) { - REBYTE *line; - - if ((line = Get_Next_Line())) return line; - - if (Fetch_Buf()) return Get_Next_Line(); - - return 0; -} - - -/*********************************************************************** -** -*/ void Put_Str(REBYTE *buf) -/* -** Outputs a null terminated UTF-8 string. -** If buf is larger than StdIO Device allows, error out. -** OS dependent line termination must be done prior to call. -** -***********************************************************************/ -{ - Std_IO_Req.length = strlen(buf); - Std_IO_Req.data = (REBYTE*)buf; - Std_IO_Req.actual = 0; - - OS_Do_Device(&Std_IO_Req, RDC_WRITE); - - if (Std_IO_Req.error) Host_Crash("stdio write"); + /* This function could be called by signal handler and inside of Fetch_Buf */ + REBREQ req; + memcpy(&req, &Std_IO_Req, sizeof(req)); + + // !!! A request should ideally have a way to enforce that it is not + // going to modify the data. For now we "trust it" and use m_cast. + // Undefined behavior will result should a RDC_WRITE request make + // modifications to the data pointed to. + // + req.common.data = m_cast(REBYTE*, buf); + req.length = LEN_BYTES(buf); + req.actual = 0; + + OS_Do_Device(&req, RDC_WRITE); + + if (req.error) Host_Crash("stdio write"); } diff --git a/src/os/host-table.c b/src/os/host-table.c new file mode 100644 index 0000000000..26ccb08d09 --- /dev/null +++ b/src/os/host-table.c @@ -0,0 +1,10 @@ +// Isolated C source file for making the host table an isolated link entity. +// +// Libraries may not wish to include the resulting %host-table.o (or %.obj) +// in order to make it possible to relink against different hosts. +// +// See %host-table.inc for more information. +// + +#include "reb-host.h" +#include "host-table.inc" diff --git a/src/os/linux/dev-signal.c b/src/os/linux/dev-signal.c new file mode 100644 index 0000000000..3f05919f7b --- /dev/null +++ b/src/os/linux/dev-signal.c @@ -0,0 +1,153 @@ +// +// File: %dev-signal.c +// Summary: "Device: Signal access on Linux" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2014 Atronix Engineering, Inc. +// Copyright 2014-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Provides a very simple interface to the signals on Linux +// + +#include +#include +#include +#include + +#include + +#include "reb-host.h" + +extern void Signal_Device(REBREQ *req, REBINT type); + +// +// Open_Signal: C +// +DEVICE_CMD Open_Signal(REBREQ *req) +{ + struct devreq_posix_signal *signal = DEVREQ_POSIX_SIGNAL(req); + +#ifdef CHECK_MASK_OVERLAP //doesn't work yet + sigset_t mask; + if (sigprocmask(SIG_BLOCK, NULL, &mask) < 0) { + goto error; + } + + sigset_t overlap; + if (sigandset(&overlap, &mask, &signal->mask) < 0) { + goto error; + } + if (!sigisemptyset(&overlap)) { + req->error = EBUSY; + return DR_ERROR; + } +#endif + + if (sigprocmask(SIG_BLOCK, &signal->mask, NULL) < 0) { + goto error; + } + + SET_OPEN(req); + Signal_Device(req, EVT_OPEN); + + return DR_DONE; + +error: + req->error = errno; + return DR_ERROR; +} + +// +// Close_Signal: C +// +DEVICE_CMD Close_Signal(REBREQ *req) +{ + struct devreq_posix_signal *signal = DEVREQ_POSIX_SIGNAL(req); + if (sigprocmask(SIG_UNBLOCK, &signal->mask, NULL) < 0) { + goto error; + } + SET_CLOSED(req); + return DR_DONE; + +error: + req->error = errno; + return DR_ERROR; +} + +// +// Read_Signal: C +// +DEVICE_CMD Read_Signal(REBREQ *req) +{ + struct timespec timeout = {0, 0}; + unsigned int i = 0; + + struct devreq_posix_signal *signal = DEVREQ_POSIX_SIGNAL(req); + errno = 0; + + for (i = 0; i < req->length; i ++) { + int result = sigtimedwait( + &signal->mask, + &(cast(siginfo_t*, req->common.data)[i]), + &timeout + ); + + if (result < 0) { + if (errno != EAGAIN && i == 0) { + Signal_Device(req, EVT_ERROR); + return DR_ERROR; + } else { + break; + } + } + } + + req->actual = i; + if (i > 0) { + //printf("read %d signals\n", req->actual); + Signal_Device(req, EVT_READ); + return DR_DONE; + } else { + return DR_PEND; + } +} + + +/*********************************************************************** +** +** Command Dispatch Table (RDC_ enum order) +** +***********************************************************************/ + +static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = +{ + 0, + 0, + Open_Signal, + Close_Signal, + Read_Signal, + 0, + 0, +}; + +DEFINE_DEV(Dev_Signal, "Signal", 1, Dev_Cmds, RDC_MAX); diff --git a/src/os/linux/host-browse.c b/src/os/linux/host-browse.c new file mode 100644 index 0000000000..9053a4f19d --- /dev/null +++ b/src/os/linux/host-browse.c @@ -0,0 +1,167 @@ +// +// File: %host-browse.c +// Summary: "Browser Launch Host" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This provides the ability to launch a web browser or file +// browser on the host. +// + +#ifndef __cplusplus + // See feature_test_macros(7) + // This definition is redundant under C++ + #define _GNU_SOURCE +#endif + +#include +#include +#include +#include /* Obtain O_* constant definitions */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "reb-host.h" + + +#ifndef PATH_MAX +#define PATH_MAX 4096 // generally lacking in Posix +#endif + +#ifdef USE_GTK_FILECHOOSER +int os_create_file_selection (void *libgtk, + char *buf, + int len, + const char *title, + const char *path, + int save, + int multiple); + +int os_init_gtk(void *libgtk); +#endif + +void OS_Destroy_Graphics(void); + + +// +// OS_Get_Current_Dir: C +// +// Return the current directory path as a string and +// its length in chars (not bytes). +// +// The result should be freed after copy/conversion. +// +int OS_Get_Current_Dir(REBCHR **path) +{ + *path = OS_ALLOC_N(char, PATH_MAX); + if (!getcwd(*path, PATH_MAX-1)) *path[0] = 0; + return strlen(*path); +} + + +// +// OS_Set_Current_Dir: C +// +// Set the current directory to local path. Return FALSE +// on failure. +// +REBOOL OS_Set_Current_Dir(REBCHR *path) +{ + return LOGICAL(chdir(path) == 0); +} + + +// +// OS_Request_File: C +// +REBOOL OS_Request_File(REBRFR *fr) +{ +#ifdef USE_GTK_FILECHOOSER + REBINT error; + const char * libs [] = { + "libgtk-3.so", + "libgtk-3.so.0", /* Some systems, like Ubuntu, don't have libgtk-3.so */ + NULL + }; + const char **ptr = NULL; + void *libgtk = NULL; + for (ptr = &libs[0]; *ptr != NULL; ptr ++) { + libgtk = OS_Open_Library(*ptr, &error); + if (libgtk != NULL) { + break; + } + } + + if (libgtk == NULL) { + //printf("open libgtk-3.so failed: %s\n", dlerror()); + return FALSE; + } + if (!os_init_gtk(libgtk)) { + //printf("init gtk failed\n"); + OS_Close_Library(libgtk); + return FALSE; + } + + REBOOL ret = FALSE; + if (os_create_file_selection(libgtk, + fr->files, + fr->len, + fr->title, + fr->dir, + GET_FLAG(fr->flags, FRF_SAVE), + GET_FLAG(fr->flags, FRF_MULTI))) { + //printf("file opened returned\n"); + ret = TRUE; + } + OS_Close_Library(libgtk); + return ret; +#else + UNUSED(fr); + return FALSE; +#endif +} + + +// +// OS_Request_Dir: C +// +// WARNING: TEMPORARY implementation! Used only by host-core.c +// Will be most probably changed in future. +// +REBOOL OS_Request_Dir(REBCHR* title, REBCHR** folder, REBCHR* path) +{ + UNUSED(title); + UNUSED(folder); + UNUSED(path); + + return FALSE; +} diff --git a/src/os/linux/host-event.c b/src/os/linux/host-event.c new file mode 100644 index 0000000000..7921e668de --- /dev/null +++ b/src/os/linux/host-event.c @@ -0,0 +1,903 @@ +/*********************************************************************** +** +** REBOL [R3] Language Interpreter and Run-time Environment +** +** Copyright 2012 Atronix Engineering +** Copyright 2012-2017 Rebol Open Source Contributors +** REBOL is a trademark of REBOL Technologies +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +** +************************************************************************ +** +** Title: Device: Event handler for X window +** Purpose: +** Processes X events to pass to REBOL +*/ + +#include +#include +#include +#include +#include + +#include "reb-host.h" + +#include "host-window.h" +#include "host-compositor.h" +#include "keysym2ucs.h" + +enum { + BUTTON_LEFT = 1, + BUTTON_MIDDLE = 2, + BUTTON_RIGHT = 3, + BUTTON_SCROLL_UP = 4, + BUTTON_SCROLL_DOWN = 5, + BUTTON_SCROLL_LEFT = 6, + BUTTON_SCROLL_RIGHT = 7 +}; + +extern x_info_t *global_x_info; +REBGOB *Find_Gob_By_Window(Window win); +host_window_t *Find_Host_Window_By_ID(Window win); +void* Find_Compositor(REBGOB *gob); +REBEVT *RL_Find_Event (REBINT model, REBINT type); + +typedef struct rebcmp_ctx REBCMP_CTX; +void rebcmp_blit_region(REBCMP_CTX* ctx, Region reg); +//void rebcmp_compose_region(REBCMP_CTX* ctx, REBGOB* winGob, REBGOB* gob, XRectangle *rect, REBOOL only); +#define GOB_HWIN(gob) ((host_window_t*)Find_Window(gob)) + +#define GOB_COMPOSITOR(gob) (Find_Compositor(gob)) //gets handle to window's compositor +#define DOUBLE_CLICK_DIFF 300 /* in milliseconds */ + +#define MAX_WINDOWS 64 +static REBGOB *resize_events[MAX_WINDOWS]; + +// Virtual key conversion table, sorted by first column. +static const REBCNT keysym_to_event[] = { + /* 0xff09 */ XK_Tab, EVK_NONE, //EVK_NONE means it is passed 'as-is' + /* 0xff50 */ XK_Home, EVK_HOME, + /* 0xff51 */ XK_Left, EVK_LEFT, + /* 0xff52 */ XK_Up, EVK_UP, + /* 0xff53 */ XK_Right, EVK_RIGHT, + /* 0xff54 */ XK_Down, EVK_DOWN, + /* 0xff55 */ XK_Page_Up, EVK_PAGE_UP, + /* 0xff56 */ XK_Page_Down, EVK_PAGE_DOWN, + /* 0xff57 */ XK_End, EVK_END, + /* 0xff63 */ XK_Insert, EVK_INSERT, + + /* 0xff91 */ XK_KP_F1, EVK_F1, + /* 0xff92 */ XK_KP_F2, EVK_F2, + /* 0xff93 */ XK_KP_F3, EVK_F3, + /* 0xff94 */ XK_KP_F4, EVK_F4, + /* 0xff95 */ XK_KP_Home, EVK_HOME, + /* 0xff96 */ XK_KP_Left, EVK_LEFT, + /* 0xff97 */ XK_KP_Up, EVK_UP, + /* 0xff98 */ XK_KP_Right, EVK_RIGHT, + /* 0xff99 */ XK_KP_Down, EVK_DOWN, + /* 0xff9a */ XK_KP_Page_Up, EVK_PAGE_UP, + /* 0xff9b */ XK_KP_Page_Down, EVK_PAGE_DOWN, + /* 0xff9c */ XK_KP_End, EVK_END, + /* 0xff9e */ XK_KP_Insert, EVK_INSERT, + /* 0xff9f */ XK_KP_Delete, EVK_DELETE, + + /* 0xffbe */ XK_F1, EVK_F1, + /* 0xffbf */ XK_F2, EVK_F2, + /* 0xffc0 */ XK_F3, EVK_F3, + /* 0xffc1 */ XK_F4, EVK_F4, + /* 0xffc2 */ XK_F5, EVK_F5, + /* 0xffc3 */ XK_F6, EVK_F6, + /* 0xffc4 */ XK_F7, EVK_F7, + /* 0xffc5 */ XK_F8, EVK_F8, + /* 0xffc6 */ XK_F9, EVK_F9, + /* 0xffc7 */ XK_F10, EVK_F10, + /* 0xffc8 */ XK_F11, EVK_F11, + /* 0xffc9 */ XK_F12, EVK_F12, + /* 0xffff */ XK_Delete, EVK_DELETE, + 0x0, 0 + +}; + +static const REBCNT keysym_to_event_fallback[] = { + /* 0xfe20 */ XK_ISO_Left_Tab, 0x09, //Tab + 0x0, 0 +}; + +static void Add_Event_XY(REBGOB *gob, REBINT id, REBINT xy, REBINT flags) +{ + REBEVT evt; + + memset(&evt, 0, sizeof(evt)); + evt.type = id; + evt.flags = (u8) (flags | (1<display, ev->xproperty.atom); + printf("Property (%s, %d) changed: %d\n", target, ev->xproperty.atom, ev->xproperty.state); + XFree(target); + */ + Atom XA_WM_STATE = x_atom_list_find_atom(global_x_info->x_atom_list, + global_x_info->display, + "_NET_WM_STATE", + False); + Atom XA_FULLSCREEN = x_atom_list_find_atom(global_x_info->x_atom_list, + global_x_info->display, + "_NET_WM_STATE_FULLSCREEN", + False); + Atom XA_MAX_HORZ = x_atom_list_find_atom(global_x_info->x_atom_list, + global_x_info->display, + "_NET_WM_STATE_MAXIMIZED_HORZ", + False); + Atom XA_MAX_VERT = x_atom_list_find_atom(global_x_info->x_atom_list, + global_x_info->display, + "_NET_WM_STATE_MAXIMIZED_VERT", + False); + Atom XA_ABOVE = x_atom_list_find_atom(global_x_info->x_atom_list, + global_x_info->display, + "_NET_WM_STATE_ABOVE", + False); + Atom XA_HIDDEN = x_atom_list_find_atom(global_x_info->x_atom_list, + global_x_info->display, + "_NET_WM_STATE_HIDDEN", + False); + if (!XA_WM_STATE + || !XA_FULLSCREEN + || !XA_MAX_HORZ + || !XA_MAX_VERT + || gob == NULL){ + return; + } + + //printf("XA_WM_STATE: %d\n", XA_WM_STATE); + + if (ev->xproperty.atom == XA_WM_STATE) { + Atom actual_type; + int actual_format; + long nitems; + long bytes; + Atom *data = NULL; + int i = 0; + int maximized_horz = 0; + int maximized_vert = 0; + int fullscreen = 0; + int on_top = 0; + int hidden = 0; + int old_maximized = GET_GOB_FLAG(gob, GOBF_MAXIMIZE); + int old_fullscreen = GET_GOB_FLAG(gob, GOBF_FULLSCREEN); + int old_hidden = GET_GOB_FLAG(gob, GOBF_HIDDEN); + host_window_t *hw = GOB_HWIN(gob); + XGetWindowProperty(global_x_info->display, + ev->xproperty.window, + XA_WM_STATE, + 0, + (~0L), + False, + XA_ATOM, + &actual_type, + &actual_format, + &nitems, + &bytes, + (unsigned char**)&data); + for(i = 0; i < nitems; i ++){ + if (data[i] == XA_FULLSCREEN){ + //printf("Window %x is Fullscreen\n", ev->xproperty.window); + fullscreen = 1; + } else if (data[i] == XA_MAX_HORZ) { + maximized_horz = 1; + } else if (data[i] == XA_MAX_VERT) { + maximized_vert = 1; + } else if (data[i] == XA_ABOVE) { + on_top = 1; + } else if (data[i] == XA_HIDDEN) { + hidden = 1; + } + } + + if (data != NULL){ + XFree(data); + } + + if (fullscreen) { + CLR_GOB_FLAG(gob, GOBF_MAXIMIZE); + SET_GOB_FLAG(gob, GOBF_FULLSCREEN); + } else { + //printf("Not fullscreen\n"); + CLR_GOB_FLAG(gob, GOBF_FULLSCREEN); + } + + if (maximized_horz && maximized_vert) { + CLR_GOB_FLAG(gob, GOBF_FULLSCREEN); + SET_GOB_FLAG(gob, GOBF_MAXIMIZE); + } else { + //printf("Not maxed\n"); + CLR_GOB_FLAG(gob, GOBF_MAXIMIZE); + } + + if (on_top) { + SET_GOB_FLAG(gob, GOBF_TOP); + } else { + //printf("Not no_top\n"); + CLR_GOB_FLAG(gob, GOBF_TOP); + } + + if (hidden) { + SET_GOB_FLAG(gob, GOBF_HIDDEN); + } else { + //printf("Not hidden\n"); + CLR_GOB_FLAG(gob, GOBF_HIDDEN); + } + hw->window_flags = gob->flags; /* save a copy of current window flags */ + } else { + //printf("Not WM_STATE, ignoring\n"); + } +} + +static void handle_button(XEvent *ev, REBGOB *gob) +{ + //printf("Button %d event at %d\n", ev->xbutton.button, ev->xbutton.time); + static Time last_click = 0; + static REBINT last_click_button = 0; + // Handle XEvents and flush the input + REBINT xyd = 0; + REBEVT *evt = NULL; + xyd = (ROUND_TO_INT(PHYS_COORD_X(ev->xbutton.x))) + (ROUND_TO_INT(PHYS_COORD_Y(ev->xbutton.y)) << 16); + REBINT id = 0, flags = 0; + flags = Check_Modifiers(0, ev->xbutton.state); + if (ev->xbutton.button < 4) { + if (ev->type == ButtonPress + && last_click_button == ev->xbutton.button + && ev->xbutton.time - last_click < DOUBLE_CLICK_DIFF){ + /* FIXME, a hack to detect double click: a double click would be a single click followed by a double click */ + flags |= 1 << EVF_DOUBLE; + //printf("Button %d double clicked\n", ev->xbutton.button); + } + switch (ev->xbutton.button){ + case BUTTON_LEFT: + id = (ev->type == ButtonPress)? EVT_DOWN: EVT_UP; + break; + case BUTTON_MIDDLE: + id = (ev->type == ButtonPress)? EVT_AUX_DOWN: EVT_AUX_UP; + break; + case BUTTON_RIGHT: + id = (ev->type == ButtonPress)? EVT_ALT_DOWN: EVT_ALT_UP; + break; + } + Add_Event_XY(gob, id, xyd, flags); + } else { + if (ev->type == ButtonRelease) { + evt = RL_Find_Event(EVM_GUI, + ev->xbutton.state & ControlMask? EVT_SCROLL_PAGE: EVT_SCROLL_LINE); + u32 data = 0; + u32 *pdata = NULL; + i16 tmp = 0; + if (evt != NULL) { + pdata = &evt->data; + } else { + pdata = &data; + } + int mw_num_lines = 3; + + if (ev->xbutton.button == BUTTON_SCROLL_UP + || ev->xbutton.button == BUTTON_SCROLL_DOWN) { + tmp = *pdata >> 16; + } else if (ev->xbutton.button == BUTTON_SCROLL_LEFT + || ev->xbutton.button == BUTTON_SCROLL_RIGHT) { + tmp = *pdata & 0xFFFF; + } else { + return; + } + + if (ev->xbutton.button == BUTTON_SCROLL_UP + || ev->xbutton.button == BUTTON_SCROLL_RIGHT) { + if (tmp < 0){ + tmp = 0; + } + if (tmp <= 0x7FFF - mw_num_lines) { /* avoid overflow */ + tmp += mw_num_lines; + } + } else if (ev->xbutton.button == BUTTON_SCROLL_DOWN + || ev->xbutton.button == BUTTON_SCROLL_LEFT) { + tmp = *pdata & 0xFFFF; + if (tmp > 0){ + tmp = 0; + } + if (tmp > -0x8000 + mw_num_lines) { /* avoid overflow */ + tmp -= mw_num_lines; + } + } + + if (ev->xbutton.button == BUTTON_SCROLL_UP + || ev->xbutton.button == BUTTON_SCROLL_DOWN) { + *pdata = (tmp << 16) | (*pdata & 0xFFFF); /* do not touch low 16-bit */ + } else if (ev->xbutton.button == BUTTON_SCROLL_LEFT + || ev->xbutton.button == BUTTON_SCROLL_RIGHT) { + *pdata = (tmp & 0xFFFF) | (*pdata & 0xFFFF0000); /* do not touch high 16-bit */ + } + + if (evt == NULL) { + Add_Event_XY(gob, + ev->xbutton.state & ControlMask? EVT_SCROLL_PAGE: EVT_SCROLL_LINE, + data, 0); + } + } + } + if (ev->type == ButtonPress) { + last_click_button = ev->xbutton.button; + last_click = ev->xbutton.time; + } +} + +static void handle_client_message(XEvent *ev) +{ + /* + const REBYTE *message_type = XGetAtomName(global_x_info->display, ev->xclient.message_type); + const REBYTE *protocol = XGetAtomName(global_x_info->display, ev->xclient.data.l[0]); + printf("client message: %s, %s\n", message_type, protocol); + XFree(message_type); + XFree(protocol); + */ + Atom XA_DELETE_WINDOW = x_atom_list_find_atom(global_x_info->x_atom_list, + global_x_info->display, + "WM_DELETE_WINDOW", + False); + Atom XA_PING = x_atom_list_find_atom(global_x_info->x_atom_list, + global_x_info->display, + "_NET_WM_PING", + False); + REBGOB *gob = NULL; + if (XA_DELETE_WINDOW + && XA_DELETE_WINDOW == ev->xclient.data.l[0]) { + gob = Find_Gob_By_Window(ev->xclient.window); + if (gob != NULL){ + Add_Event_XY(gob, EVT_CLOSE, 0, 0); + } + } else if (XA_PING + && XA_PING == ev->xclient.data.l[0]) { + //printf("Ping from window manager\n"); + ev->xclient.window = DefaultRootWindow(global_x_info->display); + XSendEvent(global_x_info->display, + ev->xclient.window, + False, + (SubstructureNotifyMask | SubstructureRedirectMask), + ev); + } +} + +static void handle_selection_request(XEvent *ev) +{ + XEvent selection_event; +#if 0 + const REBYTE *target = XGetAtomName(global_x_info->display, ev->xselectionrequest.target); + const REBYTE *property = XGetAtomName(global_x_info->display, ev->xselectionrequest.property); + printf("selection target = %s, property = %s\n", target, property); + XFree((void*)property); + XFree((void*)target); +#endif + Atom XA_UTF8_STRING = x_atom_list_find_atom(global_x_info->x_atom_list, + global_x_info->display, + "UTF8_STRING", + True); + Atom XA_TARGETS = x_atom_list_find_atom(global_x_info->x_atom_list, + global_x_info->display, + "TARGETS", + True); + Atom XA_CLIPBOARD = x_atom_list_find_atom(global_x_info->x_atom_list, + global_x_info->display, + "CLIPBOARD", + True); + selection_event.type = SelectionNotify; + if (ev->xselectionrequest.target == XA_TARGETS) { + selection_event.xselection.property = ev->xselectionrequest.property; + Atom targets[] = {XA_TARGETS, XA_UTF8_STRING, XA_STRING}; + XChangeProperty(global_x_info->display, + ev->xselectionrequest.requestor, + ev->xselectionrequest.property, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char*)&targets, + sizeof(targets)/sizeof(targets[0])); + } else if (ev->xselectionrequest.target == XA_STRING + || ev->xselectionrequest.target == XA_UTF8_STRING) { + selection_event.xselection.property = ev->xselectionrequest.property; + XChangeProperty(global_x_info->display, + ev->xselectionrequest.requestor, + ev->xselectionrequest.property, + ev->xselectionrequest.target, + 8, /* format, unsigned short */ + PropModeReplace, + global_x_info->selection.data, + global_x_info->selection.data_length); + } else { + selection_event.xselection.property = 0; + } + selection_event.xselection.send_event = 1; + selection_event.xselection.display = ev->xselectionrequest.display; + selection_event.xselection.requestor = ev->xselectionrequest.requestor; + selection_event.xselection.selection = ev->xselectionrequest.selection; + selection_event.xselection.target = ev->xselectionrequest.target; + selection_event.xselection.time = ev->xselectionrequest.time; + //printf("Sending selection_event\n"); + XSendEvent(selection_event.xselection.display, + selection_event.xselection.requestor, + False, + 0, + &selection_event); +} + +static void handle_selection_notify(XEvent *ev) +{ + Atom XA_UTF8_STRING = x_atom_list_find_atom(global_x_info->x_atom_list, + global_x_info->display, + "UTF8_STRING", + True); + Atom XA_TARGETS = x_atom_list_find_atom(global_x_info->x_atom_list, + global_x_info->display, + "TARGETS", + True); + Atom XA_CLIPBOARD = x_atom_list_find_atom(global_x_info->x_atom_list, + global_x_info->display, + "CLIPBOARD", + True); + if (ev->xselection.target == XA_TARGETS){ + Atom actual_type; + int actual_format; + long nitems; + long bytes; + Atom *data = NULL; + int status; + if (ev->xselection.property){ + status = XGetWindowProperty(ev->xselection.display, + ev->xselection.requestor, + ev->xselection.property, + 0, + (~0L), + False, + XA_ATOM, + &actual_type, + &actual_format, + &nitems, + &bytes, + (unsigned char**)&data); + int i = 0; + for(i = 0; i < nitems; i ++){ + if (data[i] == XA_UTF8_STRING + || data[i] == XA_STRING) { + XConvertSelection(ev->xselection.display, + XA_CLIPBOARD, + data[i], + ev->xselection.property, + ev->xselection.requestor, + CurrentTime); + break; + } + } + } + } else if (ev->xselection.target == XA_UTF8_STRING + || ev->xselection.target == XA_STRING) { + global_x_info->selection.property = ev->xselection.property; + global_x_info->selection.status = 1; /* response received */ + } +} + +static void handle_configure_notify(XEvent *ev, REBGOB *gob) +{ + XConfigureEvent xce = ev->xconfigure; + REBINT xyd = 0; + /* translate x,y to its gob_parent coordinates */ + int x = xce.x, y = xce.y; + /* + printf("configuranotify, window = %x, x = %d, y = %d, w = %d, h = %d\n", + xce.window, + xce.x, xce.y, xce.width, xce.height); + */ + REBGOB *gob_parent = GOB_TMP_OWNER(gob); + if (gob_parent != NULL) { + host_window_t *hw = GOB_HWIN(gob_parent); + if (hw != NULL) { + Window gob_parent_window = hw->x_id; + Window child; + if (GET_GOB_FLAG(gob, GOBF_POPUP)) { + /* for popup windows, the x, y are in screen coordinates, see OS_Create_Window */ + if (hw->x_parent_id != DefaultRootWindow(xce.display)) { + XTranslateCoordinates(xce.display, + xce.window, + DefaultRootWindow(xce.display), + 0, 0, + &x, &y, &child); + } + } else { + XTranslateCoordinates(xce.display, + xce.window, + hw->x_parent_id, + 0, 0, + &x, &y, &child); + } + //printf("XTranslateCoordinates returns %d, pos: %dx%d\n", status, x, y); + } + } + if (ROUND_TO_INT(gob->offset.x) != x + || ROUND_TO_INT(gob->offset.y) != y){ + /* + printf("%s, %s, %d: EVT_OFFSET (%dx%d) is sent\n", __FILE__, __func__, __LINE__, + ROUND_TO_INT(x), ROUND_TO_INT(y)); + */ + gob->offset.x = ROUND_TO_INT(PHYS_COORD_X(x)); + gob->offset.y = ROUND_TO_INT(PHYS_COORD_X(y)); + xyd = (ROUND_TO_INT(gob->offset.x)) + (ROUND_TO_INT(gob->offset.y) << 16); + Update_Event_XY(gob, EVT_OFFSET, xyd, 0); + /* avoid a XMoveWindow call from OS_Update_Window */ + GOB_XO(gob) = GOB_LOG_X(gob); + GOB_YO(gob) = GOB_LOG_Y(gob); + } + host_window_t* hw = Find_Host_Window_By_ID(ev->xconfigure.window); + assert(hw != NULL); + if (hw->old_width == xce.width && hw->old_height == xce.height) { + /* XResizeWindow failed, or this is a window movement */ + return; + } + gob->size.x = ROUND_TO_INT(PHYS_COORD_X(hw->old_width = xce.width)); + gob->size.y = ROUND_TO_INT(PHYS_COORD_Y(hw->old_height = xce.height)); + xyd = (ROUND_TO_INT((gob->size.x))) + (ROUND_TO_INT(gob->size.y) << 16); + if (GOB_WO_INT(gob) != GOB_LOG_W_INT(gob) + || GOB_HO_INT(gob) != GOB_LOG_H_INT(gob)) { + //printf("Resize for gob: %x to %dx%d\n", gob, GOB_LOG_W_INT(gob), GOB_LOG_H_INT(gob)); + //printf("%s, %s, %d: EVT_RESIZE is sent: %x\n", __FILE__, __func__, __LINE__, xyd); + int i = 0; + for(i = 0; i < MAX_WINDOWS; i ++){ + if (resize_events[i] == NULL){ + //printf("Filled resize_events[%d]\n", i); + resize_events[i] = gob; + if (i < MAX_WINDOWS - 1) { + resize_events[i + 1] = NULL; /* mark it the end of the array */ + } + break; + } + if (resize_events[i] == gob) + break; + } + Update_Event_XY(gob, EVT_RESIZE, xyd, 0); + } +} + +static void handle_key(XEvent *ev, REBGOB *gob) +{ + KeySym keysym; + REBINT flags = Check_Modifiers(0, ev->xkey.state); + char key_string[8]; + XComposeStatus compose_status; + int i = 0, key = -1; + int len = XLookupString(&ev->xkey, key_string, sizeof(key_string), &keysym, &compose_status); + key_string[len] = '\0'; + //RL_Print ("key %s (%x) is released\n", key_string, key_string[0]); + + for (i = 0; keysym_to_event[i] && keysym > keysym_to_event[i]; i += 2); + if (keysym == keysym_to_event[i]) { + if (keysym_to_event[i + 1] == EVK_NONE) { + key = key_string[0]; /* pass-thru */ + } else { + key = keysym_to_event[i + 1] << 16; + } + } else { + key = keysym2ucs(keysym); + if (key < 0 && len > 0){ + key = key_string[0]; /* FIXME, key_string could be longer than 1 */ + } + /* map control characters */ + if (flags & (1 << EVF_CONTROL) + && !(flags & (1 << EVF_SHIFT))) { + if (key >= 'A' && key <= '_') { + key = key - 'A' + 1; + } else if (key >= 'a' && key <= 'z') { + key = key - 'a' + 1; + } + } + } + + if (key > 0){ + Add_Event_Key(gob, + ev->type == KeyPress? EVT_KEY : EVT_KEY_UP, + key, flags); + + /* + RL_Print ("Key event %s with key %x (flags: %x) is sent\n", + ev->type == KeyPress? "EVT_KEY" : "EVT_KEY_UP", + key, + flags); + */ + } else { + for (i = 0; keysym_to_event_fallback[i] && keysym > keysym_to_event_fallback[i]; i += 2); + if (keysym == keysym_to_event_fallback[i] && keysym_to_event_fallback[i + 1] > 0) { + Add_Event_Key(gob, + ev->type == KeyPress? EVT_KEY : EVT_KEY_UP, + keysym_to_event_fallback[i + 1], flags); + + } + } +} + +static void handle_expose(XEvent *ev, REBGOB *gob) +{ + host_window_t *hw = GOB_HWIN(gob); + + XRectangle rect = {ev->xexpose.x, ev->xexpose.y, ev->xexpose.width, ev->xexpose.height}; /* in screen coordinates */ + + assert (hw != NULL); + if (hw == NULL) { + return; + } + + if (hw->exposed_region == NULL) { + hw->exposed_region = XCreateRegion(); + } + XUnionRectWithRegion(&rect, hw->exposed_region, hw->exposed_region); + if (ev->xexpose.count == 0){ + /* find wingob, copied from Draw_Window */ + REBGOB *wingob = gob; + while (GOB_PARENT(wingob) && GOB_PARENT(wingob) != Gob_Root + && GOB_PARENT(wingob) != wingob) // avoid infinite loop + wingob = GOB_PARENT(wingob); + + //check if it is really open + if (!IS_WINDOW(wingob) || !GET_GOB_STATE(wingob, GOBS_OPEN)) return; + + void *compositor = GOB_COMPOSITOR(gob); + assert (compositor != NULL); + + /* + XRectangle final_rect; + XClipBox(hw->exposed_region, &final_rect); + printf("Win Region , left: %d,\ttop: %d,\tright: %d,\tbottom: %d\n", + rect.x, + rect.y, + rect.x + rect.width, + rect.y + rect.height); + printf("exposed: x %d, y %d, w %d, h %d\n", final_rect.x, final_rect.y, final_rect.width, final_rect.height); + */ + //rebcmp_compose_region(compositor, wingob, gob, &final_rect, FALSE); + rebcmp_blit_region(compositor, hw->exposed_region); + + XDestroyRegion(hw->exposed_region); + hw->exposed_region = NULL; + } +} + +void Dispatch_Event(XEvent *ev) +{ + REBGOB *gob = NULL; + // Handle XEvents and flush the input + REBINT flags = 0; + if (resize_events[0] != NULL + && ev->type != ConfigureNotify) {/* handle expose after resizing */ + if (ev->type == Expose) { /* ignore expose after resize */ + int i = 0; + gob = Find_Gob_By_Window(ev->xexpose.window); + for (i = 0; i < MAX_WINDOWS; i ++) { + if (resize_events[i] == NULL) { + break; + } else if (resize_events[i] == gob) { + return; + } + } + } + X_Finish_Resizing(); + } + switch (ev->type) { + case CreateNotify: + /* + printf("window %x created at: %dx%d, size: %dx%d\n", + ev->xcreatewindow.window, + ev->xcreatewindow.x, ev->xcreatewindow.y, + ev->xcreatewindow.width, ev->xcreatewindow.height); + */ + break; + case Expose: + //printf("exposed\n"); + gob = Find_Gob_By_Window(ev->xexpose.window); + if (gob != NULL) { + handle_expose(ev, gob); + } + break; + + case ButtonPress: + case ButtonRelease: + gob = Find_Gob_By_Window(ev->xbutton.window); + if (gob != NULL) + handle_button(ev, gob); + break; + + case MotionNotify: + //printf("mouse motion\n"); + gob = Find_Gob_By_Window(ev->xmotion.window); + if (gob != NULL){ + REBINT xyd = (ROUND_TO_INT(PHYS_COORD_X(ev->xmotion.x))) + (ROUND_TO_INT(PHYS_COORD_Y(ev->xmotion.y)) << 16); + Update_Event_XY(gob, EVT_MOVE, xyd, 0); + } + break; + case KeyPress: + case KeyRelease: + gob = Find_Gob_By_Window(ev->xkey.window); + if(gob != NULL) + handle_key(ev, gob); + + break; + case ResizeRequest: + //RL_Print ("request to resize to %dx%d", ev->xresizerequest.width, ev->xresizerequest.height); + break; + case FocusIn: + if (ev->xfocus.mode != NotifyWhileGrabbed) { + //RL_Print ("FocusIn, type = %d, window = %x\n", ev->xfocus.type, ev->xfocus.window); + gob = Find_Gob_By_Window(ev->xfocus.window); + if (gob && !GET_GOB_STATE(gob, GOBS_ACTIVE)) { + SET_GOB_STATE(gob, GOBS_ACTIVE); + Add_Event_XY(gob, EVT_ACTIVE, 0, 0); + } + } + break; + case FocusOut: + if (ev->xfocus.mode != NotifyWhileGrabbed) { + //RL_Print ("FocusOut, type = %d, window = %x\n", ev->xfocus.type, ev->xfocus.window); + gob = Find_Gob_By_Window(ev->xfocus.window); + if (gob && GET_GOB_STATE(gob, GOBS_ACTIVE)) { + CLR_GOB_STATE(gob, GOBS_ACTIVE); + Add_Event_XY(gob, EVT_INACTIVE, 0, 0); + } + } + break; + case DestroyNotify: + //RL_Print ("destroyed %x\n", ev->xdestroywindow.window); + gob = Find_Gob_By_Window(ev->xdestroywindow.window); + if (gob != NULL){ + host_window_t *hw = GOB_HWIN(gob); + if (hw != NULL) { + OS_FREE(hw); + } + CLR_GOB_STATE(gob, GOBS_OPEN); + CLR_GOB_STATE(gob, GOBS_ACTIVE); + Free_Window(gob); + } + break; + case ClientMessage: + //printf("closed\n"); + handle_client_message(ev); + break; + case PropertyNotify: + /* check if it's fullscreen */ + gob = Find_Gob_By_Window(ev->xproperty.window); /*this event could come after window is free'ed */ + if (gob != NULL) + handle_property_notify(ev, gob); + break; + case ConfigureNotify: + gob = Find_Gob_By_Window(ev->xconfigure.window); + if (gob != NULL) { + handle_configure_notify(ev, gob); + } + break; + case SelectionRequest: + //printf("SelectionRequest\n"); + handle_selection_request(ev); + break; + case SelectionNotify: + //printf("SelectionNotify\n"); + handle_selection_notify(ev); + break; + case SelectionClear: + if (global_x_info->selection.data != NULL) { + OS_FREE(global_x_info->selection.data); + global_x_info->selection.data = NULL; + global_x_info->selection.data_length = 0; + } + break; + case MapNotify: + //printf("Window %x is mapped\n", ev->xmap.window); + { + host_window_t *hw = Find_Host_Window_By_ID(ev->xmap.window); + if (hw != NULL) { + hw->mapped = 1; + } + } + break; + case ReparentNotify: + //printf("Window %x is reparented to %x\n", ev->xreparent.window, ev->xreparent.parent); + { + host_window_t *hw = Find_Host_Window_By_ID(ev->xreparent.window); + if (hw != NULL) { + hw->x_parent_id = ev->xreparent.parent; + } + } + break; + default: + //printf("default event type: %d\n", ev->type); + break; + } +} + +void X_Event_Loop(int at_most) +{ + XEvent ev; + int n = 0; + if (global_x_info->display == NULL) { + return; + } + X_Init_Resizing(); + while(XPending(global_x_info->display) && (at_most < 0 || n < at_most)) { + ++ n; + XNextEvent(global_x_info->display, &ev); + Dispatch_Event(&ev); + } + X_Finish_Resizing(); +} + diff --git a/src/os/osx/host-exec-path.c b/src/os/osx/host-exec-path.c new file mode 100644 index 0000000000..6e5cb16ab1 --- /dev/null +++ b/src/os/osx/host-exec-path.c @@ -0,0 +1,95 @@ +// +// File: %host-exec-path.c +// Summary: "Executable Path" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// + +#include +#include +#include +#include + +// Should include , but it conflicts with reb-c.h because both defined TRUE and FALSE +#ifdef __cplusplus +extern "C" +#endif +int _NSGetExecutablePath(char* buf, uint32_t* bufsize); + +#include "reb-host.h" + + +// +// OS_Get_Current_Exec: C +// +// Return the current executable path as a string and +// its length in chars (not bytes). +// +// The result should be freed after copy/conversion. +// +int OS_Get_Current_Exec(REBCHR **path) +{ + assert (sizeof(REBCHR) == sizeof(char)); + + uint32_t path_size = 1024; + + *path = OS_ALLOC_N(REBCHR, path_size); + if (*path == NULL) return -1; + int r = _NSGetExecutablePath(*path, &path_size); + if (r == -1) { + // buffer is too small, path_size is set to the required size + assert(path_size > 1024); + + OS_FREE(*path); + *path = OS_ALLOC_N(REBCHR, path_size); + if (*path == NULL) return -1; + int r = _NSGetExecutablePath(*path, &path_size); + if (r != 0) { + OS_FREE(*path); + return -1; + } + } + + // _NSGetExecutablePath returns "a path" not a "real path", and it could be + // a symbolic link. + REBCHR *resolved_path = realpath(*path, NULL); + if (resolved_path != NULL) { + // resolved_path needs to be free'd by free, which might be different from OS_FREE, + // make a copy using memory from OS_ALLOC_N, such that the caller can call OS_FREE. + OS_FREE(*path); + REBCNT len = OS_STRLEN(resolved_path); + *path = OS_ALLOC_N(REBCHR, len + 1); + OS_STRNCPY(*path, resolved_path, len); + (*path)[len] = '\0'; + + free(resolved_path); + + return len; + } else { + // Failed to resolve, just return the unresolved path. + return OS_STRLEN(*path); + } +} diff --git a/src/os/posix/dev-event.c b/src/os/posix/dev-event.c index 29b9eaf5ce..a1b9f1cd13 100644 --- a/src/os/posix/dev-event.c +++ b/src/os/posix/dev-event.c @@ -1,146 +1,144 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Device: Event handler for Win32 -** Author: Carl Sassenrath -** Purpose: -** Processes events to pass to REBOL. Note that events are -** used for more than just windowing. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ +// +// File: %dev-event.c +// Summary: "Device: Event handler for Posix" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Processes events to pass to REBOL. Note that events are +// used for more than just windowing. +// +//=////////////////////////////////////////////////////////////////////////=// +// #include #include #include #include +#include #include "reb-host.h" -#include "host-lib.h" -void Done_Device(int handle, int error); - -/*********************************************************************** -** -*/ DEVICE_CMD Init_Events(REBREQ *dr) -/* -** Initialize the event device. -** -** Create a hidden window to handle special events, -** such as timers and async DNS. -** -***********************************************************************/ +extern void Done_Device(REBUPT handle, int error); +extern i32 Request_Size_Rebreq(REBREQ *req); + +// +// Init_Events: C +// +// Initialize the event device. +// +// Create a hidden window to handle special events, +// such as timers and async DNS. +// +DEVICE_CMD Init_Events(REBREQ *dr) { - REBDEV *dev = (REBDEV*)dr; // just to keep compiler happy - SET_FLAG(dev->flags, RDF_INIT); - return DR_DONE; + REBDEV *dev = (REBDEV*)dr; // just to keep compiler happy + SET_FLAG(dev->flags, RDF_INIT); + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Poll_Events(REBREQ *req) -/* -** Poll for events and process them. -** Returns 1 if event found, else 0. -** -** MS Notes: -** -** "The PeekMessage function normally does not remove WM_PAINT -** messages from the queue. WM_PAINT messages remain in the queue -** until they are processed." -** -***********************************************************************/ +// +// Poll_Events: C +// +// Poll for events and process them. +// Returns 1 if event found, else 0. +// +DEVICE_CMD Poll_Events(REBREQ *req) { - int flag = DR_DONE; - return flag; // different meaning compared to most commands + UNUSED(req); + + int flag = DR_DONE; + return flag; // different meaning compared to most commands } -/*********************************************************************** -** -*/ DEVICE_CMD Query_Events(REBREQ *req) -/* -** Wait for an event, or a timeout (in milliseconds) specified by -** req->length. The latter is used by WAIT as the main timing -** method. -** -***********************************************************************/ +// +// Query_Events: C +// +// Wait for an event, or a timeout (in milliseconds) specified by +// req->length. The latter is used by WAIT as the main timing +// method. +// +DEVICE_CMD Query_Events(REBREQ *req) { - struct timeval tv; - int result; - - tv.tv_sec = 0; - tv.tv_usec = req->length * 1000; - //printf("usec %d\n", tv.tv_usec); - - result = select(0, 0, 0, 0, &tv); - if (result < 0) { - // !!! set error code - printf("ERROR!!!!\n"); - return DR_ERROR; - } - - return DR_DONE; + struct timeval tv; + int result; + + tv.tv_sec = 0; + tv.tv_usec = req->length * 1000; + //printf("usec %d\n", tv.tv_usec); + + result = select(0, 0, 0, 0, &tv); + if (result < 0) { + // + // !!! In R3-Alpha this had a TBD that said "set error code" and had a + // printf that said "ERROR!!!!". However this can happen when a + // Ctrl-C interrupts a timer on a WAIT. As a patch this is tolerant + // of EINTR, but still returns the error code. :-/ + // + if (errno == EINTR) + return DR_ERROR; + + printf("select() returned -1 in dev-event.c (I/O error!)\n"); + return DR_ERROR; + } + + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Connect_Events(REBREQ *req) -/* -** Simply keeps the request pending for polling purposes. -** Use Abort_Device to remove it. -** -***********************************************************************/ +// +// Connect_Events: C +// +// Simply keeps the request pending for polling purposes. +// Use Abort_Device to remove it. +// +DEVICE_CMD Connect_Events(REBREQ *req) { - return DR_PEND; // keep pending + UNUSED(req); + return DR_PEND; // keep pending } /*********************************************************************** ** -** Command Dispatch Table (RDC_ enum order) +** Command Dispatch Table (RDC_ enum order) ** ***********************************************************************/ static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = { - Init_Events, // init device driver resources - 0, // RDC_QUIT, // cleanup device driver resources - 0, // RDC_OPEN, // open device unit (port) - 0, // RDC_CLOSE, // close device unit - 0, // RDC_READ, // read from unit - 0, // RDC_WRITE, // write to unit - Poll_Events, - Connect_Events, - Query_Events, + Request_Size_Rebreq, + Init_Events, // init device driver resources + 0, // RDC_QUIT, // cleanup device driver resources + 0, // RDC_OPEN, // open device unit (port) + 0, // RDC_CLOSE, // close device unit + 0, // RDC_READ, // read from unit + 0, // RDC_WRITE, // write to unit + Poll_Events, + Connect_Events, + Query_Events, }; -DEFINE_DEV(Dev_Event, "OS Events", 1, Dev_Cmds, RDC_MAX, 0); +DEFINE_DEV(Dev_Event, "OS Events", 1, Dev_Cmds, RDC_MAX); diff --git a/src/os/posix/dev-file.c b/src/os/posix/dev-file.c index a2d3c56958..2b0f20a8c3 100644 --- a/src/os/posix/dev-file.c +++ b/src/os/posix/dev-file.c @@ -1,41 +1,50 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Device: File access for Posix -** Author: Carl Sassenrath -** Purpose: File open, close, read, write, and other actions. -** -** Compile note: -D_FILE_OFFSET_BITS=64 to support large files -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ +// +// File: %dev-file.c +// Summary: "Device: File access for Posix" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// File open, close, read, write, and other actions. +// +// -D_FILE_OFFSET_BITS=64 to support large files +// + +// ftruncate is not a standard C function, but as we are using it then +// we have to use a special define if we want standards enforcement. +// By defining it as the first header file we include, we ensure another +// inclusion of won't be made without the definition first. +// +// http://stackoverflow.com/a/26806921/211160 +// +#define _XOPEN_SOURCE 500 + +// !!! See notes on why this is needed on #define HAS_POSIX_SIGNAL in +// reb-config.h (similar reasons, and means this file cannot be +// compiled as --std=c99 but rather --std=gnu99) +// +#define _POSIX_C_SOURCE 199309L #include #include @@ -45,477 +54,523 @@ #include #include #include +#include #include "reb-host.h" -#include "host-lib.h" #ifndef O_BINARY #define O_BINARY 0 #endif -// The BSD legacy names S_IREAD/S_IWRITE are not defined on e.g. Android. + +// The BSD legacy names S_IREAD/S_IWRITE are not defined several places. +// That includes building on Android, or if you compile as C99. + #ifndef S_IREAD -#define S_IREAD S_IRUSR + #define S_IREAD S_IRUSR #endif + #ifndef S_IWRITE -#define S_IWRITE S_IWUSR + #define S_IWRITE S_IWUSR #endif -// NOTE: the code below assumes a file id will never by zero. This should -// be safe. In posix, zero is stdin, which is handled by dev-stdio.c. +// NOTE: the code below assumes a file id will never be zero. In POSIX, +// 0 represents standard input...which is handled by dev-stdio.c. +// Though 0 for stdin is a POSIX standard, many C compilers define +// STDIN_FILENO, STDOUT_FILENO, STDOUT_FILENO. These may be set to +// different values in unusual circumstances, such as emscripten builds. /*********************************************************************** ** -** Local Functions +** Local Functions ** ***********************************************************************/ -#ifndef DT_DIR // dirent.d_type is a BSD extension, actually not part of POSIX // reformatted from: http://ports.haiku-files.org/wiki/CommonProblems +// this comes from: http://ports.haiku-files.org/wiki/CommonProblems +// modified for reformatting and to not use a variable-length-array static int Is_Dir(const char *path, const char *name) { - int len1 = strlen(path); - int len2 = strlen(name); - struct stat st; + int len_path = strlen(path); + int len_name = strlen(name); + struct stat st; + + // !!! No clue why + 13 is needed, and not sure I want to know. + // It was in the original code, not second-guessing ATM. --@HF + char *pathname = OS_ALLOC_N(char, len_path + 1 + len_name + 1 + 13); - char pathname[len1 + 1 + len2 + 1 + 13]; - strcpy(pathname, path); + strcpy(pathname, path); - /* Avoid UNC-path "//name" on Cygwin. */ - if (len1 > 0 && pathname[len1 - 1] != '/') - strcat(pathname, "/"); + /* Avoid UNC-path "//name" on Cygwin. */ + if (len_path > 0 && pathname[len_path - 1] != '/') + strcat(pathname, "/"); - strcat(pathname, name); + strcat(pathname, name); - if (stat(pathname, &st)) - return 0; + if (stat(pathname, &st)) { + OS_FREE(pathname); + return 0; + } - return S_ISDIR(st.st_mode); + OS_FREE(pathname); + return S_ISDIR(st.st_mode); } -#endif -static REBOOL Seek_File_64(REBREQ *file) + +static REBOOL Seek_File_64(struct devreq_file *file) { - // Performs seek and updates index value. TRUE on success. - // On error, returns FALSE and sets file->error field. - int h = file->id; - i64 result; - - if (file->file.index == -1) { - // Append: - result = lseek(h, 0, SEEK_END); - } - else { - result = lseek(h, file->file.index, SEEK_SET); - } - - if (result < 0) { - file->error = -RFE_NO_SEEK; - return 0; - } - - file->file.index = result; - - return 1; + // Performs seek and updates index value. TRUE on success. + // On error, returns FALSE and sets req->error field. + REBREQ *req = AS_REBREQ(file); + + int h = req->requestee.id; + i64 result; + + if (file->index == -1) { + // Append: + result = lseek(h, 0, SEEK_END); + } + else { + result = lseek(h, file->index, SEEK_SET); + } + + if (result < 0) { + req->error = -RFE_NO_SEEK; + return FALSE; + } + + file->index = result; + + return TRUE; } -static int Get_File_Info(REBREQ *file) + +static int Get_File_Info(struct devreq_file *file) { - struct stat info; - - if (stat(file->file.path, &info)) { - file->error = errno; - return DR_ERROR; - } - - if (S_ISDIR(info.st_mode)) { - SET_FLAG(file->modes, RFM_DIR); - file->file.size = 0; // in order to be consistent on all systems - } - else { - CLR_FLAG(file->modes, RFM_DIR); - file->file.size = info.st_size; - } - file->file.time.l = (long)(info.st_mtime); - - return DR_DONE; + struct stat info; + + REBREQ *req = AS_REBREQ(file); + + if (stat(file->path, &info)) { + req->error = errno; + return DR_ERROR; + } + + if (S_ISDIR(info.st_mode)) { + SET_FLAG(req->modes, RFM_DIR); + file->size = 0; // in order to be consistent on all systems + } + else { + CLR_FLAG(req->modes, RFM_DIR); + file->size = info.st_size; + } + file->time.l = cast(long, info.st_mtime); + + return DR_DONE; } -/*********************************************************************** -** -*/ static int Read_Directory(REBREQ *dir, REBREQ *file) -/* -** This function will read a file directory, one file entry -** at a time, then close when no more files are found. -** -** Procedure: -** -** This function is passed directory and file arguments. -** The dir arg provides information about the directory to read. -** The file arg is used to return specific file information. -** -** To begin, this function is called with a dir->handle that -** is set to zero and a dir->file.path string for the directory. -** -** The directory is opened and a handle is stored in the dir -** structure for use on subsequent calls. If an error occurred, -** dir->error is set to the error code and -1 is returned. -** The dir->size field can be set to the number of files in the -** dir, if it is known. The dir->file.index field can be used by this -** function to store information between calls. -** -** If the open succeeded, then information about the first file -** is stored in the file argument and the function returns 0. -** On an error, the dir->error is set, the dir is closed, -** dir->handle is nulled, and -1 is returned. -** -** The caller loops until all files have been obtained. This -** action should be uninterrupted. (The caller should not perform -** additional OS or IO operations between calls.) -** -** When no more files are found, the dir is closed, dir->handle -** is nulled, and 1 is returned. No file info is returned. -** (That is, this function is called one extra time. This helps -** for OSes that may deallocate file strings on dir close.) -** -** Note that the dir->file.path can contain wildcards * and ?. The -** processing of these can be done in the OS (if supported) or -** by a separate filter operation during the read. -** -** Store file date info in file->file.index or other fields? -** Store permissions? Ownership? Groups? Or, require that -** to be part of a separate request? -** -***********************************************************************/ +// +// Read_Directory: C +// +// This function will read a file directory, one file entry +// at a time, then close when no more files are found. +// +// Procedure: +// +// This function is passed directory and file arguments. +// The dir arg provides information about the directory to read. +// The file arg is used to return specific file information. +// +// To begin, this function is called with a dir->requestee.handle that +// is set to zero and a dir->path string for the directory. +// +// The directory is opened and a handle is stored in the dir +// structure for use on subsequent calls. If an error occurred, +// dir->error is set to the error code and -1 is returned. +// The dir->size field can be set to the number of files in the +// dir, if it is known. The dir->index field can be used by this +// function to store information between calls. +// +// If the open succeeded, then information about the first file +// is stored in the file argument and the function returns 0. +// On an error, the dir->error is set, the dir is closed, +// dir->requestee.handle is nulled, and -1 is returned. +// +// The caller loops until all files have been obtained. This +// action should be uninterrupted. (The caller should not perform +// additional OS or IO operations between calls.) +// +// When no more files are found, the dir is closed, dir->requestee.handle +// is nulled, and 1 is returned. No file info is returned. +// (That is, this function is called one extra time. This helps +// for OSes that may deallocate file strings on dir close.) +// +// Note that the dir->path can contain wildcards * and ?. The +// processing of these can be done in the OS (if supported) or +// by a separate filter operation during the read. +// +// Store file date info in file->index or other fields? +// Store permissions? Ownership? Groups? Or, require that +// to be part of a separate request? +// +static int Read_Directory(struct devreq_file *dir, struct devreq_file *file) { - struct stat info; - struct dirent *d; - char *cp; - DIR *h; - int n; - - // Remove * from tail, if present. (Allowed because the - // path was copied into to-local-path first). - n = strlen(cp = dir->file.path); - if (n > 0 && cp[n-1] == '*') cp[n-1] = 0; - - // If no dir handle, open the dir: - if (!(h = dir->handle)) { - h = opendir(dir->file.path); - if (!h) { - dir->error = errno; - return DR_ERROR; - } - dir->handle = h; - CLR_FLAG(dir->flags, RRF_DONE); - } - - // Get dir entry (skip over the . and .. dir cases): - do { - // Read next file entry or error: - if (!(d = readdir(h))) { - //dir->error = errno; - closedir(h); - dir->handle = 0; - //if (dir->error) return DR_ERROR; - SET_FLAG(dir->flags, RRF_DONE); // no more files - return DR_DONE; - } - cp = d->d_name; - } while (cp[0] == '.' && (cp[1] == 0 || (cp[1] == '.' && cp[2] == 0))); - - file->modes = 0; - COPY_BYTES(file->file.path, cp, MAX_FILE_NAME); - -#ifdef DT_DIR - // NOTE: not all posix filesystems support this (mainly - // the Linux and BSD support it.) If this fails to build, a - // different mechanism must be used. However, this is the - // most efficient, because it does not require a separate - // file system call for determining directories. - if (d->d_type == DT_DIR) SET_FLAG(file->modes, RFM_DIR); -#else - if (Is_Dir(dir->file.path, file->file.path)) SET_FLAG(file->modes, RFM_DIR); + struct dirent *d; + char *cp; + DIR *h; + int n; + + REBREQ *dir_req = AS_REBREQ(dir); + REBREQ *file_req = AS_REBREQ(file); + + // Remove * from tail, if present. (Allowed because the + // path was copied into to-local-path first). + n = strlen(cp = dir->path); + if (n > 0 && cp[n-1] == '*') cp[n-1] = 0; + + // If no dir handle, open the dir: + if (!(h = cast(DIR*, dir_req->requestee.handle))) { + h = opendir(dir->path); + if (!h) { + dir_req->error = errno; + return DR_ERROR; + } + dir_req->requestee.handle = h; + CLR_FLAG(dir_req->flags, RRF_DONE); + } + + // Get dir entry (skip over the . and .. dir cases): + do { + // Read next file entry or error: + if (!(d = readdir(h))) { + //dir->error = errno; + closedir(h); + dir_req->requestee.handle = 0; + //if (dir->error) return DR_ERROR; + SET_FLAG(dir_req->flags, RRF_DONE); // no more files + return DR_DONE; + } + cp = d->d_name; + } while (cp[0] == '.' && (cp[1] == 0 || (cp[1] == '.' && cp[2] == 0))); + + file_req->modes = 0; + strncpy(file->path, cp, MAX_FILE_NAME); + +#if 0 + // NOTE: we do not use d_type even if DT_DIR is #define-d. First of all, + // it's not a POSIX requirement and not all operating systems support it. + // (Linux/BSD have it defined in their structs, but Haiku doesn't--for + // instance). But secondly, even if your OS supports it...a filesystem + // doesn't have to. (Examples: VirtualBox shared folders, XFS.) + + if (d->d_type == DT_DIR) SET_FLAG(file_req->modes, RFM_DIR); #endif - // Line below DOES NOT WORK -- because we need full path. - //Get_File_Info(file); // updates modes, size, time + // More widely supported mechanism of determining if something is a + // directory, although less efficient than DT_DIR (because it requires + // making an additional filesystem call) - return DR_DONE; + if (Is_Dir(dir->path, file->path)) + SET_FLAG(file_req->modes, RFM_DIR); + + // Line below DOES NOT WORK -- because we need full path. + //Get_File_Info(file); // updates modes, size, time + + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Open_File(REBREQ *file) -/* -** Open the specified file with the given modes. -** -** Notes: -** 1. The file path is provided in REBOL format, and must be -** converted to local format before it is used. -** 2. REBOL performs the required access security check before -** calling this function. -** 3. REBOL clears necessary fields of file structure before -** calling (e.g. error and size fields). -** -***********************************************************************/ +// +// Open_File: C +// +// Open the specified file with the given modes. +// +// Notes: +// 1. The file path is provided in REBOL format, and must be +// converted to local format before it is used. +// 2. REBOL performs the required access security check before +// calling this function. +// 3. REBOL clears necessary fields of file structure before +// calling (e.g. error and size fields). +// +DEVICE_CMD Open_File(REBREQ *req) { - int modes; - int access = 0; - int h; - char *path; - struct stat info; - - // Posix file names should be compatible with REBOL file paths: - if (!(path = file->file.path)) { - file->error = -RFE_BAD_PATH; - return DR_ERROR; - } - - // Set the modes: - modes = O_BINARY | GET_FLAG(file->modes, RFM_READ) ? O_RDONLY : O_RDWR; - - if (GET_FLAGS(file->modes, RFM_WRITE, RFM_APPEND)) { - modes = O_BINARY | O_RDWR | O_CREAT; - if ( - GET_FLAG(file->modes, RFM_NEW) || - !( - GET_FLAG(file->modes, RFM_READ) || - GET_FLAG(file->modes, RFM_APPEND) || - GET_FLAG(file->modes, RFM_SEEK) - ) - ) modes |= O_TRUNC; - } - - //modes |= GET_FLAG(file->modes, RFM_SEEK) ? O_RANDOM : O_SEQUENTIAL; - - if (GET_FLAG(file->modes, RFM_READONLY)) - access = S_IREAD; - else - access = S_IREAD | S_IWRITE | S_IRGRP | S_IWGRP | S_IROTH; - - // Open the file: - // printf("Open: %s %d %d\n", path, modes, access); - h = open(path, modes, access); - if (h < 0) { - file->error = -RFE_OPEN_FAIL; - goto fail; - } - - // Confirm that a seek-mode file is actually seekable: - if (GET_FLAG(file->modes, RFM_SEEK)) { - if (lseek(h, 0, SEEK_CUR) < 0) { - close(h); - file->error = -RFE_BAD_SEEK; - goto fail; - } - } - - // Fetch file size (if fails, then size is assumed zero): - if (fstat(h, &info) == 0) { - file->file.size = info.st_size; - file->file.time.l = (long)(info.st_mtime); - } - - file->id = h; - - return DR_DONE; + int modes; + int access = 0; + int h; + char *path; + struct stat info; + + struct devreq_file *file = DEVREQ_FILE(req); + + // Posix file names should be compatible with REBOL file paths: + if (!(path = file->path)) { + req->error = -RFE_BAD_PATH; + return DR_ERROR; + } + + // Set the modes: + modes = O_BINARY | (GET_FLAG(req->modes, RFM_READ) ? O_RDONLY : O_RDWR); + + if (GET_FLAGS(req->modes, RFM_WRITE, RFM_APPEND)) { + modes = O_BINARY | O_RDWR | O_CREAT; + if ( + GET_FLAG(req->modes, RFM_NEW) || + !( + GET_FLAG(req->modes, RFM_READ) || + GET_FLAG(req->modes, RFM_APPEND) || + GET_FLAG(req->modes, RFM_SEEK) + ) + ) modes |= O_TRUNC; + } + + //modes |= GET_FLAG(req->modes, RFM_SEEK) ? O_RANDOM : O_SEQUENTIAL; + + if (GET_FLAG(req->modes, RFM_READONLY)) + access = S_IREAD; + else + access = S_IREAD | S_IWRITE | S_IRGRP | S_IWGRP | S_IROTH; + + // Open the file: + // printf("Open: %s %d %d\n", path, modes, access); + h = open(path, modes, access); + if (h < 0) { + req->error = -RFE_OPEN_FAIL; + goto fail; + } + + // Confirm that a seek-mode file is actually seekable: + if (GET_FLAG(req->modes, RFM_SEEK)) { + if (lseek(h, 0, SEEK_CUR) < 0) { + close(h); + req->error = -RFE_BAD_SEEK; + goto fail; + } + } + + // Fetch file size (if fails, then size is assumed zero): + if (fstat(h, &info) == 0) { + file->size = info.st_size; + file->time.l = cast(long, info.st_mtime); + } + + req->requestee.id = h; + + return DR_DONE; fail: - return DR_ERROR; + return DR_ERROR; } -/*********************************************************************** -** -*/ DEVICE_CMD Close_File(REBREQ *file) -/* -** Closes a previously opened file. -** -***********************************************************************/ +// +// Close_File: C +// +// Closes a previously opened file. +// +DEVICE_CMD Close_File(REBREQ *req) { - if (file->id) { - close(file->id); - file->id = 0; - } - return DR_DONE; + if (req->requestee.id) { + close(req->requestee.id); + req->requestee.id = 0; + } + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Read_File(REBREQ *file) -/* -***********************************************************************/ +// +// Read_File: C +// +DEVICE_CMD Read_File(REBREQ *req) { - if (GET_FLAG(file->modes, RFM_DIR)) { - return Read_Directory(file, (REBREQ*)file->data); - } - - if (!file->id) { - file->error = -RFE_NO_HANDLE; - return DR_ERROR; - } - - if (file->modes & ((1 << RFM_SEEK) | (1 << RFM_RESEEK))) { - CLR_FLAG(file->modes, RFM_RESEEK); - if (!Seek_File_64(file)) return DR_ERROR; - } - - // printf("read %d len %d\n", file->id, file->length); - file->actual = read(file->id, file->data, file->length); - if (file->actual < 0) { - file->error = -RFE_BAD_READ; - return DR_ERROR; - } else { - file->file.index += file->actual; - } - - return DR_DONE; + ssize_t bytes = 0; + + struct devreq_file *file = DEVREQ_FILE(req); + + if (GET_FLAG(req->modes, RFM_DIR)) { + return Read_Directory(file, cast(struct devreq_file*, req->common.data)); + } + + if (!req->requestee.id) { + req->error = -RFE_NO_HANDLE; + return DR_ERROR; + } + + if (req->modes & ((1 << RFM_SEEK) | (1 << RFM_RESEEK))) { + CLR_FLAG(req->modes, RFM_RESEEK); + if (!Seek_File_64(file)) return DR_ERROR; + } + + // printf("read %d len %d\n", req->requestee.id, req->length); + + bytes = read(req->requestee.id, req->common.data, req->length); + if (bytes < 0) { + req->error = -RFE_BAD_READ; + return DR_ERROR; + } else { + req->actual = bytes; + file->index += req->actual; + } + + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Write_File(REBREQ *file) -/* -** Bug?: update file->size value after write !? -** -***********************************************************************/ +// +// Write_File: C +// +// Bug?: update file->size value after write !? +// +DEVICE_CMD Write_File(REBREQ *req) { - if (!file->id) { - file->error = -RFE_NO_HANDLE; - return DR_ERROR; - } - - if (GET_FLAG(file->modes, RFM_APPEND)) { - CLR_FLAG(file->modes, RFM_APPEND); - lseek(file->id, 0, SEEK_END); - } - - if (file->modes & ((1 << RFM_SEEK) | (1 << RFM_RESEEK) | (1 << RFM_TRUNCATE))) { - CLR_FLAG(file->modes, RFM_RESEEK); - if (!Seek_File_64(file)) return DR_ERROR; - if (GET_FLAG(file->modes, RFM_TRUNCATE)) - if (ftruncate(file->id, file->file.index)) return DR_ERROR; - } - - if (file->length == 0) return DR_DONE; - - file->actual = write(file->id, file->data, file->length); - if (file->actual < 0) { - if (errno == ENOSPC) file->error = -RFE_DISK_FULL; - else file->error = -RFE_BAD_WRITE; - return DR_ERROR; - } - - return DR_DONE; + ssize_t bytes = 0; + + struct devreq_file *file = DEVREQ_FILE(req); + + if (!req->requestee.id) { + req->error = -RFE_NO_HANDLE; + return DR_ERROR; + } + + if (GET_FLAG(req->modes, RFM_APPEND)) { + CLR_FLAG(req->modes, RFM_APPEND); + lseek(req->requestee.id, 0, SEEK_END); + } + + if (req->modes & ((1 << RFM_SEEK) | (1 << RFM_RESEEK) | (1 << RFM_TRUNCATE))) { + CLR_FLAG(req->modes, RFM_RESEEK); + if (!Seek_File_64(file)) return DR_ERROR; + if (GET_FLAG(req->modes, RFM_TRUNCATE)) + if (ftruncate(req->requestee.id, file->index)) return DR_ERROR; + } + + if (req->length == 0) return DR_DONE; + + req->actual = bytes = write(req->requestee.id, req->common.data, req->length); + if (bytes < 0) { + if (errno == ENOSPC) req->error = -RFE_DISK_FULL; + else req->error = -RFE_BAD_WRITE; + return DR_ERROR; + } + + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Query_File(REBREQ *file) -/* -** Obtain information about a file. Return TRUE on success. -** On error, return FALSE and set file->error code. -** -** Note: time is in local format and must be converted -** -***********************************************************************/ +// +// Query_File: C +// +// Obtain information about a file. Return TRUE on success. +// On error, return FALSE and set req->error code. +// +// Note: time is in local format and must be converted +// +DEVICE_CMD Query_File(REBREQ *req) { - return Get_File_Info(file); + return Get_File_Info(DEVREQ_FILE(req)); } -/*********************************************************************** -** -*/ DEVICE_CMD Create_File(REBREQ *file) -/* -***********************************************************************/ +// +// Create_File: C +// +DEVICE_CMD Create_File(REBREQ *req) { - if (GET_FLAG(file->modes, RFM_DIR)) { - if (!mkdir(file->file.path, 0777)) return DR_DONE; - file->error = errno; - return DR_ERROR; - } else - return Open_File(file); + struct devreq_file *file = DEVREQ_FILE(req); + if (GET_FLAG(req->modes, RFM_DIR)) { + if (!mkdir(file->path, 0777)) return DR_DONE; + req->error = errno; + return DR_ERROR; + } else + return Open_File(req); } -/*********************************************************************** -** -*/ DEVICE_CMD Delete_File(REBREQ *file) -/* -** Delete a file or directory. Return TRUE if it was done. -** The file->file.path provides the directory path and name. -** For errors, return FALSE and set file->error to error code. -** -** Note: Dirs must be empty to succeed -** -***********************************************************************/ +// +// Delete_File: C +// +// Delete a file or directory. Return TRUE if it was done. +// The file->path provides the directory path and name. +// For errors, return FALSE and set req->error to error code. +// +// Note: Dirs must be empty to succeed +// +DEVICE_CMD Delete_File(REBREQ *req) { - if (GET_FLAG(file->modes, RFM_DIR)) { - if (!rmdir(file->file.path)) return DR_DONE; - } else - if (!remove(file->file.path)) return DR_DONE; + struct devreq_file *file = DEVREQ_FILE(req); - file->error = errno; - return DR_ERROR; + if (GET_FLAG(req->modes, RFM_DIR)) { + if (!rmdir(file->path)) return DR_DONE; + } else + if (!remove(file->path)) return DR_DONE; - return 0; + req->error = errno; + return DR_ERROR; + + return 0; } -/*********************************************************************** -** -*/ DEVICE_CMD Rename_File(REBREQ *file) -/* -** Rename a file or directory. -** Note: cannot rename across file volumes. -** -***********************************************************************/ +// +// Rename_File: C +// +// Rename a file or directory. +// Note: cannot rename across file volumes. +// +DEVICE_CMD Rename_File(REBREQ *req) { - if (!rename(file->file.path, file->data)) return DR_DONE; - file->error = errno; - return DR_ERROR; + struct devreq_file *file = DEVREQ_FILE(req); + + if (!rename(file->path, s_cast(req->common.data))) + return DR_DONE; + req->error = errno; + return DR_ERROR; } -/*********************************************************************** -** -*/ DEVICE_CMD Poll_File(REBREQ *file) -/* -***********************************************************************/ +// +// Poll_File: C +// +DEVICE_CMD Poll_File(REBREQ *req) +{ + UNUSED(req); + return DR_DONE; // files are synchronous (currently) +} + +// +// Request_Size_File: C +// +static i32 Request_Size_File(REBREQ *req) { - return DR_DONE; // files are synchronous (currently) + (void)req; //unused + return sizeof(struct devreq_file); } /*********************************************************************** ** -** Command Dispatch Table (RDC_ enum order) +** Command Dispatch Table (RDC_ enum order) ** ***********************************************************************/ static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = { - 0, - 0, - Open_File, - Close_File, - Read_File, - Write_File, - Poll_File, - 0, // connect - Query_File, - 0, // modify - Create_File, - Delete_File, - Rename_File, + Request_Size_File, + 0, + 0, + Open_File, + Close_File, + Read_File, + Write_File, + Poll_File, + 0, // connect + Query_File, + 0, // modify + Create_File, + Delete_File, + Rename_File, }; -DEFINE_DEV(Dev_File, "File IO", 1, Dev_Cmds, RDC_MAX, sizeof(REBREQ)); +DEFINE_DEV(Dev_File, "File IO", 1, Dev_Cmds, RDC_MAX); diff --git a/src/os/posix/dev-serial.c b/src/os/posix/dev-serial.c new file mode 100644 index 0000000000..e13eeabf40 --- /dev/null +++ b/src/os/posix/dev-serial.c @@ -0,0 +1,384 @@ +// +// File: %dev-serial.c +// Summary: "Device: Serial port access for Posix" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2013 REBOL Technologies +// Copyright 2013-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "reb-host.h" + +extern void Signal_Device(REBREQ *req, REBINT type); + +#define MAX_SERIAL_PATH 128 + +/* BXXX constants are defined in termios.h */ +const int speeds[] = { + 50, B50, + 75, B75, + 110, B110, + 134, B134, + 150, B150, + 200, B200, + 300, B300, + 600, B600, + 1200, B1200, + 1800, B1800, + 2400, B2400, + 4800, B4800, + 9600, B9600, + 19200, B19200, + 38400, B38400, + 57600, B57600, + 115200, B115200, + 230400, B230400, + 0 +}; + +/*********************************************************************** +** +** Local Functions +** +***********************************************************************/ + +static struct termios *Get_Serial_Settings(int ttyfd) +{ + struct termios *attr = NULL; + attr = OS_ALLOC(struct termios); + if (attr != NULL) { + if (tcgetattr(ttyfd, attr) == -1) { + OS_FREE(attr); + attr = NULL; + } + } + return attr; +} + + +static REBINT Set_Serial_Settings(int ttyfd, REBREQ *req) +{ + REBINT n; + struct termios attr; + struct devreq_serial *serial = DEVREQ_SERIAL(req); + REBINT speed = serial->baud; + CLEARS(&attr); +#ifdef DEBUG_SERIAL + printf("setting attributes: speed %d\n", speed); +#endif + for (n = 0; speeds[n]; n += 2) { + if (speed == speeds[n]) { + speed = speeds[n+1]; + break; + } + } + if (speeds[n] == 0) speed = B115200; // invalid, use default + + cfsetospeed (&attr, speed); + cfsetispeed (&attr, speed); + + // TTY has many attributes. Refer to "man tcgetattr" for descriptions. + // C-flags - control modes: + attr.c_cflag |= CREAD | CLOCAL; + + attr.c_cflag &= ~CSIZE; /* clear data size bits */ + + switch (serial->data_bits) { + case 5: + attr.c_cflag |= CS5; + break; + case 6: + attr.c_cflag |= CS6; + break; + case 7: + attr.c_cflag |= CS7; + break; + case 8: + default: + attr.c_cflag |= CS8; + } + + switch (serial->parity) { + case SERIAL_PARITY_ODD: + attr.c_cflag |= PARENB; + attr.c_cflag |= PARODD; + break; + case SERIAL_PARITY_EVEN: + attr.c_cflag |= PARENB; + attr.c_cflag &= ~PARODD; + break; + case SERIAL_PARITY_NONE: + default: + attr.c_cflag &= ~PARENB; + break; + } + + switch (serial->stop_bits) { + case 2: + attr.c_cflag |= CSTOPB; + break; + case 1: + default: + attr.c_cflag &= ~CSTOPB; + break; + } + +#ifdef CNEW_RTSCTS + switch (serial->parity) { + case SERIAL_FLOW_CONTROL_HARDWARE: + attr.c_cflag |= CNEW_RTSCTS; + break; + case SERIAL_FLOW_CONTROL_SOFTWARE: + attr.c_cflag &= ~CNEW_RTSCTS; + break; + case SERIAL_FLOW_CONTROL_NONE: + default: + break; + } +#endif + + // L-flags - local modes: + attr.c_lflag = 0; // raw, not ICANON + + // I-flags - input modes: + attr.c_iflag |= IGNPAR; + + // O-flags - output modes: + attr.c_oflag = 0; + + // Control characters: + // R3 devices are non-blocking (polled for changes): + attr.c_cc[VMIN] = 0; + attr.c_cc[VTIME] = 0; + + // Make sure OS queues are empty: + tcflush(ttyfd, TCIFLUSH); + + // Set new attributes: + if (tcsetattr(ttyfd, TCSANOW, &attr)) return 2; + + return 0; +} + +// +// Open_Serial: C +// +// serial.path = the /dev name for the serial port +// serial.baud = speed (baudrate) +// +DEVICE_CMD Open_Serial(REBREQ *req) +{ + char *path; + char devpath[MAX_SERIAL_PATH]; + REBINT h; + struct devreq_serial *serial = DEVREQ_SERIAL(req); + + if (!(path = serial->path)) { + req->error = -RFE_BAD_PATH; + return DR_ERROR; + } + + if (path[0] != '/') { //relative path + strcpy(&devpath[0], "/dev/"); + strncpy(&devpath[5], path, MAX_SERIAL_PATH-6); + path = &devpath[0]; + } + h = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK); + if (h < 0) { + req->error = -RFE_OPEN_FAIL; + return DR_ERROR; + } + + //Getting prior atttributes: + serial->prior_attr = Get_Serial_Settings(h); + if (tcgetattr(h, cast(struct termios*, serial->prior_attr))) { + close(h); + return DR_ERROR; + } + + if (Set_Serial_Settings(h, req)) { + close(h); + req->error = -RFE_OPEN_FAIL; + return DR_ERROR; + } + + req->requestee.id = h; + return DR_DONE; +} + + +// +// Close_Serial: C +// +DEVICE_CMD Close_Serial(REBREQ *req) +{ + struct devreq_serial *serial = DEVREQ_SERIAL(req); + if (req->requestee.id) { + // !!! should we free serial->prior_attr termios struct? + tcsetattr( + req->requestee.id, + TCSANOW, + cast(struct termios*, serial->prior_attr) + ); + close(req->requestee.id); + req->requestee.id = 0; + } + return DR_DONE; +} + + +// +// Read_Serial: C +// +DEVICE_CMD Read_Serial(REBREQ *req) +{ + ssize_t result = 0; + if (!req->requestee.id) { + req->error = -RFE_NO_HANDLE; + return DR_ERROR; + } + + result = read(req->requestee.id, req->common.data, req->length); +#ifdef DEBUG_SERIAL + printf("read %d ret: %d\n", req->length, result); +#endif + if (result < 0) { + req->error = -RFE_BAD_READ; + Signal_Device(req, EVT_ERROR); + return DR_ERROR; + } else if (result == 0) { + return DR_PEND; + } else { + req->actual = result; + Signal_Device(req, EVT_READ); + } + + return DR_DONE; +} + + +// +// Write_Serial: C +// +DEVICE_CMD Write_Serial(REBREQ *req) +{ + REBINT result = 0, len = 0; + len = req->length - req->actual; + if (!req->requestee.id) { + req->error = -RFE_NO_HANDLE; + return DR_ERROR; + } + + if (len <= 0) return DR_DONE; + + result = write(req->requestee.id, req->common.data, len); +#ifdef DEBUG_SERIAL + printf("write %d ret: %d\n", len, result); +#endif + if (result < 0) { + if (errno == EAGAIN) { + return DR_PEND; + } + req->error = -RFE_BAD_WRITE; + Signal_Device(req, EVT_ERROR); + return DR_ERROR; + } + req->actual += result; + req->common.data += result; + if (req->actual >= req->length) { + Signal_Device(req, EVT_WROTE); + return DR_DONE; + } else { + SET_FLAG(req->flags, RRF_ACTIVE); /* notify OS_WAIT of activity */ + return DR_PEND; + } +} + + +// +// Query_Serial: C +// +DEVICE_CMD Query_Serial(REBREQ *req) +{ +#ifdef QUERY_IMPLEMENTED + struct pollfd pfd; + + if (req->requestee.id) { + pfd.fd = req->requestee.id; + pfd.events = POLLIN; + n = poll(&pfd, 1, 0); + } +#else + UNUSED(req); +#endif + return DR_DONE; +} + + +// +// Request_Size_Serial: C +// +static i32 Request_Size_Serial(REBREQ *req) +{ + UNUSED(req); + return sizeof(struct devreq_serial); +} + + +/*********************************************************************** +** +** Command Dispatch Table (RDC_ enum order) +** +***********************************************************************/ + +static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = { + Request_Size_Serial, + 0, + 0, + Open_Serial, + Close_Serial, + Read_Serial, + Write_Serial, + 0, // poll + 0, // connect + Query_Serial, + 0, // modify + 0, // create + 0, // delete + 0 // rename +}; + +DEFINE_DEV(Dev_Serial, "Serial IO", 1, Dev_Cmds, RDC_MAX); + diff --git a/src/os/posix/dev-stdio.c b/src/os/posix/dev-stdio.c index eccfa2fa89..0ea9d11987 100644 --- a/src/os/posix/dev-stdio.c +++ b/src/os/posix/dev-stdio.c @@ -1,43 +1,37 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Device: Standard I/O for Posix -** Author: Carl Sassenrath -** Purpose: -** Provides basic I/O streams support for redirection and -** opening a console window if necessary. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ +// +// File: %dev-stdio.c +// Summary: "Device: Standard I/O for Posix" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Provides basic I/O streams support for redirection and +// opening a console window if necessary. +// #include +#include #include #include #include @@ -46,273 +40,220 @@ #include #include "reb-host.h" -#include "host-lib.h" - -#define BUF_SIZE (16*1024) -#define SF_DEV_NULL 31 // local flag to mark NULL device +#define SF_DEV_NULL 31 // local flag to mark NULL device // Temporary globals: (either move or remove?!) -static int Std_Inp = 0; -static int Std_Out = 1; -static FILE *Std_Echo = 0; - -static REBOOL Redir_Out = 0; // redirection flags -static REBOOL Redir_Inp = 0; - -#define PUTE(s) if (Std_Echo) fputs(s, Std_Echo) - -extern REBDEV *Devices[]; - -#ifndef HAS_SMART_CONSOLE // console line-editing and recall needed -void *Init_Terminal(); -void Quit_Terminal(void*); -int Read_Line(void*, char*, int); +static int Std_Inp = STDIN_FILENO; +static int Std_Out = STDOUT_FILENO; + +#ifndef HAS_SMART_CONSOLE // console line-editing and recall needed +typedef struct term_data { + char *buffer; + char *residue; + char *out; + int pos; + int end; + int hist; +} STD_TERM; + +extern STD_TERM *Init_Terminal(); +extern void Quit_Terminal(STD_TERM*); +extern int Read_Line(STD_TERM*, REBYTE*, int); + +STD_TERM *Term_IO; #endif -void Put_Str(char *buf); +extern void Put_Str(const REBYTE *buf); +extern i32 Request_Size_Rebreq(REBREQ *req); -void *Term_IO; -/* -#define PUTS(s) fputs(s, stdout) -#define GETS(s,len) fgets(s, len, stdin); -#define FLUSH() fflush(stdout) -*/ - -static void Handle_Signal(int sig) -{ - char *buf = strdup("[escape]"); - Put_Str(buf); - free(buf); - RL_Escape(0); -} - -static void Init_Signals(void) -{ - signal(SIGINT, Handle_Signal); - signal(SIGHUP, Handle_Signal); - signal(SIGTERM, Handle_Signal); -} - -static void close_stdio(void) +static void Close_Stdio(void) { #ifndef HAS_SMART_CONSOLE - if (Term_IO) { - Quit_Terminal(Term_IO); - Term_IO = 0; - } + if (Term_IO) { + Quit_Terminal(Term_IO); + Term_IO = 0; + } #endif - if (Std_Echo) { - fclose(Std_Echo); - Std_Echo = 0; - } } -/*********************************************************************** -** -*/ DEVICE_CMD Quit_IO(REBREQ *dr) -/* -***********************************************************************/ + +// +// Quit_IO: C +// +DEVICE_CMD Quit_IO(REBREQ *dr) { - REBDEV *dev = (REBDEV*)dr; // just to keep compiler happy above + REBDEV *dev = (REBDEV*)dr; // just to keep compiler happy above - close_stdio(); + Close_Stdio(); - CLR_FLAG(dev->flags, RDF_OPEN); - return DR_DONE; + CLR_FLAG(dev->flags, RDF_OPEN); + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Open_IO(REBREQ *req) -/* -***********************************************************************/ +// +// Open_IO: C +// +DEVICE_CMD Open_IO(REBREQ *req) { - REBDEV *dev; - - dev = Devices[req->device]; + REBDEV *dev; - // Avoid opening the console twice (compare dev and req flags): - if (GET_FLAG(dev->flags, RDF_OPEN)) { - // Device was opened earlier as null, so req must have that flag: - if (GET_FLAG(dev->flags, SF_DEV_NULL)) - SET_FLAG(req->modes, RDM_NULL); - SET_FLAG(req->flags, RRF_OPEN); - return DR_DONE; // Do not do it again - } + dev = Devices[req->device]; - Init_Signals(); + // Avoid opening the console twice (compare dev and req flags): + if (GET_FLAG(dev->flags, RDF_OPEN)) { + // Device was opened earlier as null, so req must have that flag: + if (GET_FLAG(dev->flags, SF_DEV_NULL)) + SET_FLAG(req->modes, RDM_NULL); + SET_FLAG(req->flags, RRF_OPEN); + return DR_DONE; // Do not do it again + } - if (!GET_FLAG(req->modes, RDM_NULL)) { + if (!GET_FLAG(req->modes, RDM_NULL)) { #ifndef HAS_SMART_CONSOLE - if (isatty(Std_Inp)) - Term_IO = Init_Terminal(); - else + if (isatty(Std_Inp)) + Term_IO = Init_Terminal(); #endif - Term_IO = 0; - //printf("%x\r\n", req->handle); - } - else - SET_FLAG(dev->flags, SF_DEV_NULL); + //printf("%x\r\n", req->requestee.handle); + } + else + SET_FLAG(dev->flags, SF_DEV_NULL); - SET_FLAG(req->flags, RRF_OPEN); - SET_FLAG(dev->flags, RDF_OPEN); + SET_FLAG(req->flags, RRF_OPEN); + SET_FLAG(dev->flags, RDF_OPEN); - return DR_DONE; + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Close_IO(REBREQ *req) -/* - ***********************************************************************/ +// +// Close_IO: C +// +DEVICE_CMD Close_IO(REBREQ *req) { - REBDEV *dev = Devices[req->device]; + REBDEV *dev = Devices[req->device]; - close_stdio(); + Close_Stdio(); - CLR_FLAG(req->flags, RRF_OPEN); + CLR_FLAG(dev->flags, RRF_OPEN); - return DR_DONE; + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Write_IO(REBREQ *req) -/* -** Low level "raw" standard output function. -** -** Allowed to restrict the write to a max OS buffer size. -** -** Returns the number of chars written. -** -***********************************************************************/ +// +// Write_IO: C +// +// Low level "raw" standard output function. +// +// Allowed to restrict the write to a max OS buffer size. +// +// Returns the number of chars written. +// +DEVICE_CMD Write_IO(REBREQ *req) { - long total; - - if (GET_FLAG(req->modes, RDM_NULL)) { - req->actual = req->length; - return DR_DONE; - } + long total; - if (Std_Out >= 0) { + if (GET_FLAG(req->modes, RDM_NULL)) { + req->actual = req->length; + return DR_DONE; + } - total = write(Std_Out, req->data, req->length); + if (Std_Out >= 0) { - if (total < 0) { - req->error = errno; - return DR_ERROR; - } + total = write(Std_Out, req->common.data, req->length); - //if (GET_FLAG(req->flags, RRF_FLUSH)) { - //FLUSH(); - //} + if (total < 0) { + req->error = errno; + return DR_ERROR; + } - req->actual = total; - } + //if (GET_FLAG(req->flags, RRF_FLUSH)) { + //FLUSH(); + //} - if (Std_Echo) { - fwrite(req->data, req->length, 1, Std_Echo); - //fflush(Std_Echo); //slow! - } + req->actual = total; + } - return DR_DONE; + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Read_IO(REBREQ *req) -/* -** Low level "raw" standard input function. -** -** The request buffer must be long enough to hold result. -** -** Result is NOT terminated (the actual field has length.) -** -***********************************************************************/ +// +// Read_IO: C +// +// Low level "raw" standard input function. +// +// The request buffer must be long enough to hold result. +// +// Result is NOT terminated (the actual field has length.) +// +DEVICE_CMD Read_IO(REBREQ *req) { - long total = 0; - int len = req->length; + long total = 0; + int len = req->length; - if (GET_FLAG(req->modes, RDM_NULL)) { - req->data[0] = 0; - return DR_DONE; - } + if (GET_FLAG(req->modes, RDM_NULL)) { + req->common.data[0] = 0; + return DR_DONE; + } - req->actual = 0; + req->actual = 0; - if (Std_Inp >= 0) { + if (Std_Inp >= 0) { - // Perform a processed read or a raw read? + // Perform a processed read or a raw read? #ifndef HAS_SMART_CONSOLE - if (Term_IO) - total = Read_Line(Term_IO, req->data, len); - else + if (Term_IO) + total = Read_Line(Term_IO, req->common.data, len); + else #endif - total = read(Std_Inp, req->data, len); + total = read(Std_Inp, req->common.data, len); /* will be restarted in case of signal */ - if (total < 0) { - req->error = errno; - return DR_ERROR; - } + if (total < 0) { + req->error = errno; + return DR_ERROR; + } - req->actual = total; - } + req->actual = total; + } - return DR_DONE; + return DR_DONE; } -/*********************************************************************** -** -*/ DEVICE_CMD Open_Echo(REBREQ *req) -/* -** Open a file for low-level console echo (output). -** -***********************************************************************/ +// +// Request_Size_IO: C +// +static i32 Request_Size_IO(REBREQ *req) { - if (Std_Echo) { - fclose(Std_Echo); - Std_Echo = 0; - } - - if (req->file.path) { - Std_Echo = fopen(req->file.path, "w"); // null on error - if (!Std_Echo) { - req->error = errno; - return DR_ERROR; - } - } - - return DR_DONE; + UNUSED(req); + return sizeof(struct devreq_file); } - /*********************************************************************** ** -** Command Dispatch Table (RDC_ enum order) +** Command Dispatch Table (RDC_ enum order) ** ***********************************************************************/ static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = { - 0, // init - Quit_IO, - Open_IO, - Close_IO, - Read_IO, - Write_IO, - 0, // poll - 0, // connect - 0, // query - 0, // modify - Open_Echo, // CREATE used for opening echo file + Request_Size_IO, + 0, // init + Quit_IO, + Open_IO, + Close_IO, + Read_IO, + Write_IO, + 0, // poll + 0, // connect + 0, // query + 0, // modify + 0, // CREATE previously used for opening echo file }; -DEFINE_DEV(Dev_StdIO, "Standard IO", 1, Dev_Cmds, RDC_MAX, 0); +DEFINE_DEV(Dev_StdIO, "Standard IO", 1, Dev_Cmds, RDC_MAX); diff --git a/src/os/posix/host-browse.c b/src/os/posix/host-browse.c new file mode 100644 index 0000000000..3e07fbbe72 --- /dev/null +++ b/src/os/posix/host-browse.c @@ -0,0 +1,114 @@ +// +// File: %host-browse.c +// Summary: "Browser Launch Host" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This provides the ability to launch a web browser or file +// browser on the host. +// + +#ifndef __cplusplus + // See feature_test_macros(7) + // This definition is redundant under C++ + #define _GNU_SOURCE +#endif + +#include +#include +#include +#include /* Obtain O_* constant definitions */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "reb-host.h" + + +#ifndef PATH_MAX +#define PATH_MAX 4096 // generally lacking in Posix +#endif + +void OS_Destroy_Graphics(void); + + +// +// OS_Get_Current_Dir: C +// +// Return the current directory path as a string and +// its length in chars (not bytes). +// +// The result should be freed after copy/conversion. +// +int OS_Get_Current_Dir(REBCHR **path) +{ + *path = OS_ALLOC_N(char, PATH_MAX); + if (!getcwd(*path, PATH_MAX-1)) *path[0] = 0; + return strlen(*path); +} + + +// +// OS_Set_Current_Dir: C +// +// Set the current directory to local path. Return FALSE +// on failure. +// +REBOOL OS_Set_Current_Dir(REBCHR *path) +{ + return LOGICAL(chdir(path) == 0); +} + + +// +// OS_Request_File: C +// +REBOOL OS_Request_File(REBRFR *fr) +{ + UNUSED(fr); + return FALSE; +} + + +// +// OS_Request_Dir: C +// +// WARNING: TEMPORARY implementation! Used only by host-core.c +// Will be most probably changed in future. +// +REBOOL OS_Request_Dir(REBCHR* title, REBCHR** folder, REBCHR* path) +{ + UNUSED(title); + UNUSED(folder); + UNUSED(path); + + return FALSE; +} diff --git a/src/os/posix/host-config.c b/src/os/posix/host-config.c new file mode 100644 index 0000000000..906f6d8b00 --- /dev/null +++ b/src/os/posix/host-config.c @@ -0,0 +1,184 @@ +// +// File: %host-config.c +// Summary: "POSIX Host Configuration Routines" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This file is for situations where there is some kind of +// configuration information (e.g. environment variables, boot +// paths) that Rebol wants to get at from the host. +// + +#include +#include + +#include "reb-host.h" + + +extern char **environ; + + +// +// OS_Config: C +// +// Return a specific runtime configuration parameter. +// +REBINT OS_Config(int id, REBYTE *result) +{ + UNUSED(result); + +#define OCID_STACK_SIZE 1 // needs to move to .h file + + switch (id) { + case OCID_STACK_SIZE: + return 0; // (size in bytes should be returned here) + } + + return 0; +} + + +// +// OS_Get_Env: C +// +// Get a value from the environment. +// Returns size of retrieved value for success or -1 if missing. +// +// If return size is greater than capacity then value contents +// are undefined, and size includes null terminator of needed buf +// +REBINT OS_Get_Env(REBCHR* buffer, const REBCHR *key, REBINT capacity) +{ + // Note: The Posix variant of this API is case-sensitive + + const char* value = getenv(key); + if (value == NULL) + return -1; + + REBINT len = strlen(value); + if (len == 0) + return 0; + + if (len + 1 > capacity) + return len + 1; + + strncpy(buffer, value, len); + return len; +} + + +// +// OS_Set_Env: C +// +// Set a value from the environment. +// Returns >0 for success and 0 for errors. +// +REBOOL OS_Set_Env(REBCHR *envname, REBCHR *envval) +{ + if (envval) { +#ifdef setenv + // we pass 1 for overwrite (make call to OS_Get_Env if you + // want to check if already exists) + + if (setenv(envname, envval, 1) == -1) + return FALSE; +#else + // WARNING: KNOWN MEMORY LEAK! + + // putenv is *fatally flawed*, and was obsoleted by setenv + // and unsetenv System V... + + // http://stackoverflow.com/a/5876818/211160 + + // once you have passed a string to it you never know when that + // string will no longer be needed. Thus it may either not be + // dynamic or you must leak it, or track a local copy of the + // environment yourself. + + // If you're stuck without setenv on some old platform, but + // really need to set an environment variable, here's a way + // that just leaks a string each time you call. + + char *expr = OS_ALLOC_N(char, + strlen(envname) + 1 + strlen(envval) + 1 + ); + + strcpy(expr, envname); + strcat(expr, "="); + strcat(expr, envval); + + if (putenv(expr) == -1) + return FALSE; +#endif + return TRUE; + } + +#ifdef unsetenv + if (unsetenv(envname) == -1) + return FALSE; +#else + // WARNING: KNOWN PORTABILITY ISSUE + + // Simply saying putenv("FOO") will delete FOO from + // the environment, but it's not consistent...does + // nothing on NetBSD for instance. But not all + // other systems have unsetenv... + // + // http://julipedia.meroh.net/2004/10/portability-unsetenvfoo-vs-putenvfoo.html + + // going to hope this case doesn't hold onto the string... + if (putenv((char*)envname) == -1) + return FALSE; +#endif + return TRUE; +} + + +// +// OS_List_Env: C +// +REBCHR *OS_List_Env(void) +{ + int n, len = 0; + char *str, *cp; + + // compute total size: + // Note: 'environ' is an extern of a global found in + for (n = 0; environ[n]; n++) len += 1 + strlen(environ[n]); + + cp = str = OS_ALLOC_N(char, len + 1); // +terminator + *cp = 0; + + // combine all strings into one: + for (n = 0; environ[n]; n++) { + len = strlen(environ[n]); + strcat(cp, environ[n]); + cp += len; + *cp++ = 0; + *cp = 0; + } + + return str; // caller will free it +} diff --git a/src/os/posix/host-error.c b/src/os/posix/host-error.c new file mode 100644 index 0000000000..d475c53b87 --- /dev/null +++ b/src/os/posix/host-error.c @@ -0,0 +1,167 @@ +// +// File: %host-error.c +// Summary: "POSIX Exit and Error Functions" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// ... +// + +#include +#include +#include +#include +#include + +#ifdef HAVE_EXECINFO_AVAILABLE + #include + #include // STDERR_FILENO +#endif + +#include "reb-host.h" + + +// +// OS_Exit: C +// +// Called in cases where REBOL needs to quit immediately +// without returning from the main() function. +// +void OS_Exit(int code) +{ + //OS_Call_Device(RDI_STDIO, RDC_CLOSE); // close echo + OS_Quit_Devices(0); +#ifndef REB_CORE + OS_Destroy_Graphics(); +#endif + exit(code); +} + +// +// OS_Crash: C +// +// Tell user that REBOL has crashed. This function must use +// the most obvious and reliable method of displaying the +// crash message. +// +// If the title is NULL, then REBOL is running in a server mode. +// In that case, we do not want the crash message to appear on +// the screen, because the system may be unattended. +// +// On some systems, the error may be recorded in the system log. +// +// coverity[+kill] +// +void OS_Crash(const REBYTE *title, const REBYTE *content) +{ + // !!! This said "close echo", but file echoing is no longer in core. + // Is it still needed? + // + OS_Call_Device(RDI_STDIO, RDC_CLOSE); + + // A title tells us we should alert the user: + if (title) { + fputs(cs_cast(title), stderr); + fputs(":\n", stderr); + } + fputs(cs_cast(content), stderr); + fputs("\n\n", stderr); + +#ifdef HAVE_EXECINFO_AVAILABLE // backtrace is a GNU extension. + { + void *backtrace_buf[1024]; + int n_backtrace = backtrace(backtrace_buf, sizeof(backtrace_buf)/sizeof(backtrace_buf[0])); + fputs("Backtrace:\n", stderr); + backtrace_symbols_fd(backtrace_buf, n_backtrace, STDERR_FILENO); + } +#endif + + exit(EXIT_FAILURE); +} + + +// +// OS_Form_Error: C +// +// Translate OS error into a string. The str is the string +// buffer and the len is the length of the buffer. +// +REBCHR *OS_Form_Error(int errnum, REBCHR *str, int len) +{ + // strerror() is not thread-safe, but strerror_r is. Unfortunately, at + // least in glibc, there are two different protocols for strerror_r(), + // depending on whether you are using the POSIX-compliant + // implementation or the GNU implementation. The convoluted test below + // is the inversion of the actual test recommended by glibc to discern + // the version of strerror_r() provided. As other, non-glibc + // implementations (such as OS X's libSystem) also provide the + // POSIX-compliant version, we invert the test: explicitly use the + // older GNU implementation when we are sure about it, and use the + // more modern POSIX-compliant version otherwise. Finally, we only + // attempt this feature detection when using glibc (__GNU_LIBRARY__), + // as this particular combination of the (more widely standardised) + // _POSIX_C_SOURCE and _XOPEN_SOURCE defines might mean something + // completely different on non-glibc implementations. (Note that + // undefined pre-processor names arithmetically compare as 0, which is + // used in the original glibc test; we are more explicit.) + +#ifdef USE_STRERROR_NOT_STRERROR_R + char *shared = strerror(errnum); + strncpy(str, shared, len); +#elif defined(__GNU_LIBRARY__) \ + && (defined(_GNU_SOURCE) \ + || ((!defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 200112L) \ + && (!defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 600))) + // May return an immutable string instead of filling the buffer + char *maybe_str = strerror_r(errnum, str, len); + if (maybe_str != str) + strncpy(str, maybe_str, len); +#else + // Quoting glibc's strerror_r manpage: "The XSI-compliant strerror_r() + // function returns 0 on success. On error, a (positive) error number is + // returned (since glibc 2.13), or -1 is returned and errno is set to + // indicate the error (glibc versions before 2.13)." + + int result = strerror_r(errnum, str, len); + + // Alert us to any problems in a debug build. + assert(result == 0); + + if (result == 0) { + // success... + } + else if (result == EINVAL) { + strncpy(str, "EINVAL: bad error num passed to strerror_r()", len); + } + else if (result == ERANGE) { + strncpy(str, "ERANGE: insufficient size in buffer for error", len); + } + else { + strncpy(str, "Unknown error while getting strerror_r() message", len); + } +#endif + + return str; +} diff --git a/src/os/posix/host-exec-path.c b/src/os/posix/host-exec-path.c new file mode 100644 index 0000000000..318f7c3757 --- /dev/null +++ b/src/os/posix/host-exec-path.c @@ -0,0 +1,114 @@ +// +// File: %host-exec-path.c +// Summary: "Executable Path" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// + +#ifndef __cplusplus + // See feature_test_macros(7) + // This definition is redundant under C++ + #define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(HAVE_PROC_PATHNAME) +#include +#endif + +#include "reb-host.h" + +#ifndef PATH_MAX +#define PATH_MAX 4096 // generally lacking in Posix +#endif + +// +// OS_Get_Current_Exec: C +// +// Return the current executable path as a string and +// its length in chars (not bytes). +// +// The result should be freed after copy/conversion. +// +// See +// https://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe +// +int OS_Get_Current_Exec(REBCHR **path) +{ + assert(sizeof(REBCHR) == sizeof(char)); + +#if !defined(PROC_EXEC_PATH) && !defined(HAVE_PROC_PATHNAME) + UNUSED(path); + return -1; +#else +#if defined(PROC_EXEC_PATH) + const char *self = PROC_EXEC_PATH; +#else //HAVE_PROC_PATHNAME + int mib[4] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PATHNAME, + -1 //current process + }; + char *self = OS_ALLOC_N(REBCHR, PATH_MAX + 1); + size_t len = PATH_MAX + 1; + if (sysctl(mib, sizeof(mib), self, &len, NULL, 0) != 0) { + OS_FREE(self); + return -1; + } +#endif + + *path = NULL; + int r = 0; + *path = OS_ALLOC_N(REBCHR, PATH_MAX); + if (*path == NULL) return -1; + + r = readlink(self, *path, PATH_MAX); + +#if defined(HAVE_PROC_PATHNAME) + OS_FREE(self); +#endif + + if (r < 0) { + OS_FREE(*path); + return -1; + } + (*path)[r] = '\0'; + + return r; +#endif +} diff --git a/src/os/posix/host-lib.c b/src/os/posix/host-lib.c deleted file mode 100644 index 1d4989e813..0000000000 --- a/src/os/posix/host-lib.c +++ /dev/null @@ -1,658 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: OS API function library called by REBOL interpreter -** Author: Carl Sassenrath -** Purpose: -** This module provides the functions that REBOL calls -** to interface to the native (host) operating system. -** REBOL accesses these functions through the structure -** defined in host-lib.h (auto-generated, do not modify). -** -** Special note: -** This module is parsed for function declarations used to -** build prototypes, tables, and other definitions. To change -** function arguments requires a rebuild of the REBOL library. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ - -/* WARNING: -** The function declarations here cannot be modified without -** also modifying those found in the other OS host-lib files! -** Do not even modify the argument names. -*/ - -#include -#include -#include -#include -#include -#include -#include - -#ifndef timeval // for older systems -#include -#endif - -#include "reb-host.h" -#include "host-lib.h" - -#ifndef NO_DL_LIB -#include -#endif - -// Semaphore lock to sync sub-task launch: -static void *Task_Ready; - -#ifndef PATH_MAX -#define PATH_MAX 4096 // generally lacking in Posix -#endif - - - -/*********************************************************************** -** -*/ static int Get_Timezone(struct tm *local_tm) -/* -** Get the time zone in minutes from GMT. -** NOT consistently supported in Posix OSes! -** We have to use a few different methods. -** -***********************************************************************/ -{ -#ifdef HAS_SMART_TIMEZONE - time_t rightnow; - time(&rightnow); - return (int)difftime(mktime(localtime(&rightnow)), mktime(gmtime(&rightnow))) / 60; -#else - struct tm tm2; - time_t rightnow; - time(&rightnow); - tm2 = *localtime(&rightnow); - tm2.tm_isdst=0; - return (int)difftime(mktime(&tm2), mktime(gmtime(&rightnow))) / 60; -#endif -// return local_tm->tm_gmtoff / 60; // makes the most sense, but no longer used -} - - -/*********************************************************************** -** -*/ void Convert_Date(time_t *stime, REBOL_DAT *dat, long zone) -/* -** Convert local format of system time into standard date -** and time structure (for date/time and file timestamps). -** -***********************************************************************/ -{ - struct tm *time; - - CLEARS(dat); - - time = gmtime(stime); - - dat->year = time->tm_year + 1900; - dat->month = time->tm_mon + 1; - dat->day = time->tm_mday; - dat->time = time->tm_hour * 3600 + time->tm_min * 60 + time->tm_sec; - dat->nano = 0; - dat->zone = Get_Timezone(time); -} - - -/*********************************************************************** -** -** OS Library Functions -** -***********************************************************************/ - -/*********************************************************************** -** -*/ REBINT OS_Config(int id, REBYTE *result) -/* -** Return a specific runtime configuration parameter. -** -***********************************************************************/ -{ -#define OCID_STACK_SIZE 1 // needs to move to .h file - - switch (id) { - case OCID_STACK_SIZE: - return 0; // (size in bytes should be returned here) - } - - return 0; -} - - -/*********************************************************************** -** -*/ void *OS_Make(size_t size) -/* -** Allocate memory of given size. -** -** This is necessary because some environments may use their -** own specific memory allocation (e.g. private heaps). -** -***********************************************************************/ -{ - return malloc(size); -} - - -/*********************************************************************** -** -*/ void OS_Free(void *mem) -/* -** Free memory allocated in this OS environment. (See OS_Make) -** -***********************************************************************/ -{ - free(mem); -} - - -/*********************************************************************** -** -*/ void OS_Exit(int code) -/* -** Called in cases where REBOL needs to quit immediately -** without returning from the main() function. -** -***********************************************************************/ -{ - //OS_Call_Device(RDI_STDIO, RDC_CLOSE); // close echo - OS_Quit_Devices(0); - exit(code); -} - - -/*********************************************************************** -** -*/ void OS_Crash(const REBYTE *title, const REBYTE *content) -/* -** Tell user that REBOL has crashed. This function must use -** the most obvious and reliable method of displaying the -** crash message. -** -** If the title is NULL, then REBOL is running in a server mode. -** In that case, we do not want the crash message to appear on -** the screen, because the system may be unattended. -** -** On some systems, the error may be recorded in the system log. -** -***********************************************************************/ -{ - // Echo crash message if echo file is open: - ///PUTE(content); - OS_Call_Device(RDI_STDIO, RDC_CLOSE); // close echo - - // A title tells us we should alert the user: - if (title) { - fputs(title, stderr); - fputs(":\n", stderr); - } - fputs(content, stderr); - fputs("\n\n", stderr); - exit(100); -} - - -/*********************************************************************** -** -*/ REBCHR *OS_Form_Error(int errnum, REBCHR *str, int len) -/* -** Translate OS error into a string. The str is the string -** buffer and the len is the length of the buffer. -** -***********************************************************************/ -{ - strerror_r(errnum, str, len); - return str; -} - - -/*********************************************************************** -** -*/ REBOOL OS_Get_Boot_Path(REBCHR *name) -/* -** Used to determine the program file path for REBOL. -** This is the path stored in system->options->boot and -** it is used for finding default boot files. -** -***********************************************************************/ -{ - return FALSE; // not yet used -} - - -/*********************************************************************** -** -*/ REBCHR *OS_Get_Locale(int what) -/* -** Used to obtain locale information from the system. -** The returned value must be freed with OS_FREE_MEM. -** -***********************************************************************/ -{ - return 0; // not yet used -} - - -/*********************************************************************** -** -*/ REBINT OS_Get_Env(REBCHR *envname, REBCHR* envval, REBINT valsize) -/* -** Get a value from the environment. -** Returns size of retrieved value for success or zero if missing. -** If return size is greater than valsize then value contents -** are undefined, and size includes null terminator of needed buf -** -***********************************************************************/ -{ - // Note: The Posix variant of this API is case-sensitive - - REBINT len; - const REBCHR* value = getenv(envname); - if (value == 0) return 0; - - len = LEN_STR(value); - if (len == 0) return -1; // shouldn't have saved an empty env string - - if (len + 1 > valsize) { - return len + 1; - } - - COPY_STR(envval, value, len); - return len; -} - - -/*********************************************************************** -** -*/ REBOOL OS_Set_Env(REBCHR *envname, REBCHR *envval) -/* -** Set a value from the environment. -** Returns >0 for success and 0 for errors. -** -***********************************************************************/ -{ - if (envval) { -#ifdef setenv - // we pass 1 for overwrite (make call to OS_Get_Env if you - // want to check if already exists) - - if (setenv(envname, envval, 1) == -1) - return FALSE; -#else - // WARNING: KNOWN MEMORY LEAK! - - // putenv is *fatally flawed*, and was obsoleted by setenv - // and unsetenv System V... - - // http://stackoverflow.com/a/5876818/211160 - - // once you have passed a string to it you never know when that - // string will no longer be needed. Thus it may either not be - // dynamic or you must leak it, or track a local copy of the - // environment yourself. - - // If you're stuck without setenv on some old platform, but - // really need to set an environment variable, here's a way - // that just leaks a string each time you call. - - char* expr = MAKE_STR(LEN_STR(envname) + 1 + LEN_STR(envval) + 1); - - strcpy(expr, envname); - strcat(expr, "="); - strcat(expr, envval); - - if (putenv(expr) == -1) - return FALSE; -#endif - return TRUE; - } - -#ifdef unsetenv - if (unsetenv(envname) == -1) - return FALSE; -#else - // WARNING: KNOWN PORTABILITY ISSUE - - // Simply saying putenv("FOO") will delete FOO from - // the environment, but it's not consistent...does - // nothing on NetBSD for instance. But not all - // other systems have unsetenv... - // - // http://julipedia.meroh.net/2004/10/portability-unsetenvfoo-vs-putenvfoo.html - - // going to hope this case doesn't hold onto the string... - if (putenv((char*)envname) == -1) - return FALSE; -#endif - return TRUE; -} - - -/*********************************************************************** -** -*/ REBCHR *OS_List_Env(void) -/* -***********************************************************************/ -{ - extern char **environ; - int n, len = 0; - char *str, *cp; - - // compute total size: - for (n = 0; environ[n]; n++) len += 1 + LEN_STR(environ[n]); - - cp = str = OS_Make(len + 1); // +terminator - *cp = 0; - - // combine all strings into one: - for (n = 0; environ[n]; n++) { - len = LEN_STR(environ[n]); - strcat(cp, environ[n]); - cp += len; - *cp++ = 0; - *cp = 0; - } - - return str; // caller will free it -} - - -/*********************************************************************** -** -*/ void OS_Get_Time(REBOL_DAT *dat) -/* -** Get the current system date/time in UTC plus zone offset (mins). -** -***********************************************************************/ -{ - struct timeval tv; - time_t stime; - - gettimeofday(&tv, 0); // (tz field obsolete) - stime = tv.tv_sec; - Convert_Date(&stime, dat, -1); - dat->nano = tv.tv_usec * 1000; -} - - -/*********************************************************************** -** -*/ i64 OS_Delta_Time(i64 base, int flags) -/* -** Return time difference in microseconds. If base = 0, then -** return the counter. If base != 0, compute the time difference. -** -** NOTE: This needs to be precise, but many OSes do not -** provide a precise time sampling method. So, if the target -** posix OS does, add the ifdef code in here. -** -***********************************************************************/ -{ - struct timeval tv; - i64 time; - - gettimeofday(&tv,0); - - time = ((i64)tv.tv_sec * 1000000) + tv.tv_usec; - - if (base == 0) return time; - - return time - base; -} - - -/*********************************************************************** -** -*/ int OS_Get_Current_Dir(REBCHR **path) -/* -** Return the current directory path as a string and -** its length in chars (not bytes). -** -** The result should be freed after copy/conversion. -** -***********************************************************************/ -{ - *path = MAKE_STR(PATH_MAX); - if (!getcwd(*path, PATH_MAX-1)) *path[0] = 0; - return LEN_STR(*path); // Be sure to call free() after usage -} - - -/*********************************************************************** -** -*/ REBOOL OS_Set_Current_Dir(REBCHR *path) -/* -** Set the current directory to local path. Return FALSE -** on failure. -** -***********************************************************************/ -{ - return chdir(path) == 0; -} - - -/*********************************************************************** -** -*/ void OS_File_Time(REBREQ *file, REBOL_DAT *dat) -/* -** Convert file.time to REBOL date/time format. -** Time zone is UTC. -** -***********************************************************************/ -{ - Convert_Date((time_t *)&(file->file.time.l), dat, 0); -} - - -/*********************************************************************** -** -*/ void *OS_Open_Library(REBCHR *path, REBCNT *error) -/* -** Load a DLL library and return the handle to it. -** If zero is returned, error indicates the reason. -** -***********************************************************************/ -{ -#ifndef NO_DL_LIB - void *dll = dlopen(path, RTLD_LAZY/*|RTLD_GLOBAL*/); - *error = 0; // dlerror() returns a char* error message, so there's - // no immediate way to return an "error code" in *error - return dll; -#else - return 0; -#endif -} - - -/*********************************************************************** -** -*/ void OS_Close_Library(void *dll) -/* -** Free a DLL library opened earlier. -** -***********************************************************************/ -{ -#ifndef NO_DL_LIB - dlclose(dll); -#endif -} - - -/*********************************************************************** -** -*/ void *OS_Find_Function(void *dll, char *funcname) -/* -** Get a DLL function address from its string name. -** -***********************************************************************/ -{ -#ifndef NO_DL_LIB - void *fp = dlsym(dll, funcname); - return fp; -#else - return 0; -#endif -} - - -/*********************************************************************** -** -*/ REBINT OS_Create_Thread(CFUNC init, void *arg, REBCNT stack_size) -/* -** Creates a new thread for a REBOL task datatype. -** -** NOTE: -** For this to work, the multithreaded library option is -** needed in the C/C++ code generation settings. -** -** The Task_Ready stops return until the new task has been -** initialized (to avoid unknown new thread state). -** -***********************************************************************/ -{ - REBINT thread; -/* - Task_Ready = CreateEvent(NULL, TRUE, FALSE, "REBOL_Task_Launch"); - if (!Task_Ready) return -1; - - thread = _beginthread(init, stack_size, arg); - - if (thread) WaitForSingleObject(Task_Ready, 2000); - CloseHandle(Task_Ready); -*/ - return 1; -} - - -/*********************************************************************** -** -*/ void OS_Delete_Thread(void) -/* -** Can be called by a REBOL task to terminate its thread. -** -***********************************************************************/ -{ - //_endthread(); -} - - -/*********************************************************************** -** -*/ void OS_Task_Ready(REBINT tid) -/* -** Used for new task startup to resume the thread that -** launched the new task. -** -***********************************************************************/ -{ - //SetEvent(Task_Ready); -} - - -/*********************************************************************** -** -*/ int OS_Create_Process(REBCHR *call, u32 flags) -/* -** Return -1 on error, otherwise the process return code. -** -***********************************************************************/ -{ - return system(call); // returns -1 on system call error -} - -static int Try_Browser(char *browser, REBCHR *url) -{ - pid_t pid; - int result, status; - - switch (pid = fork()) { - case -1: - result = FALSE; - break; - case 0: - execlp(browser, browser, url, NULL); - exit(1); - break; - default: - waitpid(pid, &status, WUNTRACED); - result = WIFEXITED(status) - && (WEXITSTATUS(status) == 0); - } - - return result; -} - -/*********************************************************************** -** -*/ int OS_Browse(REBCHR *url, int reserved) -/* -***********************************************************************/ -{ - if ( -#if defined(TO_OSX) || defined(TO_OSXI) - Try_Browser("/usr/bin/open", url) -#else - Try_Browser("xdg-open", url) - || Try_Browser("x-www-browser", url) -#endif - ) return TRUE; - return FALSE; -} - - -/*********************************************************************** -** -*/ REBOOL OS_Request_File(REBRFR *fr) -/* -***********************************************************************/ -{ - return FALSE; -} - - - -/*********************************************************************** -** -*/ REBSER *OS_GOB_To_Image(REBGOB *gob) -/* -** Render a GOB into an image. Returns an image or zero if -** it cannot be done. -** -***********************************************************************/ -{ - return 0; -} diff --git a/src/os/posix/host-library.c b/src/os/posix/host-library.c new file mode 100644 index 0000000000..de725b6219 --- /dev/null +++ b/src/os/posix/host-library.c @@ -0,0 +1,117 @@ +// +// File: %host-library.c +// Summary: "POSIX Library-related functions" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This is for support of the LIBRARY! type from the host on +// systems that support 'dlopen'. +// + +#ifndef __cplusplus + // See feature_test_macros(7) + // This definition is redundant under C++ + #define _GNU_SOURCE +#endif + +#include +#include +#include +#include /* Obtain O_* constant definitions */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "reb-host.h" + + +#ifndef NO_DL_LIB +#include +#endif + + +// +// OS_Open_Library: C +// +// Load a DLL library and return the handle to it. +// If zero is returned, error indicates the reason. +// +void *OS_Open_Library(const REBCHR *path, REBCNT *error) +{ +#ifndef NO_DL_LIB + void *dll = dlopen(path, RTLD_LAZY/*|RTLD_GLOBAL*/); + if (error) { + *error = 0; // dlerror() returns a char* error message, so there's + } + // no immediate way to return an "error code" in *error + return dll; +#else + return 0; +#endif +} + + +// +// OS_Close_Library: C +// +// Free a DLL library opened earlier. +// +void OS_Close_Library(void *dll) +{ +#ifndef NO_DL_LIB + dlclose(dll); +#endif +} + + +// +// OS_Find_Function: C +// +// Get a DLL function address from its string name. +// +CFUNC *OS_Find_Function(void *dll, const char *funcname) +{ +#ifndef NO_DL_LIB + // !!! See notes about data pointers vs. function pointers in the + // definition of CFUNC. This is trying to stay on the right side + // of the specification, but OS APIs often are not standard C. So + // this implementation is not guaranteed to work, just to suppress + // compiler warnings. See: + // + // http://stackoverflow.com/a/1096349/211160 + + CFUNC *fp; + *cast(void**, &fp) = dlsym(dll, funcname); + return fp; +#else + return NULL; +#endif +} diff --git a/src/os/posix/host-process.c b/src/os/posix/host-process.c new file mode 100644 index 0000000000..c5785d1098 --- /dev/null +++ b/src/os/posix/host-process.c @@ -0,0 +1,77 @@ +// +// File: %host-process.c +// Summary: "POSIX Process API" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This was originally the file host-lib.c, providing the entire +// host API. When the host routines were broken into smaller +// pieces, it made sense that host-lib.c be kept as the largest +// set of related routines. That turned out to be the process +// related routines and support for CALL. +// + +#if !defined( __cplusplus) && defined(TO_LINUX) + // See feature_test_macros(7) + // This definition is redundant under C++ + #define _GNU_SOURCE // Needed for pipe2 on Linux +#endif + +#include +#include +#include + +#include + +#if !defined(NDEBUG) + #include +#endif + +#include "reb-host.h" + + +/*********************************************************************** +** +** OS Library Functions +** +***********************************************************************/ + + +// +// OS_Reap_Process: C +// +// pid: +// > 0, a signle process +// -1, any child process +// +// flags: +// 0: return immediately +// +// Return -1 on error +// +int OS_Reap_Process(int pid, int *status, int flags) +{ + return waitpid(pid, status, flags == 0? WNOHANG : 0); +} diff --git a/src/os/posix/host-readline.c b/src/os/posix/host-readline.c index 391257017a..6f74911de5 100644 --- a/src/os/posix/host-readline.c +++ b/src/os/posix/host-readline.c @@ -1,655 +1,669 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Simple readline() line input handler -** Author: Carl Sassenrath -** Purpose: -** Processes special keys for input line editing and recall. -** Avoides use of complex OS libraries and GNU readline(). -** but hardcodes some parts only for the common standard. -** -** Usage: This file is meant to be used in more than just REBOL, so -** it does not include the normal REBOL header files, but rather -** defines its own types and constants. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ +// +// File: %host-readline.c +// Summary: "Simple readline() line input handler" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Processes special keys for input line editing and recall. +// +// Avoids use of complex OS libraries and GNU readline() but hardcodes some +// parts only for the common standard. +// +// !!! This code is more or less unchanged from R3-Alpha. It is very +// primitive, and does not support UTF-8. +// #include #include #include +#include //for read and write //#define TEST_MODE // teset as stand-alone program -#ifdef NO_TTY_ATTRIBUTES -#ifdef TO_WIN32 -#include -#endif -#else -#include +#ifndef NO_TTY_ATTRIBUTES + #include #endif -#define FALSE 0 -#define TRUE (0==0) - -enum { - BEL = 7, - BS = 8, - LF = 10, - CR = 13, - ESC = 27, - DEL = 127, -}; +#include "reb-host.h" // Configuration: -#define TERM_BUF_LEN 4096 // chars allowed per line -#define READ_BUF_LEN 64 // chars per read() -#define MAX_HISTORY 300 // number of lines stored - -// Macros: (does not use reb-c.h) -#define MAKE_STR(l) (char*)malloc(l) -#define WRITE_CHAR(s) write(1, s, 1) -#define WRITE_CHARS(s,l) write(1, s, l) -#define WRITE_STR(s) write(1, s, strlen(s)) +#define TERM_BUF_LEN 4096 // chars allowed per line +#define READ_BUF_LEN 64 // chars per read() +#define MAX_HISTORY 300 // number of lines stored + + +#define WRITE_CHAR(s) \ + do { \ + if (write(1, s, 1) == -1) { \ + /* Error here, or better to "just try to keep going"? */ \ + } \ + } while (0) + +#define WRITE_CHARS(s,n) \ + do { \ + if (write(1, s, n) == -1) { \ + /* Error here, or better to "just try to keep going"? */ \ + } \ + } while (0) + +#define WRITE_STR(s) \ + do { \ + if (write(1, s, strlen(s)) == -1) { \ + /* Error here, or better to "just try to keep going"? */ \ + } \ + } while (0) #define DBG_INT(t,n) //printf("\r\ndbg[%s]: %d\r\n", t, (n)); #define DBG_STR(t,s) //printf("\r\ndbg[%s]: %s\r\n", t, (s)); typedef struct term_data { - char *buffer; - char *residue; - char *out; - int pos; - int end; - int hist; + REBYTE *buffer; + REBYTE *residue; + REBYTE *out; + int pos; + int end; + int hist; } STD_TERM; // Globals: -static int Term_Init = 0; // Terminal init was successful -static char **Line_History; // Prior input lines -static int Line_Count; // Number of prior lines +static REBOOL Term_Initialized = FALSE; // Terminal init was successful +static REBYTE **Line_History; // Prior input lines +static int Line_Count; // Number of prior lines #ifndef NO_TTY_ATTRIBUTES -static struct termios Term_Attrs; // Initial settings, restored on exit +static struct termios Term_Attrs; // Initial settings, restored on exit #endif -/*********************************************************************** -** -*/ STD_TERM *Init_Terminal(void) -/* -** Change the terminal modes to those required for proper -** REBOL console handling. Return TRUE on success. -** -***********************************************************************/ +extern STD_TERM *Init_Terminal(void); + +// +// Init_Terminal: C +// +// Change the terminal modes to those required for proper +// REBOL console handling. Return TRUE on success. +// +STD_TERM *Init_Terminal(void) { - STD_TERM *term; #ifndef NO_TTY_ATTRIBUTES - struct termios attrs; + struct termios attrs; - if (Term_Init || tcgetattr(0, &Term_Attrs)) return FALSE; + if (Term_Initialized || tcgetattr(0, &Term_Attrs)) return NULL; - attrs = Term_Attrs; + attrs = Term_Attrs; - // Local modes: - attrs.c_lflag &= ~(ECHO | ICANON); // raw input + // Local modes: + attrs.c_lflag &= ~(ECHO | ICANON); // raw input - // Input modes: - attrs.c_iflag &= ~(ICRNL | INLCR); // leave CR an LF as is + // Input modes: + attrs.c_iflag &= ~(ICRNL | INLCR); // leave CR an LF as is - // Output modes: - attrs.c_oflag |= ONLCR; // On output, emit CRLF + // Output modes: + attrs.c_oflag |= ONLCR; // On output, emit CRLF - // Special modes: - attrs.c_cc[VMIN] = 1; // min num of bytes for READ to return - attrs.c_cc[VTIME] = 0; // how long to wait for input + // Special modes: + attrs.c_cc[VMIN] = 1; // min num of bytes for READ to return + attrs.c_cc[VTIME] = 0; // how long to wait for input - tcsetattr(0, TCSADRAIN, &attrs); + tcsetattr(0, TCSADRAIN, &attrs); #endif - // Setup variables: - Line_History = (char**)malloc((MAX_HISTORY+2) * sizeof(char*)); - Line_History[0] = ""; - Line_Count = 1; + // Setup variables: + Line_History = OS_ALLOC_N(REBYTE*, MAX_HISTORY + 2); - term = malloc(sizeof(*term)); - memset(term, 0, sizeof(*term)); - term->buffer = MAKE_STR(TERM_BUF_LEN); - term->buffer[0] = 0; - term->residue = MAKE_STR(TERM_BUF_LEN); - term->residue[0] = 0; + char empty_line[] = ""; + Line_History[0] = OS_ALLOC_N(REBYTE, LEN_BYTES(empty_line) + 1); + strcpy(s_cast(Line_History[0]), empty_line); + Line_Count = 1; - Term_Init = TRUE; + STD_TERM *term = OS_ALLOC_ZEROFILL(STD_TERM); + term->buffer = OS_ALLOC_N(REBYTE, TERM_BUF_LEN); + term->buffer[0] = 0; + term->residue = OS_ALLOC_N(REBYTE, TERM_BUF_LEN); + term->residue[0] = 0; - return term; + Term_Initialized = TRUE; + + return term; } -/*********************************************************************** -** -*/ void Quit_Terminal(STD_TERM *term) -/* -** Restore the terminal modes original entry settings, -** in preparation for exit from program. -** -***********************************************************************/ +extern void Quit_Terminal(STD_TERM *term); + +// +// Quit_Terminal: C +// +// Restore the terminal modes original entry settings, +// in preparation for exit from program. +// +void Quit_Terminal(STD_TERM *term) { - int n; + int n; - if (Term_Init) { + if (Term_Initialized) { #ifndef NO_TTY_ATTRIBUTES - tcsetattr(0, TCSADRAIN, &Term_Attrs); + tcsetattr(0, TCSADRAIN, &Term_Attrs); #endif - free(term->residue); - free(term->buffer); - free(term); - for (n = 1; n < Line_Count; n++) free(Line_History[n]); - free(Line_History); - } - - Term_Init = FALSE; + OS_FREE(term->residue); + OS_FREE(term->buffer); + OS_FREE(term); + for (n = 0; n < Line_Count; n++) OS_FREE(Line_History[n]); + OS_FREE(Line_History); + } + + Term_Initialized = FALSE; } -/*********************************************************************** -** -*/ static void Write_Char(char c, int n) -/* -** Write out repeated number of chars. -** Unicode: not used -** -***********************************************************************/ +// +// Write_Char: C +// +// Write out repeated number of chars. +// Unicode: not used +// +static void Write_Char(REBYTE c, int n) { - char buf[4]; + REBYTE buf[4]; - buf[0] = c; - for (; n > 0; n--) WRITE_CHAR(buf); + buf[0] = c; + for (; n > 0; n--) + WRITE_CHAR(buf); } -/*********************************************************************** -** -*/ static void Store_Line(STD_TERM *term) -/* -** Makes a copy of the current buffer and store it in the -** history list. Returns the copied string. -** -***********************************************************************/ +// +// Store_Line: C +// +// Makes a copy of the current buffer and store it in the +// history list. Returns the copied string. +// +static void Store_Line(STD_TERM *term) { - term->buffer[term->end] = 0; - term->out = MAKE_STR(term->end + 1); - strcpy(term->out, term->buffer); - - // If max history, drop older lines (but not [0] empty line): - if (Line_Count >= MAX_HISTORY) { - free(Line_History[1]); - memmove(Line_History+1, Line_History+2, (MAX_HISTORY-2)*sizeof(char*)); - Line_Count = MAX_HISTORY-1; - } - - Line_History[Line_Count++] = term->out; + term->buffer[term->end] = 0; + term->out = OS_ALLOC_N(REBYTE, term->end + 1); + strcpy(s_cast(term->out), s_cast(term->buffer)); + + // If max history, drop older lines (but not [0] empty line): + if (Line_Count >= MAX_HISTORY) { + OS_FREE(Line_History[1]); + memmove( + Line_History + 1, + Line_History + 2, + (MAX_HISTORY - 2) * sizeof(REBYTE*) + ); + Line_Count = MAX_HISTORY - 1; + } + + Line_History[Line_Count] = term->out; + ++Line_Count; } -/*********************************************************************** -** -*/ static void Recall_Line(STD_TERM *term) -/* -** Set the current buffer to the contents of the history -** list at its current position. Clip at the ends. -** Return the history line index number. -** Unicode: ok -** -***********************************************************************/ +// +// Recall_Line: C +// +// Set the current buffer to the contents of the history +// list at its current position. Clip at the ends. +// Return the history line index number. +// Unicode: ok +// +static void Recall_Line(STD_TERM *term) { - if (term->hist < 0) term->hist = 0; - - if (term->hist == 0) - Write_Char(BEL, 1); // bell - - if (term->hist >= Line_Count) { - // Special case: no "next" line: - term->hist = Line_Count; - term->buffer[0] = 0; - term->pos = term->end = 0; - } - else { - // Fetch prior line: - strcpy(term->buffer, Line_History[term->hist]); - term->pos = term->end = strlen(term->buffer); - } + if (term->hist < 0) term->hist = 0; + + if (term->hist == 0) + Write_Char(BEL, 1); // bell + + if (term->hist >= Line_Count) { + // Special case: no "next" line: + term->hist = Line_Count; + term->buffer[0] = 0; + term->pos = term->end = 0; + } + else { + // Fetch prior line: + strcpy(s_cast(term->buffer), s_cast(Line_History[term->hist])); + term->pos = term->end = LEN_BYTES(term->buffer); + } } -/*********************************************************************** -** -*/ static void Clear_Line(STD_TERM *term) -/* -** Clear all the chars from the current position to the end. -** Reset cursor to current position. -** Unicode: not used -** -***********************************************************************/ +// +// Clear_Line: C +// +// Clear all the chars from the current position to the end. +// Reset cursor to current position. +// Unicode: not used +// +static void Clear_Line(STD_TERM *term) { - Write_Char(' ', term->end - term->pos); // wipe prior line - Write_Char(BS, term->end - term->pos); // return to position + Write_Char(' ', term->end - term->pos); // wipe prior line + Write_Char(BS, term->end - term->pos); // return to position } -/*********************************************************************** -** -*/ static void Home_Line(STD_TERM *term) -/* -** Reset cursor to home position. -** Unicode: not used -** -***********************************************************************/ +// +// Home_Line: C +// +// Reset cursor to home position. +// Unicode: not used +// +static void Home_Line(STD_TERM *term) { - Write_Char(BS, term->pos); - term->pos = 0; + Write_Char(BS, term->pos); + term->pos = 0; } -/*********************************************************************** -** -*/ static void End_Line(STD_TERM *term) -/* -** Move cursor to end position. -** Unicode: not used -** -***********************************************************************/ +// +// End_Line: C +// +// Move cursor to end position. +// Unicode: not used +// +static void End_Line(STD_TERM *term) { - int len = term->end - term->pos; + int len = term->end - term->pos; - if (len > 0) { - WRITE_CHARS(term->buffer+term->pos, len); - term->pos = term->end; - } + if (len > 0) { + WRITE_CHARS(term->buffer+term->pos, len); + term->pos = term->end; + } } -/*********************************************************************** -** -*/ static void Show_Line(STD_TERM *term, int blanks) -/* -** Refresh a line from the current position to the end. -** Extra blanks can be specified to erase chars off end. -** If blanks is negative, stay at end of line. -** Reset the cursor back to current position. -** Unicode: ok -** -***********************************************************************/ +// +// Show_Line: C +// +// Refresh a line from the current position to the end. +// Extra blanks can be specified to erase chars off end. +// If blanks is negative, stay at end of line. +// Reset the cursor back to current position. +// Unicode: ok +// +static void Show_Line(STD_TERM *term, int blanks) { - int len; - - //printf("\r\nsho pos: %d end: %d ==", term->pos, term->end); - - // Clip bounds: - if (term->pos < 0) term->pos = 0; - else if (term->pos > term->end) term->pos = term->end; - - if (blanks >= 0) { - len = term->end - term->pos; - WRITE_CHARS(term->buffer+term->pos, len); - } - else { - WRITE_CHARS(term->buffer, term->end); - blanks = -blanks; - len = 0; - } - - Write_Char(' ', blanks); - Write_Char(BS, blanks + len); // return to position or end + int len; + + //printf("\r\nsho pos: %d end: %d ==", term->pos, term->end); + + // Clip bounds: + if (term->pos < 0) term->pos = 0; + else if (term->pos > term->end) term->pos = term->end; + + if (blanks >= 0) { + len = term->end - term->pos; + WRITE_CHARS(term->buffer+term->pos, len); + } + else { + WRITE_CHARS(term->buffer, term->end); + blanks = -blanks; + len = 0; + } + + Write_Char(' ', blanks); + Write_Char(BS, blanks + len); // return to position or end } -/*********************************************************************** -** -*/ static char *Insert_Char(STD_TERM *term, char *cp) -/* -** Insert a char at the current position. Adjust end position. -** Redisplay the line. -** Unicode: not yet supported! -** -***********************************************************************/ +// +// Insert_Char: C +// +// Insert a char at the current position. Adjust end position. +// Redisplay the line. +// Unicode: not yet supported! +// +static REBYTE *Insert_Char(STD_TERM *term, REBYTE *cp) { - //printf("\r\nins pos: %d end: %d ==", term->pos, term->end); - if (term->end < TERM_BUF_LEN-1) { // avoid buffer overrun - - if (term->pos < term->end) { // open space for it: - memmove(term->buffer + term->pos + 1, term->buffer + term->pos, 1 + term->end - term->pos); - } - WRITE_CHAR(cp); - term->buffer[term->pos] = *cp; - term->end++; - term->pos++; - Show_Line(term, 0); - } - - return ++cp; + //printf("\r\nins pos: %d end: %d ==", term->pos, term->end); + if (term->end < TERM_BUF_LEN-1) { // avoid buffer overrun + + if (term->pos < term->end) { // open space for it: + memmove( + term->buffer + term->pos + 1, // dest pointer + term->buffer + term->pos, // source pointer + 1 + term->end - term->pos // length + ); + } + WRITE_CHAR(cp); + term->buffer[term->pos] = *cp; + term->end++; + term->pos++; + Show_Line(term, 0); + } + + return ++cp; } -/*********************************************************************** -** -*/ static void Delete_Char(STD_TERM *term, int back) -/* -** Delete a char at the current position. Adjust end position. -** Redisplay the line. Blank out extra char at end. -** Unicode: not yet supported! -** -***********************************************************************/ +// +// Delete_Char: C +// +// Delete a char at the current position. Adjust end position. +// Redisplay the line. Blank out extra char at end. +// Unicode: not yet supported! +// +static void Delete_Char(STD_TERM *term, REBOOL back) { - int len; + int len; - if ( (term->pos == term->end) && back == 0) return; //Ctrl-D at EOL - - if (back) term->pos--; + if ( (term->pos == term->end) && back == 0) return; //Ctrl-D at EOL - len = 1 + term->end - term->pos; + if (back) term->pos--; - if (term->pos >= 0 && len > 0) { - memmove(term->buffer + term->pos, term->buffer + term->pos + 1, len); - if (back) Write_Char(BS, 1); - term->end--; - Show_Line(term, 1); - } - else term->pos = 0; + len = 1 + term->end - term->pos; + + if (term->pos >= 0 && len > 0) { + memmove(term->buffer + term->pos, term->buffer + term->pos + 1, len); + if (back) Write_Char(BS, 1); + term->end--; + Show_Line(term, 1); + } + else term->pos = 0; } -/*********************************************************************** -** -*/ static void Move_Cursor(STD_TERM *term, int count) -/* -** Move cursor right or left by one char. -** Unicode: not yet supported! -** -***********************************************************************/ +// +// Move_Cursor: C +// +// Move cursor right or left by one char. +// Unicode: not yet supported! +// +static void Move_Cursor(STD_TERM *term, int count) { - if (count < 0) { - if (term->pos > 0) { - term->pos--; - Write_Char(BS, 1); - } - } - else { - if (term->pos < term->end) { - WRITE_CHAR(term->buffer + term->pos); - term->pos++; - } - } + if (count < 0) { + if (term->pos > 0) { + term->pos--; + Write_Char(BS, 1); + } + } + else { + if (term->pos < term->end) { + WRITE_CHAR(term->buffer + term->pos); + term->pos++; + } + } } -/*********************************************************************** -** -*/ static char *Process_Key(STD_TERM *term, char *cp) -/* -** Process the next key. If it's an edit key, perform the -** necessary editing action. Return position of next char. -** Unicode: not yet supported! -** -***********************************************************************/ +// +// Process_Key: C +// +// Process the next key. If it's an edit key, perform the +// necessary editing action. Return position of next char. +// Unicode: not yet supported! +// +static REBYTE *Process_Key(STD_TERM *term, REBYTE *cp) { - int len; - - if (*cp == 0) return cp; - - // No UTF-8 yet - if (*cp < 0) *cp = '?'; - - if (*cp == ESC) { - // Escape sequence: - cp++; - if (*cp == '[' || *cp == 'O') { - - // Special key: - switch (*++cp) { - - // Arrow keys: - case 'A': // up arrow - term->hist -= 2; - case 'B': // down arrow - term->hist++; - len = term->end; - Home_Line(term); - Recall_Line(term); - if (len <= term->end) len = 0; - else len = term->end - len; - Show_Line(term, len-1); // len < 0 (stay at end) - break; - - case 'D': // left arrow - Move_Cursor(term, -1); - break; - case 'C': // right arrow - Move_Cursor(term, 1); - break; - - // Other special keys: - case '1': // home - Home_Line(term); - cp++; // remove ~ - break; - case '4': // end - End_Line(term); - cp++; // remove ~ - break; - case '3': // delete - Delete_Char(term, FALSE); - cp++; // remove ~ - break; - - case 'H': // home - Home_Line(term); - break; - case 'F': // end - End_Line(term); - break; - - default: - WRITE_STR("[ESC]"); - cp--; - } - } - else { - switch (*++cp) { - case 'H': // home - Home_Line(term); - break; - case 'F': // end - End_Line(term); - break; - default: - // Q: what other keys do we want to support ?! - WRITE_STR("[ESC]"); - cp--; - } - } - } - else { - // ASCII char: - switch (*cp) { - - case BS: // backspace - case DEL: // delete - Delete_Char(term, TRUE); - break; - - case CR: // CR - if (cp[1] == LF) cp++; // eat - case LF: // LF - WRITE_STR("\r\n"); - Store_Line(term); - break; - - case 1: // CTRL-A - Home_Line(term); - break; - case 2: // CTRL-B - Move_Cursor(term, -1); - break; - case 4: // CTRL-D - Delete_Char(term, FALSE); - break; - case 5: // CTRL-E - End_Line(term); - break; - case 6: // CTRL-F - Move_Cursor(term, 1); - break; - - default: - cp = Insert_Char(term, cp); - cp--; - } - } - - return ++cp; + if (*cp == 0) + return cp; + + // No UTF-8 yet + if (*cp > 127) + *cp = '?'; + + if (*cp == ESC) { + // Escape sequence: + cp++; + if (*cp == '[' || *cp == 'O') { + + // Special key: + switch (*++cp) { + + // Arrow keys: + case 'A': // up arrow + term->hist -= 2; + // falls through + case 'B': { // down arrow + int len = term->end; + + ++term->hist; + + Home_Line(term); + Recall_Line(term); + + if (len <= term->end) + len = 0; + else + len = term->end - len; + + Show_Line(term, len - 1); // len < 0 (stay at end) + break; } + + case 'D': // left arrow + Move_Cursor(term, -1); + break; + case 'C': // right arrow + Move_Cursor(term, 1); + break; + + // Other special keys: + case '1': // home + Home_Line(term); + cp++; // remove ~ + break; + case '4': // end + End_Line(term); + cp++; // remove ~ + break; + case '3': // delete + Delete_Char(term, FALSE); + cp++; // remove ~ + break; + + case 'H': // home + Home_Line(term); + break; + case 'F': // end + End_Line(term); + break; + + case 'J': // erase to end of screen + Clear_Line(term); + break; + + default: + WRITE_STR("[ESC]"); + cp--; + } + } + else { + switch (*++cp) { + case 'H': // home + Home_Line(term); + break; + case 'F': // end + End_Line(term); + break; + default: + // Q: what other keys do we want to support ?! + WRITE_STR("[ESC]"); + cp--; + } + } + } + else { + // ASCII char: + switch (*cp) { + + case BS: // backspace + case DEL: // delete + Delete_Char(term, TRUE); + break; + + case CR: // CR + if (cp[1] == LF) cp++; // eat + // falls through + case LF: // LF + WRITE_STR("\r\n"); + Store_Line(term); + break; + + case 1: // CTRL-A + Home_Line(term); + break; + case 2: // CTRL-B + Move_Cursor(term, -1); + break; + case 4: // CTRL-D + Delete_Char(term, FALSE); + break; + case 5: // CTRL-E + End_Line(term); + break; + case 6: // CTRL-F + Move_Cursor(term, 1); + break; + + default: + cp = Insert_Char(term, cp); + cp--; + } + } + + return ++cp; } -/*********************************************************************** -** -*/ static int Read_Bytes(STD_TERM *term, char *buf, int len) -/* -** Read the next "chunk" of data into the terminal buffer. -** -***********************************************************************/ +// +// Read_Bytes: C +// +// Read the next "chunk" of data into the terminal buffer. +// +static int Read_Bytes(STD_TERM *term, REBYTE *buf, int len) { - int end; - - // If we have leftovers: - if (term->residue[0]) { - end = strlen(term->residue); - if (end < len) len = end; - strncpy(buf, term->residue, len); // terminated below - memmove(term->residue, term->residue+len, end-len); // remove - term->residue[end-len] = 0; - } - else { - // Read next few bytes. We don't know how many may be waiting. - // We assume that escape-sequences are always complete in buf. - // (No partial escapes.) If this is not true, then we will need - // to add an additional "collection" loop here. - if ((len = read(0, buf, len)) < 0) { - WRITE_STR("\r\nI/O terminated\r\n"); - Quit_Terminal(term); // something went wrong - exit(100); - } - } - - buf[len] = 0; - buf[len+1] = 0; - - DBG_INT("read len", len); - - return len; + int end; + + // If we have leftovers: + if (term->residue[0]) { + end = LEN_BYTES(term->residue); + if (end < len) len = end; + strncpy(s_cast(buf), s_cast(term->residue), len); // terminated below + memmove(term->residue, term->residue+len, end-len); // remove + term->residue[end-len] = 0; + } + else { + // Read next few bytes. We don't know how many may be waiting. + // We assume that escape-sequences are always complete in buf. + // (No partial escapes.) If this is not true, then we will need + // to add an additional "collection" loop here. + if ((len = read(0, buf, len)) < 0) { + WRITE_STR("\r\nI/O terminated\r\n"); + Quit_Terminal(term); // something went wrong + exit(100); + } + } + + buf[len] = 0; + buf[len+1] = 0; + + DBG_INT("read len", len); + + return len; } -/*********************************************************************** -** -*/ int Read_Line(STD_TERM *term, char *result, int limit) -/* -** Read a line (as a sequence of bytes) from the terminal. -** Handles line editing and line history recall. -** Returns number of bytes in line. -** -***********************************************************************/ +extern int Read_Line(STD_TERM *term, REBYTE *result, int limit); + +// +// Read_Line: C +// +// Read a line (as a sequence of bytes) from the terminal. +// Handles line editing and line history recall. +// Returns number of bytes in line. +// +int Read_Line(STD_TERM *term, REBYTE *result, int limit) { - char buf[READ_BUF_LEN]; - char *cp; - int len; // length of IO read - - term->pos = term->end = 0; - term->hist = Line_Count; - term->out = 0; - term->buffer[0] = 0; - - do { - Read_Bytes(term, buf, READ_BUF_LEN-2); - for (cp = buf; *cp;) { - cp = Process_Key(term, cp); - } - } while (!term->out); - - // Not at end of input? Save any unprocessed chars: - if (*cp) { - if (strlen(term->residue) + strlen(cp) < TERM_BUF_LEN-1) // avoid overrun - strcat(term->residue, cp); - } - - // Fill the output buffer: - len = strlen(term->out); - if (len >= limit-1) len = limit-2; - strncpy(result, term->out, limit); - result[len++] = LF; - result[len] = 0; - - return len; + REBYTE buf[READ_BUF_LEN]; + REBYTE *cp; + int len; // length of IO read + + term->pos = term->end = 0; + term->hist = Line_Count; + term->out = 0; + term->buffer[0] = 0; + + do { + Read_Bytes(term, buf, READ_BUF_LEN-2); + for (cp = buf; *cp;) { + cp = Process_Key(term, cp); + } + } while (!term->out); + + // Not at end of input? Save any unprocessed chars: + if (*cp) { + if (LEN_BYTES(term->residue) + LEN_BYTES(cp) >= TERM_BUF_LEN - 1) { + // + // avoid overrun + } + else + strcat(s_cast(term->residue), s_cast(cp)); + } + + // Fill the output buffer: + len = LEN_BYTES(term->out); + if (len >= limit-1) len = limit-2; + strncpy(s_cast(result), s_cast(term->out), limit); + result[len++] = LF; + result[len] = 0; + + return len; } #ifdef TEST_MODE test(STD_TERM *term, char *cp) { - term->hist = Line_Count; - term->pos = term->end = 0; - term->out = 0; - term->buffer[0] = 0; - while (*cp) cp = Process_Key(term, cp); + term->hist = Line_Count; + term->pos = term->end = 0; + term->out = 0; + term->buffer[0] = 0; + while (*cp) cp = Process_Key(term, cp); } main() { - int i; - char buf[1024]; - STD_TERM *term; + int i; + char buf[1024]; + STD_TERM *term; - term = Init_Terminal(); + term = Init_Terminal(); - Write_Char('-', 50); - WRITE_STR("\r\n"); + Write_Char('-', 50); + WRITE_STR("\r\n"); #ifdef WIN32 - test(term, "text\010\010st\n"); //bs bs - test(term, "test\001xxxx\n"); // home - test(term, "test\001\005xxxx\n"); // home - test(term, "\033[A\n"); // up arrow + test(term, "text\010\010st\n"); //bs bs + test(term, "test\001xxxx\n"); // home + test(term, "test\001\005xxxx\n"); // home + test(term, "\033[A\n"); // up arrow #endif - do { - WRITE_STR(">> "); - i = Read_Line(term, buf, 1000); - printf("len: %d %s\r\n", i, term->out); - } while (i > 0); + do { + WRITE_STR(">> "); + i = Read_Line(term, buf, 1000); + printf("len: %d %s\r\n", i, term->out); + } while (i > 0); - Quit_Terminal(term); + Quit_Terminal(term); } #endif diff --git a/src/os/posix/host-time.c b/src/os/posix/host-time.c new file mode 100644 index 0000000000..2a6f5a134c --- /dev/null +++ b/src/os/posix/host-time.c @@ -0,0 +1,170 @@ +// +// File: %host-time.c +// Summary: "POSIX Host Time Functions" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Provide platform support for times and timing information. +// + +#ifndef __cplusplus + // See feature_test_macros(7) + // This definition is redundant under C++ + #define _GNU_SOURCE +#endif + +#include +#include +#include +#include /* Obtain O_* constant definitions */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "reb-host.h" + + +#ifndef timeval // for older systems +#include +#endif + +// +// Get_Timezone: C +// +// Get the time zone in minutes from GMT. +// NOT consistently supported in Posix OSes! +// We have to use a few different methods. +// +// !!! "local_tm->tm_gmtoff / 60 would make the most sense, +// but is no longer used" (said a comment) +// +static int Get_Timezone(struct tm *local_tm) +{ + UNUSED(local_tm); + +#ifdef HAS_SMART_TIMEZONE + time_t rightnow; + time(&rightnow); + return cast(int, + difftime(mktime(localtime(&rightnow)), mktime(gmtime(&rightnow))) / 60 + ); +#else + struct tm tm2; + time_t rightnow; + time(&rightnow); + tm2 = *localtime(&rightnow); + tm2.tm_isdst=0; + return (int)difftime(mktime(&tm2), mktime(gmtime(&rightnow))) / 60; +#endif +} + + +// +// Convert_Date: C +// +// Convert local format of system time into standard date +// and time structure (for date/time and file timestamps). +// +void Convert_Date(REBVAL *out, time_t *stime, long usec) +{ + struct tm *time = gmtime(stime); + + RL_Init_Date( + out, + time->tm_year + 1900, // year + time->tm_mon + 1, // month + time->tm_mday, // day + time->tm_hour * 3600 + time->tm_min * 60 + time->tm_sec, // "time" + usec * 1000, // nano + Get_Timezone(time) // zone + ); +} + + +// +// OS_Get_Time: C +// +// Get the current system date/time in UTC plus zone offset (mins). +// +void OS_Get_Time(REBVAL *out) +{ + struct timeval tv; + time_t stime; + + gettimeofday(&tv, 0); // (tz field obsolete) + stime = tv.tv_sec; + Convert_Date(out, &stime, tv.tv_usec); +} + + +// +// OS_Delta_Time: C +// +// Return time difference in microseconds. If base = 0, then +// return the counter. If base != 0, compute the time difference. +// +// NOTE: This needs to be precise, but many OSes do not +// provide a precise time sampling method. So, if the target +// posix OS does, add the ifdef code in here. +// +i64 OS_Delta_Time(i64 base, int flags) +{ + UNUSED(flags); + + struct timeval tv; + gettimeofday(&tv,0); + + i64 time = cast(i64, tv.tv_sec * 1000000) + tv.tv_usec; + if (base == 0) + return time; + + return time - base; +} + + +// +// OS_File_Time: C +// +// Convert file.time to REBOL date/time format. +// Time zone is UTC. +// +void OS_File_Time(REBVAL *out, struct devreq_file *file) +{ + if (sizeof(time_t) > sizeof(file->time.l)) { + REBI64 t = file->time.l; + t |= cast(REBI64, file->time.h) << 32; + Convert_Date(out, cast(time_t*, &t), 0); + } + else { + Convert_Date(out, cast(time_t *, &file->time.l), 0); + } +} + diff --git a/src/os/posix/host-window.c b/src/os/posix/host-window.c index bb3f6f9463..d10a092fe3 100644 --- a/src/os/posix/host-window.c +++ b/src/os/posix/host-window.c @@ -1,45 +1,39 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Windowing stubs -** File: host-window.c -** Purpose: Provides stub functions for windowing. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ - -/* WARNING: -** The function declarations here cannot be modified without -** also modifying those found in the other OS host-lib files! -** Do not even modify the argument names. -*/ +// +// File: %host-window.c +// Summary: "Windowing stubs" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Provides stub functions for windowing. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// WARNING: The function declarations here cannot be modified without also +// modifying those found in the other OS host-lib files! Do not even modify +// the argument names. +// #include #include @@ -49,7 +43,6 @@ #include #include "reb-host.h" -#include "host-lib.h" #include "rebol-lib.h" @@ -57,137 +50,157 @@ //** OSAL Library Functions ******************************************** //********************************************************************** -/*********************************************************************** -** -*/ void OS_Init_Graphics(REBGOB *gob) -/* -** Initialize graphics subsystem. Store Gob_Root. -** -***********************************************************************/ +// +// OS_Init_Graphics: C +// +// Initialize graphics subsystem. Store Gob_Root. +// +void OS_Init_Graphics(REBGOB *gob) { + UNUSED(gob); } -/*********************************************************************** -** -*/ void OS_GUI_Metrics(REBOL_OS_METRICS *met) -/* -** Provide info about the hosting GUI. -** -***********************************************************************/ +// +// OS_GUI_Metrics: C +// +// Provide info about the hosting GUI. +// +void OS_GUI_Metrics(REBOL_OS_METRICS *met) { + UNUSED(met); } -/*********************************************************************** -** -*/ REBINT OS_Show_Gob(REBGOB *gob) -/* -** Notes: -** 1. Can be called with NONE (0), Gob_Root (All), or a -** specific gob to open, close, or refresh. -** -** 2. A new window will be in Gob_Root/pane but will not -** have GOBF_WINDOW set. -** -** 3. A closed window will have no PARENT and will not be -** in the Gob_Root/pane but will have GOBF_WINDOW set. -** -***********************************************************************/ +// +// OS_Show_Gob: C +// +// Notes: +// 1. Can be called with NONE (0), Gob_Root (All), or a +// specific gob to open, close, or refresh. +// +// 2. A new window will be in Gob_Root/pane but will not +// have GOBF_WINDOW set. +// +// 3. A closed window will have no PARENT and will not be +// in the Gob_Root/pane but will have GOBF_WINDOW set. +// +REBINT OS_Show_Gob(REBGOB *gob) { - return 0; + UNUSED(gob); + + return 0; } -/*********************************************************************** -** -*/ void OS_Map_Gob(REBGOB **gob, REBPAR *xy, REBOOL inner) -/* -** Map GOB and offset to inner or outer GOB and offset. -** -***********************************************************************/ +// +// OS_Map_Gob: C +// +// Map GOB and offset to inner or outer GOB and offset. +// +void OS_Map_Gob(REBGOB **gob, REBPAR *xy, REBOOL inner) { + UNUSED(gob); + UNUSED(xy); + UNUSED(inner); } -/*********************************************************************** -** -*/ REBINT OS_Size_Text(REBGOB *gob, REBPAR *size) -/* -** Return the area size of the text. -** -***********************************************************************/ +// +// OS_Size_Text: C +// +// Return the area size of the text. +// +REBINT OS_Size_Text(REBGOB *gob, REBPAR *size) { - return 0; + UNUSED(gob); + UNUSED(size); + + return 0; } -/*********************************************************************** -** -*/ REBINT OS_Offset_To_Caret(REBGOB *gob, REBPAR xy, REBINT *element, REBINT *position) -/* -** Return the element and position for a given offset pair. -** -***********************************************************************/ +// +// OS_Offset_To_Caret: C +// +// Return the element and position for a given offset pair. +// +REBINT OS_Offset_To_Caret(REBGOB *gob, REBPAR xy, REBINT *element, REBINT *position) { - return 0; + UNUSED(gob); + UNUSED(xy); + UNUSED(element); + UNUSED(position); + + return 0; } -/*********************************************************************** -** -*/ REBINT OS_Caret_To_Offset(REBGOB *gob, REBPAR *xy, REBINT element, REBINT position) -/* -** Return the offset pair for a given element and position. -** -***********************************************************************/ +// +// OS_Caret_To_Offset: C +// +// Return the offset pair for a given element and position. +// +REBINT OS_Caret_To_Offset(REBGOB *gob, REBPAR *xy, REBINT element, REBINT position) { - return 0; + UNUSED(gob); + UNUSED(xy); + UNUSED(element); + UNUSED(position); + + return 0; } -/*********************************************************************** -** -*/ REBINT OS_Gob_To_Image(REBSER *image, REBGOB *gob) -/* -** Render gob into an image. -** Clip to keep render inside the image provided. -** -***********************************************************************/ +// +// OS_Gob_To_Image: C +// +// Render gob into an image. +// Clip to keep render inside the image provided. +// +REBINT OS_Gob_To_Image(REBSER *image, REBGOB *gob) { - return 0; + UNUSED(image); + UNUSED(gob); + + return 0; } -/*********************************************************************** -** -*/ REBINT OS_Draw_Image(REBSER *image, REBSER *block) -/* -** Render DRAW dialect into an image. -** Clip to keep render inside the image provided. -** -***********************************************************************/ +// +// OS_Draw_Image: C +// +// Render DRAW dialect into an image. +// Clip to keep render inside the image provided. +// +REBINT OS_Draw_Image(REBSER *image, REBARR *block) { - return 0; + UNUSED(image); + UNUSED(block); + + return 0; } -/*********************************************************************** -** -*/ REBINT OS_Effect_Image(REBSER *image, REBSER *block) -/* -** Render EFFECT dialect into an image. -** Clip to keep render inside the image provided. -** -***********************************************************************/ +// +// OS_Effect_Image: C +// +// Render EFFECT dialect into an image. +// Clip to keep render inside the image provided. +// +REBINT OS_Effect_Image(REBSER *image, REBARR *block) { - return 0; + UNUSED(image); + UNUSED(block); + + return 0; } -/*********************************************************************** -** -*/ void OS_Cursor_Image(REBINT n, REBSER *image) -/* -***********************************************************************/ +// +// OS_Cursor_Image: C +// +void OS_Cursor_Image(REBINT n, REBSER *image) { + UNUSED(n); + UNUSED(image); } diff --git a/src/os/sys-net.h b/src/os/sys-net.h new file mode 100644 index 0000000000..a2c9ebc3f4 --- /dev/null +++ b/src/os/sys-net.h @@ -0,0 +1,101 @@ +// +// File: %sys-net.h +// Summary: "System network definitions" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// The original R3-Alpha code said: +// +// "Network standards? What network standards?" -Bill G. +// +// This is a small file of network compatibility definitions which makes it +// easier to have more code shared in the Windows and BSD implementations. +// It's not exhaustive, but allows at least some code in the shared network +// handling to avoid having `#ifdef TO_WINDOWS` in it. +// + +#ifdef TO_WINDOWS + #include + #include // needed for ip_mreq definition for multicast + + #define GET_ERROR WSAGetLastError() + #define IOCTL ioctlsocket + #define CLOSE_SOCKET closesocket + + #define NE_ISCONN WSAEISCONN + #define NE_WOULDBLOCK WSAEWOULDBLOCK + #define NE_INPROGRESS WSAEINPROGRESS + #define NE_ALREADY WSAEALREADY + #define NE_NOTCONN WSAENOTCONN + #define NE_INVALID WSAEINVAL + + typedef int socklen_t; +#else + #ifdef TO_AMIGA + typedef char __BYTE; + typedef unsigned char __UBYTE; + typedef char * __STRPTR; + typedef long __LONG; + #endif + + #include + #include + #include + #include + #include + #include + + #define GET_ERROR errno + #define IOCTL ioctl + #define CLOSE_SOCKET close + #define SOCKET unsigned int + + #define NE_ISCONN EISCONN + #define NE_WOULDBLOCK EAGAIN // see include/asm/errno.h + #define NE_INPROGRESS EINPROGRESS + #define NE_ALREADY EALREADY + #define NE_NOTCONN ENOTCONN + #define NE_INVALID EINVAL + + // Null Win32 functions: + #define WSADATA int + + // FreeBSD mystery define: + #ifndef u_int32_t + #define u_int32_t long + #endif + + #ifndef HOSTENT + typedef struct hostent HOSTENT; + #endif + + #ifndef MAXGETHOSTSTRUCT + #define MAXGETHOSTSTRUCT ((sizeof(struct hostent)+15) & ~15) + #endif +#endif + +#define BAD_SOCKET (~0) +#define MAX_TRANSFER 32000 // Max send/recv buffer size +#define MAX_HOST_NAME 256 // Max length of host name diff --git a/src/os/unzip.reb b/src/os/unzip.reb new file mode 100644 index 0000000000..20f68e63b1 --- /dev/null +++ b/src/os/unzip.reb @@ -0,0 +1,455 @@ +REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Zip and Unzip Services" + Rights: { + Copyright 2009-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + + See README.md and CREDITS.md for more information. + } + License: { + Public Domain License + } + Notes: { + Original code from rebzip.r from www.REBOL.org + Only DEFLATE and STORE methods are supported. + } +] + +ctx-zip: context [ + crc-32: func [ + "Returns a CRC32 checksum." + data [any-string! binary!] "Data to checksum" + ][ + copy skip to binary! checksum/method data 'crc32 4 + ] + + ;signatures + local-file-sig: #{504B0304} + central-file-sig: #{504B0102} + end-of-central-sig: #{504B0506} + data-descriptor-sig: #{504B0708} + + to-ilong: func [ + "Converts an integer to a little-endian long." + value [integer!] "AnyValue to convert" + ][ + copy reverse skip to binary! value 4 + ] + + to-ishort: func [ + "Converts an integer to a little-endian short." + value [integer!] "AnyValue to convert" + ][ + copy/part reverse skip to binary! value 4 2 + ] + + to-long: func [ + "Converts an integer to a big-endian long." + value [integer!] "AnyValue to convert" + ][ + copy skip to binary! value 4 + ] + + get-ishort: func [ + "Converts a little-endian short to an integer." + value [any-string! binary! port!] "AnyValue to convert" + ][ + to integer! reverse copy/part value 2 + ] + + get-ilong: func [ + "Converts a little-endian long to an integer." + value [any-string! binary! port!] "AnyValue to convert" + ][ + to integer! reverse copy/part value 4 + ] + + to-msdos-time: func [ + "Converts to a msdos time." + value [time!] "AnyValue to convert" + ][ + to-ishort (value/hour * 2048) + or+ (value/minute * 32) + or+ to integer! value/second / 2 + ] + + to-msdos-date: func [ + "Converts to a msdos date." + value [date!] + ][ + to-ishort 512 * (max 0 value/year - 1980) + or+ (value/month * 32) or+ value/day + ] + + get-msdos-time: func [ + "Converts from a msdos time." + value [any-string! binary! port!] + ][ + value: get-ishort value + to time! reduce [ + 63488 and* value / 2048 + 2016 and* value / 32 + 31 and* value * 2 + ] + ] + + get-msdos-date: func [ + "Converts from a msdos date." + value [any-string! binary! port!] + ][ + value: get-ishort value + to date! reduce [ + 65024 and* value / 512 + 1980 + 480 and* value / 32 + 31 and* value + ] + ] + + zip-entry: function [ + {Compresses a file} + return: [block!] + {[local file header + compressed file, central directory entry]} + name [file!] + "Name of file" + date [date!] + "Modification date of file" + data [any-string! binary!] + "Data to compress" + ][ + ; info on data before compression + crc: head reverse crc-32 data + + uncompressed-size: to-ilong length-of data + + either empty? data [ + method: 'store + ][ + ; zlib stream + compressed-data: compress data + ; if compression inefficient, store the data instead + either (length-of data) > (length-of compressed-data) [ + data: copy/part + skip compressed-data 2 + skip tail compressed-data -8 + method: 'deflate + ][ + method: 'store + clear compressed-data + ] + ] + + ; info on data after compression + compressed-size: to-ilong length-of data + + reduce [ + ; local file entry + join-all [ + local-file-sig + #{0000} ; version + #{0000} ; flags + either method = 'store [ + #{0000} ; method = store + ][ + #{0800} ; method = deflate + ] + to-msdos-time date/time + to-msdos-date date/date + crc ; crc-32 + compressed-size + uncompressed-size + to-ishort length-of name ; filename length + #{0000} ; extrafield length + name ; filename + ; no extrafield + data ; compressed data + ] + ; central-dir file entry + join-all [ + central-file-sig + #{0000} ; version source + #{0000} ; version min + #{0000} ; flags + either method = 'store [ + #{0000} ; method = store + ][ + #{0800} ; method = deflate + ] + to-msdos-time date/time + to-msdos-date date/date + crc ; crc-32 + compressed-size + uncompressed-size + to-ishort length-of name ; filename length + #{0000} ; extrafield length + #{0000} ; filecomment length + #{0000} ; disknumber start + #{0000} ; internal attributes + #{00000000} ; external attributes + #{00000000} ; header offset + name ; filename + ; extrafield + ; comment + ] + ] + ] + + any-file?: func [ + "Returns TRUE for file and* url values." value [ any-value!] + ][ + any [file? value url? value] + ] + + to-path-file: func [ + {Converts url! to file! and* removes heading "/"} + value [file! url!] "AnyValue to convert" + ][ + if file? value [ + if #"/" = first value [value: copy next value] + return value + ] + value: decode-url value + join-of %"" [ + value/host "/" + any [value/path ""] + any [value/target ""] + ] + ] + + zip: function [ + {Builds a zip archive from a file or block of files.} + return: [integer!] + {Number of entries in archive.} + where [file! url! binary! string!] + "Where to build it" + source [file! url! block!] + "Files to include in archive" + /deep + "Includes files in subdirectories" + /verbose + "Lists files while compressing" + /only + "Include the root source directory" + ][ + out: func [value] either any-file? where [ + [append where value] + ][ + [where: append where value] + ] + if any-file? where [where: open/write where] + + files-size: nb-entries: 0 + central-directory: copy #{} + + either all [not only | file? source | dir? source][ + root: source source: read source + ][ + root: %./ + ] + + source: compose [(source)] + while [not tail? source][ + name: source/1 + no-modes: any [url? root/:name dir? root/:name] + files: any [ + all [dir? name name: dirize name read root/:name][] + ] + ; is name a not empty directory? + either all [deep not empty? files] [ + ; append content to file list + for-each file read root/:name [ + append source name/:file + ] + ][ + nb-entries: nb-entries + 1 + date: now + + ; is next one data or+ filename? + data: either any [tail? next source any-file? source/2][ + either #"/" = last name [copy #{}][ + if not no-modes [ + date: modified? root/:name + ] + read root/:name + ] + ][ + first source: next source + ] + all [not binary? data data: to binary! data] + name: to-path-file name + if verbose [print name] + ; get compressed file + directory entry + entry: zip-entry name date data + ; write file offset in archive + change skip entry/2 42 to-ilong files-size + ; directory entry + append central-directory entry/2 + ; compressed file + header + out entry/1 + files-size: files-size + length-of entry/1 + ] + ; next arg + source: next source + ] + out join-all [ + central-directory + end-of-central-sig + #{0000} ; disk num + #{0000} ; disk central dir + to-ishort nb-entries ; nb entries disk + to-ishort nb-entries ; nb entries + to-ilong length-of central-directory + to-ilong files-size + #{0000} ; zip file comment length + ; zip file comment + ] + if port? where [close where] + nb-entries + ] + + unzip: function [ + {Decompresses a zip archive with to a directory or a block.} + where [file! url! any-block!] + "Where to decompress it" + source [file! url! any-string! binary!] + "Archive to decompress (only STORE and DEFLATE methods supported)" + /verbose + "Lists files while decompressing (default)" + /quiet + "Don't lists files while decompressing" + ][ + errors: 0 + info: either all [quiet | not verbose] [ + func [value] [] + ][ + func [value][prin join-of "" value] + ] + if any-file? where [where: dirize where] + if all [any-file? where not exists? where][ + make-dir/deep where + ] + if any-file? source [source: read source] + nb-entries: 0 + parse source [ + to local-file-sig + some [ + to local-file-sig 4 skip + (nb-entries: nb-entries + 1) + 2 skip ; version + copy flags: 2 skip + (if not zero? flags/1 and* 1 [return false]) + copy method-number: 2 skip ( + method-number: get-ishort method-number + method: select [0 store 8 deflate] method-number + unless method [method: method-number] + ) + copy time: 2 skip (time: get-msdos-time time) + copy date: 2 skip ( + date: get-msdos-date date + date/time: time + date: date - now/zone + ) + copy crc: 4 skip ( ; crc-32 + crc: get-ilong crc + ) + copy compressed-size: 4 skip + (compressed-size: get-ilong compressed-size) + copy uncompressed-size-raw: 4 skip + (uncompressed-size: get-ilong uncompressed-size-raw) + copy name-length: 2 skip + (name-length: get-ishort name-length) + copy extrafield-length: 2 skip + (extrafield-length: get-ishort extrafield-length) + copy name: name-length skip ( + name: to-file name + info name + ) + extrafield-length skip + data: compressed-size skip + ( + uncompressed-data: catch [ + + ; STORE(0) and DEFLATE(8) are the only widespread + ; methods used for .ZIP compression in the wild today + + if method = 'store [ + throw copy/part data compressed-size + ] + + unless method = 'deflate [ + info ["^- -> failed [method " method "]^/"] + throw blank + ] + + data: copy/part data compressed-size + if error? trap [ + data: decompress/only/limit data uncompressed-size + ][ + info "^- -> failed [deflate]^/" + throw blank + ] + + if uncompressed-size != length-of data [ + info "^- -> failed [wrong output size]^/" + throw blank + ] + + if crc != checksum/method data 'crc32 [ + info "^- -> failed [bad crc32]^/" + print [ + "expected crc:" crc + | "actual crc:" checksum/method data 'crc32 + ] + throw data + ] + + throw data + ] + + either uncompressed-data [ + info unspaced ["^- -> ok [" method "]^/"] + ][ + errors: errors + 1 + ] + + either any-block? where [ + where: insert where name + where: insert where either all [ + #"/" = last name + empty? uncompressed-data + ][blank][uncompressed-data] + ][ + ; make directory and* / or+ write file + either #"/" = last name [ + if not exists? where/:name [ + make-dir/deep where/:name + ] + ][ + set [path file] split-path name + if not exists? where/:path [ + make-dir/deep where/:path + ] + if uncompressed-data [ + write where/:name + uncompressed-data +;not supported in R3 yet :-/ +; set-modes where/:name [ +; modification-date: date +; ] + ] + ] + ] + ) + ] + to end + ] + info ["^/" + "Files/Dirs unarchived: " nb-entries "^/" + "Decompression errors: " errors "^/" + ] + zero? errors + ] +] + +zip: :ctx-zip/zip +unzip: :ctx-zip/unzip diff --git a/src/os/win32/dev-clipboard.c b/src/os/win32/dev-clipboard.c deleted file mode 100644 index 8a4c46c27b..0000000000 --- a/src/os/win32/dev-clipboard.c +++ /dev/null @@ -1,204 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Device: Clipboard access for Win32 -** Author: Carl Sassenrath -** Purpose: -** Provides a very simple interface to the clipboard for text. -** May be expanded in the future for images, etc. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ - -#include - -#include "reb-host.h" -#include "host-lib.h" -#include "sys-net.h" - - -/*********************************************************************** -** -*/ DEVICE_CMD Open_Clipboard(REBREQ *req) -/* -***********************************************************************/ -{ - SET_OPEN(req); - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Close_Clipboard(REBREQ *req) -/* -***********************************************************************/ -{ - SET_CLOSED(req); - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Read_Clipboard(REBREQ *req) -/* -***********************************************************************/ -{ - HANDLE data; - REBUNI *cp; - REBUNI *bin; - REBINT len; - - req->actual = 0; - - // If there is no clipboard data: - if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) { - req->error = 10; - return DR_ERROR; - } - - if (!OpenClipboard(NULL)) { - req->error = 20; - return DR_ERROR; - } - - // Read the UTF-8 data: - if ((data = GetClipboardData(CF_UNICODETEXT)) == NULL) { - CloseClipboard(); - req->error = 30; - return DR_ERROR; - } - - cp = GlobalLock(data); - if (!cp) { - GlobalUnlock(data); - CloseClipboard(); - req->error = 40; - return DR_ERROR; - } - - len = LEN_STR(cp); // wide chars - bin = OS_Make((len+1) * sizeof(REBCHR)); - COPY_STR(bin, cp, len); - - GlobalUnlock(data); - - CloseClipboard(); - - SET_FLAG(req->flags, RRF_WIDE); - req->data = (REBYTE *)bin; - req->actual = len * sizeof(REBCHR); - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Write_Clipboard(REBREQ *req) -/* -** Works for Unicode and ASCII strings. -** Length is number of bytes passed (not number of chars). -** -***********************************************************************/ -{ - HANDLE data; - REBYTE *bin; - REBCNT err; - REBINT len = req->length; // in bytes - - req->actual = 0; - - data = GlobalAlloc(GHND, len + 4); - if (data == NULL) { - req->error = 5; - return DR_ERROR; - } - - // Lock and copy the string: - bin = GlobalLock(data); - if (bin == NULL) { - req->error = 10; - return DR_ERROR; - } - - COPY_MEM(bin, req->data, len); - bin[len] = 0; - GlobalUnlock(data); - - if (!OpenClipboard(NULL)) { - req->error = 20; - return DR_ERROR; - } - - EmptyClipboard(); - - err = !SetClipboardData(GET_FLAG(req->flags, RRF_WIDE) ? CF_UNICODETEXT : CF_TEXT, data); - - CloseClipboard(); - - if (err) { - req->error = 50; - return DR_ERROR; - } - - req->actual = len; - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Poll_Clipboard(REBREQ *req) -/* -***********************************************************************/ -{ - return DR_DONE; -} - - -/*********************************************************************** -** -** Command Dispatch Table (RDC_ enum order) -** -***********************************************************************/ - -static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = -{ - 0, - 0, - Open_Clipboard, - Close_Clipboard, - Read_Clipboard, - Write_Clipboard, - Poll_Clipboard, -}; - -DEFINE_DEV(Dev_Clipboard, "Clipboard", 1, Dev_Cmds, RDC_MAX, 0); diff --git a/src/os/win32/dev-event.c b/src/os/win32/dev-event.c deleted file mode 100644 index 796c1bf053..0000000000 --- a/src/os/win32/dev-event.c +++ /dev/null @@ -1,213 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Device: Event handler for Win32 -** Author: Carl Sassenrath -** Purpose: -** Processes events to pass to REBOL. Note that events are -** used for more than just windowing. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ - -#include -#include "reb-host.h" -#include "host-lib.h" - -void Done_Device(int handle, int error); - -// Move or remove globals? !? -HWND Event_Handle = 0; // Used for async DNS -static int Timer_Id = 0; // The timer we are using - -extern HINSTANCE App_Instance; // From Main module. - - -/*********************************************************************** -** -*/ LRESULT CALLBACK REBOL_Event_Proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) -/* -** The minimal default event handler. -** -***********************************************************************/ -{ - switch(msg) { - case WM_CLOSE: - DestroyWindow(hwnd); - break; - case WM_DESTROY: - PostQuitMessage(0); - break; - default: - // Default processing that we do not care about: - return DefWindowProc(hwnd, msg, wparam, lparam); - } - return 0; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Init_Events(REBREQ *dr) -/* -** Initialize the event device. -** -** Create a hidden window to handle special events, -** such as timers and async DNS. -** -***********************************************************************/ -{ - REBDEV *dev = (REBDEV*)dr; // just to keep compiler happy - WNDCLASSEX wc = {0}; - - // Register event object class: - wc.cbSize = sizeof(wc); - wc.lpszClassName = TEXT("REBOL-Events"); - wc.hInstance = App_Instance; - wc.lpfnWndProc = REBOL_Event_Proc; - if (!RegisterClassEx(&wc)) return DR_ERROR; - - // Create the hidden window: - Event_Handle = CreateWindowEx( - 0, - wc.lpszClassName, - wc.lpszClassName, - 0,0,0,0,0,0, - NULL, App_Instance, NULL - ); - - if (!Event_Handle) return DR_ERROR; - - SET_FLAG(dev->flags, RDF_INIT); - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Poll_Events(REBREQ *req) -/* -** Poll for events and process them. -** Returns 1 if event found, else 0. -** -** MS Notes: -** -** "The PeekMessage function normally does not remove WM_PAINT -** messages from the queue. WM_PAINT messages remain in the queue -** until they are processed." -** -***********************************************************************/ -{ - MSG msg; - int flag = DR_DONE; - - // Are there messages to process? - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - { - flag = DR_PEND; - if (msg.message == WM_TIMER) - break; - if (msg.message == WM_DNS) - Done_Device(msg.wParam, msg.lParam>>16); // error code - else { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - - return flag; // different meaning compared to most commands -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Query_Events(REBREQ *req) -/* -** Wait for an event or a timeout sepecified by req->length. -** This is used by WAIT as the main timing method. -** -***********************************************************************/ -{ - MSG msg; - - // Set timer (we assume this is very fast): - Timer_Id = SetTimer(0, Timer_Id, req->length, 0); - - // Wait for message or the timer: - if (GetMessage(&msg, NULL, 0, 0)) { - //printf("Msg: %d\n", msg.message); - if (msg.message == WM_DNS) - Done_Device(msg.wParam, msg.lParam>>16); // error code - else { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - - // Quickly check for other events: - Poll_Events(0); - - //if (Timer_Id) KillTimer(0, Timer_Id); - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Connect_Events(REBREQ *req) -/* -** Simply keeps the request pending for polling purposes. -** Use Abort_Device to remove it. -** -***********************************************************************/ -{ - return DR_PEND; // keep pending -} - - -/*********************************************************************** -** -** Command Dispatch Table (RDC_ enum order) -** -***********************************************************************/ - -static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = { - Init_Events, // init device driver resources - 0, // RDC_QUIT, // cleanup device driver resources - 0, // RDC_OPEN, // open device unit (port) - 0, // RDC_CLOSE, // close device unit - 0, // RDC_READ, // read from unit - 0, // RDC_WRITE, // write to unit - Poll_Events, - Connect_Events, - Query_Events, -}; - -DEFINE_DEV(Dev_Event, "OS Events", 1, Dev_Cmds, RDC_MAX, 0); diff --git a/src/os/win32/dev-file.c b/src/os/win32/dev-file.c deleted file mode 100644 index fb708635d5..0000000000 --- a/src/os/win32/dev-file.c +++ /dev/null @@ -1,472 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Device: File access for Win32 -** Author: Carl Sassenrath -** Purpose: File open, close, read, write, and other actions. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ - -#include -#include -#include - -#include "reb-host.h" -#include "host-lib.h" - -// MSDN V6 missed this define: -#ifndef INVALID_SET_FILE_POINTER -#define INVALID_SET_FILE_POINTER ((DWORD)-1) -#endif - - -/*********************************************************************** -** -** Local Functions -** -***********************************************************************/ - -static BOOL Seek_File_64(REBREQ *file) -{ - // Performs seek and updates index value. TRUE on scuccess. - // On error, returns FALSE and sets file->error field. - HANDLE h = (HANDLE)file->handle; - DWORD result; - DWORD highint; - - if (file->file.index == -1) { - // Append: - highint = 0; - result = SetFilePointer(h, 0, &highint, FILE_END); - } - else { - // Line below updates indexh if it is affected: - highint = (long)(file->file.index >> 32); - result = SetFilePointer(h, (long)(file->file.index), &highint, FILE_BEGIN); - } - - if (result == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) { - file->error = -RFE_NO_SEEK; - return 0; - } - - file->file.index = ((i64)highint << 32) + result; - - return 1; -} - - -/*********************************************************************** -** -*/ static int Read_Directory(REBREQ *dir, REBREQ *file) -/* -** This function will read a file directory, one file entry -** at a time, then close when no more files are found. -** -** Procedure: -** -** This function is passed directory and file arguments. -** The dir arg provides information about the directory to read. -** The file arg is used to return specific file information. -** -** To begin, this function is called with a dir->handle that -** is set to zero and a dir->file.path string for the directory. -** -** The directory is opened and a handle is stored in the dir -** structure for use on subsequent calls. If an error occurred, -** dir->error is set to the error code and -1 is returned. -** The dir->size field can be set to the number of files in the -** dir, if it is known. The dir->file.index field can be used by this -** function to store information between calls. -** -** If the open succeeded, then information about the first file -** is stored in the file argument and the function returns 0. -** On an error, the dir->error is set, the dir is closed, -** dir->handle is nulled, and -1 is returned. -** -** The caller loops until all files have been obtained. This -** action should be uninterrupted. (The caller should not perform -** additional OS or IO operations between calls.) -** -** When no more files are found, the dir is closed, dir->handle -** is nulled, and 1 is returned. No file info is returned. -** (That is, this function is called one extra time. This helps -** for OSes that may deallocate file strings on dir close.) -** -** Note that the dir->file.path can contain wildcards * and ?. The -** processing of these can be done in the OS (if supported) or -** by a separate filter operation during the read. -** -** Store file date info in file->file.index or other fields? -** Store permissions? Ownership? Groups? Or, require that -** to be part of a separate request? -** -***********************************************************************/ -{ - WIN32_FIND_DATA info; - HANDLE h= (HANDLE)(dir->handle); - REBCHR *cp = 0; - - if (!h) { - - // Read first file entry: - h = FindFirstFile(dir->file.path, &info); - if (h == INVALID_HANDLE_VALUE) { - dir->error = -RFE_OPEN_FAIL; - return DR_ERROR; - } - dir->handle = h; - CLR_FLAG(dir->flags, RRF_DONE); - cp = info.cFileName; - - } - - // Skip over the . and .. dir cases: - while (cp == 0 || (cp[0] == '.' && (cp[1] == 0 || (cp[1] == '.' && cp[2] == 0)))) { - - // Read next file entry, or error: - if (!FindNextFile(h, &info)) { - dir->error = GetLastError(); - FindClose(h); - dir->handle = 0; - if (dir->error != ERROR_NO_MORE_FILES) return DR_ERROR; - dir->error = 0; - SET_FLAG(dir->flags, RRF_DONE); // no more files - return DR_DONE; - } - cp = info.cFileName; - - } - - file->modes = 0; - if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) SET_FLAG(file->modes, RFM_DIR); - COPY_STR(file->file.path, info.cFileName, MAX_FILE_NAME); - file->file.size = ((i64)info.nFileSizeHigh << 32) + info.nFileSizeLow; - - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Open_File(REBREQ *file) -/* -** Open the specified file with the given modes. -** -** Notes: -** 1. The file path is provided in REBOL format, and must be -** converted to local format before it is used. -** 2. REBOL performs the required access security check before -** calling this function. -** 3. REBOL clears necessary fields of file structure before -** calling (e.g. error and size fields). -** -** !! Confirm that /seek /append works properly. -** -***********************************************************************/ -{ - DWORD attrib = FILE_ATTRIBUTE_NORMAL; - DWORD access = 0; - DWORD create = 0; - HANDLE h; - BY_HANDLE_FILE_INFORMATION info; - - // Set the access, creation, and attribute for file creation: - if (GET_FLAG(file->modes, RFM_READ)) { - access |= GENERIC_READ; - create = OPEN_EXISTING; - } - - if (GET_FLAGS(file->modes, RFM_WRITE, RFM_APPEND)) { - access |= GENERIC_WRITE; - if ( - GET_FLAG(file->modes, RFM_NEW) || - !( - GET_FLAG(file->modes, RFM_READ) || - GET_FLAG(file->modes, RFM_APPEND) || - GET_FLAG(file->modes, RFM_SEEK) - ) - ) create = CREATE_ALWAYS; - else create = OPEN_ALWAYS; - } - - attrib |= GET_FLAG(file->modes, RFM_SEEK) ? FILE_FLAG_RANDOM_ACCESS : FILE_FLAG_SEQUENTIAL_SCAN; - - if (GET_FLAG(file->modes, RFM_READONLY)) - attrib |= FILE_ATTRIBUTE_READONLY; - - if (!access) { - file->error = -RFE_NO_MODES; - goto fail; - } - - // Open the file (yes, this is how windows does it, the nutty kids): - h = CreateFile(file->file.path, access, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, create, attrib, 0); - if (h == INVALID_HANDLE_VALUE) { - file->error = -RFE_OPEN_FAIL; - goto fail; - } - - // Confirm that a seek-mode file is actually seekable: - if (GET_FLAG(file->modes, RFM_SEEK)) { - // Below should work because we are seeking to 0: - if (SetFilePointer(h, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { - CloseHandle(h); - file->error = -RFE_BAD_SEEK; - goto fail; - } - } - - // Fetch file size (if fails, then size is assumed zero): - if (GetFileInformationByHandle(h, &info)) { - file->file.size = ((i64)(info.nFileSizeHigh) << 32) + info.nFileSizeLow; - file->file.time.l = info.ftLastWriteTime.dwLowDateTime; - file->file.time.h = info.ftLastWriteTime.dwHighDateTime; - } - - file->handle = (void *)h; - - return DR_DONE; - -fail: - return DR_ERROR; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Close_File(REBREQ *file) -/* -** Closes a previously opened file. -** -***********************************************************************/ -{ - if (file->handle) { - CloseHandle((HANDLE)(file->handle)); - file->handle = 0; - } - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Read_File(REBREQ *file) -/* -***********************************************************************/ -{ - if (GET_FLAG(file->modes, RFM_DIR)) { - return Read_Directory(file, (REBREQ*)file->data); - } - - if (!file->handle) { - file->error = -RFE_NO_HANDLE; - return DR_ERROR; - } - - if (file->modes & ((1 << RFM_SEEK) | (1 << RFM_RESEEK))) { - CLR_FLAG(file->modes, RFM_RESEEK); - if (!Seek_File_64(file)) return DR_ERROR; - } - - if (!ReadFile(file->handle, file->data, file->length, &file->actual, 0)) { - file->error = -RFE_BAD_READ; - return DR_ERROR; - } else { - file->file.index += file->actual; - } - - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Write_File(REBREQ *file) -/* -** Bug?: update file->size value after write !? -** -***********************************************************************/ -{ - DWORD result; - DWORD size_high, size_low; - - if (!file->handle) { - file->error = -RFE_NO_HANDLE; - return DR_ERROR; - } - - if (GET_FLAG(file->modes, RFM_APPEND)) { - CLR_FLAG(file->modes, RFM_APPEND); - SetFilePointer(file->handle, 0, 0, FILE_END); - } - - if (file->modes & ((1 << RFM_SEEK) | (1 << RFM_RESEEK) | (1 << RFM_TRUNCATE))) { - CLR_FLAG(file->modes, RFM_RESEEK); - if (!Seek_File_64(file)) return DR_ERROR; - if (GET_FLAG(file->modes, RFM_TRUNCATE)) - SetEndOfFile(file->handle); - } - - if (file->length != 0) { - if (!WriteFile(file->handle, file->data, file->length, &file->actual, 0)) { - result = GetLastError(); - if (result == ERROR_HANDLE_DISK_FULL) file->error = -RFE_DISK_FULL; - else file->error = -RFE_BAD_WRITE; - return DR_ERROR; - } - } - - size_low = GetFileSize(file->handle, &size_high); - if (size_low == 0xffffffff) { - result = GetLastError(); - file->error = -RFE_BAD_WRITE; - return DR_ERROR; - } - - file->file.size = ((i64)size_high << 32) + (i64)size_low; - - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Query_File(REBREQ *file) -/* -** Obtain information about a file. Return TRUE on success. -** On error, return FALSE and set file->error code. -** -** Note: time is in local format and must be converted -** -***********************************************************************/ -{ - WIN32_FILE_ATTRIBUTE_DATA info; - - if (!GetFileAttributesEx(file->file.path, GetFileExInfoStandard, &info)) { - file->error = GetLastError(); - return DR_ERROR; - } - - if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) SET_FLAG(file->modes, RFM_DIR); - else CLR_FLAG(file->modes, RFM_DIR); - file->file.size = ((i64)info.nFileSizeHigh << 32) + (i64)info.nFileSizeLow; - file->file.time.l = info.ftLastWriteTime.dwLowDateTime; - file->file.time.h = info.ftLastWriteTime.dwHighDateTime; - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Create_File(REBREQ *file) -/* -***********************************************************************/ -{ - if (GET_FLAG(file->modes, RFM_DIR)) { - if (CreateDirectory(file->file.path, 0)) return DR_DONE; - file->error = GetLastError(); - return DR_ERROR; - } else - return Open_File(file); -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Delete_File(REBREQ *file) -/* -** Delete a file or directory. Return TRUE if it was done. -** The file->file.path provides the directory path and name. -** For errors, return FALSE and set file->error to error code. -** -** Note: Dirs must be empty to succeed -** -***********************************************************************/ -{ - if (GET_FLAG(file->modes, RFM_DIR)) { - if (RemoveDirectory(file->file.path)) return DR_DONE; - } else - if (DeleteFile(file->file.path)) return DR_DONE; - - file->error = GetLastError(); - return DR_ERROR; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Rename_File(REBREQ *file) -/* -** Rename a file or directory. -** Note: cannot rename across file volumes. -** -***********************************************************************/ -{ - if (MoveFile((REBCHR*)(file->file.path), (REBCHR*)(file->data))) return DR_DONE; - file->error = GetLastError(); - return DR_ERROR; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Poll_File(REBREQ *file) -/* -***********************************************************************/ -{ - return DR_DONE; // files are synchronous (currently) -} - - -/*********************************************************************** -** -** Command Dispatch Table (RDC_ enum order) -** -***********************************************************************/ - -static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = { - 0, - 0, - Open_File, - Close_File, - Read_File, - Write_File, - Poll_File, - 0, // connect - Query_File, - 0, // modify - Create_File, - Delete_File, - Rename_File, -}; - -DEFINE_DEV(Dev_File, "File IO", 1, Dev_Cmds, RDC_MAX, sizeof(REBREQ)); diff --git a/src/os/win32/dev-stdio.c b/src/os/win32/dev-stdio.c deleted file mode 100644 index 2782794403..0000000000 --- a/src/os/win32/dev-stdio.c +++ /dev/null @@ -1,394 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Device: Standard I/O for Win32 -** Author: Carl Sassenrath -** Purpose: -** Provides basic I/O streams support for redirection and -** opening a console window if necessary. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ - -#include -#include -#include - -#include -#include - -#include "reb-host.h" -#include "host-lib.h" - -#define BUF_SIZE (16*1024) // MS restrictions apply - -#define SF_DEV_NULL 31 // local flag to mark NULL device - -#define CONSOLE_MODES ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT \ - | 0x0040 | 0x0020 // quick edit and insert mode (not defined in VC6) - -static HANDLE Std_Out = 0; -static HANDLE Std_Inp = 0; -static HANDLE Std_Echo = 0; -static REBCHR *Std_Buf = 0; // for input and output - -static BOOL Redir_Out = 0; -static BOOL Redir_Inp = 0; - -// Special access: -extern REBDEV *Devices[]; - - -//********************************************************************** - -BOOL WINAPI Handle_Break(DWORD dwCtrlType) -{ - // Handle the MS CMD console CTRL-C, BREAK, and other events: - if (dwCtrlType >= CTRL_CLOSE_EVENT) OS_Exit(100); // close button, shutdown, etc. - RL_Escape(0); - return TRUE; // We handled it -} - -#ifdef DEBUG_METHOD -// Because this file deals with stdio, we must avoid using stdio for debug. -// This funtion is of use wne needed. -static dbgout(char *fmt, int d, char *s) -{ - char buf[255]; - FILE *f = fopen("dbgout.txt", "w"); - sprintf(buf, fmt, d, s); - fwrite(buf, strlen(buf), 1, f); - fclose(f); -} -// example: dbgout("handle: %x %s\n", hdl, name); -#endif - -#ifdef NOT_USED -static void attach_console(void) { - void *h = LoadLibraryW(TEXT("kernel32.dll")); - (BOOL (_stdcall *)(DWORD))GetProcAddress(h, "AttachConsole")(-1); - FreeLibrary(h); -} -#endif - -static void close_stdio(void) -{ - if (Std_Buf) { - OS_Free(Std_Buf); - Std_Buf = 0; - //FreeConsole(); // problem: causes a delay - } - if (Std_Echo) { - CloseHandle(Std_Echo); - Std_Echo = 0; - } -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Quit_IO(REBREQ *dr) -/* -***********************************************************************/ -{ - REBDEV *dev = (REBDEV*)dr; // just to keep compiler happy above - - close_stdio(); - //if (GET_FLAG(dev->flags, RDF_OPEN)) FreeConsole(); - CLR_FLAG(dev->flags, RDF_OPEN); - return DR_DONE; -} - -/*********************************************************************** -** -*/ DEVICE_CMD Open_IO(REBREQ *req) -/* -***********************************************************************/ -{ - REBDEV *dev; - REBCHR *title = TEXT("REBOL 3 Alpha"); - HANDLE win; - - dev = Devices[req->device]; - - // Avoid opening the console twice (compare dev and req flags): - if (GET_FLAG(dev->flags, RDF_OPEN)) { - // Device was opened earlier as null, so req must have that flag: - if (GET_FLAG(dev->flags, SF_DEV_NULL)) - SET_FLAG(req->modes, RDM_NULL); - SET_FLAG(req->flags, RRF_OPEN); - return DR_DONE; // Do not do it again - } - - if (!GET_FLAG(req->modes, RDM_NULL)) { - - // Get the raw stdio handles: - Std_Out = GetStdHandle(STD_OUTPUT_HANDLE); - Std_Inp = GetStdHandle(STD_INPUT_HANDLE); - //Std_Err = GetStdHandle(STD_ERROR_HANDLE); - Std_Echo = 0; - - Redir_Out = (GetFileType(Std_Out) != 0); - Redir_Inp = (GetFileType(Std_Inp) != 0); - - // attach_console(); // merges streams, not good - - // If output not redirected, open a console: - if (!Redir_Out) { - if (!AllocConsole()) { - req->error = GetLastError(); - return DR_ERROR; - } - - SetConsoleTitle(title); - - // The goof-balls at MS seem to require this: - // See: http://support.microsoft.com/kb/124103 - Sleep(40); - win = FindWindow(NULL, title); // What if more than one open ?! - if (win) { - SetForegroundWindow(win); - BringWindowToTop(win); - } - - // Get the new stdio handles: - Std_Out = GetStdHandle(STD_OUTPUT_HANDLE); - - if (!Redir_Inp) { - Std_Inp = GetStdHandle(STD_INPUT_HANDLE); - // Make the Win32 console a bit smarter by default: - SetConsoleMode(Std_Inp, CONSOLE_MODES); - } - } - - Std_Buf = OS_Make(BUF_SIZE * sizeof(REBCHR)); - - // Handle stdio CTRL-C interrupt: - SetConsoleCtrlHandler(Handle_Break, TRUE); - } - else - SET_FLAG(dev->flags, SF_DEV_NULL); - - SET_FLAG(req->flags, RRF_OPEN); - SET_FLAG(dev->flags, RDF_OPEN); - - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Close_IO(REBREQ *req) -/* - ***********************************************************************/ -{ - REBDEV *dev = Devices[req->device]; - - close_stdio(); - - CLR_FLAG(req->flags, RRF_OPEN); - - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Write_IO(REBREQ *req) -/* -** Low level "raw" standard output function. -** -** Allowed to restrict the write to a max OS buffer size. -** -** Returns the number of chars written. -** -***********************************************************************/ -{ - long len; - long total = 0; - BOOL ok = FALSE; - - if (GET_FLAG(req->modes, RDM_NULL)) { - req->actual = req->length; - return DR_DONE; - } - - if (Std_Out) { - - if (Redir_Out) { // Always UTF-8 - ok = WriteFile(Std_Out, req->data, req->length, &total, 0); - } - else { - // Convert UTF-8 buffer to Win32 wide-char format for console. - // Thankfully, MS provides something other than mbstowcs(); - // however, if our buffer overflows, it's an error. There's no - // efficient way at this level to split-up the input data, - // because its UTF-8 with variable char sizes. - len = MultiByteToWideChar(CP_UTF8, 0, req->data, req->length, Std_Buf, BUF_SIZE); - if (len > 0) // no error - ok = WriteConsoleW(Std_Out, Std_Buf, len, &total, 0); - } - - if (!ok) { - req->error = GetLastError(); - return DR_ERROR; - } - - req->actual = req->length; // do not use "total" (can be byte or wide) - - //if (GET_FLAG(req->flags, RRF_FLUSH)) { - // FLUSH(); - //} - } - - if (Std_Echo) { // always UTF-8 - WriteFile(Std_Echo, req->data, req->length, &total, 0); - //FlushFileBuffers(Std_Echo); - } - - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Read_IO(REBREQ *req) -/* -** Low level "raw" standard input function. -** -** The request buffer must be long enough to hold result. -** -** Result is NOT terminated (the actual field has length.) -** -***********************************************************************/ -{ - long total = 0; - int len; - BOOL ok; - - if (GET_FLAG(req->modes, RDM_NULL)) { - req->data[0] = 0; - return DR_DONE; - } - - req->actual = 0; - - if (Std_Inp) { - - if (Redir_Inp) { // always UTF-8 - len = MIN(req->length, BUF_SIZE); - ok = ReadFile(Std_Inp, req->data, len, &total, 0); - } - else { - ok = ReadConsoleW(Std_Inp, Std_Buf, BUF_SIZE-1, &total, 0); - if (ok) { - total = WideCharToMultiByte(CP_UTF8, 0, Std_Buf, total, req->data, req->length, 0, 0); - if (!total) ok = FALSE; - } - } - - if (!ok) { - req->error = GetLastError(); - return DR_ERROR; - } - - req->actual = total; - } - - return DR_DONE; -} - - -/*********************************************************************** -** -*/ DEVICE_CMD Open_Echo(REBREQ *req) -/* -** Open a file for low-level console echo (output). -** -***********************************************************************/ -{ - if (Std_Echo) { - CloseHandle(Std_Echo); - Std_Echo = 0; - } - - if (req->file.path) { - Std_Echo = CreateFile(req->file.path, GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0); - if (Std_Echo == INVALID_HANDLE_VALUE) { - Std_Echo = 0; - req->error = GetLastError(); - return DR_ERROR; - } - } - - return DR_DONE; -} - - -/*********************************************************************** -** -** Command Dispatch Table (RDC_ enum order) -** -***********************************************************************/ - -static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = -{ - 0, // init - Quit_IO, - Open_IO, - Close_IO, - Read_IO, - Write_IO, - 0, // poll - 0, // connect - 0, // query - 0, // modify - Open_Echo, // CREATE used for opening echo file -}; - -DEFINE_DEV(Dev_StdIO, "Standard IO", 1, Dev_Cmds, RDC_MAX, 0); - - - -//*** Old fragments *************************************************** - -#if OLD_CONSOLE_FILE_IO - int cfh; // C file handle - FILE *file; - - cfh = _open_osfhandle((long)Std_Out, _O_TEXT); - file = _fdopen(cfh, "w"); - *stdout = *file; - setvbuf(stdout, NULL, _IONBF, 0); - - cfh = _open_osfhandle((long)Std_Inp, _O_TEXT); - file = _fdopen(cfh, "r"); - *stdin = *file; -#endif diff --git a/src/os/win32/host-draw.c b/src/os/win32/host-draw.c deleted file mode 100644 index 02b1ce191f..0000000000 --- a/src/os/win32/host-draw.c +++ /dev/null @@ -1,693 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Draw Dialect Backend -** Author: Cyphre, Carl -** Purpose: Evaluates DRAW commands; calls graphics functions. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ - -#include -#include -#include - -#include "reb-host.h" -#include "host-lib.h" -#include "reb-types.h" -#include "reb-value.h" -#include "reb-dialect.h" -#include "words-draw.h" // Auto-generated by gen-draw-words.r -#include "rebol-lib.h" - -#define ENABLE_DRAW -#include "agg-draw.h" - -//#define AGGC ((agg_graphics*)context) - -#define ARG_LOGIC(n) VAL_LOGIC(arg+n) -#define ARG_INTEGER(n) VAL_INT32(arg+n) -#define ARG_STRING(n) VAL_STRING(arg+n) -#define ARG_PAIR(n) VAL_PAIR(arg+n) -#define ARG_DECIMAL(n) VAL_DECIMAL(arg+n) -#define ARG_TUPLE(n) VAL_TUPLE(arg+n) -#define ARG_WORD(n) VAL_WORD(arg+n) - -#define ARG_OBJECT(n) VAL_SERIES(arg+n) // temp -#define ARG_IMAGE(n) VAL_SERIES(arg+n) // temp -#define ARG_BLOCK(n) VAL_SERIES(arg+n) - -#define ARG_WORDS(n,s,e) ((ARG_WORD(n)>=s && ARG_WORD(n)<=e) ? ARG_WORD(n)-s : 0) - -enum SHAPE_Lit_Words { - DW_ARC_LIT = DW_ARC + 0x1000, - DW_CURV_LIT = DW_CURV + 0x1000, - DW_CURVE_LIT = DW_CURVE + 0x1000, - DW_HLINE_LIT = DW_HLINE + 0x1000, - DW_LINE_LIT = DW_LINE + 0x1000, - DW_MOVE_LIT = DW_MOVE + 0x1000, - DW_QCURV_LIT = DW_QCURV + 0x1000, - DW_QCURVE_LIT = DW_QCURVE + 0x1000, - DW_VLINE_LIT = DW_VLINE + 0x1000 -}; - -/*********************************************************************** -** -*/ REBINT Draw_Shape(void *graphics, REBSER *block, REBSER *args) -/* -***********************************************************************/ -{ - REBCNT index = 0; - REBINT cmd; - //REBSER *args = 0; // cannot be done here a GC problem - REBVAL *arg; - REBCNT nargs; - REBINT rel; - do { - cmd = Reb_Dialect(DIALECTS_DRAW, block, &index, &args); - - if (cmd == 0) return 0; - if (cmd < 0) { -// Reb_Print("ERROR: %d, Index %d", -cmd, index); - return -((REBINT)index+1); - } -// else -// Reb_Print("SHAPE: Cmd %d, Index %d, Args %m", cmd, index, args); - - arg = BLK_HEAD(args); - nargs = SERIES_TAIL(args); -// Reb_Print("Number of args: %d", nargs); - - rel = 0; - - switch (cmd) { - case DW_ARC_LIT: - rel = 1; - case DW_ARC: - { - REBDEC r; - REBPAR* p = &ARG_PAIR(0); - REBINT sweep = 4; - REBINT large = 6; - if (ARG_WORD(4) == DW_LARGE){ - sweep = 6; - large = 4; - } - if (IS_NONE(arg+2)) - r = (fabs(p->x) + fabs(p->y)) / 2; - else - r = ARG_DECIMAL(2); - - agg_path_arc(graphics, rel, p, r, (IS_NONE(arg+3)) ? r : ARG_DECIMAL(3), (IS_NONE(arg+5)) ? 0 : ARG_DECIMAL(5), DW_SWEEP == ARG_WORD(sweep), DW_LARGE == ARG_WORD(large)); - break; - } - case DW_CLOSE: - agg_path_close(graphics); - break; - case DW_CURV_LIT: - rel = 1; - case DW_CURV: - { - REBCNT n = 0; - for (n = 0; n < nargs; n+=2) { - if (IS_PAIR(arg+n) && IS_PAIR(arg+n+1)) - agg_path_curv(graphics, rel, &ARG_PAIR(n), &ARG_PAIR(n+1)); - } - break; - } - case DW_CURVE_LIT: - rel = 1; - case DW_CURVE: - { - REBCNT n = 0; - for (n = 0; n < nargs; n+=3) { - if (IS_PAIR(arg+n) && IS_PAIR(arg+n+1) && IS_PAIR(arg+n+2)) - agg_path_curve(graphics, rel, &ARG_PAIR(n), &ARG_PAIR(n+1), &ARG_PAIR(n+2)); - } - break; - } - break; - case DW_HLINE_LIT: - rel = 1; - case DW_HLINE: - if (IS_INTEGER(arg)) - agg_path_hline(graphics, rel, VAL_INT32(arg)); - else if (IS_DECIMAL(arg)) - agg_path_hline(graphics, rel, VAL_DECIMAL(arg)); - break; - case DW_LINE_LIT: - rel = 1; - case DW_LINE: - { - REBCNT n = 0; - for (n = 0; n < nargs; n++) { - agg_path_line(graphics, rel, &ARG_PAIR(n)); - } - break; - } - case DW_MOVE_LIT: - rel = 1; - case DW_MOVE: - { - REBCNT n = 0; - for (n = 0; n < nargs; n++) { - if(n) agg_path_line(graphics, rel, &ARG_PAIR(n)); - agg_path_move(graphics, rel, &ARG_PAIR(n)); - } - break; - } - case DW_QCURV_LIT: - rel = 1; - case DW_QCURV: - if (IS_PAIR(arg)) - agg_path_qcurv(graphics, rel, &ARG_PAIR(0)); - break; - case DW_QCURVE_LIT: - rel = 1; - case DW_QCURVE: - { - REBCNT n = 0; - for (n = 0; n < nargs; n+=2) { - if (IS_PAIR(arg+n) && IS_PAIR(arg+n+1)) - agg_path_qcurve(graphics, rel, &ARG_PAIR(n), &ARG_PAIR(n+1)); - } - break; - } - case DW_VLINE_LIT: - rel = 1; - case DW_VLINE: - if (IS_INTEGER(arg)) - agg_path_vline(graphics, rel, VAL_INT32(arg)); - else if (IS_DECIMAL(arg)) - agg_path_vline(graphics, rel, VAL_DECIMAL(arg)); - break; - } - - } while (TRUE); -} - -/*********************************************************************** -** -*/ REBINT Draw_Gob(void *graphics, REBSER *block, REBSER *args) -/* -** Handles all commands for the DRAW dialect as specified -** in the system/dialects/draw object. -** -** This function calls the REBOL_Dialect interpreter to -** parse the dialect and build and return the command number -** (the index offset in the draw object above) and a block -** of arguments. (For now, just a REBOL block, but this could -** be changed to isolate it from changes in REBOL's internals). -** -** Each arg will be of the specified datatype (given in the -** dialect) or NONE when no argument of that type was given -** and this code must determine the proper default value. -** -** If the cmd result is zero, then it is either the end of -** the block, or an error has occurred. If the error value -** is non-zero, then it was an error. -** -***********************************************************************/ -{ -// REBSER *block; - REBCNT index = 0; - REBINT cmd; -// REBSER *args = 0; // cannot be done here a GC problem - REBVAL *arg; - REBCNT nargs; - - // default values - REBPAR zero_pair = {0,0}; - REBPAR size_pair; - REBPAR center_pair; - - agg_get_size(graphics, &size_pair); - center_pair.x = size_pair.x / 2; - center_pair.y = size_pair.y / 2; - - do { - cmd = Reb_Dialect(DIALECTS_DRAW, block, &index, &args); - - if (cmd == 0) return 0; - if (cmd < 0) { -// Reb_Print("ERROR: %d, Index %d", -cmd, index); - return -((REBINT)index+1); - } -// else -// Reb_Print("DRAW: Cmd %d, Index %d, Args %m", cmd, index, args); - - arg = BLK_HEAD(args); - nargs = SERIES_TAIL(args); -// Reb_Print("Number of args: %d", nargs); - -#ifdef ENABLE_DRAW - switch (cmd) { - - case DW_TYPE_SPEC: - if (IS_BLOCK(arg)) { - REBINT result = Draw_Gob(graphics,ARG_BLOCK(0), args); - if (result < 0) return result; - } - break; - - // anti-alias: [logic!] - case DW_ANTI_ALIAS: - agg_anti_alias(graphics, IS_LOGIC(arg) && ARG_LOGIC(0)); - break; - - // arc: [pair! pair! decimal! decimal! word! decimal! word!] - case DW_ARC: - agg_arc(graphics, IS_NONE(arg) ? &zero_pair: &ARG_PAIR(0), IS_NONE(arg+1) ? &size_pair : &ARG_PAIR(1), IS_NONE(arg+2) ? 0 : ARG_DECIMAL(2), IS_NONE(arg+3) ? 90 : ARG_DECIMAL(3), DW_CLOSED == ARG_WORD(4)); - break; - - // arrow: [tuple! pair!] - case DW_ARROW: - agg_arrow(graphics, IS_TUPLE(arg) ? ARG_TUPLE(0) : NULL, &ARG_PAIR(1)); - break; - - // box: [pair! pair! decimal!] - case DW_BOX: - agg_box(graphics, IS_NONE(arg) ? &zero_pair: &ARG_PAIR(0), IS_NONE(arg+1) ? &size_pair : &ARG_PAIR(1), ARG_DECIMAL(2)); - break; - - // circle: [pair! decimal! decimal!] - case DW_CIRCLE: - { - REBDEC r = IS_NONE(arg+1) ? min(center_pair.x,center_pair.y): ARG_DECIMAL(1); - agg_circle(graphics, IS_NONE(arg) ? ¢er_pair: &ARG_PAIR(0), r, (IS_NONE(arg+2)) ? r : ARG_DECIMAL(2)); - } - break; - - // clip: [pair! pair! logic!] - case DW_CLIP: - if (!IS_PAIR(arg) || !IS_PAIR(arg+1)) - agg_clip(graphics, &zero_pair, &size_pair); - else - agg_clip(graphics, &ARG_PAIR(0), &ARG_PAIR(1)); - break; - - // curve: [* pair!] ;shared with SHAPE command - case DW_CURVE: - switch (nargs){ - case 3: - agg_curve3(graphics, &ARG_PAIR(0), &ARG_PAIR(1), &ARG_PAIR(2)); - break; - case 4: - agg_curve4(graphics, &ARG_PAIR(0), &ARG_PAIR(1), &ARG_PAIR(2), &ARG_PAIR(3)); - break; - default: - { - REBPAR tr = {size_pair.x, 0}; - REBPAR bl = {0, size_pair.y}; - agg_curve4(graphics, &zero_pair, &bl, &tr, &size_pair); - } - } - break; - - // effect: [pair! pair! block!] - case DW_EFFECT: - if (IS_BLOCK(arg+2)) - agg_effect(graphics, IS_NONE(arg) ? &zero_pair : &ARG_PAIR(0), IS_NONE(arg+1) ? &size_pair : &ARG_PAIR(1), ARG_BLOCK(2)); - break; - - // ellipse: [pair! pair!] - case DW_ELLIPSE: - agg_ellipse(graphics, IS_NONE(arg) ? &zero_pair : &ARG_PAIR(0), IS_NONE(arg+1) ? &size_pair : &ARG_PAIR(1)); - break; - - // fill-pen: [tuple! image! logic!] - case DW_FILL_PEN: - if (IS_TUPLE(arg)) { - //plain fill - agg_fill_pen(graphics, ARG_TUPLE(0)); - break; - } - if (IS_IMAGE(arg+1)) { - //image fill - agg_fill_image_pen(graphics, ARG_IMAGE(1)); - break; - } - agg_fill_pen(graphics, NULL); - break; - - // fill-rule: [word!] - case DW_FILL_RULE: - agg_fill_rule(graphics, ARG_WORDS(0,DW_NON_ZERO,DW_EVEN_ODD)); - break; - - // gamma: [decimal!] - case DW_GAMMA: - agg_gamma(graphics, ARG_DECIMAL(0)); - break; - - // grad-pen: [word! word! pair! logic! decimal! decimal! decimal! decimal! decimal! block!] - case DW_GRAD_PEN: - //[word! word! pair! logic! block! * decimal! * tuple!] - if (IS_NONE(arg) && IS_NONE(arg+1) && IS_NONE(arg+2) && IS_NONE(arg+4) && IS_NONE(arg+5) && IS_NONE(arg+6) && IS_NONE(arg+7) && IS_NONE(arg+8) && IS_NONE(arg+9)) { - //turn off the grad pen - agg_reset_gradient_pen(graphics); - } else { - unsigned char colors[256*4+1] = {2, 0,0,0,0, 0,0,0,0, 255,255,255,0}; //max number of color tuples is 256 + one length information char - REBDEC offsets[256] = {0.0 , 0.0, 1.0}; - - if (IS_BLOCK(arg+9)) { - //gradient fill - REBCNT i,j,k; - - REBSER *blk = ARG_BLOCK(9); - REBVAL *slot = BLK_HEAD(blk); - REBCNT len = SERIES_TAIL(blk); - - for (i = 0, j = 1, k = 5;i 1; nargs--, n++) { - agg_line(graphics, &ARG_PAIR(n), &ARG_PAIR(n+1)); - } - } else { - agg_line(graphics, &zero_pair, &size_pair); - } - break; - - // line-cap: [word!] - case DW_LINE_CAP: - agg_line_cap(graphics, ARG_WORDS(0,DW_BUTT,DW_ROUNDED)); - break; - - // line-join: [word!] - case DW_LINE_JOIN: - agg_line_join(graphics, ARG_WORDS(0,DW_MITER,DW_BEVEL)); - break; - - // line-pattern: [logic! tuple! * decimal!] - case DW_LINE_PATTERN: - if (nargs > 3){ - REBCNT n = 0; - REBDEC *patterns = (REBDEC*) MAKE_MEM((nargs-1) * sizeof(REBDEC)) ; - patterns[0] = (REBDEC)nargs-2; - for (n = 2; n < nargs; n++) { - if (IS_DECIMAL(arg+n)) - patterns[n-1] = ARG_DECIMAL(n); - else - break; - } - - agg_line_pattern(graphics, IS_TUPLE(arg+1) ? ARG_TUPLE(1) : NULL, patterns); - } else { - agg_line_pattern(graphics, 0, 0); - } - break; - - // line-width: [decimal! word!] - case DW_LINE_WIDTH: - agg_line_width(graphics, ARG_DECIMAL(0), ARG_WORD(1) == DW_FIXED); - break; - - // matrix: [block!] - case DW_MATRIX: - agg_matrix(graphics, ARG_BLOCK(0)); - break; - - // pen: [tuple! image! logic!] - case DW_PEN: - agg_pen( - graphics, - IS_TUPLE(arg) ? ARG_TUPLE(0) : NULL, - IS_IMAGE(arg+1) ? ARG_IMAGE(1) : NULL - ); - break; - - // polygon: [* pair!] - case DW_POLYGON: - if (nargs){ - REBINT n = 0; - if (nargs > 1){ - while (nargs--){ - if (n){ - agg_add_vertex(graphics, ARG_PAIR(n).x, ARG_PAIR(n).y); - } else { - agg_begin_poly(graphics, ARG_PAIR(n).x, ARG_PAIR(n).y); - } - n++; - } - agg_end_poly(graphics); - } - } else { - agg_begin_poly(graphics, 0, 0); - agg_add_vertex(graphics, size_pair.x, 0); - agg_add_vertex(graphics, size_pair.x, size_pair.y); - agg_add_vertex(graphics, 0, size_pair.y); - agg_end_poly(graphics); - } - break; - - // push: [block!] - case DW_PUSH: - agg_push_matrix(graphics); - if (IS_BLOCK(arg)) { - REBINT result = Draw_Gob(graphics,ARG_BLOCK(0), args); - if (result < 0) return result; - } - agg_pop_matrix(graphics); - break; - - // reset-matrix: [] - case DW_RESET_MATRIX: - agg_reset_matrix(graphics); - break; - - // rotate: [decimal!] - case DW_ROTATE: - agg_rotate(graphics, ARG_DECIMAL(0)); - break; - - // scale: [decimal! decimal!] - case DW_SCALE: - agg_scale(graphics, ARG_DECIMAL(0), ARG_DECIMAL(1)); - break; - - // shape: [block!] - case DW_SHAPE: - if (IS_BLOCK(arg)) { - REBINT result; - agg_path_open(graphics); - result = Draw_Shape(graphics, ARG_BLOCK(0), args); - if (result < 0) return result; - agg_path_close(graphics); - } - //agg_shape(graphics, ARG_BLOCK(0)); - break; - - // skew: [decimal!] - case DW_SKEW: - agg_skew(graphics, ARG_DECIMAL(0), ARG_DECIMAL(1)); - break; - - // spline: [integer! word! * pair!] - case DW_SPLINE: - if (nargs > 3){ - REBINT n = 2; - nargs-=2; //skip first two args - while (nargs--){ - if (n == 2){ - agg_begin_poly(graphics, ARG_PAIR(n).x, ARG_PAIR(n).y); - } else { - agg_add_vertex(graphics, ARG_PAIR(n).x, ARG_PAIR(n).y); - } - n++; - } - agg_end_spline(graphics, ARG_INTEGER(0), DW_CLOSED == ARG_WORD(1)); - } else { - agg_begin_poly(graphics, 0, center_pair.y); - agg_add_vertex(graphics, center_pair.x / 2, size_pair.y); - agg_add_vertex(graphics, size_pair.x - (size_pair.x / 3), 0); - agg_add_vertex(graphics, size_pair.x, center_pair.y); - agg_end_spline(graphics, 16,0); - } - break; - - // text: [word! pair! pair! block!] - case DW_TEXT: - if (IS_BLOCK(arg+3)) { - REBINT result = agg_text(graphics, DW_VECTORIAL == ARG_WORD(0), &ARG_PAIR(1), IS_NONE(arg+2) ? NULL : &ARG_PAIR(2), ARG_BLOCK(3)); - if (result < 0) return result; - } - break; - - // transform: [decimal! pair! decimal! decimal! pair!] - case DW_TRANSFORM: - agg_transform(graphics, ARG_DECIMAL(0), &ARG_PAIR(1), ARG_DECIMAL(2), ARG_DECIMAL(3), &ARG_PAIR(4)); - break; - - // translate: [pair!] - case DW_TRANSLATE: - agg_translate(graphics, &ARG_PAIR(0)); - break; - - // triangle: [pair! pair! pair! tuple! tuple! tuple! decimal!] - case DW_TRIANGLE: - { - REBPAR p1 = (IS_NONE(arg)) ? zero_pair : ARG_PAIR(0); - REBPAR p2 = (IS_NONE(arg+1)) ? size_pair : ARG_PAIR(1); - REBYTE b[4] = {0,0,0,0}; - REBYTE* pb = b; - if (IS_NONE(arg+2)) { - ARG_PAIR(2).x = p1.x; - ARG_PAIR(2).y = p2.y; - } - if (IS_NONE(arg+3)) pb = 0; - agg_triangle(graphics, &p1, &p2, &ARG_PAIR(2), !pb ? pb : ARG_TUPLE(3), IS_NONE(arg+4) ? pb : ARG_TUPLE(4), IS_NONE(arg+5) ? pb : ARG_TUPLE(5), ARG_DECIMAL(6)); - break; - } - } - -#endif // ENABLE_DRAW - - } while (TRUE); -} diff --git a/src/os/win32/host-effect.c b/src/os/win32/host-effect.c deleted file mode 100644 index 877cab1d13..0000000000 --- a/src/os/win32/host-effect.c +++ /dev/null @@ -1,298 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Effect Dialect Backend -** Author: Cyphre, Carl -** Purpose: Evaluates EFFECT commands; calls graphics functions. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ - -#include - -#include "reb-host.h" -///#include "reb-series.h" -///#include "reb-gob.h" -#include "host-lib.h" -#include "reb-types.h" -#include "reb-value.h" -#include "reb-dialect.h" -#include "words-effect.h" // Auto-generated by gen-effect-words.r -#include "rebol-lib.h" - -#include "agg-effects.h" - -//#define AGGC ((agg_graphics*)context) - -#define ARG_LOGIC(n) VAL_LOGIC(arg+n) -#define ARG_INTEGER(n) VAL_INT32(arg+n) -#define ARG_STRING(n) VAL_STRING(arg+n) -#define ARG_PAIR(n) VAL_PAIR(arg+n) -#define ARG_DECIMAL(n) VAL_DECIMAL(arg+n) -#define ARG_TUPLE(n) VAL_TUPLE(arg+n) -#define ARG_WORD(n) VAL_WORD(arg+n) - -#define ARG_OBJECT(n) VAL_SERIES(arg+n) // temp -#define ARG_IMAGE(n) VAL_SERIES(arg+n) // temp -#define ARG_BLOCK(n) VAL_SERIES(arg+n) - -#define ARG_OPT_IMAGE(n) (IS_IMAGE(arg+n)? ARG_IMAGE(n) : NULL) - -#define ARG_WORDS(n,s,e) ((ARG_WORD(n)>=s && ARG_WORD(n)<=e) ? ARG_WORD(n)-s : 0) - - - -/*********************************************************************** -** -*/ REBINT Effect_Gob(void *effects, REBSER *block) -/* -** Handles all commands for the EFFECT dialect as specified -** in the system/dialects/effect object. -** -** This function calls the REBOL_Dialect interpreter to -** parse the dialect and build and return the command number -** (the index offset in the draw object above) and a block -** of arguments. (For now, just a REBOL block, but this could -** be changed to isolate it from changes in REBOL's internals). -** -** Each arg will be of the specified datatype (given in the -** dialect) or NONE when no argument of that type was given -** and this code must determine the proper default value. -** -** If the cmd result is zero, then it is either the end of -** the block, or an error has occurred. If the error value -** is non-zero, then it was an error. -** -***********************************************************************/ -{ -// REBSER *block; - REBCNT index = 0; - REBINT cmd; - REBSER *args = 0; - REBVAL *arg; - REBCNT nargs; - - //default values - REBYTE def_color1[4] = {0,0,0,0}; - REBYTE def_color2[4] = {255,255,255,0}; - REBPAR def_pair = {1,0}; - - do { - cmd = Reb_Dialect(DIALECTS_EFFECT, block, &index, &args); - - if (cmd == 0) return 0; - if (cmd < 0) { -// Reb_Print("ERROR: %d, Index %d", -cmd, index); - return -((REBINT)index+1); - } -// else -// Reb_Print("EFFECT: Cmd %d, Index %d, Args %m", cmd, index, args); - - arg = BLK_HEAD(args); - nargs = SERIES_TAIL(args); -// Reb_Print("Number of args: %d", nargs); - - switch (cmd) { - - case EW_ADD: - FX_Add(effects, ARG_OPT_IMAGE(0), ARG_OPT_IMAGE(1)); - break; - case EW_ALPHAMUL: - if (IS_IMAGE(arg)) - FX_Alphamul(effects, ARG_IMAGE(0), IS_NONE(arg+1) ? 127 : ARG_INTEGER(1)); - break; - case EW_ASPECT: - { - REBINT type = 1, mode = 2; - - if (ARG_WORD(0) == EW_RESAMPLE){ - type = 2; - mode = 1; - } - - FX_Fit(effects,ARG_OPT_IMAGE(0), ARG_WORDS(type,EW_NEAREST,EW_GAUSSIAN), ARG_WORD(mode) == EW_RESAMPLE, IS_NONE(arg+3) ? 1.0 : ARG_DECIMAL(3), TRUE); - } - break; - case EW_BLUR: -// FX_Blur(effects, ARG_OPT_IMAGE(0)); - { - REBDEC filter[9] = {0, 1, 0, 1, 1, 1, 0, 1, 0}; - FX_Convolve(effects, ARG_OPT_IMAGE(0),filter , 5.0, 0, FALSE); - } - break; - case EW_COLORIFY: - FX_Colorify(effects, IS_NONE(arg+1) ? def_color2 : ARG_TUPLE(1) , IS_NONE(arg+2) ? 255 : max(0, min(255,ARG_INTEGER(2))), ARG_OPT_IMAGE(0)); - break; - case EW_COLORIZE: - FX_Colorize(effects, IS_NONE(arg+1) ? def_color2 : ARG_TUPLE(1) , ARG_OPT_IMAGE(0)); - break; - case EW_CONTRAST: - FX_Contrast(effects, IS_NONE(arg+1) ? 127 : ARG_INTEGER(1), ARG_OPT_IMAGE(0)); - break; - case EW_CONVOLVE: - //[image! block! decimal! decimal! logic!] - if (IS_BLOCK(arg+1)) { - REBDEC filter[9]; - REBSER* mtx = (REBSER*)ARG_BLOCK(1); - REBVAL* slot = BLK_HEAD(mtx); - REBCNT len = SERIES_TAIL(mtx) ,i, num = 0; - - for (i = 0;i -#include // For WM_MOUSELEAVE event -#include - -//-- Not currently used: -//#include -//#include -//#include - -#ifndef GET_WHEEL_DELTA_WPARAM -#define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD (wparam)) -#endif - -#include "reb-host.h" -#include "host-lib.h" - -//***** Constants ***** - -// Virtual key conversion table, sorted by first column. -const REBCNT Key_To_Event[] = { - VK_PRIOR, EVK_PAGE_UP, - VK_NEXT, EVK_PAGE_DOWN, - VK_END, EVK_END, - VK_HOME, EVK_HOME, - VK_LEFT, EVK_LEFT, - VK_UP, EVK_UP, - VK_RIGHT, EVK_RIGHT, - VK_DOWN, EVK_DOWN, - VK_INSERT, EVK_INSERT, - VK_DELETE, EVK_DELETE, - VK_F1, EVK_F1, - VK_F2, EVK_F2, - VK_F3, EVK_F3, - VK_F4, EVK_F4, - VK_F5, EVK_F5, - VK_F6, EVK_F6, - VK_F7, EVK_F7, - VK_F8, EVK_F8, - VK_F9, EVK_F9, - VK_F10, EVK_F10, - VK_F11, EVK_F11, - VK_F12, EVK_F12, - 0x7fffffff, 0 -}; - -//***** Externs ***** - -extern HCURSOR Cursor; -extern void Done_Device(int handle, int error); -extern void Paint_Window(HWND window); -extern void Close_Window(REBGOB *gob); -extern REBOOL Resize_Window(REBGOB *gob, REBOOL redraw); - - -/*********************************************************************** -** -** Local Functions -** -***********************************************************************/ - -static void Add_Event_XY(REBGOB *gob, REBINT id, REBINT xy, REBINT flags) -{ - REBEVT evt; - - evt.type = id; - evt.flags = (u8) (flags | (1<old_size.x = 0; - gob->old_size.y = 0; - Add_Event_XY(gob, EVT_MINIMIZE, xy, flags); - } else { - gob->size.x = (i16)LOWORD(xy); - gob->size.y = (i16)HIWORD(xy); - last_xy = xy; - if (mode) { - //Resize and redraw the window buffer (when resize dragging) - Resize_Window(gob, TRUE); - mode = EVT_RESIZE; - break; - } else { - //Resize only the window buffer (when win gob size changed by REBOL code or using min/max buttons) - if (!Resize_Window(gob, FALSE)){ - //size has been changed programatically - return only 'resize event - Add_Event_XY(gob, EVT_RESIZE, xy, flags); - break; - } - } - //Otherwise send combo of 'resize + maximize/restore events - if (wParam == SIZE_MAXIMIZED) i = EVT_MAXIMIZE; - else if (wParam == SIZE_RESTORED) i = EVT_RESTORE; - else i = 0; - Add_Event_XY(gob, EVT_RESIZE, xy, flags); - if (i) Add_Event_XY(gob, i, xy, flags); - } - break; - - case WM_MOVE: - // Minimize and maximize call this w/o mode set. - gob->offset.x = (i16)LOWORD(xy); - gob->offset.y = (i16)HIWORD(xy); - last_xy = xy; - if (mode) mode = EVT_OFFSET; - else Add_Event_XY(gob, EVT_OFFSET, xy, flags); - break; - - case WM_ENTERSIZEMOVE: - mode = -1; // possible to ENTER and EXIT w/o SIZE change - break; - - case WM_EXITSIZEMOVE: - if (mode > 0) Add_Event_XY(gob, mode, last_xy, flags); - mode = 0; - break; - - case WM_MOUSELEAVE: - // Get cursor position, not the one given in message: - //GetCursorPos(&x_y); - //ScreenToClient(hwnd, &x_y); - //xy = (x_y.y << 16) + (x_y.x & 0xffff); - Add_Event_XY(gob, EVT_MOVE, xy, flags); - // WIN_FLAGS(wp) &= ~WINDOW_TRACK_LEAVE; - break; - - case WM_MOUSEWHEEL: - SystemParametersInfo(SPI_GETWHEELSCROLLLINES,0, &mw_num_lines, 0); - if (LOWORD(wParam) == MK_CONTROL || mw_num_lines > WHEEL_DELTA) { - Add_Event_XY(gob, EVT_SCROLL_PAGE, (GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA) << 16, flags); - } else { - Add_Event_XY(gob, EVT_SCROLL_LINE, ((GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA) << 16) * mw_num_lines, flags); - } - break; - - case WM_TIMER: - //Add_Event_XY(gob, EVT_TIME, xy, flags); - break; - - case WM_SETCURSOR: - if (LOWORD(xy) == 1) { - SetCursor(Cursor); - return TRUE; - } else goto default_case; - - case WM_LBUTTONDBLCLK: - SET_FLAG(flags, EVF_DOUBLE); - case WM_LBUTTONDOWN: - //if (!WIN_CAPTURED(wp)) { - flags = Check_Modifiers(flags); - Add_Event_XY(gob, EVT_DOWN, xy, flags); - SetCapture(hwnd); - //WIN_CAPTURED(wp) = EVT_BTN1_UP; - break; - - case WM_LBUTTONUP: - //if (WIN_CAPTURED(wp) == EVT_BTN1_UP) { - flags = Check_Modifiers(flags); - Add_Event_XY(gob, EVT_UP, xy, flags); - ReleaseCapture(); - //WIN_CAPTURED(wp) = 0; - break; - - case WM_RBUTTONDBLCLK: - SET_FLAG(flags, EVF_DOUBLE); - case WM_RBUTTONDOWN: - //if (!WIN_CAPTURED(wp)) { - flags = Check_Modifiers(flags); - Add_Event_XY(gob, EVT_ALT_DOWN, xy, flags); - SetCapture(hwnd); - //WIN_CAPTURED(wp) = EVT_BTN2_UP; - break; - - case WM_RBUTTONUP: - //if (WIN_CAPTURED(wp) == EVT_BTN2_UP) { - flags = Check_Modifiers(flags); - Add_Event_XY(gob, EVT_ALT_UP, xy, flags); - ReleaseCapture(); - //WIN_CAPTURED(wp) = 0; - break; - - case WM_MBUTTONDBLCLK: - SET_FLAG(flags, EVF_DOUBLE); - case WM_MBUTTONDOWN: - //if (!WIN_CAPTURED(wp)) { - flags = Check_Modifiers(flags); - Add_Event_XY(gob, EVT_AUX_DOWN, xy, flags); - SetCapture(hwnd); - break; - - case WM_MBUTTONUP: - //if (WIN_CAPTURED(wp) == EVT_BTN2_UP) { - flags = Check_Modifiers(flags); - Add_Event_XY(gob, EVT_AUX_UP, xy, flags); - ReleaseCapture(); - break; - - case WM_KEYDOWN: - // Note: key repeat may cause multiple downs before an up. - case WM_KEYUP: - flags = Check_Modifiers(flags); - for (i = 0; Key_To_Event[i] && wParam > Key_To_Event[i]; i += 2); - if (wParam == Key_To_Event[i]) - Add_Event_Key(gob, (msg==WM_KEYDOWN) ? EVT_KEY : EVT_KEY_UP, Key_To_Event[i+1] << 16, flags); - break; - - case WM_CHAR: - flags = Check_Modifiers(flags); -#ifdef OS_WIDE_CHAR - i = wParam; -#else - i = wParam & 0xff; -#endif - //if (i == 127) i = 8; // Windows weirdness of converting ctrl-backspace to delete - Add_Event_Key(gob, EVT_KEY, i, flags); - break; - - case WM_DROPFILES: - Add_File_Events(gob, flags, (HDROP)wParam); - break; - - case WM_CLOSE: - Add_Event_XY(gob, EVT_CLOSE, xy, flags); - Close_Window(gob); // Needs to be removed - should be done by REBOL event handling -// DestroyWindow(hwnd);// This is done in Close_Window() - break; - - case WM_DESTROY: - PostQuitMessage(0); - break; - - default: - default_case: - return DefWindowProc(hwnd, msg, wParam, xy); - } - return 0; -} diff --git a/src/os/win32/host-graphics.c b/src/os/win32/host-graphics.c deleted file mode 100644 index 42f58a7a5d..0000000000 --- a/src/os/win32/host-graphics.c +++ /dev/null @@ -1,1240 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Graphics Compositing -** Author: Cyphre, Carl -** Purpose: Interface from graphics commands to AGG library. -** Tools: make-host-ext.r -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ - -#include - -#include "reb-host.h" -#include "host-lib.h" - -#include "agg-draw.h" -#include "agg-text.h" - -#define INCLUDE_EXT_DATA -#include "host-ext-graphics.h" -#include "host-ext-draw.h" -#include "host-ext-shape.h" -#include "host-ext-text.h" - -//***** Externs ***** - -extern REBINT As_OS_Str(REBSER *series, REBCHR **string); -extern REBINT Show_Gob(REBGOB *gob); -extern HCURSOR Cursor; -static REBOOL Custom_Cursor = FALSE; - -static u32* draw_ext_words; -static u32* shape_ext_words; -static u32* text_ext_words; -static u32* graphics_ext_words; - -void* Rich_Text; - -RL_LIB *RL; // Link back to reb-lib from embedded extensions - -/*********************************************************************** -** -*/ HCURSOR Image_To_Cursor(REBYTE* image, REBINT width, REBINT height) -/* -** Converts REBOL image! to Windows CURSOR -** -***********************************************************************/ -{ - int xHotspot = 0; - int yHotspot = 0; - - HICON result = NULL; - HBITMAP hSourceBitmap; - BITMAPINFO BitmapInfo; - ICONINFO iconinfo; - - //Get the system display DC - HDC hDC = GetDC(NULL); - - //Create DIB - unsigned char* ppvBits; - int bmlen = width * height * 4; - int i; - - BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - BitmapInfo.bmiHeader.biWidth = width; - BitmapInfo.bmiHeader.biHeight = -(signed)height; - BitmapInfo.bmiHeader.biPlanes = 1; - BitmapInfo.bmiHeader.biBitCount = 32; - BitmapInfo.bmiHeader.biCompression = BI_RGB; - BitmapInfo.bmiHeader.biSizeImage = 0; - BitmapInfo.bmiHeader.biXPelsPerMeter = 0; - BitmapInfo.bmiHeader.biYPelsPerMeter = 0; - BitmapInfo.bmiHeader.biClrUsed = 0; - BitmapInfo.bmiHeader.biClrImportant = 0; - - hSourceBitmap = CreateDIBSection(hDC, &BitmapInfo, DIB_RGB_COLORS, (void**)&ppvBits, NULL, 0); - - //Release the system display DC - ReleaseDC(NULL, hDC); - - //Copy the image content to DIB - COPY_MEM(ppvBits, image, bmlen); - - //Invert alphachannel from the REBOL format - for (i = 3;i < bmlen;i+=4){ - ppvBits[i] ^= 0xff; - } - - //Create the cursor using the masks and the hotspot values provided - iconinfo.fIcon = FALSE; - iconinfo.xHotspot = xHotspot; - iconinfo.yHotspot = yHotspot; - iconinfo.hbmMask = hSourceBitmap; - iconinfo.hbmColor = hSourceBitmap; - - result = CreateIconIndirect(&iconinfo); - - DeleteObject(hSourceBitmap); - - return result; -} - -/*********************************************************************** -** -*/ REBINT Draw_Gob(void *graphics, REBSER *block, REBSER *args) -/* -** Temporary trampoline. Move to compositor! -** -***********************************************************************/ -{ - REBCEC ctx; - - ctx.envr = graphics; - ctx.block = block; - ctx.index = 0; - - RL_Do_Commands(block, 0, &ctx); - return 0; -} - - -/*********************************************************************** -** -*/ REBINT Text_Gob(void *richtext, REBSER *block) -/* -** Temporary trampoline. Move to compositor! -** -***********************************************************************/ -{ - REBCEC ctx; - - ctx.envr = richtext; - ctx.block = block; - ctx.index = 0; - - RL_Do_Commands(block, 0, &ctx); - return 0; -} - -/*********************************************************************** -** -*/ RXIEXT int RXD_Graphics(int cmd, RXIFRM *frm, REBCEC *data) -/* -** Graphics command extension dispatcher. -** -***********************************************************************/ -{ - switch (cmd) { - - case CMD_GRAPHICS_SHOW: - Show_Gob((REBGOB*)RXA_SERIES(frm, 1)); - RXA_TYPE(frm, 1) = RXT_GOB; - return RXR_VALUE; - - case CMD_GRAPHICS_SIZE_TEXT: - if (Rich_Text) { - RXA_TYPE(frm, 2) = RXT_PAIR; - rt_size_text(Rich_Text, (REBGOB*)RXA_SERIES(frm, 1),&RXA_PAIR(frm, 2)); - RXA_PAIR(frm, 1).x = RXA_PAIR(frm, 2).x; - RXA_PAIR(frm, 1).y = RXA_PAIR(frm, 2).y; - RXA_TYPE(frm, 1) = RXT_PAIR; - return RXR_VALUE; - } - - break; - - case CMD_GRAPHICS_OFFSET_TO_CARET: - if (Rich_Text) { - REBINT element = 0, position = 0; - REBSER* dialect; - REBSER* block; - RXIARG val; //, str; - REBCNT n, type; - - rt_offset_to_caret(Rich_Text, (REBGOB*)RXA_SERIES(frm, 1), RXA_PAIR(frm, 2), &element, &position); -// RL_Print("OTC: %d, %d\n", element, position); - dialect = (REBSER *)GOB_CONTENT((REBGOB*)RXA_SERIES(frm, 1)); - block = RL_MAKE_BLOCK(RL_SERIES(dialect, RXI_SER_TAIL)); - for (n = 0; type = RL_GET_VALUE(dialect, n, &val); n++) { - if (n == element) val.index = position; - RL_SET_VALUE(block, n, val, type); - } - - RXA_TYPE(frm, 1) = RXT_BLOCK; - RXA_SERIES(frm, 1) = block; - RXA_INDEX(frm, 1) = element; - - return RXR_VALUE; - } - - break; - - case CMD_GRAPHICS_CARET_TO_OFFSET: - if (Rich_Text) { - REBXYF result; - REBINT elem,pos; - if (RXA_TYPE(frm, 2) == RXT_INTEGER){ - elem = RXA_INT64(frm, 2)-1; - } else { - elem = RXA_INDEX(frm, 2); - } - if (RXA_TYPE(frm, 3) == RXT_INTEGER){ - pos = RXA_INT64(frm, 3)-1; - } else { - pos = RXA_INDEX(frm, 3); - } -// RL_Print("CTO: %d, %d\n", element, position); - rt_caret_to_offset(Rich_Text, (REBGOB*)RXA_SERIES(frm, 1), &result, elem, pos); - - RXA_PAIR(frm, 1).x = result.x; - RXA_PAIR(frm, 1).y = result.y; - RXA_TYPE(frm, 1) = RXT_PAIR; - return RXR_VALUE; - } - break; - - case CMD_GRAPHICS_CURSOR: - { - REBINT n = 0; - REBSER image = 0; - - if (RXA_TYPE(frm, 1) == RXT_IMAGE) { - image = RXA_IMAGE_BITS(frm,1); - } else { - n = RXA_INT64(frm,1); - } - - if (Custom_Cursor) { - //Destroy cursor object only if it is a custom image - DestroyCursor(Cursor); - Custom_Cursor = FALSE; - } - - if (n > 0) - Cursor = LoadCursor(NULL, (LPCTSTR)n); - else if (image) { - Cursor = Image_To_Cursor(image, RXA_IMAGE_WIDTH(frm,1), RXA_IMAGE_HEIGHT(frm,1)); - Custom_Cursor = TRUE; - } else - Cursor = NULL; - - SetCursor(Cursor); - - } - break; - - case CMD_GRAPHICS_DRAW: - { - REBYTE* img = 0; - REBINT w,h; - if (RXA_TYPE(frm, 1) == RXT_IMAGE) { - img = RXA_IMAGE_BITS(frm, 1); - w = RXA_IMAGE_WIDTH(frm, 1); - h = RXA_IMAGE_HEIGHT(frm, 1); - } else { - REBSER* i; - w = RXA_PAIR(frm,1).x; - h = RXA_PAIR(frm,1).y; - i = RL_MAKE_IMAGE(w,h); - img = (REBYTE *)RL_SERIES(i, RXI_SER_DATA); - - RXA_TYPE(frm, 1) = RXT_IMAGE; - RXA_ARG(frm, 1).width = w; - RXA_ARG(frm, 1).height = h; - RXA_ARG(frm, 1).image = i; - } - Draw_Image(img, w, h, RXA_SERIES(frm, 2)); - return RXR_VALUE; - } - break; - - case CMD_GRAPHICS_GUI_METRIC: - { - REBINT x,y; - u32 w = RL_FIND_WORD(graphics_ext_words,RXA_WORD(frm, 1)); - - switch(w) - { - case W_GRAPHICS_SCREEN_SIZE: - x = GetSystemMetrics(SM_CXSCREEN); - y = GetSystemMetrics(SM_CYSCREEN); - break; - - case W_GRAPHICS_TITLE_SIZE: - x = 0; - y = GetSystemMetrics(SM_CYCAPTION); - break; - - case W_GRAPHICS_BORDER_SIZE: - x = GetSystemMetrics(SM_CXSIZEFRAME); - y = GetSystemMetrics(SM_CYSIZEFRAME); - break; - - case W_GRAPHICS_BORDER_FIXED: - x = GetSystemMetrics(SM_CXFIXEDFRAME); - y = GetSystemMetrics(SM_CYFIXEDFRAME); - break; - - case W_GRAPHICS_WORK_ORIGIN: - { - RECT rect; - SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0); - x = rect.left; - y = rect.top; - } - break; - - case W_GRAPHICS_WORK_SIZE: - { - RECT rect; - SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0); - x = rect.right; - y = rect.bottom; - } - break; - } - - if (w){ - RXA_PAIR(frm, 1).x = x; - RXA_PAIR(frm, 1).y = y; - RXA_TYPE(frm, 1) = RXT_PAIR; - } else { - RXA_TYPE(frm, 1) = RXT_NONE; - } - return RXR_VALUE; - } - break; - - case CMD_GRAPHICS_INIT: - Gob_Root = (REBGOB*)RXA_SERIES(frm, 1); // system/view/screen-gob - Gob_Root->size.x = (REBD32)GetSystemMetrics(SM_CXSCREEN); - Gob_Root->size.y = (REBD32)GetSystemMetrics(SM_CYSCREEN); - - //Initialize text rendering context - if (Rich_Text) Destroy_RichText(Rich_Text); - Rich_Text = Create_RichText(); - - break; - - case CMD_GRAPHICS_INIT_WORDS: - //temp hack - will be removed later - graphics_ext_words = RL_MAP_WORDS(RXA_SERIES(frm,1)); - break; - - default: - return RXR_NO_COMMAND; - } - return RXR_UNSET; -} - -/*********************************************************************** -** -*/ RXIEXT int RXD_Text(int cmd, RXIFRM *frm, REBCEC *ctx) -/* -** DRAW command dispatcher. -** -***********************************************************************/ -{ - switch (cmd) { - - case CMD_TEXT_INIT_WORDS: - //temp hack - will be removed later - text_ext_words = RL_MAP_WORDS(RXA_SERIES(frm,1)); - break; - - case CMD_TEXT_ANTI_ALIAS: - rt_anti_alias(ctx->envr, RXA_LOGIC(frm, 1)); - break; - - case CMD_TEXT_BOLD: - rt_bold(ctx->envr, RXA_LOGIC(frm, 1)); - break; - - case CMD_TEXT_CARET: - { - RXIARG val; - u32 *words, *w; - REBSER *obj; - REBCNT type; - REBXYF caret, highlightStart, highlightEnd; - REBXYF *pcaret = 0, *phighlightStart = 0; - obj = RXA_OBJECT(frm, 1); -//Reb_Print("RXI_WORDS_OF_OBJECT() called\n"); - words = RL_WORDS_OF_OBJECT(obj); -//Reb_Print("RXI_WORDS_OF_OBJECT() OK\n"); - w = words; - - while (type = RL_GET_FIELD(obj, w[0], &val)) - { -// RL_Print("word: %d %d %d\n", w[0],w[1], (REBYTE)w[1]); - switch(RL_FIND_WORD(text_ext_words,w[0])) - { - case W_TEXT_CARET: - if (type == RXT_BLOCK){ - REBSER* block = val.series; - REBINT len = RL_SERIES(block, RXI_SER_TAIL); - if (len > 1){ - RXIARG pos, elem; - if ( - RL_GET_VALUE(block, 0, &pos) == RXT_BLOCK && - RL_GET_VALUE(block, 1, &elem) == RXT_STRING - ){ - caret.x = 1 + pos.index; - caret.y = 1 + elem.index; - pcaret = ⁁ - } - } - } - break; - - case W_TEXT_HIGHLIGHT_START: - if (type == RXT_BLOCK){ - REBSER* block = val.series; - REBINT len = RL_SERIES(block, RXI_SER_TAIL); - if (len > 1){ - RXIARG pos, elem; - if ( - RL_GET_VALUE(block, 0, &pos) == RXT_BLOCK && - RL_GET_VALUE(block, 1, &elem) == RXT_STRING - ){ - highlightStart.x = 1 + pos.index; - highlightStart.y = 1 + elem.index; - phighlightStart = &highlightStart; - } - } - } - break; - - case W_TEXT_HIGHLIGHT_END: - if (type == RXT_BLOCK){ - REBSER* block = val.series; - REBINT len = RL_SERIES(block, RXI_SER_TAIL); - if (len > 1){ - RXIARG pos, elem; - if ( - RL_GET_VALUE(block, 0, &pos) == RXT_BLOCK && - RL_GET_VALUE(block, 1, &elem) == RXT_STRING - ){ - highlightEnd.x = 1 + pos.index; - highlightEnd.y = 1 + elem.index; - } - } - } - break; - } - - w++; - } - OS_Free(words); - rt_caret(ctx->envr, pcaret, phighlightStart, highlightEnd); - } - - break; - - case CMD_TEXT_CENTER: - rt_center(ctx->envr); - break; - - case CMD_TEXT_COLOR: - rt_color(ctx->envr, RXA_TUPLE(frm,1) + 1); - break; - - case CMD_TEXT_DROP: - rt_drop(ctx->envr, RXA_INT32(frm,1)); - break; - - case CMD_TEXT_FONT: - { - RXIARG val; - u32 *words,*w; - REBSER *obj; - REBCNT type; - REBFNT *font = rt_get_font(ctx->envr); - - obj = RXA_OBJECT(frm, 1); - words = RL_WORDS_OF_OBJECT(obj); - w = words; - - while (type = RL_GET_FIELD(obj, w[0], &val)) - { - switch(RL_FIND_WORD(text_ext_words,w[0])) - { - case W_TEXT_NAME: - if (type == RXT_STRING){ - font->name_gc = As_OS_Str(val.series, &(font->name)); - } - break; - - case W_TEXT_STYLE: - switch(type) - { - case RXT_WORD: - { - u32 styleWord = RL_FIND_WORD(text_ext_words,val.int32a); - if (styleWord) rt_set_font_styles(font, styleWord); - } - break; - - case RXT_BLOCK: - { - RXIARG styleVal; - REBCNT styleType; - REBCNT n; - u32 styleWord; - for (n = 0; styleType = RL_GET_VALUE(val.series, n, &styleVal); n++) { - if (styleType == RXT_WORD) { - styleWord = RL_FIND_WORD(text_ext_words,styleVal.int32a); - if (styleWord) rt_set_font_styles(font, styleWord); - } - } - } - break; - } - break; - - case W_TEXT_SIZE: - if (type == RXT_INTEGER) - font->size = val.int64; - break; - - case W_TEXT_COLOR: - if (type == RXT_TUPLE) - memcpy(font->color,val.bytes + 1 , 4); - break; - - case W_TEXT_OFFSET: - if (type == RXT_PAIR) { - font->offset_x = val.pair.x; - font->offset_y = val.pair.y; - } - break; - - case W_TEXT_SPACE: - if (type == RXT_PAIR) { - font->space_x = val.pair.x; - font->space_y = val.pair.y; - } - break; - - case W_TEXT_SHADOW: - switch(type) - { - case RXT_PAIR: - { - font->shadow_x = val.pair.x; - font->shadow_y = val.pair.y; - } - break; - - case RXT_BLOCK: - { - RXIARG shadowVal; - REBCNT shadowType; - REBCNT n; - for (n = 0; shadowType = RL_GET_VALUE(val.series, n, &shadowVal); n++) { - switch (shadowType) - { - case RXT_PAIR: - font->shadow_x = shadowVal.pair.x; - font->shadow_y = shadowVal.pair.y; - break; - - case RXT_TUPLE: - memcpy(font->shadow_color,shadowVal.bytes + 1 , 4); - break; - - case RXT_INTEGER: - font->shadow_blur = shadowVal.int64; - break; - } - } - } - break; - } - break; - } - - w++; - } - OS_Free(words); - rt_font(ctx->envr, font); - } - break; - - case CMD_TEXT_ITALIC: - rt_italic(ctx->envr, RXA_LOGIC(frm, 1)); - break; - - case CMD_TEXT_LEFT: - rt_left(ctx->envr); - break; - - case CMD_TEXT_NEWLINE: - rt_newline(ctx->envr, ctx->index + 1); - break; - - case CMD_TEXT_PARA: - { - RXIARG val; - u32 *words,*w; - REBSER *obj; - REBCNT type; - REBPRA *para = rt_get_para(ctx->envr); - - obj = RXA_OBJECT(frm, 1); - words = RL_WORDS_OF_OBJECT(obj); - w = words; - - while (type = RL_GET_FIELD(obj, w[0], &val)) - { - switch(RL_FIND_WORD(text_ext_words,w[0])) - { - case W_TEXT_ORIGIN: - if (type == RXT_PAIR) { - para->origin_x = val.pair.x; - para->origin_y = val.pair.y; - } - break; - case W_TEXT_MARGIN: - if (type == RXT_PAIR) { - para->margin_x = val.pair.x; - para->margin_y = val.pair.y; - } - break; - case W_TEXT_INDENT: - if (type == RXT_PAIR) { - para->indent_x = val.pair.x; - para->indent_y = val.pair.y; - } - break; - case W_TEXT_TABS: - if (type == RXT_INTEGER) { - para->tabs = val.int64; - } - break; - case W_TEXT_WRAPQ: - if (type == RXT_LOGIC) { - para->wrap = val.int32a; - } - break; - case W_TEXT_SCROLL: - if (type == RXT_PAIR) { - para->scroll_x = val.pair.x; - para->scroll_y = val.pair.y; - } - break; - case W_TEXT_ALIGN: - if (type == RXT_WORD) { - para->align = RL_FIND_WORD(text_ext_words,val.int32a); - } - break; - case W_TEXT_VALIGN: - if (type == RXT_WORD) { - para->valign = RL_FIND_WORD(text_ext_words,val.int32a); - } - break; - } - - w++; - } - OS_Free(words); - rt_para(ctx->envr, para); - } - break; - - case CMD_TEXT_RIGHT: - rt_right(ctx->envr); - break; - - case CMD_TEXT_SCROLL: - rt_scroll(ctx->envr, RXA_PAIR(frm, 1)); - break; - - case CMD_TEXT_SHADOW: - rt_shadow(ctx->envr, RXA_PAIR(frm, 1), RXA_TUPLE(frm,2) + 1, RXA_INT32(frm,3)); - break; - - case CMD_TEXT_SIZE: - rt_font_size(ctx->envr, RXA_INT32(frm,1)); - break; - - case CMD_TEXT_TEXT: - { - REBCHR* str; - REBOOL gc = As_OS_Str(RXA_SERIES(frm, 1), &str); - rt_text(ctx->envr, str, ctx->index + 2, gc); - } - break; - - case CMD_TEXT_UNDERLINE: - rt_underline(ctx->envr, RXA_LOGIC(frm, 1)); - break; - - default: - return RXR_NO_COMMAND; - } - return RXR_UNSET; -} - - -/*********************************************************************** -** -*/ RXIEXT int RXD_Shape(int cmd, RXIFRM *frm, REBCEC *ctx) -/* -** DRAW command dispatcher. -** -***********************************************************************/ -{ -// Reb_Print("SHAPE called\n"); - REBCNT rel = 0; - - switch (cmd) { - - case CMD_SHAPE_INIT_WORDS: - //temp hack - will be removed later - shape_ext_words = RL_MAP_WORDS(RXA_SERIES(frm,1)); - break; - - case CMD_SHAPE_ARC_LIT: - rel = 1; - case CMD_SHAPE_ARC: - agg_path_arc( - ctx->envr, - rel, - RXA_PAIR(frm, 1), - RXA_PAIR(frm, 2), - (RXA_TYPE(frm, 3) == RXT_DECIMAL) ? RXA_DEC64(frm, 3) : RXA_INT64(frm, 3), - RL_FIND_WORD(shape_ext_words , RXA_WORD(frm, 4)) - W_SHAPE_NEGATIVE, - RL_FIND_WORD(shape_ext_words , RXA_WORD(frm, 5)) - W_SHAPE_SMALL - ); - break; - - case CMD_SHAPE_CLOSE: - agg_path_close(ctx->envr); - break; - - case CMD_SHAPE_CURV_LIT: - rel = 1; - case CMD_SHAPE_CURV: - { - RXIARG val[2]; - REBCNT type; - REBCNT n, m = 0; - REBSER blk = RXA_SERIES(frm, 1); - - for (n = 0; type = RL_GET_VALUE(blk, n, &val[m]); n++) { - if (type == RXT_PAIR && ++m == 2) { - agg_path_curv(ctx->envr, rel, val[0].pair, val[1].pair); - m = 0; - } - } - } - break; - - case CMD_SHAPE_CURVE_LIT: - rel = 1; - case CMD_SHAPE_CURVE: - { - RXIARG val[3]; - REBCNT type; - REBCNT n, m = 0; - REBSER blk = RXA_SERIES(frm, 1); - - for (n = 0; type = RL_GET_VALUE(blk, n, &val[m]); n++) { - if (type == RXT_PAIR && ++m == 3) { - agg_path_curve(ctx->envr, rel, val[0].pair, val[1].pair, val[2].pair); - m = 0; - } - } - } - break; - - case CMD_SHAPE_HLINE_LIT: - rel = 1; - case CMD_SHAPE_HLINE: - agg_path_hline(ctx->envr, rel, (RXA_TYPE(frm, 1) == RXT_DECIMAL) ? RXA_DEC64(frm, 1) : RXA_INT64(frm, 1)); - break; - - case CMD_SHAPE_LINE_LIT: - rel = 1; - case CMD_SHAPE_LINE: - if (RXA_TYPE(frm, 1) == RXT_PAIR) - agg_path_line(ctx->envr, rel, RXA_PAIR(frm, 1)); - else { - RXIARG val; - REBCNT type; - REBCNT n; - REBSER blk = RXA_SERIES(frm, 1); - - for (n = 0; type = RL_GET_VALUE(blk, n, &val); n++) { - if (type == RXT_PAIR) - agg_path_line(ctx->envr, rel, val.pair); - } - } - break; - - case CMD_SHAPE_MOVE_LIT: - rel = 1; - case CMD_SHAPE_MOVE: - agg_path_move(ctx->envr, rel, RXA_PAIR(frm, 1)); - break; - - case CMD_SHAPE_QCURV_LIT: - rel = 1; - case CMD_SHAPE_QCURV: - agg_path_qcurv(ctx->envr, rel, RXA_PAIR(frm, 1)); - break; - - case CMD_SHAPE_QCURVE_LIT: - rel = 1; - case CMD_SHAPE_QCURVE: - { - RXIARG val[2]; - REBCNT type; - REBCNT n, m = 0; - REBSER blk = RXA_SERIES(frm, 1); - - for (n = 0; type = RL_GET_VALUE(blk, n, &val[m]); n++) { - if (type == RXT_PAIR && ++m == 2) { - agg_path_qcurve(ctx->envr, rel, val[0].pair, val[1].pair); - m = 0; - } - } - } - break; - - case CMD_SHAPE_VLINE_LIT: - rel = 1; - case CMD_SHAPE_VLINE: - agg_path_vline(ctx->envr, rel, (RXA_TYPE(frm, 1) == RXT_DECIMAL) ? RXA_DEC64(frm, 1) : RXA_INT64(frm, 1)); - break; - - default: - return RXR_NO_COMMAND; - } - - return RXR_UNSET; -} - -/*********************************************************************** -** -*/ RXIEXT int RXD_Draw(int cmd, RXIFRM *frm, REBCEC *ctx) -/* -** DRAW command dispatcher. -** -***********************************************************************/ -{ - switch (cmd) { - - case CMD_DRAW_INIT_WORDS: - //temp hack - will be removed later - draw_ext_words = RL_MAP_WORDS(RXA_SERIES(frm,1)); - break; - case CMD_DRAW_ANTI_ALIAS: - agg_anti_alias(ctx->envr, RXA_LOGIC(frm, 1)); - break; - - case CMD_DRAW_ARC: - agg_arc( - ctx->envr, - RXA_PAIR(frm, 1), - RXA_PAIR(frm, 2), - (RXA_TYPE(frm, 3) == RXT_DECIMAL) ? RXA_DEC64(frm, 3) : RXA_INT64(frm, 3), - (RXA_TYPE(frm, 4) == RXT_DECIMAL) ? RXA_DEC64(frm, 4) : RXA_INT64(frm, 4), - RL_FIND_WORD(draw_ext_words , RXA_WORD(frm, 5)) - W_DRAW_OPENED - ); - break; - - case CMD_DRAW_ARROW: - agg_arrow(ctx->envr, RXA_PAIR(frm, 1), (RXA_TYPE(frm, 2) == RXT_NONE) ? NULL : RXA_TUPLE(frm, 2)+1); - break; - - case CMD_DRAW_BOX: - agg_box(ctx->envr, RXA_PAIR(frm, 1), RXA_PAIR(frm, 2), (RXA_TYPE(frm, 3) == RXT_DECIMAL) ? RXA_DEC64(frm, 3) : RXA_INT64(frm, 3)); - break; - - case CMD_DRAW_CIRCLE: - agg_circle(ctx->envr, RXA_PAIR(frm, 1), RXA_PAIR(frm, 2)); - break; - - case CMD_DRAW_CLIP: - agg_clip(ctx->envr, RXA_PAIR(frm, 1), RXA_PAIR(frm, 2)); - break; - - case CMD_DRAW_CURVE: - if (RXA_TYPE(frm, 4) == RXT_NONE) - agg_curve3(ctx->envr, RXA_PAIR(frm, 1), RXA_PAIR(frm, 2), RXA_PAIR(frm, 3)); - else - agg_curve4(ctx->envr, RXA_PAIR(frm, 1), RXA_PAIR(frm, 2), RXA_PAIR(frm, 3), RXA_PAIR(frm, 4)); - - break; - - case CMD_DRAW_ELLIPSE: - agg_ellipse(ctx->envr, RXA_PAIR(frm, 1), RXA_PAIR(frm, 2)); - break; - - case CMD_DRAW_FILL_PEN: - { - //REBYTE* val; - //REBCNT type; - //REBSER* img; - - if (RXA_TYPE(frm, 1) == RXT_TUPLE) - agg_fill_pen(ctx->envr, RXA_TUPLE(frm, 1)+1); - else if (RXA_TYPE(frm, 1) == RXT_LOGIC && !RXA_LOGIC(frm,1)) - agg_fill_pen(ctx->envr, NULL); - else { - agg_fill_pen_image(ctx->envr, RXA_IMAGE_BITS(frm,1), RXA_IMAGE_WIDTH(frm,1), RXA_IMAGE_HEIGHT(frm,1)); - } - } - break; - - case CMD_DRAW_FILL_RULE: - agg_fill_rule(ctx->envr, RL_FIND_WORD(draw_ext_words , RXA_WORD(frm, 1))); - break; - - case CMD_DRAW_GAMMA: - agg_gamma(ctx->envr, (RXA_TYPE(frm, 1) == RXT_DECIMAL) ? RXA_DEC64(frm, 1) : RXA_INT64(frm, 1)); - break; - - case CMD_DRAW_GRAD_PEN: - if (RXA_TYPE(frm, 7) == RXT_NONE) - agg_reset_gradient_pen(ctx->envr); - else - agg_gradient_pen( - ctx->envr, - RL_FIND_WORD(draw_ext_words , RXA_WORD(frm, 1)), //type - RL_FIND_WORD(draw_ext_words , RXA_WORD(frm, 2)), //mode - RXA_PAIR(frm, 3), //offset - RXA_PAIR(frm, 4), //range - (begin, end) - (RXA_TYPE(frm, 5) == RXT_DECIMAL) ? RXA_DEC64(frm, 5) : RXA_INT64(frm, 5), // angle - RXA_PAIR(frm, 6), // scale - RXA_SERIES(frm, 7) // unsigned char *colors - ); - break; - - case CMD_DRAW_IMAGE: - if (RXA_TYPE(frm, 2) == RXT_PAIR) - agg_image(ctx->envr, RXA_IMAGE_BITS(frm,1), RXA_IMAGE_WIDTH(frm,1), RXA_IMAGE_HEIGHT(frm,1), RXA_PAIR(frm, 2)); - else { - agg_image_scale(ctx->envr, RXA_IMAGE_BITS(frm,1), RXA_IMAGE_WIDTH(frm,1), RXA_IMAGE_HEIGHT(frm,1), RXA_SERIES(frm, 2)); - } - break; - - case CMD_DRAW_IMAGE_FILTER: - agg_image_filter( - ctx->envr, - RL_FIND_WORD(draw_ext_words , RXA_WORD(frm, 1)) - W_DRAW_NEAREST, - RL_FIND_WORD(draw_ext_words , RXA_WORD(frm, 2)) - W_DRAW_RESIZE, - (RXA_TYPE(frm, 3) == RXT_NONE) ? 1.0 : (RXA_TYPE(frm, 3) == RXT_DECIMAL) ? RXA_DEC64(frm, 3) : RXA_INT64(frm, 3) - ); - break; - - case CMD_DRAW_IMAGE_OPTIONS: - agg_image_options(ctx->envr, (RXA_TYPE(frm, 1) == RXT_NONE) ? NULL : RXA_TUPLE(frm, 1)+1, RL_FIND_WORD(draw_ext_words , RXA_WORD(frm, 2)) - W_DRAW_NO_BORDER); - break; - - case CMD_DRAW_IMAGE_PATTERN: - agg_image_pattern(ctx->envr, RL_FIND_WORD(draw_ext_words , RXA_WORD(frm, 1)) - W_DRAW_NORMAL, RXA_PAIR(frm, 2), RXA_PAIR(frm, 3)); - break; - - - case CMD_DRAW_LINE: - { - RXIARG val[2]; - REBCNT type; - REBCNT n, m = 0; - REBSER blk = RXA_SERIES(frm, 1); - - for (n = 0; type = RL_GET_VALUE(blk, n, &val[m]); n++) { - if (type == RXT_PAIR) { - switch (++m){ - case 1: - agg_path_open(ctx->envr); - break; - case 2: - agg_line(ctx->envr, val[0].pair,val[1].pair); - val[0] = val[1]; - m--; - break; - } - } - } - } - break; - - case CMD_DRAW_LINE_CAP: - agg_line_cap(ctx->envr, RL_FIND_WORD(draw_ext_words , RXA_WORD(frm, 1)) - W_DRAW_BUTT); - break; - - case CMD_DRAW_LINE_JOIN: - agg_line_join(ctx->envr, RL_FIND_WORD(draw_ext_words , RXA_WORD(frm, 1)) - W_DRAW_MITER); - break; - - case CMD_DRAW_LINE_WIDTH: - agg_line_width(ctx->envr, (RXA_TYPE(frm, 1) == RXT_DECIMAL) ? RXA_DEC64(frm, 1) : RXA_INT64(frm, 1), RL_FIND_WORD(draw_ext_words , RXA_WORD(frm, 2)) - W_DRAW_VARIABLE); - break; - - case CMD_DRAW_LINE_PATTERN: - if (RXA_TYPE(frm, 2) == RXT_NONE) - agg_line_pattern(ctx->envr, NULL, NULL); - else { - REBSER patterns = RXA_SERIES(frm, 2); - REBINT len = RL_SERIES(patterns, RXI_SER_TAIL); - - if (len > 1){ - - RXIARG val; - REBCNT type; - REBCNT n; - REBDEC* pattern = (REBDEC*) malloc ((len + 1) * sizeof(REBDEC)) ; - - pattern[0] = len; - - for (n = 0; type = RL_GET_VALUE(patterns, n, &val); n++) { - if (type == RXT_DECIMAL) - pattern[n+1] = val.dec64; - else if (type == RXT_INTEGER) - pattern[n+1] = val.int64; - else - break; - } - agg_line_pattern(ctx->envr, RXA_TUPLE(frm, 1)+1, pattern); - } - - } - break; - - case CMD_DRAW_INVERT_MATRIX: - agg_invert_matrix(ctx->envr); - break; - - case CMD_DRAW_MATRIX: - agg_matrix(ctx->envr, RXA_SERIES(frm, 1)); - break; - - case CMD_DRAW_PEN: - if (RXA_TYPE(frm, 1) == RXT_TUPLE) - agg_pen(ctx->envr, RXA_TUPLE(frm, 1)+1); - else if (RXA_TYPE(frm, 1) == RXT_LOGIC && !RXA_LOGIC(frm,1)) - agg_pen(ctx->envr, NULL); - else - agg_pen_image(ctx->envr, RXA_IMAGE_BITS(frm,1), RXA_IMAGE_WIDTH(frm,1), RXA_IMAGE_HEIGHT(frm,1)); - break; - - case CMD_DRAW_POLYGON: - { - RXIARG val; - REBCNT type; - REBCNT n; - REBSER blk = RXA_SERIES(frm, 1); - - for (n = 0; type = RL_GET_VALUE(blk, n, &val); n++) { - if (type == RXT_PAIR) { - if (n > 0) - agg_add_vertex(ctx->envr, val.pair); - else - agg_begin_poly(ctx->envr, val.pair); - } - } - agg_end_poly(ctx->envr); - } - break; - - case CMD_DRAW_PUSH: - { - REBCEC innerCtx; - - innerCtx.envr = ctx->envr; - innerCtx.block = RXA_SERIES(frm, 1); - innerCtx.index = 0; - - agg_push_matrix(ctx->envr); - RL_Do_Commands(RXA_SERIES(frm, 1), 0, &innerCtx); - agg_pop_matrix(ctx->envr); - } - break; - - case CMD_DRAW_RESET_MATRIX: - agg_reset_matrix(ctx->envr); - break; - - case CMD_DRAW_ROTATE: - agg_rotate(ctx->envr, (RXA_TYPE(frm, 1) == RXT_DECIMAL) ? RXA_DEC64(frm, 1) : RXA_INT64(frm, 1)); - break; - - case CMD_DRAW_SCALE: - agg_scale(ctx->envr, RXA_PAIR(frm, 1)); - break; - - case CMD_DRAW_SHAPE: - { - REBCEC innerCtx; - - innerCtx.envr = ctx->envr; - innerCtx.block = RXA_SERIES(frm, 1); - innerCtx.index = 0; - - agg_path_open(ctx->envr); - RL_Do_Commands(RXA_SERIES(frm, 1), 0, &innerCtx); - agg_path_close(ctx->envr); - } - break; - - case CMD_DRAW_SKEW: - agg_skew(ctx->envr, RXA_PAIR(frm, 1)); - break; - - case CMD_DRAW_SPLINE: - { - REBSER points = RXA_SERIES(frm, 1); - REBINT len = RL_SERIES(points, RXI_SER_TAIL); - - if (len > 3){ - RXIARG val; - REBCNT type; - REBCNT n; - - for (n = 0; type = RL_GET_VALUE(points, n, &val); n++) { - if (type == RXT_PAIR) { - if (n > 0) - agg_add_vertex(ctx->envr, val.pair); - else - agg_begin_poly(ctx->envr, val.pair); - } - } - agg_end_spline(ctx->envr, RXA_INT32(frm, 2), RL_FIND_WORD(draw_ext_words , RXA_WORD(frm, 3)) - W_DRAW_OPENED); - } - - } - break; - - case CMD_DRAW_TEXT: - agg_text( - ctx->envr, - (RL_FIND_WORD(draw_ext_words , RXA_WORD(frm, 3)) == W_DRAW_VECTORIAL) ? 1 : 0, - RXA_PAIR(frm, 1), - RXA_PAIR(frm, 2), - RXA_SERIES(frm, 4) - ); - break; - - case CMD_DRAW_TRANSFORM: - agg_transform( - ctx->envr, - (RXA_TYPE(frm, 1) == RXT_DECIMAL) ? RXA_DEC64(frm, 1) : RXA_INT64(frm, 1), // angle - RXA_PAIR(frm, 2), // center - RXA_PAIR(frm, 3), // scale - RXA_PAIR(frm, 4) // offset - ); - break; - - case CMD_DRAW_TRANSLATE: - agg_translate(ctx->envr, RXA_PAIR(frm, 1)); - break; - - case CMD_DRAW_TRIANGLE: - { - REBYTE b[4] = {0,0,0,0}; - agg_triangle( - ctx->envr, - RXA_PAIR(frm, 1), // vertex-1 - RXA_PAIR(frm, 2), // vertex-2 - RXA_PAIR(frm, 3), // vertex-3 - (RXA_TYPE(frm, 4) == RXT_NONE) ? NULL : RXA_TUPLE(frm, 4)+1, // color-1 - (RXA_TYPE(frm, 5) == RXT_NONE) ? b : RXA_TUPLE(frm, 5)+1, // color-2 - (RXA_TYPE(frm, 6) == RXT_NONE) ? b : RXA_TUPLE(frm, 6)+1, // color-3 - (RXA_TYPE(frm, 7) == RXT_DECIMAL) ? RXA_DEC64(frm, 7) : RXA_INT64(frm, 7) // dilation - ); - } - break; - - default: - return RXR_NO_COMMAND; - } - - return RXR_UNSET; -} - - -/*********************************************************************** -** -*/ void Init_Graphics(void) -/* -** Initialize special variables of the graphics subsystem. -** -***********************************************************************/ -{ - RL = RL_Extend((REBYTE *)(&RX_graphics[0]), &RXD_Graphics); - RL_Extend((REBYTE *)(&RX_draw[0]), &RXD_Draw); - RL_Extend((REBYTE *)(&RX_shape[0]), &RXD_Shape); - RL_Extend((REBYTE *)(&RX_text[0]), &RXD_Text); -} - -#ifdef OLD__FUNCS_NEED_CONVERSION - -/*********************************************************************** -** -*/ REBINT OS_Effect_Image(REBSER *image, REBSER *block) -/* -** Render EFFECT dialect into an image. -** Clip to keep render inside the image provided. -** -***********************************************************************/ -{ -// return Effect_Image(image, block); - return 0; -} - -#endif - diff --git a/src/os/win32/host-lib.c b/src/os/win32/host-lib.c deleted file mode 100644 index 89e74c00b4..0000000000 --- a/src/os/win32/host-lib.c +++ /dev/null @@ -1,723 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: OS API function library called by REBOL interpreter -** Author: Carl Sassenrath -** Purpose: -** This module provides the functions that REBOL calls -** to interface to the native (host) operating system. -** REBOL accesses these functions through the structure -** defined in host-lib.h (auto-generated, do not modify). -** -** Flags: compile with -DUNICODE for Win32 wide char API -** -** Special note: -** This module is parsed for function declarations used to -** build prototypes, tables, and other definitions. To change -** function arguments requires a rebuild of the REBOL library. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ - -/* WARNING: -** The function declarations here cannot be modified without -** also modifying those found in the other OS host-lib files! -** Do not even modify the argument names. -*/ - -#include -#include -#include -#include - -#include "reb-host.h" -#include "host-lib.h" - -// Semaphore lock to sync sub-task launch: -static void *Task_Ready; - - -/*********************************************************************** -** -*/ void Convert_Date(SYSTEMTIME *stime, REBOL_DAT *dat, long zone) -/* -** Convert local format of system time into standard date -** and time structure. -** -***********************************************************************/ -{ - dat->year = stime->wYear; - dat->month = stime->wMonth; - dat->day = stime->wDay; - dat->time = stime->wHour * 3600 + stime->wMinute * 60 + stime->wSecond; - dat->nano = 1000000 * stime->wMilliseconds; - dat->zone = zone; -} - -/*********************************************************************** -** -*/ static void Insert_Command_Arg(REBCHR *cmd, REBCHR *arg, REBINT limit) -/* -** Insert an argument into a command line at the %1 position, -** or at the end if there is no %1. (An INSERT action.) -** Do not exceed the specified limit length. -** -** Too bad std Clib does not provide INSERT or REPLACE functions. -** -***********************************************************************/ -{ - #define HOLD_SIZE 2000 - REBCHR *spot; - REBCHR hold[HOLD_SIZE+4]; - - if ((REBINT)LEN_STR(cmd) >= limit) return; // invalid case, ignore it. - - // Find %1: - spot = FIND_STR(cmd, TEXT("%1")); - - if (spot) { - // Save rest of cmd line (such as end quote, -flags, etc.) - COPY_STR(hold, spot+2, HOLD_SIZE); - - // Terminate at the arg location: - spot[0] = 0; - - // Insert the arg: - JOIN_STR(spot, arg, limit - LEN_STR(cmd) - 1); - - // Add back the rest of cmd: - JOIN_STR(spot, hold, limit - LEN_STR(cmd) - 1); - } - else { - JOIN_STR(cmd, TEXT(" "), 1); - JOIN_STR(cmd, arg, limit - LEN_STR(cmd) - 1); - } -} - - -/*********************************************************************** -** -** OS Library Functions -** -***********************************************************************/ - -/*********************************************************************** -** -*/ REBINT OS_Config(int id, REBYTE *result) -/* -** Return a specific runtime configuration parameter. -** -***********************************************************************/ -{ -#define OCID_STACK_SIZE 1 // needs to move to .h file - - switch (id) { - case OCID_STACK_SIZE: - return 0; // (size in bytes should be returned here) - } - - return 0; -} - - -/*********************************************************************** -** -*/ void *OS_Make(size_t size) -/* -** Allocate memory of given size. -** -** This is necessary because some environments may use their -** own specific memory allocation (e.g. private heaps). -** -***********************************************************************/ -{ - return malloc(size); -} - - -/*********************************************************************** -** -*/ void OS_Free(void *mem) -/* -** Free memory allocated in this OS environment. (See OS_Make) -** -***********************************************************************/ -{ - free(mem); -} - - -/*********************************************************************** -** -*/ void OS_Exit(int code) -/* -** Called in cases where REBOL needs to quit immediately -** without returning from the main() function. -** -***********************************************************************/ -{ - //OS_Call_Device(RDI_STDIO, RDC_CLOSE); // close echo - OS_Quit_Devices(0); - exit(code); -} - - -/*********************************************************************** -** -*/ void OS_Crash(const REBYTE *title, const REBYTE *content) -/* -** Tell user that REBOL has crashed. This function must use -** the most obvious and reliable method of displaying the -** crash message. -** -** If the title is NULL, then REBOL is running in a server mode. -** In that case, we do not want the crash message to appear on -** the screen, because the system may be unattended. -** -** On some systems, the error may be recorded in the system log. -** -***********************************************************************/ -{ - // Echo crash message if echo file is open: - ///PUTE(content); - OS_Call_Device(RDI_STDIO, RDC_CLOSE); // close echo - - // A title tells us we should alert the user: - if (title) { - // OS_Put_Str(title); - // OS_Put_Str(":\n"); - // Use ASCII only (in case we are on non-unicode win32): - MessageBoxA(NULL, content, title, MB_ICONHAND); - } - // OS_Put_Str(content); - exit(100); -} - - -/*********************************************************************** -** -*/ REBCHR *OS_Form_Error(int errnum, REBCHR *str, int len) -/* -** Translate OS error into a string. The str is the string -** buffer and the len is the length of the buffer. -** -***********************************************************************/ -{ - LPVOID lpMsgBuf; - int ok; - - if (!errnum) errnum = GetLastError(); - - ok = FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - errnum, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language - (LPTSTR) &lpMsgBuf, - 0, - NULL); - - len--; // termination - - if (!ok) COPY_STR(str, TEXT("unknown error"), len); - else { - COPY_STR(str, lpMsgBuf, len); - LocalFree(lpMsgBuf); - } - return str; -} - - -/*********************************************************************** -** -*/ REBOOL OS_Get_Boot_Path(REBCHR *name) -/* -** Used to determine the program file path for REBOL. -** This is the path stored in system->options->boot and -** it is used for finding default boot files. -** -***********************************************************************/ -{ - return (GetModuleFileName(0, name, MAX_FILE_NAME) > 0); -} - - -/*********************************************************************** -** -*/ REBCHR *OS_Get_Locale(int what) -/* -** Used to obtain locale information from the system. -** The returned value must be freed with OS_FREE_MEM. -** -***********************************************************************/ -{ - LCTYPE type; - int len; - REBCHR *data; - LCTYPE types[] = { - LOCALE_SENGLANGUAGE, - LOCALE_SNATIVELANGNAME, - LOCALE_SENGCOUNTRY, - LOCALE_SCOUNTRY, - }; - - type = types[what]; - - len = GetLocaleInfo(0, type, 0, 0); - data = MAKE_STR(len); - len = GetLocaleInfo(0, type, data, len); - - return data; -} - - -/*********************************************************************** -** -*/ REBINT OS_Get_Env(REBCHR *envname, REBCHR* envval, REBINT valsize) -/* -** Get a value from the environment. -** Returns size of retrieved value for success or zero if missing. -** If return size is greater than valsize then value contents -** are undefined, and size includes null terminator of needed buf -** -***********************************************************************/ -{ - // Note: The Windows variant of this API is NOT case-sensitive - - REBINT result = GetEnvironmentVariable(envname, envval, valsize); - if (result == 0) { // some failure... - if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) { - return 0; // not found - } - return -1; // other error - } - return result; -} - - -/*********************************************************************** -** -*/ REBOOL OS_Set_Env(REBCHR *envname, REBCHR *envval) -/* -** Set a value from the environment. -** Returns >0 for success and 0 for errors. -** -***********************************************************************/ -{ - return SetEnvironmentVariable(envname, envval); -} - - -/*********************************************************************** -** -*/ REBCHR *OS_List_Env(void) -/* -***********************************************************************/ -{ - REBCHR *env = GetEnvironmentStrings(); - REBCNT n, len = 0; - REBCHR *str; - - str = env; - while (n = LEN_STR(str)) { - len += n + 1; - str = env + len; // next - } - len++; - - str = OS_Make(len * sizeof(REBCHR)); - MOVE_MEM(str, env, len * sizeof(REBCHR)); - - FreeEnvironmentStrings(env); - - return str; -} - - -/*********************************************************************** -** -*/ void OS_Get_Time(REBOL_DAT *dat) -/* -** Get the current system date/time in UTC plus zone offset (mins). -** -***********************************************************************/ -{ - SYSTEMTIME stime; - TIME_ZONE_INFORMATION tzone; - - GetSystemTime(&stime); - - if (TIME_ZONE_ID_DAYLIGHT == GetTimeZoneInformation(&tzone)) - tzone.Bias += tzone.DaylightBias; - - Convert_Date(&stime, dat, -tzone.Bias); -} - - -/*********************************************************************** -** -*/ i64 OS_Delta_Time(i64 base, int flags) -/* -** Return time difference in microseconds. If base = 0, then -** return the counter. If base != 0, compute the time difference. -** -** Note: Requires high performance timer. -** Q: If not found, use timeGetTime() instead ?! -** -***********************************************************************/ -{ - LARGE_INTEGER freq; - LARGE_INTEGER time; - - if (!QueryPerformanceCounter(&time)) - OS_Crash("Missing resource", "High performance timer"); - - if (base == 0) return time.QuadPart; // counter (may not be time) - - QueryPerformanceFrequency(&freq); - - return ((time.QuadPart - base) * 1000) / (freq.QuadPart / 1000); -} - - -/*********************************************************************** -** -*/ int OS_Get_Current_Dir(REBCHR **path) -/* -** Return the current directory path as a string and -** its length in chars (not bytes). -** -** The result should be freed after copy/conversion. -** -***********************************************************************/ -{ - int len; - - len = GetCurrentDirectory(0, NULL); // length, incl terminator. - *path = MAKE_STR(len); - GetCurrentDirectory(len, *path); - len--; // less terminator - - return len; // Be sure to call free() after usage -} - - -/*********************************************************************** -** -*/ REBOOL OS_Set_Current_Dir(REBCHR *path) -/* -** Set the current directory to local path. Return FALSE -** on failure. -** -***********************************************************************/ -{ - return SetCurrentDirectory(path); -} - - -/*********************************************************************** -** -*/ void OS_File_Time(REBREQ *file, REBOL_DAT *dat) -/* -** Convert file.time to REBOL date/time format. -** Time zone is UTC. -** -***********************************************************************/ -{ - SYSTEMTIME stime; - TIME_ZONE_INFORMATION tzone; - - if (TIME_ZONE_ID_DAYLIGHT == GetTimeZoneInformation(&tzone)) - tzone.Bias += tzone.DaylightBias; - - FileTimeToSystemTime((FILETIME *)(&(file->file.time)), &stime); - Convert_Date(&stime, dat, -tzone.Bias); -} - - -/*********************************************************************** -** -*/ void *OS_Open_Library(REBCHR *path, REBCNT *error) -/* -** Load a DLL library and return the handle to it. -** If zero is returned, error indicates the reason. -** -***********************************************************************/ -{ - void *dll = LoadLibraryW(path); - *error = GetLastError(); - - return dll; -} - - -/*********************************************************************** -** -*/ void OS_Close_Library(void *dll) -/* -** Free a DLL library opened earlier. -** -***********************************************************************/ -{ - FreeLibrary((HINSTANCE)dll); -} - - -/*********************************************************************** -** -*/ void *OS_Find_Function(void *dll, char *funcname) -/* -** Get a DLL function address from its string name. -** -***********************************************************************/ -{ - void *fp = GetProcAddress((HMODULE)dll, funcname); - //DWORD err = GetLastError(); - - return fp; -} - - -/*********************************************************************** -** -*/ REBINT OS_Create_Thread(CFUNC init, void *arg, REBCNT stack_size) -/* -** Creates a new thread for a REBOL task datatype. -** -** NOTE: -** For this to work, the multithreaded library option is -** needed in the C/C++ code generation settings. -** -** The Task_Ready stops return until the new task has been -** initialized (to avoid unknown new thread state). -** -***********************************************************************/ -{ - REBINT thread; - - Task_Ready = CreateEvent(NULL, TRUE, FALSE, TEXT("REBOL_Task_Launch")); - if (!Task_Ready) return -1; - - thread = _beginthread(init, stack_size, arg); - - if (thread) WaitForSingleObject(Task_Ready, 2000); - CloseHandle(Task_Ready); - - return 1; -} - - -/*********************************************************************** -** -*/ void OS_Delete_Thread(void) -/* -** Can be called by a REBOL task to terminate its thread. -** -***********************************************************************/ -{ - _endthread(); -} - - -/*********************************************************************** -** -*/ void OS_Task_Ready(REBINT tid) -/* -** Used for new task startup to resume the thread that -** launched the new task. -** -***********************************************************************/ -{ - SetEvent(Task_Ready); -} - - -/*********************************************************************** -** -*/ int OS_Create_Process(REBCHR *call, u32 flags) -/* -** Return -1 on error. -** For right now, set flags to 1 for /wait. -** -***********************************************************************/ -{ - STARTUPINFO si; - PROCESS_INFORMATION pi; -// REBOOL is_NT; -// OSVERSIONINFO info; - REBINT result; - -// GetVersionEx(&info); -// is_NT = info.dwPlatformId >= VER_PLATFORM_WIN32_NT; - - si.cb = sizeof(si); - si.lpReserved = NULL; - si.lpDesktop = NULL; - si.lpTitle = NULL; - si.dwFlags = STARTF_USESHOWWINDOW; - si.dwFlags |= STARTF_USESTDHANDLES; - si.wShowWindow = SW_SHOWNORMAL; - si.cbReserved2 = 0; - si.lpReserved2 = NULL; - - si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); - si.hStdError = GetStdHandle(STD_ERROR_HANDLE); - - result = CreateProcess( - NULL, // Executable name - call, // Command to execute - NULL, // Process security attributes - NULL, // Thread security attributes - FALSE, // Inherit handles - NORMAL_PRIORITY_CLASS // Creation flags - | CREATE_DEFAULT_ERROR_MODE, - NULL, // Environment - NULL, // Current directory - &si, // Startup information - &pi // Process information - ); - - // Wait for termination: - if (result && (flags & 1)) { - result = 0; - WaitForSingleObject(pi.hProcess, INFINITE); // check result?? - GetExitCodeProcess(pi.hProcess, (PDWORD)&result); - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); - } - - return result; // meaning depends on flags -} - - -/*********************************************************************** -** -*/ int OS_Browse(REBCHR *url, int reserved) -/* -***********************************************************************/ -{ - #define MAX_BRW_PATH 2044 - long flag; - long len; - long type; - HKEY key; - REBCHR *path; - HWND hWnd = GetFocus(); - - if (RegOpenKeyEx(HKEY_CLASSES_ROOT, TEXT("http\\shell\\open\\command"), 0, KEY_READ, &key) != ERROR_SUCCESS) - return 0; - - if (!url) url = TEXT(""); - - path = MAKE_STR(MAX_BRW_PATH+4); - len = MAX_BRW_PATH; - - flag = RegQueryValueEx(key, TEXT(""), 0, &type, (LPBYTE)path, &len); - RegCloseKey(key); - if (flag != ERROR_SUCCESS) { - FREE_MEM(path); - return 0; - } - //if (ExpandEnvironmentStrings(&str[0], result, len)) - - Insert_Command_Arg(path, url, MAX_BRW_PATH); - - len = OS_Create_Process(path, 0); - - FREE_MEM(path); - return len; -} - - -/*********************************************************************** -** -*/ REBOOL OS_Request_File(REBRFR *fr) -/* -***********************************************************************/ -{ - OPENFILENAME ofn = {0}; - BOOL ret; - //int err; - REBCHR *filters = TEXT("All files\0*.*\0REBOL scripts\0*.r\0Text files\0*.txt\0" ); - - ofn.lStructSize = sizeof(ofn); - - // ofn.hwndOwner = WIN_WIN(win); // Must find a way to set this - - ofn.lpstrTitle = fr->title; - ofn.lpstrInitialDir = fr->dir; - ofn.lpstrFile = fr->files; - ofn.lpstrFilter = fr->filter ? fr->filter : filters; - ofn.nMaxFile = fr->len; - ofn.lpstrFileTitle = 0; - ofn.nMaxFileTitle = 0; - - ofn.Flags = OFN_HIDEREADONLY | OFN_EXPLORER | OFN_NOCHANGEDIR; //|OFN_NONETWORKBUTTON; //; - - if (GET_FLAG(fr->flags, FRF_MULTI)) ofn.Flags |= OFN_ALLOWMULTISELECT; - - if (GET_FLAG(fr->flags, FRF_SAVE)) - ret = GetSaveFileName(&ofn); - else - ret = GetOpenFileName(&ofn); - - //if (!ret) - // err = CommDlgExtendedError(); // CDERR_FINDRESFAILURE - - return ret; -} - - -/*********************************************************************** -** -*/ REBSER *OS_GOB_To_Image(REBGOB *gob) -/* -** Render a GOB into an image. Returns an image or zero if -** it cannot be done. -** -***********************************************************************/ -{ - -#ifndef REB_CORE - -#ifndef NO_COMPOSITOR - return (REBSER*)Gob_To_Image(gob); -#else - return 0; -#endif - -#else - return 0; -#endif - -} diff --git a/src/os/win32/host-text.c b/src/os/win32/host-text.c deleted file mode 100644 index 7f64e522e6..0000000000 --- a/src/os/win32/host-text.c +++ /dev/null @@ -1,431 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Rich-Text Dialect Backend -** Author: Cyphre, Carl -** Purpose: Evaluates rich-text commands; calls graphics functions. -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ - -#include -#include - -#include "reb-host.h" -///#include "reb-series.h" -///#include "reb-gob.h" -#include "host-lib.h" -#include "reb-types.h" -#include "reb-value.h" -#include "reb-dialect.h" -#include "words-text.h" -#include "words-graphics.h" -#include "rebol-lib.h" - -#define ENABLE_DRAW -#include "agg-text.h" - -//#define AGGC ((agg_graphics*)context) - -#define ARG_LOGIC(n) VAL_LOGIC(arg+n) -#define ARG_INTEGER(n) VAL_INT32(arg+n) -#define ARG_STRING(n) VAL_STRING(arg+n) -#define ARG_PAIR(n) VAL_PAIR(arg+n) -#define ARG_DECIMAL(n) VAL_DECIMAL(arg+n) -#define ARG_TUPLE(n) VAL_TUPLE(arg+n) -#define ARG_WORD(n) VAL_WORD(arg+n) - -#define ARG_OBJECT(n) VAL_SERIES(arg+n) // temp -#define ARG_IMAGE(n) VAL_SERIES(arg+n) // temp -#define ARG_BLOCK(n) VAL_BLOCK(arg+n) - -#define ARG_WORDS(n,s,e) ((ARG_WORD(n)>=s && ARG_WORD(n)<=e) ? ARG_WORD(n)-s : 0) -#define ARG_OPT_LOGIC(n) (IS_LOGIC(arg+n)?VAL_LOGIC(arg+n):TRUE) -#define ARG_OPT_INTEGER(n) (IS_INTEGER(arg+n)?VAL_INT32(arg+n): 1) - -void set_font_styles(REBFNT* font, REBVAL* val){ - REBINT result = Reb_Find_Word(VAL_WORD_SYM(val), Symbol_Ids, 0); - switch (result){ - case SW_BOLD: - font->bold = TRUE; - break; - case SW_ITALIC: - font->italic = TRUE; - break; - case SW_UNDERLINE: - font->underline = TRUE; - break; - } -} - -/*********************************************************************** -** -*/ REBINT Text_Gob(void *richtext, REBSER *block) -/* -** Handles all commands for the TEXT dialect as specified -** in the system/dialects/text object. -** -** This function calls the REBOL_Dialect interpreter to -** parse the dialect and build and return the command number -** (the index offset in the text object above) and a block -** of arguments. (For now, just a REBOL block, but this could -** be changed to isolate it from changes in REBOL's internals). -** -** Each arg will be of the specified datatype (given in the -** dialect) or NONE when no argument of that type was given -** and this code must determine the proper default value. -** -** If the cmd result is zero, then it is either the end of -** the block, or an error has occurred. If the error value -** is non-zero, then it was an error. -** -***********************************************************************/ -{ - REBCNT index = 0; - REBINT cmd; - REBSER *args = 0; - REBVAL *arg; - REBCNT nargs; - - //font object conversion related values - REBFNT* font; - REBVAL* val; - REBPAR offset; - REBPAR space; - - //para object conversion related values - REBPRA* para; - REBPAR origin; - REBPAR margin; - REBPAR indent; - REBPAR scroll; - - do { - cmd = Reb_Dialect(DIALECTS_TEXT, block, &index, &args); - - if (cmd == 0) return 0; - if (cmd < 0) { -// Reb_Print("ERROR: %d, Index %d", -cmd, index); - return -((REBINT)index+1); - } -// else -// Reb_Print("TEXT: Cmd %d, Index %d, Args %m", cmd, index, args); - - arg = BLK_HEAD(args); - nargs = SERIES_TAIL(args); -// Reb_Print("Number of args: %d", nargs); - - switch (cmd) { - - case TW_TYPE_SPEC: - - if (IS_STRING(arg)) { - rt_text(richtext, ARG_STRING(0), index); - } else if (IS_TUPLE(arg)) { - rt_color(richtext, ARG_TUPLE(0)); - } - break; - case TW_ANTI_ALIAS: - rt_anti_alias(richtext, ARG_OPT_LOGIC(0)); - break; - - case TW_SCROLL: - rt_scroll(richtext, ARG_PAIR(0)); - break; - - case TW_BOLD: - case TW_B: - rt_bold(richtext, ARG_OPT_LOGIC(0)); - break; - - case TW_ITALIC: - case TW_I: - rt_italic(richtext, ARG_OPT_LOGIC(0)); - break; - - case TW_UNDERLINE: - case TW_U: - rt_underline(richtext, ARG_OPT_LOGIC(0)); - break; - case TW_CENTER: - rt_center(richtext); - break; - case TW_LEFT: - rt_left(richtext); - break; - case TW_RIGHT: - rt_right(richtext); - break; - case TW_FONT: - - if (!IS_OBJECT(arg)) break; - - font = (REBFNT*)rt_get_font(richtext); - - val = BLK_HEAD(ARG_OBJECT(0))+1; - - if (IS_STRING(val)) { - font->name = VAL_STRING(val); - } - -// Reb_Print("font/name: %s", font->name); - - val++; - - if (IS_BLOCK(val)) { - REBSER* styles = VAL_SERIES(val); - REBVAL* slot = BLK_HEAD(styles); - REBCNT len = SERIES_TAIL(styles) ,i; - - for (i = 0;isize = VAL_INT32(val); - } - -// Reb_Print("font/size: %d", font->size); - - val++; - if ((IS_TUPLE(val)) || (IS_NONE(val))) { - COPY_MEM(font->color,VAL_TUPLE(val), 4); - } - -// Reb_Print("font/color: %d.%d.%d.%d", font->color[0],font->color[1],font->color[2],font->color[3]); - - val++; - if ((IS_PAIR(val)) || (IS_NONE(val))) { - offset = VAL_PAIR(val); - font->offset_x = offset.x; - font->offset_y = offset.y; - } - -// Reb_Print("font/offset: %dx%d", offset.x,offset.y); - - val++; - if ((IS_PAIR(val)) || (IS_NONE(val))) { - space = VAL_PAIR(val); - font->space_x = space.x; - font->space_y = space.y; - } - -// Reb_Print("font/space: %dx%d", space.x, space.y); - - - val++; - - font->shadow_x = 0; - font->shadow_y = 0; - - if (IS_BLOCK(val)) { - REBSER* ser = VAL_SERIES(val); - REBVAL* slot = BLK_HEAD(ser); - REBCNT len = SERIES_TAIL(ser) ,i; - - for (i = 0;ishadow_x = shadow.x; - font->shadow_y = shadow.y; - } else if (IS_TUPLE(slot)) { - COPY_MEM(font->shadow_color,VAL_TUPLE(slot), 4); - } else if (IS_INTEGER(slot)) { - font->shadow_blur = VAL_INT32(slot); - } - slot++; - } - } else if (IS_PAIR(val)) { - REBPAR shadow = VAL_PAIR(val); - font->shadow_x = shadow.x; - font->shadow_y = shadow.y; - } - - rt_font(richtext, font); - break; - - case TW_PARA: - if (!IS_OBJECT(arg)) break; - - para = (REBPRA*)rt_get_para(richtext); - - val = BLK_HEAD(ARG_OBJECT(0))+1; - - - if (IS_PAIR(val)) { - origin = VAL_PAIR(val); - para->origin_x = origin.x; - para->origin_y = origin.y; - } - -// Reb_Print("para/origin: %dx%d", origin.x, origin.y); - - val++; - if (IS_PAIR(val)) { - margin = VAL_PAIR(val); - para->margin_x = margin.x; - para->margin_y = margin.y; - } - -// Reb_Print("para/margin: %dx%d", margin.x, margin.y); - - val++; - if (IS_PAIR(val)) { - indent = VAL_PAIR(val); - para->indent_x = indent.x; - para->indent_y = indent.y; - } - -// Reb_Print("para/indent: %dx%d", indent.x, indent.y); - - val++; - if (IS_INTEGER(val)) { - para->tabs = VAL_INT32(val); - } - -// Reb_Print("para/tabs: %d", para->tabs); - - val++; - if (IS_LOGIC(val)) { - para->wrap = VAL_LOGIC(val); - } - -// Reb_Print("para/wrap?: %d", para->wrap); - - val++; - if (IS_PAIR(val)) { - scroll = VAL_PAIR(val); - para->scroll_x = scroll.x; - para->scroll_y = scroll.y; - } -// Reb_Print("para/scroll: %dx%d", scroll.x, scroll.y); - - val++; - - if (IS_WORD(val)) { - REBINT result = Reb_Find_Word(VAL_WORD_SYM(val), Symbol_Ids, 0); - switch (result){ - case SW_RIGHT: - case SW_LEFT: - case SW_CENTER: - para->align = result; - break; - default: - para->align = SW_LEFT; - break; - } - - } - - val++; - - if (IS_WORD(val)) { - REBINT result = Reb_Find_Word(VAL_WORD_SYM(val), Symbol_Ids, 0); - switch (result){ - case SW_TOP: - case SW_BOTTOM: - case SW_MIDDLE: - para->valign = result; - break; - default: - para->valign = SW_TOP; - break; - } - } - - rt_para(richtext, para); - break; - - case TW_SIZE: - rt_font_size(richtext, ARG_INTEGER(0)); - break; - - case TW_SHADOW: - rt_shadow(richtext, &ARG_PAIR(0), ARG_TUPLE(1), ARG_INTEGER(2)); - break; - - case TW_DROP: - rt_drop(richtext, ARG_OPT_INTEGER(0)); - break; - - case TW_NEWLINE: - case TW_NL: - rt_newline(richtext, index); - break; - case TW_CARET: - { - REBPAR caret = {0,0}; - REBPAR highlightStart = {0,0}; - REBPAR highlightEnd = {0,0}; - REBVAL *slot; - if (!IS_OBJECT(arg)) break; - - val = BLK_HEAD(ARG_OBJECT(0))+1; - if (IS_BLOCK(val)) { - slot = BLK_HEAD(VAL_SERIES(val)); - if (SERIES_TAIL(VAL_SERIES(val)) == 2 && IS_BLOCK(slot) && IS_STRING(slot+1)){ - caret.x = 1 + slot->data.series.index; - caret.y = 1 + (slot+1)->data.series.index;; - //Reb_Print("caret %d, %d", caret.x, caret.y); - } - } - val++; - if (IS_BLOCK(val)) { - slot = BLK_HEAD(VAL_SERIES(val)); - if (SERIES_TAIL(VAL_SERIES(val)) == 2 && IS_BLOCK(slot) && IS_STRING(slot+1)){ - highlightStart.x = 1 + slot->data.series.index; - highlightStart.y = 1 + (slot+1)->data.series.index;; - //Reb_Print("highlight-start %d, %d", highlightStart.x, highlightStart.y); - } - } - val++; - if (IS_BLOCK(val)) { - slot = BLK_HEAD(VAL_SERIES(val)); - if (SERIES_TAIL(VAL_SERIES(val)) == 2 && IS_BLOCK(slot) && IS_STRING(slot+1)){ - highlightEnd.x = 1 + slot->data.series.index; - highlightEnd.y = 1 + (slot+1)->data.series.index;; - //Reb_Print("highlight-End %d, %d", highlightEnd.x, highlightEnd.y); - } - } - - rt_caret(richtext, &caret, &highlightStart,&highlightEnd); - } - break; - } - } while (TRUE); -} - diff --git a/src/os/win32/host-window.c b/src/os/win32/host-window.c deleted file mode 100644 index cd6219b863..0000000000 --- a/src/os/win32/host-window.c +++ /dev/null @@ -1,839 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** Title: Windowing Interface for Win32 -** Author: Carl Sassenrath -** Purpose: Provides the API used by REBOL for window management. -** Related: host-event.c, host-graphics.c -** -************************************************************************ -** -** NOTE to PROGRAMMERS: -** -** 1. Keep code clear and simple. -** 2. Document unusual code, reasoning, or gotchas. -** 3. Use same style for code, vars, indent(4), comments, etc. -** 4. Keep in mind Linux, OS X, BSD, big/little endian CPUs. -** 5. Test everything, then test it again. -** -***********************************************************************/ - -#ifndef WINVER -#define WINVER 0x0501 // this is needed to be able use WINDOWINFO struct etc. -#endif - -#include -#include - -#include "reb-host.h" -#include "host-lib.h" - -#ifndef NO_COMPOSITOR -#include "agg-compositor.h" -#endif - -//***** Constants ***** - -#define MAX_WINDOWS 64 -#define GOB_HWIN(gob) (Find_Window(gob)) -#define GOB_COMPOSITOR(gob) (Find_Compositor(gob)) //gets handle to window's compositor - -struct gob_window {REBGOB *gob; HWND win; void* compositor;}; // Maps gob to window - -//***** Externs ***** - -extern HINSTANCE App_Instance; // Set by winmain function -extern void Host_Crash(char *reason); -extern LRESULT CALLBACK REBOL_Window_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); -extern MSG Handle_Messages(); - -#ifdef TEMP_REMOVED -extern void* Create_RichText(); -extern void* Create_Effects(); -#endif - -//***** Locals ***** - -static BOOL Registered = FALSE; // Window has been registered -static const REBCHR *Window_Class_Name = TXT("REBOLWindow"); -static struct gob_window *Gob_Windows; - -REBGOB *Gob_Root; // Top level GOB (the screen) -HCURSOR Cursor; // active cursor image object -REBPAR Zero_Pair = {0, 0}; -//void* Effects; - - -/*********************************************************************** -** -*/ REBOOL As_OS_Str(REBSER *series, REBCHR **string) -/* -** If necessary, convert a string series to Win32 wide-chars. -** (Handy for GOB/TEXT handling). -** If the string series is empty the resulting string is set to NULL -** -** Function returns: -** TRUE - if the resulting string needs to be deallocated by the caller code -** FALSE - if REBOL string is used (no dealloc needed) -** -** Note: REBOL strings are allowed to contain nulls. -** -***********************************************************************/ -{ - int len, n; - void *str; - wchar_t *wstr; - - if ((len = RL_Get_String(series, 0, &str)) < 0) { - // Latin1 byte string - convert to wide chars - len = -len; - wstr = OS_Make((len+1) * sizeof(wchar_t)); - for (n = 0; n < len; n++) - wstr[n] = (wchar_t)((char*)str)[n]; - wstr[len] = 0; - //note: following string needs be deallocated in the code that uses this function - *string = (REBCHR*)wstr; - return TRUE; - } - *string = (len == 0) ? NULL : str; //empty string check - return FALSE; -} - - -/*********************************************************************** -** -*/ void Init_Windows(void) -/* -** Initialize special variables of the graphics subsystem. -** -***********************************************************************/ -{ - Gob_Windows = OS_Make(sizeof(struct gob_window) * (MAX_WINDOWS+1)); - CLEAR(Gob_Windows, sizeof(struct gob_window) * (MAX_WINDOWS+1)); - - Cursor = LoadCursor(NULL, IDC_ARROW); -} - - -/********************************************************************** -** -** Window Allocator -** -** The window handle is not stored in the gob to avoid wasting -** memory or creating too many exceptions in the gob.parent field. -** Instead, we store gob and window pointers in an array that we -** scan when required. -** -** This code below is not optimial, but works ok because: -** 1) there are usually very few windows open -** 2) window functions are not called often -** 2) window events are mapped directly to gobs -** -**********************************************************************/ - -static REBINT Alloc_Window(REBGOB *gob) { - int n; - for (n = 0; n < MAX_WINDOWS; n++) { - if (Gob_Windows[n].gob == 0) { - Gob_Windows[n].gob = gob; -#ifndef NO_COMPOSITOR - Gob_Windows[n].compositor = Create_Compositor(Gob_Root, gob); -// Reb_Print("Create_Compositor %d", Gob_Windows[n].compositor); -#endif - return n; - } - } - return -1; -} - -static HWND Find_Window(REBGOB *gob) { - int n; - for (n = 0; n < MAX_WINDOWS; n++) { - if (Gob_Windows[n].gob == gob) return Gob_Windows[n].win; - } - return 0; -} - -static HWND Find_Compositor(REBGOB *gob) { - int n; - for (n = 0; n < MAX_WINDOWS; n++) { - if (Gob_Windows[n].gob == gob) return Gob_Windows[n].compositor; - } - return 0; -} - -static void Free_Window(REBGOB *gob) { - int n; - for (n = 0; n < MAX_WINDOWS; n++) { - if (Gob_Windows[n].gob == gob) { -#ifndef NO_COMPOSITOR - Destroy_Compositor(Gob_Windows[n].compositor); -// Reb_Print("Destroy_Compositor %d", Gob_Windows[n].compositor); -#endif - Gob_Windows[n].gob = 0; - return; - } - } -} - - -/*********************************************************************** -** -*/ static void Register_Window() -/* -** Register the window class. -** -** Note: Executed in OS_Init code. -** -***********************************************************************/ -{ - WNDCLASSEX wc; - - wc.cbSize = sizeof(wc); - wc.lpszClassName = Window_Class_Name; - wc.hInstance = App_Instance; - wc.lpfnWndProc = REBOL_Window_Proc; - - wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = NULL; - wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; - - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.lpszMenuName = NULL; - - wc.hIconSm = LoadImage(App_Instance, // small class icon - MAKEINTRESOURCE(5), - IMAGE_ICON, - GetSystemMetrics(SM_CXSMICON), - GetSystemMetrics(SM_CYSMICON), - LR_DEFAULTCOLOR - ); - - // If not already registered: - //if (!GetClassInfo(App_Instance, Window_Class_Name, &wclass)) - // RegisterClass(&wclass); - - if (!RegisterClassEx(&wc)) Host_Crash("Cannot register window"); - - Registered = TRUE; -} - - -/*********************************************************************** -** -*/ BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) -/* -** Callback function which enables/disables events in windows -** specified by lParam -** -** This function is used by Win API EnumWindows() call. -** -***********************************************************************/ -{ - if (GetParent(hwnd) == (HWND)lParam){ - if (IsWindowEnabled(hwnd)){ - EnableWindow(hwnd, FALSE); - } else { - EnableWindow(hwnd, TRUE); - } - } - - return TRUE; -} - - -/*********************************************************************** -** -*/ HWND Open_Window(REBGOB *gob) -/* -** Initialize the graphics window. -** -** Note: This function is used by embedded windows as well. -** Such as those inserted into web browser output. -** -** The window handle is returned, but not expected to be used -** other than for debugging conditions. -** -***********************************************************************/ -{ - REBINT options; - REBINT windex; - HWND window; - REBCHR *title; - int x, y, w, h; - HWND parent = NULL; - REBYTE osString = FALSE; - REBPAR metric; - - if (!Registered) Register_Window(); - - windex = Alloc_Window(gob); - if (windex < 0) Host_Crash("Too many windows"); - - CLEAR_GOB_STATE(gob); - x = GOB_X_INT(gob); - y = GOB_Y_INT(gob); - w = GOB_W_INT(gob); - h = GOB_H_INT(gob); - - SET_GOB_STATE(gob, GOBS_NEW); - - // Setup window options: - - options = WS_POPUP; - - if (!GET_FLAGS(gob->flags, GOBF_NO_TITLE, GOBF_NO_BORDER)) { - metric.y = GetSystemMetrics(SM_CYCAPTION); - options |= WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU; - h += metric.y; - y -= metric.y; - } - - if (GET_GOB_FLAG(gob, GOBF_RESIZE)) { - metric.x = GetSystemMetrics(SM_CXSIZEFRAME); - metric.y = GetSystemMetrics(SM_CYSIZEFRAME); - options |= WS_SIZEBOX | WS_BORDER; - x -= metric.x; - y -= metric.y; - w += metric.x * 2; - h += metric.y * 2; - if (!GET_GOB_FLAG(gob, GOBF_NO_TITLE)) - options |= WS_MAXIMIZEBOX; - } - else if (!GET_GOB_FLAG(gob, GOBF_NO_BORDER)) { - metric.x = GetSystemMetrics(SM_CXFIXEDFRAME); - metric.y = GetSystemMetrics(SM_CYFIXEDFRAME); - options |= WS_BORDER; - if (!GET_GOB_FLAG(gob, GOBF_NO_TITLE)){ - x -= metric.x; - y -= metric.y; - w += metric.x * 2; - h += metric.y * 2; - } - } - - if (IS_GOB_STRING(gob)) - osString = As_OS_Str(GOB_CONTENT(gob), (REBCHR**)&title); - else - title = TXT("REBOL Window"); - - if (GET_GOB_FLAG(gob, GOBF_POPUP)) { - parent = GOB_HWIN(GOB_TMP_OWNER(gob)); - if (GET_GOB_FLAG(gob, GOBF_MODAL)) { - EnableWindow(parent, FALSE); - EnumWindows(EnumWindowsProc, (LPARAM)parent); - } - } - - // Create the window: - window = CreateWindowEx( - WS_EX_WINDOWEDGE, - Window_Class_Name, - title, - options, - x, y, w, h, - parent, - NULL, App_Instance, NULL - ); - - //don't let the string leak! - if (osString) OS_Free(title); - if (!window) Host_Crash("CreateWindow failed"); - - // Enable drag and drop - if (GET_GOB_FLAG(gob, GOBF_DROPABLE)) - DragAcceptFiles(window, TRUE); - - Gob_Windows[windex].win = window; - SET_GOB_FLAG(gob, GOBF_WINDOW); - SET_GOB_STATE(gob, GOBS_OPEN); - - // Provide pointer from window back to REBOL window: - SetWindowLong(window, GWL_USERDATA, (long)gob); - - if (!GET_GOB_FLAG(gob, GOBF_HIDDEN)) { - if (GET_GOB_FLAG(gob, GOBF_ON_TOP)) SetWindowPos(window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); - ShowWindow(window, SW_SHOWNORMAL); - SetForegroundWindow(window); - } - - return window; -} - -/* Removed from above code -- is any of this really needed? -CS - - // Set rectangle coordinates: - rect.left = GOB_X(gob); - rect.right = rect.left + GOB_W(gob); - rect.top = GOB_Y(gob); - rect.bottom = rect.top + GOB_H(gob); - AdjustWindowRect(&rect, options, FALSE); - - // Create window (use parent if specified): - GOB_WIN(gob) = CreateWindow(Window_Class_Name, title, options, - rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, - (wparent ? GOB_WIN(wparent) : NULL), NULL, App_Instance, NULL); - - // Drain startup messages: - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } -*/ - - -/*********************************************************************** -** -*/ void Close_Window(REBGOB *gob) -/* -** Close the window. -** -***********************************************************************/ -{ - HWND parent = NULL; - if (GET_GOB_FLAG(gob, GOBF_WINDOW) && Find_Window(gob)) { - if (GET_GOB_FLAG(gob, GOBF_MODAL)) { - parent = GetParent(GOB_HWIN(gob)); - if (parent) { - EnableWindow(parent, TRUE); - EnumWindows(EnumWindowsProc, (LPARAM)parent); - } - } - DestroyWindow(GOB_HWIN(gob)); - CLR_GOB_FLAG(gob, GOBF_WINDOW); - CLEAR_GOB_STATE(gob); // set here or in the destory? - Free_Window(gob); - } -} - - -/*********************************************************************** -** -*/ REBOOL Resize_Window(REBGOB *gob, REBOOL redraw) -/* -** Update window parameters. -** -***********************************************************************/ -{ -#ifndef NO_COMPOSITOR - void *compositor; - REBOOL changed; - compositor = GOB_COMPOSITOR(gob); - changed = Resize_Window_Buffer(compositor, gob); - if (redraw) Compose_Gob(compositor, gob, gob); - return changed; -#else - REBINT Draw_Window(REBGOB *wingob, REBGOB *gob); - Draw_Window(gob, gob); - return TRUE; -#endif -} - - -/*********************************************************************** -** -*/ void Update_Window(REBGOB *gob) -/* -** Update window parameters. -** -***********************************************************************/ -{ - RECT r; - REBCNT opts = 0; - HWND window; - WINDOWINFO wi; - REBCHR *title; - REBYTE osString = FALSE; - - wi.cbSize = sizeof(WINDOWINFO); - - if (!IS_WINDOW(gob)) return; - - window = GOB_HWIN(gob); - - if (GOB_X(gob) == GOB_XO(gob) && GOB_Y(gob) == GOB_YO(gob)) - opts |= SWP_NOMOVE; - - if (GOB_W(gob) == GOB_WO(gob) && GOB_H(gob) == GOB_HO(gob)) - opts |= SWP_NOSIZE; - else - //Resize window and/or buffer in case win size changed programatically - Resize_Window(gob, FALSE); - - //Get the new window size together with borders, tilebar etc. - GetWindowInfo(window, &wi); - r.left = GOB_X_INT(gob); - r.right = r.left + GOB_W_INT(gob); - r.top = GOB_Y_INT(gob); - r.bottom = r.top + GOB_H_INT(gob); - AdjustWindowRect(&r, wi.dwStyle, FALSE); - - //Set the new size - SetWindowPos(window, 0, r.left, r.top, r.right - r.left, r.bottom - r.top, opts | SWP_NOZORDER); - - //if (opts) -// SetWindowPos(window, 0, GOB_X(gob), GOB_Y(gob), GOB_W(gob), GOB_H(gob), opts | SWP_NOZORDER); - - if (IS_GOB_STRING(gob)){ - osString = As_OS_Str(GOB_CONTENT(gob), (REBCHR**)&title); - SetWindowText(window, title); - //don't let the string leak! - if (osString) OS_Free(title); - } - - /* - switch (arg) { - case 0: arg = SW_MINIMIZE; break; - case 1: arg = SW_RESTORE; break; - case 2: arg = SW_MAXIMIZE; break; - } - - ShowWindow(window, arg); - - SetForegroundWindow(window); - */ -} - - -/*********************************************************************** -** -*/ void Blit_Rect(REBGOB *gob, REBPAR d, REBPAR dsize, REBYTE *src, REBPAR s, REBPAR ssize) -/* -** This routine copies a rectangle from a PAN structure to the -** current output device. -** -***********************************************************************/ -{ - HDC hdc; - BITMAPINFO BitmapInfo; - REBINT mode; - - if (!IS_WINDOW(gob)) return; - - hdc = GetDC(GOB_HWIN(gob)); - - mode = SetStretchBltMode(hdc, COLORONCOLOR); - BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); -#ifdef NO_COMPOSITOR - BitmapInfo.bmiHeader.biWidth = ssize.x; - BitmapInfo.bmiHeader.biHeight = -(REBINT)dsize.y; -#else - BitmapInfo.bmiHeader.biWidth = ROUND_TO_INT(gob->size.x); - BitmapInfo.bmiHeader.biHeight = -ROUND_TO_INT(gob->size.y); -#endif - BitmapInfo.bmiHeader.biPlanes = 1; - BitmapInfo.bmiHeader.biBitCount = 32; - BitmapInfo.bmiHeader.biCompression = BI_RGB; - BitmapInfo.bmiHeader.biSizeImage = 0; - BitmapInfo.bmiHeader.biXPelsPerMeter = 1; - BitmapInfo.bmiHeader.biYPelsPerMeter = 1; - BitmapInfo.bmiHeader.biClrUsed = 0; - BitmapInfo.bmiHeader.biClrImportant = 0; - -// StretchDIBits(hdc, d.x, d.y, dsize.x, dsize.y, s.x, s.y, ssize.x, ssize.y, src, &BitmapInfo, DIB_PAL_COLORS, SRCCOPY); - - //we need little transformation to get rid of StretchDIBits() quirk when src.x and src.y = 0 - StretchDIBits(hdc, - d.x, d.y + dsize.y - 1, dsize.x, -dsize.y, - s.x, s.y + ssize.y + 1, ssize.x, -ssize.y, - src, &BitmapInfo, DIB_PAL_COLORS, SRCCOPY); - -// Reb_Print("blit: %dx%d %dx%d %dx%d %dx%d" ,d.x, d.y + dsize.y - 1, dsize.x, -dsize.y,s.x, ssize.y + s.y + 1, ssize.x, -ssize.y); - - SetStretchBltMode(hdc, mode); - - ReleaseDC(GOB_HWIN(gob), hdc); -} - - -/*********************************************************************** -** -*/ void Blit_Color(REBGOB *gob, REBPAR d, REBPAR dsize, long color) -/* -** Fill color rectangle, a pixel at a time. -** -***********************************************************************/ -{ - HDC hdc; - long clr; - RECT rect; - - if (!IS_WINDOW(gob)) return; - - clr = ((color >> 16) & 255) | ((color & 255) << 16) | (color & 255<<8); - - hdc = GetDC(GOB_HWIN(gob)); - - rect.left = d.x; - rect.top = d.y; - rect.right = dsize.x+d.x; // see note on FillRect - rect.bottom = dsize.y+d.y; - - //Reb_Print("rect: %dx%d %dx%d", rect.left, rect.top, rect.right, rect.bottom); - - FillRect(hdc, &rect, CreateSolidBrush(clr)); // excludes bottom & right borders - ReleaseDC(GOB_HWIN(gob), hdc); -} - - -/*********************************************************************** -** -*/ REBINT Draw_Window(REBGOB *wingob, REBGOB *gob) -/* -** Refresh the GOB within the given window. If the wingob -** is zero, then find the correct window for it. -** -***********************************************************************/ -{ - REBINT len; - -#ifdef NO_COMPOSITOR - REBINT n; - REBGOB **gp; - //static int nnn = 0; -#else - void *compositor; -#endif - - if (!wingob) { - wingob = gob; - while (GOB_PARENT(wingob) && GOB_PARENT(wingob) != Gob_Root - && GOB_PARENT(wingob) != wingob) // avoid infinite loop - wingob = GOB_PARENT(wingob); - - //check if it is really open - if (!IS_WINDOW(wingob) || !GET_GOB_STATE(wingob, GOBS_OPEN)) return 0; - } - -// Reb_Print("draw: %d %8x", nnn++, gob); - -#ifdef NO_COMPOSITOR - // Blit the current gob: - if (IS_GOB_IMAGE(gob)) { - Blit_Rect(wingob, gob->offset, gob->size, GOB_BITMAP(gob), Zero_Pair, gob->size); - } - else { //if (IS_GOB_COLOR(gob)) - Blit_Color(wingob, gob->offset, gob->size, (long)GOB_CONTENT(gob)); - } - - // Blit the children: - if (GOB_PANE(gob)) { - len = GOB_TAIL(gob); - gp = GOB_HEAD(gob); - for (n = 0; n < len; n++, gp++) - Draw_Window(wingob, *gp); - } - return 0; -#else - //render and blit the GOB - compositor = GOB_COMPOSITOR(wingob); - len = Compose_Gob(compositor, wingob, gob); - return len; -#endif -} - - -/*********************************************************************** -** -*/ void Paint_Window(HWND window) -/* -** Repaint the window by redrawing all the gobs. -** It just blits the whole window buffer. -** -***********************************************************************/ -{ - PAINTSTRUCT ps; - REBGOB *gob; - REBPAR size; - - gob = (REBGOB *)GetWindowLong(window, GWL_USERDATA); - - if (gob) { - - BeginPaint(window, (LPPAINTSTRUCT) &ps); - -#ifdef NO_COMPOSITOR - Draw_Window(gob, gob); -#else - size.x = ROUND_TO_INT(gob->size.x); - size.y = ROUND_TO_INT(gob->size.y); - Blit_Rect(gob, Zero_Pair, size, Get_Window_Buffer(GOB_COMPOSITOR(gob)), Zero_Pair, size); -#endif - - EndPaint(window, (LPPAINTSTRUCT) &ps); - } -} - - -/*********************************************************************** -** -*/ REBINT Show_Gob(REBGOB *gob) -/* -** Notes: -** 1. Can be called with NONE (0), Gob_Root (All), or a -** specific gob to open, close, or refresh. -** -** 2. A new window will be in Gob_Root/pane but will not -** have GOBF_WINDOW set. -** -** 3. A closed window will have no PARENT and will not be -** in the Gob_Root/pane but will have GOBF_WINDOW set. -** -***********************************************************************/ -{ - REBINT n; - REBGOB *g; - REBGOB **gp; - - if (!gob) return 0; - - // Are we asked to open/close/refresh all windows? - if (gob == Gob_Root) { // show none, and show screen-gob - - // Remove any closed windows: - for (n = 0; n < MAX_WINDOWS; n++) { - if (g = Gob_Windows[n].gob) { - if (!GOB_PARENT(g) && GET_GOB_FLAG(g, GOBF_WINDOW)) - Close_Window(g); - } - } - - // Open any new windows: - if (GOB_PANE(Gob_Root)) { - gp = GOB_HEAD(Gob_Root); - for (n = GOB_TAIL(Gob_Root)-1; n >= 0; n--, gp++) { - if (!GET_GOB_FLAG(*gp, GOBF_WINDOW)) - Open_Window(*gp); - Draw_Window(0, *gp); - } - } - return 0; - } - // Is it a window gob that needs to be closed? - else if (!GOB_PARENT(gob) && GET_GOB_FLAG(gob, GOBF_WINDOW)) { - Close_Window(gob); - return 0; - } - // Is it a window gob that needs to be opened or refreshed? - else if (GOB_PARENT(gob) == Gob_Root) { - if (!GET_GOB_FLAG(gob, GOBF_WINDOW)) - Open_Window(gob); - else - Update_Window(gob); // Problem! We may not want this all the time. - } - - // Otherwise, composite and referesh the gob or all gobs: - return Draw_Window(0, gob); // 0 = window parent of gob -} - - - -#ifdef NOT_USED_BUT_MAYBE_LATER - -HWND Main_Event_Window; -REBGOB Main_Window_GOB; - -/*********************************************************************** -** -xx void Init_Event_Window(void) -/* -** We need to do this in order to get certain events even -** if there is no window is open (async DSN for example). -** -***********************************************************************/ -{ - if (Main_Event_Window) return; - - SET_GOB_FLAG(&Main_Window_GOB, GOBF_HIDDEN); - - Main_Event_Window = Open_Window(&Main_Window_GOB); -} - - -/*********************************************************************** -** -xx static REBINT Find_Gob(REBGOB *gob, REBGOB *target) -/* -** Find a target GOB within the pane of another gob. -** Return the index, or a -1 if not found. -** -***********************************************************************/ -{ - REBINT len; - REBINT n; - REBGOB **ptr; - - len = GOB_TAIL(gob); - ptr = GOB_HEAD(gob); - for (n = 0; n < len; n++, ptr++) - if (*ptr == target) return n; - - return -1; -} - - -/*********************************************************************** -** -xx void OS_Get_Window_Size(REBGOB *gob, REBINT *w, REBINT *h) -/* -** Get the window size. -** -***********************************************************************/ -{ - RECT r; - - if (!IS_WINDOW(gob)) return; - - GetClientRect(GOB_HWIN(gob), &r); - *w = r.right; - *h = r.bottom; -} - - -/*********************************************************************** -** -xx void OS_Scroll_Bits(REBGOB *gob, REBINT x, REBINT y, REBINT w, REBINT h, REBINT dx, REBINT dy) -/* -***********************************************************************/ -{ - HDC hdc; - RECT r={x,y,x+w,y+h}; - HRGN updatergn,oldrgn,newrgn; - - if (!IS_WINDOW(gob)) return; - - updatergn = CreateRectRgn(0,0,0,0); - hdc = GetDC(GOB_HWIN(gob)); - ScrollDC(hdc,dx,dy,&r,0,updatergn,0); - ReleaseDC(GOB_HWIN(gob),hdc); - oldrgn = CreateRectRgn(x,y,x+w,y+h); - newrgn = CreateRectRgn(x+dx,y+dy,x+w+dx,y+h+dy); - CombineRgn(oldrgn,oldrgn,newrgn,RGN_DIFF); - if (NULLREGION != CombineRgn(updatergn,updatergn,oldrgn,RGN_DIFF)) - InvalidateRgn(GOB_HWIN(gob),updatergn,FALSE); - DeleteObject(newrgn); - DeleteObject(oldrgn); - DeleteObject(updatergn); -} - -#endif diff --git a/src/os/win32/rpic-test.c b/src/os/win32/rpic-test.c deleted file mode 100644 index 274ee4270e..0000000000 --- a/src/os/win32/rpic-test.c +++ /dev/null @@ -1,99 +0,0 @@ -/*********************************************************************** -** -** REBOL [R3] Language Interpreter and Run-time Environment -** -** Copyright 2012 REBOL Technologies -** REBOL is a trademark of REBOL Technologies -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -************************************************************************ -** -** rpic-test.c - Test for REBOL Plug-In Component -** -***********************************************************************/ - -//#include - -#define ODD_INT_64 - -#include "reb-c.h" -#include "reb-plugin.h" -#include "plugin-types.h" - -#define REBSER void -#include "rpi-lib.h" - -const char *init_block = - "REBOL [\n" - "Title: {Example plugin}\n" - "Name: example\n" - "Type: plugin\n" - "Exports: [map-words cmd1 cmd2 cmd2i cmd2d cmdw cmds cmdb cmdbl]\n" - "]\n" - "map-words: command [words [block!]]\n" - "cmd1: command [a]\n" - "cmd2: command [a b]\n" - "cmd2i: command [a [integer!] b [integer!]]\n" - "cmd2d: command [a [decimal!] b [decimal!]]\n" - "cmdw: command [w]\n" - "cmds: command [str [string!] index [integer!]]\n" - "cmdb: command [blk [block!] index [integer!]]\n" - "cmdbl: command [blk [block!]]\n" -; - -RPIEXT const char *RPI_Init(int opts, RPILIB *lib) { - RPI = lib; - if (lib->version == RPI_VERSION) return init_block; - return 0; -} - -RPIEXT int RPI_Quit(int opts) { - return 0; -} - -u32 *word_ids = 0; - -RPIEXT int RPI_Call(int cmd, RPIFRM *frm) { - switch (cmd) { - case 0: - word_ids = RPI_MAP_WORDS(RPA_SERIES(frm,1)); - return RPR_TRUE; - case 1: - RPA_INT64(frm,1) = -RPA_INT64(frm,1); - break; - case 2: - case 3: - RPA_INT64(frm,1) = RPA_INT64(frm, 1) + RPA_INT64(frm, 2); - break; - case 4: - RPA_DEC64(frm,1) = RPA_DEC64(frm, 1) + RPA_DEC64(frm, 2); - break; - case 5: - RPA_INT64(frm,1) = RPI_FIND_WORD(word_ids, RPA_WORD(frm,1)); - RPA_TYPE(frm,1) = RPT_INTEGER; - break; - case 6: - RPA_INT64(frm,1) = RPI_GET_CHAR(RPA_SERIES(frm,1), (u32)RPA_INT64(frm,2)-1); - RPA_TYPE(frm,1) = RPT_INTEGER; - break; - case 7: - RPA_TYPE(frm,1) = RPI_GET_VALUE(RPA_SERIES(frm,1), (u32)RPA_INT64(frm,2)-1, &RPA_ARG(frm, 1)); - break; - case 8: - RPA_INT64(frm,1) = RPI_SERIES_INFO(RPA_SERIES(frm,1), RPI_INFO_TAIL); - RPA_TYPE(frm,1) = RPT_INTEGER; - break; - } - return RPR_VALUE; -} diff --git a/src/os/windows/dev-clipboard.c b/src/os/windows/dev-clipboard.c new file mode 100644 index 0000000000..a0da06d463 --- /dev/null +++ b/src/os/windows/dev-clipboard.c @@ -0,0 +1,208 @@ +// +// File: %dev-clipboard.c +// Summary: "Device: Clipboard access for Win32" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Provides a very simple interface to the clipboard for text. +// May be expanded in the future for images, etc. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// !!! Unlike on Linux/Posix, the basic Win32 API is able to support +// a clipboard device in a non-graphical build without an added +// dependency. For this reason, the Rebol core build included the +// clipboard device...which finds its way into a fixed-size table +// when it should be registered in a more dynamic and conditional way. +// Ren/C needs to improve the way that per-platform code can be +// included in a static build to not rely on this table the way +// hostkit does. +// + +#include + +#include + +#include "reb-host.h" + +extern void Signal_Device(REBREQ *req, REBINT type); +extern i32 Request_Size_Rebreq(REBREQ *); + +// +// Open_Clipboard: C +// +DEVICE_CMD Open_Clipboard(REBREQ *req) +{ + SET_OPEN(req); + return DR_DONE; +} + + +// +// Close_Clipboard: C +// +DEVICE_CMD Close_Clipboard(REBREQ *req) +{ + SET_CLOSED(req); + return DR_DONE; +} + + +// +// Read_Clipboard: C +// +DEVICE_CMD Read_Clipboard(REBREQ *req) +{ + HANDLE data; + wchar_t *cp; + wchar_t *bin; + REBINT len; + + req->actual = 0; + + // If there is no clipboard data: + if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) { + req->error = 10; + return DR_ERROR; + } + + if (!OpenClipboard(NULL)) { + req->error = 20; + return DR_ERROR; + } + + // Read the UTF-8 data: + if ((data = GetClipboardData(CF_UNICODETEXT)) == NULL) { + CloseClipboard(); + req->error = 30; + return DR_ERROR; + } + + cp = cast(wchar_t*, GlobalLock(data)); + if (!cp) { + GlobalUnlock(data); + CloseClipboard(); + req->error = 40; + return DR_ERROR; + } + + len = wcslen(cp); + bin = OS_ALLOC_N(wchar_t, len + 1); + wcsncpy(bin, cp, len); + + GlobalUnlock(data); + + CloseClipboard(); + + SET_FLAG(req->flags, RRF_WIDE); + req->common.data = cast(REBYTE *, bin); + req->actual = len * sizeof(wchar_t); + Signal_Device(req, EVT_READ); + return DR_DONE; +} + + +// +// Write_Clipboard: C +// +// Works for Unicode and ASCII strings. +// Length is number of bytes passed (not number of chars). +// +DEVICE_CMD Write_Clipboard(REBREQ *req) +{ + HANDLE data; + REBYTE *bin; + REBCNT err; + REBINT len = req->length; // in bytes + + req->actual = 0; + + data = GlobalAlloc(GHND, len + 4); + if (data == NULL) { + req->error = 5; + return DR_ERROR; + } + + // Lock and copy the string: + bin = cast(REBYTE*, GlobalLock(data)); + if (bin == NULL) { + req->error = 10; + return DR_ERROR; + } + + memcpy(bin, req->common.data, len); + bin[len] = 0; + GlobalUnlock(data); + + if (!OpenClipboard(NULL)) { + req->error = 20; + return DR_ERROR; + } + + EmptyClipboard(); + + err = !SetClipboardData(GET_FLAG(req->flags, RRF_WIDE) ? CF_UNICODETEXT : CF_TEXT, data); + + CloseClipboard(); + + if (err) { + req->error = 50; + return DR_ERROR; + } + + req->actual = len; + Signal_Device(req, EVT_WROTE); + return DR_DONE; +} + + +// +// Poll_Clipboard: C +// +DEVICE_CMD Poll_Clipboard(REBREQ *req) +{ + UNUSED(req); + return DR_DONE; +} + +/*********************************************************************** +** +** Command Dispatch Table (RDC_ enum order) +** +***********************************************************************/ + +static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = +{ + Request_Size_Rebreq, + 0, + 0, + Open_Clipboard, + Close_Clipboard, + Read_Clipboard, + Write_Clipboard, + Poll_Clipboard, +}; + +DEFINE_DEV(Dev_Clipboard, "Clipboard", 1, Dev_Cmds, RDC_MAX); diff --git a/src/os/windows/dev-event.c b/src/os/windows/dev-event.c new file mode 100644 index 0000000000..9c898335fd --- /dev/null +++ b/src/os/windows/dev-event.c @@ -0,0 +1,212 @@ +// +// File: %dev-event.c +// Summary: "Device: Event handler for Win32" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Processes events to pass to REBOL. Note that events are +// used for more than just windowing. +// + +#include +#include "reb-host.h" + +#ifndef HWND_MESSAGE +#define HWND_MESSAGE (HWND)-3 +#endif + +extern void Done_Device(REBUPT handle, int error); +extern i32 Request_Size_Rebreq(REBREQ *); + +// Move or remove globals? !? +HWND Event_Handle = 0; // Used for async DNS +static int Timer_Id = 0; // The timer we are using + +EXTERN_C HINSTANCE App_Instance; // From Main module. + + +// +// REBOL_Event_Proc: C +// +// The minimal default event handler. +// +LRESULT CALLBACK REBOL_Event_Proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + switch(msg) { + case WM_CLOSE: + DestroyWindow(hwnd); + break; + case WM_DESTROY: + PostQuitMessage(0); + break; + default: + // Default processing that we do not care about: + return DefWindowProc(hwnd, msg, wparam, lparam); + } + return 0; +} + + +// +// Init_Events: C +// +// Initialize the event device. +// +// Create a hidden window to handle special events, +// such as timers and async DNS. +// +DEVICE_CMD Init_Events(REBREQ *dr) +{ + REBDEV *dev = (REBDEV*)dr; // just to keep compiler happy + WNDCLASSEX wc; + + memset(&wc, '\0', sizeof(wc)); + + // Register event object class: + wc.cbSize = sizeof(wc); + wc.lpszClassName = L"REBOL-Events"; + wc.hInstance = App_Instance; + wc.lpfnWndProc = REBOL_Event_Proc; + if (!RegisterClassEx(&wc)) return DR_ERROR; + + // Create the hidden window: + Event_Handle = CreateWindowEx( + 0, + wc.lpszClassName, + wc.lpszClassName, + 0,0,0,0,0, + HWND_MESSAGE, //used for message-only windows + NULL, App_Instance, NULL + ); + + if (!Event_Handle) return DR_ERROR; + + SET_FLAG(dev->flags, RDF_INIT); + return DR_DONE; +} + + +// +// Poll_Events: C +// +// Poll for events and process them. +// Returns 1 if event found, else 0. +// +// MS Notes: +// +// "The PeekMessage function normally does not remove WM_PAINT +// messages from the queue. WM_PAINT messages remain in the queue +// until they are processed." +// +DEVICE_CMD Poll_Events(REBREQ *req) +{ + UNUSED(req); + + MSG msg; + int flag = DR_DONE; + + // Are there messages to process? + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + flag = DR_PEND; + if (msg.message == WM_TIMER) + break; + if (msg.message == WM_DNS) + Done_Device(msg.wParam, msg.lParam>>16); // error code + else { + DispatchMessage(&msg); + } + } + + return flag; // different meaning compared to most commands +} + + +// +// Query_Events: C +// +// Wait for an event, or a timeout (in milliseconds) specified by +// req->length. The latter is used by WAIT as the main timing +// method. +// +DEVICE_CMD Query_Events(REBREQ *req) +{ + MSG msg; + + // Set timer (we assume this is very fast): + Timer_Id = SetTimer(0, Timer_Id, req->length, 0); + + // Wait for message or the timer: + if (GetMessage(&msg, NULL, 0, 0)) { + //printf("Msg: %d\n", msg.message); + if (msg.message == WM_DNS) + Done_Device(msg.wParam, msg.lParam>>16); // error code + else { + DispatchMessage(&msg); + } + } + + // Quickly check for other events: + Poll_Events(0); + + //if (Timer_Id) KillTimer(0, Timer_Id); + return DR_DONE; +} + + +// +// Connect_Events: C +// +// Simply keeps the request pending for polling purposes. +// Use Abort_Device to remove it. +// +DEVICE_CMD Connect_Events(REBREQ *req) +{ + UNUSED(req); + + return DR_PEND; // keep pending +} + + +/*********************************************************************** +** +** Command Dispatch Table (RDC_ enum order) +** +***********************************************************************/ + +static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = { + Request_Size_Rebreq, + Init_Events, // init device driver resources + 0, // RDC_QUIT, // cleanup device driver resources + 0, // RDC_OPEN, // open device unit (port) + 0, // RDC_CLOSE, // close device unit + 0, // RDC_READ, // read from unit + 0, // RDC_WRITE, // write to unit + Poll_Events, + Connect_Events, + Query_Events, +}; + +DEFINE_DEV(Dev_Event, "OS Events", 1, Dev_Cmds, RDC_MAX); diff --git a/src/os/windows/dev-file.c b/src/os/windows/dev-file.c new file mode 100644 index 0000000000..9e1b4490f3 --- /dev/null +++ b/src/os/windows/dev-file.c @@ -0,0 +1,492 @@ +// +// File: %dev-file.c +// Summary: "Device: File access for Win32" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// File open, close, read, write, and other actions. +// + +#include +#include +#include +#include + +#include "reb-host.h" + +// MSDN V6 missed this define: +#ifndef INVALID_SET_FILE_POINTER +#define INVALID_SET_FILE_POINTER ((DWORD)-1) +#endif + + +/*********************************************************************** +** +** Local Functions +** +***********************************************************************/ + +static BOOL Seek_File_64(struct devreq_file *file) +{ + // Performs seek and updates index value. TRUE on scuccess. + // On error, returns FALSE and sets file->error field. + REBREQ *req = AS_REBREQ(file); + HANDLE h = req->requestee.handle; + DWORD result; + LONG highint; + + if (file->index == -1) { + // Append: + highint = 0; + result = SetFilePointer(h, 0, &highint, FILE_END); + } + else { + // Line below updates index if it is affected: + highint = cast(LONG, file->index >> 32); + result = SetFilePointer( + h, cast(LONG, file->index), &highint, FILE_BEGIN + ); + } + + if (result == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) { + req->error = -RFE_NO_SEEK; + return 0; + } + + file->index = (cast(i64, highint) << 32) + result; + + return 1; +} + + +// +// Read_Directory: C +// +// This function will read a file directory, one file entry +// at a time, then close when no more files are found. +// +// Procedure: +// +// This function is passed directory and file arguments. +// The dir arg provides information about the directory to read. +// The file arg is used to return specific file information. +// +// To begin, this function is called with a dir->requestee.handle that +// is set to zero and a dir->special.file.path string for the directory. +// +// The directory is opened and a handle is stored in the dir +// structure for use on subsequent calls. If an error occurred, +// dir->error is set to the error code and -1 is returned. +// The dir->size field can be set to the number of files in the +// dir, if it is known. The dir->special.file.index field can be used by this +// function to store information between calls. +// +// If the open succeeded, then information about the first file +// is stored in the file argument and the function returns 0. +// On an error, the dir->error is set, the dir is closed, +// dir->requestee.handle is nulled, and -1 is returned. +// +// The caller loops until all files have been obtained. This +// action should be uninterrupted. (The caller should not perform +// additional OS or IO operations between calls.) +// +// When no more files are found, the dir is closed, dir->requestee.handle +// is nulled, and 1 is returned. No file info is returned. +// (That is, this function is called one extra time. This helps +// for OSes that may deallocate file strings on dir close.) +// +// Note that the dir->special.file.path can contain wildcards * and ?. The +// processing of these can be done in the OS (if supported) or +// by a separate filter operation during the read. +// +// Store file date info in file->special.file.index or other fields? +// Store permissions? Ownership? Groups? Or, require that +// to be part of a separate request? +// +static int Read_Directory(struct devreq_file *dir, struct devreq_file *file) +{ + WIN32_FIND_DATA info; + REBREQ *dir_req = AS_REBREQ(dir); + REBREQ *file_req = AS_REBREQ(file); + HANDLE h= dir_req->requestee.handle; + wchar_t *cp = 0; + + if (!h) { + + // Read first file entry: + h = FindFirstFile(dir->path, &info); + if (h == INVALID_HANDLE_VALUE) { + dir_req->error = -RFE_OPEN_FAIL; + return DR_ERROR; + } + dir_req->requestee.handle = h; + CLR_FLAG(dir_req->flags, RRF_DONE); + cp = info.cFileName; + + } + + // Skip over the . and .. dir cases: + while (cp == 0 || (cp[0] == '.' && (cp[1] == 0 || (cp[1] == '.' && cp[2] == 0)))) { + + // Read next file_req entry, or error: + if (!FindNextFile(h, &info)) { + dir_req->error = GetLastError(); + FindClose(h); + dir_req->requestee.handle = 0; + if (dir_req->error != ERROR_NO_MORE_FILES) return DR_ERROR; + dir_req->error = 0; + SET_FLAG(dir_req->flags, RRF_DONE); // no more file_reqs + return DR_DONE; + } + cp = info.cFileName; + + } + + file_req->modes = 0; + if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) SET_FLAG(file_req->modes, RFM_DIR); + wcsncpy(file->path, info.cFileName, MAX_FILE_NAME); + file->size = + (cast(i64, info.nFileSizeHigh) << 32) + info.nFileSizeLow; + + return DR_DONE; +} + + +// +// Open_File: C +// +// Open the specified file with the given modes. +// +// Notes: +// 1. The file path is provided in REBOL format, and must be +// converted to local format before it is used. +// 2. REBOL performs the required access security check before +// calling this function. +// 3. REBOL clears necessary fields of file structure before +// calling (e.g. error and size fields). +// +// !! Confirm that /seek /append works properly. +// +DEVICE_CMD Open_File(REBREQ *req) +{ + DWORD attrib = FILE_ATTRIBUTE_NORMAL; + DWORD access = 0; + DWORD create = 0; + HANDLE h; + BY_HANDLE_FILE_INFORMATION info; + struct devreq_file *file = DEVREQ_FILE(req); + + // Set the access, creation, and attribute for file creation: + if (GET_FLAG(req->modes, RFM_READ)) { + access |= GENERIC_READ; + create = OPEN_EXISTING; + } + + if (GET_FLAGS(req->modes, RFM_WRITE, RFM_APPEND)) { + access |= GENERIC_WRITE; + if ( + GET_FLAG(req->modes, RFM_NEW) || + !( + GET_FLAG(req->modes, RFM_READ) || + GET_FLAG(req->modes, RFM_APPEND) || + GET_FLAG(req->modes, RFM_SEEK) + ) + ) create = CREATE_ALWAYS; + else create = OPEN_ALWAYS; + } + + attrib |= GET_FLAG(req->modes, RFM_SEEK) ? FILE_FLAG_RANDOM_ACCESS : FILE_FLAG_SEQUENTIAL_SCAN; + + if (GET_FLAG(req->modes, RFM_READONLY)) + attrib |= FILE_ATTRIBUTE_READONLY; + + if (!access) { + req->error = -RFE_NO_MODES; + goto fail; + } + + // Open the req (yes, this is how windows does it, the nutty kids): + h = CreateFile(file->path, access, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, create, attrib, 0); + if (h == INVALID_HANDLE_VALUE) { + req->error = -RFE_OPEN_FAIL; + goto fail; + } + + // Confirm that a seek-mode req is actually seekable: + if (GET_FLAG(req->modes, RFM_SEEK)) { + // Below should work because we are seeking to 0: + if (SetFilePointer(h, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { + CloseHandle(h); + req->error = -RFE_BAD_SEEK; + goto fail; + } + } + + // Fetch req size (if fails, then size is assumed zero): + if (GetFileInformationByHandle(h, &info)) { + file->size = + (cast(i64, info.nFileSizeHigh) << 32) + info.nFileSizeLow; + file->time.l = info.ftLastWriteTime.dwLowDateTime; + file->time.h = info.ftLastWriteTime.dwHighDateTime; + } + + req->requestee.handle = h; + + return DR_DONE; + +fail: + return DR_ERROR; +} + + +// +// Close_File: C +// +// Closes a previously opened file. +// +DEVICE_CMD Close_File(REBREQ *file) +{ + if (file->requestee.handle) { + CloseHandle(file->requestee.handle); + file->requestee.handle = 0; + } + return DR_DONE; +} + + +// +// Read_File: C +// +DEVICE_CMD Read_File(REBREQ *req) +{ + struct devreq_file *file = DEVREQ_FILE(req); + if (GET_FLAG(req->modes, RFM_DIR)) { + return Read_Directory(file, cast(struct devreq_file*, req->common.data)); + } + + if (!req->requestee.handle) { + req->error = -RFE_NO_HANDLE; + return DR_ERROR; + } + + if (req->modes & ((1 << RFM_SEEK) | (1 << RFM_RESEEK))) { + CLR_FLAG(req->modes, RFM_RESEEK); + if (!Seek_File_64(file)) return DR_ERROR; + } + + assert(sizeof(DWORD) == sizeof(req->actual)); + + if (!ReadFile( + req->requestee.handle, + req->common.data, + req->length, + cast(DWORD*, &req->actual), + 0 + )) { + req->error = -RFE_BAD_READ; + return DR_ERROR; + } else { + file->index += req->actual; + } + + return DR_DONE; +} + + +// +// Write_File: C +// +// Bug?: update file->size value after write !? +// +DEVICE_CMD Write_File(REBREQ *req) +{ + DWORD result; + DWORD size_high, size_low; + struct devreq_file *file = DEVREQ_FILE(req); + + if (!req->requestee.handle) { + req->error = -RFE_NO_HANDLE; + return DR_ERROR; + } + + if (GET_FLAG(req->modes, RFM_APPEND)) { + CLR_FLAG(req->modes, RFM_APPEND); + SetFilePointer(req->requestee.handle, 0, 0, FILE_END); + } + + if (req->modes & ((1 << RFM_SEEK) | (1 << RFM_RESEEK) | (1 << RFM_TRUNCATE))) { + CLR_FLAG(req->modes, RFM_RESEEK); + if (!Seek_File_64(file)) return DR_ERROR; + if (GET_FLAG(req->modes, RFM_TRUNCATE)) + SetEndOfFile(req->requestee.handle); + } + + if (req->length != 0) { + if (!WriteFile(req->requestee.handle, req->common.data, req->length, (LPDWORD)&req->actual, 0)) { + result = GetLastError(); + if (result == ERROR_HANDLE_DISK_FULL) req->error = -RFE_DISK_FULL; + else req->error = -RFE_BAD_WRITE; + return DR_ERROR; + } + } + + size_low = GetFileSize(req->requestee.handle, &size_high); + if (size_low == 0xffffffff) { + result = GetLastError(); + req->error = -RFE_BAD_WRITE; + return DR_ERROR; + } + + file->size = + (cast(i64, size_high) << 32) + cast(i64, size_low); + + return DR_DONE; +} + + +// +// Query_File: C +// +// Obtain information about a file. Return TRUE on success. +// On error, return FALSE and set file->error code. +// +// Note: time is in local format and must be converted +// +DEVICE_CMD Query_File(REBREQ *req) +{ + WIN32_FILE_ATTRIBUTE_DATA info; + struct devreq_file *file = DEVREQ_FILE(req); + + if (!GetFileAttributesEx(file->path, GetFileExInfoStandard, &info)) { + req->error = GetLastError(); + return DR_ERROR; + } + + if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) SET_FLAG(req->modes, RFM_DIR); + else CLR_FLAG(req->modes, RFM_DIR); + file->size = + (cast(i64, info.nFileSizeHigh) << 32) + cast(i64, info.nFileSizeLow); + file->time.l = info.ftLastWriteTime.dwLowDateTime; + file->time.h = info.ftLastWriteTime.dwHighDateTime; + return DR_DONE; +} + + +// +// Create_File: C +// +DEVICE_CMD Create_File(REBREQ *req) +{ + struct devreq_file *file = DEVREQ_FILE(req); + if (GET_FLAG(req->modes, RFM_DIR)) { + if (CreateDirectory(file->path, 0)) return DR_DONE; + req->error = GetLastError(); + return DR_ERROR; + } else + return Open_File(req); +} + + +// +// Delete_File: C +// +// Delete a file or directory. Return TRUE if it was done. +// The file->special.file.path provides the directory path and name. +// For errors, return FALSE and set file->error to error code. +// +// Note: Dirs must be empty to succeed +// +DEVICE_CMD Delete_File(REBREQ *req) +{ + struct devreq_file *file = DEVREQ_FILE(req); + if (GET_FLAG(req->modes, RFM_DIR)) { + if (RemoveDirectory(file->path)) return DR_DONE; + } else + if (DeleteFile(file->path)) return DR_DONE; + + req->error = GetLastError(); + return DR_ERROR; +} + + +// +// Rename_File: C +// +// Rename a file or directory. +// Note: cannot rename across file volumes. +// +DEVICE_CMD Rename_File(REBREQ *req) +{ + struct devreq_file *file = DEVREQ_FILE(req); + if (MoveFile(cast(wchar_t*, file->path), cast(wchar_t*, req->common.data))) + return DR_DONE; + req->error = GetLastError(); + return DR_ERROR; +} + + +// +// Poll_File: C +// +DEVICE_CMD Poll_File(REBREQ *file) +{ + UNUSED(file); + return DR_DONE; // files are synchronous (currently) +} + +// +// Request_Size_File: C +// +static i32 Request_Size_File(REBREQ *req) +{ + UNUSED(req); + return sizeof(struct devreq_file); +} + + +/*********************************************************************** +** +** Command Dispatch Table (RDC_ enum order) +** +***********************************************************************/ + +static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = { + Request_Size_File, + 0, + 0, + Open_File, + Close_File, + Read_File, + Write_File, + Poll_File, + 0, // connect + Query_File, + 0, // modify + Create_File, + Delete_File, + Rename_File, +}; + +DEFINE_DEV(Dev_File, "File IO", 1, Dev_Cmds, RDC_MAX); diff --git a/src/os/windows/dev-serial.c b/src/os/windows/dev-serial.c new file mode 100644 index 0000000000..fc1d12fb3c --- /dev/null +++ b/src/os/windows/dev-serial.c @@ -0,0 +1,300 @@ +// +// File: %dev-serial.c +// Summary: "Device: Serial port access for Windows" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2013 REBOL Technologies +// Copyright 2013-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// + +#include +#include +#include + +#include "reb-host.h" + +extern void Signal_Device(REBREQ *req, REBINT type); + +#define MAX_SERIAL_DEV_PATH 128 + +const int speeds[] = { + 110, CBR_110, + 300, CBR_300, + 600, CBR_600, + 1200, CBR_1200, + 2400, CBR_2400, + 4800, CBR_4800, + 9600, CBR_9600, + 14400, CBR_14400, + 19200, CBR_19200, + 38400, CBR_38400, + 57600, CBR_57600, + 115200, CBR_115200, + 128000, CBR_128000, + 230400, CBR_256000, + 0 +}; + + +/*********************************************************************** +** +** Local Functions +** +***********************************************************************/ +static REBINT Set_Serial_Settings(HANDLE h, struct devreq_serial *serial) +{ + DCB dcbSerialParams; + REBINT n; + int speed = serial->baud; + + memset(&dcbSerialParams, '\0', sizeof(dcbSerialParams)); + dcbSerialParams.DCBlength = sizeof(dcbSerialParams); + if (GetCommState(h, &dcbSerialParams) == 0) return 1; + + + for (n = 0; speeds[n]; n += 2) { + if (speed == speeds[n]) { + dcbSerialParams.BaudRate = speeds[n+1]; + break; + } + } + if (speeds[n] == 0) dcbSerialParams.BaudRate = CBR_115200; // invalid, use default + + dcbSerialParams.ByteSize = serial->data_bits; + dcbSerialParams.StopBits = serial->stop_bits == 1? ONESTOPBIT : TWOSTOPBITS; + switch (serial->parity) { + case SERIAL_PARITY_ODD: + dcbSerialParams.Parity = ODDPARITY; + break; + case SERIAL_PARITY_EVEN: + dcbSerialParams.Parity = EVENPARITY; + break; + case SERIAL_PARITY_NONE: + default: + dcbSerialParams.Parity = NOPARITY; + break; + } + + + if(SetCommState(h, &dcbSerialParams) == 0) { + return 1; + } + + PurgeComm(h,PURGE_RXCLEAR|PURGE_TXCLEAR); //make sure buffers are clean + return 0; +} + +// +// Open_Serial: C +// +// serial.path = the /dev name for the serial port +// serial.baud = speed (baudrate) +// +DEVICE_CMD Open_Serial(REBREQ *req) +{ + HANDLE h; + COMMTIMEOUTS timeouts; //add in timeouts? Currently unused + struct devreq_serial *serial = DEVREQ_SERIAL(req); + + memset(&timeouts, '\0', sizeof(timeouts)); + + // req->special.serial.path should be prefixed with "\\.\" to allow for higher com port numbers + wchar_t fullpath[MAX_SERIAL_DEV_PATH] = L"\\\\.\\"; + + if (!serial->path) { + req->error = -RFE_BAD_PATH; + return DR_ERROR; + } + + wcsncat(fullpath, serial->path, MAX_SERIAL_DEV_PATH); + + h = CreateFile(fullpath, GENERIC_READ|GENERIC_WRITE, 0, NULL,OPEN_EXISTING, 0, NULL ); + if (h == INVALID_HANDLE_VALUE) { + req->error = -RFE_OPEN_FAIL; + return DR_ERROR; + } + + if (Set_Serial_Settings(h, serial)==0) { + CloseHandle(h); + req->error = -RFE_OPEN_FAIL; + return DR_ERROR; + } + + + // See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa363190%28v=vs.85%29.aspx + timeouts.ReadIntervalTimeout = MAXDWORD; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.ReadTotalTimeoutConstant = 0; + timeouts.WriteTotalTimeoutMultiplier = 1; // These two write lines may need to be set to 0. + timeouts.WriteTotalTimeoutConstant = 1; + if (!SetCommTimeouts(h, &timeouts)) { + CloseHandle(h); + req->error = -RFE_OPEN_FAIL; + return DR_ERROR; + } + + req->requestee.handle = h; + return DR_DONE; +} + + +// +// Close_Serial: C +// +DEVICE_CMD Close_Serial(REBREQ *req) +{ + if (req->requestee.handle) { + // !!! Should we free req->special.serial.prior_attr termios struct? + CloseHandle(req->requestee.handle); + req->requestee.handle = 0; + } + return DR_DONE; +} + + +// +// Read_Serial: C +// +DEVICE_CMD Read_Serial(REBREQ *req) +{ + DWORD result = 0; + if (!req->requestee.handle) { + req->error = -RFE_NO_HANDLE; + return DR_ERROR; + } + + //printf("reading %d bytes\n", req->length); + if (!ReadFile(req->requestee.handle, req->common.data, req->length, &result, 0)) { + req->error = -RFE_BAD_READ; + Signal_Device(req, EVT_ERROR); + return DR_ERROR; + } else { + if (result == 0) { + return DR_PEND; + } else if (result > 0){ + //printf("read %d bytes\n", req->actual); + req->actual = result; + Signal_Device(req, EVT_READ); + } + } + +#ifdef DEBUG_SERIAL + printf("read %d ret: %d\n", req->length, req->actual); +#endif + + return DR_DONE; +} + + +// +// Write_Serial: C +// +DEVICE_CMD Write_Serial(REBREQ *req) +{ + DWORD result = 0; + DWORD len = req->length - req->actual; + if (!req->requestee.handle) { + req->error = -RFE_NO_HANDLE; + return DR_ERROR; + } + + if (len <= 0) return DR_DONE; + + if (!WriteFile( + req->requestee.handle, req->common.data, len, &result, NULL + )) { + req->error = -RFE_BAD_WRITE; + Signal_Device(req, EVT_ERROR); + return DR_ERROR; + } + +#ifdef DEBUG_SERIAL + printf("write %d ret: %d\n", req->length, req->actual); +#endif + + req->actual += result; + req->common.data += result; + if (req->actual >= req->length) { + Signal_Device(req, EVT_WROTE); + return DR_DONE; + } else { + SET_FLAG(req->flags, RRF_ACTIVE); /* notify OS_WAIT of activity */ + return DR_PEND; + } +} + + +// +// Query_Serial: C +// +DEVICE_CMD Query_Serial(REBREQ *req) +{ +#ifdef QUERY_IMPLEMENTED + struct pollfd pfd; + + if (req->requestee.handle) { + pfd.fd = req->requestee.handle; + pfd.events = POLLIN; + n = poll(&pfd, 1, 0); + } +#else + UNUSED(req); +#endif + return DR_DONE; +} + +// +// Request_Size_Serial: C +// +static i32 Request_Size_Serial(REBREQ *req) +{ + UNUSED(req); + return sizeof(struct devreq_serial); +} + + +/*********************************************************************** +** +** Command Dispatch Table (RDC_ enum order) +** +***********************************************************************/ + +static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = { + Request_Size_Serial, + 0, + 0, + Open_Serial, + Close_Serial, + Read_Serial, + Write_Serial, + 0, // poll + 0, // connect + Query_Serial, + 0, // modify + 0, // create + 0, // delete + 0 // rename +}; + +DEFINE_DEV(Dev_Serial, "Serial IO", 1, Dev_Cmds, RDC_MAX); + diff --git a/src/os/windows/dev-stdio.c b/src/os/windows/dev-stdio.c new file mode 100644 index 0000000000..cb5bdc2c42 --- /dev/null +++ b/src/os/windows/dev-stdio.c @@ -0,0 +1,322 @@ +// +// File: %dev-stdio.c +// Summary: "Device: Standard I/O for Win32" +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Provides basic I/O streams support for redirection and +// opening a console window if necessary. +// + +#include +#include +#include +#include + +#include +#include + +#include "reb-host.h" + +#define BUF_SIZE (16 * 1024) // MS restrictions apply + +#define SF_DEV_NULL 31 // Local flag to mark NULL device. + +#define CONSOLE_MODES \ + ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT \ + | 0x0040 | 0x0020 // quick edit and insert mode (not defined in VC6) + +static HANDLE Std_Out = NULL; +static HANDLE Std_Inp = NULL; +static wchar_t *Std_Buf = NULL; // Used for UTF-8 conversion of stdin/stdout. + +static BOOL Redir_Out = 0; +static BOOL Redir_Inp = 0; + +extern i32 Request_Size_Rebreq(REBREQ *); + +//********************************************************************** + + +static void Close_Stdio(void) +{ + if (Std_Buf) { + OS_FREE(Std_Buf); + Std_Buf = 0; + //FreeConsole(); // problem: causes a delay + } +} + + +// +// Quit_IO: C +// +DEVICE_CMD Quit_IO(REBREQ *dr) +{ + REBDEV *dev = (REBDEV*)dr; // just to keep compiler happy above + + Close_Stdio(); + //if (GET_FLAG(dev->flags, RDF_OPEN)) FreeConsole(); + CLR_FLAG(dev->flags, RDF_OPEN); + return DR_DONE; +} + + +// +// Open_IO: C +// +DEVICE_CMD Open_IO(REBREQ *req) +{ + REBDEV *dev; + + dev = Devices[req->device]; + + // Avoid opening the console twice (compare dev and req flags): + if (GET_FLAG(dev->flags, RDF_OPEN)) { + // Device was opened earlier as null, so req must have that flag: + if (GET_FLAG(dev->flags, SF_DEV_NULL)) + SET_FLAG(req->modes, RDM_NULL); + SET_FLAG(req->flags, RRF_OPEN); + return DR_DONE; // Do not do it again + } + + if (!GET_FLAG(req->modes, RDM_NULL)) { + // Get the raw stdio handles: + Std_Out = GetStdHandle(STD_OUTPUT_HANDLE); + Std_Inp = GetStdHandle(STD_INPUT_HANDLE); + //Std_Err = GetStdHandle(STD_ERROR_HANDLE); + + Redir_Out = (GetFileType(Std_Out) != FILE_TYPE_CHAR); + Redir_Inp = (GetFileType(Std_Inp) != FILE_TYPE_CHAR); + + if (!Redir_Inp || !Redir_Out) { + // If either input or output is not redirected, preallocate + // a buffer for conversion from/to UTF-8. + Std_Buf = OS_ALLOC_N(wchar_t, BUF_SIZE); + } + + if (!Redir_Inp) { + // Make the Win32 console a bit smarter by default. + SetConsoleMode(Std_Inp, CONSOLE_MODES); + } + } + else + SET_FLAG(dev->flags, SF_DEV_NULL); + + SET_FLAG(req->flags, RRF_OPEN); + SET_FLAG(dev->flags, RDF_OPEN); + + return DR_DONE; +} + + +// +// Close_IO: C +// +DEVICE_CMD Close_IO(REBREQ *req) +{ + REBDEV *dev = Devices[req->device]; + + Close_Stdio(); + + CLR_FLAG(dev->flags, RRF_OPEN); + + return DR_DONE; +} + + +// +// Write_IO: C +// +// Low level "raw" standard output function. +// +// Allowed to restrict the write to a max OS buffer size. +// +// Returns the number of chars written. +// +DEVICE_CMD Write_IO(REBREQ *req) +{ + if (GET_FLAG(req->modes, RDM_NULL)) { + req->actual = req->length; + return DR_DONE; + } + + BOOL ok = FALSE; // Note: Windows BOOL, not REBOOL + + if (Std_Out) { + + if (Redir_Out) { // Always UTF-8 + DWORD total_bytes; + ok = WriteFile( + Std_Out, + req->common.data, + req->length, + &total_bytes, + 0 + ); + UNUSED(total_bytes); + } + else { + // Convert UTF-8 buffer to Win32 wide-char format for console. + // Thankfully, MS provides something other than mbstowcs(); + // however, if our buffer overflows, it's an error. There's no + // efficient way at this level to split-up the input data, + // because its UTF-8 with variable char sizes. + // + DWORD len = MultiByteToWideChar( + CP_UTF8, + 0, + s_cast(req->common.data), + req->length, + Std_Buf, + BUF_SIZE + ); + if (len > 0) { // no error + DWORD total_wide_chars; + ok = WriteConsoleW( + Std_Out, + Std_Buf, + len, + &total_wide_chars, + 0 + ); + UNUSED(total_wide_chars); + } + } + + if (!ok) { + req->error = GetLastError(); + return DR_ERROR; + } + + req->actual = req->length; // want byte count written, assume success + + //if (GET_FLAG(req->flags, RRF_FLUSH)) { + // FLUSH(); + //} + } + + return DR_DONE; +} + + +// +// Read_IO: C +// +// Low level "raw" standard input function. +// +// The request buffer must be long enough to hold result. +// +// Result is NOT terminated (the actual field has length.) +// +DEVICE_CMD Read_IO(REBREQ *req) +{ + DWORD total = 0; + DWORD len; + BOOL ok; + + if (GET_FLAG(req->modes, RDM_NULL)) { + req->common.data[0] = 0; + return DR_DONE; + } + + req->actual = 0; + + if (Std_Inp) { + + if (Redir_Inp) { // always UTF-8 + len = MIN(req->length, BUF_SIZE); + ok = ReadFile(Std_Inp, req->common.data, len, &total, 0); + } + else { + ok = ReadConsoleW(Std_Inp, Std_Buf, BUF_SIZE-1, &total, 0); + if (ok) { + if (total == 0) { + // WideCharToMultibyte fails if cchWideChar is 0. + assert(req->length >= 2); + strcpy(s_cast(req->common.data), ""); + } + else { + total = WideCharToMultiByte( + CP_UTF8, + 0, + Std_Buf, + total, + s_cast(req->common.data), + req->length, + 0, + 0 + ); + if (total == 0) + ok = FALSE; + } + } + } + + if (NOT(ok)) { + req->error = GetLastError(); + return DR_ERROR; + } + + req->actual = total; + } + + return DR_DONE; +} + + +// +// Request_Size_IO: C +// +static i32 Request_Size_IO(REBREQ *req) +{ + UNUSED(req); + return sizeof(struct devreq_file); +} + +/*********************************************************************** +** +** Command Dispatch Table (RDC_ enum order) +** +***********************************************************************/ + +static DEVICE_CMD_FUNC Dev_Cmds[RDC_MAX] = +{ + Request_Size_IO, + 0, // init + Quit_IO, + Open_IO, + Close_IO, + Read_IO, + Write_IO, + 0, // poll + 0, // connect + 0, // query + 0, // modify + 0, // CREATE was once used for opening echo file +}; + +DEFINE_DEV(Dev_StdIO, "Standard IO", 1, Dev_Cmds, RDC_MAX); + diff --git a/src/os/windows/host-lib.c b/src/os/windows/host-lib.c new file mode 100644 index 0000000000..9475b23830 --- /dev/null +++ b/src/os/windows/host-lib.c @@ -0,0 +1,559 @@ +// +// File: %host-lib.c +// Summary: {OS API function library called by REBOL interpreter} +// Project: "Rebol 3 Interpreter and Run-time (Ren-C branch)" +// Homepage: https://github.com/metaeducation/ren-c/ +// +//=////////////////////////////////////////////////////////////////////////=// +// +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//=////////////////////////////////////////////////////////////////////////=// +// +// This module is parsed for function declarations used to +// build prototypes, tables, and other definitions. To change +// function arguments requires a rebuild of the REBOL library. +// +// This module provides the functions that REBOL calls +// to interface to the native (host) operating system. +// REBOL accesses these functions through the structure +// defined in host-lib.h (auto-generated, do not modify). +// +// compile with -DUNICODE for Win32 wide char API +// +//=////////////////////////////////////////////////////////////////////////=// +// +// WARNING: The function declarations here cannot be modified without also +// modifying those found in the other OS host-lib files! Do not even modify +// the argument names. +// + +#include +#include +#include +#include +#include +#include +#include + +#include "reb-host.h" + +#ifndef REB_CORE +REBSER* Gob_To_Image(REBGOB *gob); +#endif + +//used to detect non-modal OS dialogs +BOOL osDialogOpen = FALSE; + + +// +// Convert_Date: C +// +// Convert local format of system time into standard date +// and time structure. +// +void Convert_Date(REBVAL *out, long zone, const SYSTEMTIME *stime) +{ + RL_Init_Date( + out, + stime->wYear, // year + stime->wMonth, // month + stime->wDay, // day + stime->wHour * 3600 + stime->wMinute * 60 + stime->wSecond, // "time" + 1000000 * stime->wMilliseconds, // nano + zone + ); +} + + +/*********************************************************************** +** +** OS Library Functions +** +***********************************************************************/ + + +// +// OS_Config: C +// +// Return a specific runtime configuration parameter. +// +REBINT OS_Config(int id, REBYTE *result) +{ + UNUSED(result); + +#define OCID_STACK_SIZE 1 // needs to move to .h file + + switch (id) { + case OCID_STACK_SIZE: + return 0; // (size in bytes should be returned here) + } + + return 0; +} + + +// +// OS_Exit: C +// +// Called in cases where REBOL needs to quit immediately +// without returning from the main() function. +// +void OS_Exit(int code) +{ + //OS_Call_Device(RDI_STDIO, RDC_CLOSE); // close echo + OS_Quit_Devices(0); +#ifndef REB_CORE + OS_Destroy_Graphics(); +#endif + exit(code); +} + + +// +// OS_Crash: C +// +// Tell user that REBOL has crashed. This function must use +// the most obvious and reliable method of displaying the +// crash message. +// +// If the title is NULL, then REBOL is running in a server mode. +// In that case, we do not want the crash message to appear on +// the screen, because the system may be unattended. +// +// On some systems, the error may be recorded in the system log. +// +void OS_Crash(const REBYTE *title, const REBYTE *content) +{ + // Echo crash message if echo file is open: + ///PUTE(content); + OS_Call_Device(RDI_STDIO, RDC_CLOSE); // close echo + + // A title tells us we should alert the user: + if (title) { + // OS_Put_Str(title); + // OS_Put_Str(":\n"); + // Use ASCII only + MessageBoxA(NULL, cs_cast(content), cs_cast(title), MB_ICONHAND); + } + // OS_Put_Str(content); + exit(100); +} + + +// +// OS_Form_Error: C +// +// Translate OS error into a string. The str is the string +// buffer and the len is the length of the buffer. +// +REBCHR *OS_Form_Error(int errnum, REBCHR *str, int len) +{ + wchar_t *lpMsgBuf; + int ok; + + if (!errnum) errnum = GetLastError(); + + // !!! Why does this allocate a buffer when FormatMessage takes a + // buffer and a size...exactly the interface we're implementing? + ok = FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + errnum, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + cast(wchar_t*, &lpMsgBuf), // see FORMAT_MESSAGE_ALLOCATE_BUFFER + 0, + NULL); + + len--; // termination + + if (!ok) wcsncpy(str, L"unknown error", len); + else { + wcsncpy(str, lpMsgBuf, len); + LocalFree(lpMsgBuf); + } + return str; +} + + +// +// OS_Get_Env: C +// +// Get a value from the environment. +// Returns size of retrieved value for success or zero if missing. +// +// If return size is greater than capacity then value contents +// are undefined, and size includes null terminator of needed buf +// +REBINT OS_Get_Env(REBCHR* buffer, const REBCHR *key, REBINT capacity) +{ + // Note: The Windows variant of this API is NOT case-sensitive + + REBINT result = GetEnvironmentVariable(key, buffer, capacity); + if (result == 0) { // some failure... + if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) { + return -1; // not found + } + return -2; // other error... fail? + } + return result; +} + + +// +// OS_Set_Env: C +// +// Set a value from the environment. +// Returns >0 for success and 0 for errors. +// +REBOOL OS_Set_Env(REBCHR *envname, REBCHR *envval) +{ + return SetEnvironmentVariable(envname, envval); +} + + +// +// OS_List_Env: C +// +REBCHR *OS_List_Env(void) +{ + wchar_t *env = GetEnvironmentStrings(); + REBCNT n, len = 0; + wchar_t *str; + + str = env; + while ((n = wcslen(str))) { + len += n + 1; + str = env + len; // next + } + len++; + + str = OS_ALLOC_N(wchar_t, len); + memmove(str, env, len * sizeof(wchar_t)); + + FreeEnvironmentStrings(env); + + return str; +} + + +// +// OS_Get_Time: C +// +// Get the current system date/time in UTC plus zone offset (mins). +// +void OS_Get_Time(REBVAL *out) +{ + SYSTEMTIME stime; + TIME_ZONE_INFORMATION tzone; + + GetSystemTime(&stime); + + if (TIME_ZONE_ID_DAYLIGHT == GetTimeZoneInformation(&tzone)) + tzone.Bias += tzone.DaylightBias; + + Convert_Date(out, -tzone.Bias, &stime); +} + + +// +// OS_Delta_Time: C +// +// Return time difference in microseconds. If base = 0, then +// return the counter. If base != 0, compute the time difference. +// +// Note: Requires high performance timer. +// Q: If not found, use timeGetTime() instead ?! +// +i64 OS_Delta_Time(i64 base, int flags) +{ + UNUSED(flags); + + LARGE_INTEGER freq; + LARGE_INTEGER time; + + if (!QueryPerformanceCounter(&time)) + OS_Crash(cb_cast("Missing resource"), cb_cast("High performance timer")); + + if (base == 0) return time.QuadPart; // counter (may not be time) + + QueryPerformanceFrequency(&freq); + + return ((time.QuadPart - base) * 1000) / (freq.QuadPart / 1000); +} + + +// +// OS_Get_Current_Dir: C +// +// Return the current directory path as a string and +// its length in chars (not bytes). +// +// The result should be freed after copy/conversion. +// +int OS_Get_Current_Dir(REBCHR **path) +{ + int len; + + len = GetCurrentDirectory(0, NULL); // length, incl terminator. + *path = OS_ALLOC_N(wchar_t, len); + GetCurrentDirectory(len, *path); + len--; // less terminator + + return len; +} + + +// +// OS_Set_Current_Dir: C +// +// Set the current directory to local path. Return FALSE +// on failure. +// +REBOOL OS_Set_Current_Dir(REBCHR *path) +{ + return SetCurrentDirectory(path); +} + + +// +// OS_File_Time: C +// +// Convert file.time to REBOL date/time format. +// Time zone is UTC. +// +void OS_File_Time(REBVAL *out, struct devreq_file *file) +{ + SYSTEMTIME stime; + TIME_ZONE_INFORMATION tzone; + + if (TIME_ZONE_ID_DAYLIGHT == GetTimeZoneInformation(&tzone)) + tzone.Bias += tzone.DaylightBias; + + FileTimeToSystemTime(cast(FILETIME *, &file->time), &stime); + Convert_Date(out, -tzone.Bias, &stime); +} + + +// +// OS_Open_Library: C +// +// Load a DLL library and return the handle to it. +// If zero is returned, error indicates the reason. +// +void *OS_Open_Library(const REBCHR *path, REBCNT *error) +{ + void *dll = LoadLibraryW(path); + *error = GetLastError(); + + return dll; +} + + +// +// OS_Close_Library: C +// +// Free a DLL library opened earlier. +// +void OS_Close_Library(void *dll) +{ + FreeLibrary((HINSTANCE)dll); +} + + +// +// OS_Find_Function: C +// +// Get a DLL function address from its string name. +// +CFUNC *OS_Find_Function(void *dll, const char *funcname) +{ + // !!! See notes about data pointers vs. function pointers in the + // definition of CFUNC. This is trying to stay on the right side + // of the specification, but OS APIs often are not standard C. So + // this implementation is not guaranteed to work, just to suppress + // compiler warnings. See: + // + // http://stackoverflow.com/a/1096349/211160 + + FARPROC fp = GetProcAddress((HMODULE)dll, funcname); + + //DWORD err = GetLastError(); + + return cast(CFUNC*, fp); +} + + +// +// OS_Reap_Process: C +// +// pid: +// > 0, a single process +// -1, any child process +// flags: +// 0: return immediately +// +// Return -1 on error +// +int OS_Reap_Process(int pid, int *status, int flags) +{ + UNUSED(pid); + UNUSED(status); + UNUSED(flags); + + // !!! It seems that process doesn't need to be reaped on Windows + return 0; +} + + +// +// OS_Request_File: C +// +REBOOL OS_Request_File(REBRFR *fr) +{ + OPENFILENAME ofn; + BOOL ret; + //int err; + const wchar_t *filters = L"All files\0*.*\0REBOL scripts\0*.r\0Text files\0*.txt\0"; + + memset(&ofn, '\0', sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + + // ofn.hwndOwner = WIN_WIN(win); // Must find a way to set this + + ofn.lpstrTitle = fr->title; + ofn.lpstrInitialDir = fr->dir; + ofn.lpstrFile = fr->files; + ofn.lpstrFilter = fr->filter ? fr->filter : filters; + ofn.nMaxFile = fr->len; + ofn.lpstrFileTitle = 0; + ofn.nMaxFileTitle = 0; + + ofn.Flags = OFN_HIDEREADONLY | OFN_EXPLORER | OFN_NOCHANGEDIR; //|OFN_NONETWORKBUTTON; //; + + if (GET_FLAG(fr->flags, FRF_MULTI)) ofn.Flags |= OFN_ALLOWMULTISELECT; + + osDialogOpen = TRUE; + + if (GET_FLAG(fr->flags, FRF_SAVE)) + ret = GetSaveFileName(&ofn); + else + ret = GetOpenFileName(&ofn); + + osDialogOpen = FALSE; + + //if (!ret) + // err = CommDlgExtendedError(); // CDERR_FINDRESFAILURE + + return ret; +} + +int CALLBACK ReqDirCallbackProc( HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData ) +{ + UNUSED(lParam); + + static REBOOL inited = FALSE; + switch (uMsg) { + case BFFM_INITIALIZED: + if (lpData) SendMessage(hWnd,BFFM_SETSELECTION,TRUE,lpData); + SetForegroundWindow(hWnd); + inited = TRUE; + break; + case BFFM_SELCHANGED: + if (inited && lpData) { + SendMessage(hWnd,BFFM_SETSELECTION,TRUE,lpData); + inited = FALSE; + } + break; + } + return 0; +} + + +// +// OS_Request_Dir: C +// +// WARNING: TEMPORARY implementation! Used only by host-core.c +// Will be most probably changed in future. +// +REBOOL OS_Request_Dir(REBCHR* title, REBCHR** folder, REBCHR* path) +{ + BROWSEINFO bi; + wchar_t buffer[MAX_PATH]; + LPCITEMIDLIST pFolder; + ZeroMemory(buffer, MAX_PATH); + ZeroMemory(&bi, sizeof(bi)); + bi.hwndOwner = NULL; + bi.pszDisplayName = buffer; + bi.lpszTitle = title; + bi.ulFlags = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS | BIF_SHAREABLE; + bi.lpfn = ReqDirCallbackProc; + bi.lParam = (LPARAM)path; + + osDialogOpen = TRUE; + pFolder = SHBrowseForFolder(&bi); + osDialogOpen = FALSE; + if (pFolder == NULL) return FALSE; + if (!SHGetPathFromIDList(pFolder, buffer) ) return FALSE; + wcscpy(*folder, buffer); + return TRUE; +} + +// +// OS_GOB_To_Image: C +// +// Render a GOB into an image. Returns an image or zero if +// it cannot be done. +// +REBVAL *OS_GOB_To_Image(REBGOB *gob) +{ +#if (defined REB_CORE) + UNUSED(gob); + return 0; +#else + return Gob_To_Image(gob); +#endif +} + +// +// OS_Get_Current_Exec: C +// +// Return the current executable path as a string and +// its length in chars (not bytes). +// +// The result should be freed after copy/conversion. +// +int OS_Get_Current_Exec(REBCHR **path) +{ + DWORD r = 0; + *path = NULL; + *path = OS_ALLOC_N(REBCHR, MAX_PATH); + if (*path == NULL) return -1; + + r = GetModuleFileName(NULL, *path, MAX_PATH); + if (r == 0) { + OS_FREE(*path); + return -1; + } + (*path)[r] = '\0'; //It might not be NULL-terminated if buffer is not big enough + + return r; +} diff --git a/src/os/windows/rpic-test.c b/src/os/windows/rpic-test.c new file mode 100644 index 0000000000..9b5f7f81e9 --- /dev/null +++ b/src/os/windows/rpic-test.c @@ -0,0 +1,97 @@ +// Copyright 2012 REBOL Technologies +// Copyright 2012-2017 Rebol Open Source Contributors +// REBOL is a trademark of REBOL Technologies +// +// See README.md and CREDITS.md for more information. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ********************************************************************** +// +// rpic-test.c - Test for REBOL Plug-In Component +// + +//#include + +#define WEIRD_INT_64 + +#include "reb-c.h" +#include "reb-plugin.h" +#include "plugin-types.h" + +#define REBSER void +#include "rpi-lib.h" + +const char *init_block = + "REBOL [\n" + "Title: {Example plugin}\n" + "Name: example\n" + "Type: plugin\n" + "Exports: [map-words cmd1 cmd2 cmd2i cmd2d cmdw cmds cmdb cmdbl]\n" + "]\n" + "map-words: command [words [block!]]\n" + "cmd1: command [a]\n" + "cmd2: command [a b]\n" + "cmd2i: command [a [integer!] b [integer!]]\n" + "cmd2d: command [a [decimal!] b [decimal!]]\n" + "cmdw: command [w]\n" + "cmds: command [str [string!] index [integer!]]\n" + "cmdb: command [blk [block!] index [integer!]]\n" + "cmdbl: command [blk [block!]]\n" +; + +RPIEXT const char *RPI_Init(int opts, RPILIB *lib) { + RPI = lib; + if (lib->version == RPI_VERSION) return init_block; + return 0; +} + +RPIEXT int RPI_Quit(int opts) { + return 0; +} + +u32 *word_ids = 0; + +RPIEXT int RPI_Call(int cmd, RPIFRM *frm) { + switch (cmd) { + case 0: + word_ids = RPI_MAP_WORDS(RPA_SERIES(frm,1)); + return RPR_TRUE; + case 1: + RPA_INT64(frm,1) = -RPA_INT64(frm,1); + break; + case 2: + case 3: + RPA_INT64(frm,1) = RPA_INT64(frm, 1) + RPA_INT64(frm, 2); + break; + case 4: + RPA_DEC64(frm,1) = RPA_DEC64(frm, 1) + RPA_DEC64(frm, 2); + break; + case 5: + RPA_INT64(frm,1) = RPI_FIND_WORD(word_ids, RPA_WORD(frm,1)); + RPA_TYPE(frm,1) = RPT_INTEGER; + break; + case 6: + RPA_INT64(frm,1) = RPI_GET_CHAR(RPA_SERIES(frm,1), (u32)RPA_INT64(frm,2)-1); + RPA_TYPE(frm,1) = RPT_INTEGER; + break; + case 7: + RPA_TYPE(frm,1) = RPI_GET_VALUE(RPA_SERIES(frm,1), (u32)RPA_INT64(frm,2)-1, &RPA_ARG(frm, 1)); + break; + case 8: + RPA_INT64(frm,1) = RPI_SERIES_INFO(RPA_SERIES(frm,1), RPI_INFO_TAIL); + RPA_TYPE(frm,1) = RPT_INTEGER; + break; + } + return RPR_VALUE; +} diff --git a/src/tools/c-lexicals.r b/src/tools/c-lexicals.r new file mode 100644 index 0000000000..45cfb90a08 --- /dev/null +++ b/src/tools/c-lexicals.r @@ -0,0 +1,180 @@ +REBOL [ + Title: "C Programming Language Lexical Definitions" + Rights: { + Copyright 2015 Brett Handley + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Author: "Brett Handley" + Purpose: {Parse C source text into preprocessing tokens.} + Description: { + Based upon N1570 Committee Draft - April 12, 2011 ISO/IEC 9899:201x + + Trigraphs are not implemented. + + Do not put any actions (e.g. executable GROUP!s in the PARSE rules) + in this file. To use these rules, copy them, call them from your + own rules or use rule injection to dynamically add emit actions. + } +] + +c.lexical: context [ + + grammar: [ + + text: [some c-pp-token] + + c-pp-token: [ + + white-space | preprocessing-token + ] + + white-space: [ + nl + | eol + | wsp + | span-comment + | line-comment + ] + + ; + ; -- A.1.1 Lexical Elements + ; Order is significant + + preprocessing-token: [ + + pp-number + | character-constant + | identifier + | string-literal + | header-name + | punctuator + | other-pp-token + ] + + other-pp-token: not-wsp + + ; + ; -- A.1.3 Identifiers + + identifier: [id.nondigit any id.char] + id.nondigit: [nondigit | universal-character-name] + + ; + ; -- A.1.4 Universal character names + + universal-character-name: [{\U} 2 hex-quad | {\u} hex-quad] + hex-quad: [4 hexadecimal-digit] + + ; + ; -- A.1.5 Constants + + character-constant: [ + #"'" some c-char #"'" + | {L'} some c-char #"'" + | {u'} some c-char #"'" + | {U'} some c-char #"'" + ] + + escape-sequence: [ + simple-escape-sequence + | octal-escape-sequence + | hexadecimal-escape-sequence + | universal-character-name + ] + + simple-escape-sequence: [ + {\'} | {\"} | {\?} | {\\} + | {\a} | {\b} | {\f} | {\n} | {\r} | {\t} | {\v} + ] + + hexadecimal-escape-sequence: [ + {\x} hexadecimal-digit any hexadecimal-digit + ] + + octal-escape-sequence: [#"\" 1 3 octal-digit] + + ; + ; -- A.1.6 String literals + + string-literal: [ + opt encoding-prefix #"^"" any s-char #"^"" + ] + encoding-prefix: [{u8} | #"L" | #"u" | #"U"] + s-char: [s-char.cs | escape-sequence] + + ; + ; -- A.1.7 Punctuators + + punctuator: [ + {->} | {++} | {--} | {<<} | {>>} | {<=} | {>=} | {==} | {!=} + | {&&} | {||} | {...} | {*=} | {/=} | {%=} | {+=} | {<<=} | {>>=} + | {&=} | {^^=} | {|=} | {##} | {<:} | {:>} | {<%} | {%>} + | {%:%:} | {%:} + | p-char + ] + + ; + ; -- A.1.8 Header names + + header-name: [#"<" some h-char #">" | #"^"" some q-char #"^""] + + ; + ; -- A.1.9 Preprocessing numbers + + pp-number: [ + [digit | #"." digit] + any [ + digit + | id.nondigit + | #"." + | [#"e" | #"p" | #"E" | #"P"] sign + ] + ] + + ; + ; -- Whitespace + + nl: {\^/} ; Line break in logical line. + eol: newline ; End of logical line. + wsp: [some ws-char] + span-comment: [{/*} thru {*/}] + line-comment: [{//} to newline] + + ] + + charsets: context [ + + ; Header name + h-char: complement charset {^/<} + q-char: complement charset {^/"} + + ; Identifier + nondigit: charset [#"_" #"a" - #"z" #"A" - #"Z"] + digit: charset {0123456789} + octal-digit: charset {01234567} + id.char: union nondigit digit + hexadecimal-digit: charset [#"0" - #"9" #"a" - #"f" #"A" - #"F"] + + ; pp-number + sign: charset {+-} + + ; character-constant + c-char: complement charset {'\^/} + + ; string-literal + s-char.cs: complement charset {"\^/} + + ; punctuator + p-char: charset "[](){}.&*+-~!/%<>^^|?:;=,#" + + ; whitespace + ws-char: charset { ^-^/^K^L} + not-wsp: complement ws-char + ] + + grammar: context bind grammar charsets + ; Grammar defined first in file. +] diff --git a/src/tools/common-emitter.r b/src/tools/common-emitter.r new file mode 100644 index 0000000000..bfabf6221e --- /dev/null +++ b/src/tools/common-emitter.r @@ -0,0 +1,141 @@ +REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Common Code for Emitting Text Files" + Rights: { + Copyright 2016 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + While emitting text files isn't exactly rocket science, it can help + to have a few sanity checks on the process. + } +] + +; !!! %make-headers.r has a dependency on BUF-EMIT, because in addition to +; just outputting data it also merges #ifdef/#endif, and does so on two files +; at once. That suggests that the emitter really needs to be an object so +; you can run multiple emit instances at once without overwriting each other, +; but the primitive bootstrap scripts weren't prepared for this case. For +; now, the buffer is exposed. +; +buf-emit: make string! 100000 + + +emit: proc [data] [ + adjoin buf-emit data +] + + +unemit: proc [ + data [char!] +][ + if data != last buf-emit [ + probe skip (tail buf-emit) -100 + fail ["UNEMIT did not match" data "as the last piece of input"] + ] + assert [data = last buf-emit] + take/last buf-emit +] + + +emit-line: proc [data /indent] [ + unless any [tail? buf-emit | newline = last buf-emit] [ + probe skip (tail buf-emit) -100 + fail "EMIT-LINE should always start a new line" + ] + data: reduce data + if find data newline [ + probe data + fail "data passed to EMIT-LINE should not contain embedded newlines" + ] + if indent [emit spaced-tab] + emit data + emit newline +] + + +emit-lines: proc [block [block!]] [ + for-each data block [emit-line data] +] + + +emit-header: proc [title [string!] file [file!]] [ + unless tail? head buf-emit [ + probe file + probe title + fail "EMIT-HEADER should only be called when the emit buffer is empty" + ] + + emit form-header/gen title file (system/script/header/file) +] + + +emit-item: proc [ + {Emits an indented identifier and comma for enums and initializer lists} + name + {Will be converted using TO-C-NAME which joins BLOCK! and forms WORD!} + /upper + {Make the name uppercase -after- the conversion using TO-C-NAME (!)} + /assign + {Give the item an assigned value} + num [integer!] +][ + name: to-c-name name + if upper [uppercase name] + either assign [ + emit-line/indent [name space "=" space num ","] + ][ + emit-line/indent [name ","] + ] + + ; NOTE: standard C++ and C do not like commas on the last item in lists, + ; so they are removed with EMIT-END, by taking the last comma out of the + ; emit buffer. +] + + +emit-annotation: procedure [ + {Adds a C++ "//"-style comment to the end of the last line emitted.} + note [word! string! integer!] +][ + unemit newline + emit [space "//" space note newline] +] + + +emit-end: proc [] [ + remove find/last buf-emit #"," + emit-line ["};"] + emit newline +] + + +write-emitted: proc [ + file + /tabbed +][ + if newline != last buf-emit [ + probe skip (tail buf-emit) -100 + fail "WRITE-EMITTED must have a NEWLINE as last character in buffer" + ] + + if find buf-emit tab-char [ + fail "tab character passed to emit" + ] + + if tabbed [ + replace/all buf-emit spaced-tab tab-char + ] + + ; Would be nice to write something here, but preferable if the begin + ; of an emit told you what was coming and then had a "...DONE" finisher. + ; + comment [print ["WRITING" file]] + + write-if-changed file buf-emit + clear buf-emit +] diff --git a/src/tools/common-parsers.r b/src/tools/common-parsers.r new file mode 100644 index 0000000000..55711562a3 --- /dev/null +++ b/src/tools/common-parsers.r @@ -0,0 +1,369 @@ +REBOL [ + System: "Ren/C Core Extraction of the Rebol System" + Title: "Common Parsers for Tools" + Rights: { + Rebol is Copyright 1997-2015 REBOL Technologies + REBOL is a trademark of REBOL Technologies + + Ren/C is Copyright 2015 MetaEducation + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Author: "@codebybrett" + Version: 2.100.0 + Needs: 2.100.100 + Purpose: { + These are some common routines used by the utilities + that build the system, which are found in %src/tools/ + } +] + +do %c-lexicals.r + +decode-key-value-text: function [ + {Decode key value formatted text.} + text [string!] +][ + + data-fields: [ + any [ + position: + data-field + | newline + ] + ] + + data-field: [ + data-field-name eof: [ + #" " to newline any [ + newline not data-field-name not newline to newline + ] + | any [1 2 newline 2 20 #" " to newline] + ] eol: (emit-meta) newline + ] + + data-field-char: charset [#"A" - #"Z" #"a" - #"z"] + data-field-name: [some data-field-char any [#" " some data-field-char] #":"] + + emit-meta: func [/local key] [ + key: replace/all copy/part position eof #" " #"-" + remove back tail key + append meta reduce [ + to word! key + trim/auto copy/part eof eol + ] + ] + + meta: make block! [] + + if not parse text data-fields [ + fail [ + {Expected key value format on line} (line-of text position) + {and lines must end with newline.} + ] + ] + + new-line/all/skip meta true 2 +] + + +decode-lines: function [ + {Decode text previously encoded using a line prefix e.g. comments (modifies).} + text [string!] + line-prefix [string! block!] {Usually "**" or "//". Matched using parse.} + indent [string! block!] {Usually " ". Matched using parse.} +] [ + pattern: compose/only [(line-prefix)] + if not empty? indent [append pattern compose/only [opt (indent)]] + line: [pos: pattern rest: (rest: remove/part pos rest) :rest thru newline] + if not parse text [any line] [ + fail [ + {Expected line} (line-of text pos) + {to begin with} (mold line-prefix) + {and end with newline.} + ] + ] + remove back tail text + text +] + + +encode-lines: func [ + {Encode text using a line prefix (e.g. comments).} + text [string!] + line-prefix [string!] {Usually "**" or "//".} + indent [string!] {Usually " ".} + /local bol pos +] [ + + ; Note: Preserves newline formatting of the block. + + ; Encode newlines. + replace/all text newline unspaced [newline line-prefix indent] + + ; Indent head if original text did not start with a newline. + pos: insert text line-prefix + if not equal? newline :pos/1 [insert pos indent] + + ; Clear indent from tail if present. + if indent = pos: skip tail text 0 - length indent [clear pos] + append text newline + + text +] + + +line-of: function [ + {Returns line number of position within text.} + text [string!] + position [string! integer!] +] [ + + if integer? position [ + position: at text position + ] + + line: _ + + count-line: [(line: 1 + any [line 0])] + + parse copy/part text next position [ + any [to newline skip count-line] skip count-line + ] + + line +] + + +load-next: function [ + {Load the next value. Return block with value and new position.} + string [string!] +] [ + out: transcode/next to binary! string + out/2: skip string subtract length string length to string! out/2 + out +] ; by @rgchris. + + +load-until-blank: function [ + {Load rebol values from text until double newline.} + text [string!] + /next {Return values and next position.} +] [ + + wsp: compose [some (charset { ^-})] + + rebol-value: parsing-at x [ + res: any [attempt [load-next x] []] + either empty? res [blank] [second res] + ] + + terminator: [opt wsp newline opt wsp newline] + + rule: [ + some [not terminator rebol-value] + opt wsp opt [1 2 newline] position: to end + ] + + either parse text rule [ + values: load copy/part text position + reduce [values position] + ][ + blank + ] +] + + +parsing-at: func [ + {Defines a rule which evaluates a block for the next input position, fails otherwise.} + 'word [word!] {Word set to input position (will be local).} + block [block!] + {Block to evaluate. Return next input position, or blank/false.} + /end {Drop the default tail check (allows evaluation at the tail).} +] [ + use [result position][ + block: compose/only [to-value (to group! block)] + if not end [ + block: compose/deep [all [not tail? (word) (block)]] + ] + block: compose/deep [result: either position: (block) [[:position]] [[end skip]]] + use compose [(word)] compose/deep [ + [(to set-word! :word) (to group! block) result] + ] + ] +] + + +proto-parser: context [ + + emit-fileheader: _ + emit-proto: _ + emit-directive: _ + proto-prefix: _ + parse.position: _ + notes: _ + lines: _ + proto.id: _ + proto.arg.1: _ + data: _ + style: _ + eoh: _ ; End of file header. + + process: func [text] [parse text grammar/rule] + + grammar: context bind [ + + rule: [ + parse.position: opt fileheader + any [parse.position: segment] + ] + + fileheader: [ + (style: data: _) + doubleslashed-lines + and is-format201603-fileheader + eoh: + ( + style: 'format201603 + emit-fileheader data + ) + ] + + segment: [ + (style: proto.id: proto.arg.1: _) + format2015-func-section + | span-comment + | line-comment any [newline line-comment] newline + | opt wsp directive + | other-segment + ] + + directive: [ + copy data [ + ["#ifndef" | "#ifdef" | "#if" | "#else" | "#elif" | "#endif"] + any [not newline c-pp-token] + ] eol + ( + emit-directive data + ) + ] + + ; We COPY/DEEP here because this part gets invasively modified by + ; the source analysis tools. + ; + other-segment: copy/deep [thru newline] + + ; we COPY/DEEP here because this part gets invasively modified by + ; the source analysis tools. + ; + format2015-func-section: copy/deep [ + doubleslashed-lines + and is-format2015-intro + function-proto some white-space + function-body + ( + style: 'format2015 + emit-proto proto + ) + ] + + function-body: #"{" + + doubleslashed-lines: [copy lines some ["//" thru newline]] + + is-format201603-fileheader: parsing-at position [ + either all [ + lines: attempt [decode-lines lines {//} { }] + parse lines [copy data to {=///} to end] + data: attempt [load-until-blank trim/auto data] + data: attempt [ + either set-word? first data/1 [data/1][blank] + ] + ][ + position ; Success. + ][ + blank + ] + ] + + is-format2015-intro: parsing-at position [ + either all [ + lines: attempt [decode-lines lines {//} { }] + data: load-until-blank lines + data: attempt [ + either set-word? first data/1 [ + notes: data/2 + data/1 + ][ + blank + ] + ] + ][ + position ; Success. + ][ + blank + ] + ] + + + ; With types being able to be parameterized macros, then function + ; prototypes can look like: + ; + ; TYPEMACRO(*) Some_Function(TYPEMACRO(const *) value, ...) + ; { ... + ; + ; !!! Matching the parentheses strings that exist in the code + ; explicitly is a maybe-temporary hack. Though as the pattern being + ; looked for is a preprocessor trick, it's outside the C spec so + ; anything will be "hacky". + ; + typemacro-parentheses: [ + "(*)" | "(const *)" + ] + + function-proto: [ + proto-prefix copy proto [ + not white-space + some [ + typemacro-parentheses + | [ + not "(" not "=" + [white-space | copy proto.id identifier | skip] + ] + ] + "(" + any white-space + opt [ + not typemacro-parentheses + not ")" + copy proto.arg.1 identifier + ] + any [typemacro-parentheses | not ")" [white-space | skip]] + ")" + ] + ] + + ] c.lexical/grammar +] + +rewrite-if-directives: function [ + {Bottom up rewrite conditional directives to remove unnecessary sections.} + position +][ + loop-until [ + parse position [ + (rewritten: false) + some [ + [ + change ["#if" thru newline "#endif" thru newline] "" + | change ["#elif" thru newline "#endif"] "#endif" + | change ["#else" thru newline "#endif"] "#endif" + ] (rewritten: true) :position + | thru newline + ] + ] + not rewritten + ] +] diff --git a/src/tools/common.r b/src/tools/common.r new file mode 100644 index 0000000000..8343dd6527 --- /dev/null +++ b/src/tools/common.r @@ -0,0 +1,371 @@ +REBOL [ + System: "Ren/C Core Extraction of the Rebol System" + Title: "Common Routines for Tools" + Rights: { + Copyright 2012-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Version: 2.100.0 + Needs: 2.100.100 + Purpose: { + These are some common routines used by the utilities + that build the system, which are found in %src/tools/ + } +] + +; !!! This file does not include the backwards compatibility %r2r3-future.r. +; The reason is that some code assumes it is running Ren-C, and that file +; disables features which are not backward compatible, which shouldn't be +; disabled if you *are* running Ren-C (e.g. the tests) + + +spaced-tab: unspaced [space space space space] + +tab-char: #"^-" ;-- only GNU Makefiles require this... +tab: does [ + fail "Don't generate files with tabs in them, use SPACED-TAB" +] + +to-c-name: function [ + {Take a Rebol value and transliterate it as a (likely) valid C identifier.} + + value [string! block! word!] + {Will be converted to a string (via UNSPACED if BLOCK!)} + /scope + {See scope rules: http://stackoverflow.com/questions/228783/} + word [word!] + {Either 'global or 'local (defaults global)} +][ + c-chars: charset [ + #"a" - #"z" + #"A" - #"Z" + #"0" - #"9" + #"_" + ] + + string: either block? :value [unspaced value][form value] + + ; Note: SWITCH/DEFAULT is deprecated in Ren-C, and ELSE is not usable in + ; R3-Alpha, required for bootstrap. Hence the wordy CASE is used here. + ; + string: case [ + ; Take care of special cases of singular symbols + + ; Used specifically by t-routine.c to make SYM_ELLIPSIS + ; + string = "..." [copy "ellipsis"] + + ; Used to make SYM_HYPHEN which is needed by `charset [#"A" - #"Z"]` + ; + string = "-" [copy "hyphen"] + + ; Used to deal with the /? refinements (which may not last) + ; + string = "?" [copy "q"] + + ; None of these are used at present, but included just in case + ; + string = "*" [copy "asterisk"] + string = "." [copy "period"] + string = "!" [copy "exclamation"] + string = "+" [copy "plus"] + string = "~" [copy "tilde"] + string = "|" [copy "bar"] + + true [ ;-- !!! See notes above, don't change to an ELSE! + ; + ; If these symbols occur composite in a longer word, they use a + ; shorthand; e.g. `true?` => `true_q` + + for-each [reb c] [ + - "_" + * "_p" ; !!! because it symbolizes a (p)ointer in C?? + . "_" ; !!! same as hyphen? + ? "_q" ; (q)uestion + ! "_x" ; e(x)clamation + + "_a" ; (a)ddition + ~ "_t" ; (t)ilde + | "_b" ; (b)ar + + ][ + replace/all string (form reb) c + ] + + string + ] + ] + + if empty? string [ + fail [ + "empty identifier produced by to-c-name for" + (mold value) "of type" (mold type-of value) + ] + ] + + comment [ + ; Don't worry about leading digits at the moment, because currently + ; the code will do a to-c-name transformation and then often prepend + ; something to it. + + if find charset [#"0" - #"9"] string/1 [ + fail ["identifier" string "starts with digit in to-c-name"] + ] + ] + + for-each char string [ + if char = space [ + ; !!! The way the callers seem to currently be written is to + ; sometimes throw "foo = 2" kinds of strings and expect them to + ; be converted to a "C string". Only check the part up to the + ; first space for legitimacy then. :-/ + break + ] + + unless find c-chars char [ + fail ["Non-alphanumeric or hyphen in" string "in to-c-name"] + ] + ] + + unless scope [word: 'global] ; default to assuming global need + + ; Easiest rule is just "never start a global identifier with underscore", + ; but we check the C rules. Since currently this routine is sometimes + ; called to produce a partial name, it may not be a problem if that part + ; starts with an underscore if something legal will be prepended. But + ; there are no instances of that need so better to plant awareness. + + case [ + string/1 != "_" [] + + word = 'global [ + fail [ + "global identifiers in C starting with underscore" + "are reserved for standard library usage" + ] + ] + + word = 'local [ + if find charset [#"A" - #"Z"] value/2 [ + fail [ + "local identifiers in C starting with underscore and then" + "a capital letter are reserved for standard library usage" + ] + ] + ] + + true [ ;-- !!! See notes above, do not change to an ELSE! + fail "scope word must be 'global or 'local" + ] + ] + + string +] + + +; http://stackoverflow.com/questions/11488616/ +binary-to-c: function [ + {Converts a binary to a string of C source that represents an initializer + for a character array. To be "strict" C standard compatible, we do not + use a string literal due to length limits (509 characters in C89, and + 4095 characters in C99). Instead we produce an array formatted as + '{0xYY, ...}' with 8 bytes per line} + + data [binary!] +][ + out: make string! 6 * (length-of data) + while [not tail? data] [ + append out spaced-tab + + ;-- grab hexes in groups of 8 bytes + hexed: enbase/base (copy/part data 8) 16 + data: skip data 8 + for-each [digit1 digit2] hexed [ + append out unspaced [{0x} digit1 digit2 {,} space] + ] + + take/last out ;-- drop the last space + if tail? data [ + take/last out ;-- lose that last comma + ] + append out newline ;-- newline after each group, and at end + ] + + ;-- Sanity check (should be one more byte in source than commas out) + parse out [(comma-count: 0) some [thru "," (comma-count: comma-count + 1)] to end] + assert [(comma-count + 1) = (length-of head data)] + + out +] + + +; !!! WARNING: Bootstrap needs to stay working with R3-Alpha. So don't +; assume this is safe for using with RETURN...because under R3-Alpha that +; will basically act as a BREAK, returning from the FOR-EACH-RECORD but not +; respecting the intention of the RETURN at the callsite. (Used to have +; the alarmist name FOR-EACH-RECORD-NO-RETURN, but that was overkill.) +; +for-each-record: procedure [ + {Iterate a table with a header by creating an object for each row} + + 'record [word!] + {Word to set each time to the row made into an object} + table [block!] + {Table of values with header block as first element} + body [block!] + {Block to evaluate each time} +][ + unless block? first table [ + fail {Table of records does not start with a header block} + ] + + headings: map-each word first table [ + unless word? word [ + fail [{Heading} word {is not a word}] + ] + to-set-word word + ] + + table: next table + + while [not tail? table] [ + if (length-of headings) > (length-of table) [ + fail {Element count isn't even multiple of header count} + ] + + spec: collect [ + for-each column-name headings [ + keep column-name + keep compose/only [quote (table/1)] + table: next table + ] + ] + + set record has spec + + do body + ] + + ; In Ren-C, to return a result this would have to be marked as returning + ; an optional value...but that syntax would confuse R3-Alpha, which this + ; has to run under. So we just don't bother returning a result. +] + + +find-record-unique: function [ + {Get a record in a table as an object, error if duplicate, blank if absent} + + ;; return: [object! blank!] + table [block!] + {Table of values with header block as first element} + key [word!] + {Object key to search for a match on} + value + {Value that the looked up key must be uniquely equal to} +][ + unless find first table key [ + fail [key {not found in table headers:} (first table)] + ] + + result: _ + for-each-record rec table [ + unless value = select rec key [continue] + + if result [ + fail [{More than one table record matches} key {=} value] + ] + + result: rec + + ; RETURN won't work when running under R3-Alpha. We could break, but + ; walk whole table to verify that it is well-formed. (Correctness is + ; more important.) + ] + result +] + + +parse-args: function [ + args ;args in form of "NAME=VALUE" +][ + ret: make block! 4 + args: any [args copy []] + unless block? args [args: split args [some " "]] + for-each a args [ + if idx: find a #"=" [ + name: to word! copy/part a (index-of idx) - 1 + value: copy next idx + append ret reduce [name value] + ] + ] + ret +] + +fix-win32-path: func [ + path [file!] + /local letter colon +][ + if 3 != fourth system/version [return path] ;non-windows system + + drive: first path + colon: second path + + if all [ + any [ + all [#"A" <= drive #"Z" >= drive] + all [#"a" <= drive #"z" >= drive] + ] + #":" = colon + ][ + insert path #"/" + remove skip path 2 ;remove ":" + ] + + path +] + +uppercase-of: func [ + {Copying variant of UPPERCASE, also FORMs words} + value [string! word!] +][ + uppercase form value +] + +lowercase-of: func [ + {Copying variant of LOWERCASE, also FORMs words} + value [string! word!] +][ + lowercase form value +] + +propercase: func [value] [uppercase/part (copy value) 1] + +propercase-of: func [ + {Make a copy of a string with just the first character uppercase} + value [string! word!] +][ + propercase form value +] + +write-if-changed: procedure [ + dest [file!] + content [any-string! block!] +][ + if block? content [ + content: spaced content + ] + + unless binary? content [ + content: to binary! content + ] + + unless all [ + exists? dest + content = read dest + ][ + write dest content + ] +] diff --git a/src/tools/file-base.r b/src/tools/file-base.r index 67d435cdac..7068fd72c8 100644 --- a/src/tools/file-base.r +++ b/src/tools/file-base.r @@ -1,205 +1,370 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Source File Database" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Author: "Carl Sassenrath" - Purpose: { - Lists of files used for creating makefiles. - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Source File Database" + Rights: { + Copyright 2012 REBOL Technologies + Copyright 2012-2017 Rebol Open Source Contributos + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + Lists of files used for creating makefiles. + } ] +; NOTE: In the following file list, a (+) preceding a file is indicative that +; the file is generated. core: [ - a-constants.c - a-globals.c - a-lib.c - b-boot.c - b-init.c - c-do.c - c-error.c - c-frame.c - c-function.c - c-port.c - c-task.c - c-word.c - d-crash.c - d-dump.c - d-print.c - f-blocks.c - f-deci.c - f-dtoa.c - f-enbase.c - f-extension.c - f-math.c - f-modify.c - f-qsort.c - f-random.c - f-round.c - f-series.c - f-stubs.c - l-scan.c - l-types.c - m-gc.c - m-pools.c - m-series.c - n-control.c - n-data.c - n-io.c - n-loop.c - n-math.c - n-sets.c - n-strings.c - n-system.c - p-clipboard.c - p-console.c - p-dir.c - p-dns.c - p-event.c - p-file.c - p-net.c - s-cases.c - s-crc.c - s-file.c - s-find.c - s-make.c - s-mold.c - s-ops.c - s-trim.c - s-unicode.c - t-bitset.c - t-block.c - t-char.c - t-datatype.c - t-date.c - t-decimal.c - t-event.c - t-function.c - t-gob.c - t-image.c - t-integer.c - t-logic.c - t-map.c - t-money.c - t-none.c - t-object.c - t-pair.c - t-port.c - t-string.c - t-time.c - t-tuple.c - t-typeset.c - t-utype.c - t-vector.c - t-word.c - u-bmp.c - u-compress.c - u-dialect.c - u-gif.c - u-jpg.c - u-md5.c - u-parse.c - u-png.c - u-sha1.c - u-zlib.c + ; (A)??? + a-constants.c + a-globals.c + a-lib.c + + ; (B)oot + b-init.c + + ; (C)ore + c-bind.c + c-do.c + c-context.c + c-error.c + c-eval.c + c-function.c + c-path.c + c-port.c + c-signal.c + c-value.c + c-word.c + + ; (D)ebug + d-break.c + d-crash.c + d-dump.c + d-eval.c + d-legacy.c + d-print.c + d-stack.c + d-trace.c + + ; (F)??? + f-blocks.c + f-deci.c + [f-dtoa.c ] + f-enbase.c + f-extension.c + f-int.c + f-math.c + f-modify.c + f-qsort.c + f-random.c + f-round.c + f-series.c + f-stubs.c + + ; (L)exer + l-scan.c + l-types.c + + ; (M)emory + m-gc.c + [m-pools.c ] + m-series.c + m-stacks.c + + ; (N)atives + n-control.c + n-data.c + n-do.c + n-error.c + n-function.c + n-io.c + n-loop.c + n-math.c + n-native.c + n-protect.c + n-reduce.c + n-sets.c + n-strings.c + n-system.c + n-textcodecs.c ; !!! should be moved to extensions + + ; (P)orts + p-clipboard.c + p-console.c + p-dir.c + p-dns.c + p-event.c + p-file.c + p-net.c + p-serial.c + p-signal.c +; p-timer.c ;--Marked as unimplemented + + ; (S)trings + s-cases.c + s-crc.c + s-file.c + s-find.c + s-make.c + s-mold.c + s-ops.c + s-trim.c + s-unicode.c + + ; (T)ypes + t-bitset.c + t-blank.c + t-block.c + t-char.c + t-datatype.c + t-date.c + t-decimal.c + t-event.c + t-function.c + t-gob.c + [t-image.c ] + t-integer.c + t-library.c + t-logic.c + t-map.c + t-money.c + t-object.c + t-pair.c + t-port.c + t-routine.c + t-string.c + t-struct.c + t-time.c + t-tuple.c + t-typeset.c + t-varargs.c + t-vector.c + t-word.c + + ; (U)??? (3rd-party code extractions) + u-compress.c + [u-md5.c ] + u-parse.c + [u-sha1.c ] + [u-zlib.c ] +] + +; Files created by the make-boot process +; +generated: [ + tmp-boot-block.c + tmp-evaltypes.c + tmp-maketypes.c + tmp-comptypes.c +] + +modules: [ + ;name module-file other-files + Crypt ../extensions/crypt/mod-crypt.c [ + ../extensions/crypt/aes/aes.c + ../extensions/crypt/bigint/bigint.c + ../extensions/crypt/dh/dh.c + ../extensions/crypt/rc4/rc4.c + ../extensions/crypt/rsa/rsa.c + ../extensions/crypt/sha256/sha256.c + ] + + Process ../extensions/process/mod-process.c [] + + LodePNG ../extensions/png/mod-lodepng.c [../extensions/png/lodepng.c] + + uPNG ../extensions/png/u-png.c [] + + GIF ../extensions/gif/mod-gif.c [] + + JPG ../extensions/jpg/mod-jpg.c [ + ; + ; The JPG sources come from elsewhere; invasive maintenance for + ; compiler rigor is not worthwhile to be out of sync with original. + ; + [ + ../extensions/jpg/u-jpg.c + + + ] + ] + + BMP ../extensions/bmp/mod-bmp.c [] + + Locale ../extensions/locale/mod-locale.c [] +] + +extensions: [ + ; [+ (builtin) | - (not builtin)] ext-name ext-file modules (defined in modules) init-script (blank if embedded) + + Crypt ../extensions/crypt/ext-crypt.c [Crypt] ../extensions/crypt/ext-crypt-init.reb + + Process ../extensions/process/ext-process.c [Process] ../extensions/process/ext-process-init.reb + + PNG ../extensions/png/ext-png.c [LodePNG uPNG] _ + + GIF ../extensions/gif/ext-gif.c [GIF] _ + + JPG ../extensions/jpg/ext-jpg.c [JPG] _ + + BMP ../extensions/bmp/ext-bmp.c [BMP] _ + + Locale ../extensions/locale/ext-locale.c [Locale] ../extensions/locale/ext-locale-init.reb ] made: [ - make-boot.r core/b-boot.c - make-headers.r include/tmp-funcs.h - make-host-ext.r include/host-ext-graphics.h - make-host-init.r include/host-init.h - make-os-ext.r include/host-lib.h - make-reb-lib.r include/reb-lib.h + make-boot.r core/tmp-boot-block.c + make-headers.r include/tmp-funcs.h + + make-host-init.r include/host-init.h + make-os-ext.r include/host-lib.h + make-reb-lib.r include/reb-lib.h ] +; +; NOTE: In the following file lists, a (+) preceding a file is indicative that +; it is to be searched for comment blocks around the function prototypes +; that indicate the function is to be gathered to be put into the host-lib.h +; exports. (This is similar to what make-headers.r does when it runs over +; the Rebol Core sources, except for the host.) +; + os: [ - host-main.c - host-args.c - host-device.c - host-stdio.c - dev-net.c - dev-dns.c + host-main.c + + host-device.c + host-stdio.c + host-table.c + dev-net.c + dev-dns.c ] -os-win32: [ - host-lib.c - dev-stdio.c - dev-file.c - dev-event.c - dev-clipboard.c -] +os-windows: [ + + generic/host-memory.c -os-win32g: [ - host-graphics.c - host-event.c - host-window.c - host-draw.c - host-text.c + + windows/host-lib.c + windows/dev-stdio.c + windows/dev-file.c + windows/dev-event.c + windows/dev-clipboard.c + windows/dev-serial.c ] os-posix: [ - host-lib.c - host-readline.c - dev-stdio.c - dev-event.c - dev-file.c + + generic/host-memory.c + + generic/host-gob.c + + posix/host-readline.c + posix/dev-stdio.c + posix/dev-event.c + posix/dev-file.c + + + posix/host-browse.c + + posix/host-config.c + + posix/host-error.c + + posix/host-library.c + + posix/host-process.c + + posix/host-time.c + + posix/host-exec-path.c +] + +os-osx: [ + + generic/host-memory.c + + generic/host-gob.c + + ; OSX uses the POSIX file I/O for now + posix/host-readline.c + posix/dev-stdio.c + posix/dev-event.c + posix/dev-file.c + posix/dev-serial.c + + + posix/host-browse.c + + posix/host-config.c + + posix/host-error.c + + posix/host-library.c + + posix/host-process.c + + posix/host-time.c + + osx/host-exec-path.c +] + +; The Rebol open source build did not differentiate between linux and simply +; posix builds. However Atronix R3/View uses a different `os-base` name. +; make-make.r requires an `os-(os-base)` entry here for each named target. +; +os-linux: [ + + generic/host-memory.c + + generic/host-gob.c + + ; Linux uses the POSIX file I/O for now + posix/host-readline.c + posix/dev-stdio.c + posix/dev-file.c + + ; It also uses POSIX for most host functions + + posix/host-config.c + + posix/host-error.c + + posix/host-library.c + + posix/host-process.c + + posix/host-time.c + + posix/host-exec-path.c + + ; Linux has some kind of MIME-based opening vs. posix /usr/bin/open + + linux/host-browse.c + + ; Atronix dev-event.c for linux depends on X11, and core builds should + ; not be using X11 as a dependency (probably) + posix/dev-event.c + + ; dev-serial should work on Linux and posix + posix/dev-serial.c + + ; Linux supports siginfo_t-style signals + linux/dev-signal.c +] + +; cloned from os-linux TODO: check'n'fix !! +os-android: [ + + generic/host-memory.c + + generic/host-gob.c + + ; Android uses the POSIX file I/O for now + posix/host-readline.c + posix/dev-stdio.c + posix/dev-file.c + + ; It also uses POSIX for most host functions + + posix/host-config.c + + posix/host-error.c + + posix/host-library.c + + posix/host-process.c + + posix/host-time.c + + posix/host-exec-path.c + + ; Android has some kind of MIME-based opening vs. posix /usr/bin/open + + linux/host-browse.c + + ; Atronix dev-event.c for linux depends on X11, and core builds should + ; not be using X11 as a dependency (probably) + posix/dev-event.c + + ; Serial should work on Android too + posix/dev-serial.c + + ; Android don't supports siginfo_t-style signals + ; linux/dev-signal.c ] boot-files: [ - version.r - graphics.r - draw.r - shape.r - text.r + version.r ] mezz-files: [ -; prot-http.r -; view-colors.r - view-funcs.r -] - -agg-files: [ - agg_arc.cpp - agg_arrowhead.cpp - agg_bezier_arc.cpp - agg_bspline.cpp - agg_curves.cpp - agg_image_filters.cpp - agg_line_aa_basics.cpp - agg_path_storage.cpp - agg_rasterizer_scanline_aa.cpp - agg_rounded_rect.cpp - agg_sqrt_tables.cpp - agg_trans_affine.cpp - agg_trans_single_path.cpp - agg_vcgen_bspline.cpp - agg_vcgen_contour.cpp - agg_vcgen_dash.cpp - agg_vcgen_markers_term.cpp - agg_vcgen_smooth_poly1.cpp - agg_vcgen_stroke.cpp - agg_vpgen_segmentator.cpp - agg_compo.cpp - agg_graphics.cpp -; agg_font_freetype.cpp - agg_font_win32_tt.cpp - agg_truetype_text.cpp -; agg_effects.cpp - compositor.cpp - graphics.cpp - rich_text.cpp + ;-- There were some of these in the R3/View build ] -tools: [ - make-host-init.r - make-host-ext.r - form-header.r +prot-files: [ + prot-tls.r + prot-http.r ] +tools: [ + make-host-init.r + make-host-ext.r + form-header.r +] diff --git a/src/tools/form-header.r b/src/tools/form-header.r index 9481acc153..c003335f81 100644 --- a/src/tools/form-header.r +++ b/src/tools/form-header.r @@ -1,26 +1,26 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Standard source code header" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Author: "Carl Sassenrath" + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Standard source code header" + Rights: { + Copyright 2012 REBOL Technologies + Copyright 2012-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } ] bv: load %../boot/version.r form-header: func [title [string!] file [file!] /gen by] [ - print ["..." title] - by: either gen [ - rejoin [{** AUTO-GENERATED FILE - Do not modify. (From: } by {)^/**^/}] - ][""] + print ["..." title] + by: either gen [ + unspaced [{** AUTO-GENERATED FILE - Do not modify. (From: } by {)^/**^/}] + ][""] - rejoin [ + unspaced [ {/*********************************************************************** ** ** REBOL [R3] Language Interpreter and Run-time Environment @@ -33,10 +33,9 @@ form-header: func [title [string!] file [file!] /gen by] [ ** ** Title: } title { ** Build: A} bv/3 { -** Date: } now/date { ** File: } file { ** -} by +} by {***********************************************************************/ } diff --git a/src/tools/make-boot-ext-header.r b/src/tools/make-boot-ext-header.r new file mode 100644 index 0000000000..e7188365f5 --- /dev/null +++ b/src/tools/make-boot-ext-header.r @@ -0,0 +1,67 @@ + +REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Generate extention native header files" + File: %make-boot-ext-header.r;-- used by EMIT-HEADER to indicate emitting script + Rights: { + Copyright 2017 Atronix Engineering + Copyright 2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Needs: 2.100.100 +] + +do %r2r3-future.r +do %common.r +do %common-emitter.r +do %form-header.r + +r3: system/version > 2.100.0 + +args: parse-args system/options/args +output-dir: fix-win32-path to file! any [:args/OUTDIR %../] +mkdir/deep output-dir/include + +extensions: either any-string? :args/EXTENSIONS [split args/EXTENSIONS #","][[]] + +emit-header "Boot Modules" output-dir/include/tmp-boot-extensions.h +remove-each ext extensions [empty? ext] ;SPLIT in r3-a111 gives an empty "" at the end +either empty? extensions [ + emit-lines [ + "#define LOAD_BOOT_EXTENSIONS(ext)" + "#define SHUTDOWN_BOOT_EXTENSIONS()" + ] +] [ + for-each ext extensions [ + emit-line ["DECLARE_EXT_INIT(" ext ");"] + emit-line ["DECLARE_EXT_QUIT(" ext ");"] + ] + + emit-line [] + emit-line ["static CFUNC *Boot_Extensions [] = {"] + for-each ext extensions [ + emit-line/indent ["cast(CFUNC *, EXT_INIT(" ext ")),"] + emit-line/indent ["cast(CFUNC *, EXT_QUIT(" ext ")),"] + ] + emit-end + + emit-line [] + + emit-lines [ + "#define LOAD_BOOT_EXTENSIONS(ext) do {\" + "Prepare_Boot_Extensions(ext, Boot_Extensions, sizeof(Boot_Extensions)/sizeof(CFUNC *));\" + "} while (0)" + ] + + emit-lines [ + "#define SHUTDOWN_BOOT_EXTENSIONS() do {\" + "Shutdown_Boot_Extensions(Boot_Extensions, sizeof(Boot_Extensions)/sizeof(CFUNC *));\" + "} while (0)" + ] +] + +write-emitted output-dir/include/tmp-boot-extensions.h diff --git a/src/tools/make-boot.r b/src/tools/make-boot.r index 58f6951459..64c75b144d 100644 --- a/src/tools/make-boot.r +++ b/src/tools/make-boot.r @@ -1,76 +1,76 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Make primary boot files" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Author: "Carl Sassenrath" - Version: 2.100.0 - Needs: 2.100.100 - Purpose: { - A lot of the REBOL system is built by REBOL, and this program - does most of the serious work. It generates most of the C include - files required to compile REBOL. - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Make primary boot files" + File: %make-boot.r ;-- used by EMIT-HEADER to indicate emitting script + Rights: { + Copyright 2012 REBOL Technologies + Copyright 2012-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Version: 2.100.0 + Needs: 2.100.100 + Purpose: { + A lot of the REBOL system is built by REBOL, and this program + does most of the serious work. It generates most of the C include + files required to compile REBOL. + } ] print "--- Make Boot : System Embedded Script ---" +do %r2r3-future.r +do %common.r +do %common-emitter.r + do %form-header.r -; Set platform TARGET do %systems.r -target: config-system/define ; default - -; Include graphics for these systems: -graphics-targets: [ - TO_WIN32 -] -has-graphics: false ;not not find graphics-targets target - -opts: system/options/args - -if all [block? opts opts/1 = ">"] [opts: none] ; cw editor - -if block? opts [ - if find opts "no-gfx" [ - has-graphics: false - opts: next opts - ] - if not tail? opts [ - opts: load first opts - unless tuple? opts [print "Invalid version arg." wait 2 quit] - target: config-system/platform opts - ] +args: parse-args system/options/args +config: config-system to-value :args/OS_ID + +first-rebol-commit: "19d4f969b4f5c1536f24b023991ec11ee6d5adfb" + +either args/GIT_COMMIT = "unknown" [ + ; + ; !!! If we used blank here, then R3-Alpha would render it as the word + ; "none" which is not defined during the execution of %sysobj.r, so by + ; using '_ it will act as a WORD! in R3-Alpha, and render as _. + ; + ; + git-commit: either word? first [_] [ + '_ ;-- R3-Alpha is being used for bootstrap + ][ + _ ;-- Ren-C is being used for bootstrap + ] +][ + git-commit: args/GIT_COMMIT + if (length-of git-commit) != (length-of first-rebol-commit) [ + print ["GIT_COMMIT should be a full hash, e.g." first-rebol-commit] + print ["Invalid hash was:" git-commit] + quit + ] ] -write-if: func [file data] [ - if data <> attempt [read file][ - print ["UPDATE:" file] - write file data - ] -] ;-- SETUP -------------------------------------------------------------- change-dir %../boot/ ;dir: %../core/temp/ ; temporary definition -inc: %../include/ -src: %../core/ +output-dir: fix-win32-path to file! any [:args/OUTDIR %../] +mkdir/deep output-dir/include +mkdir/deep output-dir/boot +mkdir/deep output-dir/core +inc: output-dir/include +src: output-dir/core +boot: output-dir/boot version: load %version.r -either tuple? opts [ - version/4: opts/2 - version/5: opts/3 -][ - version/4: system/version/4 - version/5: system/version/5 -] +version/4: config/id/2 +version/5: config/id/3 ;-- Title string put into boot.h file checksum: Title: @@ -81,35 +81,47 @@ Licensed under the Apache License, Version 2.0 } sections: [ - boot-types - boot-words - boot-root - boot-task - boot-strings - boot-booters - boot-actions - boot-natives - boot-ops - boot-typespecs - boot-errors - boot-sysobj - boot-base - boot-sys - boot-mezz - boot-protocols -; boot-script + boot-types + boot-words + boot-root + boot-task + boot-actions + boot-natives + boot-typespecs + boot-errors + boot-sysobj + boot-base + boot-sys + boot-mezz +; boot-script ] -include-protocols: false ; include protocols in build - -;-- Error handler: -error: func [msg arg] [print ["*** Make-boot error:" msg arg] halt] - -;-- Args passed: platform, product -if none? args: system/options/args [error "No platform specified." ""] +; Args passed: platform, product +; +; !!! Heed /script/args so you could say e.g. `do/args %make-boot.r [0.3.01]` +; Note however that current leaning is that scripts called by the invoked +; process will not have access to the "outer" args, hence there will be only +; one "args" to be looked at in the long run. This is an attempt to still +; be able to bootstrap under the conditions of the A111 rebol.com R3-Alpha +; as well as function either from the command line or the REPL. +; +unless args: any [ + if string? :system/script/args [ + either block? load system/script/args [ + load system/script/args + ][ + reduce [load system/script/args] + ] + ] + :system/script/args + + ; This is the only piece that should be necessary if not dealing w/legacy + system/options/args +] [ + fail "No platform specified." +] -if args/1 = ">" [args: ["Win32" "VIEW-PRO"]] ; for debugging only -product: to-word any [args/2 "core"] +product: to-word any [:args/PRODUCT | "core"] platform-data: context [type: 'windows] build: context [features: [help-strings]] @@ -119,113 +131,6 @@ build: context [features: [help-strings]] ;platform-data: platforms/:platform ;build: platform-data/builds/:product -;-- UTILITIES ---------------------------------------------------------- - -up-word: func [w] [ - w: uppercase form w - foreach [f t] [ - #"-" #"_" - ][replace/all w f t] - w -] - -;-- Emit Function -out: make string! 100000 -emit: func [data] [repend out data] - -to-c-name: func [word] [ - word: form word - foreach [f t] [ - #"-" #"_" - #"." #"_" - #"?" #"q" - #"!" #"x" - #"~" "" - #"*" "_p" - #"+" "_add" - #"|" "or_bar" - ][replace/all word f t] - word -] - -emit-enum: func [word] [emit [tab to-c-name word "," newline]] - -emit-line: func [prefix word cmt /var /define /code /decl /up1 /local str][ - - str: to-c-name word - - if word = 0 [prefix: ""] - - if not any [code decl] [ - either var [uppercase/part str 1] [uppercase str] - ] - - if up1 [uppercase/part str 1] - - str: any [ - if define [rejoin [prefix str]] - if code [rejoin [" " prefix str cmt]] - if decl [rejoin [prefix str cmt]] - rejoin [" " prefix str ","] - ] - if any [code decl] [cmt: none] - if cmt [ - len: 31 - length? str - loop to-integer len / 4 [append str tab] - any [ - if define [repend str cmt] - if cmt [repend str ["// " cmt]] - ] - ] - append str newline - append out str -] - -emit-head: func [title [string!] file [file!]] [ - clear out - emit form-header/gen title file %make-boot.r -] - -emit-end: func [/easy] [ - if not easy [remove find/last out #","] - append out {^};^/} -] - -binary-to-c: either system/version/4 = 3 [ - ; Windows format: - func [comp-data /local out] [ - out: make string! 4 * (length? comp-data) - forall comp-data [ - out: insert out reduce [to-integer first comp-data ", "] - if zero? ((index? comp-data) // 10) [out: insert out "^/^-"] - ] -; remove/part out either (pick out -1) = #" " [-2][-4] - head out - ] -][ - ; Other formats (Linux, OpenBSD, etc.): - func [comp-data /local out data] [ - out: make string! 4 * (length? comp-data) - forall comp-data [ - data: copy/part comp-data 16 - comp-data: skip comp-data 15 - data: enbase/base data 16 - forall data [ - insert data "\x" - data: skip data 3 - ] - data: tail data - insert data {"^/} - append out {"} - append out head data - ] - head out - ] -] - -remove-tests: func [d] [ - while [d: find d #test][remove/part d 2] -] ;---------------------------------------------------------------------------- ; @@ -234,79 +139,77 @@ remove-tests: func [d] [ ;---------------------------------------------------------------------------- boot-types: load %types.r -type-record: [type evalclass typeclass moldtype formtype haspath maker typesets] -emit-head "Evaluation Maps" %evaltypes.h -emit { -/*********************************************************************** -** -*/ const REBINT Eval_Type_Map[REB_MAX] = -/* -** Specifies the evaluation method used for each datatype. -** -***********************************************************************/ -^{ -} -foreach :type-record boot-types [ - emit-line "ET_" evalclass type -] -emit-end +emit-header "Evaluation Maps" %evaltypes.h + emit { -/*********************************************************************** -** -*/ const REBDOF Func_Dispatch[] = -/* -** The function evaluation dispatchers. -** -***********************************************************************/ -^{ -} -foreach :type-record boot-types [ - if find [function operator] evalclass [ - emit-line/var "Do_" type none - ] -] -emit-end +#include "sys-core.h" -emit { /*********************************************************************** ** -*/ const REBACT Value_Dispatch[REB_MAX] = +*/ const REBACT Value_Dispatch[REB_MAX] = /* -** The ACTION dispatch function for each datatype. +** The ACTION dispatch function for each datatype. ** ***********************************************************************/ -^{ } - -foreach :type-record boot-types [ - emit-line/var "T_" typeclass type +emit-line "{" + +for-each-record type boot-types [ + if group? type/class [type/class: first type/class] + + case [ + type/class = 0 [ ; REB_0 should not ever be dispatched, bad news + emit-item "NULL" + ] + type/class = '+ [ ; Extension types just fail until registered + emit-item "T_Fail" + ] + true [ ;-- R3-Alpha needs to bootstrap, do not convert to an ELSE! + ; + ; All other types should have handlers + ; + emit-item ["T_" propercase-of type/class] + ] + ] + emit-annotation type/name ] emit-end + + emit { + /*********************************************************************** ** -*/ const REBPEF Path_Dispatch[REB_MAX] = +*/ const REBPEF Path_Dispatch[REB_MAX] = /* -** The path evaluator function for each datatype. +** The path evaluator function for each datatype. ** ***********************************************************************/ -^{ } - -foreach :type-record boot-types [ - emit-line/var "PD_" switch/default haspath [ - * [typeclass] - - [0] - ][haspath] type +emit-line "{" + +for-each-record type boot-types [ + if group? type/class [type/class: first type/class] + + either type/path = '- [ + emit-item "PD_Fail" + ][ + emit-item [ + "PD_" propercase-of ( + either type/path = '* [type/class] [type/path] + ) + ] + ] + emit-annotation type/name ] emit-end -write inc/tmp-evaltypes.h out +write-emitted src/tmp-evaltypes.c ;---------------------------------------------------------------------------- @@ -315,45 +218,73 @@ write inc/tmp-evaltypes.h out ; ;---------------------------------------------------------------------------- -emit-head "Datatype Makers" %maketypes.h +emit-header "Datatype Makers" %maketypes.h emit newline -types-used: [] - -foreach :type-record boot-types [ - if all [ - maker = '* - word? typeclass - not find types-used typeclass - ][ - emit-line/up1/decl "extern REBFLG MT_" typeclass "(REBVAL *, REBVAL *, REBCNT);" - append types-used typeclass - ] +types-used: copy [] + +for-each-record type boot-types [ + if group? type/class [type/class: first type/class] + + if all [ + type/make = '* + word? type/class + not find types-used type/class + ][ + append types-used type/class + ] ] emit { +#include "sys-core.h" + /*********************************************************************** ** -*/ const MAKE_FUNC Make_Dispatch[REB_MAX] = +*/ const MAKE_FUNC Make_Dispatch[REB_MAX] = /* -** Specifies the make method used for each datatype. +** Specifies the make method used for each datatype. ** ***********************************************************************/ -^{ } - -foreach :type-record boot-types [ - either maker = '* [ - emit-line/var "MT_" typeclass type - ][ - emit-line "" "0" type - ] +emit-line "{" +for-each-record type boot-types [ + if group? type/class [type/class: first type/class] + + either type/make = '* [ + emit-item ["MAKE_" propercase-of type/class] + ][ + emit-item "MAKE_Fail" + ] + emit-annotation type/name ] +emit-end + +emit { +/*********************************************************************** +** +*/ const TO_FUNC To_Dispatch[REB_MAX] = +/* +** Specifies the TO method used for each datatype. +** +***********************************************************************/ +} +emit-line "{" +for-each-record type boot-types [ + if group? type/class [type/class: first type/class] + + either type/make = '* [ + emit-item ["TO_" propercase-of type/class] + ][ + emit-item "TO_Fail" + ] + emit-annotation type/name +] emit-end -write inc/tmp-maketypes.h out +write-emitted src/tmp-maketypes.c + ;---------------------------------------------------------------------------- ; @@ -361,299 +292,214 @@ write inc/tmp-maketypes.h out ; ;---------------------------------------------------------------------------- -emit-head "Datatype Comparison Functions" %comptypes.h +emit-header "Datatype Comparison Functions" %comptypes.h emit newline -types-used: [] +types-used: copy [] -foreach :type-record boot-types [ - if all [ - word? typeclass - not find types-used typeclass - ][ - emit-line/up1/decl "extern REBINT CT_" typeclass "(REBVAL *, REBVAL *, REBINT);" - append types-used typeclass - ] +for-each-record type boot-types [ + if group? type/class [type/class: first type/class] + + if all [ + word? type/class + not find types-used type/class + ][ + append types-used type/class + ] ] emit { + +#include "sys-core.h" + /*********************************************************************** ** -*/ const REBCTF Compare_Types[REB_MAX] = +*/ const REBCTF Compare_Types[REB_MAX] = /* -** Type comparision functions. +** Type comparision functions. ** ***********************************************************************/ -^{ } - -foreach :type-record boot-types [ - emit-line/var "CT_" typeclass type +emit-line "{" +for-each-record type boot-types [ + if group? type/class [type/class: first type/class] + + case [ + type/class = 0 [ + emit-item "NULL" + ] + type/class = '+ [ + emit-item "CT_Fail" + ] + true [ ;-- R3-Alpha needs to bootstrap, do not convert to an ELSE! + emit-item ["CT_" propercase-of type/class] + ] + ] + emit-annotation type/name ] emit-end -write inc/tmp-comptypes.h out +write-emitted src/tmp-comptypes.c -;---------------------------------------------------------------------------- -; -; Moldtypes.h - Dispatchers for Mold and Form -; -;---------------------------------------------------------------------------- - -;emit-head "Mold Dispatchers" -; -;emit { -;/*********************************************************************** -;** -;*/ const MOLD_FUNC Mold_Dispatch[REB_MAX] = -;/* -;** The MOLD dispatch function for each datatype. -;** -;***********************************************************************/ -;^{ -;} -; -;foreach :type-record boot-types [ -; f: "Mold_" -; switch/default moldtype [ -; * [t: typeclass] -; + [t: type] -; - [t: 0] -; ][t: uppercase/part form moldtype 1] -; emit [tab "case " uppercase join "REB_" type ":" tab "\\" t] -; emit newline -; ;emit-line/var f t type -;] -;emit-end -; -;emit { -;/*********************************************************************** -;** -;*/ const MOLD_FUNC Form_Dispatch[REB_MAX] = -;/* -;** The FORM dispatch function for each datatype. -;** -;***********************************************************************/ -;^{ -;} -;foreach :type-record boot-types [ -; f: "Mold_" -; switch/default formtype [ -; * [t: typeclass] -; f* [t: typeclass f: "Form_"] -; + [t: type] -; f+ [t: type f: "Form_"] -; - [t: 0] -; ][t: uppercase/part form moldtype 1] -; emit [tab "case " uppercase join "REB_" type ":" tab "\\" t] -; emit newline -; ;emit-line/var f t type -;] -;emit-end -; -;write inc/tmp-moldtypes.h out - ;---------------------------------------------------------------------------- ; ; Bootdefs.h - Boot include file ; ;---------------------------------------------------------------------------- -emit-head "Datatype Definitions" %reb-types.h +emit-header "Datatype Definitions" %reb-types.h -emit [ -{ +emit { /*********************************************************************** ** -*/ enum REBOL_Types +*/ enum Reb_Kind /* -** Internal datatype numbers. These change. Do not export. +** Internal datatype numbers. These change. Do not export. ** ***********************************************************************/ -^{ } -] +emit-line "{" -datatypes: [] +datatypes: copy [] n: 0 -foreach :type-record boot-types [ - append datatypes type - emit-line "REB_" type n - n: n + 1 -] -emit { REB_MAX -^}; -} -emit { -/*********************************************************************** -** -** REBOL Type Check Macros -** -***********************************************************************/ -} +for-each-record type boot-types [ + append datatypes type/name -new-types: [] -foreach :type-record boot-types [ - append new-types to-word join type "!" - str: uppercase form type - replace/all str #"-" #"_" - def: join {#define IS_} [str "(v)"] - len: 31 - length? def - loop to-integer len / 4 [append def tab] - emit [def "(VAL_TYPE(v)==REB_" str ")" newline] + either type/name = 0 [ + emit-item/assign "REB_0" 0 + ][ + emit-item/assign/upper ["REB_" type/name] n + ] + emit-annotation n + + n: n + 1 ] +emit-item/assign "REB_MAX" n +emit-annotation n +emit-end + emit { /*********************************************************************** ** -** REBOL Typeset Defines +** REBOL Type Check Macros ** ***********************************************************************/ } -typeset-sets: [] - -foreach :type-record boot-types [ - typesets: compose [(typesets)] - foreach ts typesets [ - spot: any [ - select typeset-sets ts - first back insert tail typeset-sets reduce [ts copy []] - ] - append spot type - ] -] -remove/part typeset-sets 2 ; the - markers - -foreach [ts types] typeset-sets [ - emit ["#define TS_" up-word ts " ("] - foreach t types [ - emit ["((REBU64)1< or < might start failing. ; -; Extension Related Tables +; !!! Consider ways of making this more robust. ; -;---------------------------------------------------------------------------- +emit { +#define IS_ANY_VALUE(v) \ + LOGICAL(VAL_TYPE(v) != REB_MAX_VOID) -ext-types: load %types-ext.r -rxt-record: [type offset size] +#define ANY_SCALAR(v) \ + LOGICAL(VAL_TYPE(v) >= REB_LOGIC && VAL_TYPE(v) <= REB_DATE) -; Generate type table with necessary gaps -rxt-types: [] -n: 0 -foreach :rxt-record ext-types [ - if integer? offset [ - insert/dup tail rxt-types 0 offset - n - n: offset - ] - append rxt-types type - n: n + 1 -] +#define ANY_SERIES(v) \ + LOGICAL(VAL_TYPE(v) >= REB_PATH && VAL_TYPE(v) <= REB_VECTOR) +#define ANY_STRING(v) \ + LOGICAL(VAL_TYPE(v) >= REB_STRING && VAL_TYPE(v) <= REB_TAG) -emit-head "Extension Types (Isolators)" %ext-types.h +#define ANY_BINSTR(v) \ + LOGICAL(VAL_TYPE(v) >= REB_BINARY && VAL_TYPE(v) <= REB_TAG) -emit [ -{ -enum REBOL_Ext_Types -^{ -} -] -n: 0 -foreach :rxt-record ext-types [ - either integer? offset [ - emit-line "RXT_" rejoin [type " = " offset] n - ][ - emit-line "RXT_" type n - ] - n: n + 1 -] -emit { RXT_MAX -^}; +inline static REBOOL ANY_ARRAY_KIND(enum Reb_Kind k) { + return LOGICAL(k >= REB_PATH && k <= REB_BLOCK); } -write inc/ext-types.h out ; part of Host-Kit distro +#define ANY_ARRAY(v) \ + ANY_ARRAY_KIND(VAL_TYPE(v)) -emit-head "Extension Type Equates" %tmp-exttypes.h -emit { -/*********************************************************************** -** -*/ const REBYTE Reb_To_RXT[REB_MAX] = -/* -***********************************************************************/ -^{ +inline static REBOOL ANY_WORD_KIND(enum Reb_Kind k) { + return LOGICAL(k >= REB_WORD && k <= REB_ISSUE); } -foreach :type-record boot-types [ - either find ext-types type [ - emit-line "RXT_" type type - ][ - emit-line "" 0 type - ] -] -emit-end +#define ANY_WORD(v) \ + ANY_WORD_KIND(VAL_TYPE(v)) -emit { -/*********************************************************************** -** -*/ const REBYTE RXT_To_Reb[RXT_MAX] = -/* -***********************************************************************/ -^{ +#define ANY_PATH(v) \ + LOGICAL(VAL_TYPE(v) >= REB_PATH && VAL_TYPE(v) <= REB_LIT_PATH) + +#define ANY_EVAL_BLOCK(v) \ + LOGICAL(VAL_TYPE(v) == REB_BLOCK || VAL_TYPE(v) == REB_GROUP) + +inline static REBOOL ANY_CONTEXT_KIND(enum Reb_Kind k) { + return LOGICAL(k >= REB_OBJECT && k <= REB_PORT); } -n: 0 -foreach type rxt-types [ - either word? type [emit-line "REB_" type n][ - emit-line "" 0 n - ] - n: n + 1 -] -emit-end +#define ANY_CONTEXT(v) \ + ANY_CONTEXT_KIND(VAL_TYPE(v)) + +} emit { /*********************************************************************** ** -*/ const REBCNT RXT_Eval_Class[RXT_MAX] = -/* +** REBOL Typeset Defines +** ***********************************************************************/ -^{ + +// User-facing typesets, such as ANY-VALUE!, do not include void (absence of +// a value) nor the internal "REB_0" type +// +#define TS_VALUE ((FLAGIT_KIND(REB_MAX_VOID) - 1) - FLAGIT_KIND(REB_0)) } -n: 0 -foreach type rxt-types [ - either all [ - word? type - rec: find ext-types type - ][ - emit-line "RXE_" rec/3 rec/1 - ][ - emit-line "" 0 n - ] - n: n + 1 +typeset-sets: copy [] + +for-each-record type boot-types [ + for-each ts compose [(type/typesets)] [ + spot: any [ + select typeset-sets ts + first back insert tail typeset-sets reduce [ts copy []] + ] + append spot type/name + ] ] -emit-end +remove/part typeset-sets 2 ; the - markers -emit { -#define RXT_ALLOWED_TYPES (} -foreach type next rxt-types [ - if word? type [ - emit replace join "((u64)" uppercase rejoin ["1< 1 [ - foreach field words-of obj [ ;R3 - f: join prefix [field #"_"] - replace/all f "-" "_" - all [ - field <> 'standard - object? get in obj field - make-obj-defs obj/:field f depth - 1 - ] - ] - ] +ob: has boot-sysobj + +make-obj-defs: procedure [obj prefix depth /selfless] [ + prefix: uppercase-of prefix + emit-line ["enum " prefix "object {"] + + either selfless [ + ; + ; Make sure *next* value starts at 1. Keys/vars in contexts start + ; at 1, and if there's no "userspace" self in the 1 slot, the first + ; key has to be...so we make `SYS_CTX_0 = 0` (for instance) + ; + emit-item/assign [prefix "0"] 0 + ][ + ; The internal generator currently puts SELF at the start of new + ; objects in key slot 1, by default. Eventually MAKE OBJECT! will + ; have nothing to do with adding SELF, and it will be entirely a + ; by-product of generators. + ; + emit-item/assign [prefix "SELF"] 1 + ] + + for-each field words-of obj [ + emit-item/upper [prefix field] + ] + emit-item [prefix "MAX"] + emit-end + + if depth > 1 [ + for-each field words-of obj [ + if all [ + field != 'standard + object? get in obj field + ][ + extended-prefix: uppercase to-c-name [prefix field "_"] + make-obj-defs obj/:field extended-prefix (depth - 1) + ] + ] + ] ] make-obj-defs ob "SYS_" 1 @@ -831,28 +671,7 @@ make-obj-defs ob/options "OPTIONS_" 4 make-obj-defs ob/locale "LOCALE_" 4 make-obj-defs ob/view "VIEW_" 4 -write inc/tmp-sysobj.h out - -;---------------------------------------------------------------------------- - -emit-head "Dialects" %reb-dialect.h -emit { -enum REBOL_dialect_error { - REB_DIALECT_END = 0, // End of dialect block - REB_DIALECT_MISSING, // Requested dialect is missing or not valid - REB_DIALECT_NO_CMD, // Command needed before the arguments - REB_DIALECT_BAD_SPEC, // Dialect spec is not valid - REB_DIALECT_BAD_ARG, // The argument type does not match the dialect - REB_DIALECT_EXTRA_ARG // There are more args than the command needs -}; - -} -make-obj-defs ob/dialects "DIALECTS_" 4 - -emit {#define DIALECT_LIT_CMD 0x1000 -} - -write inc/reb-dialect.h out +write-emitted inc/tmp-sysobj.h ;---------------------------------------------------------------------------- @@ -861,25 +680,25 @@ write inc/reb-dialect.h out ; ;---------------------------------------------------------------------------- -emit-head "Event Types" %reb-evtypes.h +emit-header "Event Types" %reb-evtypes.h emit newline -emit ["enum event_types {" newline] -foreach field ob/view/event-types [ - emit-line "EVT_" field none +emit-line ["enum event_types {"] +for-each field ob/view/event-types [ + emit-item/upper ["EVT_" field] ] -emit [tab "EVT_MAX^/"] -emit "};^/^/" +emit-item "EVT_MAX" +emit-end -emit ["enum event_keys {" newline] -emit-line "EVK_" "NONE" none -foreach field ob/view/event-keys [ - emit-line "EVK_" field none +emit-line ["enum event_keys {"] +emit-item "EVK_NONE" +for-each field ob/view/event-keys [ + emit-item/upper ["EVK_" field] ] -emit [tab "EVK_MAX^/"] -emit "};^/^/" +emit-item "EVK_MAX" +emit-end -write inc/reb-evtypes.h out +write-emitted inc/reb-evtypes.h ;---------------------------------------------------------------------------- @@ -890,85 +709,165 @@ write inc/reb-evtypes.h out ;-- Error Structure ---------------------------------------------------------- -emit-head "Error Structure and Constants" %errnums.h +emit-header "Error Structure and Constants" %errnums.h emit { -#ifdef VAL_TYPE /*********************************************************************** ** -*/ typedef struct REBOL_Error_Obj +*/ typedef struct REBOL_Error_Vars /* ***********************************************************************/ -^{ } +emit-line "{" + ; Generate ERROR object and append it to bootdefs.h: -emit-line/code "REBVAL " 'self ";" ;R3 -foreach word words-of ob/standard/error [ ;R3 - if word = 'near [word: 'nearest] ; prevents C problem - emit-line/code "REBVAL " word ";" +emit-line/indent "REBVAL self;" +for-each word words-of ob/standard/error [ + either word = 'near [ + emit-line/indent ["REBVAL nearest;"] + emit-annotation "near/far are non-standard C keywords" + ][ + emit-line/indent ["REBVAL" space (to-c-name word) ";"] + ] + ] -emit {^} ERROR_OBJ; -#endif -} +emit-line "} ERROR_VARS;" emit { /*********************************************************************** ** -*/ enum REBOL_Errors +*/ enum REBOL_Errors /* ***********************************************************************/ -^{ } +emit-line "{" boot-errors: load %errors.r -err-list: make block! 200 -errs: false - -foreach [cat msgs] boot-errors [ - code: second msgs - new1: true - foreach [word val] skip msgs 4 [ - err: uppercase form to word! word ;R3 - replace/all err "-" "_" - if find err-list err [print ["DUPLICATE Error Constant:" err] errs: true] - append err-list err - either new1 [ - emit-line "RE_" reform [err "=" code] reform [code mold val] - new1: false - ][ - emit-line "RE_" err reform [code mold val] - ] - code: code + 1 - ] - emit-line "RE_" join to word! cat "_max" none ;R3 - emit newline + +id-list: make block! 200 + +for-each [category info] boot-errors [ + unless all [ + (quote code:) == info/1 + integer? info/2 + (quote type:) == info/3 + string? info/4 + ][ + fail ["%errors.r" category "not [code: INTEGER! type: STRING! ...]"] + ] + + code: info/2 + + new-section: true + for-each [key val] skip info 4 [ + unless set-word? key [ + fail ["Non SET-WORD! key in %errors.r:" key] + ] + + id: to-word key + if find (extract id-list 2) id [ + fail ["DUPLICATE id in %errors.r:" id] + ] + + append id-list reduce [id val] + + either new-section [ + emit-item/assign/upper ["RE_" id] code + new-section: false + ][ + emit-item/upper ["RE_" id] + ] + emit-annotation spaced [code mold val] + + code: code + 1 + ] + emit-item ["RE_" (uppercase-of to word! category) "_MAX"] + emit newline ] -if errs [wait 3 quit] + emit-end -emit { -#define RE_NOTE RE_NO_LOAD -#define RE_USER RE_MESSAGE -} +emit-line {#define RE_USER MAX_I32} +emit-annotation {Hardcoded, update in %make-boot.r} + +emit-line {#define RE_CATEGORY_SIZE 1000} +emit-annotation {Hardcoded, update in %make-boot.r} -write inc/tmp-errnums.h out +emit-line {#define RE_INTERNAL_FIRST RE_MISC} +emit-annotation {GENERATED! update in %make-boot.r} + +emit-line {#define RE_MAX RE_COMMAND_MAX} +emit-annotation {GENERATED! update in %make-boot.r} +write-emitted inc/tmp-errnums.h ;------------------------------------------------------------------------- +emit-header "Error functions" %error-funcs.h +for-each [id val] id-list [ + n-args: 0 + if block? val [ + parse val [ + any [ + get-word! (++ n-args) + | skip + ] + ] + ] + + emit-line [] + emit-line ["// " mold val] + c-id: to-c-name id + f-name: uppercase/part copy c-id 1 + parse f-name [ + any [ + #"_" w: (uppercase/part w 1) + | skip + ] + ] + either zero? n-args [ + emit-line [ {static inline REBCTX *Error_} f-name {_Raw(void)}] + emit-line [ "^{" ] + emit-line/indent [ "return Error(RE_" uppercase c-id ", END);" ] + emit-line [ "^}" ] + ][ + emit-line [ {static inline REBCTX *Error_} f-name {_Raw(} ] + i: 0 + while [i < n-args] [ + emit-line compose [ {const RELVAL *arg} (i + 1) + either i < (n-args - 1) [","] [""] + ] + ++ i + ] + emit-line [")"] + emit-line [ "^{" ] + + args: copy "" + i: 0 + while [i < n-args] [ + append args compose [ {, arg} (i + 1)] + ++ i + ] + + emit-line/indent [ "return Error(RE_" uppercase c-id args ", END);"] + emit-line [ "^}" ] + ] +] +write-emitted inc/tmp-error-funcs.h -emit-head "Port Modes" %port-modes.h +;------------------------------------------------------------------------- + +emit-header "Port Modes" %port-modes.h data: load %modes.r -emit { -enum port_modes ^{ -} +emit newline +emit-line "enum port_modes {" -foreach word data [ - emit-enum word +for-each word data [ + emit-item/upper word ] emit-end -write inc/tmp-portmodes.h out +write-emitted inc/tmp-portmodes.h ;---------------------------------------------------------------------------- ; @@ -979,158 +878,138 @@ write inc/tmp-portmodes.h out ;-- Add other MEZZ functions: mezz-files: load %../mezz/boot-files.r ; base lib, sys, mezz -;append boot-mezz+ none ?? why was this needed? +for-each section [boot-base boot-sys boot-mezz] [ + set section make block! 200 + for-each file first mezz-files [ + append get section load join-of %../mezz/ file + ] -foreach section [boot-base boot-sys boot-mezz] [ - set section make block! 200 - foreach file first mezz-files [ - append get section load join %../mezz/ file - ] - remove-tests get section - mezz-files: next mezz-files + ;-- Expectation is that section does not return result; GROUP! makes unset + append get section [()] + + mezz-files: next mezz-files ] -boot-protocols: make block! 20 -foreach file first mezz-files [ - m: load/all join %../mezz/ file ; not REBOL word - append/only append/only boot-protocols m/2 skip m 2 +emit-header "Sys Context" %sysctx.h + +; We don't actually want to create the object in the R3-MAKE Rebol, because +; the constructs are intended to run in the Rebol being built. But the list +; of top-level SET-WORD!s is needed. R3-Alpha used a non-evaluating CONSTRUCT +; to do this, but Ren-C's non-evaluating construct expects direct alternation +; of SET-WORD! and unevaluated value (even another SET-WORD!). So we just +; gather the top-level set-words manually. + +sctx: has collect [ + for-each item boot-sys [ + if set-word? :item [ + keep item + keep "stub proxy for %sys-base.r item" + ] + ] ] -emit-head "Sys Context" %sysctx.h -sctx: construct boot-sys -make-obj-defs sctx "SYS_CTX_" 1 -write inc/tmp-sysctx.h out +; !!! The SYS_CTX has no SELF...it is not produced by the ordinary gathering +; constructor, but uses Alloc_Context() directly. Rather than try and force +; it to have a SELF, having some objects that don't helps pave the way +; to the userspace choice of self-vs-no-self (as with func's ) +; +make-obj-defs/selfless sctx "SYS_CTX_" 1 + +write-emitted inc/tmp-sysctx.h ;---------------------------------------------------------------------------- ; -; b-boot.c - Boot data file +; TMP-BOOT-BLOCK.R and TMP-BOOT-BLOCK.C +; +; Create the aggregated Rebol file of all the Rebol-formatted data that is +; used in bootstrap. This includes everything from a list of WORD!s that +; are built-in as symbols, to the sys and mezzanine functions. +; +; %tmp-boot-block.c is just a C file containing a literal constant of the +; compressed representation of %tmp-boot-block.r ; ;---------------------------------------------------------------------------- -;-- Build b-boot.c output file ------------------------------------------------- - - -emit-head "Natives and Bootstrap" %b-boot.c -emit { -#include "sys-core.h" - -} +emit-header "Natives and Bootstrap" %tmp-boot-block.c +emit newline +emit-line {#include "sys-core.h"} +emit newline externs: make string! 2000 -boot-booters: load %booters.r -boot-natives: load %natives.r - -if has-graphics [append boot-natives load %graphics.r] +boot-natives: load boot/tmp-natives.r +num-natives: 0 -nats: append copy boot-booters boot-natives +for-each val boot-natives [ + if set-word? val [ + num-natives: num-natives + 1 + ] +] -n: boot-sys -;while [n: find n 'native] [ -; if set-word? first back n [ -; print index? n -; append nats copy/part back n 3 -; ] -; n: next next n -;] +print [num-natives "natives"] -nat-count: 0 +emit newline -foreach val nats [ - if set-word? val [ - emit-line/decl "REBNATIVE(" to word! val ");" ;R3 - nat-count: nat-count + 1 - ] -] +emit-line {REBVAL Natives[NUM_NATIVES];} -print [nat-count "natives"] +emit-line "const REBNAT Native_C_Funcs[NUM_NATIVES] = {" -emit [newline {const REBFUN Native_Funcs[} nat-count {] = ^{ -}] -foreach val nats [ - if set-word? val [ - emit-line/code "N_" to word! val "," ;R3 - ] - ;nat-count: nat-count + 1 +for-each val boot-natives [ + if set-word? val [ + emit-item ["N_" to word! val] + ] ] emit-end emit newline -;-- Embedded REBOL Tests: -;where: find boot/script 'tests -;if where [ -; remove where -; foreach file sort load %../tests/ [ -; test: load join %../tests/ file -; if test/1 <> 'skip-test [ -; where: insert where test -; ] -; ] -;] ;-- Build typespecs block (in same order as datatypes table): + boot-typespecs: make block! 100 specs: load %typespec.r -foreach type datatypes [ - append/only boot-typespecs select specs type +for-each type datatypes [ + if type = 0 [continue] + verify [spec: select specs type] + append/only boot-typespecs spec ] ;-- Create main code section (compressed): + boot-types: new-types boot-root: load %root.r boot-task: load %task.r -boot-ops: load %ops.r -;boot-script: load %script.r -write %boot-code.r mold reduce sections +write-if-changed boot/tmp-boot-block.r mold reduce sections data: mold/flat reduce sections insert data reduce ["; Copyright (C) REBOL Technologies " now newline] insert tail data make char! 0 ; scanner requires zero termination comp-data: compress data: to-binary data -emit [ -{ - // This array contains 4 bytes encoding a 32-bit little endian value, - // followed by data which is the DEFLATE-algorithm-compressed - // representation of the textual function specs for Rebol's native - // routines. This textual representation is null-terminated. - // The leading value represents the expected length of - // the text after it is decompressed (this is redundant with - // information saved by DEFLATE, but having it twice provides a - // redundant sanity check on the compression and decompression) +emit { +// Native_Specs contains data which is the DEFLATE-algorithm-compressed +// representation of the textual function specs for Rebol's native +// routines. Though DEFLATE includes the compressed size in the payload, +// NAT_UNCOMPRESSED_SIZE is also defined to be used as a sanity check +// on the decompression process. } -] - -emit ["const REBYTE Native_Specs[" 4 + length? comp-data "] = {^/^-"] +emit newline -;-- Prefix with the length -data-len-bin: to binary! length? data -assert [parse data-len-bin [4 #{00} 4 skip]] ;-- See CC #2064 -emit binary-to-c reverse (skip data-len-bin 4) +emit-line ["const REBYTE Native_Specs[NAT_COMPRESSED_SIZE] = {"] ;-- Convert UTF-8 binary to C-encoded string: emit binary-to-c comp-data -emit-end/easy +emit-line "};" ;-- EMIT-END would erase the last comma, but there's no extra -write src/b-boot.c out +write-emitted src/tmp-boot-block.c ;-- Output stats: print [ - "Compressed" length? data "to" length? comp-data "bytes:" - to-integer ((length? comp-data) / (length? data) * 100) - "percent of original" + "Compressed" length-of data "to" length-of comp-data "bytes:" + to-integer ((length-of comp-data) / (length-of data) * 100) + "percent of original" ] -;-- Create platform string: -;platform: to-string platform -;lowercase platform -;if platform-data/type = 'windows [ ; Why?? Not sure. -; product: to-string product -; lowercase product -; replace/all product "-" "" -;] -;;dir: to-file rejoin [%../to- platform "/" product "/temp/"] ;---------------------------------------------------------------------------- ; @@ -1138,75 +1017,101 @@ print [ ; ;---------------------------------------------------------------------------- -emit-head "Bootstrap Structure and Root Module" %boot.h +emit-header "Bootstrap Structure and Root Module" %boot.h -emit [ -{ -#define MAX_NATS } nat-count { -#define NAT_SPEC_SIZE } length? comp-data { -#define CHECK_TITLE } checksum to binary! title { +emit newline -extern const REBYTE Native_Specs[]; -extern const REBFUN Native_Funcs[]; +emit-line ["#define NUM_NATIVES" space num-natives] +emit-line ["#define NAT_UNCOMPRESSED_SIZE" space (length-of data)] +emit-line ["#define NAT_COMPRESSED_SIZE" space (length-of comp-data)] +emit-line ["#define CHECK_TITLE" space (checksum to binary! title)] -typedef struct REBOL_Boot_Block ^{ +emit { +// Compressed data of the native specifications. This is uncompressed during +// boot and executed. +// +extern const REBYTE Native_Specs[NAT_COMPRESSED_SIZE]; + +// Raw C function pointers for natives. +// +extern const REBNAT Native_C_Funcs[NUM_NATIVES]; + +// A canon FUNCTION! REBVAL of the native, accessible by the native's index #. +// +extern REBVAL Natives[NUM_NATIVES]; } + +emit newline +emit-line "enum Native_Indices {" + +nat-index: 0 +for-each val boot-natives [ + if set-word? val [ + emit-item/assign ["N_" (to word! val) "_ID"] nat-index + nat-index: nat-index + 1 + ] ] -foreach word sections [ - word: form word - remove/part word 5 ; boot_ - emit-line/code "REBVAL " word ";" +emit-end + +emit newline +emit-line "typedef struct REBOL_Boot_Block {" + +for-each word sections [ + word: form word + remove/part word 5 ; boot_ + emit-line/indent ["REBVAL" space (to-c-name word) ";"] ] emit "} BOOT_BLK;" ;------------------- -emit [ -{ +emit [newline newline] -//**** ROOT Context (Root Module): +emit-line {//**** ROOT Vars (GC protected special values):} +emit newline -typedef struct REBOL_Root_Context ^{ -} -] +emit-line "typedef struct REBOL_Root_Vars {" -foreach word boot-root [ - emit-line/code "REBVAL " word ";" +for-each word boot-root [ + emit-line/indent ["REBVAL" space (to-c-name word) ";"] ] -emit ["} ROOT_CTX;" lf lf] +emit-line ["} ROOT_VARS;"] +emit newline n: 0 -foreach word boot-root [ - emit-line/define "#define ROOT_" word join "(&Root_Context->" [lowercase replace/all form word #"-" #"_" ")"] - n: n + 1 +for-each word boot-root [ + emit-line [ + "#define" space (uppercase to-c-name ["ROOT_" word]) space + "(&Root_Vars->" (lowercase to-c-name word) ")" + ] + n: n + 1 ] -emit ["#define ROOT_MAX " n lf] +emit-line ["#define ROOT_MAX" space n] ;------------------- -emit [ -{ +emit [newline newline] -//**** Task Context +emit-line {//**** TASK Vars (GC protected special values)} +emit newline -typedef struct REBOL_Task_Context ^{ -} -] +emit-line "typedef struct REBOL_Task_Vars {" -foreach word boot-task [ - emit-line/code "REBVAL " word ";" +for-each word boot-task [ + emit-line/indent ["REBVAL" space (to-c-name word) ";"] ] -emit ["} TASK_CTX;" lf lf] +emit-line ["} TASK_VARS;"] +emit newline n: 0 -foreach word boot-task [ - emit-line/define "#define TASK_" word join "(&Task_Context->" [lowercase replace/all form word #"-" #"_" ")"] - n: n + 1 +for-each word boot-task [ + emit-line [ + "#define" space (uppercase to-c-name ["TASK_" word]) space + "(&Task_Vars->" (lowercase to-c-name word) ")" + ] + n: n + 1 ] -emit ["#define TASK_MAX " n lf] +emit-line ["#define TASK_MAX" space n] -write inc/tmp-boot.h out -;print ask "-DONE-" -;wait .3 -print " " +write-emitted inc/tmp-boot.h diff --git a/src/tools/make-embedded-header.r b/src/tools/make-embedded-header.r new file mode 100644 index 0000000000..ac3f2a968d --- /dev/null +++ b/src/tools/make-embedded-header.r @@ -0,0 +1,62 @@ +REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Generate C string for the embedded headers" + Rights: { + Copyright 2017 Atronix Engineering + Copyright 2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Needs: 2.100.100 +] + +do %r2r3-future.r +do %common.r +do %common-parsers.r +do %form-header.r + +print "------ Building embedded header file" +args: parse-args system/options/args +output-dir: fix-win32-path to file! any [:args/OUTDIR %../] +mkdir/deep output-dir/core + +inp: read fix-win32-path to file! output-dir/include/sys-core.i +replace/all inp "// #define" "#define" +replace/all inp "// #undef" "#undef" +replace/all inp "" "##" ;bug in tcc?? + +;remove "#define __BASE_FILE__" to avoid duplicates +remove-macro: proc [ + macro [any-string!] + /local pos-m inc eol +][ + unless binary? macro [macro: to binary! macro] + pos-m: find inp macro + if pos-m [ + inc: find/reverse pos-m to binary! "#define" + eol: find pos-m to binary! newline + remove/part inc (index? eol) - (index? inc) + ] +] + +remove-macro "__BASE_FILE__" + +;remove everything up to REN_C_STDIO_OK +;they all seem to be builtin macros +remove/part inp -1 + index? find inp to binary! "#define REN_C_STDIO_OK" + +;write %/tmp/sys-core.i inp +out: unspaced [ + form-header/gen "Embedded sys-core.h" %e-embedded-header.c %make-embedded-header.r + + {#include "sys-core.h"^/} + "extern const REBYTE core_header_source[];^/" + "const REBYTE core_header_source[] = {^/" + binary-to-c join-of inp #{00} + "};^/" +] +print "------ Writing embedded header file" +write-if-changed output-dir/core/e-embedded-header.c out diff --git a/src/tools/make-ext-init.r b/src/tools/make-ext-init.r new file mode 100644 index 0000000000..865c60675c --- /dev/null +++ b/src/tools/make-ext-init.r @@ -0,0 +1,80 @@ +REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Make REBOL host initialization code" + File: %make-host-init.r + Rights: { + Copyright 2012 REBOL Technologies + Copyright 2012-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Package: "REBOL 3 Host Kit" + Version: 1.1.1 + Needs: 2.100.100 + Purpose: { + Build a single init-file from a collection of scripts. + This is used during the REBOL host startup sequence. + } +] + +do %r2r3-future.r +do %common.r +do %common-emitter.r + +args: parse-args system/options/args +src: fix-win32-path to file! :args/SRC +set [in-dir file-name] split-path src +output-dir: fix-win32-path to file! any [:args/OUTDIR in-dir] +mkdir/deep output-dir + +dest: either select args 'DEST [ + fix-win32-path to file! :args/DEST +][ + join-of output-dir either ext-name: any [ + find/last file-name ".reb" + find/last file-name ".r3" + find/last file-name ".r" + ][ + join-of %tmp- head change ext-name ".inc" + ][ + fail spaced ["ext-name has to be one of [reb r3 r]:" file-name] + ] +] + +print unspaced ["--- Make Extension Init Code from " src " ---"] + +do %form-header.r + +write-c-file: function [ + c-file + r-file +][ + emit-header "Ext custom init code" c-file + + data: read r-file + + comp-data: compress data + comp-size: length-of comp-data + + emit-line ["static const REBYTE script_bytes[" comp-size "] = {"] + + emit binary-to-c comp-data + emit-line "};" + + write-emitted c-file + + ;-- Output stats: + print [ + newline + "Compressed" length-of data "to" comp-size "bytes:" + to-integer (comp-size / (length-of data) * 100) + "percent of original" + ] + + return comp-size +] + +write-c-file dest src diff --git a/src/tools/make-ext-natives.r b/src/tools/make-ext-natives.r new file mode 100644 index 0000000000..a79db62165 --- /dev/null +++ b/src/tools/make-ext-natives.r @@ -0,0 +1,315 @@ +REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Generate extention native header files" + File: %make-ext-native.r ;-- EMIT-HEADER uses to indicate emitting script + Rights: { + Copyright 2017 Atronix Engineering + Copyright 2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Needs: 2.100.100 + Description: { + This script is used to preprocess C source files containing code for + extension DLLs, designed to load new native code into the interpreter. + + Such code is very similar to that of the code which is built into + the EXE itself. Hence, features like scanning the C comments for + native specifications is reused. + } +] + +do %r2r3-future.r +do %common.r +do %common-emitter.r +do %form-header.r + + +args: parse-args system/options/args + +m-name: ensure string! args/MODULE +l-m-name: lowercase copy m-name +u-m-name: uppercase copy m-name + +c-src: fix-win32-path to file! ensure string! args/SRC + +print ["building" m-name "from" c-src] + + +output-dir: fix-win32-path to file! any [:args/OUTDIR %../] +mkdir/deep output-dir/include + + +verbose: false + + +; The way that the processing code for extracting Rebol information out of +; C file comments is written is that the PROTO-PARSER has several callback +; functions that can be registered to receive each item it detects. +; + +do %common-parsers.r +do %native-emitters.r ;for emit-native-proto and emit-include-params-macro + +proto-count: 0 +module-header: _ + +source.text: read c-src +if system/version > 2.100.0 [ ;-- !!! Why is this necessary? + source.text: deline to-string source.text +] + +; When the header information in the comments at the top of the file is +; seen, save it into a variable. +; + +proto-parser/emit-fileheader: func [header] [module-header: header] + +; Reuse the emitter that is used on processing natives in the core source. +; It will add the information to UNSORTED-BUFFER +; +c-natives: make block! 128 +unsorted-buffer: make string! 20000 +proto-parser/emit-proto: :emit-native-proto + +the-file: c-src ;-- global used for comments in the native emitter + +proto-parser/process source.text + + +; +; At this point the natives will all be in the UNSORTED-BUFFER. Extensions +; have added a concept of adding words (as from %words.r) for use as symbols, +; as well as errors--both possible to specify in the C comments just like +; the native headers have. +; + +native-list: load unsorted-buffer +word-list: copy [] +export-list: copy [] +error-list: copy [] +num-native: 0 +unless parse native-list [ + while [ + set w set-word! + [ + 'native block! + | + 'native/body 2 block! + | + [ + 'native/export block! + | + 'native/export/body 2 block! + | + 'native/body/export 2 block! + ] + (append export-list to word! w) + ] + (++ num-native) + | + remove [ + quote new-words: set words block! (append word-list words) + ] + | + remove [ + quote new-errors: set errors block! (append error-list errors) + ] + ] +][ + fail [ + "failed to parse" mold native-list ":" + "current word-list:" mold word-list + ] +] +;print ["specs:" mold native-list] +word-list: unique word-list +spec: compose/deep/only [ + REBOL [ + name: (to word! m-name) + exports: (export-list) + ] +] +unless empty? word-list [ + append spec compose/only [ + words: (word-list) + ] +] +unless empty? error-list [ + append spec compose/only [ + errors: (error-list) + ] +] +append spec native-list +comp-data: compress data: to-binary mold spec +;print ["buf:" to string! data] + +emit-header m-name join-all [%tmp-mod- l-m-name %-last.h] +emit-lines [ + [{int Module_Init_} m-name {(RELVAL *out);}] + [{int Module_Quit_} m-name {(void);}] + ["#if !defined(MODULE_INCLUDE_DECLARATION_ONLY)"] + ["#define EXT_NUM_NATIVES_" u-m-name space num-native] + ["#define EXT_NAT_COMPRESSED_SIZE_" u-m-name space length-of comp-data] + [ + "const REBYTE Ext_Native_Specs_" m-name + "[EXT_NAT_COMPRESSED_SIZE_" u-m-name "] = {" + ] +] + +;-- Convert UTF-8 binary to C-encoded string: +emit binary-to-c comp-data +emit-line "};" ;-- EMIT-END erases the last comma, but there's no extra + +either num-native > 0 [ + emit-line [ + "REBNAT Ext_Native_C_Funcs_" m-name + "[EXT_NUM_NATIVES_" u-m-name "] = {" + ] + for-each item native-list [ + if set-word? item [ + emit-item ["N_" to word! item] + ] + ] + emit-end +][ + emit-line ["REBNAT *Ext_Native_C_Funcs_" m-name space "= NULL;"] +] + +emit-line [ { +int Module_Init_} m-name {(RELVAL *out) +^{ + INIT_} u-m-name {_WORDS;} +either empty? error-list [ unspaced [ { + REBARR * arr = Make_Extension_Module_Array( + Ext_Native_Specs_} m-name {, EXT_NAT_COMPRESSED_SIZE_} u-m-name {, + Ext_Native_C_Funcs_} m-name {, EXT_NUM_NATIVES_} u-m-name {, + 0);} ] + ][ + unspaced [ { + Ext_} m-name {_Error_Base = Find_Next_Error_Base_Code(); + assert(Ext_} m-name {_Error_Base > 0); + REBARR * arr = Make_Extension_Module_Array( + Ext_Native_Specs_} m-name {, EXT_NAT_COMPRESSED_SIZE_} u-m-name {, + Ext_Native_C_Funcs_} m-name {, EXT_NUM_NATIVES_} u-m-name {, + Ext_} m-name {_Error_Base);}] + ] { + if (!IS_BLOCK(out)) { + Init_Block(out, arr); + } else { + Append_Values_Len(VAL_ARRAY(out), KNOWN(ARR_HEAD(arr)), ARR_LEN(arr)); + Free_Array(arr); + } + + return 0; +^} + +int Module_Quit_} m-name {(void) +{ + return 0; +} +#endif //MODULE_INCLUDE_DECLARATION_ONLY +} +] + +write-emitted to file! unspaced [ + output-dir/include/tmp-mod- l-m-name %-last.h +] + +;-------------------------------------------------------------- +; args + +emit-header + "PARAM() and REFINE() Automatic Macros" + to file! unspaced [%tmp-mod- l-m-name %-first.h] + +emit-native-include-params-macro native-list + +;-------------------------------------------------------------- +; words +emit-lines [ + ["// Local words"] + ["#define NUM_EXT_" u-m-name "_WORDS" space length-of word-list] +] + +either empty? word-list [ + emit-line ["#define INIT_" u-m-name "_WORDS"] +][ + emit-line [ + "static const char* Ext_Words_" m-name + "[NUM_EXT_" u-m-name "_WORDS] = {" + ] + for-next word-list [ + emit-line/indent [ {"} to string! word-list/1 {",} ] + ] + emit-end + + emit-line [ + "static REBSTR* Ext_Canons_" m-name + "[NUM_EXT_" u-m-name "_WORDS];" + ] + + word-seq: 0 + for-next word-list [ + emit-line [ + "#define" + space + u-m-name {_WORD_} uppercase to-c-name word-list/1 + space + {Ext_Canons_} m-name {[} word-seq {]} + ] + ++ word-seq + ] + emit-line ["#define INIT_" u-m-name "_WORDS" space "\"] + emit-line/indent [ + "Init_Extension_Words(" + "cast(const REBYTE**, Ext_Words_" m-name ")" + "," space + "Ext_Canons_" m-name + "," space + "NUM_EXT_" u-m-name "_WORDS" + ")" + ] +] + +;-------------------------------------------------------------- +; errors + +emit-line ["// Local errors"] +unless empty? error-list [ + emit-line [ {enum Ext_} m-name {_Errors ^{}] + error-collected: copy [] + for-each [key val] error-list [ + unless set-word? key [ + fail ["key (" mold key ") must be a set-word!"] + ] + if find error-collected key [ + fail ["Duplicate error key" to word! key] + ] + append error-collected key + emit-item/upper [ + {RE_EXT_ENUM_} u-m-name {_} to-c-name to word! key + ] + ] + emit-end + emit-line ["static REBINT Ext_" m-name "_Error_Base;"] + + emit-line [] + for-each [key val] error-list [ + key: uppercase to-c-name to word! key + emit-line [ + {#define RE_EXT_} u-m-name {_} key + space + {(} + {Ext_} m-name {_Error_Base + RE_EXT_ENUM_} u-m-name {_} key + {)} + ] + ] +] + +write-emitted to file! unspaced [ + output-dir/include/tmp-mod- l-m-name %-first.h +] diff --git a/src/tools/make-headers.r b/src/tools/make-headers.r index 9e7523376c..be6604a8d6 100644 --- a/src/tools/make-headers.r +++ b/src/tools/make-headers.r @@ -1,230 +1,364 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Generate auto headers" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Author: "Carl Sassenrath" - Needs: 2.100.100 + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Generate auto headers" + File: %make-headers.r ;-- used by EMIT-HEADER to indicate emitting script + Rights: { + Copyright 2012 REBOL Technologies + Copyright 2012-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Needs: 2.100.100 ] +do %r2r3-future.r +do %common.r +do %common-emitter.r +do %common-parsers.r +do %form-header.r +do %native-emitters.r ;for emit-include-params-macro and emit-native-include-params-macro + print "------ Building headers" +args: parse-args system/options/args +output-dir: fix-win32-path to file! any [:args/OUTDIR %../] +mkdir/deep output-dir/include +mkdir/deep output-dir/core r3: system/version > 2.100.0 verbose: false -chk-dups: true -dups: make block! 10000 ; get pick [map! hash!] r3 1000 -dup-found: false +check-duplicates: true +prototypes: make block! 10000 ; get pick [map! hash!] r3 1000 +has-duplicates: false -do %form-header.r +change-dir %../core/ -tmp: context [ +collapse-whitespace: [some [change some white-space #" " | skip]] +bind collapse-whitespace c.lexical/grammar + +emit-proto: proc [proto] [ + + if find proto "()" [ + print [ + proto + newline + {C-Style void arguments should be foo(void) and not foo()} + newline + http://stackoverflow.com/questions/693788/c-void-arguments + ] + fail "C++ no-arg prototype used instead of C style" + ] + + ;?? proto + assert [proto] + if all [ + not find proto "static" + not find proto "REBNATIVE(" + + ; The REBTYPE macro actually is expanded in the tmp-funcs + ; Should we allow macro expansion or do the REBTYPE another way? + (comment [not find proto "REBTYPE("] true) + + find proto #"(" + ][ + + parse proto collapse-whitespace + proto: trim proto + + either all [ + check-duplicates + find prototypes proto + ][ + print ["Duplicate:" the-file ":" proto] + has-duplicates: true + ][ + append prototypes proto + ] + either find proto "RL_API" [ + emit-rlib ["extern " proto "; // " the-file] + ][ + emit-line ["RL_API " proto "; // " the-file] + either "REBTYPE" = proto-parser/proto.id [ + emit-fsymb [" SYM_FUNC(T_" proto-parser/proto.arg.1 "), // " the-file] + ][ + emit-fsymb [" SYM_FUNC(" proto-parser/proto.id "), // " the-file] + ] + ] + proto-count: proto-count + 1 + ] +] -change-dir %../core/ +emit-directive: procedure [directive] [ + process-conditional directive proto-parser/parse.position :emit-line buf-emit + process-conditional directive proto-parser/parse.position :emit-fsymb fsymbol-buffer +] + +process-conditional: procedure [ + directive + dir-position + emit [function!] + buffer +][ + emit [ + directive + ;;; " // " the-file " #" line-of head dir-position dir-position + ] + + ; Minimise conditionals for the reader - unnecessary for compilation. + if all [ + find/match directive "#endif" + position: find/last tail buffer "#if" + ][ + rewrite-if-directives position + ] +] -count: 0 -output: make string! 20000 +process: func [file] [ + if verbose [probe [file]] + data: read the-file: file + if r3 [data: deline to-string data] + proto-parser/emit-proto: :emit-proto + proto-parser/emit-directive: :emit-directive + proto-parser/process data +] -emit: func [d] [append repend output d newline] -remit: func [d] [append repend rlib d newline] +;------------------------------------------------------------------------- -emit-header: func [t f] [emit form-header/gen t f %make-headers] rlib: form-header/gen "REBOL Interface Library" %reb-lib.h %make-headers.r append rlib newline +emit-rlib: func [d] [append adjoin rlib d newline] -append-spec: func [spec] [ - ;?? spec - if all [ - spec - not find spec "static" - not find spec "scan_state" - not find spec "REBNATIVE" - find spec #"(" - ][ - spec: trim spec - either all [ - chk-dups - find dups spec - ][ - print ["Duplicate:" the-file ":" spec] - dup-found: true - ][ - append dups spec - ] - either find spec "RL_API" [ - remit ["extern " spec "; // " the-file] - ][ - emit ["extern " spec "; // " the-file] - ] - count: count + 1 - ] -] -func-header: [ - ;-- Scan for function header box: - "^/**" to newline - "^/*/" any [#" " | #"^-"] - copy spec to newline (append-spec spec) - newline - [ - "/*" ; must be in func header section, not file banner - any [ - thru "**" - [#" " | #"^-"] - copy line thru newline - ] - thru "*/" - | - none - ] -] +;------------------------------------------------------------------------- -process: func [file] [ - if verbose [?? file] - data: read the-file: file - if r3 [data: deline to-string data] - parse/all data [ - any [ - thru "/******" to newline - [ - func-header | thru newline - ] - ] - ] -] +proto-count: 0 -emit-header "Function Prototypes" %funcs.h +fsymbol-file: %tmp-symbols.c +fsymbol-buffer: make string! 20000 +emit-fsymb: func [x] [append adjoin fsymbol-buffer x newline] -files: sort read %./ +emit-header "Function Prototypes" %funcs.h -;do -[ - remove find files %a-lib2.c - print "Non-extended reb-lib version" - wait 5 +emit-fsymb form-header/gen "Function Symbols" fsymbol-file %make-headers.r +emit-fsymb {#include "sys-core.h" + +// Note that cast() macro causes problems here with clang for some reason. +// +// !!! Also, void pointers and function pointers are not guaranteed to be +// the same size, even if TCC assumes so for these symbol purposes. +// +#define SYM_FUNC(x) {#x, cast(CFUNC*, x)} +#define SYM_DATA(x) {#x, &x} + +struct rebol_sym_func_t { + const char *name; + CFUNC *func; +}; + +struct rebol_sym_data_t { + const char *name; + void *data; +}; + +extern const struct rebol_sym_func_t rebol_sym_funcs []; +const struct rebol_sym_func_t rebol_sym_funcs [] = ^{} + +emit { +// When building as C++, the linkage on these functions should be done without +// "name mangling" so that library clients will not notice a difference +// between a C++ build and a C build. +// +// http://stackoverflow.com/q/1041866/ +// +#ifdef __cplusplus +extern "C" ^{ +#endif + +// +// Native Prototypes: REBNATIVE is a macro which will expand such that +// REBNATIVE(parse) will define a function named `N_parse`. The prototypes +// are included in a system-wide header in order to allow recognizing a +// given native by identity in the C code, e.g.: +// +// if (VAL_FUNC_DISPATCHER(native) == &N_parse) { ... } +// +} +emit newline + +boot-natives: load output-dir/boot/tmp-natives.r + +for-each val boot-natives [ + if set-word? val [ + emit-line ["REBNATIVE(" to-c-name (to word! val) ");"] + ] ] -foreach file files [ - if all [ - %.c = suffix? file - not find/match file "host-" - not find/match file "os-" - ][process file] +emit { + +// +// Other Prototypes: These are the functions that are scanned for in the %.c +// files by %make-headers.r, and then their prototypes placed here. This +// means it is not necessary to manually keep them in sync to make calls to +// functions living in different sources. (`static` functions are skipped +// by the scan.) +// +} +emit newline + +file-base: has load %../tools/file-base.r + +for-each item file-base/core [ + ; + ; Items can be blocks if there's special flags for the file ( + ; marks it to be skipped by this script) + ; + either block? item [ + either all [ + 2 <= length-of item + = item/2 + ][; skip this file + continue + ][ + file: to file first item + ] + ][ + file: to file! item + ] + + assert [ + | %.c = suffix? file + | not find/match file "host-" + | not find/match file "os-" + ] + + process file ] -write %../include/tmp-funcs.h output -print [count "function prototypes"] +emit newline +emit-line "#ifdef __cplusplus" +emit-line "}" +emit-line "#endif" + +write-emitted output-dir/include/tmp-funcs.h + +print [proto-count "function prototypes"] ;wait 1 ;------------------------------------------------------------------------- -clear output - -emit-header "Function Argument Enums" %func-args.h - -make-arg-enums: func [word] [ - ; Search file for definition: - def: find acts to-set-word word - def: skip def 2 - args: copy [] - refs: copy [] - ; Gather arg words: - foreach w first def [ - if any-word? w [ - append args uw: uppercase replace/all form to word! w #"-" #"_" ; R3 - if refinement? w [append refs uw w: to word! w] ; R3 - ] - ] - - uword: uppercase form word - replace/all uword #"-" #"_" - word: lowercase copy uword - - ; Argument numbers: - emit ["enum act_" word "_arg {"] - emit [tab "ARG_" uword "_0,"] - foreach w args [emit [tab "ARG_" uword "_" w ","]] - emit "};^/" - - ; Argument bitmask: - n: 0 - emit ["enum act_" word "_mask {"] - foreach w args [ - emit [tab "AM_" uword "_" w " = 1 << " n ","] - n: n + 1 - ] - emit "};^/" - - repend output ["#define ALL_" uword "_REFS ("] - foreach w refs [ - repend output ["AM_" uword "_" w "|"] - ] - remove back tail output - append output ")^/^/" - - ;?? output halt +sys-globals.parser: context [ + + emit-directive: _ + emit-identifier: _ + parse.position: _ + id: _ + + process: func [text] [parse text grammar/rule] + + grammar: context bind [ + + rule: [ + any [ + parse.position: + segment + ] + ] + + segment: [ + (id: _) + span-comment + | line-comment any [newline line-comment] newline + | opt wsp directive + | declaration + | other-segment + ] + + declaration: [ + some [opt wsp [copy id identifier | not #";" punctuator] ] #";" thru newline ( + emit-fsymb [" SYM_DATA(" id "),"] + ) + ] + + directive: [ + copy data [ + ["#ifndef" | "#ifdef" | "#if" | "#else" | "#elif" | "#endif"] + any [not newline c-pp-token] + ] eol + ( + process-conditional data parse.position :emit-fsymb fsymbol-buffer + ) + ] + + other-segment: [thru newline] + + ] c.lexical/grammar + ] -acts: load %../boot/actions.r +emit-fsymb "^/ {NULL, NULL} //Terminator^/};" +emit-fsymb "^/// Globals from sys-globals.h^/" +emit-fsymb { +extern const struct rebol_sym_data_t rebol_sym_data []; +const struct rebol_sym_data_t rebol_sym_data [] = ^{^/} -foreach word [ - copy - find - select - insert - trim - open - read - write -] [make-arg-enums word] +the-file: %sys-globals.h +sys-globals.parser/process read/string %../include/sys-globals.h -acts: load %../boot/natives.r +emit-fsymb "^/ {NULL, NULL} //Terminator^/};" +write output-dir/core/:fsymbol-file fsymbol-buffer -foreach word [ - checksum - request-file -] [make-arg-enums word] +;------------------------------------------------------------------------- -;?? output -write %../include/tmp-funcargs.h output +emit-header "PARAM() and REFINE() Automatic Macros" %func-args.h +action-list: load output-dir/boot/tmp-actions.r -;------------------------------------------------------------------------- +; Search file for definition. Will be `action-name: action [paramlist]` +; +for-next action-list [ + if 'action = pick action-list 2 [ + assert [set-word? action-list/1] + emit-include-params-macro (to-word action-list/1) (action-list/3) + emit newline + ] +] -clear output +native-list: load output-dir/boot/tmp-natives.r -emit-header "REBOL Constants Strings" %str-consts.h +emit-native-include-params-macro native-list + +write-emitted output-dir/include/tmp-paramlists.h -data: to string! read %a-constants.c ;R3 - -parse/all data [ - some [ - to "^/const" - copy d to "=" - ( - remove d - ;replace d "const" "extern" - insert d "extern " - append trim/tail d #";" - emit d - ) - ] -] -write %../include/tmp-strings.h output +;------------------------------------------------------------------------- + +emit-header "REBOL Constants Strings" %str-consts.h +data: to string! read %a-constants.c + +parse data [ + some [ + to "^/const" + copy constd to "=" + ( + remove constd + ;replace constd "const" "extern" + insert constd "extern " + append trim/tail constd #";" + emit-line constd + ) + ] ] -if any [dup-found verbose] [ - print "** NOTE ABOVE PROBLEM!" - wait 5 +write-emitted output-dir/include/tmp-strings.h + +if any [has-duplicates verbose] [ + print "** NOTE ABOVE PROBLEM!" + wait 5 ] print " " diff --git a/src/tools/make-host-ext.r b/src/tools/make-host-ext.r index 443d6cf303..a480cb9d5e 100644 --- a/src/tools/make-host-ext.r +++ b/src/tools/make-host-ext.r @@ -1,91 +1,61 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Build REBOL 3.0 boot extension module" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Author: "Carl Sassenrath" - Needs: 2.100.100 - Purpose: { - Collects host-kit extension modules and writes them out - to a .h file in a compilable data format. - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Build REBOL 3.0 boot extension module" + Rights: { + Copyright 2012 REBOL Technologies + Copyright 2012-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Needs: 2.100.100 + Purpose: { + Collects host-kit extension modules and writes them out + to a .h file in a compilable data format. + } ] print "--- Make Host Boot Extension ---" -secure none -do %form-header.r +do %r2r3-future.r +do %common.r -;-- Conversion to C strings, depending on compiler --------------------------- - -to-cstr: either system/version/4 = 3 [ - ; Windows format: - func [str /local out] [ - out: make string! 4 * (length? str) - out: insert out tab - forall str [ - out: insert out reduce [to-integer first str ", "] - if zero? ((index? str) // 10) [out: insert out "^/^-"] - ] - ;remove/part out either (pick out -1) = #" " [-2][-4] - head out - ] -][ - ; Other formats (Linux, OpenBSD, etc.): - func [str /local out data] [ - out: make string! 4 * (length? str) - forall str [ - data: copy/part str 16 - str: skip str 15 - data: enbase/base data 16 - forall data [ - insert data "\x" - data: skip data 3 - ] - data: tail data - insert data {"^/} - append out {"} - append out head data - ] - head out - ] -] +do %form-header.r +args: parse-args system/options/args +output-dir: fix-win32-path to file! any [:args/OUTDIR %../] +mkdir/deep output-dir/include ;-- Collect Sources ---------------------------------------------------------- collect-files: func [ - "Collect contents of several source files and return combined with header." - files [block!] - /local source data header + "Collect contents of several source files and return combined with header." + files [block!] + /local source data header ][ - source: make block! 1000 - - foreach file files [ - data: load/all file - remove-each [a b] data [issue? a] ; commented sections - unless block? header: find data 'rebol [ - print ["Missing header in:" file] halt - ] - unless empty? source [data: next next data] ; first one includes header - append source data - ] - - source + source: make block! 1000 + + for-each file files [ + data: load/all file + remove-each [a b] data [issue? a] ; commented sections + unless block? header: find data 'rebol [ + print ["Missing header in:" file] halt + ] + unless empty? source [data: next next data] ; first one includes header + append source data + ] + + source ] ;-- Emit Functions ----------------------------------------------------------- out: make string! 10000 -emit: func [d] [repend out d] +emit: func [d] [adjoin out d] emit-cmt: func [text] [ - emit [ + emit [ {/*********************************************************************** ** ** } text { @@ -96,81 +66,76 @@ emit-cmt: func [text] [ ] form-name: func [word] [ - uppercase replace/all replace/all to-string word #"-" #"_" #"?" #"Q" + uppercase replace/all replace/all to-string word #"-" #"_" #"?" #"Q" ] emit-file: func [ - "Emit command enum and source script code." - file [file!] - source [block!] - /local title name data exports words src prefix + "Emit command enum and source script code." + file [file!] + source [block!] + /local title name data exports words exported-words src prefix ][ - source: collect-files source - - title: select source/2 to-set-word 'title - name: form select source/2 to-set-word 'name - replace/all name "-" "_" - prefix: uppercase copy name - - clear out - emit form-header/gen title second split-path file %make-host-ext.r - - emit ["enum " name "_commands {^/"] - - ; Gather exported words if exports field is a block: - words: make block! 100 - src: source - while [src: find src set-word!] [ - if find [command func function funct] src/2 [ - append words to-word src/1 - ] - src: next src - ] - - if block? exports: select second source to-set-word 'exports [ - insert exports words - ] - - foreach word words [emit [tab "CMD_" prefix #"_" replace/all form-name word "'" "_LIT" ",^/"]] - emit "};^/^/" - - if src: select source to-set-word 'words [ - emit ["enum " name "_words {^/"] - emit [tab "W_" prefix "_0,^/"] - foreach word src [emit [tab "W_" prefix #"_" form-name word ",^/"]] - emit "};^/^/" - ] - - emit "#ifdef INCLUDE_EXT_DATA^/" - data: append trim/head mold/only/flat source newline - append data to-char 0 ; null terminator may be required - emit ["const unsigned char RX_" name "[] = {^/" to-cstr data "^/};^/^/"] - emit "#endif^/" - - write rejoin [%../include/ file %.h] out - -; clear out -; emit form-header/gen join title " - Module Initialization" second split-path file %make-host-ext.r -; write rejoin [%../os/ file %.c] out -] - -;-- Create Files ------------------------------------------------------------- - -emit-file %host-ext-graphics [ - %../boot/graphics.r - %../mezz/view-funcs.r + source: collect-files source + + title: select source/2 to-set-word 'title + name: form select source/2 to-set-word 'name + replace/all name "-" "_" + prefix: uppercase copy name + + clear out + emit form-header/gen title second split-path file %make-host-ext.r + + emit ["enum " name "_commands {^/"] + + ; Gather exported words if exports field is a block: + words: make block! 100 + exported-words: make block! 100 + src: source + while [src: find src set-word!] [ + if all [ + != first back src + find [command func function funct] src/2 + ][ + append exported-words to-word src/1 + ] + if src/2 = 'command [append words to-word src/1] + src: next src + ] + + if block? exports: select second source to-set-word 'exports [ + insert exports exported-words + ] + + for-each word words [ + emit [ + spaced-tab + "CMD_" prefix #"_" replace/all form-name word "'" "_LIT" "," + newline + ] + ] + emit [spaced-tab "CMD_MAX" newline] + emit ["};" newline newline] + + if src: select source to-set-word 'words [ + emit ["enum " name "_words {" newline] + emit [spaced-tab "W_" prefix "_0," newline] + for-each word src [ + emit [spaced-tab "W_" prefix #"_" form-name word "," newline] + ] + emit [spaced-tab "W_MAX" newline] + emit ["};" newline newline] + ] + + emit ["#ifdef INCLUDE_EXT_DATA" newline] + code: append trim/head mold/only/flat source newline + append code to-char 0 ; null terminator may be required + emit [ + "const unsigned char RX_" name "[] = {" newline + binary-to-c to-binary code + "};" newline + newline + ] + emit ["#endif" newline] + + write-if-changed join-all [output-dir/include %/ file %.h] out ] - -emit-file %host-ext-draw [ - %../boot/draw.r -] - -emit-file %host-ext-shape [ - %../boot/shape.r -] - -emit-file %host-ext-text [ - %../boot/text.r -] - -print " " diff --git a/src/tools/make-host-init.r b/src/tools/make-host-init.r index 31f3e4f6e6..61c14c6e15 100644 --- a/src/tools/make-host-init.r +++ b/src/tools/make-host-init.r @@ -1,198 +1,135 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Make REBOL host initialization code" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Package: "REBOL 3 Host Kit" - Version: 1.1.1 - Needs: 2.100.100 - Author: "Carl Sassenrath" - Purpose: { - Build a single init-file from a collection of scripts. - This is used during the REBOL host startup sequence. - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Make REBOL host initialization code" + File: %make-host-init.r + Rights: { + Copyright 2012 REBOL Technologies + Copyright 2012-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Package: "REBOL 3 Host Kit" + Version: 1.1.1 + Needs: 2.100.100 + Purpose: { + Build a single init-file from a collection of scripts. + This is used during the REBOL host startup sequence. + } ] -print "--- Make Host Init Code ---" -;print ["REBOL version:" system/version] +do %r2r3-future.r +do %common.r +do %common-emitter.r + +args: parse-args system/options/args +output-dir: fix-win32-path to file! any [:args/OUTDIR %../] +mkdir/deep output-dir/os -; Options: -include-vid: off -proof: off +print "--- Make Host Init Code ---" do %form-header.r ; Output directory for temp files: dir: %os/ -; Files to include in the host program: -files: [ - %mezz/prot-http.r -; %mezz/view-colors.r -] +; This script starts running in the %tools/ directory, but the %host-main.c +; file which wants to #include "tmp-host-start.inc" currently lives in the +; %os/ directory. (That's also where host-start.r is.) +; +change-dir %../os/ -vid-files: [ - %mezz/dial-draw.r - %mezz/dial-text.r - %mezz/dial-effect.r - %mezz/view-funcs.r - %mezz/vid-face.r - %mezz/vid-events.r - %mezz/vid-styles.r - %mezz/mezz-splash.r -] -if include-vid [append files vid-files] +write-c-file: function [ + c-file + code +][ + emit-header "Host custom init code" c-file -; Change back to the main souce directory: -change-dir %../ -make-dir dir + data: either system/version > 2.7.5 [ + mold/flat/only code ; crashes 2.7 + ][ + mold/only code + ] + append data newline ; BUG? why does MOLD not provide it? -;** Utility Functions ************************************************** + insert data reduce ["; Copyright REBOL Technologies " now newline] + insert tail data make char! 0 ; zero termination required -out: make string! 100000 -emit: func [data] [repend out data] + comp-data: compress data + comp-size: length-of comp-data -emit-head: func [title file] [ - clear out - emit form-header/gen title file %make-host-init.r -] + emit-line ["#define REB_INIT_SIZE" space comp-size] -emit-end: func [/easy] [ - if not easy [remove find/last out #","] - append out {^};^/} -] + emit-line "const unsigned char Reb_Init_Code[REB_INIT_SIZE] = {" -; Convert binary to C code depending on the compiler requirements. -; (Some compilers cannot create long string concatenations.) -binary-to-c: either system/version/4 = 3 [ - ; Windows MSVC 6 compatible format (as integer chars): - func [comp-data /local out] [ - out: make string! 4 * (length? comp-data) - forall comp-data [ - out: insert out reduce [to-integer first comp-data ", "] - if zero? ((index? comp-data) // 10) [out: insert out "^/^-"] - ] - ;remove/part out either (pick out -1) = #" " [-2][-4] - head out - ] -][ - ; Other compilers (as hex-escaped char strings "\x00"): - func [comp-data /local out] [ - out: make string! 4 * (length? comp-data) - forall comp-data [ - data: copy/part comp-data 16 - comp-data: skip comp-data 15 - data: enbase/base data 16 - forall data [ - insert data "\x" - data: skip data 3 - ] - data: tail data - insert data {"^/} - append out {"} - append out head data - ] - head out - ] + emit binary-to-c comp-data + emit-line "};" + + write-emitted c-file + + ;-- Output stats: + print [ + newline + "Compressed" length-of data "to" comp-size "bytes:" + to-integer (comp-size / (length-of data) * 100) + "percent of original" + ] + + return comp-size ] -;** Main Functions ***************************************************** -write-c-file: func [ - c-file - code - /local data comp-data comp-size +load-files: function [ + file-list ][ - ;print "writing C code..." - emit-head "Host custom init code" c-file - - data: either system/version > 2.7.5 [ - mold/flat/only/all code ; crashes 2.7 - ][ - mold/only/all code - ] - append data newline ; BUG? why does MOLD not provide it? - - insert data reduce ["; Copyright REBOL Technologies " now newline] - insert tail data make char! 0 ; zero termination required - - if proof [ - write %tmp.r to-binary data - ;ask "wrote tmp.r for proofreading (press return)" - ;probe data - ] - - comp-data: compress data - comp-size: length? comp-data - - emit ["#define REB_INIT_SIZE " comp-size newline newline] - - emit "const unsigned char Reb_Init_Code[REB_INIT_SIZE] = {^/^-" - - ;-- Convert to C-encoded string: - ;print "converting..." - emit binary-to-c comp-data - emit-end/easy - - print ["writing" c-file] - write c-file to-binary out -; write h-file to-binary reform [ -; form-header "Host custom init header" second split-path h-file newline -; "#define REB_INIT_SIZE" comp-size newline -; "extern REBYTE Reb_Init_Code[REB_INIT_SIZE];" newline -; ] - - ;-- Output stats: - print [ - newline - "Compressed" length? data "to" comp-size "bytes:" - to-integer (comp-size / (length? data) * 100) - "percent of original" - ] - - return comp-size + data: make block! 100 + for-each file file-list [ + print ["loading:" file] + file: load/header file + header: take file + if header/type = 'module [ + file: compose/deep [ + import module + [ + title: (header/title) + version: (header/version) + name: (header/name) + ][ + (file) + ] + ] + ;probe file/2 + ] + append data file + ] + data ] -load-files: func [ - file-list - /local data -][ - data: make block! 100 - ;append data [print "REBOL Host-Init"] ; for startup debug only - foreach file file-list [ - print ["loading:" file] - file: load/header file - header: file/1 - remove file - if header/type = 'module [ - file: compose/deep [ - import module - [ - title: (header/title) - version: (header/version) - name: (header/name) - ][ - (file) - ] - ] - ;probe file/2 - ] - append data file - ] - data +host-start: load-files [ + %encap.reb + %unzip.reb + %host-console.r + %host-start.r ] -code: load-files files +; script evaluates to the startup function, which will in turn evaluate +; to either an exit status code or a REPL function. +; +append host-start [:host-start] + -save %boot/host-init.r code +file-base: has load %../tools/file-base.r + +; copied from make-boot.r +host-protocols: make block! 2 +for-each file file-base/prot-files [ + m: load/all join-of %../mezz/ file ; not REBOL word + append/only append/only host-protocols m/2 skip m 2 +] -write-c-file %include/host-init.h code +insert host-start compose/only [host-prot: (host-protocols)] -print " " ; (separate the output for build watch window) +write-c-file output-dir/os/tmp-host-start.inc host-start diff --git a/src/tools/make-make.r b/src/tools/make-make.r index 02ef000898..cbc1535222 100644 --- a/src/tools/make-make.r +++ b/src/tools/make-make.r @@ -1,68 +1,388 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Make the R3 Core Makefile" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Author: "Carl Sassenrath" - Purpose: { - Build a new makefile for a given platform. - } - Note: [ - "This runs relative to ../tools directory." - "Make OS-specific changes to the systems.r file." - ] + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Make the R3 Core Makefile" + Rights: { + Copyright 2012 REBOL Technologies + Copyright 2012-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + Build a new makefile for a given platform. + + The reason that Rebol is not used to drive the build directly is to + make it possible to port to any platform which supports GNU make. + A R3-Alpha or Ren-C interpreter is thus only needed to produce the + files, which can be done on a different platform, with the products + transferred over to the new system which bootstrap is being done on. + } + Note: [ + "This runs relative to ../tools directory." + "Make OS-specific changes to the systems.r file." + ] ] -file-files: %file-base.r -file-system: %systems.r +do %r2r3-future.r +do %common.r +do %common-emitter.r -path-host: %../os/ -path-make: %../../make/ -path-incl: %../../src/include/ -;****************************************************************************** +path-host: %../os/ +path-make: %../../make/ +path-incl: %../../src/include/ -; (Warning: format is a bit sensitive to extra spacing. E.g. see macro+ func) -makefile-head: +; +; PROCESS COMMAND LINE ARGS +; +; Arguments are like `r3 %make-make.r A=B X=Y`, so turn that into an args +; object with keys and values (e.g. args/A = "B"). Values are STRING! +; -{# REBOL Makefile -- Generated by make-make.r (do not edit) on !date -# This makefile is intentional kept simple to make builds possible on -# a wider range of target platforms. +args: parse-args system/options/args -# To regenerate this file: -# make make -# To generate this file for a different platform, check systems.r file -# and provide an OS_ID (from the systems table). Linux 2.5 for example: -# make make OS_ID=0.4.3 +; +; IMPORT PLATFORM CONFIGURATION TO `CONFIG` OBJECT (from %systems.r) +; +; If OS_ID is not provided, it will be detected. +; -# To cross compile using a different toolchain and include files: -# $TOOLS - should point to bin where gcc is found -# $INCL - should point to the dir for includes +do %systems.r + +config: case [ + any [blank? args/OS_ID | args/OS_ID = "detect"] [ + config-system blank + ] + true [ + config-system args/OS_ID + ] +] + +print ["Option set for building:" config/id config/os-name] + +flag?: function [ + {Test if a flag is applicable for the current platform (see %systems.r)} + 'flag [word!] +][ + not blank? find config/build-flags flag +] + + +; +; DECODE DEBUG OPTION INTO FLAGS +; +; DEBUG can be "none", "asserts", "symbols", "sanitize"...each a level of +; assumed greater debugging. Adding symbols makes the executable much +; larger, and Address Sanitization makes the executable much slower. To +; try and get casual builders to bear a modest useful burden, the default +; is set to just including the asserts. +; + +case [ + args/DEBUG = "none" [ + asserts: false + symbols: false + sanitize: false + optimize-level: 2 + ] + any [blank? args/DEBUG | args/DEBUG = "asserts"] [ + asserts: true + symbols: false + sanitize: false + optimize-level: 2 + ] + args/DEBUG = "symbols" [ + asserts: true + symbols: true + sanitize: false + optimize-level: 0 + ] + args/DEBUG = "sanitize" [ + asserts: true + symbols: true + sanitize: true + optimize-level: 0 + ] + true [ + fail [ + "DEBUG must be [none | asserts | symbols | sanitize], not" + (args/DEBUG) + ] + ] +] + +switch/default args/OPTIMIZE [ + "auto" [ + ;set by args/DEBUG + ] + "none" [ + optimize-level: 0 + ] + "0" "1" "2" "3" [ + optimize-level: args/OPTIMIZE + ] +][ + fail [ + "OPTIMIZE must be [auto | none | 0 | 1 | 2 | 3], not" + (args/OPTIMIZE) + ] +] + + +; +; PROCESS LIST OF INPUT C AND HEADER FILES FROM %FILE-BASE.R +; + +file-base: has load %file-base.r + +; Collect OS-specific host files. +; +os-specific-objs: select file-base to word! unspaced ["os-" config/os-base] +unless os-specific-objs [ + fail [ + "make-make.r requires os-specific obj list in file-base.r" + "Nothing was provided for" unspaced ["os-" config/os-base] + ] +] + +; The + sign is used to tell the make-os-ext.r script to scan a host kit file +; for headers (the way make-headers.r does). But we don't care about that +; here in make-make.r... so remove any + signs we find before processing. +; +remove-each item file-base/os [item = '+] +remove-each item os-specific-objs [item = '+] + +if flag? +SC [remove find os-specific-objs 'host-readline.c] + + +emit [ + +{# REBOL Makefile -- Generated by make-make.r (!!! EDITS WILL BE LOST !!!) +# This automatically produced file was created } now newline + +newline + +{# This makefile is intentionally kept simple to make builds possible on +# a wide range of target platforms. While this generated file has several +# capabilities, it is not tracked by version control. So to kick off the +# process you need to use the tracked bootstrap makefile: +# +# make -f makefile.boot +# +# See the comments in %makefile.boot for more information on the workings of +# %make-make.r and what the version numbers mean. +# +# To cross-compile using a different toolchain and include files: +# +# $TOOLS - should point to bin where gcc is found +# $INCL - should point to the dir for includes +# $EXTRA_CC_FLAGS - optional compiler flags (useful for --sysroot) +# # Example make: -# make TOOLS=~/amiga/amiga/bin/ppc-amigaos- INCL=/SDK/newlib/include - -# For the build toolchain: -CC= $(TOOLS)gcc -NM= $(TOOLS)nm -STRIP= $(TOOLS)strip - -# CP allows different copy progs: -CP= -# LS allows different ls progs: -LS= -# UP - some systems do not use ../ -UP= -# CD - some systems do not use ./ -CD= -# Special tools: +# +# make TOOLS=~/amiga/amiga/bin/ppc-amigaos- INCL=/SDK/newlib/include +# +# make TOOLS=/android-ndk/toolchains/.../bin/arm-linux-androideabi- \ +# EXTRA_CC_FLAGS="--sysroot=/android-ndk/platforms/.../arch-arm" +# +# !!! Efforts to be able to have Rebol build itself in absence of a make +# tool are being considered. Please come chime in on chat if you are +# interested in that and other projects, or need support while building: +# +# http://rebolsource.net/go/chat-faq +# +} + +{OS_ID?=} space (config/id) newline + +newline + +{GIT_COMMIT?=} space (any [args/GIT_COMMIT "unknown"]) newline + +newline + +{DEBUG_FLAGS?=} space ( + unspaced [ + either symbols ["-g -g3 "] [""] + either asserts [""] ["-DNDEBUG "] ; http://stackoverflow.com/q/9229978/ + "-O" optimize-level + ] +) newline + +newline + +( + either sanitize [ + unspaced [ + {SANITIZE_FLAGS= -fno-omit-frame-pointer -fsanitize=address} newline + {SANITIZE_LINK_FLAGS= -fsanitize=address} newline + ] + ][ + unspaced [ + {SANITIZE_FLAGS=} newline + {SANITIZE_LINK_FLAGS=} newline + ] + ] +) newline + +newline + +{LANGUAGE_FLAGS?=} space case [ + any [blank? args/STANDARD | args/STANDARD = "c"] [ + cplusplus: false + "" + ] + find ["gnu89" "c99" "gnu99" "c11"] args/STANDARD [ + cplusplus: false + unspaced ["--std=" args/STANDARD] + ] + "c++" = args/STANDARD [ + cplusplus: true + "-x c++" + ] + find ["c++98" "c++0x" "c++11" "c++14" "c++17"] args/STANDARD [ + cplusplus: true + + ; Note: The C and C++ standards do not dictate if `char` is signed + ; or unsigned. Lest anyone think all environments have settled on + ; them being signed, they're not... Android NDK uses unsigned: + ; + ; http://stackoverflow.com/questions/7414355/ + ; + ; In order to give the option some exercise, make the C++11 builds + ; and above use unsigned chars. + ; + unspaced [ + "-x c++" space "--std=" args/STANDARD space "-funsigned-char" + ] + ] + true [ + fail [ + "STANDARD should be [c gnu89 gnu99 c99 c11 c++ c++11 c++14 c++17]" + "not" (args/STANDARD) + ] + ] +] newline + +{RIGOROUS_FLAGS?=} space ( + case [ + args/RIGOROUS = "yes" [ + rigorous: true + spaced [ + "-Werror" ;-- convert warnings to errors + + ; If you use pedantic in a C build on an older GNU compiler, + ; (that defaults to thinking it's a C89 compiler), it will + ; complain about using `//` style comments. There is no + ; way to turn this complaint off. So don't use pedantic + ; warnings unless you're at c99 or higher, or C++. + ; + ( + either any [ + cplusplus | not find ["c" "gnu89"] args/STANDARD + ][ + "--pedantic" + ][ + "" + ] + ) + + "-Wextra" + "-Wall" + + "-Wchar-subscripts" + "-Wwrite-strings" + "-Wundef" + "-Wformat=2" + "-Wdisabled-optimization" + "-Wlogical-op" + "-Wredundant-decls" + "-Woverflow" + "-Wpointer-arith" + "-Wparentheses" + "-Wmain" + "-Wsign-compare" + "-Wtype-limits" + "-Wclobbered" + + ; Neither C++98 nor C89 had "long long" integers, but they + ; were fairly pervasive before being present in the standard. + ; + "-Wno-long-long" + + ; When constness is being deliberately cast away, `m_cast` is + ; used (for "m"utability). However, this is just a plain cast + ; in C as it has no const_cast. Since the C language has no + ; way to say you're doing a mutability cast on purpose, the + ; warning can't be used... but assume the C++ build covers it. + ; + (either cplusplus ["-Wcast-qual"] ["-Wno-cast-qual"]) + + ; The majority of Rebol's C code was written with little + ; attention to overflow in arithmetic. Frequently REBUPT + ; is assigned to REBCNT, size_t to REBYTE, etc. The issue + ; needs systemic review, but will be most easy to do so when + ; the core is broken out fully from less critical code + ; in optional extensions. + ; + ;"-Wstrict-overflow=5" + "-Wno-conversion" + "-Wno-strict-overflow" + ] + + ] + any [blank? args/RIGOROUS | args/RIGOROUS = "no"] [ + rigorous: false + {} + ] + true [ + fail ["RIGOROUS must be yes or no, not" (args/RIGOROUS)] + ] + ] +) newline + +{# For the build toolchain: +CC=} space (either cplusplus ["$(TOOLS)g++"] ["$(TOOLS)gcc"]) space "$(EXTRA_CC_FLAGS)" newline + +newline + +{NM= $(TOOLS)nm} newline + +newline + +{# CP allows different copy progs: +CP?=} space (either flag? COP ["copy"] ["cp"]) newline + +{# LS allows different directory list progs: +LS?=} space (either flag? DIR ["dir"] ["ls -l"]) newline + +{# UP - some systems do not use ../ +UP?=} space (either flag? -SP [""] [".."]) newline + +{# CD - some systems do not use ./ +CD?=} space (either flag? -SP [""] ["./"]) newline + +newline + +( + either symbols [ + ; + ; Easier in the rules below to have something that just takes a + ; filename than to actually conditionally use the strip commands. + ; + {STRIP= $(LS)} + ][ + {STRIP= $(TOOLS)strip} + ] +) newline + +newline + +{# Special tools: T= $(UP)/src/tools # Paths used by make: S= ../src @@ -70,345 +390,541 @@ R= $S/core INCL ?= . I= -I$(INCL) -I$S/include/ +PKGCONFIG ?= pkg-config +} + +newline + +{TO_OS_BASE?=} space (uppercase to-c-name [{TO_} config/os-base]) newline +{TO_OS_NAME?=} space (uppercase to-c-name [{TO_} config/os-name]) newline + +newline + +{BIN_SUFFIX=} space (either flag? EXE [".exe"] [""]) newline + +newline + +case [ + args/WITH_FFI = "static" [ + unspaced [ + {FFI_FLAGS=`${PKGCONFIG} --cflags libffi` -DHAVE_LIBFFI_AVAILABLE} + newline + {#only statically link ffi} + newline + {FFI_LIBS=-Wl,-Bstatic `${PKGCONFIG} --libs libffi`} + space {-Wl,-Bdynamic} + newline + ] + ] + args/WITH_FFI = "dynamic" [ + unspaced [ + {FFI_FLAGS=`${PKGCONFIG} --cflags libffi` -DHAVE_LIBFFI_AVAILABLE} + newline + {FFI_LIBS=`${PKGCONFIG} --libs libffi`} + newline + ] + ] + any [blank? args/WITH_FFI | args/WITH_FFI = "no"] [ + unspaced [ + {FFI_FLAGS=} newline + {FFI_LIBS=} newline + ] + ] + true [ + fail ["WITH_FFI must be static, dynamic or no, not" (args/WITH_FFI)] + ] +] + +newline + +case [ + any [blank? args/WITH_TCC | args/WITH_TCC = "no"] [ + unspaced [ + {TCC=} newline + {TCC_FLAGS=} newline + {TCC_LIB_DIR?=} + {TCC_LIBS=} newline + {TCC_LINK_FLAGS=} newline + ] + ] + true [ + path: to-rebol-file args/WITH_TCC + unless 'file = exists? path [ + fail ["WITH_TCC must be the path to the tcc executable or no, not" (args/WITH_TCC)] + ] + unspaced [ + {TCC=} path newline + {TCC_FLAGS=-DWITH_TCC -I../external/tcc} newline + {TCC_LIB_DIR?=} first split-path path newline + {TCC_LIBS=$(TCC_LIB_DIR)/libtcc1.a $(TCC_LIB_DIR)/libtcc.a} newline + {TCC_LINK_FLAGS=-L$(TCC_LIB_DIR)} newline + ] + ] +] + +newline +] + + + +; +; LIBRARY FLAGS +; +emit [ + {RAPI_FLAGS= $(LANGUAGE_FLAGS) $(DEBUG_FLAGS)} + space {$(SANITIZE_FLAGS) $(RIGOROUS_FLAGS)} +] + +for-each [flag switches] compiler-flags [ + if all [flag? (flag) | switches] [ + emit [space switches] + ] +] + +for-each [flag switches] lib-compiler-flags [ + if all [flag? (flag) | switches] [ + emit [space switches] + ] +] + +emit newline + +; +; HOST FLAGS +; -TO_OS?= -OS_ID?= -BIN_SUFFIX= -RAPI_FLAGS= -HOST_FLAGS= -DREB_EXE -RLIB_FLAGS= +emit [ + {HOST_FLAGS= $(LANGUAGE_FLAGS) $(DEBUG_FLAGS) $(SANITIZE_FLAGS)} + space {$(RIGOROUS_FLAGS) -DREB_EXE} +] + +for-each [flag switches] compiler-flags [ + if all [flag? (flag) | switches] [ + emit [space switches] + ] +] -# Flags for core and for host: -RFLAGS= -c -D$(TO_OS) -DREB_API $(RAPI_FLAGS) $I -HFLAGS= -c -D$(TO_OS) -DREB_CORE $(HOST_FLAGS) $I -CLIB= +emit newline -# REBOL is needed to build various include files: -REBOL_TOOL= r3-make -REBOL= $(CD)$(REBOL_TOOL) -qs +emit [ +newline +{# Flags for core and for host: +RFLAGS= -D$(TO_OS_BASE) -D$(TO_OS_NAME) -DREB_API $(RAPI_FLAGS) $(FFI_FLAGS) $I $(TCC_FLAGS) +HFLAGS= -D$(TO_OS_BASE) -D$(TO_OS_NAME) -DREB_CORE $(HOST_FLAGS) $I} + +newline newline + +{# Flags used by tcc to preprocess sys-core.h +# filter out options that tcc doesn't support +TCC_CPP_FLAGS_tmp=$(RFLAGS:--std%=) +TCC_CPP_FLAGS=$(TCC_CPP_FLAGS_tmp:--pedantic=)} + +newline newline +] + + +; +; LINKER FLAGS +; +; See %systems.r for the abbreviated table of linker flags per-system +; + +emit ["CLIB= $(SANITIZE_LINK_FLAGS)" space] + +emit case [ + any [blank? args/STATIC | args/STATIC = "no"] [ + ; + ; !!! Is there a way to explicitly request dynamic linking? + ; + {} + ] + args/STATIC = "yes" [ + join-of either sanitize ["-static-libasan "][{}] + either cplusplus [ + unspaced ["-static-libgcc -static-libstdc++" space] + ][ + unspaced ["-static-libgcc" space] + ] + ] + true [ + fail ["STATIC needs to be yes or no, not" (args/STATIC)] + ] +] + +for-each [flag switches] linker-flags [ + if all [flag? (flag) | switches] [ + emit [switches space] + ] +] + +emit newline +emit newline + + +; +; REBOL TOOLING +; + +emit [ +{# REBOL is needed to build various include files: +REBOL_TOOL= r3-make$(BIN_SUFFIX) +REBOL= $(CD)$(REBOL_TOOL) -qs # For running tests, ship, build, etc. -R3= $(CD)r3$(BIN_SUFFIX) -qs +R3_TARGET= r3$(BIN_SUFFIX) +R3= $(CD)$(R3_TARGET) -qs ### Build targets: top: - $(MAKE) r3$(BIN_SUFFIX) + $(MAKE) $(R3_TARGET) update: - -cd $(UP)/; cvs -q update src - -make: - $(REBOL) $T/make-make.r $(OS_ID) + -cd $(UP)/; cvs -q update src clean: - @-rm -rf libr3.so objs/ + @-rm -rf $(R3_TARGET) libr3.so objs/ + @-find ../src -name 'tmp-*' -exec rm -f {} \; + @-grep -l "AUTO-GENERATED FILE" ../src/include/*.h |grep -v sys-zlib.h|xargs rm 2>/dev/null || true all: - $(MAKE) clean - $(MAKE) prep - $(MAKE) r3$(BIN_SUFFIX) - $(MAKE) lib - $(MAKE) host$(BIN_SUFFIX) - -prep: - $(REBOL) $T/make-headers.r - $(REBOL) $T/make-boot.r $(OS_ID) - $(REBOL) $T/make-host-init.r - $(REBOL) $T/make-os-ext.r # ok, but not always - $(REBOL) $T/make-host-ext.r - $(REBOL) $T/make-reb-lib.r + $(MAKE) clean + $(MAKE) prep + $(MAKE) $(R3_TARGET) + $(MAKE) lib + $(MAKE) host$(BIN_SUFFIX) + +prep: $(REBOL_TOOL) + $(REBOL) $T/make-natives.r + $(REBOL) $T/make-headers.r + $(REBOL) $T/make-boot.r OS_ID=$(OS_ID) GIT_COMMIT=$(GIT_COMMIT) + $(REBOL) $T/make-host-init.r + $(REBOL) $T/make-os-ext.r + $(REBOL) $T/make-host-ext.r + $(REBOL) $T/make-reb-lib.r} newline + ; + ;-- more lines added to this section by the boot extensions +] + + +; +; EMIT BOOT EXTENSIONS +; +; The concept in Ren-C is to allow various pieces of Rebol to be chosen as +; either built into the EXE, available as a dynamic library, or not built +; at all. This is new work, and for starters just cryptography and image +; codecs are covered. But the concept behind it is that even the /VIEW +; GUI behavior itself would be such an extension. +; + +boot-extension-src: copy [] +extensions: copy "" +for-each [is-built-in ext-name ext-src modules init-script] file-base/extensions [ + unless '+ = is-built-in [ + continue + ] + + unless empty? extensions [append extensions ","] + append extensions to string! ext-name + append/only boot-extension-src ext-src ; ext-src is a path!, /ONLY needed + + ; Though not scanned for natives, there can be additional C files + ; specified for a module. + ; + for-each m modules [ + m-spec: find file-base/modules m + + ; Currently, only the extension's main C file is scanned for natives. + ; m-spec/2 is that main C file, see %file-base.r's "modules" + ; + emit [ + { $(REBOL) $T/make-ext-natives.r} space + {MODULE=} m-spec/1 space {SRC=} m-spec/2 newline + ] + + append/only boot-extension-src m-spec/2 ; main C file + append boot-extension-src m-spec/3 ; other files of the module + ] + + unless any [ + blank? init-script + init-script = '_ + ][ + emit [ + { $(REBOL) $T/make-ext-init.r} space {SRC=} init-script newline + ] + ] +] + +emit [ + { $(REBOL) $T/make-boot-ext-header.r EXTENSIONS=} extensions newline +] + +unless any [blank? args/WITH_TCC | args/WITH_TCC = "no"] [ + emit [ + { $(TCC) -E -dD -nostdlib -DREN_C_STDIO_OK -UHAVE_ASAN_INTERFACE_H -o ../src/include/sys-core.i $(TCC_CPP_FLAGS) $(TCC_CPP_EXTRA_FLAGS) -I../external/tcc/include ../src/include/sys-core.h} newline + { $(REBOL) $T/make-embedded-header.r} newline +] + + append file-base/generated [tmp-symbols.c e-embedded-header.c] +] + +emit [ +{zlib: + $(REBOL) $T/make-zlib.r ### Provide more info if make fails due to no local Rebol build tool: tmps: $S/include/tmp-bootdefs.h $S/include/tmp-bootdefs.h: $(REBOL_TOOL) - $(MAKE) prep + $(MAKE) prep $(REBOL_TOOL): - @echo - @echo "*** ERROR: Missing $(REBOL_TOOL) to build various tmp files." - @echo "*** Download Rebol 3 and copy it here as $(REBOL_TOOL), then" - @echo "*** make prep. Or, make prep on some other machine and copy" - @echo "*** the src/include files here. See README for details." - @echo - false + $(MAKE) -f makefile.boot $(REBOL_TOOL) ### Post build actions purge: - -rm libr3.* - -rm host$(BIN_SUFFIX) - $(MAKE) lib - $(MAKE) host$(BIN_SUFFIX) + -rm libr3.* + -rm host$(BIN_SUFFIX) + $(MAKE) lib + $(MAKE) host$(BIN_SUFFIX) test: - $(CP) r3$(BIN_SUFFIX) $(UP)/src/tests/ - $(R3) $S/tests/test.r + $(CP) $(R3_TARGET) $(UP)/src/tests/ + $(R3) $S/tests/test.r install: - sudo cp r3$(BIN_SUFFIX) /usr/local/bin + sudo cp $(R3_TARGET) /usr/local/bin ship: - $(R3) $S/tools/upload.r + $(R3) $S/tools/upload.r -build: libr3.so - $(R3) $S/tools/make-build.r +build: libr3.so + $(R3) $S/tools/make-build.r cln: - rm libr3.* r3.o + rm libr3.* r3.o check: - $(STRIP) -s -o r3.s r3$(BIN_SUFFIX) - $(STRIP) -x -o r3.x r3$(BIN_SUFFIX) - $(STRIP) -X -o r3.X r3$(BIN_SUFFIX) - $(LS) r3* + $(STRIP) -s -o r3.s $(R3_TARGET) + $(STRIP) -x -o r3.x $(R3_TARGET) + $(STRIP) -X -o r3.X $(R3_TARGET) + $(LS) r3* + +}] -} -;****************************************************************************** +; +; EMIT OBJ FILE DEPENDENCIES +; +; !!! The use of split path to remove directory in TO-OBJ had been commented +; out, but was re-added to incorporate the paths on codecs in a stop-gap +; measure to use make-make.r with Atronix repo +; -makefile-link: { +to-obj: function [ + "Create .o object filename (with no dir path)." + file +][ + file: (comment [to-file file] second split-path to-file file) + head change back tail file "o" +] + +emit-obj-files: procedure [ + "Output a line-wrapped list of object files." + file-list [block!] +][ + num-on-line: 0 + pending: _ + for-each item file-list [ + if pending [ + emit pending + pending: _ + ] + + file: either block? item [first item] [item] + + emit [%objs/ to-obj file space] + + if num-on-line = 4 [ + pending: unspaced ["\" newline spaced-tab] + num-on-line: 0 + ] + num-on-line: num-on-line + 1 + ] + emit [newline newline] +] + +emit ["OBJS =" space] +emit-obj-files compose [ + (file-base/core) (file-base/generated) (boot-extension-src) +] + +emit ["HOST =" space] +emit-obj-files compose [ + (file-base/os) (os-specific-objs) +] + +emit { # Directly linked r3 executable: -r3$(BIN_SUFFIX): tmps objs $(OBJS) $(HOST) - $(CC) -o r3$(BIN_SUFFIX) $(OBJS) $(HOST) $(CLIB) - $(STRIP) r3$(BIN_SUFFIX) - -$(NM) -a r3$(BIN_SUFFIX) - $(LS) r3$(BIN_SUFFIX) +$(R3_TARGET): tmps objs $(OBJS) $(HOST) + $(CC) -o $(R3_TARGET) $(OBJS) $(HOST) $(CLIB) $(FFI_LIBS) $(TCC_LINK_FLAGS) $(TCC_LIBS) + $(STRIP) $(R3_TARGET) + $(LS) $(R3_TARGET) objs: - mkdir -p objs + mkdir -p objs } + +; +; EMIT STATIC OR DYNAMIC LIBRARY +; +; Depending on the kind of target being built the R3-library can be either +; static or dynamic +; + makefile-so: { -lib: libr3.so +lib: libr3.so # PUBLIC: Shared library: # NOTE: Did not use "-Wl,-soname,libr3.so" because won't find .so in local dir. -libr3.so: $(OBJS) - $(CC) -o libr3.so -shared $(OBJS) $(CLIB) - $(STRIP) libr3.so - -$(NM) -D libr3.so - -$(NM) -a libr3.so | grep "Do_" - $(LS) libr3.so +libr3.so: $(OBJS) + $(CC) -o libr3.so -shared $(OBJS) $(CLIB) $(FFI_LIBS) + $(STRIP) libr3.so + $(LS) libr3.so # PUBLIC: Host using the shared lib: -host$(BIN_SUFFIX): $(HOST) - $(CC) -o host$(BIN_SUFFIX) $(HOST) libr3.so $(CLIB) - $(STRIP) host$(BIN_SUFFIX) - $(LS) host$(BIN_SUFFIX) - echo "export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH" +host$(BIN_SUFFIX): $(HOST) + $(CC) -o host$(BIN_SUFFIX) $(HOST) libr3.so $(CLIB) + $(STRIP) host$(BIN_SUFFIX) + $(LS) host$(BIN_SUFFIX) + echo "export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH" } makefile-dyn: { -lib: libr3.dylib +lib: libr3.dylib # Private static library (to be used below for OSX): -libr3.dylib: $(OBJS) - ld -r -o r3.o $(OBJS) - $(CC) -dynamiclib -o libr3.dylib r3.o $(CLIB) - $(STRIP) -x libr3.dylib - -$(NM) -D libr3.dylib - -$(NM) -a libr3.dylib | grep "Do_" - $(LS) libr3.dylib +libr3.dylib: $(OBJS) + ld -r -o r3.o $(OBJS) + $(CC) -dynamiclib -o libr3.dylib r3.o $(CLIB) + $(STRIP) -x libr3.dylib + $(LS) libr3.dylib # PUBLIC: Host using the shared lib: -host$(BIN_SUFFIX): $(HOST) - $(CC) -o host$(BIN_SUFFIX) $(HOST) libr3.dylib $(CLIB) - $(STRIP) host$(BIN_SUFFIX) - $(LS) host$(BIN_SUFFIX) - echo "export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH" +host$(BIN_SUFFIX): $(HOST) + $(CC) -o host$(BIN_SUFFIX) $(HOST) libr3.dylib $(CLIB) + $(STRIP) host$(BIN_SUFFIX) + $(LS) host$(BIN_SUFFIX) + echo "export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH" } not-used: { # PUBLIC: Static library (to distrirbute) -- does not work! -libr3.lib: r3.o - ld -static -r -o libr3.lib r3.o - $(STRIP) libr3.lib - -$(NM) -a libr3.lib | grep "Do_" - $(LS) libr3.lib +libr3.lib: r3.o + ld -static -r -o libr3.lib r3.o + $(STRIP) libr3.lib + $(LS) libr3.lib } -;****************************************************************************** -;** Options and Config -;****************************************************************************** - -opts: system/options/args -if block? opts [opts: first opts] -if opts = ">" [opts: "0.3.1"] ; bogus editor - -do %systems.r - -fb: make object! load %file-base.r - -either opts [ - opts: load opts - unless all [ - tuple? opts - os: config-system/platform opts - ][ - print ["*** Expected platform id (tuple like 0.3.1), not:" opts] - wait 2 - quit - ] - print ["Option set for building:" os/1 os/2] +either config/id/2 = 2 [ + emit makefile-dyn ][ - unless os: config-system [ - print ["*** Platform for" system/version "not supported"] - wait 2 - quit - ] -] - -set [os-plat os-name os-base build-flags] os -compile-flags: context compile-flags -linker-flags: context linker-flags -other-flags: context other-flags - -; Make plat id string: -plat-id: form os-plat/2 -if tail? next plat-id [insert plat-id #"0"] -append plat-id os-plat/3 - -; Create TO-OSNAME to indicate target OS: -to-def: join "TO_" uppercase copy os-name - -; Collect OS-specific host files: -os-specific-objs: select fb to word! join "os-" os-base -os-specific-dir: dirize to file! join %os/ os-base - -outdir: path-make -make-dir outdir -make-dir outdir/objs - -nl2: "^/^/" -output: make string! 10000 - -;****************************************************************************** -;** Functions -;****************************************************************************** - -flag?: func ['word] [found? find build-flags word] - -macro+: func [ - "Appends value to end of macro= line" - 'name - value - /local n a -][ - n: join newline name - value: form value - unless parse makefile-head [ - thru n any space ["=" | "?="] to newline ; over simplified - insert #" " insert value to end - ][ - print ajoin ["Cannot find " name "= definition"] - ] -] - -macro++: func ['name obj [object!] /local out] [ - out: make string! 10 - foreach n words-of obj [ - all [ - obj/:n - flag? (n) - repend out [space obj/:n] - ] - ] - macro+ (name) out -] - -emit: func [d] [repend output d] - -pad: func [str] [head insert/dup copy "" " " 16 - length? str] - -to-obj: func [ - "Create .o object filename (with no dir path)." - file + emit makefile-so +] + + +; +; EMIT FILE DEPENDENCIES +; +; !!! Because of how much scanning of header files to build temporary files +; there is, it's very hard to tell what kinds of changes to the source will +; necessitate a full build vs. an incremental one. So the dependencies list +; is not as useful as it is for many makefiles, as full builds are usually +; required. +; + +emit-file-deps: function [ + "Emit compiler and file dependency lines." + file-list + /dir path ; from path ][ - ;?? file - file: to-file file ;second split-path file - head change back tail file "o" + for-each item file-list [ + ; + ; Item may be like foo.c, or [foo.c ] + ; Make sure it's a block so it can be uniformly searched for options + ; + unless block? item [item: reduce [item]] + + file: first item + + obj: unspaced [%objs/ (to-obj file)] + + src: either not dir [ + unspaced ["$R/" file] + ][ + unspaced ["$S/" path file] + ] + + emit-line [obj ":" space src] + + file-specific-flags: copy "" + if rigorous [ + for-each [setting switches] [ + "-Wno-uninitialized" + "-Wno-unused-parameter" + "-Wno-shift-negative-value" + ;-Wno-unknown-warning suppresses a warning when + ;-Wimplicit-fallthrough is not recognized, e.g. GCC older than 7 + "-Wno-unknown-warning -Wno-implicit-fallthrough" + ][ + if not find item setting [continue] + + if not empty? file-specific-flags [ + append file-specific-flags space + ] + append file-specific-flags switches + ] + ] + + emit-line/indent spaced [ + "$(CC) -c" + pick ["$(RFLAGS)" "$(HFLAGS)"] not dir + file-specific-flags + src + "-o" + obj + ] + + emit newline + ] ] -emit-obj-files: func [ - "Output a line-wrapped list of object files." - files [block!] - /local cnt -][ - cnt: 1 - foreach file files [ - file: to-obj file - emit [%objs/ file " "] - if cnt // 4 = 0 [emit "\^/^-"] - cnt: cnt + 1 - ] - if tab = last output [clear skip tail output -3] - emit nl2 -] - -emit-file-deps: func [ - "Emit compiler and file dependency lines." - files - ;flags - /dir path ; from path - /local obj -][ - foreach src files [ - obj: to-obj src - src: rejoin pick [["$R/" src]["$S/" path src]] not dir - emit [ - %objs/ obj ":" pad obj src - newline tab - "$(CC) " - src " " - ;flags " " - pick ["$(RFLAGS)" "$(HFLAGS)"] not dir - " -o " %objs/ obj ; " " src - nl2 - ] - ] -] - -;****************************************************************************** -;** Build -;****************************************************************************** - -replace makefile-head "!date" now - -macro+ TO_OS to-def -macro+ OS_ID os-plat -macro+ LS pick ["dir" "ls -l"] flag? DIR -macro+ CP pick [copy cp] flag? COP -unless flag? -SP [ ; Use standard paths: - macro+ UP ".." - macro+ CD "./" -] -if os-plat/2 = 3 [macro+ REBOL ">NUL:"] ; Temporary workaround for R3 on Win7. -if flag? EXE [macro+ BIN_SUFFIX %.exe] -macro++ CLIB linker-flags -macro++ RAPI_FLAGS compile-flags -macro++ HOST_FLAGS make compile-flags [PIC: NCM: none] -macro+ HOST_FLAGS compile-flags/f64 ; default for all - -if flag? +SC [remove find os-specific-objs 'host-readline.c] - -emit makefile-head -emit ["OBJS =" tab] -emit-obj-files fb/core -emit ["HOST =" tab] -emit-obj-files append copy fb/os os-specific-objs -emit makefile-link -emit get pick [makefile-dyn makefile-so] os-plat/2 = 2 emit { ### File build targets: -b-boot.c: $(SRC)/boot/boot.r - $(REBOL) -sqw $(SRC)/tools/make-boot.r +tmp-boot-block.c: $(SRC)/boot/tmp-boot-block.r + $(REBOL) -sqw $(SRC)/tools/make-boot.r } emit newline -emit-file-deps fb/core -emit-file-deps/dir fb/os %os/ -emit-file-deps/dir os-specific-objs os-specific-dir -;print copy/part output 300 halt -print ["Created:" outdir/makefile] -write outdir/makefile output +emit-file-deps file-base/core +emit-file-deps file-base/generated +emit-file-deps boot-extension-src + +emit-file-deps/dir file-base/os %os/ +emit-file-deps/dir os-specific-objs %os/ + + +; +; OUTPUT MAKEFILE AND CREATE OBJ DIRECTORY +; +; Unfortunately, GNU make requires you use tab characters to indent, as part +; of the file format. This code uses 4 spaces instead, but then converts to +; tabs at the last minute--so this Rebol source file doesn't need to have +; actual tab characters in it. +; +make-dir path-make +write-emitted/tabbed path-make/makefile +make-dir path-make/objs +print ["Created:" path-make/makefile] diff --git a/src/tools/make-natives.r b/src/tools/make-natives.r new file mode 100644 index 0000000000..daf7ad8aa7 --- /dev/null +++ b/src/tools/make-natives.r @@ -0,0 +1,114 @@ +REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Generate native specifications" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Author: "@codebybrett" + Needs: 2.100.100 +] + +do %r2r3-future.r +do %common.r +do %common-parsers.r +do %native-emitters.r ;for emit-native-proto + +print "------ Generate tmp-natives.r" + +r3: system/version > 2.100.0 + +args: parse-args system/options/args +output-dir: fix-win32-path to file! any [:args/OUTDIR %../] +mkdir/deep output-dir/boot + +verbose: false + +unsorted-buffer: make string! 20000 + +process: func [ + file + ; the-file ;-- note external variable (can't do this in R3-Alpha) +][ + the-file: file + if verbose [probe [file]] + + source.text: read join-of core-folder file + if r3 [source.text: deline to-string source.text] + proto-parser/emit-proto: :emit-native-proto + proto-parser/process source.text +] + +;------------------------------------------------------------------------- + +output-buffer: make string! 20000 + +append output-buffer {REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Native function specs" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0. + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Note: {This is a generated file.} +] +} + +proto-count: 0 + +files: sort read core-folder: %../core/ + +remove-each file files [ + + not all [ + %.c = suffix? file + not find/match file "host-" + not find/match file "os-" + ] +] + +for-each file files [process file] + +append output-buffer unsorted-buffer + +write-if-changed output-dir/boot/tmp-natives.r output-buffer + +print [proto-count "natives"] +print " " + + +print "------ Generate tmp-actions.r" + +clear output-buffer + +append output-buffer {REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Action function specs" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0. + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Note: {This is a generated file.} +] + +} + +boot-types: load %../boot/types.r + +append output-buffer mold/only load %../boot/actions.r + +append output-buffer unspaced [newline newline] + +write-if-changed output-dir/boot/tmp-actions.r output-buffer diff --git a/src/tools/make-os-ext.r b/src/tools/make-os-ext.r index deeed32e29..42d7f9520f 100644 --- a/src/tools/make-os-ext.r +++ b/src/tools/make-os-ext.r @@ -1,16 +1,15 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Generate OS host API headers" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Author: "Carl Sassenrath" - Needs: 2.100.100 + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Generate OS host API headers" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Needs: 2.100.100 ] verbose: false @@ -20,176 +19,492 @@ version: load %../boot/version.r lib-version: version/3 print ["--- Make OS Ext Lib --- Version:" lib-version] -; Set platform TARGET +do %r2r3-future.r +do %common.r +do %common-parsers.r do %systems.r -target: config-system/os-dir + +args: parse-args system/options/args +config: config-system to-value :args/OS_ID +output-dir: fix-win32-path to file! any [:args/OUTDIR %../] +mkdir/deep output-dir/include do %form-header.r -change-dir append %../os/ target +file-base: has load %file-base.r -files: [ - %host-lib.c - %../host-device.c -] +change-dir %../os/ -; If it is graphics enabled: -if all [ - not find any [system/options/args []] "no-gfx" - find [3] system/version/4 -][ - append files [%host-window.c] +; Collect OS-specific host files: +unless ( + os-specific-objs: select file-base to word! unspaced ["os-" config/os-base] +)[ + fail [ + "make-os-ext.r requires os-specific obj list in file-base.r" + "none was provided for" unspaced ["os-" config/os-base] + ] ] -cnt: 0 +; We want a list of files to search for the host-lib.h export function +; prototypes (called out with fancy /******* headers). Those files are +; any preceded by a + sign in either the os or "os-specific" lists in +; file-base.r, so get those and ignore the rest. -xlib: make string! 20000 -rlib: make string! 1000 -mlib: make string! 1000 -dlib: make string! 1000 -xsum: make string! 1000 +files: copy [] -emit: func [d] [append repend xlib d newline] -remit: func [d] [append repend rlib d newline] -demit: func [d] [append repend dlib d newline] -memit: func [d /nol] [ - repend mlib d - if not nol [append mlib newline] -] +rule: ['+ set scannable [word! | path!] (append files to-file scannable) | skip] + +parse file-base/os [some rule] +parse os-specific-objs [some rule] + +proto-count: 0 + +host-lib-externs: make string! 20000 + +host-lib-struct: make string! 1000 + +host-lib-instance: make string! 1000 + +rebol-lib-macros: make string! 1000 +host-lib-macros: make string! 1000 + +; +; A checksum value is made to see if anything about the hostkit API changed. +; This collects the function specs for the purposes of calculating that value. +; +checksum-source: make string! 1000 count: func [s c /local n] [ - if find ["()" "(void)"] s [return "()"] - out: copy "(a" - n: 1 - while [s: find/tail s c][ - repend out [#"," #"a" + n] - n: n + 1 - ] - append out ")" + if find ["()" "(void)"] s [return "()"] + output-buffer: copy "(a" + n: 1 + while [s: find/tail s c][ + adjoin output-buffer [#"," #"a" + n] + n: n + 1 + ] + append output-buffer ")" ] -pads: func [start col] [ - col: col - offset? start tail start - head insert/dup clear "" #" " col -] +emit-proto: proc [ + proto +] [ + + if all [ + proto + trim proto + not find proto "static" + + pos.id: find proto "OS_" + + ;-- !!! All functions *should* start with OS_, not just + ;-- have OS_ somewhere in it! At time of writing, Atronix + ;-- has added As_OS_Str and when that is addressed in a + ;-- later commit to OS_STR_FROM_SERIES (or otherwise) this + ;-- backwards search can be removed + pos.id: next find/reverse pos.id space + pos.id: either #"*" = first pos.id [next pos.id] [pos.id] + + find proto #"(" + ] [ + + ; !!! We know 'the-file', but it's kind of noise to annotate + append host-lib-externs reduce [ + "extern " proto ";" newline + ] + + append checksum-source proto -func-header: [ - [ - thru "/***" 10 100 "*" newline - thru "*/" - copy spec to newline - (if all [ - spec - trim spec - not find spec "static" - fn: find spec "OS_" - find spec #"(" - ][ - emit ["extern " spec "; // " the-file] - append xsum spec - p1: copy/part spec fn - p3: find fn #"(" - p2: copy/part fn p3 - p2u: uppercase copy p2 - p2l: lowercase copy p2 - demit [tab p2 ","] - remit [tab p1 "(*" p2l ")" p3 ";"] - args: count p3 #"," - m: tail mlib - memit/nol ["#define " p2u args] - memit [pads m 35 " Host_Lib->" p2l args] - cnt: cnt + 1 - ] - ) - newline - [ - "/*" ; must be in func header section, not file banner - any [ - thru "**" - [#" " | #"^-"] - copy line thru newline - ] - thru "*/" - | - none - ] - ] + fn.declarations: copy/part proto pos.id + pos.lparen: find pos.id #"(" + fn.name: copy/part pos.id pos.lparen + fn.name.upper: uppercase copy fn.name + fn.name.lower: lowercase copy fn.name + + append host-lib-instance reduce [spaced-tab fn.name "," newline] + + append host-lib-struct reduce [ + spaced-tab fn.declarations "(*" fn.name.lower ")" pos.lparen ";" + newline + ] + + args: count pos.lparen #"," + append rebol-lib-macros reduce [ + {#define} space fn.name.upper args space {Host_Lib->} fn.name.lower args newline + ] + + append host-lib-macros reduce [ + "#define" space fn.name.upper args space fn.name args newline + ] + + proto-count: proto-count + 1 + ] ] process: func [file] [ - if verbose [?? file] - data: read the-file: file - data: to-string data ; R3 - parse/all data [ - any func-header - ] + if verbose [probe [file]] + data: read the-file: file + data: to-string data + proto-parser/emit-proto: :emit-proto + proto-parser/process data ] -;process %mem_string.c halt - -remit { +append host-lib-struct { typedef struct REBOL_Host_Lib ^{ - int size; - unsigned int ver_sum; - REBDEV **devices;} - -memit { -extern REBOL_HOST_LIB *Host_Lib; + int size; + unsigned int ver_sum; + REBDEV **devices; } -foreach file files [ - print ["scanning" file] - if all [ - %.c = suffix? file - ][process file] +for-each file files [ + print ["scanning" file] + if all [ + %.c = suffix? file + ][process file] ] -remit "} REBOL_HOST_LIB;" +append host-lib-struct "} REBOL_HOST_LIB;" + + +; +; Do a reduce which produces the output string we will write to host-lib.h +; + +output-buffer: reduce [ + +form-header/gen "Host Access Library" %host-lib.h %make-os-ext.r + +newline + +{#define HOST_LIB_VER} space lib-version newline +{#define HOST_LIB_SUM} space checksum/tcp to-binary checksum-source newline +{#define HOST_LIB_SIZE} space proto-count newline -out: reduce [ - form-header/gen "Host Access Library" %host-lib.h %make-os-ext.r { -#define HOST_LIB_VER } lib-version { -#define HOST_LIB_SUM } checksum/tcp to-binary xsum { -#define HOST_LIB_SIZE } cnt { +// !!! SEE **WARNING** BEFORE EDITING + +#ifdef __cplusplus +extern "C" ^{ +#endif extern REBDEV *Devices[]; + +/*********************************************************************** +** +** HOST LIB TABLE DEFINITION +** +** !!! +** !!! **WARNING!** DO NOT EDIT THIS! (until you've checked...) +** !!! BE SURE YOU ARE EDITING MAKE-OS-EXT.R AND NOT HOST-LIB.H +** !!! +** +** The "Rebol Host" provides a "Host Lib" interface to operating +** system services that can be used by "Rebol Core". Each host +** provides functions with names starting with OS_ and then a +** mixed-case name separated by underscores (e.g. OS_Get_Time). +** +** Rebol cannot call these functions directly. Instead, they are +** put into a table (which is actually a struct whose members are +** function pointers of the appropriate type for each call). It is +** similar in spirit to how IOCTLs work in operating systems: +** +** https://en.wikipedia.org/wiki/Ioctl +** +** To give a sense of scale, there are 48 separate functions in the +** Linux build at time of writing. Some functions are very narrow +** in what they do...such as OS_Browse which will open a web browser. +** Other functions are doorways to dispatching a wide variety of +** requests, such as OS_Do_Device.) +** +** So instead of OS_Get_Time, Core uses 'Host_Lib->os_get_time(...)'. +** Since that is verbose, an all-caps macro is provided, which in +** this case would be OS_GET_TIME. For parity, all-caps macros are +** provided in the host like '#define OS_GET_TIME OS_Get_Time'. As +** a result, the all-caps forms should be preserved since they can +** be read/copied/pasted consistently between host and core code. +** +** !!! +** !!! **WARNING!** DO NOT EDIT THIS! (until you've checked...) +** !!! BE SURE YOU ARE EDITING MAKE-OS-EXT.R AND NOT HOST-LIB.H +** !!! +** +***********************************************************************/ } -rlib -{ -//** Included by HOST ********************************************* -#ifndef REB_DEF +(host-lib-struct) newline -} -xlib { -#ifdef OS_LIB_TABLE +extern REBOL_HOST_LIB *Host_Lib; -REBOL_HOST_LIB *Host_Lib; -REBOL_HOST_LIB Host_Lib_Init = ^{ // Host library function vector table. - HOST_LIB_SIZE, - (HOST_LIB_VER << 16) + HOST_LIB_SUM, - (REBDEV**)&Devices, +//** Included by HOST ********************************************* + +#ifndef REB_DEF } -dlib -{^}; -#endif //OS_LIB_TABLE +newline (host-lib-externs) newline + +newline (host-lib-macros) newline +{ #else //REB_DEF //** Included by REBOL ******************************************** + } -mlib + +newline newline (rebol-lib-macros) + { #endif //REB_DEF + + +/*********************************************************************** +** +** "OS" MEMORY ALLOCATION AND FREEING MACROS +** +** !!! +** !!! **WARNING!** DO NOT EDIT THIS! (until you've checked...) +** !!! BE SURE YOU ARE EDITING MAKE-OS-EXT.R AND NOT HOST-LIB.H +** !!! +** +** These parallel Rebol's ALLOC/ALLOC_N/FREE macros. +** Main difference is that there is only one FREE, as the +** hostkit API is not required to remember the size on free. +** +** It is not strictly necessary to use these to allocate memory +** from the hostkit allocator instead of malloc(). The only +** time you are *required* to use the hostkit allocator is if +** you are exchanging memory with Rebol Core and have to +** agree about how to free it. (So if Rebol allocates +** something the Host may have to free, or vice-versa.) +** +** However, in embedded programming it is thought that perhaps +** malloc would not be available (or not the best choice) on +** small systems. So getting in the habit of using the +** habit of using the host allocator isn't a bad thing, and +** these macros make it convenient and type safe. +** +** In the Ren/C codebase where the goal is to be able to +** build with both ANSI C89 *and* C++ (all the way up to the +** latest standard, C++14 or C++17 etc.) then these macros +** are much better than doing the casting of malloc manually. +** +** Note: OS_ALLOC_N/OS_FREE_N used to be called OS_ALLOC_ARRAY +** and OS_FREE_ARRAY. But with the change of Rebol's ANY-BLOCK! +** to ANY-ARRAY! the ARRAY term has a more important use. So +** this uses N to mean "allocate N items contiguously". +** +** !!! +** !!! **WARNING!** DO NOT EDIT THIS! (until you've checked...) +** !!! BE SURE YOU ARE EDITING MAKE-OS-EXT.R AND NOT HOST-LIB.H +** !!! +** +***********************************************************************/ + +// !!! SEE **WARNING** BEFORE EDITING +#define OS_ALLOC(t) \ + cast(t *, OS_ALLOC_MEM(sizeof(t))) +#define OS_ALLOC_ZEROFILL(t) \ + cast(t *, memset(OS_ALLOC(t), '\0', sizeof(t))) +#define OS_ALLOC_N(t,n) \ + cast(t *, OS_ALLOC_MEM(sizeof(t) * (n))) +#define OS_ALLOC_N_ZEROFILL(t,n) \ + cast(t *, memset(OS_ALLOC_N(t, (n)), '\0', sizeof(t) * (n))) +#define OS_FREE(p) \ + OS_FREE_MEM(p) + + +/*********************************************************************** +** +** "OS" STRING FUNCTION ABSTRACTIONS +** +** !!! +** !!! **WARNING!** DO NOT EDIT THIS! (until you've checked...) +** !!! BE SURE YOU ARE EDITING MAKE-OS-EXT.R AND NOT HOST-LIB.H +** !!! +** +** Rebol's string values are currently represented internally as +** a series of either 8-bit REBYTEs (if codepoints are all <= 255) or +** a series of 16-bit REBUNIs otherwise. This is unrelated to +** the issue of what the native character width is on the +** platform which Rebol runs. Windows has standardized on 16-bit +** wide characters, and the wchar_t type is required to be 2 bytes +** on windows platforms. +** +** (There is no guarantee of the size of wchar_t on Linux, and +** the C standard itself does not require a guarantee on other +** platforms either.) +** +** Yet at *some* point, Rebol must communicate with the OS in its +** native format. The API interfaces for asking to read from a file +** or even to print a message out on the screen have different +** encodings on each platform. In order to speak of these strings, +** Rebol introduced a variable-sized character type called a REBCHR. +** +** !!! +** !!! **WARNING!** DO NOT EDIT THIS! (until you've checked...) +** !!! BE SURE YOU ARE EDITING MAKE-OS-EXT.R AND NOT HOST-LIB.H +** !!! +** +** REBCHR creates some complexity, because while code running on +** the host knows what size it is...Rebol's codebase has to treat +** it as a black box. However, it did not quite treat it so--and +** has a number of places where the strings were inspected and +** handled. These inspections generally relied upon wrappers of +** strncpy, strncat, strchr and strlen. But most of the code +** that used REBCHR at all was sketchy-at-best. +** +** @HostileFork feels that Rebol's model for extension probably +** needs another answer (or a more coherent version of the current +** answer) vs. having the core itself getting too hands-on with +** brokering native format strings. And the reach of REBCHR should +** be reigned in as much as possible, with host code using its +** own type (char, wchar_t). +** +** So in order to limit the scope of REBCHR, and ensure that type +** checking in the core is as rigorous as possible when dealing +** with it (effectively letting the wide char developers test +** their impacts on the non-wide char builds, and vice versa), the +** REBCHR type is "opaque" inside the core (see sys-core.h). It +** is so opaque as to be a struct containing the native char type +** in Debug builds. +** +** By contrast, REBCHR is "transparent" to the host (see reb-host.h) +** The expectation is that the host not use REBCHR or the wrappers +** like OS_STRLEN...instead using char/strlen or wchar_t/wcslen. +** However--the wrappers are still exported to the host, because +** there are some pieces of code that are written outside the core +** but are designed to be reused across hosts, so that code has to +** be as agnostic about the character size as the core does. +** +** !!! +** !!! **WARNING!** DO NOT EDIT THIS! (until you've checked...) +** !!! BE SURE YOU ARE EDITING MAKE-OS-EXT.R AND NOT HOST-LIB.H +** !!! +** +***********************************************************************/ + +#ifdef OS_WIDE_CHAR +// !!! SEE **WARNING** BEFORE EDITING + #define OS_WIDE TRUE + #define OS_STR_LIT(s) cast(const REBCHR*, L##s) +#else +// !!! SEE **WARNING** BEFORE EDITING + #define OS_WIDE FALSE + #define OS_STR_LIT(s) cast(const REBCHR*, s) +#endif + +#if defined(NDEBUG) || !defined(REB_DEF) +// !!! SEE **WARNING** BEFORE EDITING + #define OS_MAKE_CH(c) (c) + #define OS_CH_VALUE(c) (c) + #define OS_CH_EQUAL(os_ch, ch) ((os_ch) == (ch)) + + #ifdef OS_WIDE_CHAR + // !!! SEE **WARNING** BEFORE EDITING + #define OS_STRNCPY(d,s,m) \ + wcsncpy(cast(wchar_t*, (d)), cast(const wchar_t*, (s)), (m)) + #define OS_STRNCAT(d,s,m) \ + wcsncat(cast(wchar_t*, (d)), cast(const wchar_t*, (s)), (m)) + #define OS_STRNCMP(l,r,m) \ + wcsncmp(cast(wchar_t*, (l)), cast(const wchar_t*, (r)), (m)) + // We have to m_cast because C++ actually has a separate overload of + // wcschr which will return a const pointer if the in pointer was + // const. + #define OS_STRCHR(d,s) \ + cast(REBCHR*, \ + m_cast(wchar_t*, wcschr(cast(const wchar_t*, (d)), (s))) \ + ) + #define OS_STRLEN(s) wcslen(cast(const wchar_t*, (s))) + #else + #ifdef TO_OPENBSD + // !!! SEE **WARNING** BEFORE EDITING + #define OS_STRNCPY(d,s,m) \ + strlcpy(cast(char*, (d)), cast(const char*, (s)), (m)) + #define OS_STRNCAT(d,s,m) \ + strlcat(cast(char*, (d)), cast(const char*, (s)), (m)) + #else + // !!! SEE **WARNING** BEFORE EDITING + #define OS_STRNCPY(d,s,m) \ + strncpy(cast(char*, (d)), cast(const char*, (s)), (m)) + #define OS_STRNCAT(d,s,m) \ + strncat(cast(char*, (d)), cast(const char*, (s)), (m)) + #endif + #define OS_STRNCMP(l,r,m) \ + strncmp(cast(const char*, (l)), cast(const char*, (r)), (m)) + // We have to m_cast because C++ actually has a separate overload of + // strchr which will return a const pointer if the in pointer was + // const. + #define OS_STRCHR(d,s) \ + cast(REBCHR*, m_cast(char*, strchr(cast(const char*, (d)), (s)))) + #define OS_STRLEN(s) strlen(cast(const char*, (s))) + #endif +#else +// !!! SEE **WARNING** BEFORE EDITING + // Debug build only; fully opaque type and functions for certainty + #define OS_CH_VALUE(c) ((c).num) + #define OS_CH_EQUAL(os_ch, ch) ((os_ch).num == ch) + #define OS_MAKE_CH(c) OS_MAKE_CH_(c) + #define OS_STRNCPY(d,s,m) OS_STRNCPY_((d), (s), (m)) + #define OS_STRNCAT(d,s,m) OS_STRNCAT_((d), (s), (m)) + #define OS_STRNCMP(l,r,m) OS_STRNCMP_((l), (r), (m)) + #define OS_STRCHR(d,s) OS_STRCHR_((d), (s)) + #define OS_STRLEN(s) OS_STRLEN_(s) +#endif + +#ifdef __cplusplus +^} +#endif +} +] + +;print output-buffer ;halt +;print ['checksum checksum/tcp checksum-source] +write-if-changed output-dir/include/host-lib.h output-buffer + + +output-buffer: unspaced [ +form-header/gen "Host Table Definition" %host-table.inc %make-os-ext.r + +{ +/*********************************************************************** +** +** HOST LIB TABLE DEFINITION +** +** This is the actual definition of the host table. In order for +** the assignments to work, you must have included host-lib.h with +** REB_DEF undefined, to get the prototypes for the host kit +** functions. (You'll get this automatically if you are doing +** #include "reb-host.h). +** +** There can be only one instance of this table linked into your +** program, or you will get multiple defintitions of the Host_Lib +** table. You may wish to make a .c file that only includes +** this, in order to easily call out which object file has the +** singular definition of Host_Lib that you need. +** +** !!! +** !!! **WARNING!** DO NOT EDIT THIS! (until you've checked...) +** !!! BE SURE YOU ARE EDITING MAKE-OS-EXT.R AND NOT HOST-LIB.H +** !!! +** +***********************************************************************/ + +EXTERN_C REBOL_HOST_LIB Host_Lib_Init; + +REBOL_HOST_LIB Host_Lib_Init = ^{ + + HOST_LIB_SIZE, + (HOST_LIB_VER << 16) + HOST_LIB_SUM, + (REBDEV**)&Devices, } + +(host-lib-instance) + +"^};" newline ] -;print out ;halt -;print ['checksum checksum/tcp xsum] -write %../../include/host-lib.h out +write-if-changed output-dir/include/host-table.inc output-buffer + ;ask "Done" print " " diff --git a/src/tools/make-reb-lib.r b/src/tools/make-reb-lib.r index b2208a851c..59ba9a2c7e 100644 --- a/src/tools/make-reb-lib.r +++ b/src/tools/make-reb-lib.r @@ -1,18 +1,23 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "Make Reb-Lib related files" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Author: "Carl Sassenrath" - Needs: 2.100.100 + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Make Reb-Lib related files" + Rights: { + Copyright 2012 REBOL Technologies + Copyright 2012-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Needs: 2.100.100 ] +do %r2r3-future.r +do %common.r +do %common-parsers.r +do %form-header.r + print "--- Make Reb-Lib Headers ---" verbose: true @@ -25,196 +30,123 @@ src-dir: %../core/ reb-lib: src-dir/a-lib.c ext-lib: src-dir/f-extension.c -out-dir: %../include/ +args: parse-args system/options/args +output-dir: to file! any [:args/OUTDIR %../] +output-dir: fix-win32-path output-dir +out-dir: output-dir/include +mkdir/deep out-dir + reb-ext-lib: out-dir/reb-lib.h ; for Host usage reb-ext-defs: out-dir/reb-lib-lib.h ; for REBOL usage ver: load %../boot/version.r -do %form-header.r ;----------------------------------------------------------------------------- ;----------------------------------------------------------------------------- -cnt: 0 - -xlib: make string! 20000 -rlib: make string! 1000 -mlib: make string! 1000 -dlib: make string! 1000 -cmts: make string! 1000 -xsum: make string! 1000 - -emit: func [d] [append repend xlib d newline] -remit: func [d] [append repend rlib d newline] -demit: func [d] [append repend dlib d newline] -cemit: func [d] [append repend cmts d newline] -memit: func [d /nol] [ - repend mlib d - if not nol [append mlib newline] -] +proto-count: 0 -count: func [s c /local n] [ - if find ["()" "(void)"] s [return "()"] - out: copy "(a" - n: 1 - while [s: find/tail s c][ - repend out [#"," #"a" + n] - n: n + 1 - ] - append out ")" -] +xlib-buffer: make string! 20000 +rlib-buffer: make string! 1000 +mlib-buffer: make string! 1000 +dlib-buffer: make string! 1000 +xsum-buffer: make string! 1000 -in-sub: func [str pat /local f] [ - all [ - f: find cmt pat ":" - insert f "^/:" - f: find next f newline - remove f - insert f " - " - ] +emit: func [d] [append adjoin xlib-buffer d newline] +emit-rlib: func [d] [append adjoin rlib-buffer d newline] +emit-dlib: func [d] [append adjoin dlib-buffer d newline] +emit-mlib: proc [d /nol] [ + adjoin mlib-buffer d + if not nol [append mlib-buffer newline] ] -gen-doc: func [fspec spec cmt] [ - replace/all cmt "**" " " - replace/all cmt "/*" " " - replace/all cmt "*/" " " - trim cmt - append cmt newline - - insert find cmt "Arguments:" "^/:" - bb: beg: find/tail cmt "Arguments:" - insert any [find bb "notes:" tail bb] newline - while [ - all [ - beg: find beg " - " - positive? offset? beg any [find beg "notes:" tail beg] - ] - ][ - insert beg - insert find/tail/reverse beg newline {
} - beg: find/tail beg " - " - ] - - beg: insert bb { - } ;
} - remove find beg newline - remove/part find beg "
" 4 ; extra
- - remove find cmt "^/Returns:" - in-sub cmt "Returns:" - in-sub cmt "Notes:" - - insert cmt reduce [ - ":Function: - " spec - "^/^/:Summary: - " - ] - cemit ["===" fspec newline newline cmt] +count: func [s c /local n] [ + if find ["()" "(void)"] s [return "()"] + out: copy "(a" + n: 1 + while [s: find/tail s c][ + adjoin out [#"," #"a" + n] + n: n + 1 + ] + append out ")" ] -pads: func [start col] [ - col: col - offset? start tail start - head insert/dup clear "" #" " col +in-sub: func [text pattern /local position] [ + all [ + position: find text pattern ":" + insert position "^/:" + position: find next position newline + remove position + insert position " - " + ] ] -func-header: [ - [ - thru "RL_API " - copy spec to newline skip - ["/*" copy cmt thru "*/" | none] - ( - if all [ - spec - trim spec - fn: find spec preface - find spec #"(" - ][ - emit ["RL_API " spec ";"] ; // " the-file] - append xsum spec - p1: copy/part spec fn - p3: find fn #"(" - p2: copy/part fn p3 - p2u: uppercase copy p2 - p2l: lowercase copy find/tail p2 preface - demit [tab p2 ","] - remit [tab p1 "(*" p2l ")" p3 ";"] - args: count p3 #"," - m: tail mlib - memit/nol ["#define " p2u args] - memit [pads m 35 " RL->" p2l args] - if w: find cmt "****" [append clear w "*/"] - memit ["/*^/**^-" spec "^/**" cmt newline] - - gen-doc p2 spec cmt - cnt: cnt + 1 - ] - ) - newline - [ - "/*" ; must be in func header section, not file banner - any [ - thru "**" - [#" " | #"^-"] - copy line thru newline - ] - thru "*/" - | - none - ] - ] +pads: func [start col] [ + str: copy "" + col: col - offset-of start tail start + head insert/dup str #" " col ] -write-if: func [file data] [ - if data <> attempt [to string! read file][ ;R3 - print ["UPDATE:" file] - write file data - ] +emit-proto: proc [ + proto +] [ + + if all [ + proto + trim proto + pos.id: find proto preface + find proto #"(" + ] [ + emit ["RL_API " proto ";"] ; // " the-file] + append xsum-buffer proto + fn.declarations: copy/part proto pos.id + pos.lparen: find pos.id #"(" + fn.name: copy/part pos.id pos.lparen + fn.name.upper: uppercase copy fn.name + fn.name.lower: lowercase copy find/tail fn.name preface + + emit-dlib [spaced-tab fn.name ","] + + emit-rlib [ + spaced-tab fn.declarations "(*" fn.name.lower ")" pos.lparen ";" + ] + + args: count pos.lparen #"," + mlib.tail: tail mlib-buffer + emit-mlib/nol ["#define " fn.name.upper args] + emit-mlib [pads mlib.tail 35 " RL->" fn.name.lower args] + + comment-text: proto-parser/notes + encode-lines comment-text {**} { } + + emit-mlib [ + "/*" newline + "**" space space proto newline + "**" newline + comment-text + "*/" newline + ] + + proto-count: proto-count + 1 + ] ] process: func [file] [ - if verbose [?? file] - data: to string! read the-file: file ;R3 - parse/all data [ - any func-header - ] + if verbose [probe [file]] + data: read the-file: file + data: to-string data + + proto-parser/proto-prefix: "RL_API " + proto-parser/emit-proto: :emit-proto + proto-parser/process data ] ;----------------------------------------------------------------------------- -remit { +emit-rlib { typedef struct rebol_ext_api ^{} -cemit [{Host/Extension API - -=r3 - -=*Updated for A} ver/3 { on } now/date { - -=*Describes the functions of reb-lib, the REBOL API (both the DLL and extension library.) - -=!This document is auto-generated and changes should not be made within this wiki. - -=note WARNING: PRELIMINARY Documentation - -=*This API is under development and subject to change. Various functions may be moved, removed, renamed, enhanced, etc. - -Also note: the formatting of this document will be enhanced in future revisions. - -=/note - -==Concept - -The REBOL API provides common API functions needed by the Host-Kit and also by -REBOL extension modules. This interface is commonly referred to as "reb-lib". - -There are two methods of linking to this code: - -*Direct calls as you would use functions within any DLL. - -*Indirect calls through a set of macros (that use a structure pointer to the library.) - -==Functions -}] - ;----------------------------------------------------------------------------- process reb-lib @@ -222,7 +154,7 @@ process ext-lib ;----------------------------------------------------------------------------- -remit "} RL_LIB;" +emit-rlib "} RL_LIB;" out: to-string reduce [ form-header/gen "REBOL Host and Extension API" %reb-lib.r %make-reb-lib.r @@ -233,28 +165,24 @@ form-header/gen "REBOL Host and Extension API" %reb-lib.r %make-reb-lib.r #define RL_REV } ver/2 { #define RL_UPD } ver/3 { -// Compatiblity with the lib requires that structs are aligned using the same -// method. This is concrete, not abstract. The macro below uses struct -// sizes to inform the developer that something is wrong. -#define CHECK_STRUCT_ALIGN (sizeof(REBREQ) == 80 && sizeof(REBEVT) == 12) // Function entry points for reb-lib (used for MACROS below):} -rlib +rlib-buffer { // Extension entry point functions: -#ifdef TO_WIN32 -#ifdef __cplusplus -#define RXIEXT extern "C" __declspec(dllexport) +#ifdef TO_WINDOWS + #define RXIEXT __declspec(dllexport) #else -#define RXIEXT __declspec(dllexport) + #define RXIEXT extern #endif -#else -#define RXIEXT extern + +#ifdef __cplusplus +extern "C" ^{ #endif RXIEXT const char *RX_Init(int opts, RL_LIB *lib); RXIEXT int RX_Quit(int opts); -RXIEXT int RX_Call(int cmd, RXIFRM *frm, void *data); +RXIEXT int RX_Call(int cmd, const REBVAL *frm, void *data); // The macros below will require this base pointer: extern RL_LIB *RL; // is passed to the RX_Init() function @@ -262,7 +190,7 @@ extern RL_LIB *RL; // is passed to the RX_Init() function // Macros to access reb-lib functions (from non-linked extensions): } -mlib +mlib-buffer { #define RL_MAKE_BINARY(s) RL_MAKE_STRING(s, FALSE) @@ -270,13 +198,18 @@ mlib #ifndef REB_EXT // not extension lib, use direct calls to r3lib } -xlib +xlib-buffer { +#endif // REB_EXT + +#ifdef __cplusplus +^} #endif + } ] -write-if reb-ext-lib out +write-if-changed reb-ext-lib out ;----------------------------------------------------------------------------- @@ -284,14 +217,12 @@ out: to-string reduce [ form-header/gen "REBOL Host/Extension API" %reb-lib-lib.r %make-reb-lib.r {RL_LIB Ext_Lib = ^{ } -dlib +dlib-buffer {^}; } ] -write-if reb-ext-defs out - -write-if %../reb-lib-doc.txt cmts +write-if-changed reb-ext-defs out ;ask "Done" print " " diff --git a/src/tools/make-zlib.r b/src/tools/make-zlib.r new file mode 100644 index 0000000000..6361c5d069 --- /dev/null +++ b/src/tools/make-zlib.r @@ -0,0 +1,215 @@ +REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Make sys-zlib.h and u-zlib.c" + Rights: { + Copyright 2012 REBOL Technologies + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + In order to limit build dependencies, Rebol makes a snapshot of a + subset of certain libraries to include in the source distribution. + This script will extract just the parts of ZLIB that Rebol needs + to put into one .h file and one .c file. It generates its + snapshot from their official source repository: + + https://github.com/madler/zlib + + Any significant reorganization of the ZLIB codebase would require + updating this script accordingly. It was last tested on 1.2.8 + (released 28-Apr-2013) + } + Note: [ + "This runs relative to ../tools directory." + ] +] + + +; +; Target paths+filenames for the generated include and source file +; +path-include: %../../src/include/ +file-include: %sys-zlib.h +path-source: %../../src/core/ +file-source: %u-zlib.c + + +; +; In theory this should be able to run against the network to get the +; source data from the Git raw format of the latest version. But until +; https is in Rebol core, github is not an option; you have to clone +; locally and reset this path to wherever you put it. +; +path-zlib: https://raw.github.com/madler/zlib/master/ + + +; +; Disable #include "foo.h" style inclusions (but not #include style) +; Optionally will inline a list of files at the inclusion point +; +disable-user-includes: func [ + lines [block!] {Block of strings} + /inline headers [block!] {Block of filenames to inline if seen} + /local name line-iter line pos +] [ + line-iter: lines + while [line-iter <> tail lines] [ + line: first line-iter + if parse line [any space {#} any space {include} some space {"} copy name to {"} to end] [ + either all [ + inline + pos: find headers to file! name + ] [ + change/part line-iter (read/lines join-all [path-zlib name]) 1 + take pos + ] [ + insert line unspaced [{//} space] + append line unspaced [ + space {/* REBOL: see make-zlib.r */} + ] + ] + ] + line-iter: next line-iter + ] + + ; If we inline a header, it should happen once and only once for each + unless empty? headers [ + fail [{Not all headers inlined by make-zlib:} (mold headers)] + ] +] + + +; +; Stern warning not to edit the files +; + +make-warning-lines: func [name [file!] title [string!]] [ + reduce [ + {/***********************************************************************} + {**} + {** Extraction of ZLIB compression and decompression routines} + {** for REBOL [R3] Language Interpreter and Run-time Environment} + {** This is a code-generated file.} + {**} + {** ZLIB Copyright notice:} + {**} + {** (C) 1995-2013 Jean-loup Gailly and Mark Adler} + {**} + {** This software is provided 'as-is', without any express or implied} + {** warranty. In no event will the authors be held liable for any damages} + {** arising from the use of this software.} + {**} + {** Permission is granted to anyone to use this software for any purpose,} + {** including commercial applications, and to alter it and redistribute it} + {** freely, subject to the following restrictions:} + {**} + {** 1. The origin of this software must not be misrepresented; you must not} + {** claim that you wrote the original software. If you use this software} + {** in a product, an acknowledgment in the product documentation would be} + {** appreciated but is not required.} + {** 2. Altered source versions must be plainly marked as such, and must not be} + {** misrepresented as being the original software.} + {** 3. This notice may not be removed or altered from any source distribution.} + {**} + {** Jean-loup Gailly Mark Adler} + {** jloup@gzip.org madler@alumni.caltech.edu} + {**} + {** REBOL is a trademark of REBOL Technologies} + {** Licensed under the Apache License, Version 2.0} + {**} + {************************************************************************} + {**} + unspaced [{** Title: } title] + {** Build: A0} + unspaced [{** Date: } now/date] + unspaced [{** File: } to string! name] + {**} + {** AUTO-GENERATED FILE - Do not modify. (From: make-zlib.r)} + {**} + {***********************************************************************/} + ] +] + +do %r2r3-future.r +do %common.r + +;; +;; Generate %sys-zlib.h Aggregate Header File +;; + +header-lines: copy [] + +for-each h-file [ + %zconf.h + %zutil.h + %zlib.h + %deflate.h +] [ + append header-lines read/lines join-all [path-zlib h-file] +] + +disable-user-includes header-lines + +insert header-lines [ + {} + {#define NO_DUMMY_DECL 1} + {#define Z_PREFIX 1} + {} +] + +insert header-lines make-warning-lines file-include {ZLIB aggregated header file} + +write/lines join-all [path-include file-include] header-lines + + + +;; +;; Generate %u-zlib.c Aggregate Source File +;; + +source-lines: copy [] + +append source-lines read/lines join-all [path-zlib %crc32.c] + +; +; Macros DO1 and DO8 are defined differently in crc32.c, and if you don't #undef +; them you'll get a redefinition warning. +; +append source-lines [ + {#undef DO1 /* REBOL: see make-zlib.r */} + {#undef DO8 /* REBOL: see make-zlib.r */} +] + +for-each c-file [ + %adler32.c + + %deflate.c + %zutil.c + %compress.c + %uncompr.c + %trees.c + + %inftrees.h + %inftrees.c + %inffast.h + %inflate.h + %inffast.c + %inflate.c +] [ + append source-lines read/lines join-all [path-zlib c-file] +] + +disable-user-includes/inline source-lines [%trees.h %inffixed.h %crc32.h] + +insert source-lines [ + {} + {#include "sys-zlib.h" /* REBOL: see make-zlib.r */} + {} +] + +insert source-lines make-warning-lines file-source {ZLIB aggregated source file} + +write/lines join-all [path-source file-source] source-lines diff --git a/src/tools/native-emitters.r b/src/tools/native-emitters.r new file mode 100644 index 0000000000..06d465e17f --- /dev/null +++ b/src/tools/native-emitters.r @@ -0,0 +1,114 @@ +REBOL [ + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "Generate auto headers" + File: %natives-emitters.r + Rights: { + Copyright 2017 Atronix Engineering + Copyright 2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Needs: 2.100.100 +] + +emit-native-proto: proc [ + "Emit native prototypes to @unsorted-buffer" + proto +] [ + + if all [ + 'format2015 = proto-parser/style + block? proto-parser/data + any [ + 'native = proto-parser/data/2 + all [ + path? proto-parser/data/2 + 'native = proto-parser/data/2/1 + ] + ] + ] [ + line: line-of source.text proto-parser/parse.position + + if not block? proto-parser/data/3 [ + fail [ + "Native" (uppercase form to word! proto-parser/data/1) + "needs loadable specification block." + (mold the-file) (line) + ] + ] + + append case [ + ; could do tests here to create special buffer categories to + ; put certain natives first or last, etc. (not currently needed) + ; + true [ ;-- R3-Alpha needs to bootstrap, do not convert to an ELSE! + unsorted-buffer + ] + ] unspaced [ + newline newline + {; !!! DO NOT EDIT HERE! This is generated from } + mold the-file { line } line newline + mold/only proto-parser/data + ] + + proto-count: proto-count + 1 + ] +] + +emit-include-params-macro: procedure [ + "Emit macros for a native's parameters" + word [word!] "name of the native" + paramlist [block!] "paramlist of the native" +] [ + ; + ; start emitting what will be a multi line macro (backslash as last + ; character on line is how macros span multiple lines in C). + ; + emit-line [ + {#define} space "INCLUDE_PARAMS_OF_" (uppercase to-c-name word) + space "\" + ] + + ; Collect the argument and refinements, converted to their "C names" + ; (so dashes become underscores, * becomes _P, etc.) + ; + n: 1 + for-each item paramlist [ + if all [any-word? item | not set-word? item] [ + param-name: to-c-name to-word item + + which: either refinement? item ["REFINE"] ["PARAM"] + emit-line/indent [ + which "(" n "," space param-name ");" space "\" + ] + n: n + 1 + ] + ] + + comment [ + ; Get rid of trailing \ for multi-line macro continuation. + unemit newline + unemit #"\" + emit newline + ] + + emit-line [spaced-tab "Enter_Native(frame_);" space] +] + +emit-native-include-params-macro: proc [native-list [block!]][ + for-next native-list [ + if tail? next native-list [break] + + if any [ + 'native = native-list/2 + all [path? native-list/2 | 'native = first native-list/2] + ][ + assert [set-word? native-list/1] + emit-include-params-macro (to-word native-list/1) (native-list/3) + emit newline + ] + ] +] diff --git a/src/tools/r2r3-future.r b/src/tools/r2r3-future.r new file mode 100644 index 0000000000..479f7bf7d1 --- /dev/null +++ b/src/tools/r2r3-future.r @@ -0,0 +1,470 @@ +REBOL [ + Title: "Rebol2 and R3-Alpha Future Bridge to Ren-C" + Rights: { + Rebol 3 Language Interpreter and Run-time Environment + "Ren-C" branch @ https://github.com/metaeducation/ren-c + + Copyright 2012 REBOL Technologies + Copyright 2012-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + These routines can be run from R3-Alpha or Rebol2 to make them act + more like the vision of Rebol3-Beta and beyond (as conceived by the + "Ren-C" initiative). + + It also must remain possible to run it from Ren-C without disrupting + the environment. This is because the primary motivation for its + existence is to shim older R3-MAKE utilities to be compatible with + Ren-C...and the script is run without knowing whether the R3-MAKE + you are using is old or new. No canonized versioning strategy has + been yet chosen, so words are "sniffed" for existing definitions in + this somewhat simplistic method. + + !!! Because the primary purpose is for Ren-C's bootstrap, the file + is focused squarely on those needs. However, it is a beginning for + a more formalized compatibility effort. Hence it is awaiting someone + who has a vested interest in Rebol2 or R3-Alpha code to become a + "maintenance czar" to extend the concept. In the meantime it will + remain fairly bare-bones, but enhanced if-and-when needed. + } +] + + +if true = attempt [void? :some-undefined-thing] [ + ; + ; Ren-C, define an "optional" marker for returns. (You can't use + ; on parameters in code designed to run Ren-C-like code in R3-Alpha.) + ; + *opt-legacy*: _ + + ; ELSE uses a mechanic (non-tight infix evaluation) that is simply + ; impossible in R3-Alpha or Rebol2. + ; + else: does [ + fail "Do not use ELSE in scripts which want compatibility w/R3-Alpha" + ] + + QUIT ;-- !!! stops running if Ren-C here. +] + + +; Running R3-Alpha/Rebol2, bootstrap VOID? into existence and continue +; +void?: :unset? +void: does [] + + +; Older versions of Rebol had a different concept of what FUNCTION meant +; (an arity-3 variation of FUNC). Eventually the arity-2 construct that +; did locals-gathering by default named FUNCT overtook it, with the name +; FUNCT deprecated. +; +unless (copy/part words-of :function 2) = [spec body] [ + function: :funct +] + + +; `func [x [*opt-legacy* integer!]]` is like `func [x [ integer!]]`, +; and with these modifications can work in either Ren-C or R3-Alpha/Rebol2. +; +*opt-legacy*: unset! + + +blank?: get 'none? +blank!: get 'none! +blank: get 'none +_: none + + +; ANY-VALUE! is anything that isn't void. +; +any-value!: difference any-type! (make typeset! [unset!]) +any-value?: func [item [*opt-legacy* any-value!]] [not void? :item] + + +; Used in function definitions before the mappings +; +any-context!: :any-object! +any-context?: :any-object? + + +set?: func [ + "Returns whether a bound word has a value (fails if unbound)" + any-word [any-word!] +][ + unless bound? any-word [ + fail [any-word "is not bound in set?"] + ] + value? any-word ;-- the "old" meaning of value... +] + +verify: :assert ;-- ASSERT is a no-op in Ren-C in "release", but verify isn't + + + +leave: does [ + do make error! "LEAVE cannot be implemented in usermode R3-Alpha" +] + +proc: func [spec body] [ + func spec compose [(body) void] +] + +procedure: func [spec body] [ + function spec compose [(body) void] +] + + +; Ren-C replaces the awkward term PAREN! with GROUP! (Retaining PAREN! +; for compatibility as pointing to the same datatype). Older Rebols +; haven't heard of GROUP!, so establish the reverse compatibility. +; +group?: get 'paren? +group!: get 'paren! + + +; The HAS routine in Ren-C is used for object creation with no spec, as +; a parallel between FUNCTION and DOES. It is favored for this purpose +; over CONTEXT which is very "noun-like" and may be better for holding +; a variable that is an ANY-CONTEXT! +; +; Additionally, the CONSTRUCT option behaves like MAKE ANY-OBJECT, sort of, +; as the way of creating objects with parents or otherwise. +; +has: :context + +construct-legacy: :construct + +construct: function [ + "Creates an ANY-CONTEXT! instance" + spec [datatype! block! any-context!] + "Datatype to create, specification, or parent/prototype context" + body [block! any-context! none!] + "keys and values defining instance contents (bindings modified)" + /only + "Values are kept as-is" +][ + either only [ + if block? spec [spec: make object! spec] + construct-legacy/only/with body spec + ][ + if block? spec [ + ; + ; If they supplied a spec block, do a minimal behavior which + ; will create a parent object with those fields...then run + ; the traditional gathering added onto that using the body + ; + spec: map-each item spec [ + assert [word? :item] + to-set-word item + ] + append spec none + spec: make object! spec + ] + make spec body + ] +] + + +; Lone vertical bar is an "expression barrier" in Ren-C, but a word character +; in other situations. Having a word behave as a function that returns an +; UNSET! in older Rebols is not quite the same, but can have a similar effect +; in terms of creating errors if picked up by most function args. +; +|: does [] + + +; SET/OPT is the Ren-C replacement for SET/ANY, with /ANY supported +; via . But Rebol2 and R3-Alpha do not know /OPT. +; +lib-set: get 'set ; overwriting lib/set for now +set: func [ + {Sets a word, path, block of words, or context to specified value(s).} + + target [any-word! any-path! block! any-context!] + {Word, block of words, path, or object to be set (modified)} + + value [*opt-legacy* any-value!] + "Value or block of values" + /opt + "Value is optional, and if no value is provided then unset the word" + /pad + {For objects, set remaining words to NONE if block is too short} + /any + "Deprecated legacy synonym for /opt" +][ + set_ANY: any + any: :lib/any ;-- in case it needs to be used + opt_ANY: opt + lib-set/any 'opt () ;-- doesn't exist in R3-Alpha + + apply :lib-set [target :value (any [opt_ANY set_ANY]) pad] +] + + +; GET/OPT is the Ren-C replacement for GET/ANY, with /ANY supported +; via . But Rebol2 and R3-Alpha do not know /OPT. +; +lib-get: get 'get +get: function [ + {Gets the value of a word or path, or values of a context.} + source + "Word, path, context to get" + /opt + "The source may optionally have no value (allows returning void)" + /any + "Deprecated legacy synonym for /OPT" +][ + set_ANY: any + any: :lib/any ;-- in case it needs to be used + opt_ANY: opt + lib-set/any 'opt () ;-- doesn't exist in R3-Alpha + + lib-set/any (quote temp:) lib-get/any source + either any [opt_ANY set_ANY] [ + :temp ;-- voids okay + ][ + either void? :temp [blank] [:temp] + ] +] + + +; R3-Alpha would only REDUCE a block and pass through other outputs. +; REDUCE in Ren-C (and also in Red) is willing to reduce anything that +; does not require EVAL-like argument consumption (so GROUP!, GET-WORD!, +; GET-PATH!). +; +lib-reduce: get 'reduce +reduce: func [ + {Evaluates expressions and returns multiple results.} + value + /no-set + "Keep set-words as-is. Do not set them." + /only + "Only evaluate words and paths, not functions" + words [block! blank!] + "Optional words that are not evaluated (keywords)" + /into + {Output results into a series with no intermediate storage} + target [any-block!] +][ + either block? :value [ + apply :lib-reduce [value no-set only words into target] + ][ + ; For non-blocks, put the item in a block, reduce the block, + ; then pick the first element out. This may error (e.g. if you + ; try to reduce a word looking up to a function taking arguments) + ; + ; !!! Simple with no refinements for now--enhancement welcome. + ; + assert [not no-set not only not into] + first (lib-reduce lib-reduce [:value]) + ] +] + + +; Ren-C's FAIL dialect is still being designed, but the basic is to be +; able to ramp up from simple strings to block-composed messages to +; fully specifying ERROR! object fields. Most commonly it is a synonym +; for `do make error! form [...]`. +; +fail: func [ + {Interrupts execution by reporting an error (TRAP can intercept it).} + reason [error! string! block!] + "ERROR! value, message string, or failure spec" +][ + case [ + error? reason [do error] + string? reason [do make error! reason] + block? reason [ + for-each item reason [ + unless any [ + any-scalar? :item + string? :item + group? :item + all [ + word? :item + not any-function? get :item + ] + ][ + probe reason + do make error! ( + "FAIL requires complex expressions in a GROUP!" + ) + ] + ] + do make error! form reduce reason + ] + ] +] + + +unset!: does [ + fail "UNSET! not a type, use *opt-legacy* as in func specs" +] + +unset?: does [ + fail "UNSET? reserved for future use, use VOID? to test no value" +] + + +; Note: EVERY cannot be written in R3-Alpha because there is no way +; to write loop wrappers, given lack of definitionally scoped return +; +for-each: get 'foreach +foreach: does [ + fail "In Ren-C code, please use FOR-EACH and not FOREACH" +] + +for-next: get 'forall +forall: does [ + fail "In Ren-C code, please use FOR-NEXT and not FORALL" +] + + +; Not having category members have the same name as the category +; themselves helps both cognition and clarity inside the source of the +; implementation. +; +any-array?: get 'any-block? +any-array!: get 'any-block! + + +; Renamings to conform to ?-means-returns-true-false rule +; https://trello.com/c/BxLP8Nch +; +length: length-of: get 'length? +index-of: get 'index? +offset-of: get 'offset? +type-of: get 'type? + + +; Source code that comes back from LOAD or is in a module is read-only in +; Ren-C by default. Non-mutating forms of the "mutate by default" +; operators are suffixed by -OF (APPEND-OF, INSERT-OF, etc.) There +; is a relationship between historical "JOIN" and "REPEND" that is very +; much like this, and with JOIN the mutating form and JOIN-OF the one +; that copies, it brings about consistency and kills an annoying word. +; +; Rather than change this all at once, JOIN becomes JOIN-OF and REPEND +; is left as it is (as the word has no intent to be reclaimed for other +; purposes.) +; +join-of: get 'join +join: does [ + fail "use JOIN-OF for JOIN (one day, JOIN will replace REPEND)" +] + +; R3-Alpha's version of REPEND was built upon R3-Alpha's notion of REDUCE, +; which wouldn't reduce anything but BLOCK!. Having it be a no-op on PATH! +; or WORD! was frustrating, so Red and Ren-C made it actually reduce whatever +; it got. But that affected REPEND so that it arguably became less useful. +; +; With Ren-C retaking JOIN, it makes more sense to take more artistic license +; and make the function more useful than strictly APPEND REDUCE as suggested +; by the name REPEND. So in that spirit, the JOIN will only reduce blocks. +; This makes it like R3-Alpha's REPEND. +; +; The temporary name is ADJOIN, which will be changed to JOIN someday when +; existing JOIN usages have all been changed to JOIN-OF. +; +adjoin: get 'repend + + +; It's not possible to write loop wrappers that work correctly with RETURN, +; and so a good forward-compatible version of UNTIL as WHILE-NOT isn't really +; feasible. So just don't use it. +; +loop-until: get 'until +until: does [ + fail "UNTIL in Ren-C will be arity 2 (WHILE-NOT), can't mimic in R3-Alpha" +] + + +; Note: any-context! and any-context? supplied at top of file + +; *all* typesets now ANY-XXX to help distinguish them from concrete types +; https://trello.com/c/d0Nw87kp +; +any-scalar?: get 'scalar? +any-scalar!: scalar! +any-series?: get 'series? +any-series!: series! +any-number?: get 'number? +any-number!: number! + + +; "optional" (a.k.a. void) handling +opt: func [ + {Turns blanks to voids, all other value types pass through.} + value [*opt-legacy* any-value!] +][ + either* blank? :value [()] [:value] +] + +to-value: func [ + {Turns voids to blank, with ANY-VALUE! passing through. (See: OPT)} + value [*opt-legacy* any-value!] +][ + get 'value +] + +something?: func [value [*opt-legacy* any-value!]] [ + not any [ + void? :value + blank? :value + ] +] + +; It is not possible to make a version of eval that does something other +; than everything DO does in an older Rebol. Which points to why exactly +; it's important to have only one function like eval in existence. +; +eval: get 'do + + +; R3-Alpha and Rebol2 did not allow you to make custom infix operators. +; There is no way to get a conditional infix AND using those binaries. +; In some cases, the bitwise and will be good enough for logic purposes... +; +and*: get 'and +and?: func [a b] [true? all [:a :b]] +and: get 'and ; see above + +or+: get 'or +or?: func [a b] [true? any [:a :b]] +or: get 'or ; see above + +xor+: get 'xor +xor?: func [a b] [true? any [all [:a (not :b)] all [(not :a) :b]]] + + +; UNSPACED in Ren-C corresponds rougly to AJOIN, and SPACED corresponds very +; roughly to REFORM. A similar "sort-of corresponds" applies to REJOIN being +; like JOIN-ALL. There are missing features in the handling of voids and +; blanks, as well as CHAR!s and BAR!s. +; +; Since the only code really running modern Ren-C-named constructs through an +; R3-Alpha is the bootstrap, the necessity of making this work well depends +; on how aggressive the use of modern features in bootstrap are. +; +unspaced: :ajoin +spaced: :reform +join-all: :rejoin + + +; This isn't a full implementation of ENSURE with function-oriented testing, +; but it works well enough for types. +; +ensure: function [type [datatype!] value [*opt-legacy* any-value!]] [ + if type != type-of :value [ + probe :value + fail ["ENSURE expected:" (mold type) "but got" (mold type-of :value)] + ] + return :value +] diff --git a/src/tools/systems.r b/src/tools/systems.r index d6d1751a9d..10155b3675 100644 --- a/src/tools/systems.r +++ b/src/tools/systems.r @@ -1,104 +1,296 @@ REBOL [ - System: "REBOL [R3] Language Interpreter and Run-time Environment" - Title: "System build targets" - Rights: { - Copyright 2012 REBOL Technologies - REBOL is a trademark of REBOL Technologies - } - License: { - Licensed under the Apache License, Version 2.0 - See: http://www.apache.org/licenses/LICENSE-2.0 - } - Author: "Carl Sassenrath" - Purpose: { - These are the target system definitions used to build REBOL - with a variety of compilers and libraries. I prefer to keep it - simple like this rather than using a complex configuration tool - that could make it difficult to support REBOL on older platforms. - } + System: "REBOL [R3] Language Interpreter and Run-time Environment" + Title: "System build targets" + Rights: { + Copyright 2012 REBOL Technologies + Copyright 2012-2017 Rebol Open Source Contributors + REBOL is a trademark of REBOL Technologies + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Purpose: { + These are the target system definitions used to build REBOL + with a variety of compilers and libraries. We prefer to keep it + simple like this rather than using a complex configuration tool + that could make it difficult to support REBOL on older platforms. + + Note that these numbers for the OS are the minor numbers at the + tail of the system/version tuple. (The first tuple values are + used for the Rebol code version itself.) + + If you have a comment to make about a build, make it in the + form of a flag...even if the functionality for that flag is a no-op + (signaled by a BLANK!). This keeps the table clean and readable. + + This file uses a table format processed by routines in %common.r, + so be sure to include that via DO before calling CONFIG-SYSTEM. + } ] systems: [ - [plat os-name os-base build-flags] - [0.1.03 "amiga" posix [HID NPS +SC CMT COP -SP -LM]] - [0.2.04 "osx" posix [+OS NCM -LM]] ; no shared lib possible - [0.2.05 "osxi" posix [ARC +O1 NPS PIC NCM HID STX -LM]] - [0.3.01 "win32" win32 [+O2 UNI W32 CON S4M EXE DIR -LM]] - [0.4.02 "linux" posix [+O2 LDL ST1 -LM]] ; libc 2.3 - [0.4.03 "linux" posix [+O2 HID LDL ST1 -LM]] ; libc 2.5 - [0.4.04 "linux" posix [+O2 HID LDL ST1 M32 -LM]] ; libc 2.11 - [0.4.10 "linux_ppc" posix [+O1 HID LDL ST1 -LM]] - [0.4.20 "linux_arm" posix [+O2 HID LDL ST1 -LM]] - [0.4.21 "linux_arm" posix [+O2 HID LDL ST1 -LM PIE]] ; bionic (Android) - [0.4.30 "linux_mips" posix [+O2 HID LDL ST1 -LM]] ; glibc does not need C++ - [0.5.75 "haiku" posix [+O2 ST1 NWK]] - [0.7.02 "freebsd" posix [+O1 C++ ST1 -LM]] - [0.9.04 "openbsd" posix [+O1 C++ ST1 -LM]] - [0.13.01 "android_arm" android [HID F64 LDL LLOG -LM CST]] + ;------------------------------------------------------------------------- + [id os-name os-base + build-flags] + ;------------------------------------------------------------------------- + 0.1.03 amiga posix + [BEN LLC HID NPS +SC CMT COP -SP -LM F64] + ;------------------------------------------------------------------------- + 0.2.04 osx-ppc osx + [BEN LLC NCM -LM NSO F64] + + 0.2.05 osx-x86 osx + [ARC LEN LLC NPS PIC NCM HID STX -LM F64] + + 0.2.40 osx-x64 osx + [LP64 LEN LLC NPS PIC NCM HID STX -LM F64] + ;------------------------------------------------------------------------- + 0.3.01 windows-x86 windows + [LEN LL? UNI W32 CON S4M EXE DIR -LM F64] + + 0.3.40 windows-x64 windows + [LLP64 LEN LL? UNI W32 CON S4M EXE DIR -LM F64] + ;------------------------------------------------------------------------- + 0.4.02 linux-x86 linux + [M32 LEN LLC LDL ST1 -LM LC23 UFS NSP NSER F64] + + 0.4.03 linux-x86 linux + [M32 LEN LLC LDL ST1 -LM LC25 UFS HID F64] + + 0.4.04 linux-x86 linux + [M32 LEN LLC LDL ST1 -LM LC211 HID PIP2 F64] + + 0.4.10 linux-ppc linux + [BEN LLC HID LDL ST1 -LM PIP2 F64] + + 0.4.11 linux-ppc64 linux + [LP64 BEN LLC HID LDL ST1 -LM PIP2 F64] + + 0.4.20 linux-arm linux + [LEN LLC HID LDL ST1 -LM PIP2 F64] + + 0.4.21 linux-arm linux + [LEN LLC HID LDL ST1 -LM PIE LCB PIP2 F64] + + 0.4.22 linux-aarch64 linux + [LP64 LEN LLC HID LDL ST1 -LM PIP2 F64] + + 0.4.30 linux-mips linux + [LEN LLC HID LDL ST1 -LM PIP2 F64] + + 0.4.31 linux-mips32be linux + [BEN LLC HID LDL ST1 -LM PIP2 F64] + + 0.4.40 linux-x64 linux + [LP64 LEN LLC HID LDL ST1 -LM PIP2 F64] + + 0.4.60 linux-axp linux + [LP64 LEN LLC HID LDL ST1 -LM PIP2 F64] + + 0.4.61 linux-ia64 linux + [LP64 LEN LLC HID LDL ST1 -LM PIP2 F64] + ;------------------------------------------------------------------------- + 0.5.75 haiku posix + [LEN LLC ST1 NWK F64] + ;------------------------------------------------------------------------- + 0.7.02 freebsd-x86 posix + [LEN LLC ST1 -LM F64] + + 0.7.40 freebsd-x64 posix + [LP64 LEN LLC ST1 -LM F64] + ;------------------------------------------------------------------------- + 0.9.04 openbsd posix + [LEN LLC ST1 -LM F64] + + 0.9.40 openbsd posix + [LP64 LEN LLC ST1 -LM F64] + ;------------------------------------------------------------------------- + 0.13.01 android-arm android + [LEN LLC HID F64 LDL LLOG -LM F64] + ;------------------------------------------------------------------------- + 0.13.02 android5-arm android + [LEN LLC HID F64 LDL LLOG -LM PIE PIC F64] + ;------------------------------------------------------------------------- + 0.14.01 syllable-dtp posix + [LEN LLC HID LDL ST1 -LM LC25 F64] + + 0.14.02 syllable-svr linux + [M32 LEN LLC HID LDL ST1 -LM LC211 F64] +] + +compiler-flags: context [ + M32: "-m32" ; use 32-bit memory model + ARC: "-arch i386" ; x86 32 bit architecture (OSX) + + LP64: "-D__LP64__" ; 64-bit, and 'void *' is sizeof(long) + LLP64: "-D__LLP64__" ; 64-bit, and 'void *' is sizeof(long long) + + BEN: "-DENDIAN_BIG" ; big endian byte order + LEN: "-DENDIAN_LITTLE" ; little endian byte order + + LLC: "-DHAS_LL_CONSTS" ; supports e.g. 0xffffffffffffffffLL + LL?: _ ; might have LL consts, reb-config.h checks + + UNI: "-DUNICODE" ; win32 wants it + HID: "-fvisibility=hidden" ; all syms are hidden + F64: "-D_FILE_OFFSET_BITS=64" ; allow larger files + NPS: "-Wno-pointer-sign" ; OSX fix + PIE: "-fPIE" ; position independent (executables) + + ; There are variations in what functions different compiler versions will + ; wind up linking in to support the same standard C functions. This + ; means it is not possible to a-priori know what libc version that + ; compiler's build product will depend on when using a shared libc.so + ; + ; To get a list of the glibc stubs your build depends on, run this: + ; + ; objdump -T ./r3 | fgrep GLIBC + ; + ; Notably, increased security measures caused functions like poll() and + ; longjmp() to link to checked versions available only in later libc, + ; or to automatically insert stack_chk calls for stack protection: + ; + ; http://stackoverflow.com/a/35404501/211160 + ; http://unix.stackexchange.com/a/92780/118919 + ; + ; As compilers evolve, the workarounds to make them effectively cross + ; compile to older versions of the same platform will become more complex. + ; Switches that are needed to achieve this compilation may not be + ; supported by old compilers. This simple build system is not prepared + ; to handle both "platform" and "compiler" variations; each OS_ID is + ; intended to be used with the standard compiler for that platform. + ; + NSP: "-fno-stack-protector" ; stack protect pulls in glibc 2.4 calls + PIP2: "-DUSE_PIPE2_NOT_PIPE" ; pipe2() linux only, glibc 2.9 or later + UFS: "-U_FORTIFY_SOURCE" ; don't link to _chk variants of C calls + NSER: ; strerror_r() in glibc 2.3.4, not 2.3.0 + "-DUSE_STRERROR_NOT_STRERROR_R" ] -compile-flags: [ - +OS: "-Os" ; size optimize - +O1: "-O1" ; full optimize - +O2: "-O2" ; full optimize - UNI: "-DUNICODE" ; win32 wants it - CST: "-DCUSTOM_STARTUP" ; include custom startup script at host boot - HID: "-fvisibility=hidden" ; all syms are hidden - F64: "-D_FILE_OFFSET_BITS=64" ; allow larger files - NPS: "-Wno-pointer-sign" ; OSX fix - NSP: "-fno-stack-protector" ; avoid insert of functions names - PIC: "-fPIC" ; position independent (used for libs) - PIE: "-fPIE" ; position independent (executables) - DYN: "-dynamic" ; optimize for dll?? - NCM: "-fno-common" ; lib cannot have common vars - PAK: "-fpack-struct" ; pack structures - ARC: "-arch i386" ; x86 32 bit architecture (OSX) - M32: "-m32" ; use 32-bit memory model + +; These flags are only applicable to the library, so %make-make.r considers +; them separately from the common flags. +; +lib-compiler-flags: context [ + PIC: "-fPIC" ; position independent (used for libs) + NCM: "-fno-common" ; lib cannot have common vars ] -linker-flags: [ - MAP: "-Wl,-M" ; output a map - STA: "--strip-all" - C++: "-lstdc++" ; link with stdc++ - LDL: "-ldl" ; link with dynamic lib lib - LLOG: "-llog" ; on Android, link with liblog.so - ARC: "-arch i386" ; x86 32 bit architecture (OSX) - M32: "-m32" ; use 32-bit memory model (Linux x64) - W32: "-lwsock32 -lcomdlg32" - WIN: "-mwindows" ; build as Windows GUI binary - CON: "-mconsole" ; build as Windows Console binary - S4M: "-Wl,--stack=4194300" - -LM: "-lm" ; HaikuOS has math in libroot, for instance - NWK: "-lnetwork" ; Needed by HaikuOS + +linker-flags: context [ + M32: "-m32" ; use 32-bit memory model (Linux x64) + ARC: "-arch i386" ; x86 32 bit architecture (OSX) + + NSO: _ ; no shared libs + LDL: "-ldl" ; link with dynamic lib lib + LLOG: "-llog" ; on Android, link with liblog.so + + W32: "-lwsock32 -lcomdlg32" + CON: "-mconsole" ; build as Windows Console binary + S4M: "-Wl,--stack=4194300" + -LM: "-lm" ; Math library (Haiku has it in libroot) + NWK: "-lnetwork" ; Needed by HaikuOS + + PIE: "-pie" + + ; Which libc is used is commentary, it has to be influenced by other + ; flags. See notes above about NSP, PIP1, UFS which are used to try and + ; actually control these outcomes. + ; + LC23: _ ; libc 2.3 + LC25: _ ; libc 2.5 + LC211: _ ; libc 2.11 + LCB: _ ; bionic (Android) ] -other-flags: [ - +SC: "" ; has smart console - -SP: "" ; non standard paths - COP: "" ; use COPY as cp program - DIR: "" ; use DIR as ls program - ST1: "-s" ; strip flags... - STX: "-x" - ST2: "-S -x -X" - CMT: "-R.comment" - EXE: "" ; use %.exe as binary file suffix + +other-flags: context [ + +SC: _ ; has smart console + -SP: _ ; non standard paths + COP: _ ; use COPY as cp program + DIR: _ ; use DIR as ls program + ST1: "-s" ; strip flags... + STX: "-x" + CMT: "-R.comment" + EXE: _ ; use %.exe as binary file suffix ] -config-system: func [ - "Return build configuration information" - /fields "record variables" - /define "the TO_TARGET define name" - /os-dir "the %osname/ directory" - /platform v [tuple!] + +; A little bit of sanity-checking on the systems table +use [rec unknown-flags used-flags] [ + ; + ; !!! See notes about RETURN from FOR-EACH-RECORD in its definition. + ; + used-flags: copy [] + for-each-record rec systems [ + assert [ + | tuple? rec/id + | (to-string rec/os-name) == (lowercase to-string rec/os-name) + | (to-string rec/os-base) == (lowercase to-string rec/os-base) + | not find (to-string rec/os-base) charset [#"-" #"_"] + | block? rec/build-flags + ] + + for-each flag rec/build-flags [assert [word? flag]] + + ; Exclude should mutate (CC#2222), but this works either way + unknown-flags: exclude (unknown_flags: copy rec/build-flags) compose [ + (words-of compiler-flags) + (words-of linker-flags) + (words-of other-flags) + (words-of lib-compiler-flags) + ] + if not empty? unknown-flags [ + print mold unknown-flags + fail "Unknown flag used in %systems.r specification" + ] + + used-flags: union used-flags rec/build-flags + ] + + unused-flags: exclude compose [ + (words-of compiler-flags) + (words-of linker-flags) + (words-of other-flags) + (words-of lib-compiler-flags) + ] used-flags + + if not empty? unused-flags [ + print mold unused-flags + fail "Unused flags in %systems.r specifications" + ] +] + + +config-system: function [ + {Return build configuration information} + hint [blank! string! tuple!] + {Version ID (blank means guess)} ][ - if fields [return first systems] - v: any [v to tuple! reduce [0 system/version/4 system/version/5]] - foreach rec next systems [ - if rec/1 = v [ - if os-dir [return dirize to-file rec/3] - if define [return to-word uppercase join "TO_" rec/2] - return rec - ] - ] - none + version: case [ + blank? hint [ + ; + ; Try same version as this r3-make was built with + ; + to tuple! reduce [0 system/version/4 system/version/5] + ] + string? hint [ + load hint + ] + ] + + unless tuple? version [ + fail [ + "Expected OS_ID tuple like 0.3.1, not:" version + ] + ] + + unless result: find-record-unique systems 'id version [ + fail [ + {No table entry for} version {found in systems.r} + ] + ] + + result ] diff --git a/tests/LICENSE b/tests/LICENSE new file mode 100644 index 0000000000..57bc88a15a --- /dev/null +++ b/tests/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000000..1ff15fc232 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,212 @@ +> These tests originated from the repository [on GitHub][1]. It was originally the work of Carl Sassenrath, Ladislav Mecir, Andreas Bolka, Brian Hawley, and John K. +> +> The Ren-C fork was moved from a submodule directly into the %tests/ subdirectory. This is because development on the original repository ceased, and it makes keeping the tests in sync with the Ren-C version easier. +> +> In the Ren-C version, an extensive source analysis script was added by Brett Handley. This has parse rules for C, and is used to check the source codebase itself for static analysis properties. +> +> The test suite itself as well as the test framework are Licensed under the [Apache License, Version 2.0][2]. +> +> * Copyright 2012 REBOL Technologies +> * Copyright 2013 Saphirion AG +> * Copyright 2013-2017 Rebol Open Source Contributors +> +> This README was originally written by Ladislav Mecir. + +[1]: https://github.com/rebolsource/rebol-test +[2]: http://www.apache.org/licenses/LICENSE-2.0 + + +# Running the Tests + +The test cases are listed in `core-tests.r` and the script for running them is called `run-recover.r`. Executing it on Kubuntu might look like: + + ladislav@lkub64:/rebol-test$ /r3/make/r3 run-recover.r + +If the program crashes (either the test framework or the interpreter), just run the tests again the same way as before. The crash will be noticed and the log will pick up from the previous point. + +*(Note: Although the original core-tests contained cases that were known to crash or fail, the baseline for the Ren-C fork is a zero-tolerance for crashes or failures. It should therefore not be considered normal for a crash necessitating recovery to occur, and patches should not be submitted if the tests cannot pass.)* + + +# Log Files + +The tests in the log file are always text-copies of the tests from the test file, which means that they are not modified in any way. It is possible to run them in REBOL console as well as to find them using text search in the test file if desired. + +A run-recover.r result in Kubuntu running a build of the 2.101.0.4.4 interpreter might be named: + + r_2_101_0_4_4_F9A855_E85A1B.log + +The first character of the log file name, #"r" is common to all run-recover log files. The next part describes the version of the interpreter, the following 6 characters are a part of the interpreter executable checksum, and the last 6 characters preceding the file extension are a part of the core-tests.r file checksum. + +*(Note: The test framework needs a path to the interpreter executable to be able to calculate the interpreter checksum. If the full path to the interpreter executable isn't obtained from the command line, the argument of the run-recover.r script will be used as the path to the executable. Otherwise the system/build variable will be used.)* + + +# Test File Format + +The test file format was originally designed by Carl Sassenrath to be Rebol compatible, and as simple as possible. Here are some tests cases for the closure! datatype, notice that only some of them are marked as #r3only, suggesting they are meant just for the R3 interpreter: + + ; datatypes/closure.r + [closure? closure [] ["OK"]] + [not closure? 1] + #r3only + [closure! = type? closure [] ["OK"]] + ; minimum + [closure? closure [] []] + ; literal form + #r3only + [closure? first [#[closure! [[] []]]]] + +Despite its Rebol appearance, the test file is not LOAD-ed in its entirety by the interpreter. This complicates test file parsing a bit, but it brings significant advantages: + +- An individual "malformed" test will not cause all tests to fail. + +- A single test file can be used to test different (more or less source-code compatible) interpreters, every one of them having a different "idea" what "Rebol" is. + +- One of the properties that can be tested is the ability of the interpreter to load the test as Rebol code. + +- Since the test file is handled by the test framework as a text file having the format described below, the test framework is able to always record/handle the original "look" of the tests. Therefore, the original tests cannot be "distorted" in the log by any incorrect LOAD/MOLD transformation performed by the interpreter. + +- Tests "stand for themselves" not needing any names. (Test writers can use whatever naming convention they prefer, but names are not required for the test framework to be able to handle the tests.) + + +# Test Dialect + +### Test cases + +Test cases have to be enclosed in properly matched square brackets + +A test is successful only if it can be correctly loaded and it yields LOGIC! TRUE when evaluated. + +Breaks, throws, errors, returns, etc. leading out of the test code are detected and marked as test failures. The test framework is built in such a way that it can recover from any kind of crash and finish the testing after the restart. + +### Comments + +Comments following the semicolon character until the end of the line are allowed. + +### Flags + +Issues are used to signal special handling of the test. They are handled by the environment as flags excluding the marked test from processing. Only if all flags used are in the set of acceptable flags, the specific test is processed by the environment, otherwise it is skipped. + +Issues are used to indicate special character of tests. For example, + + #64bit + +...indicates that the test is meant to be used only in 64-bit builds. Any test may be marked by as many flags as desired. + +Flags restrict the usage of tests. If the DO-RECOVER function is called without a specific flag being mentioned in the FLAGS argument, all tests marked using that flag are ignored. For example, if the above #64bit flag is not mentioned in the FLAGS argument, no #64bit test is run. + +Currently available flags are: + + ; the flag influences only the test immediately following it, + ; if not explicitly stated otherwise + + #32bit + ; the test is meant to be used only when integers are 32bit + + #64bit + ; the test is meant to be used only when integers are 64bit + +*(Note: Originally flags existed for selecting if tests ran in R3-Alpha vs. Rebol2, but these were removed in the Ren-C fork.)* + +### Files/URLs + +Files or URLs specify what to include, i.e., they allow a file to contain references to other test files. + +*(Note: Despite the existence of this feature, the core-tests were created as a giant monolithic file. @HostileFork didn't even know this feature existed, because it wasn't used. If this test suite's methodology is to be used going forward, then certainly multiple files must be used!)* + + +# Summary + +The summary (can be found at the end of the log file), e.g.: + + system/version: 2.7.8.3.1 + interpreter-checksum: #{1DEF65DDE53AB24C122DA6C76646A36D7D910790} + test-checksum: #{E85A1B2945437E38E7654B9904937821C8F2FA92} + Total: 4598 + Succeeded: 3496 + Test-failures: 156 + Crashes: 7 + Dialect-failures: 0 + Skipped: 939 + +...in the former case and + + system/version: 2.101.0.4.4 + interpreter-checksum: #{F9A855727FE738149B8E769C37A542D4E4C8FF82} + test-checksum: #{E85A1B2945437E38E7654B9904937821C8F2FA92} + Total: 4598 + Succeeded: 4136 + Test-failures: 142 + Crashes: 15 + Dialect-failures: 0 + Skipped: 305 + +...in the latter. + +The test-checksums and the total number of the tests are equal. That is because we used the same version of the tests. + +However, the numbers of succeeded tests, failed tests, crashing tests and skipped tests differ. + +The reason why the number of skipped tests differ is that 2.7.8 is R2 while 2.101.0 is R3. These interpreter versions are different in many aspects and it does not make sense to perform some R2 tests in R3 environment and vice versa, which leads to the necessity to skip some tests depending on the interpreter type. + +The "Dialect failures" number counts the cases when the test framework found incorrectnesses in the test file, cases when the test file was not written in accordance with the formatting rules described below. + +If you get more than zero dialect failures, you should correct the respective test file. + +The test environment counts successful tests, failed tests, crashing tests, skipped tests and test dialect failures, i.e., the cases when the test file is not properly formatted. + +Files or URLs in the test file "outside" of tests are handled as directives for the test environment to process the tests in the respective file as well. + + +# Filtering test logs + +Sometimes we are not interested in all test results preferring to see only a list of failed tests. The log-filter.r script can be used for that as follows: + + e:\Ladislav\rebol\rebol-view.exe log-filter.r r_2_7_8_3_1_1DEF65_E85A1B.log + +The result is the file: + + f_2_7_8_3_1_1DEF65_E85A1B.log + +i.e., the file having a prefix #"f", otherwise having the same name as the original log file and containing just the list of failed tests. + + +# Comparing test logs + +We have seen that we obtained different test summaries for different interpreter versions. There is a log-diff.r script allowing us to obtain the list and summary of the differences between two log files. + +The `log-diff.r` script can be run as follows: + + e:\Ladislav\rebol\rebol-view.exe log-diff.r r_2_7_8_3_1_1 DEF65_E85A1B.log r_2_101_0_4_4_F9A855_E85A1B.log + +The first log file given is the "old log file" and the second file is "new log file". + +The result is the `diff.r` file containing the list of the tests with different results and the summary as follows: + + new-successes: 907 + new-failures: 25 + new-crashes: 4 + progressions: 119 + regressions: 94 + removed: 302 + unchanged: 3147 + total: 4598 + +Where, again, we see that the total number of tests was 4598. + +- **new-successes**: how many successful tests were newly performed (performed in the new log, but not performed in the old log) + +- **new-failures**: how many failing tests were newly performed + +- **new-crashes**: how many crashing tests were newly performed + +- **progressions**: how many tests have improved results + +- **regressions**: how many tests have worse results than before + +- **removed**: how many tests are not performed in the new log + +- **unchanged**: how many tests have the same result both in the old and in the new log + +The log difference is useful if for knowing the effect of an interpreter code update. In this case it is most convenient (but not required) to perform the same test suite in both the old as well as the new interpreter version. + +The difference can also be used to find the effect of test suite changes. In this case it is most convenient (but not required) to perform both the old and new test suite version using the same interpreter and compare the logs. diff --git a/tests/atronix/README.md b/tests/atronix/README.md new file mode 100644 index 0000000000..6029056f13 --- /dev/null +++ b/tests/atronix/README.md @@ -0,0 +1,12 @@ +These were some non-GUI-related tests which were pulled from Atronix's +r3 repository during the Ren/C refactoring. They lived underneath +the make/ directory. + +Ren/C includes the test repository as a submodule, hence these tests +can at least be found by a process which needs them. + +It is not currently clear at time of writing exactly which non-GUI +features the Ren/C library will choose to support. Erring on the side +of being usable by Atronix for their work is clearly favorable. + +@HostileFork, 19-Jun-2015 diff --git a/tests/atronix/libs.so b/tests/atronix/libs.so new file mode 100644 index 0000000000..614f0f9325 Binary files /dev/null and b/tests/atronix/libs.so differ diff --git a/tests/atronix/ms-drives.r b/tests/atronix/ms-drives.r new file mode 100644 index 0000000000..6cfd27f954 --- /dev/null +++ b/tests/atronix/ms-drives.r @@ -0,0 +1,16 @@ +REBOL [] + +msvcrt: make library! %msvcrt.dll +getdrives: make-routine msvcrt "_getdrives" compose/deep [ + return: [uint32] +] + +maps: getdrives +i: 0 +while [i < 26] [ + unless zero? maps and* shift 1 i [ + print unspaced [to char! (to integer! #"A") + i ":"] + ] + ++ i +] +close msvcrt diff --git a/tests/atronix/qsort.r b/tests/atronix/qsort.r new file mode 100644 index 0000000000..2eaa0bb2b1 --- /dev/null +++ b/tests/atronix/qsort.r @@ -0,0 +1,53 @@ +REBOL [] + +recycle/torture + + +f: func [ + a [integer!] "pointer to an integer" + b [integer!] "pointer to an integer" +][ + i: make struct! compose/deep [ + [raw-memory: (a)] + int32 i + ] + j: make struct! compose/deep [ + [raw-memory: (b)] + int32 i + ] + case [ + i/i = j/i [0] + i/i < j/i [-1] + i/i > j/i [1] + ] +] + +cb: make callback! [ + [ + a [pointer] + b [pointer] + return: [int32] + ] + :f +] + +libc: make library! %libc.so.6 + +x64?: 40 = fifth system/version +size_t: either x64? ['int64]['int32] +qsort: make routine! compose/deep [ + [ + base [pointer] + nmemb [(size_t)] + size [(size_t)] + comp [pointer] + ] + (libc) "qsort" +] + +array: make vector! [integer! 32 5 [10 8 2 9 5]] +print ["array:" mold array] +qsort array 5 4 (reflect cb 'addr) +print ["array:" mold array] ; [2 5 8 9 10] + +close libc diff --git a/tests/atronix/so-main.c b/tests/atronix/so-main.c new file mode 100644 index 0000000000..93f093bb24 --- /dev/null +++ b/tests/atronix/so-main.c @@ -0,0 +1,48 @@ +#include + +struct base { + int bi; +}; + +struct c { + struct base bs; + int i[2]; + int j; +}; + +struct d { + struct base bs; + float f; + double d; +}; + +void read_s (struct c a) +{ + printf("a.bs.bi: %d, a.i[0]: %d, a.i[1]: %d, a.j: %d\n", a.bs.bi, a.i[0], a.i[1], a.j); +} +void read_s10 (struct c a, + struct d a1, + struct c a2, + struct d a3, + struct c a4, + struct c a5, + struct c a6, + struct c a7, + struct c a8, + struct c a9) +{ + printf("a.bs.bi: %d, a.i[0]: %d, a.i[1]: %d, a.j: %d\n", a.bs.bi, a.i[0], a.i[1], a.j); + //printf("a1.bs.bi: %d, a1.i[0]: %d, a1.i[1]: %d, a1.j: %d\n", a1.bs.bi, a1.i[0], a1.i[1], a1.j); + printf("a1.bs.bi: %d, a1.f: %f, a1.d: %f\n", a1.bs.bi, a1.f, a1.d); + printf("a2.bs.bi: %d, a2.i[0]: %d, a2.i[1]: %d, a2.j: %d\n", a2.bs.bi, a2.i[0], a2.i[1], a2.j); + printf("a3.bs.bi: %d, a3.f: %f, a3.d: %f\n", a3.bs.bi, a3.f, a3.d); + printf("a9.bs.bi: %d, a9.i[0]: %d, a9.i[1]: %d, a9.j: %d\n", a9.bs.bi, a9.i[0], a9.i[1], a9.j); + a9.bs.bi = 12345; +} + +struct c return_s (int i) +{ + struct c cs; + cs.j = i; + return cs; +} diff --git a/tests/atronix/test-ffi.r b/tests/atronix/test-ffi.r new file mode 100644 index 0000000000..c7b8cd3025 --- /dev/null +++ b/tests/atronix/test-ffi.r @@ -0,0 +1,65 @@ +REBOL [] +recycle/torture +libc: make library! %libc.so.6 +;fopen: make routine! compose [library: (libc) name: "fopen" return: 'pointer [pointer pointer]] +;fclose: make routine! compose [library: (libc) name: "fclose" return: 'int32 [pointer]] +;fwrite: make routine! compose [library: (libc) name: "fwrite" return: 'int64 [pointer int64 int64 pointer]] +fopen: make routine! compose [[ path [pointer] mode [pointer] return: [pointer]] (libc) "fopen"] + +fclose: make routine! compose [[ fp [pointer] return: [int32] ] (libc) "fclose"] + +fwrite: make routine! compose [[ ptr [pointer] size [int64] nmemb [int64] stream [int64] return: [int64] ] (libc) "fwrite"] + +fread: make routine! compose [[ ptr [pointer] size [int64] nmemb [int64] stream [int64] return: [int64] ] (libc) "fread"] + +fseek: make routine! compose [[ fp [pointer] offset [int64] where [int32] return: [int32]] (libc) "fseek"] + +fp: fopen "/tmp/test.txt" "w+" +cnt: "hello world" +fwrite cnt length? cnt 1 fp + +buf: make struct! [s [uint8 [128]]] +fseek fp 0 0 +fread (reflect buf 'addr) length? buf 1 fp +print ["read:" to string! values-of buf "(" values-of buf ")"] + +a: "XXXXXXXXXXXXXX" +fseek fp 0 0 +fread a length? a 1 fp +print ["read: " a] +fclose fp + +;struct tm { +; int tm_sec; /* Seconds (0-60) */ +; int tm_min; /* Minutes (0-59) */ +; int tm_hour; /* Hours (0-23) */ +; int tm_mday; /* Day of the month (1-31) */ +; int tm_mon; /* Month (0-11) */ +; int tm_year; /* Year - 1900 */ +; int tm_wday; /* Day of the week (0-6, Sunday = 0) */ +; int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */ +; int tm_isdst; /* Daylight saving time */ +; }; + +tm: make struct! [ + tm_sec [int32] + tm_min [int32] + tm_hour [int32] ; /* Hours (0-23) */ + tm_mday [int32] ; /* Day of the month (1-31) */ + tm_mon [int32] ; /* Month (0-11) */ + tm_year [int32] ; /* Year - 1900 */ + tm_wday [int32] ; /* Day of the week (0-6, Sunday = 0) */ + tm_yday [int32] ; /* Day in the year (0-365, 1 Jan = 0) */ + tm_isdst [int32]; /* Daylight saving time */ +] +time_t: make struct! [ + t [int64] +] + +time: make routine! compose [[t [pointer] return: [int64]] (libc) "time"] +print ["time:" time (reflect time_t 'addr)] +localtime_r: make routine! compose [[t [pointer] tm [pointer] return: [int64]] (libc) "localtime_r"] + +print ["localtime:" localtime_r | reflect time_t 'addr | reflect tm 'addr] + +print ["tm:" mold tm] diff --git a/tests/atronix/test-libs.r b/tests/atronix/test-libs.r new file mode 100644 index 0000000000..5fdb402337 --- /dev/null +++ b/tests/atronix/test-libs.r @@ -0,0 +1,167 @@ +REBOL [] +recycle/torture +forever [ + libs: make library! %./libs.so + N_REPEAT: 10 + read-s10: make routine! compose [ + [ + a [ + struct! [ + struct! [int32 bi] bs + int32 [2] i + int32 j + ] + ] + a1 [ + struct! [ + struct! [int32 bi] bs + float f + double d + ] + ] + a2 [ + struct! [ + struct! [int32 bi] bs + int32 [2] i + int32 j + ] + ] + a3 [ + struct! [ + struct! [int32 bi] bs + float f + double d + ] + ] + a4 [ + struct! [ + struct! [int32 bi] bs + int32 [2] i + int32 j + ] + ] + a5 [ + struct! [ + struct! [int32 bi] bs + int32 [2] i + int32 j + ] + ] + a6 [ + struct! [ + struct! [int32 bi] bs + int32 [2] i + int32 j + ] + ] + a7 [ + struct! [ + struct! [int32 bi] bs + int32 [2] i + int32 j + ] + ] + a8 [ + struct! [ + struct! [int32 bi] bs + int32 [2] i + int32 j + ] + ] + a9 [ + struct! [ + struct! [int32 bi] bs + int32 [2] i + int32 j + ] + ] + ] + (libs) "read_s10" + ] + + a: make struct! [ + struct! [int32 bi] bs + int32 [2] i + int32 j + ] + a1: make struct! [ + struct! [int32 bi] bs + float f + double d + ] + a2: make struct! [ + struct! [int32 bi] bs + int32 [2] i + int32 j + ] + a3: make struct! [ + struct! [int32 bi] bs + float f + double d + ] + + a4: a5: a6: a7: a8: make struct! [ + struct! [int32 bi] bs + int32 [2] i + int32 j + ] + + a9: make struct! [ + struct! [int32 bi] bs + int32 [2] i + int32 j + ] + + i: 0 + while [i < N_REPEAT] [ + a/bs/bi: 100 + i + a/i/1: 200 + i + a/i/2: 300 + i + a/j: 400 + i + + a1/bs/bi: 110 + i + a1/f: 210 + i + a1/d: 310 + i + + a2/bs/bi: 120 + i + a2/i/1: 220 + i + a2/i/2: 320 + i + a2/j: 420 + i + + a3/bs/bi: 130 + i + a3/f: 230 + i + a3/d: 330 + i + + a9/bs/bi: 190 + i + a9/i/1: 290 + i + a9/i/2: 390 + i + a9/j: 490 + i + + read-s10 a a1 a2 a3 a4 a5 a6 a7 a8 a9 + ++ i + ] + + print ["a9:" mold a9] + + return-s: make routine! compose/deep [ + [ + i [int32] + return: [(a)] + ] + (libs) "return_s" + ] + + i: 0 + print ["i = " i] + s: return-s i + print ["s: " s] + i: 0 + while [i < N_REPEAT] [ + print ["i = " i] + s: return-s i + print ["s:" mold s] + ++ i + ] + print now + wait [2] +] diff --git a/tests/atronix/test-struct.r b/tests/atronix/test-struct.r new file mode 100644 index 0000000000..1e6620a93e --- /dev/null +++ b/tests/atronix/test-struct.r @@ -0,0 +1,9 @@ +REBOL[] +recycle/torture +a: make struct! [ + bs [struct! [bi [int32]]] + i [int32 [2]] + j [int32] +] +print ["a:" mold a] + diff --git a/tests/atronix/varargs.r b/tests/atronix/varargs.r new file mode 100644 index 0000000000..326835a846 --- /dev/null +++ b/tests/atronix/varargs.r @@ -0,0 +1,71 @@ +REBOL [] + +recycle/torture + +libc: switch fourth system/version [ + 3 [ + make library! %msvcrt.dll + ] + 4 [ + make library! %libc.so.6 + ] +] + +printf: make routine! [ + [ + "An example of wrapping variadic functions" + fmt [pointer] "fixed" + ... "variadic" + return: [int32] + ] + libc "printf" +] + +sprintf: make routine! [ + [ + "An example of wrapping variadic functions" + buf [pointer] "destination buffer, must be big enough" + fmt [pointer] "fixed" + ... "variadic" + return: [int32] + ] + libc "sprintf" +] + +i: 1000 +j: 0.0 +printf reduce [ + join-of "i: %d, %f" newline + i [int32] + j [float] +] + +printf compose [ + "hello %p%c" + ;10.0 + "ffi" [pointer] + ;"ffi" + (to integer! newline) [int8] +] + +printf compose [ + "hello %s%c" + "world" [pointer] + (to integer! newline) [int8] +] + +printf compose [ + "hello %s%c" + "ffi" [pointer] + (to integer! newline) [int8] +] + +h: make struct! [ + a [uint8 [128]] +] +len: sprintf reduce [ + addr-of h + join "hello %s" newline + "world" [pointer] +] +prin ["h:" copy/part to string! values-of h len] diff --git a/tests/bench.r3 b/tests/bench.r3 new file mode 100644 index 0000000000..d4a00f199d --- /dev/null +++ b/tests/bench.r3 @@ -0,0 +1,367 @@ +REBOL [ +Title: "Benchmark program" +Author: "Ladislav Mecir" +Date: 5-Oct-2010/11:36:11+2:00 +File: %bench.r +Purpose: "Runs several benchmarks" +] +tick-time: 0.01 +time-block: func [ +"Time a block." +block [block!] +precision [decimal!] "suggested value: 0.05 to 0.30" +/verbose +/local guess count start finish time result +] [ +if verbose [print ["Timing a block:" mold block]] +guess: 0 +count: 1 +while [ +start: now/precise +loop :count :block +finish: now/precise +time: to decimal! difference finish start +result: time / count +if verbose [ +prin "Iterations: " +prin count +prin ". Time/iteration: " +prin result +prin " seconds.^/" +] +any [ +result <= 0 (abs result - guess) / result + (tick-time / time * 4) > precision +] +] [ +guess: result +if error? try [count: count * 2] [return none] +] +result +] +sieve: func [size /local flags i prime series] [ +flags: make block! :size +change/dup :flags :true :size +while [not tail? :flags] [ +if first :flags [ +i: index? :flags +prime: :i + :i + 1 +series: skip :flags (:prime * :i) +while [not tail? :series] [ +change :series :false +series: skip :series :prime +] +] +flags: next :flags +] +head :flags +] +fourbang: func [ +/local +ten +one +temp +] [ +ten: 10.0 +one: 1.0 +temp: ten +temp: temp + one +temp: temp - one +temp: temp * ten +temp: temp / ten +temp: temp - one +temp: temp * ten +temp: temp + ten +temp: temp / ten +temp: temp + one +temp: temp - one +temp: temp * ten +temp: temp / ten +temp: temp - one +temp: temp * ten +temp: temp + ten +temp: temp / ten +temp: temp + one +temp: temp - one +temp: temp * ten +temp: temp / ten +temp: temp - one +temp: temp * ten +temp: temp + ten +temp: temp / ten +temp: temp + one +temp: temp - one +temp: temp * ten +temp: temp / ten +temp: temp - one +temp: temp * ten +temp: temp + ten +temp: temp / ten +] +gqf2: func [ +"Gaussian quadrature formula of the second order" +func [any-function!] "function to compute a definite integral of" +a [number!] "starting point of the integration interval" +b [number!] "end point of the integration interval" +n [integer!] "number of subintervals" +/local h m sum alpha beta sqrt3 halfh +] [ +h: (b - a) / n +halfh: h / 2 +m: 0 +sum: 0 +sqrt3: 1 / (square-root 3) +alpha: a + (halfh * (1 - sqrt3)) +beta: a + (halfh * (1 + sqrt3)) +while [:n > :m] [ +sum: :sum + (func :alpha) + (func :beta) +alpha: :alpha + :h +beta: :beta + :h +m: :m + 1 +] +sum: :halfh * :sum +] +msort: func [ +"Merge-sort a series in place." +a [series!] +compare [any-function!] +/local msort-do merge +] [ +msort-do: func [a l /local mid b] [ +either l <= 2 [ +unless any [ +l < 2 +compare first a second a +] [ +set/any 'b first a +change/only a second a +change/only next a get/any 'b +] +] [ +mid: to integer! l / 2 +msort-do a mid +msort-do skip a mid l - mid +merge a mid skip a mid l - mid +] +] +merge: func [ +{Uses auxiliary storage, at most half the size of the sorted series.} +a la b lb /local c +] [ +c: copy/part a la +loop-until [ +either (compare first b first c) [ +change/only a first b +b: next b +a: next a +zero? lb: lb - 1 +] [ +change/only a first c +c: next c +a: next a +empty? c +] +] +change a c +] +msort-do a length? a +a +] +set-words: func [ +"Get all set-words from a block" +block [block!] +/deep "also search in subblocks/parens" +/local elem words rule here +] [ +words: make block! length? block +rule: either deep [[ +any [ +set elem set-word! ( +insert tail words to word! :elem +) | here: [block! | paren!] :here into rule | skip +] +]] [[ +any [ +set elem set-word! ( +insert tail words to word! :elem +) | skip +] +]] +parse block rule +words +] +cfor: func [ +"General loop" [throw] +init [block!] +test [block!] +inc [block!] +body [block!] +] [ +use set-words init reduce [ +:do init +:while test head insert tail copy body inc +] +] +enum: function [ +"Enumerates a block" +from [integer!] +to [integer!] +] [result] [ +result: make block! to + 1 - from +cfor [i: from] [i <= to] [i: i + 1] [ +insert tail result i +] +result +] +locals?: func [ +"Get all locals from a spec block." +spec [block!] +/args "get only arguments" +/local locals item item-rule +] [ +locals: make block! 16 +item-rule: either args [[ +refinement! to end (item-rule: [end skip]) | +set item any-word! (insert tail locals to word! :item) | skip +]] [[ +set item any-word! (insert tail locals to word! :item) | skip +]] +parse spec [any item-rule] +locals +] +funcs: func [ +{Define a function with auto local and static variables.} [throw] +spec [block!] {Help string (opt) followed by arg words with opt type and string} +init [block!] "Set-words become static variables, shallow scan" +body [block!] "Set-words become local variables, deep scan" +/local svars lvars +] [ +spec: copy spec +init: copy/deep init +body: copy/deep body +svars: set-words init +lvars: set-words/deep body +unless empty? svars [ +use svars reduce [reduce [init body]] +] +unless empty? lvars: exclude exclude lvars locals? spec svars [ +insert any [find spec /local insert tail spec /local] lvars +] +do init +make function! reduce [spec body] +] +round-place: funcs [ +x [number!] +place [integer!] +/ceiling "round up" +/floor "round down" +] [] [ +scale: 10.0 ** place +x: either place <= 0 [ +if (abs x) + scale - (abs x) = 0 [return x] +scale: 10.0 ** negate place +x * scale +] [ +x / scale +] +r: x // 1.0 +s: case [ +floor [either r >= 0 [0.0] [-1.0]] +ceiling [either r > 0 [1.0] [0.0]] +r >= 0.0 [ +case [ +r > 0.5 [1.0] +r < 0.5 [0.0] +x // 2.0 = 0.5 [0.0] +true [1.0] +] +] +r < -0.5 [-1.0] +r > -0.5 [0.0] +x // 2.0 = -0.5 [0.0] +true [-1.0] +] +either place <= 0 [x + s - r / scale] [x + s - r * scale] +] +autoround: funcs [[catch] +x [number!] "number to round" +digits [integer!] "digits to keep" +/ceiling "round up" +/floor "round down" +] [] [ +if digits < 1 [throw make error! "digits needs to be >= 1"] +if zero? x [return x] +place: round/floor/to log-10 abs x 1 +if positive? 10.0 ** place - abs x [place: place - 1] +place: place - digits + 1 +case [ +floor [round-place/floor x place] +ceiling [round-place/ceiling x place] +true [round-place x place] +] +] +random/seed 1 +use [computer precision os size flags t count result sinerad icount serf compare mcount] [ +prin "Benchmark run " +prin now +prin ". Rebol " +print Rebol/version +prin "Computer: " +computer: input +prin "OS: " +os: input +precision: make decimal! ask "Precision: " +prin "Empty block: " +t: time-block [] precision +print rejoin [autoround 1 / t 3 "Hz"] +size: 8190 +prin rejoin ["Eratosthenes Sieve Prime (size: " size "): "] +t: time-block [flags: sieve size] precision +count: 0 +foreach flag flags [ +if flag [count: count + 1] +] +print rejoin [ +autoround 1 / t 3 +"Hz, result: " +count +" primes" +] +prin "Four-Banger test (+,-,*,/): " +t: time-block [result: fourbang] precision +print rejoin [ +autoround 1 / t 3 +"Hz, result: " +result +] +icount: 10000 +prin rejoin ["Integral (icount: " icount ") of sin(x) 0<=x<=pi/2: "] +sinerad: func [x] [sine (x * 180 / pi)] +t: time-block [result: gqf2 :sinerad 0 (pi / 2) icount] precision +print rejoin [ +autoround 1 / t 3 +"Hz, result: " +result +] +prin rejoin ["Integral (icount: " icount ") of exp(x) 0<=x<=1: "] +t: time-block [result: gqf2 :exp 0 1 icount] precision +print rejoin [ +autoround 1 / t 3 +"Hz, result: " +result +] +mcount: 500 +prin rejoin [ +"Merge Sort (" +mcount +" elements): " +] +compare: func [a b] [ +return a <= b +] +b: random enum 1 mcount +t: time-block [msort copy b :compare] precision +print rejoin [ +autoround 1 / t 3 +"Hz" +] +] \ No newline at end of file diff --git a/tests/call/call.test.reb b/tests/call/call.test.reb new file mode 100644 index 0000000000..95d24a5b3d --- /dev/null +++ b/tests/call/call.test.reb @@ -0,0 +1,21 @@ +; call/call.test.reb + +[ + ; small - works + data: copy {} + call/wait/output [%../make/r3 "--suppress" "*" %call/print.reb "100"] data + 100 == (length-of data) +] +[ + ; medium - fails test (just under 5000 bytes transferred) + data: copy {} + call/wait/output [%../make/r3 "--suppress" "*" %call/print.reb "9000"] data + 9000 == (length-of data) +] +[ + ; crashes :( + data: copy {} + call/wait/output [%../make/r3 "--suppress" "*" %call/print.reb "80000"] data + 80'000 == (length-of data) +] + diff --git a/tests/call/print.reb b/tests/call/print.reb new file mode 100644 index 0000000000..2c9c1c0686 --- /dev/null +++ b/tests/call/print.reb @@ -0,0 +1,5 @@ +Rebol [] + +repeat n to-integer first system/options/args [ + prin "." +] diff --git a/tests/catch-any.r b/tests/catch-any.r new file mode 100644 index 0000000000..69e45c5df1 --- /dev/null +++ b/tests/catch-any.r @@ -0,0 +1,83 @@ +Rebol [ + Title: "Catch-any" + File: %catch-any.r + Copyright: [2012 "Saphirion AG"] + License: { + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + } + Author: "Ladislav Mecir" + Purpose: "Catch any REBOL exception" +] + +make object! [ + do-block: function [ + ; helper for catching BREAK, CONTINUE, THROW or QUIT + return: [ any-value!] + block [block!] + exception [word!] + ] [ + ; TRY wraps CATCH/QUIT to circumvent bug#851 + try [ + catch/quit [ + catch [ + loop 1 [ + try [ + set exception 'return + print mold block ;-- !!! make this an option + result: do block + set exception blank + return :result + ] + ; an error was triggered + set exception 'error + return () + ] + ; BREAK or CONTINUE + set exception 'break + return () + ] + ; THROW + set exception 'throw + return () + ] + ; QUIT + set exception 'quit + return () + ] + ] + + set 'catch-any func [ + {catches any REBOL exception} + return: [ any-value!] + block [block!] {block to evaluate} + exception [word!] {used to return the exception type} + /local result + ][ + ; !!! outdated comment, RETURN/REDO no longer exists, look into what + ; this was supposed to be for. --HF + + ; catch RETURN, EXIT and RETURN/REDO + ; using the DO-BLOCK helper call + ; the helper call is enclosed in a block + ; not containing any additional values + ; to not give REDO any "excess arguments" + ; also, it is necessary to catch all above exceptions again + ; in case they are triggered by REDO + ; TRY wraps CATCH/QUIT to circumvent bug#851 + try [ + catch/quit [ + try [ + catch [ + loop 1 [result: do-block block exception] + ] + ] + ] + ] + if get exception [return ()] + return :result + ] +] diff --git a/tests/comparison/equalq.test.reb b/tests/comparison/equalq.test.reb new file mode 100644 index 0000000000..015d37230e --- /dev/null +++ b/tests/comparison/equalq.test.reb @@ -0,0 +1,640 @@ +; functions/comparison/equalq.r +; reflexivity test for native +[equal? :abs :abs] +[not equal? :abs :add] +[equal? :all :all] +[not equal? :all :any] +; reflexivity test for infix +[equal? :+ :+] +[not equal? :+ :-] +; reflexivity test for function! +; Uses func instead of make function! so the test is compatible. +[equal? a-value: func [] [] :a-value] +; No structural equivalence for function! +; Uses FUNC instead of make function! so the test is compatible. +[not equal? func [] [] func [] []] +; reflexivity test for closure! +; Uses CLOSURE to make the test compatible. +[equal? a-value: closure [] [] :a-value] +; No structural equivalence for closure! +; Uses CLOSURE to make the test compatible. +[not equal? closure [] [] closure [] []] +[equal? a-value: #{00} a-value] +; binary! +; Same contents +[equal? #{00} #{00}] +; Different contents +[not equal? #{00} #{01}] +; Offset + similar contents at reference +[equal? #{00} #[binary! [#{0000} 2]]] +; Offset + similar contents at reference +[equal? #{00} #[binary! [#{0100} 2]]] +[equal? equal? #{00} #[binary! [#{0100} 2]] equal? #[binary! [#{0100} 2]] #{00}] +; No binary! padding +[not equal? #{00} #{0000}] +[equal? equal? #{00} #{0000} equal? #{0000} #{00}] +; Empty binary! not blank +[not equal? #{} blank] +[equal? equal? #{} blank equal? blank #{}] +; case sensitivity +; bug#1459 +[not-equal? #{0141} #{0161}] +; email! vs. string! +; RAMBO #3518 +[ + a-value: to email! "" + equal? a-value to string! a-value +] +; email! vs. string! symmetry +[ + a-value: to email! "" + equal? equal? to string! a-value a-value equal? a-value to string! a-value +] +; file! vs. string! +; RAMBO #3518 +[ + a-value: %"" + equal? a-value to string! a-value +] +; file! vs. string! symmetry +[ + a-value: %"" + equal? equal? a-value to string! a-value equal? to string! a-value a-value +] +; image! same contents +[equal? a-value: #[image! [1x1 #{000000}]] a-value] +[equal? #[image! [1x1 #{000000}]] #[image! [1x1 #{000000}]]] +[equal? #[image! [1x1 #{}]] #[image! [1x1 #{000000}]]] +; image! different size +[not equal? #[image! [1x2 #{000000}]] #[image! [1x1 #{000000}]]] +; image! different size +[not equal? #[image! [2x1 #{000000}]] #[image! [1x1 #{000000}]]] +; image! different rgb +[not equal? #[image! [1x1 #{000001}]] #[image! [1x1 #{000000}]]] +; image! alpha not specified = ff +[equal? #[image! [1x1 #{000000} #{ff}]] #[image! [1x1 #{000000}]]] +; image! alpha different +[not equal? #[image! [1x1 #{000000} #{01}]] #[image! [1x1 #{000000} #{00}]]] +; Literal offset not supported in R2. +[equal? #[image! [1x1 #{000000} 2]] #[image! [1x1 #{000000} 2]]] +; Literal offset not supported in R2. +[not equal? #[image! [1x1 #{000000} 2]] #[image! [1x1 #{000000}]]] +[ + a-value: #[image! [1x1 #{000000}]] + not equal? a-value next a-value +] +; image! offset + structural equivalence +[equal? #[image! [0x0 #{}]] next #[image! [1x1 #{000000}]]] +; image! offset + structural equivalence +[equal? #[image! [1x0 #{}]] next #[image! [1x1 #{000000}]]] +; image! offset + structural equivalence +[equal? #[image! [0x1 #{}]] next #[image! [1x1 #{000000}]]] +#r2 +; image! offset + structural equivalence +[not equal? #[image! [0x0 #{}]] next #[image! [1x1 #{000000}]]] +#r2 +; image! offset + structural equivalence +[not equal? #[image! [1x0 #{}]] next #[image! [1x1 #{000000}]]] +#r2 +; image! offset + structural equivalence +[not equal? #[image! [0x1 #{}]] next #[image! [1x1 #{000000}]]] +; No implicit to binary! from image! +[not equal? #{00} #[image! [1x1 #{000000}]]] +; No implicit to binary! from image! +[not equal? #{00000000} #[image! [1x1 #{000000}]]] +; No implicit to binary! from image! +[not equal? #{0000000000} #[image! [1x1 #{000000}]]] +[equal? equal? #{00} #[image! [1x1 #{00}]] equal? #[image! [1x1 #{00}]] #{00}] +; No implicit to binary! from integer! +[not equal? #{00} to integer! #{00}] +[equal? equal? #{00} to integer! #{00} equal? to integer! #{00} #{00}] +; issue! vs. string! +; RAMBO #3518 +[not-equal? a-value: #a to string! a-value] +[ + a-value: #a + equal? equal? a-value to string! a-value equal? to string! a-value a-value +] +; No implicit to binary! from string! +[not equal? a-value: "" to binary! a-value] +[ + a-value: "" + equal? equal? a-value to binary! a-value equal? to binary! a-value a-value +] +; tag! vs. string! +; RAMBO #3518 +[equal? a-value: to tag! "" to string! a-value] +[ + a-value: to tag! "" + equal? equal? a-value to string! a-value equal? to string! a-value a-value +] +[equal? 0.0.0 0.0.0] +[not equal? 0.0.1 0.0.0] +; tuple! right-pads with 0 +[equal? 1.0.0 1.0.0.0.0.0.0] +; tuple! right-pads with 0 +[equal? 1.0.0.0.0.0.0 1.0.0] +; No implicit to binary! from tuple! +[ + a-value: 0.0.0.0 + not equal? to binary! a-value a-value +] +[ + a-value: 0.0.0.0 + equal? equal? to binary! a-value a-value equal? a-value to binary! a-value +] +[equal? #[bitset! #{00}] #[bitset! #{00}]] +; bitset! with no bits set does not equal empty bitset +; This is because of the COMPLEMENT problem: bug#1085. +[not equal? #[bitset! #{}] #[bitset! #{00}]] +; No implicit to binary! from bitset! +[not equal? #{00} #[bitset! #{00}]] +[equal? equal? #[bitset! #{00}] #{00} equal? #{00} #[bitset! #{00}]] +[equal? [] []] +[equal? a-value: [] a-value] +; Reflexivity for past-tail blocks +; Error in R2. +[ + a-value: tail [1] + clear head a-value + equal? a-value a-value +] +; Reflexivity for cyclic blocks +[ + a-value: copy [] + insert/only a-value a-value + equal? a-value a-value +] +; bug#1049 +; Comparison of cyclic blocks +; NOTE: The stackoverflow will likely trigger in valgrind an error such as: +; "Warning: client switching stacks? SP change: 0xffec17f68 --> 0xffefff860" +; " to suppress, use: --max-stackframe=4094200 or greater" +[ + a-value: copy [] + insert/only a-value a-value + b-value: copy [] + insert/only b-value b-value + error? try [equal? a-value b-value] + true +] +[not equal? [] blank] +[equal? equal? [] blank equal? blank []] +; block! vs. group! +[not equal? [] first [()]] +; block! vs. group! symmetry +[equal? equal? [] first [()] equal? first [()] []] +; block! vs. path! +[not equal? [a b] 'a/b] +; block! vs. path! symmetry +[ + a-value: 'a/b + b-value: [a b] + equal? equal? :a-value :b-value equal? :b-value :a-value +] +; block! vs. lit-path! +[not equal? [a b] first ['a/b]] +; block! vs. lit-path! symmetry +[ + a-value: first ['a/b] + b-value: [a b] + equal? equal? :a-value :b-value equal? :b-value :a-value +] +; block! vs. set-path! +[not equal? [a b] first [a/b:]] +; block! vs. set-path! symmetry +[ + a-value: first [a/b:] + b-value: [a b] + equal? equal? :a-value :b-value equal? :b-value :a-value +] +; block! vs. get-path! +[not equal? [a b] first [:a/b]] +; block! vs. get-path! symmetry +[ + a-value: first [:a/b] + b-value: [a b] + equal? equal? :a-value :b-value equal? :b-value :a-value +] +[equal? decimal! decimal!] +[not equal? decimal! integer!] +[equal? equal? decimal! integer! equal? integer! decimal!] +; datatype! vs. typeset! +[not equal? any-number! integer!] +; datatype! vs. typeset! symmetry +[equal? equal? any-number! integer! equal? integer! any-number!] +; datatype! vs. typeset! +[not equal? integer! make typeset! [integer!]] +; datatype! vs. typeset! +[not equal? integer! to typeset! [integer!]] +; datatype! vs. typeset! +; Supported by R2/Forward. +[not equal? integer! to-typeset [integer!]] +; typeset! (or pseudo-type in R2) +[equal? any-number! any-number!] +; typeset! (or pseudo-type in R2) +[not equal? any-number! any-series!] +[equal? make typeset! [integer!] make typeset! [integer!]] +[equal? to typeset! [integer!] to typeset! [integer!]] +; Supported by R2/Forward. +[equal? to-typeset [integer!] to-typeset [integer!]] +[equal? -1 -1] +[equal? 0 0] +[equal? 1 1] +[equal? 0.0 0.0] +[equal? 0.0 -0.0] +[equal? 1.0 1.0] +[equal? -1.0 -1.0] +#64bit +[equal? -9223372036854775808 -9223372036854775808] +#64bit +[equal? -9223372036854775807 -9223372036854775807] +#64bit +[equal? 9223372036854775807 9223372036854775807] +#64bit +[not equal? -9223372036854775808 -9223372036854775807] +#64bit +[not equal? -9223372036854775808 -1] +#64bit +[not equal? -9223372036854775808 0] +#64bit +[not equal? -9223372036854775808 1] +#64bit +[not equal? -9223372036854775808 9223372036854775806] +#64bit +[not equal? -9223372036854775807 -9223372036854775808] +#64bit +[not equal? -9223372036854775807 -1] +#64bit +[not equal? -9223372036854775807 0] +#64bit +[not equal? -9223372036854775807 1] +#64bit +[not equal? -9223372036854775807 9223372036854775806] +#64bit +[not equal? -9223372036854775807 9223372036854775807] +#64bit +[not equal? -1 -9223372036854775808] +#64bit +[not equal? -1 -9223372036854775807] +[not equal? -1 0] +[not equal? -1 1] +#64bit +[not equal? -1 9223372036854775806] +#64bit +[not equal? -1 9223372036854775807] +#64bit +[not equal? 0 -9223372036854775808] +#64bit +[not equal? 0 -9223372036854775807] +[not equal? 0 -1] +[not equal? 0 1] +#64bit +[not equal? 0 9223372036854775806] +#64bit +[not equal? 0 9223372036854775807] +#64bit +[not equal? 1 -9223372036854775808] +#64bit +[not equal? 1 -9223372036854775807] +[not equal? 1 -1] +[not equal? 1 0] +#64bit +[not equal? 1 9223372036854775806] +#64bit +[not equal? 1 9223372036854775807] +#64bit +[not equal? 9223372036854775806 -9223372036854775808] +#64bit +[not equal? 9223372036854775806 -9223372036854775807] +#64bit +[not equal? 9223372036854775806 -1] +#64bit +[not equal? 9223372036854775806 0] +#64bit +[not equal? 9223372036854775806 1] +#64bit +[not equal? 9223372036854775806 9223372036854775807] +#64bit +[not equal? 9223372036854775807 -9223372036854775808] +#64bit +[not equal? 9223372036854775807 -9223372036854775807] +#64bit +[not equal? 9223372036854775807 -1] +#64bit +[not equal? 9223372036854775807 0] +#64bit +[not equal? 9223372036854775807 1] +#64bit +[not equal? 9223372036854775807 9223372036854775806] +; decimal! approximate equality +[equal? 0.3 0.1 + 0.1 + 0.1] +; decimal! approximate equality symmetry +[equal? equal? 0.3 0.1 + 0.1 + 0.1 equal? 0.1 + 0.1 + 0.1 0.3] +[equal? 0.15 - 0.05 0.1] +[equal? equal? 0.15 - 0.05 0.1 equal? 0.1 0.15 - 0.05] +[equal? -0.5 cosine 120] +[equal? equal? -0.5 cosine 120 equal? cosine 120 -0.5] +[equal? 0.5 * square-root 2.0 sine 45] +[equal? equal? 0.5 * square-root 2.0 sine 45 equal? sine 45 0.5 * square-root 2.0] +[equal? 0.5 sine 30] +[equal? equal? 0.5 sine 30 equal? sine 30 0.5] +[equal? 0.5 cosine 60] +[equal? equal? 0.5 cosine 60 equal? cosine 60 0.5] +[equal? 0.5 * square-root 3.0 sine 60] +[equal? equal? 0.5 * square-root 3.0 sine 60 equal? sine 60 0.5 * square-root 3.0] +[equal? 0.5 * square-root 3.0 cosine 30] +[equal? equal? 0.5 * square-root 3.0 cosine 30 equal? cosine 30 0.5 * square-root 3.0] +[equal? square-root 3.0 tangent 60] +[equal? equal? square-root 3.0 tangent 60 equal? tangent 60 square-root 3.0] +[equal? (square-root 3.0) / 3.0 tangent 30] +[equal? equal? (square-root 3.0) / 3.0 tangent 30 equal? tangent 30 (square-root 3.0) / 3.0] +[equal? 1.0 tangent 45] +[equal? equal? 1.0 tangent 45 equal? tangent 45 1.0] +[ + num: square-root 2.0 + equal? 2.0 num * num +] +[ + num: square-root 2.0 + equal? equal? 2.0 num * num equal? num * num 2.0 +] +[ + num: square-root 3.0 + equal? 3.0 num * num +] +[ + num: square-root 3.0 + equal? equal? 3.0 num * num equal? num * num 3.0 +] +; integer! vs. decimal! +[equal? 0 0.0] +; integer! vs. money! +[equal? 0 $0] +; integer! vs. percent! +[equal? 0 0%] +; decimal! vs. money! +[equal? 0.0 $0] +; decimal! vs. percent! +[equal? 0.0 0%] +; money! vs. percent! +[equal? $0 0%] +; integer! vs. decimal! symmetry +[equal? equal? 1 1.0 equal? 1.0 1] +; integer! vs. money! symmetry +[equal? equal? 1 $1 equal? $1 1] +; integer! vs. percent! symmetry +[equal? equal? 1 100% equal? 100% 1] +; decimal! vs. money! symmetry +[equal? equal? 1.0 $1 equal? $1 1.0] +; decimal! vs. percent! symmetry +[equal? equal? 1.0 100% equal? 100% 1.0] +; money! vs. percent! symmetry +[equal? equal? $1 100% equal? 100% $1] +; percent! approximate equality +[equal? 10% + 10% + 10% 30%] +; percent! approximate equality symmetry +[equal? equal? 10% + 10% + 10% 30% equal? 30% 10% + 10% + 10%] +[equal? 2-Jul-2009 2-Jul-2009] +; date! doesn't ignore time portion +[not equal? 2-Jul-2009 2-Jul-2009/22:20] +[equal? equal? 2-Jul-2009 2-Jul-2009/22:20 equal? 2-Jul-2009/22:20 2-Jul-2009] +; date! missing time and zone = 00:00:00+00:00 +[equal? 2-Jul-2009 2-Jul-2009/00:00:00+00:00] +[equal? equal? 2-Jul-2009 2-Jul-2009/00:00 equal? 2-Jul-2009/00:00 2-Jul-2009] +; Timezone math in date! +[equal? 2-Jul-2009/22:20 2-Jul-2009/20:20-2:00] +[equal? 00:00 00:00] +; time! missing components are 0 +[equal? 0:0 00:00:00.0000000000] +[equal? equal? 0:0 00:00:00.0000000000 equal? 00:00:00.0000000000 0:0] +; time! vs. integer! +; bug#1103 +[not equal? 0:00 0] +; integer! vs. time! +; bug#1103 +[not equal? 0 00:00] +[equal? #"a" #"a"] +; char! vs. integer! +; No implicit to char! from integer! in R3. +[not equal? #"a" 97] +; char! vs. integer! symmetry +[equal? equal? #"a" 97 equal? 97 #"a"] +; char! vs. decimal! +; No implicit to char! from decimal! in R3. +[not equal? #"a" 97.0] +; char! vs. decimal! symmetry +[equal? equal? #"a" 97.0 equal? 97.0 #"a"] +; char! case +[equal? #"a" #"A"] +; string! case +[equal? "a" "A"] +; issue! case +[equal? #a #A] +; tag! case +[equal? ] +; url! case +[equal? http://a.com httP://A.coM] +; email! case +[equal? a@a.com A@A.Com] +[equal? 'a 'a] +[equal? 'a 'A] +[equal? equal? 'a 'A equal? 'A 'a] +; word binding +[equal? 'a use [a] ['a]] +; word binding symmetry +[equal? equal? 'a use [a] ['a] equal? use [a] ['a] 'a] +; word! vs. get-word! +[equal? 'a first [:a]] +; word! vs. get-word! symmetry +[equal? equal? 'a first [:a] equal? first [:a] 'a] +; {word! vs. lit-word! +[equal? 'a first ['a]] +; word! vs. lit-word! symmetry +[equal? equal? 'a first ['a] equal? first ['a] 'a] +; word! vs. refinement! +[equal? 'a /a] +; word! vs. refinement! symmetry +[equal? equal? 'a /a equal? /a 'a] +; word! vs. set-word! +[equal? 'a first [a:]] +; word! vs. set-word! symmetry +[equal? equal? 'a first [a:] equal? first [a:] 'a] +; get-word! reflexivity +[equal? first [:a] first [:a]] +; get-word! vs. lit-word! +[equal? first [:a] first ['a]] +; get-word! vs. lit-word! symmetry +[equal? equal? first [:a] first ['a] equal? first ['a] first [:a]] +; get-word! vs. refinement! +[equal? first [:a] /a] +; get-word! vs. refinement! symmetry +[equal? equal? first [:a] /a equal? /a first [:a]] +; get-word! vs. set-word! +[equal? first [:a] first [a:]] +; get-word! vs. set-word! symmetry +[equal? equal? first [:a] first [a:] equal? first [a:] first [:a]] +; lit-word! reflexivity +[equal? first ['a] first ['a]] +; lit-word! vs. refinement! +[equal? first ['a] /a] +; lit-word! vs. refinement! symmetry +[equal? equal? first ['a] /a equal? /a first ['a]] +; lit-word! vs. set-word! +[equal? first ['a] first [a:]] +; lit-word! vs. set-word! symmetry +[equal? equal? first ['a] first [a:] equal? first [a:] first ['a]] +; refinement! reflexivity +[equal? /a /a] +; refinement! vs. set-word! +[equal? /a first [a:]] +; refinement! vs. set-word! symmetry +[equal? equal? /a first [a:] equal? first [a:] /a] +; set-word! reflexivity +[equal? first [a:] first [a:]] +[equal? true true] +[equal? false false] +[not equal? true false] +[not equal? false true] +; object! reflexivity +[equal? a-value: make object! [a: 1] a-value] +; object! simple structural equivalence +[equal? make object! [a: 1] make object! [a: 1]] +; object! different values +[not equal? make object! [a: 1] make object! [a: 2]] +; object! different words +[not equal? make object! [a: 1] make object! [b: 1]] +[not equal? make object! [a: 1] make object! []] +; object! complex structural equivalence +[ + a-value: has/only [ + a: 1 b: 1.0 c: $1 d: 1% + e: [a 'a :a a: /a #"a" #{00}] + f: ["a" #a http://a a@a.com ] + g: :a/b/(c: 'd/e/f)/(b/d: [:f/g h/i]) + ] + b-value: has/only [ + a: 1 b: 1.0 c: $1 d: 1% + e: [a 'a :a a: /a #"a" #{00}] + f: ["a" #a http://a a@a.com ] + g: :a/b/(c: 'd/e/f)/(b/d: [:f/g h/i]) + ] + equal? a-value b-value +] +; object! complex structural equivalence +; Slight differences. +; bug#1133 +; object! structural equivalence verified +; Structural equality requires equality of the object's fields. +[ + a-value: has/only [ + a: 1 b: 1.0 c: $1 d: 1% + e: [a 'a :a a: /a #"a" #{00}] + f: ["a" #a http://a a@a.com ] + g: :a/b/(c: 'd/e/f)/(b/d: [:f/g h/i]) + ] + b-value: has/only [ + a: 1 b: 1.0 c: $1 d: 1% + e: [a 'a :a a: /a #"a" #{00}] + f: ["a" #a http://a a@a.com ] + g: :a/b/(c: 'd/e/f)/(b/d: [:f/g h/i]) + ] + test: :equal? + equal? + test a-value b-value + true? for-each [w v] a-value [ + unless test :v select b-value w [break] + ] +] +; object! structural equivalence verified +; Structural equality requires equality of the object's fields. +[ + a-value: has/only [ + a: 1 b: 1.0 c: $1 d: 1% + e: [a 'a :a a: /a #"a" #{00}] + f: ["a" #a http://a a@a.com ] + g: :a/b/(c: 'd/e/f)/(b/d: [:f/g h/i]) + ] + b-value: has/only [ + a: 1.0 b: $1 c: 100% d: 0.01 + e: [/a a 'a :a a: #"A" #[binary! [#{0000} 2]]] + f: [#a http://A a@A.com "A"] + g: :a/b/(c: 'd/e/f)/(b/d: [:f/g h/i]) + ] + test: :equal? + equal? + test a-value b-value + true? for-each [w v] a-value [ + unless test :v select b-value w [break] + ] +] +; unset! comparison fails +[equal? () ()] +; basic comparison with unset first argument fails +[not-equal? () blank] +; basic comparison with unset second argument fails +[not-equal? blank ()] +; unset! symmetry +[equal? equal? blank () equal? () blank] +; unset! symmetry +; Fails on R2 because there is no structural comparison of objects. +; basic comparison with unset first argument succeeds with = op +; Code in R3 mezzanines depends on this. +[not (() = blank)] +; basic comparison with unset first argument succeeds with != op +; Code in R3 mezzanines depends on this. +[() <> blank] +; basic comparison with unset second argument fails with = op +[not blank = ()] +; basic comparison with unset second argument fails with != op +[blank != ()] +[() = ()] +[not () != ()] +; unset! symmetry with = +[equal? blank = () () = blank] +; error! reflexivity +; Evaluates (try [1 / 0]) to get error! value. +[ + a-value: blank + set/opt 'a-value (try [1 / 0]) + equal? a-value a-value +] +; error! structural equivalence +; Evaluates (try [1 / 0]) to get error! value. +[equal? (try [1 / 0]) (try [1 / 0])] +; error! structural equivalence +[equal? (make error! "hello") (make error! "hello")] +; error! difference in code +[not equal? (try [1 / 0]) (make error! "hello")] +; error! difference in data +[not equal? (make error! "hello") (make error! "there")] +; error! basic comparison +[not equal? (try [1 / 0]) blank] +; error! basic comparison +[not equal? blank (try [1 / 0])] +; error! basic comparison symmetry +[equal? equal? (try [1 / 0]) blank equal? blank (try [1 / 0])] +; error! basic comparison with = op +[not ((try [1 / 0]) = blank)] +; error! basic comparison with != op +[(try [1 / 0]) != blank] +; error! basic comparison with = op +[not (blank = (try [1 / 0]))] +; error! basic comparison with != op +[blank != (try [1 / 0])] +; error! symmetry with = op +[equal? not ((try [1 / 0]) = blank) not (blank = (try [1 / 0]))] +; error! symmetry with != op +[equal? (try [1 / 0]) != blank blank != (try [1 / 0])] +; port! reflexivity +; Error in R2 (could be fixed). +[equal? p: make port! http:// p] +; No structural equivalence for port! +; Error in R2 (could be fixed). +[not equal? make port! http:// make port! http://] +; bug#859 +[ + a: copy quote () + insert/only a a + error? try [do a] +] diff --git a/tests/comparison/lesserq.test.reb b/tests/comparison/lesserq.test.reb new file mode 100644 index 0000000000..e09dd54b64 --- /dev/null +++ b/tests/comparison/lesserq.test.reb @@ -0,0 +1,196 @@ +; functions/comparison/lesserq.r +; integer -9223372036854775808 < x tests +#64bit +[not -9223372036854775808 < -9223372036854775808] +#64bit +[-9223372036854775808 < -9223372036854775807] +#64bit +[-9223372036854775808 < -2147483648] +#64bit +[-9223372036854775808 < -1] +#64bit +[-9223372036854775808 < 0] +; bug#2054 +#64bit +[-9223372036854775808 < 1] +#64bit +[-9223372036854775808 < 9223372036854775806] +#64bit +[-9223372036854775808 < 9223372036854775807] +; integer -9223372036854775807 < x tests +#64bit +[not -9223372036854775807 < -9223372036854775808] +#64bit +[not -9223372036854775807 < -9223372036854775807] +#64bit +[-9223372036854775807 < -1] +#64bit +[-9223372036854775807 < 0] +#64bit +[-9223372036854775807 < 1] +#64bit +[-9223372036854775807 < 9223372036854775806] +#64bit +[-9223372036854775807 < 9223372036854775807] +; integer -2147483648 < x tests +[not -2147483648 < -2147483648] +[-2147483648 < -1] +[-2147483648 < 0] +[-2147483648 < 1] +[-2147483648 < 2147483647] +; integer -1 < x tests +#64bit +[not -1 < -9223372036854775808] +#64bit +[not -1 < -9223372036854775807] +[not -1 < -1] +[-1 < 0] +[-1 < 1] +#64bit +[-1 < 9223372036854775806] +#64bit +[-1 < 9223372036854775807] +; integer 0 < x tests +#64bit +[not 0 < -9223372036854775808] +#64bit +[not 0 < -9223372036854775807] +[not 0 < -1] +[not 0 < 0] +[0 < 1] +#64bit +[0 < 9223372036854775806] +#64bit +[0 < 9223372036854775807] +; integer 1 < x tests +#64bit +[not 1 < -9223372036854775808] +#64bit +[not 1 < -9223372036854775807] +[not 1 < -1] +[not 1 < 0] +[not 1 < 1] +#64bit +[1 < 9223372036854775806] +#64bit +[1 < 9223372036854775807] +; integer 2147483647 < x tests +[not 2147483647 < -2147483648] +[not 2147483647 < -1] +[not 2147483647 < 0] +[not 2147483647 < 1] +[not 2147483647 < 2147483647] +; integer 9223372036854775806 < x tests +#64bit +[not 9223372036854775806 < -9223372036854775808] +#64bit +[not 9223372036854775806 < -9223372036854775807] +#64bit +[not 9223372036854775806 < -1] +#64bit +[not 9223372036854775806 < 0] +#64bit +[not 9223372036854775806 < 1] +#64bit +[not 9223372036854775806 < 9223372036854775806] +#64bit +[9223372036854775806 < 9223372036854775807] +; integer 9223372036854775807 < x tests +#64bit +[not 9223372036854775807 < -9223372036854775808] +#64bit +[not 9223372036854775807 < -9223372036854775807] +#64bit +[not 9223372036854775807 < -1] +#64bit +[not 9223372036854775807 < 0] +#64bit +[not 9223372036854775807 < 1] +#64bit +[not 9223372036854775807 < 9223372036854775806] +#64bit +[not 9223372036854775807 < 9223372036854775807] +; decimal < integer +[not 1.1 < 1] +[1.0 < 2147483647] +[not -1.0 < -2147483648] +; integer < decimal +[1 < 1.1] +[not 2147483647 < 1.0] +[-2147483648 < -1.0] +; -1.7976931348623157e308 < decimal +[not -1.7976931348623157e308 < -1.7976931348623157e308] +[-1.7976931348623157e308 < -1.0] +[-1.7976931348623157e308 < -4.94065645841247E-324] +[-1.7976931348623157e308 < 0.0] +[-1.7976931348623157e308 < 4.94065645841247E-324] +[-1.7976931348623157e308 < 1.0] +[-1.7976931348623157e308 < 1.7976931348623157e308] +; -1.0 < decimal +[not -1.0 < -1.7976931348623157e308] +[not -1.0 < -1.0] +[-1.0 < -4.94065645841247E-324] +[-1.0 < 0.0] +[-1.0 < 4.94065645841247E-324] +[-1.0 < 1.0] +[-1.0 < 1.7976931348623157e308] +; -4.94065645841247E-324 < decimal +[not -4.94065645841247E-324 < -1.7976931348623157e308] +[not -4.94065645841247E-324 < -1.0] +[not -4.94065645841247E-324 < -4.94065645841247E-324] +[-4.94065645841247E-324 < 0.0] +[-4.94065645841247E-324 < 4.94065645841247E-324] +[-4.94065645841247E-324 < 1.0] +[-4.94065645841247E-324 < 1.7976931348623157e308] +; 0.0 < decimal +[not 0.0 < -1.7976931348623157e308] +[not 0.0 < -1.0] +[not 0.0 < -4.94065645841247E-324] +[not 0.0 < 0.0] +[0.0 < 4.94065645841247E-324] +[0.0 < 1.0] +[0.0 < 1.7976931348623157e308] +; 4.94065645841247E-324 < decimal +[not 4.94065645841247E-324 < -1.7976931348623157e308] +[not 4.94065645841247E-324 < -1.0] +[not 4.94065645841247E-324 < -4.94065645841247E-324] +[not 4.94065645841247E-324 < 0.0] +[not 4.94065645841247E-324 < 4.94065645841247E-324] +[4.94065645841247E-324 < 1.0] +[4.94065645841247E-324 < 1.7976931348623157e308] +; 1.0 < decimal +[not 1.0 < -1.7976931348623157e308] +[not 1.0 < -1.0] +[not 1.0 < -4.94065645841247E-324] +[not 1.0 < 0.0] +[not 1.0 < 4.94065645841247E-324] +[not 1.0 < 1.0] +[1.0 < 1.7976931348623157e308] +; 1.7976931348623157e308 < decimal +[not 1.7976931348623157e308 < -1.7976931348623157e308] +[not 1.7976931348623157e308 < -1.0] +[not 1.7976931348623157e308 < -4.94065645841247E-324] +[not 1.7976931348623157e308 < 0.0] +[not 1.7976931348623157e308 < 4.94065645841247E-324] +[not 1.7976931348623157e308 < 1.0] +[not 1.7976931348623157e308 < 1.7976931348623157e308] +; char +[not #"^(00)" < #"^(00)"] +[#"^(00)" < #"^(01)"] +[#"^(00)" < #"^(ff)"] +[not #"^(01)" < #"^(00)"] +[not #"^(01)" < #"^(01)"] +[#"^(01)" < #"^(ff)"] +[not #"^(ff)" < #"^(00)"] +[not #"^(ff)" < #"^(01)"] +[not #"^(ff)" < #"^(ff)"] +; tuple +[not 0.0.0 < 0.0.0] +[0.0.0 < 0.0.1] +[0.0.0 < 0.0.255] +[not 0.0.1 < 0.0.0] +[not 0.0.1 < 0.0.1] +[0.0.1 < 0.0.255] +[not 0.0.255 < 0.0.0] +[not 0.0.255 < 0.0.1] +[not 0.0.255 < 0.0.255] diff --git a/tests/comparison/maximum-of.test.reb b/tests/comparison/maximum-of.test.reb new file mode 100644 index 0000000000..16380c2e94 --- /dev/null +++ b/tests/comparison/maximum-of.test.reb @@ -0,0 +1,3 @@ +; functions/comparison/maximum-of.r +; bug#8 +[3 = first maximum-of [1 2 3]] diff --git a/tests/comparison/sameq.test.reb b/tests/comparison/sameq.test.reb new file mode 100644 index 0000000000..a228109582 --- /dev/null +++ b/tests/comparison/sameq.test.reb @@ -0,0 +1,421 @@ +; functions/comparison/sameq.r +; reflexivity test for action +[same? :abs :abs] +; reflexivity test for native +[same? :all :all] +; reflexivity test for infix +[same? :+ :+] +; reflexivity test for function! +[ + a-value: func [] [] + same? :a-value :a-value +] +; no structural equality for function! +[not same? func [] [] func [] []] +; reflexivity test for closure! +[ + a-value: closure [] [] + same? :a-value :a-value +] +; no structural equality for closure! +[not same? closure [] [] closure [] []] +; binary! +[not same? #{00} #{00}] +; binary versus bitset +[not same? #{00} #[bitset! #{00}]] +; symmetry +[equal? same? #[bitset! #{00}] #{00} same? #{00} #[bitset! #{00}]] +; email versus string +[ + a-value: to email! "" + not same? a-value to string! a-value +] +; symmetry +[ + a-value: to email! "" + equal? same? to string! a-value a-value same? a-value to string! a-value +] +[ + a-value: %"" + not same? a-value to string! a-value +] +; symmetry +[ + a-value: %"" + equal? same? a-value to string! a-value same? to string! a-value a-value +] +[not same? #{00} #[image! [1x1 #{00}]]] +; symmetry +[equal? same? #{00} #[image! [1x1 #{00}]] same? #[image! [1x1 #{00}]] #{00}] +[not same? #{00} to integer! #{00}] +; symmetry +[equal? same? #{00} to integer! #{00} same? to integer! #{00} #{00}] +[ + a-value: #a + not same? a-value to string! a-value +] +; symmetry +[ + a-value: #a + equal? same? a-value to string! a-value same? to string! a-value a-value +] +[not same? #{} blank] +; symmetry +[equal? same? #{} blank same? blank #{}] +[ + a-value: "" + not same? a-value to binary! a-value +] +; symmetry +[ + a-value: "" + equal? same? a-value to binary! a-value same? to binary! a-value a-value +] +[ + a-value: to tag! "" + not same? a-value to string! a-value +] +; symmetry +[ + a-value: to tag! "" + equal? same? a-value to string! a-value same? to string! a-value a-value +] +[ + a-value: 0.0.0.0 + not same? to binary! a-value a-value +] +; symmetry +[ + a-value: 0.0.0.0 + equal? same? to binary! a-value a-value same? a-value to binary! a-value +] +[not same? #[bitset! #{00}] #[bitset! #{00}]] +[not same? #[bitset! #{}] #[bitset! #{00}]] +; block! +[not same? [] []] +; reflexivity +[ + a-value: [] + same? a-value a-value +] +; reflexivity for past-tail blocks +[ + a-value: tail [1] + clear head a-value + same? a-value a-value +] +; reflexivity for cyclic blocks +[ + a-value: copy [] + insert/only a-value a-value + same? a-value a-value +] +; comparison of cyclic blocks +[ + a-value: copy [] + insert/only a-value a-value + b-value: copy [] + insert/only b-value b-value + not same? a-value b-value +] +; bug#1068 +; bug#1066 +[ + a-value: first ['a/b] + parse :a-value [b-value:] + same? :a-value :b-value +] +; symmetry +[ + a-value: first ['a/b] + parse :a-value [b-value:] + equal? same? :a-value :b-value same? :b-value :a-value +] +[not same? [] blank] +; symmetry +[equal? same? [] blank same? blank []] +; bug#1068 +; bug#1066 +[ + a-value: first [()] + parse a-value [b-value:] + same? a-value b-value +] +; symmetry +[ + a-value: first [()] + parse a-value [b-value:] + equal? same? a-value b-value same? b-value a-value +] +; bug#1068 +; bug#1066 +[ + a-value: 'a/b + parse a-value [b-value:] + same? :a-value :b-value +] +; symmetry +[ + a-value: 'a/b + parse a-value [b-value:] + equal? same? :a-value :b-value same? :b-value :a-value +] +; bug#1068 +; bug#1066 +[ + a-value: first [a/b:] + parse :a-value [b-value:] + same? :a-value :b-value +] +; symmetry +[ + a-value: first [a/b:] + parse :a-value [b-value:] + equal? same? :a-value :b-value same? :b-value :a-value +] +[not same? any-number! integer!] +; symmetry +[equal? same? any-number! integer! same? integer! any-number!] +; reflexivity +[same? -1 -1] +; reflexivity +[same? 0 0] +; reflexivity +[same? 1 1] +; reflexivity +[same? 0.0 0.0] +[not same? 0.0 -0.0] +; reflexivity +[same? 1.0 1.0] +; reflexivity +[same? -1.0 -1.0] +; reflexivity +#64bit +[same? -9223372036854775808 -9223372036854775808] +; reflexivity +#64bit +[same? -9223372036854775807 -9223372036854775807] +; reflexivity +#64bit +[same? 9223372036854775807 9223372036854775807] +; -9223372036854775808 not same? +#64bit +[not same? -9223372036854775808 -9223372036854775807] +#64bit +[not same? -9223372036854775808 -1] +#64bit +[not same? -9223372036854775808 0] +#64bit +[not same? -9223372036854775808 1] +#64bit +[not same? -9223372036854775808 9223372036854775806] +#64bit +[not same? -9223372036854775808 9223372036854775807] +; -9223372036854775807 not same? +#64bit +[not same? -9223372036854775807 -9223372036854775808] +#64bit +[not same? -9223372036854775807 -1] +#64bit +[not same? -9223372036854775807 0] +#64bit +[not same? -9223372036854775807 1] +#64bit +[not same? -9223372036854775807 9223372036854775806] +#64bit +[not same? -9223372036854775807 9223372036854775807] +; -1 not same? +#64bit +[not same? -1 -9223372036854775808] +#64bit +[not same? -1 -9223372036854775807] +[not same? -1 0] +[not same? -1 1] +#64bit +[not same? -1 9223372036854775806] +#64bit +[not same? -1 9223372036854775807] +; 0 not same? +#64bit +[not same? 0 -9223372036854775808] +#64bit +[not same? 0 -9223372036854775807] +[not same? 0 -1] +[not same? 0 1] +#64bit +[not same? 0 9223372036854775806] +#64bit +[not same? 0 9223372036854775807] +; 1 not same? +#64bit +[not same? 1 -9223372036854775808] +#64bit +[not same? 1 -9223372036854775807] +[not same? 1 -1] +[not same? 1 0] +#64bit +[not same? 1 9223372036854775806] +#64bit +[not same? 1 9223372036854775807] +; 9223372036854775806 not same? +#64bit +[not same? 9223372036854775806 -9223372036854775808] +#64bit +[not same? 9223372036854775806 -9223372036854775807] +#64bit +[not same? 9223372036854775806 -1] +#64bit +[not same? 9223372036854775806 0] +#64bit +[not same? 9223372036854775806 1] +#64bit +[not same? 9223372036854775806 9223372036854775807] +; 9223372036854775807 not same? +#64bit +[not same? 9223372036854775807 -9223372036854775808] +#64bit +[not same? 9223372036854775807 -9223372036854775807] +#64bit +[not same? 9223372036854775807 -1] +#64bit +[not same? 9223372036854775807 0] +#64bit +[not same? 9223372036854775807 1] +#64bit +[not same? 9223372036854775807 9223372036854775806] +; "decimal tolerance" +[not same? to decimal! #{3FD3333333333333} to decimal! #{3FD3333333333334}] +; symmetry +[ + equal? same? to decimal! #{3FD3333333333333} to decimal! #{3FD3333333333334} + same? to decimal! #{3FD3333333333334} to decimal! #{3FD3333333333333} +] +[not same? to decimal! #{3FB9999999999999} to decimal! #{3FB999999999999A}] +; symmetry +[ + equal? same? to decimal! #{3FB9999999999999} to decimal! #{3FB999999999999A} + same? to decimal! #{3FB999999999999A} to decimal! #{3FB9999999999999} +] +; datatype differences +[not same? 0 0.0] +; datatype differences +[not same? 0 $0] +; datatype differences +[not same? 0 0%] +; datatype differences +[not same? 0.0 $0] +; datatype differences +[not same? 0.0 0%] +; datatype differences +[not same? $0 0%] +; symmetry +[equal? same? 1 1.0 same? 1.0 1] +; symmetry +[equal? same? 1 $1 same? $1 1] +; symmetry +[equal? same? 1 100% same? 100% 1] +; symmetry +[equal? same? 1.0 $1 same? $1 1.0] +; symmetry +[equal? same? 1.0 100% same? 100% 1.0] +; symmetry +[equal? same? $1 100% same? 100% $1] +; approximate equality +[not same? 10% + 10% + 10% 30%] +; symmetry +[equal? same? 10% + 10% + 10% 30% same? 30% 10% + 10% + 10%] +; date!; approximate equality +[not same? 2-Jul-2009 2-Jul-2009/22:20] +; symmetry +[equal? same? 2-Jul-2009 2-Jul-2009/22:20 same? 2-Jul-2009/22:20 2-Jul-2009] +; missing time is considered a difference +[not same? 2-Jul-2009 2-Jul-2009/00:00:00+00:00] +; symmetry +[equal? not same? 2-Jul-2009 2-Jul-2009/00:00 not same? 2-Jul-2009/00:00 2-Jul-2009] +; no timezone math +[not same? 2-Jul-2009/22:20 2-Jul-2009/20:20-2:00] +; time! +[same? 00:00 00:00] +; missing components are 0 +[same? 00:00 00:00:00] +; no timezone math +[not same? 22:20 20:20] +; char!; symmetry +[equal? same? #"a" 97 same? 97 #"a"] +; symmetry +[equal? same? #"a" 97.0 same? 97.0 #"a"] +; case +[not same? #"a" #"A"] +; case +[not same? "a" "A"] +; words; reflexivity +[same? 'a 'a] +; aliases +[not same? 'a 'A] +; symmetry +[equal? same? 'a 'A same? 'A 'a] +; binding +[not same? 'a use [a] ['a]] +; symmetry +[equal? same? 'a use [a] ['a] same? use [a] ['a] 'a] +; different word types +[not same? 'a first [:a]] +; symmetry +[equal? same? 'a first [:a] same? first [:a] 'a] +; different word types +[not same? 'a first ['a]] +; symmetry +[equal? same? 'a first ['a] same? first ['a] 'a] +; different word types +[not same? 'a /a] +; symmetry +[equal? same? 'a /a same? /a 'a] +; different word types +[not same? 'a first [a:]] +; symmetry +[equal? same? 'a first [a:] same? first [a:] 'a] +; reflexivity +[same? first [:a] first [:a]] +; different word types +[not same? first [:a] first ['a]] +; symmetry +[equal? same? first [:a] first ['a] same? first ['a] first [:a]] +; different word types +[not same? first [:a] /a] +; symmetry +[equal? same? first [:a] /a same? /a first [:a]] +; different word types +[not same? first [:a] first [a:]] +; symmetry +[equal? same? first [:a] first [a:] same? first [a:] first [:a]] +; reflexivity +[same? first ['a] first ['a]] +; different word types +[not same? first ['a] /a] +; symmetry +[equal? same? first ['a] /a same? /a first ['a]] +; different word types +[not same? first ['a] first [a:]] +; symmetry +[equal? same? first ['a] first [a:] same? first [a:] first ['a]] +; reflexivity +[same? /a /a] +; different word types +[not same? /a first [a:]] +; symmetry +[equal? same? /a first [a:] same? first [a:] /a] +; reflexivity +[same? first [a:] first [a:]] +; logic! values +[same? true true] +[same? false false] +[not same? true false] +[not same? false true] +; port! values; reflexivity; in this case the error should not be generated, I think +[ + p: make port! http:// + any [ + error? try [same? p p] + same? p p + ] +] diff --git a/tests/comparison/strict-equalq.test.reb b/tests/comparison/strict-equalq.test.reb new file mode 100644 index 0000000000..34a7e979b5 --- /dev/null +++ b/tests/comparison/strict-equalq.test.reb @@ -0,0 +1,418 @@ +; functions/comparison/strict-equalq.r +[strict-equal? :abs :abs] +; reflexivity test for native +[strict-equal? :all :all] +; reflexivity test for infix +[strict-equal? :+ :+] +; reflexivity test for function! +[ + a-value: func [] [] + strict-equal? :a-value :a-value +] +; no structural equality for function! +[not strict-equal? func [] [] func [] []] +; reflexivity test for closure! +[ + a-value: closure [] [] + strict-equal? :a-value :a-value +] +; no structural equality for closure! +[not strict-equal? closure [] [] closure [] []] +; binary! +[strict-equal? #{00} #{00}] +; binary versus bitset +[not strict-equal? #{00} #[bitset! #{00}]] +; symmetry +[equal? strict-equal? #[bitset! #{00}] #{00} strict-equal? #{00} #[bitset! #{00}]] +; email versus string +[ + a-value: to email! "" + not strict-equal? a-value to string! a-value +] +; symmetry +[ + a-value: to email! "" + equal? strict-equal? to string! a-value a-value strict-equal? a-value to string! a-value +] +[ + a-value: %"" + not strict-equal? a-value to string! a-value +] +; symmetry +[ + a-value: %"" + equal? strict-equal? a-value to string! a-value strict-equal? to string! a-value a-value +] +[not strict-equal? #{00} #[image! [1x1 #{00}]]] +; symmetry +[equal? strict-equal? #{00} #[image! [1x1 #{00}]] strict-equal? #[image! [1x1 #{00}]] #{00}] +[not strict-equal? #{00} to integer! #{00}] +; symmetry +[equal? strict-equal? #{00} to integer! #{00} strict-equal? to integer! #{00} #{00}] +[ + a-value: #a + not strict-equal? a-value to string! a-value +] +; symmetry +[ + a-value: #a + equal? strict-equal? a-value to string! a-value strict-equal? to string! a-value a-value +] +[not strict-equal? #{} blank] +; symmetry +[equal? strict-equal? #{} blank strict-equal? blank #{}] +[ + a-value: "" + not strict-equal? a-value to binary! a-value +] +; symmetry +[ + a-value: "" + equal? strict-equal? a-value to binary! a-value strict-equal? to binary! a-value a-value +] +[ + a-value: to tag! "" + not strict-equal? a-value to string! a-value +] +; symmetry +[ + a-value: to tag! "" + equal? strict-equal? a-value to string! a-value strict-equal? to string! a-value a-value +] +[ + a-value: 0.0.0.0 + not strict-equal? to binary! a-value a-value +] +; symmetry +[ + a-value: 0.0.0.0 + equal? strict-equal? to binary! a-value a-value strict-equal? a-value to binary! a-value +] +[strict-equal? #[bitset! #{00}] #[bitset! #{00}]] +[not strict-equal? #[bitset! #{}] #[bitset! #{00}]] +; block! +[strict-equal? [] []] +; reflexivity +[ + a-value: [] + strict-equal? a-value a-value +] +; reflexivity for past-tail blocks +[ + a-value: tail [1] + clear head a-value + strict-equal? a-value a-value +] +; reflexivity for cyclic blocks +[ + a-value: copy [] + insert/only a-value a-value + strict-equal? a-value a-value +] +; bug#1049 +; comparison of cyclic blocks +[ + a-value: copy [] + insert/only a-value a-value + b-value: copy [] + insert/only b-value b-value + error? try [strict-equal? a-value b-value] + true +] +; bug#1068 +; bug#1066 +[ + a-value: first ['a/b] + parse :a-value [b-value:] + strict-equal? :a-value :b-value +] +; symmetry +[ + a-value: first ['a/b] + parse :a-value [b-value:] + equal? strict-equal? :a-value :b-value strict-equal? :b-value :a-value +] +[not strict-equal? [] blank] +; symmetry +[equal? strict-equal? [] blank strict-equal? blank []] +; bug#1068 +; bug#1066 +[ + a-value: first [()] + parse a-value [b-value:] + strict-equal? a-value b-value +] +; symmetry +[ + a-value: first [()] + parse a-value [b-value:] + equal? strict-equal? a-value b-value strict-equal? b-value a-value +] +; bug#1068 +; bug#1066 +[ + a-value: 'a/b + parse a-value [b-value:] + strict-equal? :a-value :b-value +] +; symmetry +[ + a-value: 'a/b + parse a-value [b-value:] + equal? strict-equal? :a-value :b-value strict-equal? :b-value :a-value +] +; bug#1068 +; bug#1066 +[ + a-value: first [a/b:] + parse :a-value [b-value:] + strict-equal? :a-value :b-value +] +; symmetry +[ + a-value: first [a/b:] + parse :a-value [b-value:] + equal? strict-equal? :a-value :b-value strict-equal? :b-value :a-value +] +[not strict-equal? any-number! integer!] +; symmetry +[equal? strict-equal? any-number! integer! strict-equal? integer! any-number!] +; reflexivity +[strict-equal? -1 -1] +; reflexivity +[strict-equal? 0 0] +; reflexivity +[strict-equal? 1 1] +; reflexivity +[strict-equal? 0.0 0.0] +[strict-equal? 0.0 -0.0] +; reflexivity +[strict-equal? 1.0 1.0] +; reflexivity +[strict-equal? -1.0 -1.0] +; reflexivity +#64bit +[strict-equal? -9223372036854775808 -9223372036854775808] +; reflexivity +#64bit +[strict-equal? -9223372036854775807 -9223372036854775807] +; reflexivity +#64bit +[strict-equal? 9223372036854775807 9223372036854775807] +; -9223372036854775808 not strict-equal? +#64bit +[not strict-equal? -9223372036854775808 -9223372036854775807] +#64bit +[not strict-equal? -9223372036854775808 -1] +#64bit +[not strict-equal? -9223372036854775808 0] +#64bit +[not strict-equal? -9223372036854775808 1] +#64bit +[not strict-equal? -9223372036854775808 9223372036854775806] +#64bit +[not strict-equal? -9223372036854775808 9223372036854775807] +; -9223372036854775807 not strict-equal? +#64bit +[not strict-equal? -9223372036854775807 -9223372036854775808] +#64bit +[not strict-equal? -9223372036854775807 -1] +#64bit +[not strict-equal? -9223372036854775807 0] +#64bit +[not strict-equal? -9223372036854775807 1] +#64bit +[not strict-equal? -9223372036854775807 9223372036854775806] +#64bit +[not strict-equal? -9223372036854775807 9223372036854775807] +; -1 not strict-equal? +#64bit +[not strict-equal? -1 -9223372036854775808] +#64bit +[not strict-equal? -1 -9223372036854775807] +[not strict-equal? -1 0] +[not strict-equal? -1 1] +#64bit +[not strict-equal? -1 9223372036854775806] +#64bit +[not strict-equal? -1 9223372036854775807] +; 0 not strict-equal? +#64bit +[not strict-equal? 0 -9223372036854775808] +#64bit +[not strict-equal? 0 -9223372036854775807] +[not strict-equal? 0 -1] +[not strict-equal? 0 1] +#64bit +[not strict-equal? 0 9223372036854775806] +#64bit +[not strict-equal? 0 9223372036854775807] +; 1 not strict-equal? +#64bit +[not strict-equal? 1 -9223372036854775808] +#64bit +[not strict-equal? 1 -9223372036854775807] +[not strict-equal? 1 -1] +[not strict-equal? 1 0] +#64bit +[not strict-equal? 1 9223372036854775806] +#64bit +[not strict-equal? 1 9223372036854775807] +; 9223372036854775806 not strict-equal? +#64bit +[not strict-equal? 9223372036854775806 -9223372036854775808] +#64bit +[not strict-equal? 9223372036854775806 -9223372036854775807] +#64bit +[not strict-equal? 9223372036854775806 -1] +#64bit +[not strict-equal? 9223372036854775806 0] +#64bit +[not strict-equal? 9223372036854775806 1] +#64bit +[not strict-equal? 9223372036854775806 9223372036854775807] +; 9223372036854775807 not strict-equal? +#64bit +[not strict-equal? 9223372036854775807 -9223372036854775808] +#64bit +[not strict-equal? 9223372036854775807 -9223372036854775807] +#64bit +[not strict-equal? 9223372036854775807 -1] +#64bit +[not strict-equal? 9223372036854775807 0] +#64bit +[not strict-equal? 9223372036854775807 1] +#64bit +[not strict-equal? 9223372036854775807 9223372036854775806] +; "decimal tolerance" +[not strict-equal? to decimal! #{3FD3333333333333} to decimal! #{3FD3333333333334}] +; symmetry +[ + equal? strict-equal? to decimal! #{3FD3333333333333} to decimal! #{3FD3333333333334} + strict-equal? to decimal! #{3FD3333333333334} to decimal! #{3FD3333333333333} +] +[not strict-equal? to decimal! #{3FB9999999999999} to decimal! #{3FB999999999999A}] +; symmetry +[ + equal? strict-equal? to decimal! #{3FB9999999999999} to decimal! #{3FB999999999999A} + strict-equal? to decimal! #{3FB999999999999A} to decimal! #{3FB9999999999999} +] +; datatype differences +[not strict-equal? 0 0.0] +; datatype differences +[not strict-equal? 0 $0] +; datatype differences +[not strict-equal? 0 0%] +; datatype differences +[not strict-equal? 0.0 $0] +; datatype differences +[not strict-equal? 0.0 0%] +; datatype differences +[not strict-equal? $0 0%] +; symmetry +[equal? strict-equal? 1 1.0 strict-equal? 1.0 1] +; symmetry +[equal? strict-equal? 1 $1 strict-equal? $1 1] +; symmetry +[equal? strict-equal? 1 100% strict-equal? 100% 1] +; symmetry +[equal? strict-equal? 1.0 $1 strict-equal? $1 1.0] +; symmetry +[equal? strict-equal? 1.0 100% strict-equal? 100% 1.0] +; symmetry +[equal? strict-equal? $1 100% strict-equal? 100% $1] +; approximate equality +[not strict-equal? 10% + 10% + 10% 30%] +; symmetry +[equal? strict-equal? 10% + 10% + 10% 30% strict-equal? 30% 10% + 10% + 10%] +; date!; approximate equality +[not strict-equal? 2-Jul-2009 2-Jul-2009/22:20] +; symmetry +[equal? strict-equal? 2-Jul-2009 2-Jul-2009/22:20 strict-equal? 2-Jul-2009/22:20 2-Jul-2009] +; missing time = 00:00:00+00:00, by time compatibility standards +[not strict-equal? 2-Jul-2009 2-Jul-2009/00:00:00+00:00] +; symmetry +[equal? strict-equal? 2-Jul-2009 2-Jul-2009/00:00 strict-equal? 2-Jul-2009/00:00 2-Jul-2009] +; no timezone math in date! +[not strict-equal? 2-Jul-2009/22:20 2-Jul-2009/20:20-2:00] +; time! +[strict-equal? 00:00 00:00] +; char!; symmetry +[equal? strict-equal? #"a" 97 strict-equal? 97 #"a"] +; symmetry +[equal? strict-equal? #"a" 97.0 strict-equal? 97.0 #"a"] +; case +[not strict-equal? #"a" #"A"] +; case +[not strict-equal? "a" "A"] +; words; reflexivity +[strict-equal? 'a 'a] +; aliases +[not strict-equal? 'a 'A] +; symmetry +[equal? strict-equal? 'a 'A strict-equal? 'A 'a] +; binding not checked by STRICT-EQUAL? in Ren-C (only casing and type) +[strict-equal? 'a use [a] ['a]] +; symmetry +[equal? strict-equal? 'a use [a] ['a] strict-equal? use [a] ['a] 'a] +; different word types +[not strict-equal? 'a first [:a]] +; symmetry +[equal? strict-equal? 'a first [:a] strict-equal? first [:a] 'a] +; different word types +[not strict-equal? 'a first ['a]] +; symmetry +[equal? strict-equal? 'a first ['a] strict-equal? first ['a] 'a] +; different word types +[not strict-equal? 'a /a] +; symmetry +[equal? strict-equal? 'a /a strict-equal? /a 'a] +; different word types +[not strict-equal? 'a first [a:]] +; symmetry +[equal? strict-equal? 'a first [a:] strict-equal? first [a:] 'a] +; reflexivity +[strict-equal? first [:a] first [:a]] +; different word types +[not strict-equal? first [:a] first ['a]] +; symmetry +[equal? strict-equal? first [:a] first ['a] strict-equal? first ['a] first [:a]] +; different word types +[not strict-equal? first [:a] /a] +; symmetry +[equal? strict-equal? first [:a] /a strict-equal? /a first [:a]] +; different word types +[not strict-equal? first [:a] first [a:]] +; symmetry +[equal? strict-equal? first [:a] first [a:] strict-equal? first [a:] first [:a]] +; reflexivity +[strict-equal? first ['a] first ['a]] +; different word types +[not strict-equal? first ['a] /a] +; symmetry +[equal? strict-equal? first ['a] /a strict-equal? /a first ['a]] +; different word types +[not strict-equal? first ['a] first [a:]] +; symmetry +[equal? strict-equal? first ['a] first [a:] strict-equal? first [a:] first ['a]] +; reflexivity +[strict-equal? /a /a] +; different word types +[not strict-equal? /a first [a:]] +; symmetry +[equal? strict-equal? /a first [a:] strict-equal? first [a:] /a] +; reflexivity +[strict-equal? first [a:] first [a:]] +; logic! values +[strict-equal? true true] +[strict-equal? false false] +[not strict-equal? true false] +[not strict-equal? false true] +; port! values; reflexivity; in this case the error should not be generated, I think +[ + p: make port! http:// + any [ + error? try [strict-equal? p p] + strict-equal? p p + ] +] diff --git a/tests/comparison/strict-not-equalq.test.reb b/tests/comparison/strict-not-equalq.test.reb new file mode 100644 index 0000000000..3c21bbd301 --- /dev/null +++ b/tests/comparison/strict-not-equalq.test.reb @@ -0,0 +1,3 @@ +; functions/comparison/strict-not-equalq.r +; bug#32 +[strict-not-equal? 0 1] diff --git a/tests/context/bind.test.reb b/tests/context/bind.test.reb new file mode 100644 index 0000000000..db1ed0a75e --- /dev/null +++ b/tests/context/bind.test.reb @@ -0,0 +1,46 @@ +; functions/context/bind.r +; bug#50 +[blank? context-of to word! "zzz"] +; BIND works 'as expected' in object spec +; bug#1549 +[ + b1: [self] + ob: make object! [ + b2: [self] + set 'a same? first b2 first bind/copy b1 'b2 + ] + a +] +; bug#1549 +; BIND works 'as expected' in function body +[ + b1: [self] + f: func [/local b2] [ + b2: [self] + same? first b2 first bind/copy b1 'b2 + ] + f +] +; bug#1549 +; BIND works 'as expected' in closure body +[ + b1: [self] + f: closure [/local b2] [ + b2: [self] + same? first b2 first bind/copy b1 'b2 + ] + f +] +; bug#1549 +; BIND works 'as expected' in REPEAT body +[ + b1: [self] + repeat i 1 [ + b2: [self] + same? first b2 first bind/copy b1 'i + ] +] +; bug#1655 +[not head? bind next [1] 'rebol] +; bug#892, bug#216 +[y: 'x eval func [ x] [x: true get bind y 'x]] diff --git a/tests/context/bindq.test.reb b/tests/context/bindq.test.reb new file mode 100644 index 0000000000..5a7ac62624 --- /dev/null +++ b/tests/context/bindq.test.reb @@ -0,0 +1,5 @@ +; functions/context/bindq.r +[ + o: make object! [a: _] + same? o context-of in o 'a +] diff --git a/tests/context/boundq.test.reb b/tests/context/boundq.test.reb new file mode 100644 index 0000000000..394030ff54 --- /dev/null +++ b/tests/context/boundq.test.reb @@ -0,0 +1 @@ +; functions/context/boundq.r diff --git a/tests/context/resolve.test.reb b/tests/context/resolve.test.reb new file mode 100644 index 0000000000..0575b00f60 --- /dev/null +++ b/tests/context/resolve.test.reb @@ -0,0 +1,3 @@ +; functions/context/resolve.r +; bug#2017: crash in RESOLVE/extend/only +[get in resolve/extend/only context [] context [a: true] [a] 'a] diff --git a/tests/context/set.test.reb b/tests/context/set.test.reb new file mode 100644 index 0000000000..aab93f7ee3 --- /dev/null +++ b/tests/context/set.test.reb @@ -0,0 +1,34 @@ +; functions/context/set.r +; bug#1763 +[a: 1 all [error? try [set [a] reduce [()]] a = 1]] +[a: 1 attempt [set [a b] reduce [2 ()]] a = 1] +[x: has [a: 1] all [error? try [set x reduce [()]] x/a = 1]] +[x: has [a: 1 b: 2] all [error? try [set x reduce [3 ()]] x/a = 1]] +; set [:get-word] [word] +[a: 1 b: _ set [b] [a] b = 'a] + +[ + a: 10 + b: 20 + all? [blank = set [a b] blank | blank? a | blank? b] +][ + a: 10 + b: 20 + all? [ + [x y] = set/only [a b] [x y] + a = [x y] + b = [x y] + ] +][ + a: 10 + b: 20 + c: 30 + set [a b c] [_ 99] + all? [a = _ | b = 99 | c = _] +][ + a: 10 + b: 20 + c: 30 + set/some [a b c] [_ 99] + all? [a = 10 | b = 99 | c = 30] +] diff --git a/tests/context/unset.test.reb b/tests/context/unset.test.reb new file mode 100644 index 0000000000..fc41637535 --- /dev/null +++ b/tests/context/unset.test.reb @@ -0,0 +1,12 @@ +; functions/context/unset.r +[ + a: _ + unset 'a + not set? 'a +] +[ + a: _ + unset 'a + unset 'a + not set? 'a +] diff --git a/tests/context/use.test.reb b/tests/context/use.test.reb new file mode 100644 index 0000000000..a594816672 --- /dev/null +++ b/tests/context/use.test.reb @@ -0,0 +1,44 @@ +; functions/context/use.r +; local word test +[ + a: 1 + use [a] [a: 2] + a = 1 +] +[ + a: 1 + error? try [use 'a [a: 2]] + a = 1 +] +; initialization (lack of) +[a: 10 all [use [a] [void? :a] a = 10]] +; BREAK out of USE +[ + blank? loop 1 [ + use [a] [break] + 2 + ] +] +; THROW out of USE +[ + 1 = catch [ + use [a] [throw 1] + 2 + ] +] +; "error out" of USE +[ + error? try [ + use [a] [1 / 0] + 2 + ] +] +; bug#539 +; RETURN out of USE +[ + f: func [] [ + use [a] [return 1] + 2 + ] + 1 = f +] diff --git a/tests/context/valueq.test.reb b/tests/context/valueq.test.reb new file mode 100644 index 0000000000..48516532e9 --- /dev/null +++ b/tests/context/valueq.test.reb @@ -0,0 +1,5 @@ +; functions/context/valueq.r +[false == set? 'nonsense] +[true == set? 'set?] +; #1914 ... Ren-C indefinite extent prioritizes failure if not indefinite +[error? try [set? eval func [x] ['x] blank]] diff --git a/tests/control/all.test.reb b/tests/control/all.test.reb new file mode 100644 index 0000000000..816ec9ccf6 --- /dev/null +++ b/tests/control/all.test.reb @@ -0,0 +1,348 @@ +; functions/control/all.r +; zero values +[void? all []] +; one value +[:abs = all [:abs]] +[ + a-value: #{} + same? a-value all [a-value] +] +[ + a-value: charset "" + same? a-value all [a-value] +] +[ + a-value: [] + same? a-value all [a-value] +] +[ + a-value: blank! + same? a-value all [a-value] +] +[1/Jan/0000 = all [1/Jan/0000]] +[0.0 == all [0.0]] +[1.0 == all [1.0]] +[ + a-value: me@here.com + same? a-value all [a-value] +] +[error? all [try [1 / 0]]] +[ + a-value: %"" + same? a-value all [a-value] +] +[ + a-value: does [] + same? :a-value all [:a-value] +] +[ + a-value: first [:a] + :a-value == all [:a-value] +] +[#"^@" == all [#"^@"]] +[ + a-value: make image! 0x0 + same? a-value all [a-value] +] +[0 == all [0]] +[1 == all [1]] +[#a == all [#a]] +[ + a-value: first ['a/b] + :a-value == all [:a-value] +] +[ + a-value: first ['a] + :a-value == all [:a-value] +] +[true = all [true]] +[blank? all [false]] +[$1 == all [$1]] +[same? :type-of all [:type-of]] +[blank? all [_]] +[ + a-value: make object! [] + same? :a-value all [:a-value] +] +[ + a-value: first [()] + same? :a-value all [:a-value] +] +[same? get '+ all [get '+]] +[0x0 == all [0x0]] +[ + a-value: 'a/b + :a-value == all [:a-value] +] +[ + a-value: make port! http:// + port? all [:a-value] +] +[/a == all [/a]] +[ + a-value: first [a/b:] + :a-value == all [:a-value] +] +[ + a-value: first [a:] + :a-value == all [:a-value] +] +[ + a-value: "" + same? :a-value all [:a-value] +] +[ + a-value: make tag! "" + same? :a-value all [:a-value] +] +[0:00 == all [0:00]] +[0.0.0 == all [0.0.0]] +[void? all [()]] +['a == all ['a]] +; two values +[:abs = all [true :abs]] +[ + a-value: #{} + same? a-value all [true a-value] +] +[ + a-value: charset "" + same? a-value all [true a-value] +] +[ + a-value: [] + same? a-value all [true a-value] +] +[ + a-value: blank! + same? a-value all [true a-value] +] +[1/Jan/0000 = all [true 1/Jan/0000]] +[0.0 == all [true 0.0]] +[1.0 == all [true 1.0]] +[ + a-value: me@here.com + same? a-value all [true a-value] +] +[error? all [true try [1 / 0]]] +[ + a-value: %"" + same? a-value all [true a-value] +] +[ + a-value: does [] + same? :a-value all [true :a-value] +] +[ + a-value: first [:a] + same? :a-value all [true :a-value] +] +[#"^@" == all [true #"^@"]] +[ + a-value: make image! 0x0 + same? a-value all [true a-value] +] +[0 == all [true 0]] +[1 == all [true 1]] +[#a == all [true #a]] +[ + a-value: first ['a/b] + :a-value == all [true :a-value] +] +[ + a-value: first ['a] + :a-value == all [true :a-value] +] +[$1 == all [true $1]] +[same? :type-of all [true :type-of]] +[blank? all [true _]] +[ + a-value: make object! [] + same? :a-value all [true :a-value] +] +[ + a-value: first [()] + same? :a-value all [true :a-value] +] +[same? get '+ all [true get '+]] +[0x0 == all [true 0x0]] +[ + a-value: 'a/b + :a-value == all [true :a-value] +] +[ + a-value: make port! http:// + port? all [true :a-value] +] +[/a == all [true /a]] +[ + a-value: first [a/b:] + :a-value == all [true :a-value] +] +[ + a-value: first [a:] + :a-value == all [true :a-value] +] +[ + a-value: "" + same? :a-value all [true :a-value] +] +[ + a-value: make tag! "" + same? :a-value all [true :a-value] +] +[0:00 == all [true 0:00]] +[0.0.0 == all [true 0.0.0]] +[1020 == all [1020 ()]] +['a == all [true 'a]] +[true = all [:abs true]] +[ + a-value: #{} + true = all [a-value true] +] +[ + a-value: charset "" + true = all [a-value true] +] +[ + a-value: [] + true = all [a-value true] +] +[ + a-value: blank! + true = all [a-value true] +] +[true = all [1/Jan/0000 true]] +[true = all [0.0 true]] +[true = all [1.0 true]] +[ + a-value: me@here.com + true = all [a-value true] +] +[true = all [try [1 / 0] true]] +[ + a-value: %"" + true = all [a-value true] +] +[ + a-value: does [] + true = all [:a-value true] +] +[ + a-value: first [:a] + true = all [:a-value true] +] +[true = all [#"^@" true]] +[ + a-value: make image! 0x0 + true = all [a-value true] +] +[true = all [0 true]] +[true = all [1 true]] +[true = all [#a true]] +[ + a-value: first ['a/b] + true = all [:a-value true] +] +[ + a-value: first ['a] + true = all [:a-value true] +] +[true = all [true true]] +[blank? all [false true]] +[blank? all [true false]] +[true = all [$1 true]] +[true = all [:type-of true]] +[blank? all [_ true]] +[ + a-value: make object! [] + true = all [:a-value true] +] +[ + a-value: first [()] + true = all [:a-value true] +] +[true = all [get '+ true]] +[true = all [0x0 true]] +[ + a-value: 'a/b + true = all [:a-value true] +] +[ + a-value: make port! http:// + true = all [:a-value true] +] +[true = all [/a true]] +[ + a-value: first [a/b:] + true = all [:a-value true] +] +[ + a-value: first [a:] + true = all [:a-value true] +] +[ + a-value: "" + true = all [:a-value true] +] +[ + a-value: make tag! "" + true = all [:a-value true] +] +[true = all [0:00 true]] +[true = all [0.0.0 true]] +[true = all [() true]] +[true = all ['a true]] +; evaluation stops after encountering FALSE or NONE +[ + success: true + all [false success: false] + success +] +[ + success: true + all [blank success: false] + success +] +; evaluation continues otherwise +[ + success: false + all [true success: true] + success +] +[ + success: false + all [1 success: true] + success +] +; RETURN stops evaluation +[ + f1: does [all [return 1 2] 2] + 1 = f1 +] +; THROW stops evaluation +[ + 1 = catch [ + all [ + throw 1 + 2 + ] + ] +] +; BREAK stops evaluation +[ + blank? loop 1 [ + all [ + break + 2 + ] + ] +] +; recursivity +[all [true all [true]]] +[not all [true all [false]]] +; infinite recursion +[ + blk: [all blk] + error? try blk +] diff --git a/tests/control/any.test.reb b/tests/control/any.test.reb new file mode 100644 index 0000000000..7a6713e23d --- /dev/null +++ b/tests/control/any.test.reb @@ -0,0 +1,349 @@ +; functions/control/any.r +; zero values +[void? any []] +; one value +[:abs = any [:abs]] +[ + a-value: #{} + same? a-value any [a-value] +] +[ + a-value: charset "" + same? a-value any [a-value] +] +[ + a-value: [] + same? a-value any [a-value] +] +[ + a-value: blank! + same? a-value any [a-value] +] +[1/Jan/0000 = any [1/Jan/0000]] +[0.0 == any [0.0]] +[1.0 == any [1.0]] +[ + a-value: me@here.com + same? a-value any [a-value] +] +[error? any [try [1 / 0]]] +[ + a-value: %"" + same? a-value any [a-value] +] +[ + a-value: does [] + same? :a-value any [:a-value] +] +[ + a-value: first [:a] + :a-value == any [:a-value] +] +[#"^@" == any [#"^@"]] +[ + a-value: make image! 0x0 + same? a-value any [a-value] +] +[0 == any [0]] +[1 == any [1]] +[#a == any [#a]] +[ + a-value: first ['a/b] + :a-value == any [:a-value] +] +[ + a-value: first ['a] + :a-value == any [:a-value] +] +[true = any [true]] +[blank? any [false]] +[$1 == any [$1]] +[same? :type-of any [:type-of]] +[blank? any [_]] +[ + a-value: make object! [] + same? :a-value any [:a-value] +] +[ + a-value: first [()] + same? :a-value any [:a-value] +] +[same? get '+ any [get '+]] +[0x0 == any [0x0]] +[ + a-value: 'a/b + :a-value == any [:a-value] +] +[ + a-value: make port! http:// + port? any [:a-value] +] +[/a == any [/a]] +; routine test? +[ + a-value: first [a/b:] + :a-value == any [:a-value] +] +[ + a-value: first [a:] + :a-value == any [:a-value] +] +[ + a-value: "" + same? :a-value any [:a-value] +] +[ + a-value: make tag! "" + same? :a-value any [:a-value] +] +[0:00 == any [0:00]] +[0.0.0 == any [0.0.0]] +[void? any [()]] +['a == any ['a]] +; two values +[:abs = any [false :abs]] +[ + a-value: #{} + same? a-value any [false a-value] +] +[ + a-value: charset "" + same? a-value any [false a-value] +] +[ + a-value: [] + same? a-value any [false a-value] +] +[ + a-value: blank! + same? a-value any [false a-value] +] +[1/Jan/0000 = any [false 1/Jan/0000]] +[0.0 == any [false 0.0]] +[1.0 == any [false 1.0]] +[ + a-value: me@here.com + same? a-value any [false a-value] +] +[error? any [false try [1 / 0]]] +[ + a-value: %"" + same? a-value any [false a-value] +] +[ + a-value: does [] + same? :a-value any [false :a-value] +] +[ + a-value: first [:a] + :a-value == any [false :a-value] +] +[#"^@" == any [false #"^@"]] +[ + a-value: make image! 0x0 + same? a-value any [false a-value] +] +[0 == any [false 0]] +[1 == any [false 1]] +[#a == any [false #a]] +[ + a-value: first ['a/b] + :a-value == any [false :a-value] +] +[ + a-value: first ['a] + :a-value == any [false :a-value] +] +[true = any [false true]] +[blank? any [false false]] +[$1 == any [false $1]] +[same? :type-of any [false :type-of]] +[blank? any [false _]] +[ + a-value: make object! [] + same? :a-value any [false :a-value] +] +[ + a-value: first [()] + same? :a-value any [false :a-value] +] +[same? get '+ any [false get '+]] +[0x0 == any [false 0x0]] +[ + a-value: 'a/b + :a-value == any [false :a-value] +] +[ + a-value: make port! http:// + port? any [false :a-value] +] +[/a == any [false /a]] +[ + a-value: first [a/b:] + :a-value == any [false :a-value] +] +[ + a-value: first [a:] + :a-value == any [false :a-value] +] +[ + a-value: "" + same? :a-value any [false :a-value] +] +[ + a-value: make tag! "" + same? :a-value any [false :a-value] +] +[0:00 == any [false 0:00]] +[0.0.0 == any [false 0.0.0]] +[blank? any [false ()]] +['a == any [false 'a]] +[:abs = any [:abs false]] +[ + a-value: #{} + same? a-value any [a-value false] +] +[ + a-value: charset "" + same? a-value any [a-value false] +] +[ + a-value: [] + same? a-value any [a-value false] +] +[ + a-value: blank! + same? a-value any [a-value false] +] +[1/Jan/0000 = any [1/Jan/0000 false]] +[0.0 == any [0.0 false]] +[1.0 == any [1.0 false]] +[ + a-value: me@here.com + same? a-value any [a-value false] +] +[error? any [try [1 / 0] false]] +[ + a-value: %"" + same? a-value any [a-value false] +] +[ + a-value: does [] + same? :a-value any [:a-value false] +] +[ + a-value: first [:a] + :a-value == any [:a-value false] +] +[#"^@" == any [#"^@" false]] +[ + a-value: make image! 0x0 + same? a-value any [a-value false] +] +[0 == any [0 false]] +[1 == any [1 false]] +[#a == any [#a false]] +[ + a-value: first ['a/b] + :a-value == any [:a-value false] +] +[ + a-value: first ['a] + :a-value == any [:a-value false] +] +[true = any [true false]] +[$1 == any [$1 false]] +[same? :type-of any [:type-of false]] +[blank? any [_ false]] +[ + a-value: make object! [] + same? :a-value any [:a-value false] +] +[ + a-value: first [()] + same? :a-value any [:a-value false] +] +[same? get '+ any [get '+ false]] +[0x0 == any [0x0 false]] +[ + a-value: 'a/b + :a-value == any [:a-value false] +] +[ + a-value: make port! http:// + port? any [:a-value false] +] +[/a == any [/a false]] +[ + a-value: first [a/b:] + :a-value == any [:a-value false] +] +[ + a-value: first [a:] + :a-value == any [:a-value false] +] +[ + a-value: "" + same? :a-value any [:a-value false] +] +[ + a-value: make tag! "" + same? :a-value any [:a-value false] +] +[0:00 == any [0:00 false]] +[0.0.0 == any [0.0.0 false]] +[blank? any [() false]] +['a == any ['a false]] +; evaluation stops after encountering something else than FALSE or NONE +[ + success: true + any [true success: false] + success +] +[ + success: true + any [1 success: false] + success +] +; evaluation continues otherwise +[ + success: false + any [false success: true] + success +] +[ + success: false + any [blank success: true] + success +] +; RETURN stops evaluation +[ + f1: does [any [return 1 2] 2] + 1 = f1 +] +; THROW stops evaluation +[ + 1 = catch [ + any [ + throw 1 + 2 + ] + ] +] +; BREAK stops evaluation +[ + blank? loop 1 [ + any [ + break + 2 + ] + ] +] +; recursivity +[any [false any [true]]] +[blank? any [false any [false]]] +; infinite recursion +[ + blk: [any blk] + error? try blk +] diff --git a/tests/control/apply.test.reb b/tests/control/apply.test.reb new file mode 100644 index 0000000000..282ab6406d --- /dev/null +++ b/tests/control/apply.test.reb @@ -0,0 +1,122 @@ +; functions/control/apply.r +; bug#44 +[error? try [r3-alpha-apply 'type-of/word []]] +[1 == r3-alpha-apply :subtract [2 1]] +[1 = (r3-alpha-apply :- [2 1])] +[error? try [r3-alpha-apply func [a] [a] []]] +[error? try [r3-alpha-apply/only func [a] [a] []]] + +; CC#2237 +[error? try [r3-alpha-apply func [a] [a] [1 2]]] +[error? try [r3-alpha-apply/only func [a] [a] [1 2]]] + +[true = r3-alpha-apply func [/a] [a] [true]] +[false == r3-alpha-apply func [/a] [a] [false]] +[false == r3-alpha-apply func [/a] [a] []] +[true = r3-alpha-apply/only func [/a] [a] [true]] +; the word 'false +[true = r3-alpha-apply/only func [/a] [a] [false]] +[false == r3-alpha-apply/only func [/a] [a] []] +[use [a] [a: true true = r3-alpha-apply func [/a] [a] [a]]] +[use [a] [a: false false == r3-alpha-apply func [/a] [a] [a]]] +[use [a] [a: false true = r3-alpha-apply func [/a] [a] ['a]]] +[use [a] [a: false true = r3-alpha-apply func [/a] [a] [/a]]] +[use [a] [a: false true = r3-alpha-apply/only func [/a] [a] [a]]] +[group! == r3-alpha-apply/only :type-of [()]] +[[1] == head r3-alpha-apply :insert [copy [] [1] blank blank blank]] +[[1] == head r3-alpha-apply :insert [copy [] [1] blank blank false]] +[[[1]] == head r3-alpha-apply :insert [copy [] [1] blank blank true]] +[function! == r3-alpha-apply :type-of [:print]] +[get-word! == r3-alpha-apply/only :type-of [:print]] +; bug#1760 +[1 == eval does [r3-alpha-apply does [] [return 1] 2]] +; bug#1760 +[1 == eval does [r3-alpha-apply func [a] [a] [return 1] 2]] +; bug#1760 +[1 == eval does [r3-alpha-apply does [] [return 1]]] +[1 == eval does [r3-alpha-apply func [a] [a] [return 1]]] +[1 == eval does [r3-alpha-apply :also [return 1 2]]] +; bug#1760 +[1 == eval does [r3-alpha-apply :also [2 return 1]]] + +; EVAL/ONLY +[ + o: make object! [a: 0] + b: eval/only (quote o/a:) 1 + 2 + all [o/a = 1 | b = 1] ;-- above acts as `b: (eval/only (quote o/a:) 1) + 2` +] +[ + a: func [b c :d] [reduce [b c d]] + [1 + 2] = (eval/only :a 1 + 2) +] + +[ + void? r3-alpha-apply func [ + return: [ any-value!] + x [ any-value!] + ][ + get/opt 'x + ][ + () + ] +][ + void? r3-alpha-apply func [ + return: [ any-value!] + 'x [ any-value!] + ][ + get/opt 'x + ][ + () + ] +][ + void? r3-alpha-apply func [ + return: [ any-value!] + x [ any-value!] + ][ + return get/opt 'x + ][ + () + ] +][ + void? r3-alpha-apply func [ + return: [ any-value!] + 'x [ any-value!] + ][ + return get/opt 'x + ][ + () + ] +] +[error? r3-alpha-apply func ['x [ any-value!]] [return get/opt 'x] [make error! ""]] +[ + error? r3-alpha-apply/only func [x [ any-value!]] [ + return get/opt 'x + ] head insert copy [] make error! "" +][ + error? r3-alpha-apply/only func ['x [ any-value!]] [ + return get/opt 'x + ] head insert copy [] make error! "" +] +[use [x] [x: 1 strict-equal? 1 r3-alpha-apply func ['x] [:x] [:x]]] +[use [x] [x: 1 strict-equal? 1 r3-alpha-apply func ['x] [:x] [:x]]] +[use [x] [x: 1 strict-equal? first [:x] r3-alpha-apply/only func [:x] [:x] [:x]]] +[ + use [x] [ + unset 'x + strict-equal? first [:x] r3-alpha-apply/only func ['x [ any-value!]] [ + return get/opt 'x + ] [:x] + ] +] +[use [x] [x: 1 strict-equal? 1 r3-alpha-apply func [:x] [:x] [x]]] +[use [x] [x: 1 strict-equal? 'x r3-alpha-apply func [:x] [:x] ['x]]] +[use [x] [x: 1 strict-equal? 'x r3-alpha-apply/only func [:x] [:x] [x]]] +[use [x] [x: 1 strict-equal? 'x r3-alpha-apply/only func [:x] [return :x] [x]]] +[ + use [x] [ + unset 'x + strict-equal? 'x r3-alpha-apply/only func ['x [ any-value!]] [ + return get/opt 'x + ] [x] + ] +] diff --git a/tests/control/attempt.test.reb b/tests/control/attempt.test.reb new file mode 100644 index 0000000000..b461135292 --- /dev/null +++ b/tests/control/attempt.test.reb @@ -0,0 +1,22 @@ +; functions/control/attempt.r +; bug#41 +[blank? attempt [1 / 0]] +[1 = attempt [1]] +[void? attempt []] +; RETURN stops attempt evaluation +[ + f1: does [attempt [return 1 2] 2] + 1 == f1 +] +; THROW stops attempt evaluation +[1 == catch [attempt [throw 1 2] 2]] +; BREAK stops attempt evaluation +[blank? loop 1 [attempt [break 2] 2]] +; recursion +[1 = attempt [attempt [1]]] +[blank? attempt [attempt [1 / 0]]] +; infinite recursion +[ + blk: [attempt blk] + blank? attempt blk +] diff --git a/tests/control/break.test.reb b/tests/control/break.test.reb new file mode 100644 index 0000000000..b5661bd927 --- /dev/null +++ b/tests/control/break.test.reb @@ -0,0 +1,33 @@ +; functions/control/break.r +; see loop functions for basic breaking functionality +; just testing return values, but written as if break could fail altogether +; in case that becomes an issue. break failure tests are with the functions +; that they are failing to break from. +[blank? loop 1 [break 2]] + +; the "result" of break should not be assignable +[#1515 | a: 1 | loop 1 [a: break] | :a = 1] +[#1515 | a: 1 | loop 1 [set 'a break] :a = 1] +[#1515 | a: 1 | loop 1 [set/opt 'a break] | :a = 1] + +; the "result" of break should not be passable to functions +[#1509 | a: 1 | loop 1 [a: error? break] | :a = 1] +[#1509 | a: 1 | loop 1 [a: type-of break] | :a = 1] +[#1509 | foo: func [x y] [9] | a: 1 | loop 1 [a: foo break 5] | :a = 1] +[#1509 | foo: func [x y] [9] | a: 1 | loop 1 [a: foo 5 break] | :a = 1] +[#1509 | foo: func [x y] [9] a: 1 loop 1 [a: foo break break] | :a = 1] + +; check that BREAK is evaluated (and not CONTINUE): +[foo: func [x y] [] a: 1 loop 2 [a: a + 1 foo break continue a: a + 10] :a =? 2] + +; check that BREAK is not evaluated (but CONTINUE is): +[foo: func [x y] [] a: 1 loop 2 [a: a + 1 foo continue break a: a + 10] :a =? 3] + +; bug#1535 +[#1535 | loop 1 [words-of break] true] +[#1535 | loop 1 [values-of break] true] + +[#1945 | loop 1 [spec-of break] true] + +; the "result" of break should not be caught by try +[a: 1 loop 1 [a: error? try [break]] :a =? 1] diff --git a/tests/control/case.test.reb b/tests/control/case.test.reb new file mode 100644 index 0000000000..52f00c547f --- /dev/null +++ b/tests/control/case.test.reb @@ -0,0 +1,61 @@ +; functions/control/case.r +[ + success: false + case [true [success: true]] + success +] +[ + success: true + case [false [success: false]] + success +] + +[ + void? case [] +][ + #2246 + void? case* [true []] +][ + blank? case [true []] +] + +; case results +[case [true [true]]] +[not case [true [false]]] +; RETURN stops evaluation +[ + f1: does [case [return 1 2]] + 1 = f1 +] +; THROW stops evaluation +[ + 1 = catch [ + case [throw 1 2] + 2 + ] +] +; BREAK stops evaluation +[ + blank? loop 1 [ + case [break 2] + 2 + ] +] +; /all refinement +; bug#86 +[ + s1: false + s2: false + case/all [ + true [s1: true] + true [s2: true] + ] + s1 and* s2 +] +; recursivity +[1 = case [true [case [true [1]]]]] +; infinite recursion +[ + blk: [case blk] + error? try blk +] diff --git a/tests/control/catch.test.reb b/tests/control/catch.test.reb new file mode 100644 index 0000000000..2d00dfb0d5 --- /dev/null +++ b/tests/control/catch.test.reb @@ -0,0 +1,114 @@ +; functions/control/catch.r +; see also functions/control/throw.r +[ + catch [ + throw success: true + sucess: false + ] + success +] +; catch results +[void? catch []] +[void? catch [()]] +[error? catch [try [1 / 0]]] +[1 = catch [1]] +[void? catch [throw ()]] +[error? first catch [throw reduce [try [1 / 0]]]] +[1 = catch [throw 1]] +; catch/name results +[void? catch/name [] 'catch] +[void? catch/name [()] 'catch] +[error? catch/name [try [1 / 0]] 'catch] +[1 = catch/name [1] 'catch] +[void? catch/name [throw/name () 'catch] 'catch] +[error? first catch/name [throw/name reduce [try [1 / 0]] 'catch] 'catch] +[1 = catch/name [throw/name 1 'catch] 'catch] +; recursive cases +[ + num: 1 + catch [ + catch [throw 1] + num: 2 + ] + 2 = num +] +[ + num: 1 + catch [ + catch/name [ + throw 1 + ] 'catch + num: 2 + ] + 1 = num +] +[ + num: 1 + catch/name [ + catch [throw 1] + num: 2 + ] 'catch + 2 = num +] +[ + num: 1 + catch/name [ + catch/name [ + throw/name 1 'name + ] 'name + num: 2 + ] 'name + 2 = num +] +; CATCH and RETURN +[ + f: does [catch [return 1] 2] + 1 = f +] +; CATCH and BREAK +[ + blank? loop 1 [ + catch [break 2] + 2 + ] +] +; CATCH/QUIT +[ + catch/quit [quit] + true +] +; bug#851 +[error? try [catch/quit [] fail make error! ""]] +; bug#851 +[blank? attempt [catch/quit [] fail make error! ""]] + + +; DO-ALL is a sort of CATCH/TRAP hybrid. +; +[ + x: _ + all? [ + error? trap [do-all [ + x: 10 + | + fail "some error" + | + x: 20 + ]] + x = 20 + ] +] + +[ + x: _ + all? [ + 30 = catch [do-all [ + x: 10 + | + throw 30 + | + x: 20 + ]] + x = 20 + ] +] \ No newline at end of file diff --git a/tests/control/compose.test.reb b/tests/control/compose.test.reb new file mode 100644 index 0000000000..8ac639b602 --- /dev/null +++ b/tests/control/compose.test.reb @@ -0,0 +1,48 @@ +; functions/control/compose.r +[ + num: 1 + [1 num] = compose [(num) num] +] +[[] = compose []] +[ + blk: [] + append blk [try [1 / 0]] + blk = compose blk +] +; RETURN stops the evaluation +[ + f1: does [compose [(return 1)] 2] + 1 = f1 +] +; THROW stops the evaluation +[1 = catch [compose [(throw 1 2)] 2]] +; BREAK stops the evaluation +[blank? loop 1 [compose [(break 2)] 2]] +; Test that errors do not stop the evaluation: +[block? compose [(try [1 / 0])]] +[ + blk: [] + not same? blk compose blk +] +[ + blk: [[]] + same? first blk first compose blk +] +[ + blk: [] + same? blk first compose [(reduce [blk])] +] +[ + blk: [] + same? blk first compose/only [(blk)] +] +; recursion +[ + num: 1 + [num 1] = compose [num (compose [(num)])] +] +; infinite recursion +[ + blk: [(compose blk)] + error? try blk +] diff --git a/tests/control/continue.test.reb b/tests/control/continue.test.reb new file mode 100644 index 0000000000..eac77b5e14 --- /dev/null +++ b/tests/control/continue.test.reb @@ -0,0 +1,15 @@ +; functions/control/continue.r +; see loop functions for basic continuing functionality +; the "result" of continue should not be assignable, bug#1515 +[a: 1 loop 1 [a: continue] :a =? 1] +[a: 1 loop 1 [set 'a continue] :a =? 1] +[a: 1 loop 1 [set/opt 'a continue] :a =? 1] +; the "result" of continue should not be passable to functions, bug#1509 +[a: 1 loop 1 [a: error? continue] :a =? 1] +; bug#1535 +[loop 1 [words-of continue] true] +[loop 1 [values-of continue] true] +; bug#1945 +[loop 1 [spec-of continue] true] +; continue should not be caught by try +[a: 1 loop 1 [a: error? try [continue]] :a =? 1] diff --git a/tests/control/default.test.reb b/tests/control/default.test.reb new file mode 100644 index 0000000000..1c6bfc0af4 --- /dev/null +++ b/tests/control/default.test.reb @@ -0,0 +1,19 @@ +[ + unset 'x + x: default 10 + x = 10 +][ + x: _ + x: default 10 + x = 10 +][ + x: 20 + x: default 10 + x = 20 +][ + o: make object! [x: 10 y: _ z: ()] + o/x: default 20 + o/y: default 20 + o/z: default 20 + [10 20 20] = reduce [o/x o/y o/z] +] diff --git a/tests/control/disarm.test.reb b/tests/control/disarm.test.reb new file mode 100644 index 0000000000..2e2e4fadc3 --- /dev/null +++ b/tests/control/disarm.test.reb @@ -0,0 +1 @@ +; functions/control/disarm.r diff --git a/tests/control/do.test.reb b/tests/control/do.test.reb new file mode 100644 index 0000000000..45174d9dbc --- /dev/null +++ b/tests/control/do.test.reb @@ -0,0 +1,274 @@ +; functions/control/do.r +[ + success: false + do [success: true] + success +] +[1 == eval :abs -1] +[ + a-value: to binary! "1 + 1" + 2 == do a-value +] +[ + a-value: charset "" + same? a-value eval a-value +] +; do block start +[void? do []] +[:abs = do [:abs]] +[ + a-value: #{} + same? a-value do reduce [a-value] +] +[ + a-value: charset "" + same? a-value do reduce [a-value] +] +[ + a-value: [] + same? a-value do reduce [a-value] +] +[same? blank! do reduce [blank!]] +[1/Jan/0000 = do [1/Jan/0000]] +[0.0 == do [0.0]] +[1.0 == do [1.0]] +[ + a-value: me@here.com + same? a-value do reduce [a-value] +] +[error? do [try [1 / 0]]] +[ + a-value: %"" + same? a-value do reduce [a-value] +] +[ + a-value: does [] + same? :a-value do [:a-value] +] +[ + a-value: first [:a-value] + :a-value == do reduce [:a-value] +] +[#"^@" == do [#"^@"]] +[ + a-value: make image! 0x0 + same? a-value do reduce [a-value] +] +[0 == do [0]] +[1 == do [1]] +[#a == do [#a]] +[ + a-value: first ['a/b] + :a-value == do [:a-value] +] +[ + a-value: first ['a] + :a-value == do [:a-value] +] +[#[true] == do [#[true]]] +[#[false] == do [#[false]]] +[$1 == do [$1]] +[same? :type-of do [:type-of]] +[blank? do [_]] +[ + a-value: make object! [] + same? :a-value do reduce [:a-value] +] +[ + a-value: first [()] + same? :a-value do [:a-value] +] +[same? get '+ do [get '+]] +[0x0 == do [0x0]] +[ + a-value: 'a/b + :a-value == do [:a-value] +] +[ + a-value: make port! http:// + port? do reduce [:a-value] +] +[/a == do [/a]] +[ + a-value: first [a/b:] + :a-value == do [:a-value] +] +[ + a-value: first [a:] + :a-value == do [:a-value] +] +[ + a-value: "" + same? :a-value do reduce [:a-value] +] +[ + a-value: make tag! "" + same? :a-value do reduce [:a-value] +] +[0:00 == do [0:00]] +[0.0.0 == do [0.0.0]] +[void? do [()]] +['a == do ['a]] +; do block end +[ + a-value: blank! + same? a-value eval a-value +] +[1/Jan/0000 == eval 1/Jan/0000] +[0.0 == eval 0.0] +[1.0 == eval 1.0] +[ + a-value: me@here.com + same? a-value eval a-value +] +[error? try [do try [1 / 0] 1]] +[ + a-value: does [5] + 5 == eval :a-value +] +[ + a: 12 + a-value: first [:a] + :a == eval :a-value +] +[#"^@" == eval #"^@"] +[ + a-value: make image! 0x0 + same? a-value eval a-value +] +[0 == eval 0] +[1 == eval 1] +[#a == eval #a] +;-- CC#2101, #1434 +[ + a-value: first ['a/b] + all [ + lit-path? a-value + path? eval :a-value + (to-path :a-value) == (eval :a-value) + ] +] +[ + a-value: first ['a] + all [ + lit-word? a-value + word? eval :a-value + (to-word :a-value) == (eval :a-value) + ] +] +[true = eval true] +[false = eval false] +[$1 == eval $1] +[_ = eval :type-of ()] +[blank? do _] +[ + a-value: make object! [] + same? :a-value eval :a-value +] +[ + a-value: first [(2)] + 2 == do as block! :a-value +] +[ + a-value: 'a/b + a: make object! [b: 1] + 1 == eval :a-value +] +[ + a-value: make port! http:// + port? eval :a-value +] +[ + a-value: first [a/b:] + all [ + set-path? :a-value + error? try [eval :a-value] ;-- no value to assign after it... + ] +] +[ + a-value: "1" + 1 == do :a-value +] +[void? do ""] +[1 = do "1"] +[3 = do "1 2 3"] +[ + a-value: make tag! "" + same? :a-value eval :a-value +] +[0:00 == eval 0:00] +[0.0.0 == eval 0.0.0] +[ + a-value: 'b-value + b-value: 1 + 1 == eval :a-value +] +; RETURN stops the evaluation +[ + f1: does [do [return 1 2] 2] + 1 = f1 +] +; THROW stops evaluation +[ + 1 = catch [ + do [ + throw 1 + 2 + ] + 2 + ] +] +; BREAK stops evaluation +[ + blank? loop 1 [ + do [ + break + 2 + ] + 2 + ] +] +; do/next block tests +[ + success: false + do/next [success: true success: false] 'b + success +] +[ + all [ + 1 = do/next [1 2] 'b + [2] = b + ] +] +[void? do/next [] 'b] +[error? do/next [try [1 / 0]] 'b] +[ + f1: does [do/next [return 1 2] 'b 2] + 1 = f1 +] +; recursive behaviour +[1 = do [do [1]]] +[1 = do "do [1]"] +[1 == 1] +[3 = eval :eval :add 1 2] +; infinite recursion for block +[ + blk: [do blk] + error? try blk +] +; infinite recursion for string +; bug#1896 +[ + str: "do str" + error? try [do str] +] +; infinite recursion for do/next +[ + blk: [do/next blk 'b] + error? try blk +] +[ + val1: try [do [1 / 0]] + val2: try [do/next [1 / 0] 'b] + val1/near = val2/near +] diff --git a/tests/control/either.test.reb b/tests/control/either.test.reb new file mode 100644 index 0000000000..188f2869d5 --- /dev/null +++ b/tests/control/either.test.reb @@ -0,0 +1,74 @@ +; functions/control/either.r +[ + either true [success: true] [success: false] + success +] +[ + either false [success: false] [success: true] + success +] +[1 = either true [1] [2]] +[2 = either false [1] [2]] + +[void? either* true [] [1]] +[void? either* false [1] []] + +[blank? either true [] [1]] +[blank? either false [1] []] + +[error? either true [try [1 / 0]] []] +[error? either false [] [try [1 / 0]]] + +; RETURN stops the evaluation +[ + f1: does [ + either true [return 1 2] [2] + 2 + ] + 1 = f1 +] +[ + f1: does [ + either false [2] [return 1 2] + 2 + ] + 1 = f1 +] +; THROW stops the evaluation +[ + 1 == catch [ + either true [throw 1 2] [2] + 2 + ] +] +[ + 1 == catch [ + either false [2] [throw 1 2] + 2 + ] +] +; BREAK stops the evaluation +[ + blank? loop 1 [ + either true [break 2] [2] + 2 + ] +] +[ + blank? loop 1 [ + either false [2] [break 2] + 2 + ] +] +; recursive behaviour +[2 = either true [either false [1] [2]] []] +[1 = either false [] [either true [1] [2]]] +; infinite recursion +[ + blk: [either true blk []] + error? try blk +] +[ + blk: [either false [] blk] + error? try blk +] diff --git a/tests/control/else.test.reb b/tests/control/else.test.reb new file mode 100644 index 0000000000..25e9db49b1 --- /dev/null +++ b/tests/control/else.test.reb @@ -0,0 +1,25 @@ +[ + success: + if 1 > 2 [success: false] else [success: true] + success +][ + success: + if 1 < 2 [success: true] else [success: false] + success +][ + success: + unless 1 > 2 [success: true] else [success: false] + success +][ + success: + unless 1 < 2 [success: false] else [success: true] + success +][ + success: + if true does [success: true] + success +][ + success: true + if false does [success: false] + success +] diff --git a/tests/control/exit.test.reb b/tests/control/exit.test.reb new file mode 100644 index 0000000000..4f99d6d1ac --- /dev/null +++ b/tests/control/exit.test.reb @@ -0,0 +1,22 @@ +; functions/control/exit.r +[ + success: true + f1: does [exit success: false] + f1 + success +] +[ + f1: does [exit] + void? f1 +] +; the "result" of exit should not be assignable, bug#1515 +[a: 1 eval does [a: exit] :a =? 1] +[a: 1 eval does [set 'a exit] :a =? 1] +[a: 1 eval does [set/opt 'a exit] :a =? 1] +; the "result" of exit should not be passable to functions, bug#1509 +[a: 1 eval does [a: error? exit] :a =? 1] +; bug#1535 +[eval does [words-of exit] true] +[eval does [values-of exit] true] +; bug#1945 +[eval does [spec-of exit] true] diff --git a/tests/control/for-each.test.reb b/tests/control/for-each.test.reb new file mode 100644 index 0000000000..d8aafcf463 --- /dev/null +++ b/tests/control/for-each.test.reb @@ -0,0 +1,75 @@ +; functions/control/for-each.r +[ + out: copy "" + str: "abcdef" + for-each i str [append out i] + out = str +] +[ + blk: [1 2 3 4] + sum: 0 + for-each i blk [sum: sum + i] + sum = 10 +] +; cycle return value +[ + blk: [1 2 3 4] + true = for-each i blk [true] +] +[ + blk: [1 2 3 4] + bar? for-each i blk [false] +] +; break cycle +[ + str: "abcdef" + for-each i str [ + num: i + if i = #"c" [break] + ] + num = #"c" +] +; break return value +[ + blk: [1 2 3 4] + blank? for-each i blk [break] +] +; continue cycle +[ + success: true + for-each i [1] [continue success: false] + success +] +; zero repetition +[ + success: true + blk: [] + for-each i blk [success: false] + success +] +; Test that return stops the loop +[ + blk: [1] + f1: does [for-each i blk [return 1 2]] + 1 = f1 +] +; Test that errors do not stop the loop and errors can be returned +[ + num: 0 + blk: [1 2] + e: for-each i blk [num: i try [1 / 0]] + all [error? e num = 2] +] +; "recursive safety", "locality" and "body constantness" test in one +[for-each i [1] b: [not same? 'i b/3]] +; recursivity +[ + num: 0 + for-each i [1 2 3 4 5] [ + for-each i [1 2] [num: num + 1] + ] + num = 10 +] +[ + error? trap [for-each [:x] [] []] +] diff --git a/tests/control/for.test.reb b/tests/control/for.test.reb new file mode 100644 index 0000000000..2088eeb970 --- /dev/null +++ b/tests/control/for.test.reb @@ -0,0 +1,188 @@ +; functions/control/for.r +[ + success: true + num: 0 + for i 1 10 1 [ + num: num + 1 + success: (i = num) and* success + ] + (10 = num) and* success +] +; cycle return value +[bar? for i 1 1 1 [false]] +; break cycle +[ + num: 0 + for i 1 10 1 [num: i break] + num = 1 +] +; break return value +[blank? for i 1 10 1 [break]] + +; continue cycle +[ + #58 + success: true + for i 1 1 1 [continue success: false] + success +] +[ + success: true + x: "a" + for i x tail x 1 [continue success: false] + success +] +; string! test +[ + out: copy "" + for i s: "abc" back tail s 1 [append out i] + out = "abcbcc" +] +; block! test +[ + out: copy [] + for i b: [1 2 3] back tail b 1 [append out i] + out = [1 2 3 2 3 3] +] +; zero repetition +[ + success: true + for i 1 0 1 [success: false] + success +] +; zero repetition block test +[ + success: true + for i b: [1] tail :b -1 [success: false] + success +] +; Test that return stops the loop +[ + f1: does [for i 1 1 1 [return 1 2] 2] + 1 = f1 +] +; Test that errors do not stop the loop and errors can be returned +[ + num: 0 + e: for i 1 2 1 [num: i try [1 / 0]] + all [error? e num = 2] +] +; infinite loop tests +[ + num: 0 + for i b: [1] tail b 1 [ + num: num + 1 + if num > 2 [break] + ] + num <= 2 +] +[ + num: 0 + for i 2147483647 2147483647 1 [ + num: num + 1 + either num > 1 [break] [true] + ] +] +[ + num: 0 + for i -2147483648 -2147483648 -1 [ + num: num + 1 + either num > 1 [break] [true] + ] +] +; bug#1136 +#64bit +[ + num: 0 + for i 9223372036854775807 9223372036854775807 -9223372036854775808 [ + num: num + 1 + if num <> 1 [break] + true + ] +] +#64bit +[ + num: 0 + for i -9223372036854775808 -9223372036854775808 9223372036854775807 [ + num: num + 1 + if num <> 1 [break] + true + ] +] +[ + num: 0 + for i 2147483647 2147483647 2147483647 [ + num: num + 1 + if num <> 1 [break] + true + ] +] +[ + num: 0 + for i 2147483647 2147483647 -2147483648 [ + num: num + 1 + if num <> 1 [break] + true + ] +] +[ + num: 0 + for i -2147483648 -2147483648 2147483647 [ + num: num + 1 + if num <> 1 [break] + true + ] +] +[ + num: 0 + for i -2147483648 -2147483648 -2147483648 [ + num: num + 1 + if num <> 1 [break] + true + ] +] +; bug#1993 +[equal? type-of for i -1 -2 0 [break] type-of for i 2 1 0 [break]] +; skip before head test +[[] = for i b: tail [1] head b -2 [i]] +; "recursive safety", "locality" and "body constantness" test in one +[for i 1 1 1 b: [not same? 'i b/3]] +; recursivity +[ + num: 0 + for i 1 5 1 [ + for i 1 2 1 [num: num + 1] + ] + num = 10 +] +; infinite recursion +[ + blk: [for i 1 1 1 blk] + error? try blk +] +; local variable changeability - this is how it works in R3 +[ + test: false + blank? for i 1 3 1 [ + if i = 2 [ + if test [break] + test: true + i: 1 + ] + ] +] +; local variable type safety +[ + test: false + error? try [ + for i 1 2 [ + either test [i == 2] [ + test: true + i: false + ] + ] + ] +] +; FOR should not bind 'self +; bug#1529 +[same? 'self for i 1 1 1 ['self]] diff --git a/tests/control/forall.test.reb b/tests/control/forall.test.reb new file mode 100644 index 0000000000..f8c04becf9 --- /dev/null +++ b/tests/control/forall.test.reb @@ -0,0 +1,79 @@ +; functions/control/for-next.r +[ + str: "abcdef" + out: copy "" + for-next str [append out first str] + all [ + head? str + out = head str + ] +] +[ + blk: [1 2 3 4] + sum: 0 + for-next blk [sum: sum + first blk] + sum = 10 +] +; cycle return value +[ + blk: [1 2 3 4] + true = for-next blk [true] +] +[ + blk: [1 2 3 4] + bar? for-next blk [false] +] +; break cycle +[ + str: "abcdef" + for-next str [if #"c" = char: str/1 [break]] + char = #"c" +] +; break return value +[ + blk: [1 2 3 4] + blank? for-next blk [break] +] +; continue cycle +[ + success: true + x: "a" + for-next x [continue success: false] + success +] +; zero repetition +[ + success: true + blk: [] + for-next blk [success: false] + success +] +; Test that return stops the loop +[ + blk: [1] + f1: does [for-next blk [return 1 2]] + 1 = f1 +] +; Test that errors do not stop the loop and errors can be returned +[ + num: 0 + blk: [1 2] + e: for-next blk [num: first blk try [1 / 0]] + all [error? e num = 2] +] +; recursivity +[ + num: 0 + blk1: [1 2 3 4 5] + blk2: [6 7] + for-next blk1 [ + num: num + first blk1 + for-next blk2 [num: num + first blk2] + ] + num = 80 +] +; bug#81 +[ + blk: [1] + 1 == for-next blk [blk/1] +] diff --git a/tests/control/forever.test.reb b/tests/control/forever.test.reb new file mode 100644 index 0000000000..c59e5157a1 --- /dev/null +++ b/tests/control/forever.test.reb @@ -0,0 +1,51 @@ +; functions/control/forever.r +[ + num: 0 + forever [ + num: num + 1 + if num = 10 [break] + ] + num = 10 +] +; Test break and continue +[blank? forever [break]] +[ + success: true + cycle?: true + forever [if cycle? [cycle?: false continue success: false] break] + success +] +; Test that return stops the loop +[ + f1: does [forever [return 1]] + 1 = f1 +] +; Test that exit stops the loop +[void? eval does [forever [exit]]] +; Test that errors do not stop the loop and errors can be returned +[ + num: 0 + e: _ + forever [ + num: num + 1 + if num = 10 [e: try [1 / 0] break] + try [1 / 0] + ] + all [error? e | num = 10] +] +; Recursion check +[ + num1: 0 + num3: 0 + forever [ + if num1 = 5 [break] + num2: 0 + forever [ + if num2 = 2 [break] + num3: num3 + 1 + num2: num2 + 1 + ] + num1: num1 + 1 + ] + 10 = num3 +] diff --git a/tests/control/forskip.test.reb b/tests/control/forskip.test.reb new file mode 100644 index 0000000000..8f43d69a1f --- /dev/null +++ b/tests/control/forskip.test.reb @@ -0,0 +1,67 @@ +; functions/control/for-skip.r +[ + blk: copy out: copy [] + for i 1 25 1 [append blk i] + for-skip blk 3 [append out blk/1] + out = [1 4 7 10 13 16 19 22 25] +] +; cycle return value +[ + blk: [1 2 3 4] + true = for-skip blk 1 [true] +] +[ + blk: [1 2 3 4] + bar? for-skip blk 1 [false] +] +; break cycle +[ + str: "abcdef" + for-skip str 2 [ + if #"c" = char: str/1 [break] + ] + char = #"c" +] +; break return value +[ + blk: [1 2 3 4] + blank? for-skip blk 2 [break] +] +; continue cycle +[ + success: true + x: "a" + for-skip x 1 [continue success: false] + success +] +; zero repetition +[ + success: true + blk: [] + for-skip blk 1 [success: false] + success +] +; Test that return stops the loop +[ + blk: [1] + f1: does [for-skip blk 2 [return 1 2]] + 1 = f1 +] +; Test that errors do not stop the loop and errors can be returned +[ + num: 0 + blk: [1 2] + e: for-skip blk 1 [num: first blk try [1 / 0]] + all [error? e num = 2] +] +; recursivity +[ + num: 0 + blk1: [1 2 3 4 5] + blk2: [6 7] + for-skip blk1 1 [ + num: num + first blk1 + for-skip blk2 1 [num: num + first blk2] + ] + num = 80 +] diff --git a/tests/control/halt.test.reb b/tests/control/halt.test.reb new file mode 100644 index 0000000000..e45cd2e3a9 --- /dev/null +++ b/tests/control/halt.test.reb @@ -0,0 +1,2 @@ +; functions/control/halt.r +[function? :halt] diff --git a/tests/control/if.test.reb b/tests/control/if.test.reb new file mode 100644 index 0000000000..9d42922c14 --- /dev/null +++ b/tests/control/if.test.reb @@ -0,0 +1,90 @@ +; functions/control/if.r +[ + success: false + if true [success: true] + success +] +[ + success: true + if false [success: false] + success +] +[1 = if true [1]] + +[void? if* true []] +[blank? if true []] + +[error? if true [try [1 / 0]]] +; RETURN stops the evaluation +[ + f1: does [ + if true [return 1 2] + 2 + ] + 1 = f1 +] +; condition datatype tests; action +[if get 'abs [true]] +; binary +[if #{00} [true]] +; bitset +[if make bitset! "" [true]] +; literal blocks illegal as condition in Ren-C, but evaluation products ok +[error? trap [if [] [true]]] +[if ([]) [true]] +; datatype +[if blank! [true]] +; typeset +[if any-number! [true]] +; date +[if 1/1/0000 [true]] +; decimal +[if 0.0 [true]] +[if 1.0 [true]] +[if -1.0 [true]] +; email +[if me@rt.com [true]] +[if %"" [true]] +[if does [] [true]] +[if first [:first] [true]] +[if #"^@" [true]] +[if make image! 0x0 [true]] +; integer +[if 0 [true]] +[if 1 [true]] +[if -1 [true]] +[if #a [true]] +[if first ['a/b] [true]] +[if first ['a] [true]] +[if true [true]] +[void? if false [true]] +[if $1 [true]] +[if :type-of [true]] +[void? if blank [true]] +[if make object! [] [true]] +[if get '+ [true]] +[if 0x0 [true]] +[if first [()] [true]] +[if 'a/b [true]] +[if make port! http:// [true]] +[if /a [true]] +[if first [a/b:] [true]] +[if first [a:] [true]] +[if "" [true]] +[if to tag! "" [true]] +[if 0:00 [true]] +[if 0.0.0 [true]] +[if http:// [true]] +[if 'a [true]] + +; recursive behaviour + +[blank? if true [if false [1]]] +[void? if* true [if* false [1]]] +[1 = if true [if true [1]]] + +; infinite recursion +[ + blk: [if true blk] + error? try blk +] diff --git a/tests/control/loop.test.reb b/tests/control/loop.test.reb new file mode 100644 index 0000000000..ee1fad67a2 --- /dev/null +++ b/tests/control/loop.test.reb @@ -0,0 +1,69 @@ +; functions/control/loop.r +[ + num: 0 + loop 10 [num: num + 1] + 10 = num +] +; cycle return value +[bar? loop 1 [false]] +; break cycle +[ + num: 0 + loop 10 [num: num + 1 break] + num = 1 +] +; break return value +[blank? loop 10 [break]] +; continue cycle +[ + success: true + loop 1 [continue success: false] + success +] +; zero repetition +[ + success: true + loop 0 [success: false] + success +] +[ + success: true + loop -1 [success: false] + success +] +; Test that return stops the loop +[ + f1: does [loop 1 [return 1 2]] + 1 = f1 +] +; Test that errors do not stop the loop and errors can be returned +[ + num: 0 + e: loop 2 [num: num + 1 try [1 / 0]] + all [error? e num = 2] +] +; loop recursivity +[ + num: 0 + loop 5 [ + loop 2 [num: num + 1] + ] + num = 10 +] +; recursive use of 'break +[ + f: func [x] [ + loop 1 [ + either x = 1 [ + use [break] [ + break: 1 + f 2 + 1 = get/opt 'break + ] + ][ + false + ] + ] + ] + f 1 +] diff --git a/tests/control/map-each.test.reb b/tests/control/map-each.test.reb new file mode 100644 index 0000000000..fbfcdbb87e --- /dev/null +++ b/tests/control/map-each.test.reb @@ -0,0 +1,5 @@ +; functions/control/map-each.r +; "return bug" +[ + integer? eval does [map-each v [] [] 1] +] diff --git a/tests/control/quit.test.reb b/tests/control/quit.test.reb new file mode 100644 index 0000000000..28762dbe2b --- /dev/null +++ b/tests/control/quit.test.reb @@ -0,0 +1,28 @@ +; functions/control/quit.r +; In R3, DO of a script provided as a string! code catches QUIT, just as it +; would do for scripts in files. + +[42 = do "quit/with 42"] + +[99 = do {do {quit/with 42} 99}] + +; Returning of Rebol values from called to calling script via QUIT/return. +[ + do-script-returning: func [value /local script] [ + save/header script: %tmp-inner.reb compose ['quit/return (value)] [] + do script + ] + all map-each value reduce [ + 42 + {foo} + #{CAFE} + blank + http://somewhere + 1900-01-30 + context [x: 42] + ] [ + value = do-script-returning value + ] +] + +[#2190 | error? try [catch/quit [attempt [quit]] 1 / 0]] diff --git a/tests/control/reduce.test.reb b/tests/control/reduce.test.reb new file mode 100644 index 0000000000..2d8a5ca884 --- /dev/null +++ b/tests/control/reduce.test.reb @@ -0,0 +1,34 @@ +; functions/control/reduce.r +[[1 2] = reduce [1 1 + 1]] +[ + success: false + reduce [success: true] + success +] +[[] = reduce []] +[error? try [first reduce [()]]] +["1 + 1" = reduce "1 + 1"] +[error? first reduce [try [1 / 0]]] +; unwind functions should stop evaluation, bug#1760 +[blank? loop 1 [reduce [break]]] +[bar? loop 1 [reduce [continue]]] +[1 = catch [reduce [throw 1]]] +[1 = catch/name [reduce [throw/name 1 'a]] 'a] +[1 = eval does [reduce [return 1 2] 2]] +[void? if 1 < 2 [eval does [reduce [exit/from :if 1] 2]]] +; recursive behaviour +[1 = first reduce [first reduce [1]]] +; infinite recursion +[ + blk: [reduce blk] + error? try blk +] + +; Quick flatten test, here for now +[ + [a b c d e f] = flatten [[a] [b] c d [e f]] +][ + [a b [c d] c d e f] = flatten [[a] [b [c d]] c d [e f]] +][ + [a b c d c d e f] = flatten/deep [[a] [b [c d]] c d [e f]] +] diff --git a/tests/control/remove-each.test.reb b/tests/control/remove-each.test.reb new file mode 100644 index 0000000000..813fbfe638 --- /dev/null +++ b/tests/control/remove-each.test.reb @@ -0,0 +1,9 @@ +; functions/control/remove-each.r +[ + remove-each i s: [1 2] [true] + empty? s +] +[ + remove-each i s: [1 2] [false] + [1 2] = s +] diff --git a/tests/control/repeat.test.reb b/tests/control/repeat.test.reb new file mode 100644 index 0000000000..33cf4acbf6 --- /dev/null +++ b/tests/control/repeat.test.reb @@ -0,0 +1,98 @@ +; functions/control/repeat.r +[ + success: true + num: 0 + repeat i 10 [ + num: num + 1 + success: (i = num) and* success + ] + (10 = num) and* success +] +; cycle return value +[bar? repeat i 1 [false]] +; break cycle +[ + num: 0 + repeat i 10 [num: i break] + num = 1 +] +; break return value +[blank? repeat i 10 [break]] +; continue cycle +[ + success: true + repeat i 1 [continue success: false] + success +] +[ + success: true + repeat i "a" [continue success: false] + success +] +[ + success: true + repeat i [a] [continue success: false] + success +] +; decimal! test +[[1 2 3] == collect [repeat i 3.0 [keep i]]] +[[1 2 3] == collect [repeat i 3.1 [keep i]]] +[[1 2 3] == collect [repeat i 3.5 [keep i]]] +[[1 2 3] == collect [repeat i 3.9 [keep i]]] +; string! test +[ + out: copy "" + repeat i "abc" [append out i] + out = "abcbcc" +] +; block! test +[ + out: copy [] + repeat i [1 2 3] [append out i] + out = [1 2 3 2 3 3] +] +; TODO: is hash! test and list! test needed too? +; zero repetition +[ + success: true + repeat i 0 [success: false] + success +] +[ + success: true + repeat i -1 [success: false] + success +] +; Test that return stops the loop +[ + f1: does [repeat i 1 [return 1 2]] + 1 = f1 +] +; Test that errors do not stop the loop and errors can be returned +[ + num: 0 + e: repeat i 2 [num: i try [1 / 0]] + all [error? e num = 2] +] +; "recursive safety", "locality" and "body constantness" test in one +[repeat i 1 b: [not same? 'i b/3]] +; recursivity +[ + num: 0 + repeat i 5 [ + repeat i 2 [num: num + 1] + ] + num = 10 +] +; local variable type safety +[ + test: false + error? try [ + repeat i 2 [ + either test [i == 2] [ + test: true + i: false + ] + ] + ] +] diff --git a/tests/control/return.test.reb b/tests/control/return.test.reb new file mode 100644 index 0000000000..d648b0bf9a --- /dev/null +++ b/tests/control/return.test.reb @@ -0,0 +1,33 @@ +; functions/control/return.r +[ + f1: does [return 1 2] + 1 = f1 +] +[ + success: true + f1: does [return 1 success: false] + f1 + success +] +; return value tests +[ + f1: does [return ()] + void? f1 +] +[ + f1: does [return try [1 / 0]] + error? f1 +] +; the "result" of return should not be assignable, bug#1515 +[a: 1 eval does [a: return 2] :a =? 1] +[a: 1 eval does [set 'a return 2] :a =? 1] +[a: 1 eval does [set/opt 'a return 2] :a =? 1] +; the "result" of return should not be passable to functions, bug#1509 +[a: 1 eval does [a: error? return 2] :a =? 1] +; bug#1535 +[eval does [words-of return blank] true] +[eval does [values-of return blank] true] +; bug#1945 +[eval does [spec-of return blank] true] +; return should not be caught by try +[a: 1 eval does [a: error? try [return 2]] :a =? 1] diff --git a/tests/control/switch.test.reb b/tests/control/switch.test.reb new file mode 100644 index 0000000000..21209a38ff --- /dev/null +++ b/tests/control/switch.test.reb @@ -0,0 +1,30 @@ +; functions/control/switch.r +[ + 11 = switch 1 [ + 1 [11] + 2 [12] + ] +] +[ + 12 = switch 2 [ + 1 [11] + 2 [12] + ] +] + +[void? switch* 1 [1 []]] +[blank? switch 1 [1 []]] + +[ + cases: reduce [1 head insert copy [] try [1 / 0]] + error? switch 1 cases +] + +[ + #2242 + 11 = eval does [switch/all 1 [1 [return 11 88]] 99] +] + +[t: 1 | 1 = switch t [(t)]] +[1 = switch/default 1 [] 1] + diff --git a/tests/control/throw.test.reb b/tests/control/throw.test.reb new file mode 100644 index 0000000000..bfb49dbee9 --- /dev/null +++ b/tests/control/throw.test.reb @@ -0,0 +1,20 @@ +; functions/control/throw.r +; see functions/control/catch.r for basic functionality +; the "result" of throw should not be assignable, bug#1515 +[a: 1 catch [a: throw 2] :a =? 1] +[a: 1 catch [set 'a throw 2] :a =? 1] +[a: 1 catch [set/opt 'a throw 2] :a =? 1] +[a: 1 catch/name [a: throw/name 2 'b] 'b :a =? 1] +[a: 1 catch/name [set 'a throw/name 2 'b] 'b :a =? 1] +[a: 1 catch/name [set/opt 'a throw/name 2 'b] 'b :a =? 1] +; the "result" of throw should not be passable to functions, bug#1509 +[a: 1 catch [a: error? throw 2] :a =? 1] +; bug#1535 +[catch [words-of throw blank] true] +[catch [values-of throw blank] true] +; bug#1945 +[catch [spec-of throw blank] true] +[a: 1 catch/name [a: error? throw/name 2 'b] 'b :a =? 1] +; throw should not be caught by try +[a: 1 catch [a: error? try [throw 2]] :a =? 1] +[a: 1 catch/name [a: error? try [throw/name 2 'b]] 'b :a =? 1] diff --git a/tests/control/try.test.reb b/tests/control/try.test.reb new file mode 100644 index 0000000000..24449e6cae --- /dev/null +++ b/tests/control/try.test.reb @@ -0,0 +1,31 @@ +; functions/control/try.r +[ + e: try [1 / 0] + e/id = 'zero-divide +] +[ + success: true + error? try [ + 1 / 0 + success: false + ] + success +] +[ + success: true + f1: does [ + 1 / 0 + success: false + ] + error? try [f1] + success +] +; testing TRY/EXCEPT +; bug#822 +[error? try/except [make error! ""] [0]] +[try/except [fail make error! ""] [true]] +[try/except [1 / 0] :error?] +[try/except [1 / 0] func [e] [error? e]] +[try/except [true] func [e] [false]] +; bug#1514 +[error? try [try/except [1 / 0] :add]] diff --git a/tests/control/unless.test.reb b/tests/control/unless.test.reb new file mode 100644 index 0000000000..0f4279e4a7 --- /dev/null +++ b/tests/control/unless.test.reb @@ -0,0 +1,28 @@ +; functions/control/unless.r +[ + success: false + unless false [success: true] + success +] +[ + success: true + unless true [success: false] + success +] +[1 = unless false [1]] + +[void? unless* true [1]] +[void? unless* false []] +[void? unless true [1]] +[blank? unless false []] + +[error? unless false [try [1 / 0]]] + +; RETURN stops the evaluation +[ + f1: does [ + unless false [return 1 2] + 2 + ] + 1 = f1 +] diff --git a/tests/control/until.test.reb b/tests/control/until.test.reb new file mode 100644 index 0000000000..e677a6526c --- /dev/null +++ b/tests/control/until.test.reb @@ -0,0 +1,38 @@ +; functions/control/until.r +[ + num: 0 + loop-until [num: num + 1 num > 9] + num = 10 +] +; Test body-block return values +[1 = loop-until [1]] +; Test break +[blank? loop-until [break true]] +; Test continue +[ + success: true + cycle?: true + loop-until [if cycle? [cycle?: false continue success: false] true] + success +] +; Test that return stops the loop +[ + f1: does [loop-until [return 1]] + 1 = f1 +] +; Test that errors do not stop the loop +[1 = loop-until [try [1 / 0] 1]] +; Recursion check +[ + num1: 0 + num3: 0 + loop-until [ + num2: 0 + loop-until [ + num3: num3 + 1 + 1 < (num2: num2 + 1) + ] + 4 < (num1: num1 + 1) + ] + 10 = num3 +] diff --git a/tests/control/wait.test.reb b/tests/control/wait.test.reb new file mode 100644 index 0000000000..197e57e35b --- /dev/null +++ b/tests/control/wait.test.reb @@ -0,0 +1,3 @@ +; functions/control/wait.r +; bug#5 +[wait 0:0:0.3 true] diff --git a/tests/control/while.test.reb b/tests/control/while.test.reb new file mode 100644 index 0000000000..e673b8c9b7 --- /dev/null +++ b/tests/control/while.test.reb @@ -0,0 +1,113 @@ +; functions/control/while.r +[ + num: 0 + while [num < 10] [num: num + 1] + num = 10 +] +; bug#37 +; Test body-block return values +[ + num: 0 + 1 = while [num < 1] [num: num + 1] +] +[void? while [false] []] +; zero repetition +[ + success: true + while [false] [success: false] + success +] +; Test break and continue +[cycle?: true blank? while [cycle?] [break cycle?: false]] +; Test reactions to break and continue in the condition +[ + was-stopped: true + while [true] [ + while [break] [] + was-stopped: false + break + ] + was-stopped +] +[ + first-time: true + was-continued: false + while [true] [ + unless first-time [ + was-continued: true + break + ] + first-time: false + while [continue] [break] + break + ] + was-continued +] +[ + success: true + cycle?: true + while [cycle?] [cycle?: false continue success: false] + success +] +[ + num: 0 + while [true] [num: 1 break num: 2] + num = 1 +] +; RETURN should stop the loop +[ + cycle?: true + f1: does [while [cycle?] [cycle?: false return 1] 2] + 1 = f1 +] +[ ; bug#1519 + cycle?: true + f1: does [while [if cycle? [return 1] cycle?] [cycle?: false 2]] + 1 = f1 +] +; EXIT/FROM the IF should stop the loop +[ + cycle?: true + f1: does [if 1 < 2 [while [cycle?] [cycle?: false exit/from :if] 2]] + void? f1 +] +[ ; bug#1519 + cycle?: true + f1: does [ + unless 1 > 2 [ + while [if cycle? [exit/from :unless] cycle?] [cycle?: false 2] + ] + ] + void? f1 +] +; THROW should stop the loop +[1 = catch [cycle?: true while [cycle?] [throw 1 cycle?: false]]] +[ ; bug#1519 + cycle?: true + 1 = catch [while [if cycle? [throw 1] false] [cycle?: false]] +] +[1 = catch/name [cycle?: true while [cycle?] [throw/name 1 'a cycle?: false]] 'a] +[ ; bug#1519 + cycle?: true + 1 = catch/name [while [if cycle? [throw/name 1 'a] false] [cycle?: false]] 'a +] +; Test that disarmed errors do not stop the loop and errors can be returned +[ + num: 0 + e: while [num < 10] [num: num + 1 try [1 / 0]] + all [error? e num = 10] +] +; Recursion check +[ + num1: 0 + num3: 0 + while [num1 < 5] [ + num2: 0 + while [num2 < 2] [ + num3: num3 + 1 + num2: num2 + 1 + ] + num1: num1 + 1 + ] + 10 = num3 +] diff --git a/tests/convert/as-binary.test.reb b/tests/convert/as-binary.test.reb new file mode 100644 index 0000000000..9df894d351 --- /dev/null +++ b/tests/convert/as-binary.test.reb @@ -0,0 +1 @@ +; functions/convert/as-binary.r diff --git a/tests/convert/as-string.test.reb b/tests/convert/as-string.test.reb new file mode 100644 index 0000000000..e8ba297524 --- /dev/null +++ b/tests/convert/as-string.test.reb @@ -0,0 +1 @@ +; functions/convert/as-string.r diff --git a/tests/convert/encode.test.reb b/tests/convert/encode.test.reb new file mode 100644 index 0000000000..2fe09295a9 --- /dev/null +++ b/tests/convert/encode.test.reb @@ -0,0 +1,4 @@ +; functions/convert/encode.r +[binary? encode 'bmp make image! 10x20] +; bug#2040 +[binary? encode 'png make image! 10x20] diff --git a/tests/convert/load.test.reb b/tests/convert/load.test.reb new file mode 100644 index 0000000000..7e57f0d335 --- /dev/null +++ b/tests/convert/load.test.reb @@ -0,0 +1,28 @@ +; functions/convert/load.r +; bug#20 +[block? load/all "1"] +; bug#22a +[error? try [load "':a"]] +; bug#22b +[error? try [load "':a:"]] +; bug#858 +[ + a: [ < ] + a = load mold a +] +[error? try [load "1xyz#"]] +; load/next +[error? try [load/next "1"]] +; bug#1122 +[ + any [ + error? try [load "9999999999999999999"] + greater? load "9999999999999999999" load "9223372036854775807" + ] +] +; R2 bug +[ + x: 1 + error? try [x: load/header ""] + not error? x +] diff --git a/tests/convert/mold.test.reb b/tests/convert/mold.test.reb new file mode 100644 index 0000000000..72d3895077 --- /dev/null +++ b/tests/convert/mold.test.reb @@ -0,0 +1,41 @@ +; functions/convert/mold.r +; bug#860 +; bug#6 +; cyclic block +[ + a: copy [] + insert/only a a + string? mold a +] +; cyclic paren +[ + a: first [()] + insert/only a a + string? mold a +] +; cyclic object +; bug#69 +[ + a: make object! [a: self] + string? mold a +] +; deep nested block mold +; bug#876 +[ + n: 1 + catch [forever [ + a: copy [] + if error? try [ + loop n [a: append/only copy [] a] + mold a + ] [throw true] + n: n * 2 + ]] +] +[#719 | "()" = mold quote ()] + +[#77 | "#[block! [[1 2] 2]]" == mold/all next [1 2]] +[#77 | blank? find mold/flat make object! [a: 1] " "] + +[#84 | equal? mold make bitset! "^(00)" "make bitset! #{80}"] +[#84 | equal? mold/all make bitset! "^(00)" "#[bitset! #{80}]"] diff --git a/tests/convert/to-hex.test.reb b/tests/convert/to-hex.test.reb new file mode 100644 index 0000000000..86d7916a80 --- /dev/null +++ b/tests/convert/to-hex.test.reb @@ -0,0 +1,3 @@ +; functions/convert/to-hex.r +; bug#43 +[#FFFFFFFE = to-hex/size -2 8] diff --git a/tests/convert/to.test.reb b/tests/convert/to.test.reb new file mode 100644 index 0000000000..5ae6986633 --- /dev/null +++ b/tests/convert/to.test.reb @@ -0,0 +1,7 @@ +; functions/convert/to.r +; bug#38 +['logic! = to word! logic!] +['percent! = to word! percent!] +['money! = to word! money!] +; bug#1967 +[not same? to binary! [1] to binary! [2]] diff --git a/tests/core-tests.r b/tests/core-tests.r new file mode 100644 index 0000000000..a7b8ae5829 --- /dev/null +++ b/tests/core-tests.r @@ -0,0 +1,217 @@ +; Rebol [] +; ***************************************************************************** +; Title: Rebol core tests +; Copyright: +; 2012 REBOL Technologies +; 2013 Saphirion AG +; Author: +; Carl Sassenrath, Ladislav Mecir, Andreas Bolka, Brian Hawley, John K +; License: +; Licensed under the Apache License, Version 2.0 (the "License"); +; you may not use this file except in compliance with the License. +; You may obtain a copy of the License at +; +; http://www.apache.org/licenses/LICENSE-2.0 +; +; Unless required by applicable law or agreed to in writing, software +; distributed under the License is distributed on an "AS IS" BASIS, +; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +; See the License for the specific language governing permissions and +; limitations under the License. +; ***************************************************************************** +%parse-tests.r + +%datatypes/action.test.reb +%datatypes/binary.test.reb +%datatypes/bitset.test.reb +%datatypes/block.test.reb +%datatypes/char.test.reb +%datatypes/closure.test.reb +%datatypes/datatype.test.reb +%datatypes/date.test.reb +%datatypes/decimal.test.reb +%datatypes/email.test.reb +%datatypes/error.test.reb +%datatypes/event.test.reb +%datatypes/file.test.reb +%datatypes/function.test.reb +%datatypes/get-path.test.reb +%datatypes/get-word.test.reb +%datatypes/gob.test.reb +%datatypes/hash.test.reb +%datatypes/image.test.reb +%datatypes/integer.test.reb +%datatypes/issue.test.reb +%datatypes/list.test.reb +%datatypes/lit-path.test.reb +%datatypes/lit-word.test.reb +%datatypes/logic.test.reb +%datatypes/map.test.reb +%datatypes/module.test.reb +%datatypes/money.test.reb +%datatypes/native.test.reb +%datatypes/none.test.reb +%datatypes/object.test.reb +%datatypes/op.test.reb +%datatypes/pair.test.reb +%datatypes/paren.test.reb +%datatypes/path.test.reb +%datatypes/percent.test.reb +%datatypes/port.test.reb +%datatypes/refinement.test.reb +%datatypes/set-path.test.reb +%datatypes/set-word.test.reb +%datatypes/string.test.reb +%datatypes/symbol.test.reb +%datatypes/time.test.reb +%datatypes/tuple.test.reb +%datatypes/typeset.test.reb +%datatypes/unset.test.reb +%datatypes/url.test.reb +%datatypes/varargs.test.reb +%datatypes/vector.test.reb +%datatypes/word.test.reb +%comparison/lesserq.test.reb +%comparison/maximum-of.test.reb +%comparison/equalq.test.reb +%comparison/sameq.test.reb +%comparison/strict-equalq.test.reb +%comparison/strict-not-equalq.test.reb +%context/bind.test.reb +%context/boundq.test.reb +%context/bindq.test.reb +%context/resolve.test.reb +%context/set.test.reb +%context/unset.test.reb +%context/use.test.reb +%context/valueq.test.reb +%control/all.test.reb +%control/any.test.reb +%control/apply.test.reb +%control/attempt.test.reb +%control/break.test.reb +%control/case.test.reb +%control/catch.test.reb +%control/compose.test.reb +%control/continue.test.reb +%control/default.test.reb +%control/disarm.test.reb +%control/do.test.reb +%control/either.test.reb +%control/else.test.reb +%control/exit.test.reb +%control/for.test.reb +%control/forall.test.reb +%control/for-each.test.reb +%control/forever.test.reb +%control/forskip.test.reb +%control/halt.test.reb +%control/if.test.reb +%control/loop.test.reb +%control/map-each.test.reb +%control/reduce.test.reb +%control/remove-each.test.reb +%control/repeat.test.reb +%control/return.test.reb +%control/switch.test.reb +%control/throw.test.reb +%control/try.test.reb +%control/unless.test.reb +%control/until.test.reb +%control/wait.test.reb +%control/while.test.reb +%control/quit.test.reb +%convert/as-binary.test.reb +%convert/as-string.test.reb +%convert/encode.test.reb +%convert/load.test.reb +%convert/mold.test.reb +%convert/to.test.reb +%define/func.test.reb +%convert/to-hex.test.reb +%file/clean-path.test.reb +%file/existsq.test.reb +%file/make-dir.test.reb +%file/open.test.reb +%file/file-typeq.test.reb +%functions/adapt.test.reb +%functions/apply.test.reb +%functions/chain.test.reb +%functions/hijack.test.reb +%functions/specialize.test.reb +%math/absolute.test.reb +%math/add.test.reb +%math/and.test.reb +%math/arcsine.test.reb +%math/arctangent.test.reb +%math/complement.test.reb +%math/cosine.test.reb +%math/difference.test.reb +%math/divide.test.reb +%math/evenq.test.reb +%math/exp.test.reb +%math/log-10.test.reb +%math/log-2.test.reb +%math/log-e.test.reb +%math/mod.test.reb +%math/modulo.test.reb +%math/multiply.test.reb +%math/negate.test.reb +%math/negativeq.test.reb +%math/not.test.reb +%math/oddq.test.reb +%math/positiveq.test.reb +%math/power.test.reb +%math/random.test.reb +%math/remainder.test.reb +%math/round.test.reb +%math/shift.test.reb +%math/signq.test.reb +%math/sine.test.reb +%math/square-root.test.reb +%math/subtract.test.reb +%math/tangent.test.reb +%math/zeroq.test.reb +%reflectors/body-of.test.reb +%secure/protect.test.reb +%secure/unprotect.test.reb +%series/append.test.reb +%series/at.test.reb +%series/back.test.reb +%series/change.test.reb +%series/clear.test.reb +%series/copy.test.reb +%series/difference.test.reb +%series/emptyq.test.reb +%series/exclude.test.reb +%series/find.test.reb +%series/indexq.test.reb +%series/insert.test.reb +%series/intersect.test.reb +%series/last.test.reb +%series/lengthq.test.reb +%series/next.test.reb +%series/ordinals.test.reb +%series/pick.test.reb +%series/poke.test.reb +%series/remove.test.reb +%series/reverse.test.reb +%series/select.test.reb +%series/skip.test.reb +%series/sort.test.reb +%series/split.test.reb +%series/tailq.test.reb +%series/trim.test.reb +%series/union.test.reb +%string/checksum.test.reb +%string/compress.test.reb +%string/decloak.test.reb +%string/decode.test.reb +%string/encode.test.reb +%string/decompress.test.reb +%string/dehex.test.reb +%system/system.test.reb +%system/file.test.reb +%system/gc.test.reb +%call/call.test.reb +%source/analysis.test.reb diff --git a/tests/datatypes/action.test.reb b/tests/datatypes/action.test.reb new file mode 100644 index 0000000000..76cfbaf027 --- /dev/null +++ b/tests/datatypes/action.test.reb @@ -0,0 +1,8 @@ +; datatypes/action.r + +[action? :abs] +[not action? 1] +[function! = type-of :abs] +; bug#1659 +; actions are active +[1 == do reduce [:abs -1]] diff --git a/tests/datatypes/binary.test.reb b/tests/datatypes/binary.test.reb new file mode 100644 index 0000000000..44b5fcf50d --- /dev/null +++ b/tests/datatypes/binary.test.reb @@ -0,0 +1,37 @@ +; datatypes/binary.r +[binary? #{00}] +[not binary? 1] +[binary! = type-of #{00}] +[ + system/options/binary-base: 2 + "2#{00000000}" == mold #{00} +] +[ + system/options/binary-base: 64 + "64#{AAAA}" == mold #{000000} +] +[ + system/options/binary-base: 16 + "#{00}" == mold #{00} +] +[#{00} == 2#{00000000}] +[#{000000} == 64#{AAAA}] +[#{} == make binary! 0] +[#{00} == to binary! "^(00)"] +; minimum +[binary? #{}] +; alternative literal representation +[#{} == #[binary! #{}]] +; access symmetry +[ + b: #{0b} + not error? try [b/1: b/1] +] +; bug#42 +[ + b: #{0b} + b/1 == 11 +] +; case sensitivity +; bug#1459 +[lesser? #{0141} #{0161}] diff --git a/tests/datatypes/bitset.test.reb b/tests/datatypes/bitset.test.reb new file mode 100644 index 0000000000..4d6f0722bb --- /dev/null +++ b/tests/datatypes/bitset.test.reb @@ -0,0 +1,8 @@ +; datatypes/bitset.r +[bitset? make bitset! "a"] +[not bitset? 1] +[bitset! = type-of make bitset! "a"] +; minimum, literal representation +[bitset? #[bitset! #{}]] +; TS crash +[bitset? charset reduce [to-char "^(A0)"]] diff --git a/tests/datatypes/block.test.reb b/tests/datatypes/block.test.reb new file mode 100644 index 0000000000..a135f24533 --- /dev/null +++ b/tests/datatypes/block.test.reb @@ -0,0 +1,11 @@ +; datatypes/block.r +[block? [1]] +[not block? 1] +[block! = type-of [1]] +; minimum +[block? []] +; alternative literal representation +[[] == #[block! [[] 1]]] +[[] == make block! 0] +[[] == to block! ""] +["[]" == mold []] diff --git a/tests/datatypes/char.test.reb b/tests/datatypes/char.test.reb new file mode 100644 index 0000000000..c63b6fe7ca --- /dev/null +++ b/tests/datatypes/char.test.reb @@ -0,0 +1,149 @@ +; datatypes/char.r +[char? #"a"] +[not char? 1] +[char! = type-of #"a"] +[#"^@" = #"^(00)"] +[#"^A" = #"^(01)"] +[#"^B" = #"^(02)"] +[#"^C" = #"^(03)"] +[#"^D" = #"^(04)"] +[#"^E" = #"^(05)"] +[#"^F" = #"^(06)"] +[#"^G" = #"^(07)"] +[#"^H" = #"^(08)"] +[#"^I" = #"^(09)"] +[#"^J" = #"^(0A)"] +[#"^K" = #"^(0B)"] +[#"^L" = #"^(0C)"] +[#"^M" = #"^(0D)"] +[#"^N" = #"^(0E)"] +[#"^O" = #"^(0F)"] +[#"^P" = #"^(10)"] +[#"^Q" = #"^(11)"] +[#"^R" = #"^(12)"] +[#"^S" = #"^(13)"] +[#"^T" = #"^(14)"] +[#"^U" = #"^(15)"] +[#"^V" = #"^(16)"] +[#"^W" = #"^(17)"] +[#"^X" = #"^(18)"] +[#"^Y" = #"^(19)"] +[#"^Z" = #"^(1A)"] +[#"^[" = #"^(1B)"] +[#"^\" = #"^(1C)"] +[#"^]" = #"^(1D)"] +[#"^!" = #"^(1E)"] +[#"^_" = #"^(1F)"] +[#" " = #"^(20)"] +[#"!" = #"^(21)"] +[#"^"" = #"^(22)"] +[#"#" = #"^(23)"] +[#"$" = #"^(24)"] +[#"%" = #"^(25)"] +[#"&" = #"^(26)"] +[#"'" = #"^(27)"] +[#"(" = #"^(28)"] +[#")" = #"^(29)"] +[#"*" = #"^(2A)"] +[#"+" = #"^(2B)"] +[#"," = #"^(2C)"] +[#"-" = #"^(2D)"] +[#"." = #"^(2E)"] +[#"/" = #"^(2F)"] +[#"0" = #"^(30)"] +[#"1" = #"^(31)"] +[#"2" = #"^(32)"] +[#"3" = #"^(33)"] +[#"4" = #"^(34)"] +[#"5" = #"^(35)"] +[#"6" = #"^(36)"] +[#"7" = #"^(37)"] +[#"8" = #"^(38)"] +[#"9" = #"^(39)"] +[#":" = #"^(3A)"] +[#";" = #"^(3B)"] +[#"<" = #"^(3C)"] +[#"=" = #"^(3D)"] +[#">" = #"^(3E)"] +[#"?" = #"^(3F)"] +[#"@" = #"^(40)"] +[#"A" = #"^(41)"] +[#"B" = #"^(42)"] +[#"C" = #"^(43)"] +[#"D" = #"^(44)"] +[#"E" = #"^(45)"] +[#"F" = #"^(46)"] +[#"G" = #"^(47)"] +[#"H" = #"^(48)"] +[#"I" = #"^(49)"] +[#"J" = #"^(4A)"] +[#"K" = #"^(4B)"] +[#"L" = #"^(4C)"] +[#"M" = #"^(4D)"] +[#"N" = #"^(4E)"] +[#"O" = #"^(4F)"] +[#"P" = #"^(50)"] +[#"Q" = #"^(51)"] +[#"R" = #"^(52)"] +[#"S" = #"^(53)"] +[#"T" = #"^(54)"] +[#"U" = #"^(55)"] +[#"V" = #"^(56)"] +[#"W" = #"^(57)"] +[#"X" = #"^(58)"] +[#"Y" = #"^(59)"] +[#"Z" = #"^(5A)"] +[#"[" = #"^(5B)"] +[#"\" = #"^(5C)"] +[#"]" = #"^(5D)"] +[#"^^" = #"^(5E)"] +[#"_" = #"^(5F)"] +[#"`" = #"^(60)"] +[#"a" = #"^(61)"] +[#"b" = #"^(62)"] +[#"c" = #"^(63)"] +[#"d" = #"^(64)"] +[#"e" = #"^(65)"] +[#"f" = #"^(66)"] +[#"g" = #"^(67)"] +[#"h" = #"^(68)"] +[#"i" = #"^(69)"] +[#"j" = #"^(6A)"] +[#"k" = #"^(6B)"] +[#"l" = #"^(6C)"] +[#"m" = #"^(6D)"] +[#"n" = #"^(6E)"] +[#"o" = #"^(6F)"] +[#"p" = #"^(70)"] +[#"q" = #"^(71)"] +[#"r" = #"^(72)"] +[#"s" = #"^(73)"] +[#"t" = #"^(74)"] +[#"u" = #"^(75)"] +[#"v" = #"^(76)"] +[#"w" = #"^(77)"] +[#"x" = #"^(78)"] +[#"y" = #"^(79)"] +[#"z" = #"^(7A)"] +[#"{" = #"^(7B)"] +[#"|" = #"^(7C)"] +[#"}" = #"^(7D)"] +[#"~" = #"^(7E)"] +[#"^~" = #"^(7F)"] +; alternatives +[#"^(null)" = #"^(00)"] +[#"^(line)" = #"^(0A)"] +[#"^/" = #"^(0A)"] +[#"^(tab)" = #"^(09)"] +[#"^-" = #"^(09)"] +[#"^(page)" = #"^(0C)"] +[#"^(esc)" = #"^(1B)"] +[#"^(back)" = #"^(08)"] +[#"^(del)" = #"^(7f)"] +[#"^(00)" = make char! 0] +[#"^(00)" = to char! 0] +[{#"a"} = mold #"a"] +; minimum +[char? #"^(00)"] +; maximum +[char? #"^(ff)"] diff --git a/tests/datatypes/closure.test.reb b/tests/datatypes/closure.test.reb new file mode 100644 index 0000000000..d4569ce42f --- /dev/null +++ b/tests/datatypes/closure.test.reb @@ -0,0 +1,290 @@ +; datatypes/closure.r +[closure? closure [] ["OK"]] +[not closure? 1] +[closure! = type-of closure [] ["OK"]] +; minimum +[closure? closure [] []] +; return-less return value tests +[ + f: closure [] [] + void? f +] +[ + f: closure [] [:abs] + :abs = f +] +[ + a-value: #{} + f: closure [] [a-value] + same? a-value f +] +[ + a-value: charset "" + f: closure [] [a-value] + same? a-value f +] +[ + a-value: [] + f: closure [] [a-value] + same? a-value f +] +[ + a-value: blank! + f: closure [] [a-value] + same? a-value f +] +[ + f: closure [] [1/Jan/0000] + 1/Jan/0000 = f +] +[ + f: closure [] [0.0] + 0.0 == f +] +[ + f: closure [] [1.0] + 1.0 == f +] +[ + a-value: me@here.com + f: closure [] [a-value] + same? a-value f +] +[ + f: closure [] [try [1 / 0]] + error? f +] +[ + a-value: %"" + f: closure [] [a-value] + same? a-value f +] +[ + a-value: does [] + f: closure [] [:a-value] + same? :a-value f +] +[ + a-value: first [:a] + f: closure [] [:a-value] + (same? :a-value f) and* (:a-value == f) +] +[ + f: closure [] [#"^@"] + #"^@" == f +] +[ + a-value: make image! 0x0 + f: closure [] [a-value] + same? a-value f +] +[ + f: closure [] [0] + 0 == f +] +[ + f: closure [] [1] + 1 == f +] +[ + f: closure [] [#a] + #a == f +] +[ + a-value: first ['a/b] + f: closure [] [:a-value] + :a-value == f +] +[ + a-value: first ['a] + f: closure [] [:a-value] + :a-value == f +] +[ + f: closure [] [true] + true = f +] +[ + f: closure [] [false] + false = f +] +[ + f: closure [] [$1] + $1 == f +] +[ + f: closure [] [:type-of] + same? :type-of f +] +[ + f: closure [] [_] + blank? f +] +[ + a-value: make object! [] + f: closure [] [:a-value] + same? :a-value f +] +[ + a-value: first [()] + f: closure [] [:a-value] + same? :a-value f +] +[ + f: closure [] [get '+] + same? get '+ f +] +[ + f: closure [] [0x0] + 0x0 == f +] +[ + a-value: 'a/b + f: closure [] [:a-value] + :a-value == f +] +[ + a-value: make port! http:// + f: closure [] [:a-value] + port? f +] +[ + f: closure [] [/a] + /a == f +] +[ + a-value: first [a/b:] + f: closure [] [:a-value] + :a-value == f +] +[ + a-value: first [a:] + f: closure [] [:a-value] + :a-value == all [:a-value] +] +[ + a-value: "" + f: closure [] [:a-value] + same? :a-value f +] +[ + a-value: make tag! "" + f: closure [] [:a-value] + same? :a-value f +] +[ + f: closure [] [0:00] + 0:00 == f +] +[ + f: closure [] [0.0.0] + 0.0.0 == f +] +[ + f: closure [] [()] + void? f +] +[ + f: closure [] ['a] + 'a == f +] +; basic test for recursive closure! invocation +[i: 0 countdown: closure [n] [if n > 0 [++ i countdown n - 1]] countdown 10 i = 10] +; bug#21 +[ + c: closure [a] [return a] + 1 == c 1 +] +; two-function return test +[ + g: closure [f [function!]] [f [return 1] 2] + 1 = g :do +] +; BREAK out of a closure +[ + blank? loop 1 [ + f: closure [] [break] + f + 2 + ] +] +; THROW out of a closure +[ + 1 = catch [ + f: closure [] [throw 1] + f + 2 + ] +] +; "error out" of a closure +[ + error? try [ + f: closure [] [1 / 0 2] + f + 2 + ] +] +; BREAK out leaves a "running" closure in a "clean" state +[ + 1 = loop 1 [ + f: closure [x] [ + either x = 1 [ + loop 1 [f 2] + x + ] [break] + ] + f 1 + ] +] +; THROW out leaves a "running" closure in a "clean" state +[ + 1 = catch [ + f: closure [x] [ + either x = 1 [ + catch [f 2] + x + ] [throw 1] + ] + f 1 + ] +] +; "error out" leaves a "running" closure in a "clean" state +[ + f: closure [x] [ + either x = 1 [ + error? try [f 2] + x = 1 + ] [1 / 0] + ] + f 1 +] +; bug#1659 +; inline closure test +[ + f: closure [] reduce [closure [] [true]] + f +] +; rebind test +[ + a: closure [b] [does [b]] + b: a 1 + c: a 2 + all [ + 1 = b + 2 = c + ] +] +; bug#447 +[slf: 'self eval closure [x] [same? slf 'self] 1] +; bug#1528 +[closure? closure [self] []] +[ + f: make closure! reduce [[x] f-body: [x + x]] + change f-body 'x ;-- makes copies now + x: 1 + 4 == f 2 ; #2048 said this should be 3, but it should not. + ; function and closure bodies are not "swappable", because keeping the + ; original series would mean that the original formation would always + ; drop the index position (there is no index slot in the body series). + ; A copy must be made -or- series forced to be at their head. +] diff --git a/tests/datatypes/datatype.test.reb b/tests/datatypes/datatype.test.reb new file mode 100644 index 0000000000..a0e2426ca4 --- /dev/null +++ b/tests/datatypes/datatype.test.reb @@ -0,0 +1,52 @@ +; datatypes/datatype.r +[not datatype? 1] +[datatype! = type-of function!] +[datatype? function!] +[datatype? binary!] +[datatype? bitset!] +[datatype? block!] +[datatype? char!] +[datatype? closure!] ; closure! =? function! in R2/Forward, R2 2.7.7+ +[datatype? datatype!] +[datatype? date!] +[datatype? decimal!] +[datatype? email!] +[datatype? error!] +[datatype? event!] +[datatype? file!] +[datatype? function!] +[datatype? get-path!] ; get-path! =? path! in R2/Forward, R2 2.7.7+ +[datatype? get-word!] +[datatype? gob!] +[datatype? handle!] +[datatype? image!] +[datatype? integer!] +[datatype? issue!] +[datatype? library!] +[datatype? lit-path!] +[datatype? lit-word!] +[datatype? logic!] +[datatype? map!] ; map! =? hash! in R2/Forward, R2 2.7.7+ +[datatype? module!] +[datatype? money!] +[datatype? blank!] +[datatype? object!] +[datatype? pair!] +[datatype? group!] +[datatype? path!] +[datatype? percent!] +[datatype? port!] +[datatype? refinement!] +[datatype? set-path!] +[datatype? set-word!] +[datatype? string!] +[datatype? struct!] +[datatype? tag!] +[datatype? time!] +[datatype? tuple!] +[datatype? typeset!] ; typeset! =? block! in R2/Forward, R2 2.7.7+ +[datatype? url!] +[datatype? vector!] +[datatype? word!] +; alternative literal representation +[datatype? #[datatype! function!]] diff --git a/tests/datatypes/date.test.reb b/tests/datatypes/date.test.reb new file mode 100644 index 0000000000..e4ba4cc18e --- /dev/null +++ b/tests/datatypes/date.test.reb @@ -0,0 +1,28 @@ +; datatypes/date.r +[date? 25/Sep/2006] +[not date? 1] +[date! = type-of 25/Sep/2006] +; alternative formats +[25/Sep/2006 = 25/9/2006] +[25/Sep/2006 = 25-Sep-2006] +[25/Sep/2006 = 25-9-2006] +[25/Sep/2006 = make date! "25/Sep/2006"] +[25/Sep/2006 = to date! "25-Sep-2006"] +["25-Sep-2006" = mold 25/Sep/2006] +; minimum +[date? 1/Jan/0000] +; another minimum +[date? 1/Jan/0000/0:00] +; extreme behaviour +[ + any? [ + error? try [date-d: 1/Jan/0000 - 1] + date-d = load mold date-d + ] +] +[ + any? [ + error? try [date-d: 31-Dec-16383 + 1] + date-d = load mold date-d + ] +] diff --git a/tests/datatypes/decimal.test.reb b/tests/datatypes/decimal.test.reb new file mode 100644 index 0000000000..90dc41a610 --- /dev/null +++ b/tests/datatypes/decimal.test.reb @@ -0,0 +1,143 @@ +; datatypes/decimal.r +[decimal? 0.0] +[not decimal? 0] +[decimal! = type-of 0.0] +[decimal? 1.0] +[decimal? -1.0] +[decimal? 1.5] +; LOAD decimal and to binary! tests +; 64-bit IEEE 754 maximum +[equal? #{7FEFFFFFFFFFFFFF} to binary! 1.7976931348623157e308] +; Minimal positive normalized +[equal? #{0010000000000000} to binary! 2.2250738585072014E-308] +; Maximal positive denormalized +[equal? #{000FFFFFFFFFFFFF} to binary! 2.225073858507201E-308] +; Minimal positive denormalized +[equal? #{0000000000000001} to binary! 4.9406564584124654E-324] +; zero +[equal? #{0000000000000000} to binary! 0.0] +; negative zero +[equal? #{8000000000000000} to binary! -0.0] +; Maximal negative denormalized +[equal? #{8000000000000001} to binary! -4.9406564584124654E-324] +; Minimal negative denormalized +[equal? #{800FFFFFFFFFFFFF} to binary! -2.225073858507201E-308] +; Maximal negative normalized +[equal? #{8010000000000000} to binary! -2.2250738585072014E-308] +; 64-bit IEEE 754 minimum +[equal? #{FFEFFFFFFFFFFFFF} to binary! -1.7976931348623157e308] +; bug#729 +; MOLD decimal accuracy tests +[ + system/options/decimal-digits: 17 + system/options/decimal-digits = 17 +] +; 64-bit IEEE 754 maximum +[zero? 1.7976931348623157e308 - load mold 1.7976931348623157e308] +[same? 1.7976931348623157e308 load mold 1.7976931348623157e308] +; Minimal positive normalized +[zero? 2.2250738585072014E-308 - load mold 2.2250738585072014E-308] +[same? 2.2250738585072014E-308 load mold 2.2250738585072014E-308] +; Maximal positive denormalized +[zero? 2.225073858507201E-308 - load mold 2.225073858507201E-308] +[same? 2.225073858507201E-308 load mold 2.225073858507201E-308] +; Minimal positive denormalized +[zero? 4.9406564584124654E-324 - load mold 4.9406564584124654E-324] +[same? 4.9406564584124654E-324 load mold 4.9406564584124654E-324] +; Positive zero +[zero? 0.0 - load mold 0.0] +[same? 0.0 load mold 0.0] +; Negative zero +[zero? -0.0 - load mold -0.0] +[same? -0.0 load mold -0.0] +; Maximal negative denormalized +[zero? -4.9406564584124654E-324 - load mold -4.9406564584124654E-324] +[same? -4.9406564584124654E-324 load mold -4.9406564584124654E-324] +; Minimal negative denormalized +[zero? -2.225073858507201E-308 - load mold -2.225073858507201E-308] +[same? -2.225073858507201E-308 load mold -2.225073858507201E-308] +; Maximal negative normalized +[zero? -2.2250738585072014E-308 - load mold -2.2250738585072014E-308] +[same? -2.2250738585072014E-308 load mold -2.2250738585072014E-308] +; 64-bit IEEE 754 minimum +[zero? -1.7976931348623157E308 - load mold -1.7976931348623157e308] +[same? -1.7976931348623157E308 load mold -1.7976931348623157e308] +[zero? 0.10000000000000001 - load mold 0.10000000000000001] +[same? 0.10000000000000001 load mold 0.10000000000000001] +[zero? 0.29999999999999999 - load mold 0.29999999999999999] +[same? 0.29999999999999999 load mold 0.29999999999999999] +[zero? 0.30000000000000004 - load mold 0.30000000000000004] +[same? 0.30000000000000004 load mold 0.30000000000000004] +[zero? 9.9999999999999926e152 - load mold 9.9999999999999926e152] +[same? 9.9999999999999926e152 load mold 9.9999999999999926e152] +; bug#718 +[ + a: 9.9999999999999926e152 * 1e-138 + zero? a - load mold a +] +; bug#897 +; MOLD/ALL decimal accuracy tests +; 64-bit IEEE 754 maximum +[zero? 1.7976931348623157e308 - load mold/all 1.7976931348623157e308] +[same? 1.7976931348623157e308 load mold/all 1.7976931348623157e308] +; Minimal positive normalized +[zero? 2.2250738585072014E-308 - load mold/all 2.2250738585072014E-308] +[same? 2.2250738585072014E-308 load mold/all 2.2250738585072014E-308] +; Maximal positive denormalized +[zero? 2.225073858507201E-308 - load mold/all 2.225073858507201E-308] +[same? 2.225073858507201E-308 load mold/all 2.225073858507201E-308] +; Minimal positive denormalized +[zero? 4.9406564584124654E-324 - load mold/all 4.9406564584124654E-324] +[same? 4.9406564584124654E-324 load mold/all 4.9406564584124654E-324] +; Positive zero +[zero? 0.0 - load mold/all 0.0] +[same? 0.0 load mold/all 0.0] +; Negative zero +[zero? -0.0 - load mold/all -0.0] +[same? -0.0 load mold/all -0.0] +; Maximal negative denormalized +[zero? -4.9406564584124654E-324 - load mold/all -4.9406564584124654E-324] +[same? -4.9406564584124654E-324 load mold/all -4.9406564584124654E-324] +; Minimal negative denormalized +[zero? -2.225073858507201E-308 - load mold/all -2.225073858507201E-308] +[same? -2.225073858507201E-308 load mold/all -2.225073858507201E-308] +; Maximal negative normalized +[zero? -2.2250738585072014E-308 - load mold/all -2.2250738585072014E-308] +[same? -2.2250738585072014E-308 load mold/all -2.2250738585072014E-308] +; 64-bit IEEE 754 minimum +[zero? -1.7976931348623157E308 - load mold/all -1.7976931348623157e308] +[same? -1.7976931348623157E308 load mold/all -1.7976931348623157e308] +[zero? 0.10000000000000001 - load mold/all 0.10000000000000001] +[same? 0.10000000000000001 load mold/all 0.10000000000000001] +[zero? 0.29999999999999999 - load mold/all 0.29999999999999999] +[same? 0.29999999999999999 load mold/all 0.29999999999999999] +[zero? 0.30000000000000004 - load mold/all 0.30000000000000004] +[same? 0.30000000000000004 load mold/all 0.30000000000000004] +[zero? 9.9999999999999926e152 - load mold/all 9.9999999999999926e152] +[same? 9.9999999999999926e152 load mold/all 9.9999999999999926e152] +; LOAD decimal accuracy tests +[equal? to binary! 2.2250738585072004e-308 #{000FFFFFFFFFFFFE}] +[equal? to binary! 2.2250738585072005e-308 #{000FFFFFFFFFFFFE}] +[equal? to binary! 2.2250738585072006e-308 #{000FFFFFFFFFFFFE}] +[equal? to binary! 2.2250738585072007e-308 #{000FFFFFFFFFFFFF}] +[equal? to binary! 2.2250738585072008e-308 #{000FFFFFFFFFFFFF}] +[equal? to binary! 2.2250738585072009e-308 #{000FFFFFFFFFFFFF}] +[equal? to binary! 2.225073858507201e-308 #{000FFFFFFFFFFFFF}] +[equal? to binary! 2.2250738585072011e-308 #{000FFFFFFFFFFFFF}] +[equal? to binary! 2.2250738585072012e-308 #{0010000000000000}] +[equal? to binary! 2.2250738585072013e-308 #{0010000000000000}] +[equal? to binary! 2.2250738585072014e-308 #{0010000000000000}] +; bug#1753 +[c: last mold/all 1e16 (#"0" <= c) and* (#"9" >= c)] +; alternative form +[1.1 == 1,1] +[1.1 = make decimal! 1.1] +[1.1 = make decimal! "1.1"] +[1.1 = to decimal! 1.1] +[1.1 = to decimal! "1.1"] +[error? try [to decimal! "t"]] +; decimal! to binary! and binary! to decimal! +[equal? #{3ff0000000000000} to binary! 1.0] +[same? to decimal! #{3ff0000000000000} 1.0] +; bug#747 +[equal? #{3FF0000000000009} to binary! to decimal! #{3FF0000000000009}] diff --git a/tests/datatypes/email.test.reb b/tests/datatypes/email.test.reb new file mode 100644 index 0000000000..e00c9fc31e --- /dev/null +++ b/tests/datatypes/email.test.reb @@ -0,0 +1,8 @@ +; datatypes/email.r +[email? me@here.com] +[not email? 1] +[email! = type-of me@here.com] +; "minimum" +[email? #[email! ""]] +[strict-equal? #[email! ""] make email! 0] +[strict-equal? #[email! ""] to email! ""] diff --git a/tests/datatypes/error.test.reb b/tests/datatypes/error.test.reb new file mode 100644 index 0000000000..2143d34811 --- /dev/null +++ b/tests/datatypes/error.test.reb @@ -0,0 +1,138 @@ +; datatypes/error.r +[error? try [1 / 0]] +[not error? 1] +[error! = type-of try [1 / 0]] + +; error evaluation +[error? do head insert copy [] try [1 / 0]] + +; error that does not exist in the SCRIPT category--all of whose ids are +; reserved by the system and must be formed from mezzanine/user code in +; accordance with the structure the system would form. Hence, illegal. +; +[try/except [make error! [type: 'script id: 'nonexistent-id]] [true]] + +; triggered errors should not be assignable +; +[a: 1 error? try [a: 1 / 0] :a =? 1] +[a: 1 error? try [set 'a 1 / 0] :a =? 1] +[a: 1 error? try [set/opt 'a 1 / 0] :a =? 1] + +; bug#2190 +[127 = catch/quit [attempt [catch/quit [1 / 0]] quit/with 127]] + +; error types that should be predefined + +[error? make error! [type: 'syntax id: 'scan-invalid]] +[error? make error! [type: 'syntax id: 'scan-missing]] +[error? make error! [type: 'syntax id: 'scan-extra]] +[error? make error! [type: 'syntax id: 'scan-mismatch]] +[error? make error! [type: 'syntax id: 'no-header]] +[error? make error! [type: 'syntax id: 'bad-header]] +[error? make error! [type: 'syntax id: 'bad-checksum]] +[error? make error! [type: 'syntax id: 'malconstruct]] +[error? make error! [type: 'syntax id: 'bad-char]] +[error? make error! [type: 'syntax id: 'needs]] + +[error? make error! [type: 'script id: 'no-value]] +[error? make error! [type: 'script id: 'need-value]] +[error? make error! [type: 'script id: 'not-bound]] +[error? make error! [type: 'script id: 'not-in-context]] +[error? make error! [type: 'script id: 'no-arg]] +[error? make error! [type: 'script id: 'expect-arg]] +[error? make error! [type: 'script id: 'expect-val]] +[error? make error! [type: 'script id: 'expect-type]] +[error? make error! [type: 'script id: 'cannot-use]] +[error? make error! [type: 'script id: 'invalid-arg]] +[error? make error! [type: 'script id: 'invalid-type]] +[error? make error! [type: 'script id: 'invalid-op]] +[error? make error! [type: 'script id: 'no-op-arg]] +[error? make error! [type: 'script id: 'invalid-data]] +[error? make error! [type: 'script id: 'not-same-type]] +[error? make error! [type: 'script id: 'not-related]] +[error? make error! [type: 'script id: 'bad-func-def]] +[error? make error! [type: 'script id: 'bad-func-arg]] +[error? make error! [type: 'script id: 'no-refine]] +[error? make error! [type: 'script id: 'bad-refines]] +[error? make error! [type: 'script id: 'bad-refine]] +[error? make error! [type: 'script id: 'invalid-path]] +[error? make error! [type: 'script id: 'bad-path-type]] +[error? make error! [type: 'script id: 'bad-path-set]] +[error? make error! [type: 'script id: 'bad-field-set]] +[error? make error! [type: 'script id: 'dup-vars]] +[error? make error! [type: 'script id: 'past-end]] +[error? make error! [type: 'script id: 'missing-arg]] +[error? make error! [type: 'script id: 'too-short]] +[error? make error! [type: 'script id: 'too-long]] +[error? make error! [type: 'script id: 'invalid-chars]] +[error? make error! [type: 'script id: 'invalid-compare]] +[error? make error! [type: 'script id: 'verify-failed]] +[error? make error! [type: 'script id: 'verify-void]] +[error? make error! [type: 'script id: 'invalid-part]] +[error? make error! [type: 'script id: 'no-return]] +[error? make error! [type: 'script id: 'block-lines]] +[error? make error! [type: 'script id: 'bad-bad]] +[error? make error! [type: 'script id: 'bad-make-arg]] +[error? make error! [type: 'script id: 'wrong-denom]] +[error? make error! [type: 'script id: 'bad-compression]] +[error? make error! [type: 'script id: 'dialect]] +[error? make error! [type: 'script id: 'bad-command]] +[error? make error! [type: 'script id: 'parse-rule]] +[error? make error! [type: 'script id: 'parse-end]] +[error? make error! [type: 'script id: 'parse-variable]] +[error? make error! [type: 'script id: 'parse-command]] +[error? make error! [type: 'script id: 'parse-series]] + +[error? make error! [type: 'math id: 'zero-divide]] +[error? make error! [type: 'math id: 'overflow]] +[error? make error! [type: 'math id: 'positive]] +[error? make error! [type: 'math id: 'type-limit]] +[error? make error! [type: 'math id: 'size-limit]] +[error? make error! [type: 'math id: 'out-of-range]] + +[error? make error! [type: 'access id: 'protected-word]] +[error? make error! [type: 'access id: 'hidden]] +[error? make error! [type: 'access id: 'cannot-open]] +[error? make error! [type: 'access id: 'not-open]] +[error? make error! [type: 'access id: 'already-open]] +[error? make error! [type: 'access id: 'no-connect]] +[error? make error! [type: 'access id: 'not-connected]] +[error? make error! [type: 'access id: 'no-script]] +[error? make error! [type: 'access id: 'no-scheme-name]] +[error? make error! [type: 'access id: 'no-scheme]] +[error? make error! [type: 'access id: 'invalid-spec]] +[error? make error! [type: 'access id: 'invalid-port]] +[error? make error! [type: 'access id: 'invalid-actor]] +[error? make error! [type: 'access id: 'invalid-port-arg]] +[error? make error! [type: 'access id: 'no-port-action]] +[error? make error! [type: 'access id: 'protocol]] +[error? make error! [type: 'access id: 'invalid-check]] +[error? make error! [type: 'access id: 'write-error]] +[error? make error! [type: 'access id: 'read-error]] +[error? make error! [type: 'access id: 'read-only]] +[error? make error! [type: 'access id: 'timeout]] +[error? make error! [type: 'access id: 'no-create]] +[error? make error! [type: 'access id: 'no-delete]] +[error? make error! [type: 'access id: 'no-rename]] +[error? make error! [type: 'access id: 'bad-file-path]] +[error? make error! [type: 'access id: 'bad-file-mode]] +[error? make error! [type: 'access id: 'security]] +[error? make error! [type: 'access id: 'security-level]] +[error? make error! [type: 'access id: 'security-error]] +[error? make error! [type: 'access id: 'no-codec]] +[error? make error! [type: 'access id: 'bad-media]] +[error? make error! [type: 'access id: 'no-extension]] +[error? make error! [type: 'access id: 'bad-extension]] +[error? make error! [type: 'access id: 'extension-init]] +[error? make error! [type: 'access id: 'call-fail]] + +[error? make error! [type: 'user id: 'message]] + +[error? make error! [type: 'internal id: 'bad-path]] +[error? make error! [type: 'internal id: 'not-here]] +[error? make error! [type: 'internal id: 'no-memory]] +[error? make error! [type: 'internal id: 'stack-overflow]] +[error? make error! [type: 'internal id: 'globals-full]] +[error? make error! [type: 'internal id: 'bad-sys-func]] +[error? make error! [type: 'internal id: 'not-done]] +[error? make error! [type: 'internal id: 'bad-utf8]] diff --git a/tests/datatypes/event.test.reb b/tests/datatypes/event.test.reb new file mode 100644 index 0000000000..47367dff0b --- /dev/null +++ b/tests/datatypes/event.test.reb @@ -0,0 +1,2 @@ +; datatypes/event.r +[not event? 1] diff --git a/tests/datatypes/file.test.reb b/tests/datatypes/file.test.reb new file mode 100644 index 0000000000..1cc5d2b3ee --- /dev/null +++ b/tests/datatypes/file.test.reb @@ -0,0 +1,12 @@ +; datatypes/file.r +[file? %myscript.r] +[not file? 1] +[file! = type-of %myscript.r] +; minimum +[file? %""] +[%"" == #[file! ""]] +[%"" == make file! 0] +[%"" == to file! ""] +["%%2520" = mold to file! "%20"] +; bug#1241 +[file? %"/c/Program Files (x86)"] diff --git a/tests/datatypes/function.test.reb b/tests/datatypes/function.test.reb new file mode 100644 index 0000000000..754e7d349b --- /dev/null +++ b/tests/datatypes/function.test.reb @@ -0,0 +1,361 @@ +; datatypes/function.r +[function? does ["OK"]] +[not function? 1] +[function! = type-of does ["OK"]] +; minimum +[function? does []] +; literal form +[function? first [#[function! [[] []]]]] +; return-less return value tests +[ + f: does [] + void? f +] +[ + f: does [:abs] + :abs = f +] +[ + a-value: #{} + f: does [a-value] + same? a-value f +] +[ + a-value: charset "" + f: does [a-value] + same? a-value f +] +[ + a-value: [] + f: does [a-value] + same? a-value f +] +[ + a-value: blank! + f: does [a-value] + same? a-value f +] +[ + f: does [1/Jan/0000] + 1/Jan/0000 = f +] +[ + f: does [0.0] + 0.0 == f +] +[ + f: does [1.0] + 1.0 == f +] +[ + a-value: me@here.com + f: does [a-value] + same? a-value f +] +[ + f: does [try [1 / 0]] + error? f +] +[ + a-value: %"" + f: does [a-value] + same? a-value f +] +[ + a-value: does [] + f: does [:a-value] + same? :a-value f +] +[ + a-value: first [:a] + f: does [:a-value] + (same? :a-value f) and* (:a-value == f) +] +[ + f: does [#"^@"] + #"^@" == f +] +[ + a-value: make image! 0x0 + f: does [a-value] + same? a-value f +] +[ + f: does [0] + 0 == f +] +[ + f: does [1] + 1 == f +] +[ + f: does [#a] + #a == f +] +[ + a-value: first ['a/b] + f: does [:a-value] + :a-value == f +] +[ + a-value: first ['a] + f: does [:a-value] + :a-value == f +] +[ + f: does [true] + true = f +] +[ + f: does [false] + false = f +] +[ + f: does [$1] + $1 == f +] +[ + f: does [:type-of] + same? :type-of f +] +[ + f: does [_] + blank? f +] +[ + a-value: make object! [] + f: does [:a-value] + same? :a-value f +] +[ + a-value: first [()] + f: does [:a-value] + same? :a-value f +] +[ + f: does [get '+] + same? get '+ f +] +[ + f: does [0x0] + 0x0 == f +] +[ + a-value: 'a/b + f: does [:a-value] + :a-value == f +] +[ + a-value: make port! http:// + f: does [:a-value] + port? f +] +[ + f: does [/a] + /a == f +] +[ + a-value: first [a/b:] + f: does [:a-value] + :a-value == f +] +[ + a-value: first [a:] + f: does [:a-value] + :a-value == all [:a-value] +] +[ + a-value: "" + f: does [:a-value] + same? :a-value f +] +[ + a-value: make tag! "" + f: does [:a-value] + same? :a-value f +] +[ + f: does [0:00] + 0:00 == f +] +[ + f: does [0.0.0] + 0.0.0 == f +] +[ + f: does [()] + void? f +] +[ + f: does ['a] + 'a == f +] +; two-function return tests +[ + g: func [f [function!]] [f [return 1] 2] + 1 = g :do +] +; BREAK out of a function +[ + blank? loop 1 [ + f: does [break] + f + 2 + ] +] +; THROW out of a function +[ + 1 = catch [ + f: does [throw 1] + f + 2 + ] +] +; "error out" of a function +[ + error? try [ + f: does [1 / 0 2] + f + 2 + ] +] +; BREAK out leaves a "running" function in a "clean" state +[ + 1 = loop 1 [ + f: func [x] [ + either x = 1 [ + loop 1 [f 2] + x + ] [break] + ] + f 1 + ] +] +; THROW out leaves a "running" function in a "clean" state +[ + 1 = catch [ + f: func [x] [ + either x = 1 [ + catch [f 2] + x + ] [throw 1] + ] + f 1 + ] +] +; "error out" leaves a "running" function in a "clean" state +[ + f: func [x] [ + either x = 1 [ + error? try [f 2] + x = 1 + ] [1 / 0] + ] + f 1 +] +; Argument passing of "get arguments" ("get-args") +[gf: func [:x] [:x] 10 == gf 10] +[gf: func [:x] [:x] 'a == gf a] +[gf: func [:x] [:x] (quote 'a) == gf 'a] +[gf: func [:x] [:x] (quote :a) == gf :a] +[gf: func [:x] [:x] (quote a:) == gf a:] +[gf: func [:x] [:x] (quote (10 + 20)) == gf (10 + 20)] +[gf: func [:x] [:x] o: context [f: 10] (quote :o/f) == gf :o/f] +; Argument passing of "literal arguments" ("lit-args") +[lf: func ['x] [:x] 10 == lf 10] +[lf: func ['x] [:x] 'a == lf a] +[lf: func ['x] [:x] (quote 'a) == lf 'a] +[lf: func ['x] [:x] a: 10 10 == lf :a] +[lf: func ['x] [:x] (quote a:) == lf a:] +[lf: func ['x] [:x] 30 == lf (10 + 20)] +[lf: func ['x] [:x] o: context [f: 10] 10 == lf :o/f] +; basic test for recursive function! invocation +[i: 0 countdown: proc [n] [if n > 0 [++ i countdown n - 1]] countdown 10 i = 10] + +; In Ren-C's specific binding, a function-local word that escapes the +; function's extent cannot be used when re-entering the same function later +[ + f: func [code value] [either blank? code ['value] [do code]] + f-value: f blank blank + error? try [f compose [2 * (f-value)] 21] ; re-entering same function +] +[ + f: func [code value] [either blank? code ['value] [do code]] + g: func [code value] [either blank? code ['value] [do code]] + f-value: f blank blank + error? try [g compose [2 * (f-value)] 21] ; re-entering different function +] +; bug#19 - but duplicate specializations currently not legal in Ren-C +[ + f: func [/r x] [x] + error? trap [2 == f/r/r 1 2] +] +; bug#27 +[error? try [(type-of) 1]] +; bug#1659 +; inline function test +[ + f: does reduce [does [true]] + f +] +; no-rebind test--succeeds in R3-Alpha but fails in Ren-C. Second time f is +; called, `a` has been cleared so `a [d]` doesn't recapture the local, and +; `c` holds the `[d]` from the first call. +[ + a: func [b] [a: _ c: b] + f: func [d] [a [d] do c] + all? [ + 1 = f 1 + error? try [2 = f 2] + ] +] +; bug#1528 +[function? func [self] []] +; bug#1756 +[eval does [reduce reduce [:self] true]] +; bug#2025 +[ + ; ensure x and y are unset from previous tests, as the test here + ; is trying to cause an error... + unset 'x + unset 'y + + body: [x + y] + f: make function! reduce [[x] body] + g: make function! reduce [[y] body] + error? try [f 1] +] +; bug#2044 +[ + o: make object! [f: func [x] ['x]] + p: make o [] + not same? o/f 1 p/f 1 +] + +[ + o1: make object! [x: {x} o2: make object! [y: {y}]] + outer: {outer} + n: 20 + + f: function [ + /count + n (2) + o1 o1/o2 + outer + static (10 + n) + ][ + data: reduce [n x y outer static] + return case [ + n = 0 [reduce [data]] + true [ + append/only (f/count n - 1) data + ] + ] + ] + + f = [ + [0 "x" "y" "outer" 30] + [1 "x" "y" "outer" 30] + [2 "x" "y" "outer" 30] + ] +] diff --git a/tests/datatypes/get-path.test.reb b/tests/datatypes/get-path.test.reb new file mode 100644 index 0000000000..6eda7349d4 --- /dev/null +++ b/tests/datatypes/get-path.test.reb @@ -0,0 +1,11 @@ +; datatypes/get-path.r +; minimum +; bug#1947 +; empty get-path test +[get-path? load "#[get-path! [[a] 1]]"] +[ + all [ + get-path? a: load "#[get-path! [[a b c] 2]]" + 2 == index? a + ] +] diff --git a/tests/datatypes/get-word.test.reb b/tests/datatypes/get-word.test.reb new file mode 100644 index 0000000000..34e3075fd2 --- /dev/null +++ b/tests/datatypes/get-word.test.reb @@ -0,0 +1,13 @@ +; datatypes/get-word.r +[get-word? first [:a]] +[not get-word? 1] +[get-word! = type-of first [:a]] +[ + ; context-less get-word + e: try [do to block! ":a"] + e/id = 'not-bound +] +[ + unset 'a + void? :a +] diff --git a/tests/datatypes/gob.test.reb b/tests/datatypes/gob.test.reb new file mode 100644 index 0000000000..afbafb9e13 --- /dev/null +++ b/tests/datatypes/gob.test.reb @@ -0,0 +1,32 @@ +; datatypes/gob.r +; minimum +[gob? make gob! []] +[gob! = type-of make gob! []] +; bug#62 +[ + g: make gob! [] + 1x1 == g/offset: 1x1 +] +; bug#1969 +[ + g1: make gob! [] + g2: make gob! [] + insert g1 g2 + same? g1 g2/parent + do "g1: _" + do "recycle" + g3: make gob! [] + insert g2/parent g3 + true +] +[ + main: make gob! [] + for-each i [31 325 1] [ + clear main + recycle + loop i [ + append main make gob! [] + ] + ] + true +] diff --git a/tests/datatypes/hash.test.reb b/tests/datatypes/hash.test.reb new file mode 100644 index 0000000000..8ada98d5be --- /dev/null +++ b/tests/datatypes/hash.test.reb @@ -0,0 +1 @@ +; datatypes/hash.r diff --git a/tests/datatypes/image.test.reb b/tests/datatypes/image.test.reb new file mode 100644 index 0000000000..7308722681 --- /dev/null +++ b/tests/datatypes/image.test.reb @@ -0,0 +1,11 @@ +; datatypes/image.r +[image? make image! 100x100] +[not image? 1] +[image! = type-of make image! 0x0] +; minimum +[image? #[image! [0x0 #{}]]] +; default colours +[ + a-value: #[image! [1x1 #{}]] + equal? pick a-value 0x0 0.0.0.255 +] diff --git a/tests/datatypes/integer.test.reb b/tests/datatypes/integer.test.reb new file mode 100644 index 0000000000..d8acd81360 --- /dev/null +++ b/tests/datatypes/integer.test.reb @@ -0,0 +1,51 @@ +; datatypes/integer.r +[integer? 0] +; bug#33 +[integer? -0] +[not integer? 1.1] +[integer! = type-of 0] +[integer? 1] +[integer? -1] +[integer? 2] +; 32bit minimum +[integer? -2147483648] +; 32bit maximum +[integer? 2147483647] +; 64bit minimum +#64bit +[integer? -9223372036854775808] +; 64bit maximum +#64bit +[integer? 9223372036854775807] +[0 == make integer! 0] +[0 == make integer! "0"] +[0 == to integer! 0] +[-2147483648 == to integer! -2147483648.0] +[-2147483648 == to integer! -2147483648.9] +[2147483647 == to integer! 2147483647.9] +#32bit +[error? try [to integer! -2147483649.0]] +#32bit +[error? try [to integer! 2147483648.0]] +; bug#921 +[error? try [to integer! 9.2233720368547765e18]] +[error? try [to integer! -9.2233720368547779e18]] +[0 == to integer! "0"] +[error? try [to integer! false]] +[error? try [to integer! true]] +[0 == to integer! #"^@"] +[1 == to integer! #"^a"] +[0 == to integer! #0] +[1 == to integer! #1] +[0 == to integer! #{00}] +[1 == to integer! #{01}] +#32bit +[-1 == to integer! #{ffffffff}] +#64bit +[-1 == to integer! #{ffffffffffffffff}] +#64bit +[302961000000 == to integer! "3.02961E+11"] +[error? try [to integer! "t"]] +["0" = mold 0] +["1" = mold 1] +["-1" = mold -1] diff --git a/tests/datatypes/issue.test.reb b/tests/datatypes/issue.test.reb new file mode 100644 index 0000000000..735e3bba00 --- /dev/null +++ b/tests/datatypes/issue.test.reb @@ -0,0 +1,6 @@ +; datatypes/issue.r +[issue? #aa] +[not issue? 1] +[issue! = type-of #aa] +; minimum +[issue? #a] diff --git a/tests/datatypes/list.test.reb b/tests/datatypes/list.test.reb new file mode 100644 index 0000000000..1cd389d477 --- /dev/null +++ b/tests/datatypes/list.test.reb @@ -0,0 +1 @@ +; datatypes/list.r diff --git a/tests/datatypes/lit-path.test.reb b/tests/datatypes/lit-path.test.reb new file mode 100644 index 0000000000..3467322ae6 --- /dev/null +++ b/tests/datatypes/lit-path.test.reb @@ -0,0 +1,18 @@ +; datatypes/lit-path.r +[lit-path? first ['a/b]] +[not lit-path? 1] +[lit-path! = type-of first ['a/b]] +; minimum +; bug#1947 +[lit-path? load "#[lit-path! [[a] 1]]"] +[ + all [ + lit-path? a: load "#[lit-path! [[a b c] 2]]" + 2 == index? a + ] +] +; lit-paths are active +[ + a-value: first ['a/b] + strict-equal? to path! :a-value do reduce [:a-value] +] diff --git a/tests/datatypes/lit-word.test.reb b/tests/datatypes/lit-word.test.reb new file mode 100644 index 0000000000..189fe7c6b0 --- /dev/null +++ b/tests/datatypes/lit-word.test.reb @@ -0,0 +1,15 @@ +; datatypes/lit-word.r +[lit-word? first ['a]] +[not lit-word? 1] +[lit-word! = type-of first ['a]] +; lit-words are active +[ + a-value: first ['a] + strict-equal? to word! :a-value do reduce [:a-value] +] +; bug#1342 +[word? '<] +[word? '>] +[word? '<=] +[word? '>=] +[word? '<>] diff --git a/tests/datatypes/logic.test.reb b/tests/datatypes/logic.test.reb new file mode 100644 index 0000000000..3bb53b5ef4 --- /dev/null +++ b/tests/datatypes/logic.test.reb @@ -0,0 +1,19 @@ +; datatypes/logic.r +[logic? true] +[logic? false] +[not logic? 1] +[logic! = type-of true] +[logic! = type-of false] +[true = #[true]] +[false = #[false]] +[on = true] +[off = false] +[yes = true] +[no = false] +[false = make logic! 0] +[true = make logic! 1] +[true = to logic! 0] +[true = to logic! 1] +[true = to logic! "f"] +["true" = mold true] +["false" = mold false] diff --git a/tests/datatypes/map.test.reb b/tests/datatypes/map.test.reb new file mode 100644 index 0000000000..0ce2eea4d3 --- /dev/null +++ b/tests/datatypes/map.test.reb @@ -0,0 +1,22 @@ +; datatypes/map.r +; map! =? hash! in R2/Forward, R2 2.7.7+ +[empty? make map! []] +[empty? make map! 4] +; The length of a map is the number of key/value pairs it holds. +[2 == length? make map! [a 1 b 2]] ; 4 in R2, R2/Forward +[m: make map! [a 1 b 2] 1 == m/a] +[m: make map! [a 1 b 2] 2 == m/b] +[ + m: make map! [a 1 b 2] + error? trap [m/c] +] +[m: make map! [a 1 b 2] m/c: 3 3 == m/c] +; Maps contain key/value pairs and must be created from blocks of even length. +[error? try [make map! [1]]] +[empty? clear make map! [a 1 b 2]] +[ + #1930 + m: make map! 8 + clear m + not find m 'a +] diff --git a/tests/datatypes/module.test.reb b/tests/datatypes/module.test.reb new file mode 100644 index 0000000000..6379ca19c3 --- /dev/null +++ b/tests/datatypes/module.test.reb @@ -0,0 +1,95 @@ +; datatypes/module.r + +[module? module [] []] +[not module? 1] +[module! = type-of module [] []] + +[ + a-module: module [ + ] [ + ; 'var will be in the module + var: 1 + ] + var: 2 + 1 == a-module/var +] + +; import test +[ + a-module: module [ + exports: [var] + ] [ + var: 2 + ] + import a-module + 2 == var +] + +; import test +[ + var: 1 + a-module: module [ + exports: [var] + ] [ + var: 2 + ] + import a-module + 1 == var +] + +;-- Tests that were in %sys-load.r, these were originally using XLOAD, but +;-- there is no XLOAD. + +[[1 2 3] = load ["1" "2" "3"]] +[[] = load " "] +[1 = load "1"] +[[1] = load "[1]"] +[[1 2 3] = load "1 2 3"] +[[1 2 3] = load/type "1 2 3" blank] +[[1 2 3] = load "rebol [] 1 2 3"] +[ + d: load/header "rebol [] 1 2 3" + all [object? first d [1 2 3] = next d] +] + +; This was a test from the %sys-load.r which trips up the loading mechanic +; (at time of writing). LOAD thinks that the entirety of the script is the +; "rebol [] 1 2 3", and skips the equality comparison etc. so it gets +; loaded as [1 2 3], which then evaluates to 3. The test framework then +; considers that "not a logic". +; +; [[rebol [] 1 2 3] = load/all "rebol [] 1 2 3"] + +; File variations: +[equal? read %./ load %./] +[ + write %test.txt s: "test of text" + s = load %test.txt +] +[ + save %test1.r 1 + 1 = load %test1.r +] +[ + save %test2.r [1 2] + [1 2] = load %test2.r +] +[ + save/header %test.r [1 2 3] [title: "Test"] + [1 2 3] = load %test.r +] +[ + save/header %test-checksum.r [1 2 3] [checksum: true] + ;print read/string %test-checksum.r + [1 2 3] = load %test-checksum.r +] +[ + save/header %test-checksum.r [1 2 3] [checksum: true compress: true] + ;print read/string %test-checksum.r + [1 2 3] = load %test-checksum.r +] +[ + save/header %test-checksum.r [1 2 3] [checksum: script compress: true] + ;print read/string %test-checksum.r + [1 2 3] = load %test-checksum.r +] \ No newline at end of file diff --git a/tests/datatypes/money.test.reb b/tests/datatypes/money.test.reb new file mode 100644 index 0000000000..72fbf2a389 --- /dev/null +++ b/tests/datatypes/money.test.reb @@ -0,0 +1,152 @@ +; datatypes/money.r +[money? $0.0] +[not money? 0] +[money! = type-of $0.0] +[money? $1.0] +[money? -$1.0] +[money? $1.5] +; moldable maximum for R2 +[money? $999999999999999.87] +; moldable minimum for R2 +[money? -$999999999999999.87] +; check, whether these are moldable +[ + x: $999999999999999 + any? [ + error? try [x: x + $1] + not error? try [mold x] + ] +] +[ + x: -$999999999999999 + any? [ + error? try [x: x - $1] + not error? try [mold x] + ] +] +; alternative form +[$1.1 == $1,1] +[ + any? [ + error? try [x: $1234567890123456] + not error? try [mold x] + ] +] +[$11 = make money! 11] +[$1.1 = make money! "1.1"] +; bug#4 +[$11 = to money! 11] +[$1.1 = to money! "1.1"] +["$1.10" = mold $1.10] +["-$1.10" = mold -$1.10] +["$0" = mold $0] +; equality +[$1 = $1.0000000000000000000000000] +[not $1 = $2] +; maximum for R3 +[equal? $99999999999999999999999999e127 $99999999999999999999999999e127] +; minimum for R3 +[equal? -$99999999999999999999999999e127 -$99999999999999999999999999e127] +[not $0 = $1e-128] +[not $0 = -$1e-128] +; inequality +[not $1 <> $1] +[$1 <= $2] +[not $2 <= $1] +[not zero? $1e-128] +[not zero? -$1e-128] +; positive? tests +[not positive? negate $0] +[positive? $1e-128] +[not positive? -$1e-128] +[not negative? negate $0] +[not negative? $1e-128] +[negative? -$1e-128] +; same? tests +[same? $0 $0] +[same? $0 negate $0] +[same? $1 $1] +[not same? $1 $1.0] +["$1.0000000000000000000000000" = mold $2.0000000000000000000000000 - $1] +["$1" = mold $2 - $1] +["$1" = mold $1 * $1] +["$4" = mold $2 * $2] +["$1.0000000000000000000000000" = mold $1 * $1.0000000000000000000000000] +["$1.0000000000000000000000000" = mold $1.0000000000000000000000000 * $1.0000000000000000000000000] +; division uses "full precision" +["$1.0000000000000000000000000" = mold $1 / $1] +["$1.0000000000000000000000000" = mold $1 / $1.0] +["$1.0000000000000000000000000" = mold $1 / $1.000] +["$1.0000000000000000000000000" = mold $1 / $1.000000] +["$1.0000000000000000000000000" = mold $1 / $1.000000000] +["$1.0000000000000000000000000" = mold $1 / $1.000000000000] +["$1.0000000000000000000000000" = mold $1 / $1.0000000000000000000000000] +["$0.10000000000000000000000000" = mold $1 / $10] +["$0.33333333333333333333333333" = mold $1 / $3] +["$0.66666666666666666666666667" = mold $2 / $3] +; conversion to integer +[1 = to integer! $1] +#64bit +[-9223372036854775808 == to integer! -$9223372036854775808.99] +#64bit +[9223372036854775807 == to integer! $9223372036854775807.99] +; conversion to decimal +[1.0 = to decimal! $1] +[zero? 0.3 - to decimal! $0.3] +[zero? 0.1 - to decimal! $0.1] +[ + x: 9.9999999999999981e152 + zero? x - to decimal! to money! x +] +[ + x: -9.9999999999999981e152 + zero? x - to decimal! to money! x +] +[ + x: 9.9999999999999926E152 + zero? x - to decimal! to money! x +] +[ + x: -9.9999999999999926E152 + zero? x - to decimal! to money! x +] +[ + x: 9.9999999999999293E152 + zero? x - to decimal! to money! x +] +[ + x: -9.9999999999999293E152 + zero? x - to decimal! to money! x +] +[ + x: to decimal! $1e-128 + zero? x - to decimal! to money! x +] +[ + x: to decimal! -$1e-128 + zero? x - to decimal! to money! x +] +[ + x: 9.2233720368547758E18 + zero? x - to decimal! to money! x +] +[ + x: -9.2233720368547758E18 + zero? x - to decimal! to money! x +] +[ + x: 9.2233720368547748E18 + zero? x - to decimal! to money! x +] +[ + x: -9.2233720368547748E18 + zero? x - to decimal! to money! x +] +[ + x: 9.2233720368547779E18 + zero? x - to decimal! to money! x +] +[ + x: -9.2233720368547779E18 + zero? x - to decimal! to money! x +] diff --git a/tests/datatypes/native.test.reb b/tests/datatypes/native.test.reb new file mode 100644 index 0000000000..ee45f40418 --- /dev/null +++ b/tests/datatypes/native.test.reb @@ -0,0 +1,7 @@ +; datatypes/native.r +[function? :reduce] +[not function? 1] +[function! = type-of :reduce] +; bug#1659 +; natives are active +[same? blank! do reduce [:type-of make blank! blank]] diff --git a/tests/datatypes/none.test.reb b/tests/datatypes/none.test.reb new file mode 100644 index 0000000000..109bca4f7d --- /dev/null +++ b/tests/datatypes/none.test.reb @@ -0,0 +1,19 @@ +; datatypes/none.r +[blank? blank] +[not blank? 1] +[blank! = type-of blank] +; literal form +[blank = _] +; bug#845 +[blank = _] +[blank = #] ;-- Deprecated! +[blank = make blank! blank] +[blank = to blank! blank] ;-- only thing you can convert a blank to +[blank = to blank! 1] +["_" = mold blank] +; bug#1666 +; bug#1650 +[ + f: does [#] + # == f +] diff --git a/tests/datatypes/object.test.reb b/tests/datatypes/object.test.reb new file mode 100644 index 0000000000..7d86c7f832 --- /dev/null +++ b/tests/datatypes/object.test.reb @@ -0,0 +1,115 @@ +; datatypes/object.r +[object? make object! [x: 1]] +[not object? 1] +[object! = type-of make object! [x: 1]] +; minimum +[object? make object! []] +; literal form +[object? #[object! [[][]]]] +; local words +[ + x: 1 + make object! [x: 2] + x = 1 +] +; BREAK out of make object! +[ + #846 + blank? loop 1 [ + make object! [break] + 2 + ] +] +; THROW out of make object! +; bug#847 +[ + 1 = catch [ + make object! [throw 1] + 2 + ] +] +; "error out" of make object! +[ + error? try [ + make object! [1 / 0] + 2 + ] +] +; RETURN out of make object! +; bug#848 +[ + f: func [] [ + make object! [return 1] + 2 + ] + 1 = f +] +; object cloning +; bug#2045 +[ + a: 1 + f: func [] [a] + g: :f + o: make object! [a: 2 g: :f] + p: make o [a: 3] + 1 == p/g +] +; object cloning +; bug#2045 +[ + a: 1 + b: [a] + c: b + o: make object! [a: 2 c: b] + p: make o [a: 3] + 1 == do p/c +] +; multiple inheritance +; bug#1863 +[ + o1: make object! [a: 1 f: does [a]] + o2: make object! [a: 2] + o3: make o1 o2 + 2 == o3/f +] +; object cloning +; bug#2049 +[ + o: make object! [n: 'o f: closure [] [n]] + p: make o [n: 'p] + 'p = p/f +] +; appending to objects +; bug#1979 +[ + o: make object! [] + append o [b: 1 b: 2] + 1 == length? words-of o +] +[ + o: make object! [b: 0] + append o [b: 1 b: 2] + 1 == length? words-of o +] +[ + o: make object! [] + c: "c" + append o compose [b: "b" b: (c)] + same? c o/b +] +[ + o: make object! [b: "a"] + c: "c" + append o compose [b: "b" b: (c)] + same? c o/b +] +[ + o: make object! [] + append o 'self + true +] +[ + o: make object! [] + ; currently disallowed..."would expose or modify hidden values" + error? try [append o [self: 1]] +] diff --git a/tests/datatypes/op.test.reb b/tests/datatypes/op.test.reb new file mode 100644 index 0000000000..70a66cb898 --- /dev/null +++ b/tests/datatypes/op.test.reb @@ -0,0 +1,9 @@ +; datatypes/op.r +[lookback? '+] +[error? try [lookback? 1]] +[function? get '+] + +; #1934 +[error? try [do reduce [1 get '+ 2]]] +[3 = do reduce [:+ 1 2]] + diff --git a/tests/datatypes/pair.test.reb b/tests/datatypes/pair.test.reb new file mode 100644 index 0000000000..b3cd10c2ea --- /dev/null +++ b/tests/datatypes/pair.test.reb @@ -0,0 +1,15 @@ +; datatypes/pair.r +[pair? 1x2] +[not pair? 1] +[pair! = type-of 1x2] +[1x1 = make pair! 1] +[1x2 = make pair! [1 2]] +[1x1 = to pair! 1] +; bug#17 +[error? try [to pair! [0.4]]] +[1x2 = to pair! [1 2]] +["1x1" = mold 1x1] +; minimum +[pair? -2147483648x-2147483648] +; maximum +[pair? 2147483647x2147483647] diff --git a/tests/datatypes/paren.test.reb b/tests/datatypes/paren.test.reb new file mode 100644 index 0000000000..e97186e271 --- /dev/null +++ b/tests/datatypes/paren.test.reb @@ -0,0 +1,30 @@ +; datatypes/paren.r +[group? first [(1 + 1)]] +[not group? 1] +; minimum +[group! = type-of first [()]] +; alternative literal form +[strict-equal? first [()] first [#[group! [[] 1]]]] +[strict-equal? first [()] make group! 0] +[strict-equal? first [()] to group! []] +["()" == mold first [()]] +; parens are active +[ + a-value: first [(1)] + 1 == do reduce [:a-value] +] +; finite recursion +[ + num1: 4 + num2: 1 + fact: to group! [either num1 = 1 [num2] [num2: num1 * num2 num1: num1 - 1]] + insert/only tail last fact fact + 24 = do as block! fact +] +; bug#1665 +; infinite recursion +[ + fact: to group! [] + insert/only fact fact + error? try [do fact] +] diff --git a/tests/datatypes/path.test.reb b/tests/datatypes/path.test.reb new file mode 100644 index 0000000000..9758271749 --- /dev/null +++ b/tests/datatypes/path.test.reb @@ -0,0 +1,158 @@ +; datatypes/path.r +[path? 'a/b] +['a/b == first [a/b]] +[not path? 1] +[path! = type-of 'a/b] +; the minimum +; bug#1947 +[path? load "#[path! [[a] 1]]"] +[ + all [ + path? a: load "#[path! [[a b c] 2]]" + 2 == index? a + ] +] +["a/b" = mold 'a/b] +[ + a-word: 1 + data: #{0201} + 2 = data/:a-word +] +[ + blk: reduce [:abs 2] + 2 == blk/:abs +] +[ + blk: [#{} 2] + 2 == blk/#{} +] +[ + blk: reduce [charset "a" 3] + 3 == do reduce [to path! reduce ['blk charset "a"]] +] +[ + blk: [[] 3] + 3 == blk/#[block! [[] 1]] +] +[ + blk: [_ 3] + 3 == do [blk/_] +] +[ + blk: [blank 3] + 3 == do [blk/blank] +] +[ + a-value: 1/Jan/0000 + 0 == a-value/1 +] +[ + a-value: me@here.com + #"m" == a-value/1 +] +[ + a-value: make error! "" + blank? a-value/type +] +[ + a-value: make image! 1x1 + 0.0.0.255 == a-value/1 +] +[ + a-value: first ['a/b] + 'a == a-value/1 +] +[ + a-value: make object! [a: 1] + 1 == a-value/a +] +[ + a-value: 2x3 + 2 = a-value/1 +] +[ + a-value: first [(2)] + 2 == a-value/1 +] +[ + a-value: 'a/b + 'a == a-value/1 +] +[ + a-value: make port! http:// + blank? a-value/data +] +[ + a-value: first [a/b:] + 'a == a-value/1 +] +[ + a-value: "12" + #"1" == a-value/1 +] +[ + a-value: + #"t" == a-value/1 +] +[ + a-value: 2:03 + 2 == a-value/1 +] +[ + a-value: 1.2.3 + 1 == a-value/1 +] + +; Ren-C changed INTEGER! path picking to act as PICK, only ANY-STRING! and +; WORD! actually merge with a slash. +[ + a-value: file://a + #"f" = a-value/1 +] + +; calling functions through paths: function in object +[ + obj: make object! [fun: func [] [1]] + 1 == obj/fun +] +[ + obj: make object! [fun: func [/ref val] [val]] + 1 == obj/fun/ref 1 +] +; calling functions through paths: function in block, positional +[ + blk: reduce [func [] [10] func [] [20]] + 10 == blk/1 +] +; calling functions through paths: function in block, "named" +[ + blk: reduce ['foo func [] [10] 'bar func [] [20]] + 20 == blk/bar +] +; bug#26 +[ + b: [b 1] + 1 = b/b +] +; recursive path +[ + a: make object! [] + path: 'a/a + change/only back tail path path + error? try [do path] + true +] + +; bug#71 +[ + a: "abcd" + "abcd/x" = a/x +] + +; bug#1820: Word USER can't be selected with path syntax +[ + b: [user 1 _user 2] + 1 = b/user +] +; bug#1977 +[f: func [/r] [1] error? try [f/r/%]] diff --git a/tests/datatypes/percent.test.reb b/tests/datatypes/percent.test.reb new file mode 100644 index 0000000000..237df3522a --- /dev/null +++ b/tests/datatypes/percent.test.reb @@ -0,0 +1,56 @@ +; datatypes/percent.r +[percent? 0%] +[not percent? 1] +[percent! = type-of 0%] +[percent? 0.0%] +[percent? 1%] +[percent? -1.0%] +[percent? 2.2%] +[0% = make percent! 0] +[0% = make percent! "0"] +[0% = to percent! 0] +[0% = to percent! "0"] +[100% = to percent! 1] +[10% = to percent! 0.1] +[error? try [to percent! "t"]] +[0 = to decimal! 0%] +[0.1 = to decimal! 10%] +[1.0 = to decimal! 100%] +[0% = load mold 0.0%] +[1% = load mold 1.0%] +[1.1% = load mold 1.1%] +[-1% = load mold -1.0%] +; bug#57 +[-5% = negate 5%] +; bug#57 +[10% = (5% + 5%)] +; bug#57 +[6% = round 5.55%] +; bug#97 +[$59.0 = (10% * $590)] +; bug#97 +[$100.6 = ($100 + 60%)] +; 64-bit IEEE 754 maximum +; bug#1475 +; Minimal positive normalized +[same? 2.2250738585072014E-310% load mold/all 2.2250738585072014E-310%] +; Maximal positive denormalized +[same? 2.2250738585072009E-310% load mold/all 2.2250738585072009E-310%] +; Minimal positive denormalized +[same? 4.9406564584124654E-322% load mold/all 4.9406564584124654E-322%] +; Maximal negative normalized +[same? -2.2250738585072014E-306% load mold/all -2.2250738585072014E-306%] +; Minimal negative denormalized +[same? -2.2250738585072009E-306% load mold/all -2.2250738585072009E-306%] +; Maximal negative denormalized +[same? -4.9406564584124654E-322% load mold/all -4.9406564584124654E-322%] +[same? 10.000000000000001% load mold/all 10.000000000000001%] +[same? 29.999999999999999% load mold/all 29.999999999999999%] +[same? 30.000000000000004% load mold/all 30.000000000000004%] +[same? 9.9999999999999926e154% load mold/all 9.9999999999999926e154%] +; alternative form +[1.1% == 1,1%] +[110% = make percent! 110%] +[110% = make percent! "110%"] +[1.1% = to percent! 1.1%] +[1.1% = to percent! "1.1%"] diff --git a/tests/datatypes/port.test.reb b/tests/datatypes/port.test.reb new file mode 100644 index 0000000000..99828c0690 --- /dev/null +++ b/tests/datatypes/port.test.reb @@ -0,0 +1,4 @@ +; datatypes/port.r +[port? make port! http://] +[not port? 1] +[port! = type-of make port! http://] diff --git a/tests/datatypes/refinement.test.reb b/tests/datatypes/refinement.test.reb new file mode 100644 index 0000000000..d9b7bbe2a4 --- /dev/null +++ b/tests/datatypes/refinement.test.reb @@ -0,0 +1,4 @@ +; datatypes/refinement.r +[refinement? /a] +[not refinement? 1] +[refinement! = type-of /a] diff --git a/tests/datatypes/set-path.test.reb b/tests/datatypes/set-path.test.reb new file mode 100644 index 0000000000..49e4f2eeee --- /dev/null +++ b/tests/datatypes/set-path.test.reb @@ -0,0 +1,42 @@ +; datatypes/set-path.r +[set-path? first [a/b:]] +[not set-path? 1] +[set-path! = type-of first [a/b:]] +; the minimum +; bug#1947 +[set-path? load "#[set-path! [[a] 1]]"] +[ + all [ + set-path? a: load "#[set-path! [[a b c] 2]]" + 2 == index? a + ] +] +["a/b:" = mold first [a/b:]] +; set-paths are active +[ + a: make object! [b: _] + a/b: 5 + 5 == a/b +] +; bug#1 +[ + o: make object! [a: 0x0] + o/a/x: 71830 + o/a/x = 71830 +] +; set-path evaluation order +[ + a: 1x2 + a/x: (a: [x 4] 3) + any [ + a == 3x2 + a == [x 3] + ] +] +; bug#64 +[ + blk: [1] + i: 1 + blk/:i: 2 + blk = [2] +] diff --git a/tests/datatypes/set-word.test.reb b/tests/datatypes/set-word.test.reb new file mode 100644 index 0000000000..9bfc903c5d --- /dev/null +++ b/tests/datatypes/set-word.test.reb @@ -0,0 +1,33 @@ +; datatypes/set-word.r +[set-word? first [a:]] +[not set-word? 1] +[set-word! = type-of first [a:]] +; set-word is active +[ + a: :abs + equal? :a :abs +] +[ + a: #{} + equal? :a #{} +] +[ + a: charset "" + equal? :a charset "" +] +[ + a: [] + equal? a [] +] +[ + a: function! + equal? :a function! +] +; bug#1817 +[ + a: make map! [] + a/b: make object! [ + c: make map! [] + ] + integer? a/b/c/d: 1 +] diff --git a/tests/datatypes/string.test.reb b/tests/datatypes/string.test.reb new file mode 100644 index 0000000000..a07d630cb5 --- /dev/null +++ b/tests/datatypes/string.test.reb @@ -0,0 +1,149 @@ +; datatypes/string.r +[string? "ahoj"] +[not string? 1] +[string! = type-of "ahoj"] +; minimum +[string? ""] +; alternative literal form +["" == #[string! ""]] +["" == make string! 0] +["^@" = "^(00)"] +["^A" = "^(01)"] +["^B" = "^(02)"] +["^C" = "^(03)"] +["^D" = "^(04)"] +["^E" = "^(05)"] +["^F" = "^(06)"] +["^G" = "^(07)"] +["^H" = "^(08)"] +["^I" = "^(09)"] +["^J" = "^(0A)"] +["^K" = "^(0B)"] +["^L" = "^(0C)"] +["^M" = "^(0D)"] +["^N" = "^(0E)"] +["^O" = "^(0F)"] +["^P" = "^(10)"] +["^Q" = "^(11)"] +["^R" = "^(12)"] +["^S" = "^(13)"] +["^T" = "^(14)"] +["^U" = "^(15)"] +["^V" = "^(16)"] +["^W" = "^(17)"] +["^X" = "^(18)"] +["^Y" = "^(19)"] +["^Z" = "^(1A)"] +["^[" = "^(1B)"] +["^\" = "^(1C)"] +["^]" = "^(1D)"] +["^!" = "^(1E)"] +["^_" = "^(1F)"] +[" " = "^(20)"] +["!" = "^(21)"] +["^"" = "^(22)"] +["#" = "^(23)"] +["$" = "^(24)"] +["%" = "^(25)"] +["&" = "^(26)"] +["'" = "^(27)"] +["(" = "^(28)"] +[")" = "^(29)"] +["*" = "^(2A)"] +["+" = "^(2B)"] +["," = "^(2C)"] +["-" = "^(2D)"] +["." = "^(2E)"] +["/" = "^(2F)"] +["0" = "^(30)"] +["1" = "^(31)"] +["2" = "^(32)"] +["3" = "^(33)"] +["4" = "^(34)"] +["5" = "^(35)"] +["6" = "^(36)"] +["7" = "^(37)"] +["8" = "^(38)"] +["9" = "^(39)"] +[":" = "^(3A)"] +[";" = "^(3B)"] +["<" = "^(3C)"] +["=" = "^(3D)"] +[">" = "^(3E)"] +["?" = "^(3F)"] +["@" = "^(40)"] +["A" = "^(41)"] +["B" = "^(42)"] +["C" = "^(43)"] +["D" = "^(44)"] +["E" = "^(45)"] +["F" = "^(46)"] +["G" = "^(47)"] +["H" = "^(48)"] +["I" = "^(49)"] +["J" = "^(4A)"] +["K" = "^(4B)"] +["L" = "^(4C)"] +["M" = "^(4D)"] +["N" = "^(4E)"] +["O" = "^(4F)"] +["P" = "^(50)"] +["Q" = "^(51)"] +["R" = "^(52)"] +["S" = "^(53)"] +["T" = "^(54)"] +["U" = "^(55)"] +["V" = "^(56)"] +["W" = "^(57)"] +["X" = "^(58)"] +["Y" = "^(59)"] +["Z" = "^(5A)"] +["[" = "^(5B)"] +["\" = "^(5C)"] +["]" = "^(5D)"] +["^^" = "^(5E)"] +["_" = "^(5F)"] +["`" = "^(60)"] +["a" = "^(61)"] +["b" = "^(62)"] +["c" = "^(63)"] +["d" = "^(64)"] +["e" = "^(65)"] +["f" = "^(66)"] +["g" = "^(67)"] +["h" = "^(68)"] +["i" = "^(69)"] +["j" = "^(6A)"] +["k" = "^(6B)"] +["l" = "^(6C)"] +["m" = "^(6D)"] +["n" = "^(6E)"] +["o" = "^(6F)"] +["p" = "^(70)"] +["q" = "^(71)"] +["r" = "^(72)"] +["s" = "^(73)"] +["t" = "^(74)"] +["u" = "^(75)"] +["v" = "^(76)"] +["w" = "^(77)"] +["x" = "^(78)"] +["y" = "^(79)"] +["z" = "^(7A)"] +["{" = "^(7B)"] +["|" = "^(7C)"] +["}" = "^(7D)"] +["~" = "^(7E)"] +["^~" = "^(7F)"] +["^(null)" = "^(00)"] +["^(line)" = "^(0A)"] +["^/" = "^(0A)"] +["^(tab)" = "^(09)"] +["^-" = "^(09)"] +["^(page)" = "^(0C)"] +["^(esc)" = "^(1B)"] +["^(back)" = "^(08)"] +["^(del)" = "^(7f)"] +["ahoj" = #[string! "ahoj"]] +["1" = to string! 1] +[{""} = mold ""] diff --git a/tests/datatypes/symbol.test.reb b/tests/datatypes/symbol.test.reb new file mode 100644 index 0000000000..eb44148558 --- /dev/null +++ b/tests/datatypes/symbol.test.reb @@ -0,0 +1,11 @@ +; datatypes/symbol.r +[tag? ] +[not tag? 1] +[tag! = type-of ] +; minimum +[tag? #[tag! ""]] +[strict-equal? #[tag! ""] make tag! 0] +[strict-equal? #[tag! ""] to tag! ""] +["" == mold ] +; bug#2169 +["<ēee>" == mold <ēee>] diff --git a/tests/datatypes/time.test.reb b/tests/datatypes/time.test.reb new file mode 100644 index 0000000000..c5c220e85e --- /dev/null +++ b/tests/datatypes/time.test.reb @@ -0,0 +1,42 @@ +; datatypes/time.r +[time? 0:00] +[not time? 1] +[time! = type-of 0:00] +[0:0:10 = make time! 10] +[0:0:10 = to time! 10] +[error? try [to time! "a"]] +["0:00" = mold 0:00] +; small value +[ + any? [ + error? try [t: -596522:0:0 - 1:00] + t = load mold t + ] +] +; big value +[ + any? [ + error? try [t: 596522:0:0 + 1:00] + t = load mold t + ] +] +; strange value +[error? try [load "--596523:-14:-07.772224"]] +; minimal time +[time? -596523:14:07.999999999] +; maximal negative time +[negative? -0:0:0.000000001] +; minimal positive time +[positive? 0:0:0.000000001] +; maximal time +[time? 596523:14:07.999999999] +; bug#96 +[ + time: 1:23:45.6 + 1:23:45.7 = (time + 0.1) +] +; bug#96 +[ + time: 1:23:45.6 + 0:41:52.8 = (time * .5) +] diff --git a/tests/datatypes/tuple.test.reb b/tests/datatypes/tuple.test.reb new file mode 100644 index 0000000000..56b8536a3e --- /dev/null +++ b/tests/datatypes/tuple.test.reb @@ -0,0 +1,11 @@ +; datatypes/tuple.r +[tuple? 1.2.3] +[not tuple? 1] +[tuple! = type-of 1.2.3] +[1.2.3 = to tuple! [1 2 3]] +["1.2.3" = mold 1.2.3] +; minimum +[tuple? make tuple! []] +; maximum +[tuple? 255.255.255.255.255.255.255] +[error? try [load "255.255.255.255.255.255.255.255.255.255.255"]] diff --git a/tests/datatypes/typeset.test.reb b/tests/datatypes/typeset.test.reb new file mode 100644 index 0000000000..82720baa61 --- /dev/null +++ b/tests/datatypes/typeset.test.reb @@ -0,0 +1,30 @@ +; datatypes/typeset.r +[typeset? any-array!] +[typeset? to-typeset any-array!] +[typeset? any-path!] +[typeset? to-typeset any-path!] +[typeset? any-context!] +[typeset? to-typeset any-context!] +[typeset? any-string!] +[typeset? to-typeset any-string!] +[typeset? any-word!] +[typeset? to-typeset any-word!] +[typeset? immediate!] +[typeset? to-typeset immediate!] +[typeset? internal!] +[typeset? to-typeset internal!] +[typeset? any-number!] +[typeset? to-typeset any-number!] +[typeset? any-scalar!] +[typeset? to-typeset any-scalar!] +[typeset? any-series!] +[typeset? to-typeset any-series!] +[typeset? make typeset! [integer! blank!]] +[typeset? make typeset! reduce [integer! blank!]] +[typeset? to-typeset [integer! blank!]] +[typeset! = type-of any-series!] +; bug#92 +[ + x: to typeset! [] + not x = now +] diff --git a/tests/datatypes/unset.test.reb b/tests/datatypes/unset.test.reb new file mode 100644 index 0000000000..40e2457344 --- /dev/null +++ b/tests/datatypes/unset.test.reb @@ -0,0 +1,8 @@ +; datatypes/unset.r +[void? ()] +[blank? type-of ()] +[not void? 1] +; bug#68 +[void? try [a: ()]] +[error? try [a: () a]] +[not error? try [set/opt 'a ()]] diff --git a/tests/datatypes/url.test.reb b/tests/datatypes/url.test.reb new file mode 100644 index 0000000000..1295d9558d --- /dev/null +++ b/tests/datatypes/url.test.reb @@ -0,0 +1,10 @@ +; datatypes/url.r +[url? http://www.fm.tul.cz/~ladislav/rebol] +[not url? 1] +[url! = type-of http://www.fm.tul.cz/~ladislav/rebol] +; minimum; alternative literal form +[url? #[url! ""]] +[strict-equal? #[url! ""] make url! 0] +[strict-equal? #[url! ""] to url! ""] +["http://" = mold http://] +["http://a%2520b" = mold http://a%2520b] diff --git a/tests/datatypes/varargs.test.reb b/tests/datatypes/varargs.test.reb new file mode 100644 index 0000000000..f5db9a5d9f --- /dev/null +++ b/tests/datatypes/varargs.test.reb @@ -0,0 +1,69 @@ +[ + foo: func [x [integer! <...>]] [ + sum: 0 + while [not tail? x] [ + sum: sum + take x + ] + ] + y: (z: foo 1 2 3 | 4 5) + all [y = 5 | z = 6] +] +[ + foo: func [x [integer! <...>]] [make block! x] + [1 2 3 4] = foo 1 2 3 4 +] + +;-- !!! Chaining was removed, this test should be rethought or redesigned. +;[ +; alpha: func [x [integer! string! tag! <...>]] [ +; beta 1 2 (x) 3 4 +; ] +; beta: func ['x [integer! string! word! <...>]] [ +; reverse (make block! x) +; ] +; all [ +; [4 3 "back" "wards" 2 1] = alpha "wards" "back" +; | +; error? trap [alpha ] ;-- both checks are applied in chain +; | +; [4 3 other thing 2 1] = alpha thing other +; ] +;] + +[ + ;-- leaked VARARGS! cannot be accessed after call is over + error? trap [take eval (foo: func [x [integer! <...>]] [x])] +] + +[ + f: func [args [any-value! <...>]] [ + b: take args + either tail? args [b] ["not at end"] + ] + x: make varargs! [_] + blank? apply :f [args: x] +] + +[ + f: func [:look [<...>]][first look] + blank? apply 'f [look: make varargs! []] +] + +; Testing the variadic behavior of |> and <| is easier than rewriting tests +; here to do the same thing. + +[ + (value: 1 + 2 <| 30 + 40 () () ()) + value = 3 +][ + (value: 1 + 2 |> 30 + 40 () () ()) + value = 70 +][ + void? (<| 10) +][ + void? (10 |>) +][ + 2 = (1 |> 2 | 3 + 4 | 5 + 6) +][ + 1 = (1 <| 2 | 3 + 4 | 5 + 6) +] diff --git a/tests/datatypes/vector.test.reb b/tests/datatypes/vector.test.reb new file mode 100644 index 0000000000..ce8b3b46a6 --- /dev/null +++ b/tests/datatypes/vector.test.reb @@ -0,0 +1,24 @@ +; datatypes/vector.r +[vector? make vector! 0] +[vector? make vector! [integer! 8]] +[vector? make vector! [integer! 16]] +[vector? make vector! [integer! 32]] +[vector? make vector! [integer! 64]] +[0 = length? make vector! 0] +[1 = length? make vector! 1] +[1 = length? make vector! [integer! 32]] +[2 = length? make vector! 2] +[2 = length? make vector! [integer! 32 2]] +; bug#1538 +[10 = length? make vector! 10.5] +; bug#1213 +[error? try [make vector! -1]] +[0 = first make vector! [integer! 32]] +[all map-each x make vector! [integer! 32 16] [zero? x]] +[ + v: make vector! [integer! 32 3] + v/1: 10 + v/2: 20 + v/3: 30 + v = make vector! [integer! 32 [10 20 30]] +] diff --git a/tests/datatypes/word.test.reb b/tests/datatypes/word.test.reb new file mode 100644 index 0000000000..88c0d8dfed --- /dev/null +++ b/tests/datatypes/word.test.reb @@ -0,0 +1,159 @@ +; datatypes/word.r +[word? 'a] +[not word? 1] +[word! = type-of 'a] +; literal form +[word? first [a]] +; words are active; actions are word-active +[1 == abs -1] +[ + a-value: #{} + same? :a-value a-value +] +[ + a-value: charset "" + same? :a-value a-value +] +[ + a-value: [] + same? :a-value a-value +] +[ + a-value: blank! + same? :a-value a-value +] +[ + a-value: 1/Jan/0000 + same? :a-value a-value +] +[ + a-value: 0.0 + :a-value == a-value +] +[ + a-value: 1.0 + :a-value == a-value +] +[ + a-value: me@here.com + same? :a-value a-value +] +[ + error? a-value: try [1 / 0] + same? :a-value a-value +] +[ + a-value: %"" + same? :a-value a-value +] +; functions are word-active +[ + a-value: does [1] + 1 == a-value +] +[ + a-value: first [:a] + :a-value == a-value +] +[ + a-value: #"^@" + :a-value == a-value +] +[ + a-value: make image! 0x0 + same? :a-value a-value +] +[ + a-value: 0 + :a-value == a-value +] +[ + a-value: 1 + :a-value == a-value +] +[ + a-value: # + same? :a-value a-value +] +; lit-paths aren't word-active +[ + a-value: first ['a/b] + a-value == :a-value +] +; lit-words aren't word-active +[ + a-value: first ['a] + a-value == :a-value +] +[:true == true] +[:false == false] +[ + a-value: $1 + :a-value == a-value +] +; natives are word-active +[native! == type-of :reduce] +[:blank == blank] +; library test? +[ + a-value: make object! [] + same? :a-value a-value +] +[ + a-value: first [()] + same? :a-value a-value +] +[ + a-value: get '+ + (a-value 1 2) == 3 +] +[ + a-value: 0x0 + :a-value == a-value +] +[ + a-value: 'a/b + :a-value == a-value +] +[ + a-value: make port! http:// + port? a-value +] +[ + a-value: /a + :a-value == a-value +] +; routine test? +[ + a-value: first [a/b:] + :a-value == a-value +] +[ + a-value: first [a:] + :a-value == a-value +] +[ + a-value: "" + same? :a-value a-value +] +[ + a-value: make tag! "" + same? :a-value a-value +] +[ + a-value: 0:00 + same? :a-value a-value +] +[ + a-value: 0.0.0 + same? :a-value a-value +] +[ + unset 'a-value + e: try [a-value] + e/id = 'no-value +] +[ + a-value: 'a + :a-value == a-value +] diff --git a/tests/define/func.test.reb b/tests/define/func.test.reb new file mode 100644 index 0000000000..6326c1a8b8 --- /dev/null +++ b/tests/define/func.test.reb @@ -0,0 +1,15 @@ +; functions/define/func.r +; recursive safety +[ + f: func [return: [function!]] [ + func [x] [ + either x = 1 [ + eval f 2 + x = 1 + ][ + false + ] + ] + ] + eval f 1 +] diff --git a/tests/file/clean-path.test.reb b/tests/file/clean-path.test.reb new file mode 100644 index 0000000000..f2617f886f --- /dev/null +++ b/tests/file/clean-path.test.reb @@ -0,0 +1,3 @@ +; functions/file/clean-path.r +; bug#35 +[function? :clean-path] diff --git a/tests/file/existsq.test.reb b/tests/file/existsq.test.reb new file mode 100644 index 0000000000..c8b3dab5b4 --- /dev/null +++ b/tests/file/existsq.test.reb @@ -0,0 +1 @@ +; functions/file/existsq.r diff --git a/tests/file/file-typeq.test.reb b/tests/file/file-typeq.test.reb new file mode 100644 index 0000000000..6fbdb66a86 --- /dev/null +++ b/tests/file/file-typeq.test.reb @@ -0,0 +1,3 @@ +; functions/file/file-typeq.r +; bug#1651: "FILE-TYPE? should return NONE for unknown types" +[blank? file-type? %foo.0123456789bar0123456789] diff --git a/tests/file/make-dir.test.reb b/tests/file/make-dir.test.reb new file mode 100644 index 0000000000..af5b1312a3 --- /dev/null +++ b/tests/file/make-dir.test.reb @@ -0,0 +1,8 @@ +; functions/file/make-dir.r +; bug#1674 +[ + any [ + not error? e: try [make-dir %/folder-to-save-test-files] + e/type = 'access + ] +] diff --git a/tests/file/open.test.reb b/tests/file/open.test.reb new file mode 100644 index 0000000000..b7200e0fc9 --- /dev/null +++ b/tests/file/open.test.reb @@ -0,0 +1,10 @@ +; functions/file/open.r +; bug#1422: "Rebol crashes when opening the 128th port" +[ + error? try [ + repeat n 200 [ + try [close open open join-of tcp://localhost: n] + ] + ] + true +] diff --git a/tests/fixtures/rebol-logo.bmp b/tests/fixtures/rebol-logo.bmp new file mode 100644 index 0000000000..72282f94d8 Binary files /dev/null and b/tests/fixtures/rebol-logo.bmp differ diff --git a/tests/fixtures/rebol-logo.gif b/tests/fixtures/rebol-logo.gif new file mode 100644 index 0000000000..f213fc0eba Binary files /dev/null and b/tests/fixtures/rebol-logo.gif differ diff --git a/tests/fixtures/rebol-logo.jpg b/tests/fixtures/rebol-logo.jpg new file mode 100644 index 0000000000..aaa6a9a164 Binary files /dev/null and b/tests/fixtures/rebol-logo.jpg differ diff --git a/tests/fixtures/rebol-logo.png b/tests/fixtures/rebol-logo.png new file mode 100644 index 0000000000..7e262fec2d Binary files /dev/null and b/tests/fixtures/rebol-logo.png differ diff --git a/tests/fixtures/umlauts-utf16be.txt b/tests/fixtures/umlauts-utf16be.txt new file mode 100644 index 0000000000..066f66c4e0 Binary files /dev/null and b/tests/fixtures/umlauts-utf16be.txt differ diff --git a/tests/fixtures/umlauts-utf16le.txt b/tests/fixtures/umlauts-utf16le.txt new file mode 100644 index 0000000000..9683d1059a Binary files /dev/null and b/tests/fixtures/umlauts-utf16le.txt differ diff --git a/tests/fixtures/umlauts-utf32be.txt b/tests/fixtures/umlauts-utf32be.txt new file mode 100644 index 0000000000..572323d287 Binary files /dev/null and b/tests/fixtures/umlauts-utf32be.txt differ diff --git a/tests/fixtures/umlauts-utf32le.txt b/tests/fixtures/umlauts-utf32le.txt new file mode 100644 index 0000000000..1d4b02a3fc Binary files /dev/null and b/tests/fixtures/umlauts-utf32le.txt differ diff --git a/tests/fixtures/umlauts-utf8.txt b/tests/fixtures/umlauts-utf8.txt new file mode 100644 index 0000000000..aa3cf647e1 --- /dev/null +++ b/tests/fixtures/umlauts-utf8.txt @@ -0,0 +1 @@ +äöü \ No newline at end of file diff --git a/tests/fixtures/umlauts-utf8bom.txt b/tests/fixtures/umlauts-utf8bom.txt new file mode 100644 index 0000000000..d3bde47e8a --- /dev/null +++ b/tests/fixtures/umlauts-utf8bom.txt @@ -0,0 +1 @@ +äöü \ No newline at end of file diff --git a/tests/flags.r b/tests/flags.r new file mode 100644 index 0000000000..26db70e3d5 --- /dev/null +++ b/tests/flags.r @@ -0,0 +1,17 @@ +; the flag influences only the test immediately following it, +; if not explicitly stated otherwise + +#32bit +; the test is meant to be used only when integers are 32bit + +#64bit +; the test is meant to be used only when integers are 64bit + +#r2only +; the test is not meant to be used with the R3 interpreter + +#r3only +; the test is not meant to be used with the R2 interpreter + +#r3 +; the test can work with R2 if using R2/Forward, or with R3 diff --git a/tests/functions/adapt.test.reb b/tests/functions/adapt.test.reb new file mode 100644 index 0000000000..9d48607c44 --- /dev/null +++ b/tests/functions/adapt.test.reb @@ -0,0 +1,16 @@ +; better-than-nothing ADAPT tests + +[ + x: 10 + foo: adapt 'any [x: 20] + foo [1 2 3] + x = 20 +] +[ + capture: blank + foo: adapt 'any [capture: block] + all? [ + foo [1 2 3] + capture = [1 2 3] + ] +] diff --git a/tests/functions/apply.test.reb b/tests/functions/apply.test.reb new file mode 100644 index 0000000000..63ac0e7646 --- /dev/null +++ b/tests/functions/apply.test.reb @@ -0,0 +1,6 @@ +; better-than-nothing (New)APPLY tests + +[ + s: apply :append [series: [a b c] value: [d e] dup: true count: 2] + s = [a b c d e d e] +] diff --git a/tests/functions/chain.test.reb b/tests/functions/chain.test.reb new file mode 100644 index 0000000000..5ca4425fcd --- /dev/null +++ b/tests/functions/chain.test.reb @@ -0,0 +1,14 @@ +; better-than-nothing CHAIN tests + +[ + add-one: func [x] [x + 1] + mp-ad-ad: chain [:multiply | :add-one | :add-one] + 202 = (mp-ad-ad 10 20) +] +[ + add-one: func [x] [x + 1] + mp-ad-ad: chain [:multiply | :add-one | :add-one] + sub-one: specialize 'subtract [value2: 1] + mp-normal: chain [:mp-ad-ad | :sub-one | :sub-one] + 200 = (mp-normal 10 20) +] diff --git a/tests/functions/hijack.test.reb b/tests/functions/hijack.test.reb new file mode 100644 index 0000000000..74fb1b4254 --- /dev/null +++ b/tests/functions/hijack.test.reb @@ -0,0 +1,71 @@ +; better than-nothing HIJACK tests + +[ + foo: func [x] [x + 1] + another-foo: :foo + + old-foo: copy :foo + + all? [ + (old-foo 10) = 11 + hijack 'foo func [x] [(old-foo x) + 20] + (old-foo 10) = 11 + (foo 10) = 31 + (another-foo 10) = 31 + ] +] + + +; Hijacking and un-hijacking out from under specializations, as well as +; specializing hijacked functions afterward. +[ + three: func [x y z /available add-me] [ + x + y + z + either available [add-me] [0] + ] + step1: (three 10 20 30) ; 60 + + old-three: copy :three + + two-30: specialize 'three [z: 30] + step2: (two-30 10 20) ; 60 + + hijack 'three func [a b c /unavailable /available mul-me] [ + a * b * c * either available [mul-me] [1] + ] + + step3: (three 10 20 30) ; 6000 + step4: (two-30 10 20) ; 6000 + + step5: trap [three/unavailable 10 20 30] ; error + + step6: (three/available 10 20 30 40) ; 240000 + + step7: (two-30/available 10 20 40) ; 240000 + + one-20: specialize 'two-30 [y: 20] + + hijack 'three func [q r s] [ + q - r - s + ] + + step8: (one-20 10) ; -40 + + hijack 'three 'old-three + + step9: (three 10 20 30) ; 60 + + step10: (two-30 10 20) ; 60 + + all? [ + step1 = 60 + step2 = 60 + step3 = 6000 + step4 = 6000 + error? step5 + step6 = 240000 + step7 = 240000 + step8 = -40 + step9 = 60 + step10 = 60 + ] +] diff --git a/tests/functions/specialize.test.reb b/tests/functions/specialize.test.reb new file mode 100644 index 0000000000..870ec28d0c --- /dev/null +++ b/tests/functions/specialize.test.reb @@ -0,0 +1,12 @@ +; better-than-nothing SPECIALIZE tests + +[ + append-123: specialize :append [value: [1 2 3] only: true] + [a b c [1 2 3] [1 2 3]] = append-123/dup [a b c] 2 +] +[ + append-123: specialize :append [value: [1 2 3] only: true] + append-123-twice: specialize :append-123 [dup: true count: 2] + [a b c [1 2 3] [1 2 3]] = append-123-twice [a b c] +] + diff --git a/tests/lib/text-lines.reb b/tests/lib/text-lines.reb new file mode 100644 index 0000000000..85ca1c830d --- /dev/null +++ b/tests/lib/text-lines.reb @@ -0,0 +1,128 @@ +REBOL [ + Title: "Text Lines" + Version: 1.0.0 + Rights: { + Copyright 2015 Brett Handley + + Rebol3 load-next by Chris Ross-Gill. + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Author: "Brett Handley" + Purpose: {Functions operating on lines of text.} +] + +decode-lines: function [ + {Decode text previously encoded using a line prefix, e.g. comments (modifies).} + text [string!] + line-prefix [string!] {Usually "**" or "//".} + indent [string!] {Usually " ".} +] [ + if not parse text [any [line-prefix thru newline]] [ + fail [{decode-lines expects each line to begin with} (mold line-prefix) { and finish with a newline.}] + ] + insert text newline + replace/all text join-of newline line-prefix newline + if not empty? indent [ + replace/all text join-of newline indent newline + ] + remove text + remove back tail text + text +] + +encode-lines: func [ + {Encode text using a line prefix, e.g. comments (modifies).} + text [string!] + line-prefix [string!] {Usually "**" or "//".} + indent [string!] {Usually " ".} + /local bol pos +] [ + + ; Note: Preserves newline formatting of the block. + + ; Encode newlines. + replace/all text newline unspaced [newline line-prefix indent] + + ; Indent head if original text did not start with a newline. + pos: insert text line-prefix + if not equal? newline pos/1 [insert pos indent] + + ; Clear indent from tail if present. + if indent = pos: skip tail text 0 - length-of indent [clear pos] + append text newline + + text +] + +for-each-line: func [ + {Iterate over text lines.} + 'record [word!] {Word set to metadata for each line.} + text [string!] {Text with lines.} + body [block!] {Block to evaluate each time.} + /local eol +] [ + + set/opt 'result while [not tail? text] [ + + eol: any [ + find text newline + tail text + ] + + set record compose [position (text) length (subtract index-of eol index-of text)] + text: next eol + + do body + ] + + get/opt 'result +] + +lines-exceeding: function [ + {Return the line numbers of lines exceeding line-length.} + line-length [integer!] + text [string!] +] [ + + line-list: line: _ + + count-line: [ + ( + line: 1 + any [line 0] + if line-length < subtract index-of eol index-of bol [ + append line-list: any [line-list copy []] line + ] + ) + ] + + parse text [ + any [bol: to newline eol: skip count-line] + bol: skip to end eol: count-line + ] + + line-list +] + +line-of: function [ + {Returns line number of position within text.} + text [string! binary!] + position [string! binary! integer!] +] [ + + if integer? position [ + position: at text position + ] + + line: _ + + count-line: [(line: 1 + any [line 0])] + + parse copy/part text next position [ + any [to newline skip count-line] skip count-line + ] + + line +] diff --git a/tests/line-numberq.r b/tests/line-numberq.r new file mode 100644 index 0000000000..43135bf84c --- /dev/null +++ b/tests/line-numberq.r @@ -0,0 +1,28 @@ +Rebol [ + Title: "Line number" + File: %line-numberq.r + Copyright: [2012 "Saphirion AG"] + License: { + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + } + Author: "Ladislav Mecir" + Purpose: "Compute the line number" +] + +line-number?: func [ + s [string! binary!] + /local t line-number +] [ + line-number: 1 + t: head s + parse t [ + any [ + (if greater-or-equal? index? t index? s [return line-number]) + [[crlf | cr | lf] (line-number: line-number + 1) | skip] t: + ] + ] +] diff --git a/tests/log-diff.r b/tests/log-diff.r new file mode 100644 index 0000000000..9e9efe71c8 --- /dev/null +++ b/tests/log-diff.r @@ -0,0 +1,201 @@ +Rebol [ + Title: "Log diff" + File: %log-diff.r + Copyright: [2012 "Saphirion AG"] + License: { + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + } + Author: "Ladislav Mecir" + Purpose: "Test framework" +] + +do %test-parsing.r + +make-diff: func [ + old-log [file!] + new-log [file!] + diff-file [file!] + /local old-log-contents new-log-contents + old-test old-result new-test new-result + new-successes new-failures new-crashes + progressions regressions removed summary + next-old-log next-new-log +] [ + if exists? diff-file [delete diff-file] + + collect-logs old-log-contents: copy [] old-log + collect-logs new-log-contents: copy [] new-log + + sort/case/skip old-log-contents 2 + sort/case/skip new-log-contents 2 + + ; counter initialization + new-successes: + new-failures: + new-crashes: + progressions: + regressions: + removed: + unchanged: + 0 + + ; cycle initialization + set [old-test old-result] old-log-contents + old-log-contents: skip old-log-contents 2 + + set [new-test new-result] new-log-contents + new-log-contents: skip new-log-contents 2 + + while [any [old-test new-test]] [ + case [ + all [ + new-test + new-result <> 'skipped + any [ + blank? old-test + all [ + strict-not-equal? old-test new-test + old-test == second sort/case reduce [new-test old-test] + ] + all [ + old-test == new-test + old-result = 'skipped + ] + ] + ] [ + ; fresh test + write/append diff-file spaced [ + new-test + switch new-result [ + succeeded [ + new-successes: new-successes + 1 + "succeeded" + ] + failed [ + new-failures: new-failures + 1 + "failed" + ] + crashed [ + new-crashes: new-crashes + 1 + "crashed" + ] + ] + newline + ] + ] + all [ + old-test + old-result <> 'skipped + any [ + blank? new-test + all [ + strict-not-equal? new-test old-test + new-test == second sort/case reduce [old-test new-test] + ] + all [ + new-test == old-test + new-result = 'skipped + ] + ] + ] [ + ; removed test + removed: removed + 1 + write/append diff-file spaced [old-test "removed" newline] + ] + any [ + old-result = new-result + strict-not-equal? old-test new-test + ] [unchanged: unchanged + 1] + ; having one test with different results + ( + write/append diff-file new-test + any [ + old-result = 'succeeded + all [ + old-result = 'failed + new-result = 'crashed + ] + ] + ) [ + ; regression + regressions: regressions + 1 + write/append diff-file spaced [ + space "regression," new-result newline + ] + ] + ] + else [ + ; progression + progressions: progressions + 1 + write/append diff-file spaced [ + space "progression," new-result newline + ] + ] + + next-old-log: all [ + old-test + any [ + blank? new-test + old-test == first sort/case reduce [old-test new-test] + ] + ] + next-new-log: all [ + new-test + any [ + blank? old-test + new-test == first sort/case reduce [new-test old-test] + ] + ] + if next-old-log [ + if old-test == pick old-log-contents 1 [ + print old-test + do make error! {duplicate test in old-log} + ] + set [old-test old-result] old-log-contents + old-log-contents: skip old-log-contents 2 + ] + if next-new-log [ + if new-test == pick new-log-contents 1 [ + print new-test + do make error! {duplicate test in new-log} + ] + set [new-test new-result] new-log-contents + new-log-contents: skip new-log-contents 2 + ] + ] + + print "Done." + + summary: spaced [ + "new-successes:" new-successes + | + "new-failures:" new-failures + | + "new-crashes:" new-crashes + | + "progressions:" progressions + | + "regressions:" regressions + | + "removed:" removed + | + "unchanged:" unchanged + | + "total:" + new-successes + new-failures + new-crashes + progressions + + regressions + removed + unchanged + ] + print summary + + write/append diff-file unspaced [ + newline + "Summary:" newline + summary newline + ] +] + +make-diff to-file first load system/script/args to-file second load system/script/args %diff.r diff --git a/tests/log-filter.r b/tests/log-filter.r new file mode 100644 index 0000000000..ef97fb5030 --- /dev/null +++ b/tests/log-filter.r @@ -0,0 +1,42 @@ +Rebol [ + Title: "Log filter" + File: %log-filter.r + Copyright: [2012 "Saphirion AG"] + License: { + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + } + Author: "Ladislav Mecir" + Purpose: "Test framework" +] + +do %test-parsing.r + +log-filter: func [ + source-log [file!] + /local source-log-contents target-log +] [ + ; if the source log is r_2_7_8_3_1_1DEF65_002052.log + ; the target log will be f_2_7_8_3_1_1DEF65_002052.log + ; , i.e., using the "f" prefix + target-log: copy source-log + change target-log %f + + if exists? target-log [delete target-log] + + collect-logs source-log-contents: copy [] source-log + + for-each [source-test source-result] source-log-contents [ + if find [crashed failed] source-result [ + ; test failure + write/append target-log spaced [ + source-test _ mold source-result _ newline + ] + ] + ] +] + +log-filter to-file system/script/args diff --git a/tests/math/absolute.test.reb b/tests/math/absolute.test.reb new file mode 100644 index 0000000000..84a726c73d --- /dev/null +++ b/tests/math/absolute.test.reb @@ -0,0 +1,33 @@ +; functions/math/absolute.r +[:abs = :absolute] +[0 = abs 0] +[1 = abs 1] +[1 = abs -1] +[2147483647 = abs 2147483647] +[2147483647 = abs -2147483647] +[0.0 = abs 0.0] +[zero? 1.0 - abs 1.0] +[zero? 1.0 - abs -1.0] +; simple tests verify correct args and refinements; integer tests +#64bit +[9223372036854775807 = abs 9223372036854775807] +#64bit +[9223372036854775807 = abs -9223372036854775807] +; pair! tests +[0x0 = abs 0x0] +[0x1 = abs 0x1] +[1x0 = abs 1x0] +[1x1 = abs 1x1] +[0x1 = abs 0x-1] +[1x0 = abs -1x0] +[1x1 = abs -1x-1] +[2147483647x2147483647 = abs 2147483647x2147483647] +[2147483647x2147483647 = abs 2147483647x-2147483647] +[2147483647x2147483647 = abs -2147483647x2147483647] +[2147483647x2147483647 = abs -2147483647x-2147483647] +; bug#833 +#64bit +[ + a: try [abs to integer! #{8000000000000000}] + any [error? a not negative? a] +] diff --git a/tests/math/add.test.reb b/tests/math/add.test.reb new file mode 100644 index 0000000000..924b66a1aa --- /dev/null +++ b/tests/math/add.test.reb @@ -0,0 +1,223 @@ +; functions/math/add.r +[3 = add 1 2] +; integer -9223372036854775808 + x tests +#64bit +[error? try [add -9223372036854775808 -9223372036854775808]] +#64bit +[error? try [add -9223372036854775808 -9223372036854775807]] +#64bit +[error? try [add -9223372036854775808 -2147483648]] +#64bit +[error? try [add -9223372036854775808 -1]] +#64bit +[-9223372036854775808 = add -9223372036854775808 0] +#64bit +[-9223372036854775807 = add -9223372036854775808 1] +#64bit +[-2 = add -9223372036854775808 9223372036854775806] +#64bit +[-1 = add -9223372036854775808 9223372036854775807] +; integer -9223372036854775807 + x tests +#64bit +[error? try [add -9223372036854775807 -9223372036854775808]] +#64bit +[error? try [add -9223372036854775807 -9223372036854775807]] +#64bit +[-9223372036854775808 = add -9223372036854775807 -1] +#64bit +[-9223372036854775807 = add -9223372036854775807 0] +#64bit +[-9223372036854775806 = add -9223372036854775807 1] +#64bit +[-1 = add -9223372036854775807 9223372036854775806] +#64bit +[0 = add -9223372036854775807 9223372036854775807] +; integer -2147483648 + x tests +#32bit +[error? try [add -2147483648 -2147483648]] +#64bit +[-4294967296 = add -2147483648 -2147483648] +#32bit +[error? try [add -2147483648 -1]] +#64bit +[-2147483649 = add -2147483648 -1] +[-2147483648 = add -2147483648 0] +[-2147483647 = add -2147483648 1] +[-1 = add -2147483648 2147483647] +; integer -1 + x tests +#64bit +[error? try [add -1 -9223372036854775808]] +#64bit +[-9223372036854775808 = add -1 -9223372036854775807] +[-2 = add -1 -1] +[-1 = add -1 0] +[0 = add -1 1] +#64bit +[9223372036854775805 = add -1 9223372036854775806] +#64bit +[9223372036854775806 = add -1 9223372036854775807] +; integer 0 + x tests +#64bit +[-9223372036854775808 = add 0 -9223372036854775808] +#64bit +[-9223372036854775807 = add 0 -9223372036854775807] +[-1 = add 0 -1] +; bug#28 +[0 = add 0 0] +[1 = add 0 1] +#64bit +[9223372036854775806 = add 0 9223372036854775806] +#64bit +[9223372036854775807 = add 0 9223372036854775807] +; integer 1 + x tests +#64bit +[-9223372036854775807 = add 1 -9223372036854775808] +#64bit +[-9223372036854775806 = add 1 -9223372036854775807] +[0 = add 1 -1] +[1 = add 1 0] +[2 = add 1 1] +#64bit +[9223372036854775807 = add 1 9223372036854775806] +#64bit +[error? try [add 1 9223372036854775807]] +; integer 2147483647 + x +[-1 = add 2147483647 -2147483648] +[2147483646 = add 2147483647 -1] +[2147483647 = add 2147483647 0] +#32bit +[error? try [add 2147483647 1]] +#64bit +[2147483648 = add 2147483647 1] +#32bit +[error? try [add 2147483647 2147483647]] +#64bit +[4294967294 = add 2147483647 2147483647] +; integer 9223372036854775806 + x tests +#64bit +[-2 = add 9223372036854775806 -9223372036854775808] +#64bit +[-1 = add 9223372036854775806 -9223372036854775807] +#64bit +[9223372036854775805 = add 9223372036854775806 -1] +#64bit +[9223372036854775806 = add 9223372036854775806 0] +#64bit +[9223372036854775807 = add 9223372036854775806 1] +#64bit +[error? try [add 9223372036854775806 9223372036854775806]] +#64bit +[error? try [add 9223372036854775806 9223372036854775807]] +; integer 9223372036854775807 + x tests +#64bit +[-1 = add 9223372036854775807 -9223372036854775808] +#64bit +[0 = add 9223372036854775807 -9223372036854775807] +#64bit +[9223372036854775806 = add 9223372036854775807 -1] +#64bit +[9223372036854775807 = add 9223372036854775807 0] +#64bit +[error? try [add 9223372036854775807 1]] +#64bit +[error? try [add 9223372036854775807 9223372036854775806]] +#64bit +[error? try [add 9223372036854775807 9223372036854775807]] +; decimal + integer +[2.1 = add 1.1 1] +[2147483648.0 = add 1.0 2147483647] +[-2147483649.0 = add -1.0 -2147483648] +; integer + decimal +[2.1 = add 1 1.1] +[2147483648.0 = add 2147483647 1.0] +[-2147483649.0 = add -2147483648 -1.0] +; -1.7976931348623157e308 + decimal +[error? try [add -1.7976931348623157e308 -1.7976931348623157e308]] +[-1.7976931348623157e308 = add -1.7976931348623157e308 -1.0] +[-1.7976931348623157e308 = add -1.7976931348623157e308 -4.94065645841247E-324] +[-1.7976931348623157e308 = add -1.7976931348623157e308 0.0] +[-1.7976931348623157e308 = add -1.7976931348623157e308 4.94065645841247E-324] +[-1.7976931348623157e308 = add -1.7976931348623157e308 1.0] +[0.0 = add -1.7976931348623157e308 1.7976931348623157e308] +; -1.0 + decimal +[-1.7976931348623157e308 = add -1.0 -1.7976931348623157e308] +[-2.0 = add -1.0 -1.0] +[-1.0 = add -1.0 -4.94065645841247E-324] +[-1.0 = add -1.0 0.0] +[-1.0 = add -1.0 4.94065645841247E-324] +[0.0 = add -1.0 1.0] +[1.7976931348623157e308 = add -1.0 1.7976931348623157e308] +; -4.94065645841247E-324 + decimal +[-1.7976931348623157e308 = add -4.94065645841247E-324 -1.7976931348623157e308] +[-1.0 = add -4.94065645841247E-324 -1.0] +[-9.88131291682493e-324 = add -4.94065645841247E-324 -4.94065645841247E-324] +[-4.94065645841247E-324 = add -4.94065645841247E-324 0.0] +[0.0 = add -4.94065645841247E-324 4.94065645841247E-324] +[1.0 = add -4.94065645841247E-324 1.0] +[1.7976931348623157e308 = add -4.94065645841247E-324 1.7976931348623157e308] +; 0.0 + decimal +[-1.7976931348623157e308 = add 0.0 -1.7976931348623157e308] +[-1.0 = add 0.0 -1.0] +[-4.94065645841247E-324 = add 0.0 -4.94065645841247E-324] +[0.0 = add 0.0 0.0] +[4.94065645841247E-324 = add 0.0 4.94065645841247E-324] +[1.0 = add 0.0 1.0] +[1.7976931348623157e308 = add 0.0 1.7976931348623157e308] +; 4.94065645841247E-324 + decimal +[-1.7976931348623157e308 = add 4.94065645841247E-324 -1.7976931348623157e308] +[-1.0 = add 4.94065645841247E-324 -1.0] +[0.0 = add 4.94065645841247E-324 -4.94065645841247E-324] +[4.94065645841247E-324 = add 4.94065645841247E-324 0.0] +[9.88131291682493e-324 = add 4.94065645841247E-324 4.94065645841247E-324] +[1.0 = add 4.94065645841247E-324 1.0] +[1.7976931348623157e308 = add 4.94065645841247E-324 1.7976931348623157e308] +; 1.0 + decimal +[-1.7976931348623157e308 = add 1.0 -1.7976931348623157e308] +[0.0 = add 1.0 -1.0] +[1.0 = add 1.0 4.94065645841247E-324] +[1.0 = add 1.0 0.0] +[1.0 = add 1.0 -4.94065645841247E-324] +[2.0 = add 1.0 1.0] +[1.7976931348623157e308 = add 1.0 1.7976931348623157e308] +; 1.7976931348623157e308 + decimal +[0.0 = add 1.7976931348623157e308 -1.7976931348623157e308] +[1.7976931348623157e308 = add 1.7976931348623157e308 -1.0] +[1.7976931348623157e308 = add 1.7976931348623157e308 -4.94065645841247E-324] +[1.7976931348623157e308 = add 1.7976931348623157e308 0.0] +[1.7976931348623157e308 = add 1.7976931348623157e308 4.94065645841247E-324] +[1.7976931348623157e308 = add 1.7976931348623157e308 1.0] +[error? try [add 1.7976931348623157e308 1.7976931348623157e308]] +; pair +[-2147483648x-2147483648 = add -2147483648x-2147483648 0x0] +[-2x-2 = add -1x-1 -1x-1] +[-1x-1 = add -1x-1 0x0] +[0x0 = add -1x-1 1x1] +[-2147483648x-2147483648 = add 0x0 -2147483648x-2147483648] +[-1x-1 = add 0x0 -1x-1] +[0x0 = add 0x0 0x0] +[1x1 = add 0x0 1x1] +[2147483647x2147483647 = add 0x0 2147483647x2147483647] +[0x0 = add 1x1 -1x-1] +[1x1 = add 1x1 0x0] +[2x2 = add 1x1 1x1] +[2147483647x2147483647 = add 2147483647x2147483647 0x0] +; pair + ... +[error? try [0x0 + blank]] +[error? try [0x0 + ""]] +; char +[#"^(00)" = add #"^(00)" #"^(00)"] +[#"^(01)" = add #"^(00)" #"^(01)"] +[#"^(ff)" = add #"^(00)" #"^(ff)"] +[#"^(01)" = add #"^(01)" #"^(00)"] +[#"^(02)" = add #"^(01)" #"^(01)"] +[#"^(ff)" = add #"^(ff)" #"^(00)"] +; tuple +[0.0.0 = add 0.0.0 0.0.0] +[0.0.1 = add 0.0.0 0.0.1] +[0.0.255 = add 0.0.0 0.0.255] +[0.0.1 = add 0.0.1 0.0.0] +[0.0.2 = add 0.0.1 0.0.1] +[0.0.255 = add 0.0.1 0.0.255] +[0.0.255 = add 0.0.255 0.0.0] +[0.0.255 = add 0.0.255 0.0.1] +[0.0.255 = add 0.0.255 0.0.255] diff --git a/tests/math/and.test.reb b/tests/math/and.test.reb new file mode 100644 index 0000000000..39ea95c75a --- /dev/null +++ b/tests/math/and.test.reb @@ -0,0 +1,47 @@ +; functions/math/and.r +[true and* true = true] +[true and* false = false] +[false and* true = false] +[false and* false = false] +; integer +[1 and* 1 = 1] +[1 and* 0 = 0] +[0 and* 1 = 0] +[0 and* 0 = 0] +[1 and* 2 = 0] +[2 and* 1 = 0] +[2 and* 2 = 2] +; char +[#"^(00)" and* #"^(00)" = #"^(00)"] +[#"^(01)" and* #"^(00)" = #"^(00)"] +[#"^(00)" and* #"^(01)" = #"^(00)"] +[#"^(01)" and* #"^(01)" = #"^(01)"] +[#"^(01)" and* #"^(02)" = #"^(00)"] +[#"^(02)" and* #"^(02)" = #"^(02)"] +; tuple +[0.0.0 and* 0.0.0 = 0.0.0] +[1.0.0 and* 1.0.0 = 1.0.0] +[2.0.0 and* 2.0.0 = 2.0.0] +[255.255.255 and* 255.255.255 = 255.255.255] +; binary +[#{030000} and* #{020000} = #{020000}] +[0 = arccosine 1] +[0 = arccosine/radians 1] +[30 = arccosine (square-root 3) / 2] +[(pi / 6) = arccosine/radians (square-root 3) / 2] +[45 = arccosine (square-root 2) / 2] +[(pi / 4) = arccosine/radians (square-root 2) / 2] +[60 = arccosine 0.5] +[(pi / 3) = arccosine/radians 0.5] +[90 = arccosine 0] +[(pi / 2) = arccosine/radians 0] +[180 = arccosine -1] +[pi = arccosine/radians -1] +[150 = arccosine (square-root 3) / -2] +[((pi * 5) / 6) = arccosine/radians (square-root 3) / -2] +[135 = arccosine (square-root 2) / -2] +[((pi * 3) / 4) = arccosine/radians (square-root 2) / -2] +[120 = arccosine -0.5] +[((pi * 2) / 3) = arccosine/radians -0.5] +[error? try [arccosine 1.1]] +[error? try [arccosine -1.1]] diff --git a/tests/math/arcsine.test.reb b/tests/math/arcsine.test.reb new file mode 100644 index 0000000000..a81a886a4b --- /dev/null +++ b/tests/math/arcsine.test.reb @@ -0,0 +1,23 @@ +; functions/math/arcsine.r +[0 = arcsine 0] +[0 = arcsine/radians 0] +[30 = arcsine 0.5] +[(pi / 6) = arcsine/radians 0.5] +[45 = arcsine (square-root 2) / 2] +[(pi / 4) = arcsine/radians (square-root 2) / 2] +[60 = arcsine (square-root 3) / 2] +[(pi / 3) = arcsine/radians (square-root 3) / 2] +[90 = arcsine 1] +[(pi / 2) = arcsine/radians 1] +[-30 = arcsine -0.5] +[(pi / -6) = arcsine/radians -0.5] +[-45 = arcsine (square-root 2) / -2] +[(pi / -4) = arcsine/radians (square-root 2) / -2] +[-60 = arcsine (square-root 3) / -2] +[(pi / -3) = arcsine/radians (square-root 3) / -2] +[-90 = arcsine -1] +[(pi / -2) = arcsine/radians -1] +[(1e-12 / (arcsine 1e-12)) = (pi / 180)] +[(1e-9 / (arcsine/radians 1e-9)) = 1.0] +[error? try [arcsine 1.1]] +[error? try [arcsine -1.1]] diff --git a/tests/math/arctangent.test.reb b/tests/math/arctangent.test.reb new file mode 100644 index 0000000000..8294d69635 --- /dev/null +++ b/tests/math/arctangent.test.reb @@ -0,0 +1,19 @@ +; functions/math/arctangent.r +[-90 = arctangent -1e16] +[(pi / -2) = arctangent/radians -1e16] +[-60 = arctangent negate square-root 3] +[(pi / -3) = arctangent/radians negate square-root 3] +[-45 = arctangent -1] +[(pi / -4) = arctangent/radians -1] +[-30 = arctangent (square-root 3) / -3] +[(pi / -6) = arctangent/radians (square-root 3) / -3] +[0 = arctangent 0] +[0 = arctangent/radians 0] +[30 = arctangent (square-root 3) / 3] +[(pi / 6) = arctangent/radians (square-root 3) / 3] +[45 = arctangent 1] +[(pi / 4) = arctangent/radians 1] +[60 = arctangent square-root 3] +[(pi / 3) = arctangent/radians square-root 3] +[90 = arctangent 1e16] +[(pi / 2) = arctangent/radians 1e16] diff --git a/tests/math/complement.test.reb b/tests/math/complement.test.reb new file mode 100644 index 0000000000..8b248f4d9f --- /dev/null +++ b/tests/math/complement.test.reb @@ -0,0 +1,29 @@ +; functions/math/complement.r +; bug#849 +[false = complement true] +[true = complement false] +; integer +[-1 = complement 0] +[0 = complement -1] +[2147483647 = complement -2147483648] +[-2147483648 = complement 2147483647] +[255.255.255 = complement 0.0.0] +[0.0.0 = complement 255.255.255] +; binary +[#{ffffffffff} = complement #{0000000000}] +[#{0000000000} = complement #{ffffffffff}] +[not find complement charset "b" #"b"] +[find complement charset "a" #"b"] +[ + a: make bitset! #{0000000000000000000000000000000000000000000000000000000000000000} + a == complement complement a +] +[ + a: make bitset! #{FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF} + a == complement complement a +] +; bug#1706 +; image +[(make image! [1x1 #{000000} #{00}]) = complement make image! [1x1 #{ffffff} #{ff}]] +[(make image! [1x1 #{ffffff} #{ff}]) = complement make image! [1x1 #{000000} #{00}]] +; typeset diff --git a/tests/math/cosine.test.reb b/tests/math/cosine.test.reb new file mode 100644 index 0000000000..62cac420ec --- /dev/null +++ b/tests/math/cosine.test.reb @@ -0,0 +1,19 @@ +; functions/math/cosine.r +[1 = cosine 0] +[1 = cosine/radians 0] +[((square-root 3) / 2) = cosine 30] +[((square-root 3) / 2) = cosine/radians pi / 6] +[((square-root 2) / 2) = cosine 45] +[((square-root 2) / 2) = cosine/radians pi / 4] +[0.5 = cosine 60] +[0.5 = cosine/radians pi / 3] +[0 = cosine 90] +[0 = cosine/radians pi / 2] +[-1 = cosine 180] +[-1 = cosine/radians pi] +[((square-root 3) / -2) = cosine 150] +[((square-root 3) / -2) = cosine/radians pi * 5 / 6] +[((square-root 2) / -2) = cosine 135] +[((square-root 2) / -2) = cosine/radians pi * 3 / 4] +[-0.5 = cosine 120] +[-0.5 = cosine/radians pi * 2 / 3] diff --git a/tests/math/difference.test.reb b/tests/math/difference.test.reb new file mode 100644 index 0000000000..470b8e3cb7 --- /dev/null +++ b/tests/math/difference.test.reb @@ -0,0 +1,10 @@ +; functions/math/difference.r +[24:00 = difference 1/Jan/2007 31/Dec/2006] +[0:00 = difference 1/Jan/2007 1/Jan/2007] +; block +[[1 2] = difference [1 3] [2 3]] +[[] = difference [1 2] [1 2]] +; bitset +[(charset "a") = difference charset "a" charset ""] +; bug#1822: DIFFERENCE on date!s problem +[12:00 = difference 13/1/2011/12:00 13/1/2011] diff --git a/tests/math/divide.test.reb b/tests/math/divide.test.reb new file mode 100644 index 0000000000..514562f7f5 --- /dev/null +++ b/tests/math/divide.test.reb @@ -0,0 +1,67 @@ +; functions/math/divide.r +[1 == divide -2147483648 -2147483648] +[2 == divide -2147483648 -1073741824] +[1073741824 == divide -2147483648 -2] +#32bit +[error? try [divide -2147483648 -1]] +[error? try [divide -2147483648 0]] +[-2147483648 == divide -2147483648 1] +[-1073741824 == divide -2147483648 2] +[-2 == divide -2147483648 1073741824] +[0.5 == divide -1073741824 -2147483648] +[1 == divide -1073741824 -1073741824] +[536870912 == divide -1073741824 -2] +[1073741824 == divide -1073741824 -1] +[error? try [divide -1073741824 0]] +[-1073741824 == divide -1073741824 1] +[-536870912 == divide -1073741824 2] +[-1 == divide -1073741824 1073741824] +[1 == divide -2 -2] +[2 == divide -2 -1] +[error? try [divide -2 0]] +[-2 == divide -2 1] +[-1 == divide -2 2] +[0.5 == divide -1 -2] +[1 == divide -1 -1] +[error? try [divide -1 0]] +[-1 == divide -1 1] +[-0.5 == divide -1 2] +[0 == divide 0 -2147483648] +[0 == divide 0 -1073741824] +[0 == divide 0 -2] +[0 == divide 0 -1] +[error? try [divide 0 0]] +[0 == divide 0 1] +[0 == divide 0 2] +[0 == divide 0 1073741824] +[0 == divide 0 2147483647] +[-0.5 == divide 1 -2] +[-1 == divide 1 -1] +[error? try [divide 1 0]] +[1 == divide 1 1] +[0.5 == divide 1 2] +[-1 == divide 2 -2] +[-2 == divide 2 -1] +[error? try [divide 2 0]] +[2 == divide 2 1] +[1 == divide 2 2] +[-0.5 == divide 1073741824 -2147483648] +[-1 == divide 1073741824 -1073741824] +[-536870912 == divide 1073741824 -2] +[-1073741824 == divide 1073741824 -1] +[error? try [divide 1073741824 0]] +[1073741824 == divide 1073741824 1] +[536870912 == divide 1073741824 2] +[1 == divide 1073741824 1073741824] +[-1 == divide 2147483647 -2147483647] +[-1073741823.5 == divide 2147483647 -2] +[-2147483647 == divide 2147483647 -1] +[error? try [divide 2147483647 0]] +[2147483647 == divide 2147483647 1] +[1073741823.5 == divide 2147483647 2] +[1 == divide 2147483647 2147483647] +[10.0 == divide 1 .1] +[10.0 == divide 1.0 .1] +[10x10 == divide 1x1 .1] +; bug#1974 +[10.10.10 == divide 1.1.1 .1] diff --git a/tests/math/evenq.test.reb b/tests/math/evenq.test.reb new file mode 100644 index 0000000000..0ce39f2d15 --- /dev/null +++ b/tests/math/evenq.test.reb @@ -0,0 +1,40 @@ +; functions/math/evenq.r +[even? 0] +[not even? 1] +[not even? -1] +[not even? 2147483647] +[even? -2147483648] +#64bit +[not even? 9223372036854775807] +#64bit +[even? -9223372036854775808] +; decimal +[even? 0.0] +[not even? 1.0] +[even? 2.0] +[not even? -1.0] +[even? -2.0] +; bug#1775 +[even? 1.7976931348623157e308] +[even? -1.7976931348623157e308] +; char +[even? #"^@"] +[not even? #"^a"] +[even? #"^b"] +[not even? #"^(ff)"] +; money +[even? $0] +[not even? $1] +[even? $2] +[not even? -$1] +[even? -$2] +[not even? $999999999999999] +[not even? -$999999999999999] +; time +[even? 0:00] +[even? 0:1:00] +[even? -0:1:00] +[not even? 0:0:01] +[even? 0:0:02] +[not even? -0:0:01] +[even? -0:0:02] diff --git a/tests/math/exp.test.reb b/tests/math/exp.test.reb new file mode 100644 index 0000000000..0280c1e69f --- /dev/null +++ b/tests/math/exp.test.reb @@ -0,0 +1,6 @@ +; functions/math/exp.r +[1 = exp 0] +[2.718281828459045 = exp 1] +[(2.718281828459045 * 2.718281828459045) = exp 2] +[(square-root 2.718281828459045) = exp 0.5] +[(1 / 2.718281828459045) = exp -1] diff --git a/tests/math/log-10.test.reb b/tests/math/log-10.test.reb new file mode 100644 index 0000000000..810c1f5fde --- /dev/null +++ b/tests/math/log-10.test.reb @@ -0,0 +1,11 @@ +; functions/math/log-10.r +[0 = log-10 1] +[0.5 = log-10 square-root 10] +[1 = log-10 10] +[-1 = log-10 0.1] +[2 = log-10 100] +[-2 = log-10 0.01] +[3 = log-10 1000] +[-3 = log-10 0.001] +[error? try [log-10 0]] +[error? try [log-10 -1]] diff --git a/tests/math/log-2.test.reb b/tests/math/log-2.test.reb new file mode 100644 index 0000000000..e50a8e1464 --- /dev/null +++ b/tests/math/log-2.test.reb @@ -0,0 +1,10 @@ +; functions/math/log-2.r +[0 = log-2 1] +[1 = log-2 2] +[-1 = log-2 0.5] +[2 = log-2 4] +[-2 = log-2 0.25] +[3 = log-2 8] +[-3 = log-2 0.125] +[error? try [log-2 0]] +[error? try [log-2 -1]] diff --git a/tests/math/log-e.test.reb b/tests/math/log-e.test.reb new file mode 100644 index 0000000000..a8045aa072 --- /dev/null +++ b/tests/math/log-e.test.reb @@ -0,0 +1,8 @@ +; functions/math/log-e.r +[0 = log-e 1] +[0.5 = log-e square-root 2.718281828459045] +[1 = log-e 2.718281828459045] +[-1 = log-e 1 / 2.718281828459045] +[2 = log-e 2.718281828459045 * 2.718281828459045] +[error? try [log-e 0]] +[error? try [log-e -1]] diff --git a/tests/math/mod.test.reb b/tests/math/mod.test.reb new file mode 100644 index 0000000000..cc748a83e3 --- /dev/null +++ b/tests/math/mod.test.reb @@ -0,0 +1,43 @@ +; functions/math/mod.r +[0.0 == mod 1E15 1] +[0.0 == mod -1E15 1] +[0.0 == mod 1E14 1] +[0.0 == mod -1E14 1] +[0 == mod -1 1] +[0.75 == mod -1.25 1] +[0.5 == mod -1.5 1] +[0.25 == mod -1.75 1] +; these have small error; due to binary approximation of decimal numbers +[not negative? 1e-8 - abs 0.9 - mod 99'999'999.9 1] +[not negative? 1e-8 - abs 0.99 - mod 99'999'999.99 1] +[not negative? 1e-8 - abs 0.999 - mod 99'999'999.999 1] +[not negative? 1e-8 - abs 0.9999 - mod 99'999'999.9999 1] +[not negative? 1e-8 - abs 0.99999 - mod 99'999'999.99999 1] +[not negative? 1e-8 - abs 0.999999 - mod 99'999'999.999999 1] +[$0 == mod $999'999'999'999'999 1] +[$0 == mod $999'999'999'999'999 $1] +[0.0 == mod 9'999'999'999'999'999 1.0] +[0.0 == mod 999'999'999'999'999 1.0] +[0.0 == mod 562'949'953'421'311.0 1] +[0.0 == mod -562'949'953'421'311.0 1] +[0.25 == mod 562'949'953'421'311.25 1] +[0.5 == mod 562'949'953'421'311.5 1] +[0.5 == mod -562'949'953'421'311.5 1] +[0.25 == mod -562'949'953'421'311.75 1] +[0.0 == mod 562'949'953'421'312.0 1] +[0.0 == mod -562'949'953'421'312.0 1] +[0.25 == mod 562'949'953'421'312.25 1] +[0.5 == mod -562'949'953'421'312.5 1] +[0.5 == mod 562'949'953'421'312.5 1] +[0.25 == mod -562'949'953'421'312.75 1] +[0.0 == mod 562'949'953'421'313.0 1.0] +[0.0 == mod -562'949'953'421'313.0 1.0] +[0.5 == mod -562'949'953'421'313.5 1] +[0.5 == mod 562'949'953'421'313.5 1] +[0.0 == mod -562'949'953'421'314.0 1] +[0.5 == mod -562'949'953'421'314.5 1] +[0.5 == mod 562'949'953'421'314.5 1] +[not negative? 1e-16 - abs mod 0.15 - 0.05 - 0.1 0.1] +[not negative? 1e-16 - abs mod 0.1 + 0.1 + 0.1 0.3] +[not negative? 1e-16 - abs mod 0.3 0.1 + 0.1 + 0.1] +[not negative? 1e-16 - abs mod to money! 0.1 + 0.1 + 0.1 0.3] diff --git a/tests/math/modulo.test.reb b/tests/math/modulo.test.reb new file mode 100644 index 0000000000..5230a8c7ab --- /dev/null +++ b/tests/math/modulo.test.reb @@ -0,0 +1,9 @@ +; functions/math/modulo.r +[0.0 == modulo 0.1 + 0.1 + 0.1 0.3] +[0.0 == modulo 0.3 0.1 + 0.1 + 0.1] +[$0.0 == modulo $0.1 + $0.1 + $0.1 $0.3] +[$0.0 == modulo $0.3 $0.1 + $0.1 + $0.1] +[0.0 == modulo 1 0.1] +[0.0 == modulo 0.15 - 0.05 - 0.1 0.1] +; bug#56 +[0 = modulo 1 1] diff --git a/tests/math/multiply.test.reb b/tests/math/multiply.test.reb new file mode 100644 index 0000000000..e430f8e543 --- /dev/null +++ b/tests/math/multiply.test.reb @@ -0,0 +1,116 @@ +; functions/math/multiply.r +#32bit +[error? try [multiply -2147483648 -2147483648]] +#32bit +[error? try [multiply -2147483648 -1073741824]] +#32bit +[error? try [multiply -2147483648 -2]] +#32bit +[error? try [multiply -2147483648 -1]] +[0 = multiply -2147483648 0] +[-2147483648 = multiply -2147483648 1] +#32bit +[error? try [multiply -2147483648 2]] +#32bit +[error? try [multiply -2147483648 1073741824]] +#32bit +[error? try [multiply -2147483648 2147483647]] +#32bit +[error? try [multiply -1073741824 -2147483648]] +#32bit +[error? try [multiply -1073741824 -1073741824]] +#32bit +[error? try [multiply -1073741824 -2]] +[1073741824 = multiply -1073741824 -1] +[0 = multiply -1073741824 0] +[-1073741824 = multiply -1073741824 1] +[-2147483648 = multiply -1073741824 2] +#32bit +[error? try [multiply -1073741824 1073741824]] +#32bit +[error? try [multiply -1073741824 2147483647]] +#32bit +[error? try [multiply -2 -2147483648]] +#32bit +[error? try [multiply -2 -1073741824]] +[4 = multiply -2 -2] +[2 = multiply -2 -1] +[0 = multiply -2 0] +[-2 = multiply -2 1] +[-4 = multiply -2 2] +[-2147483648 = multiply -2 1073741824] +#32bit +[error? try [multiply -2 2147483647]] +#32bit +[error? try [multiply -1 -2147483648]] +[1073741824 = multiply -1 -1073741824] +[2 = multiply -1 -2] +[1 = multiply -1 -1] +[0 = multiply -1 0] +[-1 = multiply -1 1] +[-2 = multiply -1 2] +[-1073741824 = multiply -1 1073741824] +[-2147483647 = multiply -1 2147483647] +[0 = multiply 0 -2147483648] +[0 = multiply 0 -1073741824] +[0 = multiply 0 -2] +[0 = multiply 0 -1] +[0 = multiply 0 0] +[0 = multiply 0 1] +[0 = multiply 0 2] +[0 = multiply 0 1073741824] +[0 = multiply 0 2147483647] +[-2147483648 = multiply 1 -2147483648] +[-1073741824 = multiply 1 -1073741824] +[-2 = multiply 1 -2] +[-1 = multiply 1 -1] +[0 = multiply 1 0] +[1 = multiply 1 1] +[2 = multiply 1 2] +[1073741824 = multiply 1 1073741824] +[2147483647 = multiply 1 2147483647] +#32bit +[error? try [multiply 2 -2147483648]] +[-2147483648 = multiply 2 -1073741824] +[-4 = multiply 2 -2] +[-2 = multiply 2 -1] +[0 = multiply 2 0] +[2 = multiply 2 1] +#32bit +[error? try [multiply 2 1073741824]] +#32bit +[error? try [multiply 2 2147483647]] +#32bit +[error? try [multiply 1073741824 -2147483648]] +#32bit +[error? try [multiply 1073741824 -1073741824]] +[-2147483648 = multiply 1073741824 -2] +[-1073741824 = multiply 1073741824 -1] +[0 = multiply 1073741824 0] +[1073741824 = multiply 1073741824 1] +#32bit +[error? try [multiply 1073741824 2]] +#32bit +[error? try [multiply 1073741824 1073741824]] +#32bit +[error? try [multiply 1073741824 2147483647]] +#32bit +[error? try [multiply 2147483647 -2147483648]] +#32bit +[error? try [multiply 2147483647 -1073741824]] +#32bit +[error? try [multiply 2147483647 -2]] +[-2147483647 = multiply 2147483647 -1] +[0 = multiply 2147483647 0] +[2147483647 = multiply 2147483647 1] +#32bit +[error? try [multiply 2147483647 2]] +#32bit +[error? try [multiply 2147483647 1073741824]] +#32bit +[error? try [multiply 2147483647 2147483647]] +#64bit +[error? try [multiply -1 -9223372036854775808]] +#64bit +[error? try [multiply -9223372036854775808 -1]] +[0:0:1 == multiply 0:0:2 0.5] diff --git a/tests/math/negate.test.reb b/tests/math/negate.test.reb new file mode 100644 index 0000000000..3f83f6f003 --- /dev/null +++ b/tests/math/negate.test.reb @@ -0,0 +1,36 @@ +; functions/math/negate.r +[0 = negate 0] +[-1 = negate 1] +[1 = negate -1] +#32bit +[error? try [negate -2147483648]] +; decimal +[0.0 == negate 0.0] +[-1.0 == negate 1.0] +[1.0 == negate -1.0] +[1.7976931348623157e308 = negate -1.7976931348623157e308] +[-1.7976931348623157e308 = negate 1.7976931348623157e308] +[4.94065645841247E-324 = negate -4.94065645841247E-324] +[-4.94065645841247E-324 = negate 4.94065645841247E-324] +; pair +[0x0 = negate 0x0] +[-1x-1 = negate 1x1] +[1x1 = negate -1x-1] +[-1x1 = negate 1x-1] +; money +[$0 = negate $0] +[-$1 = negate $1] +[$1 = negate -$1] +; time +[0:00 = negate 0:00] +[-1:01 = negate 1:01] +[1:01 = negate -1:01] +; bitset +[ + a: make bitset! #{FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF} + a == negate negate a +] +[ + a: make bitset! #{0000000000000000000000000000000000000000000000000000000000000000} + a == negate negate a +] diff --git a/tests/math/negativeq.test.reb b/tests/math/negativeq.test.reb new file mode 100644 index 0000000000..7d32926f2d --- /dev/null +++ b/tests/math/negativeq.test.reb @@ -0,0 +1,25 @@ +; functions/math/negativeq.r +[not negative? 0] +[not negative? 1] +[negative? -1] +[not negative? 2147483647] +[negative? -2147483648] +#64bit +[not negative? 9223372036854775807] +#64bit +[negative? -9223372036854775808] +; decimal +[not negative? 0.0] +[not negative? 4.94065645841247E-324] +[negative? -4.94065645841247E-324] +[not negative? 1.7976931348623157e308] +[negative? -1.7976931348623157e308] +[not negative? $0] +[not negative? $0.01] +[negative? -$0.01] +[not negative? $999999999999999.87] +[negative? -$999999999999999.87] +; time +[not negative? 0:00] +[not negative? 0:00:0.000000001] +[negative? -0:00:0.000000001] diff --git a/tests/math/not.test.reb b/tests/math/not.test.reb new file mode 100644 index 0000000000..8f77385019 --- /dev/null +++ b/tests/math/not.test.reb @@ -0,0 +1,39 @@ +; functions/math/not.r +[false = not :abs] +[false = not #{}] +[false = not charset ""] +[false = not []] +[false = not #"a"] +[false = not datatype!] +[false = not 1/1/2007] +[false = not 0.0] +[false = not me@mydomain.com] +[false = not %myfile] +[false = not func [] []] +[false = not first [:a]] +[false = not make image! 0x0] +[false = not 0] +[false = not #1444] +[false = not first ['a/b]] +[false = not first ['a]] +[false = not true] +[true = not false] +[false = not make map! []] +[false = not $0.00] +[false = not :type-of] +[true = not blank] +[false = not make object! []] +[false = not type-of get '+] +[false = not 0x0] +[false = not first [()]] +[false = not first [a/b]] +[false = not make port! http://] +[false = not /refinement] +[false = not first [a/b:]] +[false = not first [a:]] +[false = not ""] +[false = not ] +[false = not 1:00] +[false = not 1.2.3] +[false = not http://] +[false = not 'a] diff --git a/tests/math/oddq.test.reb b/tests/math/oddq.test.reb new file mode 100644 index 0000000000..073daa5295 --- /dev/null +++ b/tests/math/oddq.test.reb @@ -0,0 +1,39 @@ +; functions/math/oddq.r +[not odd? 0] +[odd? 1] +[odd? -1] +[odd? 2147483647] +[not odd? -2147483648] +#64bit +[odd? 9223372036854775807] +#64bit +[not odd? -9223372036854775808] +; decimal +[not odd? 0.0] +[odd? 1.0] +[not odd? 2.0] +[odd? -1.0] +[not odd? -2.0] +[not odd? 1.7976931348623157e308] +[not odd? -1.7976931348623157e308] +; char +[not odd? #"^@"] +[odd? #"^a"] +[not odd? #"^b"] +[odd? #"^(ff)"] +; money +[not odd? $0] +[odd? $1] +[not odd? $2] +[odd? -$1] +[not odd? -$2] +[odd? $999999999999999] +[odd? -$999999999999999] +; time +[not odd? 0:00] +[not odd? 0:1:00] +[not odd? -0:1:00] +[odd? 0:0:01] +[not odd? 0:0:02] +[odd? -0:0:01] +[not odd? -0:0:02] diff --git a/tests/math/positiveq.test.reb b/tests/math/positiveq.test.reb new file mode 100644 index 0000000000..ee20edfa80 --- /dev/null +++ b/tests/math/positiveq.test.reb @@ -0,0 +1,25 @@ +; functions/math/positiveq.r +[not positive? 0] +[positive? 1] +[not positive? -1] +[positive? 2147483647] +[not positive? -2147483648] +#64bit +[positive? 9223372036854775807] +#64bit +[not positive? -9223372036854775808] +; decimal +[not positive? 0.0] +[positive? 4.94065645841247E-324] +[not positive? -4.94065645841247E-324] +[positive? 1.7976931348623157e308] +[not positive? -1.7976931348623157e308] +[not positive? $0] +[positive? $0.01] +[not positive? -$0.01] +[positive? $999999999999999.87] +[not positive? -$999999999999999.87] +; time +[not positive? 0:00] +[positive? 0:00:0.000000001] +[not positive? -0:00:0.000000001] diff --git a/tests/math/power.test.reb b/tests/math/power.test.reb new file mode 100644 index 0000000000..c9dcc477d7 --- /dev/null +++ b/tests/math/power.test.reb @@ -0,0 +1,6 @@ +; functions/math/power.r +[1 = power 1 1000] +[1 = power 1000 0] +[4 = power 2 2] +[0.5 = power 2 -1] +[0.1 = power 10 -1] diff --git a/tests/math/random.test.reb b/tests/math/random.test.reb new file mode 100644 index 0000000000..856705bbd8 --- /dev/null +++ b/tests/math/random.test.reb @@ -0,0 +1,22 @@ +; functions/math/random.r +; bug#1084 +[ + random/seed 0 + not any [ + negative? random 1.0 + negative? random 1.0 + ] +] +; bug#1875 +[ + random/seed 0 + 2 = random/only next [1 2] +] +; bug#932 +[ + s: "aa" + random/seed s + a: random 10000 + random/seed s + a = random 10000 +] diff --git a/tests/math/remainder.test.reb b/tests/math/remainder.test.reb new file mode 100644 index 0000000000..1dd69f59e9 --- /dev/null +++ b/tests/math/remainder.test.reb @@ -0,0 +1,9 @@ +; functions/math/remainder.r +#64bit +; integer! tests +[0 = remainder -9223372036854775808 -1] +; integer! tests +[0 == remainder -2147483648 -1] +; time! tests +[-1:00 == remainder -1:00 -3:00] +[1:00 == remainder 1:00 -3:00] diff --git a/tests/math/round.test.reb b/tests/math/round.test.reb new file mode 100644 index 0000000000..0e4bb9988b --- /dev/null +++ b/tests/math/round.test.reb @@ -0,0 +1,376 @@ +; functions/math/round.r +[0 == round 0] +[1 == round 1] +[-1 == round -1] +[zero? 2 - round 1.5] +[zero? 3 - round 2.5] +[zero? -2 - round -1.5] +[zero? -3 - round -2.5] +; REBOL rounds to 2.0 beyond this +[zero? 1 - round 1.499999999999995] +[zero? 2 - round 1.500000000000001] +[zero? -1 - round -1.499999999999995] +[zero? -2 - round -1.500000000000001] +[1:03:01 == round 1:03:01.1] +[1:03:02 == round 1:03:01.5] +[1:03:02 == round 1:03:01.9] +[zero? round 0.00001] +[zero? round 0.49999999] +[zero? 1 - round 0.5] +[zero? 1 - round 1.49999999] +[2 == round 2] +[zero? 2 - round 2.49999999] +[zero? round -0.00001] +[zero? round -0.49999999] +[zero? -1 - round -0.5] +[zero? -1 - round -1.49999999] +[-2 == round -2] +[1E20 == round 1E20] +[2147483648.0 == round 2147483648.0] +[9223372036854775808.0 == round 9223372036854775808.0] +[$101 == round $100.5] +[-$101 = round -$100.5] +; REBOL2 rounds to $100.5 beyond this +[$100 == round $100.4999999999998] +; REBOL2 rounds to $100.5 beyond this +[-$100 == round -$100.4999999999998] +; REBOL2 rounds to $1000.5 beyond this +[$1000 == round $1000.499999999999] +; REBOL2 rounds to $1000.5 beyond this +[-$1000 == round -$1000.499999999999] +[0:0:1 == round 0:0:1.4999] +[0:1:0 == round 0:1:0.4999] +[1:0:0 == round 1:0:0.4999] +[-0:0:1 == round -0:0:1.4999] +[-0:1:0 == round -0:1:0.4999] +[-1:0:0 == round -1:0:0.4999] +[0:0:2 == round 0:0:1.5] +[0:1:1 == round 0:1:0.5] +[1:0:1 == round 1:0:0.5] +[-0:0:2 == round -0:0:1.5] +[-0:1:1 == round -0:1:0.5] +[-1:0:1 == round -1:0:0.5] +; round/to tests +[100 == round/to 108 25] +[zero? 100 - round/to 100.000001 25] +[zero? 100 - round/to 112.499999 25] +[zero? 125 - round/to 112.5 25] +[zero? -100 - round/to -100.000001 25] +[zero? -100 - round/to -112.499999 25] +[zero? -125 - round/to -112.5 25] +[zero? -125 - round/to -112.500001 25] +[125 == round/to 133 25] +[1.00 == round/to 1.08 0.25] +[1.25 == round/to 1.33 0.25] +[1:00 == round/to 1:03 0:15] +[1:15 == round/to 1:08 0:15] +[1:15 == round/to 1:18 0:15] +[1:30 == round/to 1:22:31 0:15] +[1:02 == round/to 1:02:03 0:01] +[562'949'953'421'312.0 == round/to 562'949'953'421'312.0 1.0] +[-562'949'953'421'312.0 == round/to -562'949'953'421'312.0 1.0] +[562'949'953'421'313.0 == round/to 562'949'953'421'313.0 1.0] +[-562'949'953'421'313.0 == round/to -562'949'953'421'313.0 1.0] +[562'949'953'421'314.0 == round/to 562'949'953'421'314.0 1.0] +[-562'949'953'421'314.0 == round/to -562'949'953'421'314.0 1.0] +[zero? $100.2 - round/to 100.15 $0.1] +[$100.2 == round/to $100.15 $0.1] +[1:1:1.2 == round/to 1:1:1.15 0:0:0.1] +[$100 == round/to $100.15 $2] +[1:1:2 == round/to 1:1:1.15 0:0:2] +[0 == round/even 0] +[1 == round/even 1] +[-1 == round/even -1] +[zero? 2 - round/even 1.5] +[zero? 2 - round/even 2.5] +[zero? -2 - round/even -1.5] +[zero? -2 - round/even -2.5] +; REBOL2 rounds to 2.0 beyond this +[zero? 1 - round/even 1.49999999999995] +[zero? 1 - round/even 1.499999999999995] +[zero? 2 - round/even 1.500000000000001] +[zero? -1 - round/even -1.499999999999995] +[zero? -2 - round/even -1.500000000000001] +[2147483647 == round/even 2147483647] +[-2147483648 == round/even -2147483648] +[9223372036854780000.0 == round/even 9223372036854780000.0] +[1:03:01 == round/even 1:03:01.1] +[1:03:02 == round/even 1:03:01.5] +[1:03:02 == round/even 1:03:01.9] +[$100 == round/even $100.25] +[-$100 == round/even -$100.25] +; round/even/to; divide by 0 +[error? try [round/even/to 0.1 0]] +[zero? round/even/to 0.1 -1.0] +[zero? round/even/to 0.1 -1] +[zero? round/even/to 0.5 -1.0] +[zero? round/even/to 0.5 -1] +[zero? 2 - round/even/to 1.5 -1.0] +[zero? 2 - round/even/to 1.5 -1] +[zero? round/even/to -0.1 -1.0] +[zero? round/even/to -0.1 -1] +[0.0 == round/even/to -0.5 -1.0] +[zero? round/even/to -0.5 -1] +[-2.0 == round/even/to -1.5 -1.0] +[zero? -2 - round/even/to -1.5 -1] +[0.0 == round/even/to 0.1 1.0] +[0.0 == round/even/to 0.1 1E-0] +[0.0 == round/even/to -0.1 1E-0] +[0.1 == round/even/to 0.12 1E-1] +[-0.1 == round/even/to -0.12 1E-1] +[0.12 == round/even/to 0.123 1E-2] +[-0.12 == round/even/to -0.123 1e-2] +[0.123 == round/even/to 0.1234 1E-3] +[-0.123 == round/even/to -0.1234 1E-3] +[0.1234 = round/even/to 0.12345 1E-4] +[-0.1234 = round/even/to -0.12345 1E-4] +; bug#1470 +[2.6 == round/even/to $2.55 0.1] +; bug#1470 +[$2.6 == round/even/to 2.55 $0.1] +; round-up breakpoint +[0.12346 = round/even/to 0.123456 1E-5] +[-0.12346 = round/even/to -0.123456 1E-5] +[1.0 == round/even/to 0.9 1E-0] +[-1.0 == round/even/to -0.9 1E-0] +[0.6 = round/even/to 0.55 1E-1] +[-0.6 = round/even/to -0.55 1E-1] +[0.56 == round/even/to 0.555 1E-2] +[-0.56 == round/even/to -0.555 1E-2] +[2.0 == round/even/to 1.5 1E-0] +[1.6 == round/even/to 1.55 1E-1] +[1.56 == round/even/to 1.555 1E-2] +[1.556 == round/even/to 1.5555 1E-3] +[1.5556 == round/even/to 1.55555 1E-4] +[1.55556 = round/even/to 1.555555 1E-5] +[1.555556 == round/even/to 1.5555555 1E-6] +[1.5555556 == round/even/to 1.55555555 1E-7] +[1.55555556 == round/even/to 1.555555555 1E-8] +[1.555555556 = round/even/to 1.5555555555 1E-9] +[0.2 == round/even/to 0.15 1E-1] +[-0.2 == round/even/to -0.15 1E-1] +[0.4 == round/even/to 0.35 1E-1] +[1.0 == round/even/to 0.95 1E-1] +[1.2 = round/even/to 1.15 1E-1] +[2.2 == round/even/to 2.15 1E-1] +[2.6 == round/even/to 2.55 1E-1] +[10.0 == round/even/to 10 1E-1] +[zero? 110 - round/even/to 107.5 5] +[zero? 110 - round/even/to 112.5 5] +[zero? 120 - round/even/to 115 10] +[zero? 100 - round/even/to 100.000001 25] +[zero? 100 - round/even/to 112.499999 25] +[zero? 100 - round/even/to 112.5 25] +[zero? 150 - round/even/to 137.5 25] +[zero? -100 - round/even/to -100.000001 25] +[zero? -100 - round/even/to -112.499999 25] +[zero? -100 - round/even/to -112.5 25] +[zero? -125 - round/even/to -112.500001 25] +[zero? -150 - round/even/to -137.5 25] +[1:02:3.1 == round/even/to 1:02:3.14999 0:0:0.1] +[1:02:3.2 == round/even/to 1:02:3.15 0:0:0.1] +[1:02:3.2 == round/even/to 1:02:3.25 0:0:0.1] +[1:02:3.3 == round/even/to 1:02:3.25001 0:0:0.1] +[1:15 == round/even/to 1:22:29.9999 0:15] +[1:30 == round/even/to 1:22:30 0:15] +[0.0 == (562'949'953'421'312.0 - round/even/to 562'949'953'421'312.0 1.0)] +[0.0 == (-562'949'953'421'312.0 - round/even/to -562'949'953'421'312.0 1.0)] +[0.0 == (562'949'953'421'313.0 - round/even/to 562'949'953'421'313.0 1.0)] +[0.0 == (-562'949'953'421'313.0 - round/even/to -562'949'953'421'313.0 1.0)] +[562'949'953'421'314.0 == round/even/to 562'949'953'421'314.0 1.0] +[-562'949'953'421'314.0 == round/even/to -562'949'953'421'314.0 1.0] +; bug#1116 +[$1.15 == round/even/to 1.15 $0.01] +; this fails, by design +[0:0:1.15 == round/even/to 0:0:1.15 0:0:0.01] +[1.15 == round/even/to $1.15 0.01] +[-0:0:2.6 == round/even/to -0:0:2.55 0:0:0.1] +[-$2.6 == round/even/to -$2.55 $0.1] +[0.0 == (1e-15 - round/even/to 1.1e-15 1e-15)] +[$0.0 == ($0.000'000'000'000'001 - round/even/to $0.000'000'000'000'001'1 $1e-15)] +[not negative? 1e-31 - abs 26e-17 - round/even/to 25.5e-17 1e-17] +[not negative? ($1e-31) - abs $26e-17 - round/even/to $0.000'000'000'000'000'255 $1e-17] +[0:0:2.6 == round/even/to 0:0:2.55 0:0:0.1] +[$2.6 == round/even/to $2.55 $0.1] +[not negative? 1e-31 - abs -26e-17 - round/even/to -25.5e-17 1e-17] +[not negative? $1e-31 - abs -$26e-17 - round/even/to -$0.000'000'000'000'000'255 $1e-17] +[$1 == round/even/to $1.23456789 $1] +[$1.2 == round/even/to $1.23456789 $0.1] +[$1.23 == round/even/to $1.23456789 $0.01] +[$1.235 == round/even/to $1.23456789 $0.001] +[$1.2346 == round/even/to $1.23456789 $0.0001] +[$1.23457 == round/even/to $1.23456789 $0.00001] +[$1.234568 == round/even/to $1.23456789 $0.000001] +[$1.2345679 == round/even/to $1.23456789 $0.0000001] +[$1.23456789 == round/even/to $1.23456789 $0.00000001] +; round/ceiling +[0 == round/ceiling 0] +[1 == round/ceiling 1] +[-1 == round/ceiling -1] +[zero? 2 - round/ceiling 1.1] +[zero? 1 - round/ceiling 0.00000000000001] +[zero? round/ceiling -0.00000000000001] +[zero? 1 - round/ceiling 0.99999999999995] +[zero? -1 - round/ceiling -1.00000000000001] +[zero? -1 - round/ceiling -1.99999999999995] +[zero? 1 - round/ceiling 0.00001] +[zero? 1 - round/ceiling 0.49999999] +[zero? 1 - round/ceiling 0.5] +[zero? 2 - round/ceiling 1.49999999] +[zero? 2 - round/ceiling 1.5] +[2 == round/ceiling 2] +[zero? 3 - round/ceiling 2.49999999] +[zero? 3 - round/ceiling 2.5] +[zero? round/ceiling -0.00001] +[zero? round/ceiling -0.49999999] +[zero? round/ceiling -0.5] +[zero? -1 - round/ceiling -1.49999999] +[zero? -1 - round/ceiling -1.5] +[-2 == round/ceiling -2] +; round/ceiling/to +[562'949'953'421'312.0 == round/ceiling/to 562'949'953'421'312.0 1.0] +[-562'949'953'421'312.0 == round/ceiling/to -562'949'953'421'312.0 1.0] +[562'949'953'421'313.0 == round/ceiling/to 562'949'953'421'313.0 1.0] +[-562'949'953'421'313.0 == round/ceiling/to -562'949'953'421'313.0 1.0] +[562'949'953'421'314.0 == round/ceiling/to 562'949'953'421'314.0 1.0] +[-562'949'953'421'314.0 == round/ceiling/to -562'949'953'421'314.0 1.0] +; round/floor +[-1 == round/floor -1] +[zero? 1 - round/floor 1.1] +[zero? round/floor 0.00000000000001] +[zero? -1 - round/floor -0.00000000000001] +[zero? round/floor 0.99999999999995] +[zero? -2 - round/floor -1.00000000000001] +[zero? -2 - round/floor -1.99999999999995] +; round/floor/to +[zero? 100 - round/floor/to 112.499999 25] +[zero? 100 - round/floor/to 112.5 25] +[zero? -125 - round/floor/to -112.000001 25] +[zero? -125 - round/floor/to -112.5 25] +[zero? -125 - round/floor/to -112.500001 25] +[562'949'953'421'312.0 == round/floor/to 562'949'953'421'312.0 1.0] +[-562'949'953'421'312.0 == round/floor/to -562'949'953'421'312.0 1.0] +[562'949'953'421'313.0 == round/floor/to 562'949'953'421'313.0 1.0] +[-562'949'953'421'313.0 == round/floor/to -562'949'953'421'313.0 1.0] +[562'949'953'421'314.0 == round/floor/to 562'949'953'421'314.0 1.0] +[-562'949'953'421'314.0 == round/floor/to -562'949'953'421'314.0 1.0] +; round/down +[0 == round/down 0] +[1 == round/down 1] +[-1 == round/down -1] +[zero? 1 - round/down 1.1] +[zero? round/down 0.00000000000001] +[zero? round/down -0.00000000000001] +[zero? round/down 0.99999999999995] +[zero? -1 - round/down -1.00000000000001] +[zero? -1 - round/down -1.99999999999995] +[1:02:03 == round/down 1:02:03] +[1:02:03 == round/down 1:02:03.00000000001] +[1:02:03 == round/down 1:02:03.999999999] +; round/down/to +[9.6 == round/down/to 10.0 0.96] +[9.6 == round/down/to 10.55 0.96] +[562'949'953'421'312.0 == round/down/to 562'949'953'421'312.0 1.0] +[-562'949'953'421'312.0 == round/down/to -562'949'953'421'312.0 1.0] +[562'949'953'421'313.0 == round/down/to 562'949'953'421'313.0 1.0] +[-562'949'953'421'313.0 == round/down/to -562'949'953'421'313.0 1.0] +[562'949'953'421'314.0 == round/down/to 562'949'953'421'314.0 1.0] +[-562'949'953'421'314.0 == round/down/to -562'949'953'421'314.0 1.0] +[1.1 == round/down/to 1.123456789 1E-1] +[1.12 == round/down/to 1.123456789 1E-2] +[1.123 == round/down/to 1.123456789 1E-3] +[1.1234 == round/down/to 1.123456789 1E-4] +[1.12345 == round/down/to 1.123456789 1E-5] +[1.123456 == round/down/to 1.123456789 1E-6] +[1.1234567 == round/down/to 1.123456789 1E-7] +[1.12345678 == round/down/to 1.123456789 1E-8] +[1:0:0 == round/down/to 1:02:3.456789 0:5:0] +[1:0:0 == round/down/to 1:02:3.456789 0:3:0] +[1:2:0 == round/down/to 1:02:3.456789 0:2:0] +[1:2:0 == round/down/to 1:02:3.456789 0:1:0] +[1:2:0 == round/down/to 1:02:3.456789 0:0:5] +[1:2:0 == round/down/to 1:02:3.456789 0:0:4] +[1:02:3 == round/down/to 1:02:3.456789 0:0:3] +[1:2:2 == round/down/to 1:02:3.456789 0:0:2] +[1:2:3 == round/down/to 1:02:3.456789 0:0:1] +[1:2:3.4 == round/down/to 1:02:3.456789 0:0:0.1] +[1:2:3.45 == round/down/to 1:02:3.456789 0:0:0.01] +[1:2:3.456 == round/down/to 1:02:3.456789 0:0:0.001] +[1:2:3.4567 == round/down/to 1:02:3.456789 0:0:0.0001] +[1:2:3.45678 == round/down/to 1:02:3.456789 0:0:0.00001] +; round/half-ceiling +[0 == round/half-ceiling 0] +[1 == round/half-ceiling 1] +[-1 == round/half-ceiling -1] +[zero? 2 - round/half-ceiling 1.5] +[zero? 3 - round/half-ceiling 2.5] +[zero? -1 - round/half-ceiling -1.5] +[zero? -2 - round/half-ceiling -2.5] +; REBOL2 rounds to 1.5 beyond this +[zero? 1 - round/half-ceiling 1.499999999999995] +[zero? 2 - round/half-ceiling 1.50000000000001] +[zero? -1 - round/half-ceiling -1.49999999999995] +[zero? -2 - round/half-ceiling -1.50000000000001] +[1:03:01 == round/half-ceiling 1:03:01.1] +[1:03:02 == round/half-ceiling 1:03:01.5] +[1:03:02 == round/half-ceiling 1:03:01.9] +[-1:03:01 == round/half-ceiling -1:03:01] +[-1:03:01 == round/half-ceiling -1:03:01.5] +[-1:03:02 == round/half-ceiling -1:03:01.50001] +[$100 == round/half-ceiling $100] +[$101 == round/half-ceiling $100.5] +[$101 == round/half-ceiling $100.5000000001] +[-$100 == round/half-ceiling -$100] +[-$100 == round/half-ceiling -$100.5] +; bug#1471 +[-$101 == round/half-ceiling -$100.5000000001] +; round/half-ceiling/to +[0.0 == round/half-ceiling/to 0.1 -1.0] +[zero? round/half-ceiling/to 0.1 -1] +[1.0 == round/half-ceiling/to 0.5 -1.0] +[zero? 1 - round/half-ceiling/to 0.5 -1] +[2.0 == round/half-ceiling/to 1.5 -1.0] +[zero? 2 - round/half-ceiling/to 1.5 -1] +[0.0 == round/half-ceiling/to -0.1 -1.0] +[zero? round/half-ceiling/to -0.1 -1] +[0.0 == round/half-ceiling/to -0.5 -1.0] +[zero? round/half-ceiling/to -0.5 -1] +[-1.0 == round/half-ceiling/to -1.5 -1.0] +[zero? -1 - round/half-ceiling/to -1.5 -1] +; round/half-down +[0 == round/half-down 0] +[1 == round/half-down 1] +[-1 == round/half-down -1] +[zero? 1 - round/half-down 1.5] +[zero? 2 - round/half-down 1.50000000001] +[zero? 2 - round/half-down 2.5] +[zero? 3 - round/half-down 2.50000000001] +[zero? -1 - round/half-down -1.5] +[zero? -2 - round/half-down -1.50000000001] +[zero? -2 - round/half-down -2.5] +[zero? -3 - round/half-down -2.50000000001] +[1:03:01 == round/half-down 1:03:01.1] +[1:03:01 == round/half-down 1:03:01.5] +[1:03:02 == round/half-down 1:03:01.9] +[-1:03:01 == round/half-down -1:03:01] +[-1:03:01 == round/half-down -1:03:01.5] +[-1:03:02 == round/half-down -1:03:01.50001] +[$100 == round/half-down $100] +[$100 == round/half-down $100.5] +[$101 == round/half-down $100.5000000001] +[-$100 == round/half-down -$100] +[-$100 == round/half-down -$100.5] +[-$101 == round/half-down -$100.5000000001] +; round/half-down/to +[0.1 == round/half-down/to 0.15 0.1] +[0.2 == round/half-down/to 0.15001 0.1] +[0.5 == round/half-down/to 0.55 0.1] +[0.6 == round/half-down/to 0.55001 0.1] +[0.5 == round/half-down/to 0.75 0.5] +[1.0 == round/half-down/to 0.75001 0.5] +[-0.1 == round/half-down/to -0.15 0.1] +[-0.2 == round/half-down/to -0.15001 0.1] +[-0.5 == round/half-down/to -0.55 0.1] +[-0.6 == round/half-down/to -0.55001 0.1] +[-0.5 == round/half-down/to -0.75 0.5] +[-1.0 == round/half-down/to -0.75001 0.5] diff --git a/tests/math/shift.test.reb b/tests/math/shift.test.reb new file mode 100644 index 0000000000..6f8285a412 --- /dev/null +++ b/tests/math/shift.test.reb @@ -0,0 +1,380 @@ +; functions/math/shift.r +; bug#2067 +; logical shift of to integer! #{8000000000000000} +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000000} to integer! #{8000000000000000}] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000000} to integer! #{8000000000000001}] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000000} -65] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000000} -64] +#64bit +[strict-equal? 1 shift/logical to integer! #{8000000000000000} -63] +#64bit +[strict-equal? 2 shift/logical to integer! #{8000000000000000} -62] +#64bit +[strict-equal? to integer! #{4000000000000000} shift/logical to integer! #{8000000000000000} -1] +#64bit +[strict-equal? to integer! #{8000000000000000} shift/logical to integer! #{8000000000000000} 0] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000000} 1] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000000} 62] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000000} 63] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000000} 64] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000000} 65] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000000} to integer! #{7ffffffffffffffe}] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000000} to integer! #{7fffffffffffffff}] +; logical shift of to integer! #{8000000000000001} +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000001} to integer! #{8000000000000000}] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000001} to integer! #{8000000000000001}] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000001} -65] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000001} -64] +#64bit +[strict-equal? 1 shift/logical to integer! #{8000000000000001} -63] +#64bit +[strict-equal? 2 shift/logical to integer! #{8000000000000001} -62] +#64bit +[strict-equal? to integer! #{4000000000000000} shift/logical to integer! #{8000000000000001} -1] +#64bit +[strict-equal? to integer! #{8000000000000001} shift/logical to integer! #{8000000000000001} 0] +#64bit +[strict-equal? 2 shift/logical to integer! #{8000000000000001} 1] +#64bit +[strict-equal? to integer! #{4000000000000000} shift/logical to integer! #{8000000000000001} 62] +#64bit +[strict-equal? to integer! #{8000000000000000} shift/logical to integer! #{8000000000000001} 63] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000001} 64] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000001} 65] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000001} to integer! #{7ffffffffffffffe}] +#64bit +[strict-equal? 0 shift/logical to integer! #{8000000000000001} to integer! #{7fffffffffffffff}] +; logical shift of -1 +#64bit +[strict-equal? 0 shift/logical -1 to integer! #{8000000000000000}] +#64bit +[strict-equal? 0 shift/logical -1 to integer! #{8000000000000001}] +[strict-equal? 0 shift/logical -1 -65] +[strict-equal? 0 shift/logical -1 -64] +#64bit +[strict-equal? 1 shift/logical -1 -63] +[strict-equal? 3 shift/logical -1 -62] +#64bit +[strict-equal? to integer! #{7fffffffffffffff} shift/logical -1 -1] +[strict-equal? -1 shift/logical -1 0] +[strict-equal? -2 shift/logical -1 1] +#64bit +[strict-equal? to integer! #{c000000000000000} shift/logical -1 62] +#64bit +[strict-equal? to integer! #{8000000000000000} shift/logical -1 63] +[strict-equal? 0 shift/logical -1 64] +[strict-equal? 0 shift/logical -1 65] +#64bit +[strict-equal? 0 shift/logical -1 to integer! #{7ffffffffffffffe}] +#64bit +[strict-equal? 0 shift/logical -1 to integer! #{7fffffffffffffff}] +; logical shift of 0 +#64bit +[strict-equal? 0 shift/logical 0 to integer! #{8000000000000000}] +#64bit +[strict-equal? 0 shift/logical 0 to integer! #{8000000000000001}] +[strict-equal? 0 shift/logical 0 -65] +[strict-equal? 0 shift/logical 0 -64] +[strict-equal? 0 shift/logical 0 -63] +[strict-equal? 0 shift/logical 0 -62] +[strict-equal? 0 shift/logical 0 -1] +[strict-equal? 0 shift/logical 0 0] +[strict-equal? 0 shift/logical 0 1] +[strict-equal? 0 shift/logical 0 62] +[strict-equal? 0 shift/logical 0 63] +[strict-equal? 0 shift/logical 0 64] +[strict-equal? 0 shift/logical 0 65] +#64bit +[strict-equal? 0 shift/logical 0 to integer! #{7ffffffffffffffe}] +#64bit +[strict-equal? 0 shift/logical 0 to integer! #{7fffffffffffffff}] +; logical shift of 1 +#64bit +[strict-equal? 0 shift/logical 1 to integer! #{8000000000000000}] +#64bit +[strict-equal? 0 shift/logical 1 to integer! #{8000000000000001}] +[strict-equal? 0 shift/logical 1 -65] +[strict-equal? 0 shift/logical 1 -64] +[strict-equal? 0 shift/logical 1 -63] +[strict-equal? 0 shift/logical 1 -62] +[strict-equal? 0 shift/logical 1 -1] +[strict-equal? 1 shift/logical 1 0] +[strict-equal? 2 shift/logical 1 1] +#64bit +[strict-equal? to integer! #{4000000000000000} shift/logical 1 62] +#64bit +[strict-equal? to integer! #{8000000000000000} shift/logical 1 63] +[strict-equal? 0 shift/logical 1 64] +[strict-equal? 0 shift/logical 1 65] +#64bit +[strict-equal? 0 shift/logical 1 to integer! #{7ffffffffffffffe}] +#64bit +[strict-equal? 0 shift/logical 1 to integer! #{7fffffffffffffff}] +; logical shift of to integer! #{7ffffffffffffffe} +#64bit +[strict-equal? 0 shift/logical to integer! #{7ffffffffffffffe} to integer! #{8000000000000000}] +#64bit +[strict-equal? 0 shift/logical to integer! #{7ffffffffffffffe} to integer! #{8000000000000001}] +#64bit +[strict-equal? 0 shift/logical to integer! #{7ffffffffffffffe} -65] +#64bit +[strict-equal? 0 shift/logical to integer! #{7ffffffffffffffe} -64] +#64bit +[strict-equal? 0 shift/logical to integer! #{7ffffffffffffffe} -63] +#64bit +[strict-equal? 1 shift/logical to integer! #{7ffffffffffffffe} -62] +#64bit +[strict-equal? to integer! #{3fffffffffffffff} shift/logical to integer! #{7ffffffffffffffe} -1] +#64bit +[strict-equal? to integer! #{7ffffffffffffffe} shift/logical to integer! #{7ffffffffffffffe} 0] +#64bit +[strict-equal? -4 shift/logical to integer! #{7ffffffffffffffe} 1] +#64bit +[strict-equal? to integer! #{8000000000000000} shift/logical to integer! #{7ffffffffffffffe} 62] +#64bit +[strict-equal? 0 shift/logical to integer! #{7ffffffffffffffe} 63] +#64bit +[strict-equal? 0 shift/logical to integer! #{7ffffffffffffffe} 64] +#64bit +[strict-equal? 0 shift/logical to integer! #{7ffffffffffffffe} 65] +#64bit +[strict-equal? 0 shift/logical to integer! #{7ffffffffffffffe} to integer! #{7ffffffffffffffe}] +#64bit +[strict-equal? 0 shift/logical to integer! #{7ffffffffffffffe} to integer! #{7fffffffffffffff}] +; logical shift of to integer! #{7fffffffffffffff} +#64bit +[strict-equal? 0 shift/logical to integer! #{7fffffffffffffff} to integer! #{8000000000000000}] +#64bit +[strict-equal? 0 shift/logical to integer! #{7fffffffffffffff} to integer! #{8000000000000001}] +#64bit +[strict-equal? 0 shift/logical to integer! #{7fffffffffffffff} -65] +#64bit +[strict-equal? 0 shift/logical to integer! #{7fffffffffffffff} -64] +#64bit +[strict-equal? 0 shift/logical to integer! #{7fffffffffffffff} -63] +#64bit +[strict-equal? 1 shift/logical to integer! #{7fffffffffffffff} -62] +#64bit +[strict-equal? to integer! #{3fffffffffffffff} shift/logical to integer! #{7fffffffffffffff} -1] +#64bit +[strict-equal? to integer! #{7fffffffffffffff} shift/logical to integer! #{7fffffffffffffff} 0] +#64bit +[strict-equal? -2 shift/logical to integer! #{7fffffffffffffff} 1] +#64bit +[strict-equal? to integer! #{c000000000000000} shift/logical to integer! #{7fffffffffffffff} 62] +#64bit +[strict-equal? to integer! #{8000000000000000} shift/logical to integer! #{7fffffffffffffff} 63] +#64bit +[strict-equal? 0 shift/logical to integer! #{7fffffffffffffff} 64] +#64bit +[strict-equal? 0 shift/logical to integer! #{7fffffffffffffff} 65] +#64bit +[strict-equal? 0 shift/logical to integer! #{7fffffffffffffff} to integer! #{7ffffffffffffffe}] +#64bit +[strict-equal? 0 shift/logical to integer! #{7fffffffffffffff} to integer! #{7fffffffffffffff}] +; arithmetic shift of to integer! #{8000000000000000} +#64bit +[strict-equal? -1 shift to integer! #{8000000000000000} to integer! #{8000000000000000}] +#64bit +[strict-equal? -1 shift to integer! #{8000000000000000} to integer! #{8000000000000001}] +#64bit +[strict-equal? -1 shift to integer! #{8000000000000000} -65] +#64bit +[strict-equal? -1 shift to integer! #{8000000000000000} -64] +#64bit +[strict-equal? -1 shift to integer! #{8000000000000000} -63] +#64bit +[strict-equal? -2 shift to integer! #{8000000000000000} -62] +#64bit +[strict-equal? to integer! #{c000000000000000} shift to integer! #{8000000000000000} -1] +#64bit +[strict-equal? to integer! #{8000000000000000} shift to integer! #{8000000000000000} 0] +#64bit +[error? try [shift to integer! #{8000000000000000} 1]] +#64bit +[error? try [shift to integer! #{8000000000000000} 62]] +#64bit +[error? try [shift to integer! #{8000000000000000} 63]] +#64bit +[error? try [shift to integer! #{8000000000000000} 64]] +#64bit +[error? try [shift to integer! #{8000000000000000} 65]] +#64bit +[error? try [shift to integer! #{8000000000000000} to integer! #{7ffffffffffffffe}]] +#64bit +[error? try [shift to integer! #{8000000000000000} to integer! #{7fffffffffffffff}]] +#64bit +; arithmetic shift of to integer! #{8000000000000001} +#64bit +[strict-equal? -1 shift to integer! #{8000000000000001} to integer! #{8000000000000000}] +#64bit +[strict-equal? -1 shift to integer! #{8000000000000001} to integer! #{8000000000000001}] +#64bit +[strict-equal? -1 shift to integer! #{8000000000000001} -65] +#64bit +[strict-equal? -1 shift to integer! #{8000000000000001} -64] +#64bit +[strict-equal? -1 shift to integer! #{8000000000000001} -63] +#64bit +[strict-equal? -2 shift to integer! #{8000000000000001} -62] +#64bit +[strict-equal? to integer! #{c000000000000000} shift to integer! #{8000000000000001} -1] +#64bit +[strict-equal? to integer! #{8000000000000001} shift to integer! #{8000000000000001} 0] +#64bit +[error? try [shift to integer! #{8000000000000001} 1]] +#64bit +[error? try [shift to integer! #{8000000000000001} 62]] +#64bit +[error? try [shift to integer! #{8000000000000001} 63]] +#64bit +[error? try [shift to integer! #{8000000000000001} 64]] +#64bit +[error? try [shift to integer! #{8000000000000001} 65]] +#64bit +[error? try [shift to integer! #{8000000000000001} to integer! #{7ffffffffffffffe}]] +#64bit +[error? try [shift to integer! #{8000000000000001} to integer! #{7fffffffffffffff}]] +; arithmetic shift of -1 +#64bit +[strict-equal? -1 shift -1 to integer! #{8000000000000000}] +#64bit +[strict-equal? -1 shift -1 to integer! #{8000000000000001}] +[strict-equal? -1 shift -1 -65] +[strict-equal? -1 shift -1 -64] +[strict-equal? -1 shift -1 -63] +[strict-equal? -1 shift -1 -62] +[strict-equal? -1 shift -1 -1] +[strict-equal? -1 shift -1 0] +[strict-equal? -2 shift -1 1] +#64bit +[strict-equal? to integer! #{c000000000000000} shift -1 62] +#64bit +[strict-equal? to integer! #{8000000000000000} shift -1 63] +[error? try [shift -1 64]] +[error? try [shift -1 65]] +#64bit +[error? try [shift -1 to integer! #{7ffffffffffffffe}]] +#64bit +[error? try [shift -1 to integer! #{7fffffffffffffff}]] +; arithmetic shift of 0 +#64bit +[strict-equal? 0 shift 0 to integer! #{8000000000000000}] +#64bit +[strict-equal? 0 shift 0 to integer! #{8000000000000001}] +[strict-equal? 0 shift 0 -65] +[strict-equal? 0 shift 0 -64] +[strict-equal? 0 shift 0 -63] +[strict-equal? 0 shift 0 -62] +[strict-equal? 0 shift 0 -1] +[strict-equal? 0 shift 0 0] +[strict-equal? 0 shift 0 1] +[strict-equal? 0 shift 0 62] +[strict-equal? 0 shift 0 63] +[strict-equal? 0 shift 0 64] +[strict-equal? 0 shift 0 65] +#64bit +[strict-equal? 0 shift 0 to integer! #{7ffffffffffffffe}] +#64bit +[strict-equal? 0 shift 0 to integer! #{7fffffffffffffff}] +; arithmetic shift of 1 +#64bit +[strict-equal? 0 shift 1 to integer! #{8000000000000000}] +#64bit +[strict-equal? 0 shift 1 to integer! #{8000000000000001}] +[strict-equal? 0 shift 1 -65] +[strict-equal? 0 shift 1 -64] +[strict-equal? 0 shift 1 -63] +[strict-equal? 0 shift 1 -62] +[strict-equal? 0 shift 1 -1] +[strict-equal? 1 shift 1 0] +[strict-equal? 2 shift 1 1] +#64bit +[strict-equal? to integer! #{4000000000000000} shift 1 62] +[error? try [shift 1 63]] +[error? try [shift 1 64]] +[error? try [shift 1 65]] +#64bit +[error? try [shift 1 to integer! #{7ffffffffffffffe}]] +#64bit +[error? try [shift 1 to integer! #{7fffffffffffffff}]] +; arithmetic shift of to integer! #{7ffffffffffffffe} +#64bit +[strict-equal? 0 shift to integer! #{7ffffffffffffffe} to integer! #{8000000000000000}] +#64bit +[strict-equal? 0 shift to integer! #{7ffffffffffffffe} to integer! #{8000000000000001}] +#64bit +[strict-equal? 0 shift to integer! #{7ffffffffffffffe} -65] +#64bit +[strict-equal? 0 shift to integer! #{7ffffffffffffffe} -64] +#64bit +[strict-equal? 0 shift to integer! #{7ffffffffffffffe} -63] +#64bit +[strict-equal? 1 shift to integer! #{7ffffffffffffffe} -62] +#64bit +[strict-equal? to integer! #{3fffffffffffffff} shift to integer! #{7ffffffffffffffe} -1] +#64bit +[strict-equal? to integer! #{7ffffffffffffffe} shift to integer! #{7ffffffffffffffe} 0] +#64bit +[error? try [shift to integer! #{7ffffffffffffffe} 1]] +#64bit +[error? try [shift to integer! #{7ffffffffffffffe} 62]] +#64bit +[error? try [shift to integer! #{7ffffffffffffffe} 63]] +#64bit +[error? try [shift to integer! #{7ffffffffffffffe} 64]] +#64bit +[error? try [shift to integer! #{7ffffffffffffffe} 65]] +#64bit +[error? try [shift to integer! #{7ffffffffffffffe} to integer! #{7ffffffffffffffe}]] +#64bit +[error? try [shift to integer! #{7ffffffffffffffe} to integer! #{7fffffffffffffff}]] +; arithmetic shift of to integer! #{7fffffffffffffff} +#64bit +[strict-equal? 0 shift to integer! #{7fffffffffffffff} to integer! #{8000000000000000}] +#64bit +[strict-equal? 0 shift to integer! #{7fffffffffffffff} to integer! #{8000000000000001}] +#64bit +[strict-equal? 0 shift to integer! #{7fffffffffffffff} -65] +#64bit +[strict-equal? 0 shift to integer! #{7fffffffffffffff} -64] +#64bit +[strict-equal? 0 shift to integer! #{7fffffffffffffff} -63] +#64bit +[strict-equal? 1 shift to integer! #{7fffffffffffffff} -62] +#64bit +[strict-equal? to integer! #{3fffffffffffffff} shift to integer! #{7fffffffffffffff} -1] +#64bit +[strict-equal? to integer! #{7fffffffffffffff} shift to integer! #{7fffffffffffffff} 0] +#64bit +[error? try [shift to integer! #{7fffffffffffffff} 1]] +#64bit +[error? try [shift to integer! #{7fffffffffffffff} 62]] +#64bit +[error? try [shift to integer! #{7fffffffffffffff} 63]] +#64bit +[error? try [shift to integer! #{7fffffffffffffff} 64]] +#64bit +[error? try [shift to integer! #{7fffffffffffffff} 65]] +#64bit +[error? try [shift to integer! #{7fffffffffffffff} to integer! #{7ffffffffffffffe}]] +#64bit +[error? try [shift to integer! #{7fffffffffffffff} to integer! #{7fffffffffffffff}]] diff --git a/tests/math/signq.test.reb b/tests/math/signq.test.reb new file mode 100644 index 0000000000..d87ea2e310 --- /dev/null +++ b/tests/math/signq.test.reb @@ -0,0 +1,20 @@ +; functions/math/signq.r +[0 = sign? 0] +[1 = sign? 1] +[-1 = sign? -1] +[1 = sign? 2147483647] +[-1 = sign? -2147483648] +; decimal +[0 = sign? 0.0] +[1 = sign? 4.94065645841247E-324] +[-1 = sign? -4.94065645841247E-324] +[1 = sign? 1.7976931348623157e308] +[-1 = sign? -1.7976931348623157e308] +; money +[0 = sign? $0] +[1 = sign? $0.000000000000001] +[-1 = sign? -$0.000000000000001] +; time +[0 = sign? 0:00] +[1 = sign? 0:00:0.000000001] +[-1 = sign? -0:00:0.000000001] diff --git a/tests/math/sine.test.reb b/tests/math/sine.test.reb new file mode 100644 index 0000000000..dc601e3bd7 --- /dev/null +++ b/tests/math/sine.test.reb @@ -0,0 +1,37 @@ +; functions/math/sine.r +[0 = sine 0] +[0 = sine/radians 0] +[0.5 = sine 30] +[0.5 = sine/radians pi / 6] +[((square-root 2) / 2) = sine 45] +[((square-root 2) / 2) = sine/radians pi / 4] +[((square-root 3) / 2) = sine 60] +[((square-root 3) / 2) = sine/radians pi / 3] +[1 = sine 90] +[1 = sine/radians pi / 2] +[0 = sine 180] +[0 = sine/radians pi] +[-0.5 = sine -30] +[-0.5 = sine/radians pi / -6] +[((square-root 2) / -2) = sine -45] +[((square-root 2) / -2) = sine/radians pi / -4] +[((square-root 3) / -2) = sine -60] +[((square-root 3) / -2) = sine/radians pi / -3] +[-1 = sine -90] +[-1 = sine/radians pi / -2] +[0 = sine -180] +[0 = sine/radians negate pi] +[((sine 1e-12) / 1e-12) = (pi / 180)] +[((sine/radians 1e-9) / 1e-9) = 1.0] +; #bug#852 +; Flint Hills test +[ + n: 25000 + s4: 0.0 + repeat l n [ + k: to decimal! l + ks: sine/radians k + s4: (1.0 / (k * k * k * ks * ks)) + s4 + ] + 30.314520404 = round/to s4 1e-9 +] diff --git a/tests/math/square-root.test.reb b/tests/math/square-root.test.reb new file mode 100644 index 0000000000..7fa3ff9172 --- /dev/null +++ b/tests/math/square-root.test.reb @@ -0,0 +1,8 @@ +; functions/math/square-root.r +[0 = square-root 0] +[error? try [square-root -1]] +[1 = square-root 1] +[0.5 = square-root 0.25] +[2 = square-root 4] +[3 = square-root 9] +[1.1 = square-root 1.21] diff --git a/tests/math/subtract.test.reb b/tests/math/subtract.test.reb new file mode 100644 index 0000000000..3d96f8363a --- /dev/null +++ b/tests/math/subtract.test.reb @@ -0,0 +1,219 @@ +; functions/math/subtract.r +[1 == subtract 3 2] +; integer -9223372036854775808 - x tests +#64bit +[0 == subtract -9223372036854775808 -9223372036854775808] +#64bit +[-1 == subtract -9223372036854775808 -9223372036854775807] +#64bit +[-9223372036854775807 == subtract -9223372036854775808 -1] +#64bit +[-9223372036854775808 = subtract -9223372036854775808 0] +#64bit +[error? try [subtract -9223372036854775808 1]] +#64bit +[error? try [subtract -9223372036854775808 9223372036854775806]] +#64bit +[error? try [subtract -9223372036854775808 9223372036854775807]] +; integer -9223372036854775807 - x tests +#64bit +[1 = subtract -9223372036854775807 -9223372036854775808] +#64bit +[0 = subtract -9223372036854775807 -9223372036854775807] +#64bit +[-9223372036854775806 = subtract -9223372036854775807 -1] +#64bit +[-9223372036854775807 = subtract -9223372036854775807 0] +#64bit +[-9223372036854775808 = subtract -9223372036854775807 1] +#64bit +[error? try [subtract -9223372036854775807 9223372036854775806]] +#64bit +[error? try [subtract -9223372036854775807 9223372036854775807]] +; integer -2147483648 - x tests +[0 = subtract -2147483648 -2147483648] +[-2147483647 = subtract -2147483648 -1] +[-2147483648 = subtract -2147483648 0] +#32bit +[error? try [subtract -2147483648 1]] +#64bit +[-2147483649 = subtract -2147483648 1] +#32bit +[error? try [subtract -2147483648 2147483647]] +#64bit +[-4294967295 = subtract -2147483648 2147483647] +; integer -1 - x tests +#64bit +[9223372036854775807 = subtract -1 -9223372036854775808] +#64bit +[9223372036854775806 = subtract -1 -9223372036854775807] +[0 = subtract -1 -1] +[-1 = subtract -1 0] +[-2 = subtract -1 1] +#64bit +[-9223372036854775807 = subtract -1 9223372036854775806] +#64bit +[-9223372036854775808 = subtract -1 9223372036854775807] +; integer 0 - x tests +#64bit +[error? try [subtract 0 -9223372036854775808]] +#32bit +[error? try [subtract 0 -2147483648]] +#64bit +[2147483648 = subtract 0 -2147483648] +#64bit +[9223372036854775807 = subtract 0 -9223372036854775807] +[1 = subtract 0 -1] +[0 = subtract 0 0] +[-1 = subtract 0 1] +#64bit +[-9223372036854775806 = subtract 0 9223372036854775806] +#64bit +[-9223372036854775807 = subtract 0 9223372036854775807] +; integer 1 - x tests +#64bit +[error? try [subtract 1 -9223372036854775808]] +#64bit +[error? try [subtract 1 -9223372036854775807]] +[2 = subtract 1 -1] +[1 = subtract 1 0] +[0 = subtract 1 1] +#64bit +[-9223372036854775805 = subtract 1 9223372036854775806] +#64bit +[-9223372036854775806 = subtract 1 9223372036854775807] +; integer 2147483647 + x +#32bit +[error? try [subtract 2147483647 -2147483648]] +#64bit +[4294967295 = subtract 2147483647 -2147483648] +#32bit +[error? try [subtract 2147483647 -1]] +#64bit +[2147483648 = subtract 2147483647 -1] +[2147483647 = subtract 2147483647 0] +#32bit +[2147483646 = subtract 2147483647 1] +#32bit +[0 = subtract 2147483647 2147483647] +; integer 9223372036854775806 - x tests +#64bit +[error? try [subtract 9223372036854775806 -9223372036854775808]] +#64bit +[error? try [subtract 9223372036854775806 -9223372036854775807]] +#64bit +[9223372036854775807 = subtract 9223372036854775806 -1] +#64bit +[9223372036854775806 = subtract 9223372036854775806 0] +#64bit +[9223372036854775805 = subtract 9223372036854775806 1] +#64bit +[0 = subtract 9223372036854775806 9223372036854775806] +#64bit +[-1 = subtract 9223372036854775806 9223372036854775807] +; integer 9223372036854775807 - x tests +#64bit +[error? try [subtract 9223372036854775807 -9223372036854775808]] +#64bit +[error? try [subtract 9223372036854775807 -9223372036854775807]] +#64bit +[error? try [subtract 9223372036854775807 -1]] +#64bit +[9223372036854775807 = subtract 9223372036854775807 0] +#64bit +[9223372036854775806 = subtract 9223372036854775807 1] +#64bit +[1 = subtract 9223372036854775807 9223372036854775806] +#64bit +[0 = subtract 9223372036854775807 9223372036854775807] +; decimal - integer +[0.1 = subtract 1.1 1] +[-2147483648.0 = subtract -1.0 2147483647] +[2147483649.0 = subtract 1.0 -2147483648] +; integer - decimal +[-0.1 = subtract 1 1.1] +[2147483648.0 = subtract 2147483647 -1.0] +[-2147483649.0 = subtract -2147483648 1.0] +; -1.7976931348623157e308 - decimal +[0.0 = subtract -1.7976931348623157e308 -1.7976931348623157e308] +[-1.7976931348623157e308 = subtract -1.7976931348623157e308 -1.0] +[-1.7976931348623157e308 = subtract -1.7976931348623157e308 -4.94065645841247E-324] +[-1.7976931348623157e308 = subtract -1.7976931348623157e308 0.0] +[-1.7976931348623157e308 = subtract -1.7976931348623157e308 4.94065645841247E-324] +[-1.7976931348623157e308 = subtract -1.7976931348623157e308 1.0] +[error? try [subtract -1.7976931348623157e308 1.7976931348623157e308]] +; -1.0 + decimal +[1.7976931348623157e308 = subtract -1.0 -1.7976931348623157e308] +[0.0 = subtract -1.0 -1.0] +[-1.0 = subtract -1.0 -4.94065645841247E-324] +[-1.0 = subtract -1.0 0.0] +[-1.0 = subtract -1.0 4.94065645841247E-324] +[-2.0 = subtract -1.0 1.0] +[-1.7976931348623157e308 = subtract -1.0 1.7976931348623157e308] +; -4.94065645841247E-324 + decimal +[1.7976931348623157e308 = subtract -4.94065645841247E-324 -1.7976931348623157e308] +[1.0 = subtract -4.94065645841247E-324 -1.0] +[0.0 = subtract -4.94065645841247E-324 -4.94065645841247E-324] +[-4.94065645841247E-324 = subtract -4.94065645841247E-324 0.0] +[-9.88131291682493E-324 = subtract -4.94065645841247E-324 4.94065645841247E-324] +[-1.0 = subtract -4.94065645841247E-324 1.0] +[-1.7976931348623157e308 = subtract -4.94065645841247E-324 1.7976931348623157e308] +; 0.0 + decimal +[1.7976931348623157e308 = subtract 0.0 -1.7976931348623157e308] +[1.0 = subtract 0.0 -1.0] +[4.94065645841247E-324 = subtract 0.0 -4.94065645841247E-324] +[0.0 = subtract 0.0 0.0] +[-4.94065645841247E-324 = subtract 0.0 4.94065645841247E-324] +[-1.0 = subtract 0.0 1.0] +[-1.7976931348623157e308 = subtract 0.0 1.7976931348623157e308] +; 4.94065645841247E-324 + decimal +[1.7976931348623157e308 = subtract 4.94065645841247E-324 -1.7976931348623157e308] +[1.0 = subtract 4.94065645841247E-324 -1.0] +[9.88131291682493E-324 = subtract 4.94065645841247E-324 -4.94065645841247E-324] +[4.94065645841247E-324 = subtract 4.94065645841247E-324 0.0] +[0.0 = subtract 4.94065645841247E-324 4.94065645841247E-324] +[-1.0 = subtract 4.94065645841247E-324 1.0] +[-1.7976931348623157e308 = subtract 4.94065645841247E-324 1.7976931348623157e308] +; 1.0 + decimal +[1.7976931348623157e308 = subtract 1.0 -1.7976931348623157e308] +[2.0 = subtract 1.0 -1.0] +[1.0 = subtract 1.0 4.94065645841247E-324] +[1.0 = subtract 1.0 0.0] +[1.0 = subtract 1.0 -4.94065645841247E-324] +[0.0 = subtract 1.0 1.0] +[-1.7976931348623157e308 = subtract 1.0 1.7976931348623157e308] +; 1.7976931348623157e308 + decimal +[error? try [subtract 1.7976931348623157e308 -1.7976931348623157e308]] +[1.7976931348623157e308 = subtract 1.7976931348623157e308 -1.0] +[1.7976931348623157e308 = subtract 1.7976931348623157e308 -4.94065645841247E-324] +[1.7976931348623157e308 = subtract 1.7976931348623157e308 0.0] +[1.7976931348623157e308 = subtract 1.7976931348623157e308 4.94065645841247E-324] +[1.7976931348623157e308 = subtract 1.7976931348623157e308 1.0] +[0.0 = subtract 1.7976931348623157e308 1.7976931348623157e308] +; pair +[0x0 = subtract -2147483648x-2147483648 -2147483648x-2147483648] +[-2147483647x-2147483647 = subtract -2147483648x-2147483648 -1x-1] +[-2147483648x-2147483648 = subtract -2147483648x-2147483648 0x0] +[0x0 = subtract -1x-1 -1x-1] +[-1x-1 = subtract -1x-1 0x0] +[-2x-2 = subtract -1x-1 1x1] +[2147483648x2147483648 = subtract 0x0 -2147483648x-2147483648] +[1x1 = subtract 0x0 -1x-1] +[0x0 = subtract 0x0 0x0] +[-1x-1 = subtract 0x0 1x1] +[-2147483647x-2147483647 = subtract 0x0 2147483647x2147483647] +[2x2 = subtract 1x1 -1x-1] +[1x1 = subtract 1x1 0x0] +[0x0 = subtract 1x1 1x1] +[2147483647x2147483647 = subtract 2147483647x2147483647 0x0] +[0x0 = subtract 2147483647x2147483647 2147483647x2147483647] +; char +[0.0.0 = subtract 0.0.0 0.0.0] +[0.0.0 = subtract 0.0.0 0.0.1] +[0.0.0 = subtract 0.0.0 0.0.255] +[0.0.1 = subtract 0.0.1 0.0.0] +[0.0.0 = subtract 0.0.1 0.0.1] +[0.0.0 = subtract 0.0.1 0.0.255] +[0.0.255 = subtract 0.0.255 0.0.0] +[0.0.254 = subtract 0.0.255 0.0.1] +[0.0.0 = subtract 0.0.255 0.0.255] diff --git a/tests/math/tangent.test.reb b/tests/math/tangent.test.reb new file mode 100644 index 0000000000..c87600f6ee --- /dev/null +++ b/tests/math/tangent.test.reb @@ -0,0 +1,30 @@ +; functions/math/tangent.r +[error? try [tangent -90]] +[error? try [tangent/radians pi / -2]] +[(negate square-root 3) = tangent -60] +[(negate square-root 3) = tangent/radians pi / -3] +[-1 = tangent -45] +[-1 = tangent/radians pi / -4] +[((square-root 3) / -3) = tangent -30] +[((square-root 3) / -3) = tangent/radians pi / -6] +[0 = tangent 0] +[0 = tangent/radians 0] +[((square-root 3) / 3) = tangent 30] +[((square-root 3) / 3) = tangent/radians pi / 6] +[1 = tangent 45] +[1 = tangent/radians pi / 4] +[(square-root 3) = tangent 60] +[(square-root 3) = tangent/radians pi / 3] +[error? try [tangent 90]] +[error? try [tangent/radians pi / 2]] +; Flint Hills test +[ + n: 25000 + s4t: 0.0 + repeat l n [ + k: to decimal! l + kt: tangent/radians k + s4t: (((1.0 / (kt * kt)) + 1.0) / (k * k * k)) + s4t + ] + 30.314520404 = round/to s4t 1e-9 +] diff --git a/tests/math/zeroq.test.reb b/tests/math/zeroq.test.reb new file mode 100644 index 0000000000..03b5f5d295 --- /dev/null +++ b/tests/math/zeroq.test.reb @@ -0,0 +1,47 @@ +; functions/math/zeroq.r +[zero? 0] +[not zero? 1] +[not zero? -1] +[not zero? 2147483647] +[not zero? -2147483648] +#64bit +[not zero? 9223372036854775807] +#64bit +[not zero? -9223372036854775808] +; decimal +[zero? 0.0] +[not zero? 1.7976931348623157e308] +[not zero? -1.7976931348623157e308] +; pair +[zero? 0x0] +[not zero? 1x0] +[not zero? -1x0] +[not zero? 2147483647x0] +[not zero? -2147483648x0] +[not zero? 0x1] +[not zero? 0x-1] +[not zero? 0x2147483647] +[not zero? 0x-2147483648] +; char +[zero? #"^@"] +[not zero? #"^a"] +[not zero? #"^(ff)"] +; money +[zero? $0] +[not zero? $0.01] +[not zero? -$0.01] +[not zero? $999999999999999.87] +[not zero? -$999999999999999.87] +[zero? negate $0] +; time +[zero? 0:00] +[not zero? 0:00:0.000000001] +[not zero? -0:00:0.000000001] +; tuple +[zero? 0.0.0] +[not zero? 1.0.0] +[not zero? 255.0.0] +[not zero? 0.1.0] +[not zero? 0.255.0] +[not zero? 0.0.1] +[not zero? 0.0.255] diff --git a/tests/misc/fib.r b/tests/misc/fib.r new file mode 100644 index 0000000000..56f41c8d58 --- /dev/null +++ b/tests/misc/fib.r @@ -0,0 +1,64 @@ +REBOL [ + Title: {Initial test for user natives by @ShixinZeng} +] + +c-fib: make-native [ + "nth Fibonacci Number" + n [integer!] +]{ + int n = VAL_INT64(ARG(n)); + + if (n < zero) { Init_Integer(D_OUT, -1); return R_OUT; } + if (n <= one) { Init_Integer(D_OUT, n); return R_OUT; } + + int i0 = zero; + int i1 = one; + while (n > one) { + int t = i1; + i1 = i1 + i0; + i0 = t; + --n; + } + Init_Integer(D_OUT, i1); + return R_OUT; +} + +compile/options [ + "const int zero = 0;" + "const int one = 1;" + c-fib +] compose [ + options "-nostdlib" +] + +fib: func [ + n [integer!] +][ + if n < 0 [return -1] + if n <= 1 [return n] + i0: 0 + i1: 1 + while [n > 1] [ + t: i1 + i1: i0 + i1 + i0: t + -- n + ] + i1 +] + +print ["c-fib 30:" c-r: c-fib 30] +print ["fib 30:" r: fib 30] +assert [r = c-r] + +if find system/options/args "bench" [ + n-loop: 10000 + + c-t: dt [ + loop n-loop [c-fib 30] + ] + r-t: dt [ + loop n-loop [fib 30] + ] + print ["c-t:" c-t "r-t:" r-t "improvement:" r-t / c-t] +] diff --git a/tests/misc/gtk.r b/tests/misc/gtk.r new file mode 100644 index 0000000000..4618f2a433 --- /dev/null +++ b/tests/misc/gtk.r @@ -0,0 +1,291 @@ +REBOL [] + +recycle/torture + +libgtk: try/except [ + make library! %libgtk-3.so +][ + make library! %libgtk-3.so.0 +] +libglib: try/except [ + make library! %libglib-2.0.so +][ + make library! %libglib-2.0.so.0 +] +libgob: try/except [ + make library! %libgobject-2.0.so +][ + make library! %libgobject-2.0.so.0 +] + +gtk-init: + make-routine libgtk "gtk_init" [ + argc [pointer] + argv [pointer] + ] + +gtk-window-new: + make-routine libgtk "gtk_window_new" [ + type [int32] + return: [pointer] + ] + +gtk-window-set-default-size: + make-routine libgtk "gtk_window_set_default_size" [ + windown [pointer] + width [int32] + height [int32] + return: [void] + ] + +gtk-window-set-resizable: + make-routine libgtk "gtk_window_set_resizable" [ + window [pointer] + resizable [int32] + return: [void] + ] + +gtk-window-set-title: + make-routine libgtk "gtk_window_set_title" [ + win [pointer] + title [pointer] + ] + +gtk-widget-show: + make-routine libgtk "gtk_widget_show" [ + widget [pointer] + ] + +gtk-hbox-new: + make-routine libgtk "gtk_hbox_new" [ + return: [pointer] + ] + +gtk-box-pack-start: + make-routine libgtk "gtk_box_pack_start" [ + box [pointer] + child [pointer] + expand [uint8] + fill [uint8] + padding [uint32] + return: [pointer] + ] + +gtk-box-set-spacing: + make-routine libgtk "gtk_box_set_spacing" [ + box [pointer] + spacing [int32] + return: [void] + ] + +gtk-box-get-spacing: + make-routine libgtk "gtk_box_get_spacing" [ + box [pointer] + return: [int32] + ] + +gtk-toggle-button-new-with-label: + make-routine libgtk "gtk_toggle_button_new_with_label" [ + label [pointer] + return: [pointer] + ] + +gtk-font-button-new: + make-routine libgtk "gtk_font_button_new" [ + return: [pointer] + ] + +gtk-font-chooser-widget-new: + make-routine libgtk "gtk_font_chooser_widget_new" [ + return: [pointer] + ] + +gtk-font-chooser-set-font: + make-routine libgtk "gtk_font_chooser_set_font" [ + fontchooser [pointer] + fontname [pointer] + ] + +gtk-color-button-new: + make-routine libgtk "gtk_color_button_new" [ + return: [pointer] + ] + +gtk-main: + make-routine libgtk "gtk_main" [] + +gtk-main-quit: + make-routine libgtk "gtk_main_quit" [] + +g-signal-connect-data: + make-routine libgob "g_signal_connect_data" [ + instance [pointer] + detailed-signal [pointer] + c-handler [pointer] + data [pointer] + destroy-data [pointer] + connect-flags [int32] + return: [int64] + ] + +g-signal-connect: func [ + instance [integer!] + detailed-signal [integer! string! binary!] + c-handler [integer!] + data [integer!] +][ + g-signal-connect-data instance detailed-signal c-handler data 0 0 +] + +gtk-button-new-with-label: + make-routine libgtk "gtk_button_new_with_label" [ + label [pointer] + return: [pointer] + ] + +gtk-button-set-label: + make-routine libgtk "gtk_button_set_label" [ + button [pointer] + label [pointer] + ] + +gtk-container-add: + make-routine libgtk "gtk_container_add" [ + container [pointer] + elem [pointer] + ] + +init-gtk: function [app] [ + arg0: make struct! compose/deep [ + appn [uint8 [(1 + length? app)]] + ] + change arg0 append to binary! app #{00} + + argv: make struct! [ + args [pointer [2]] + ] + + print ["assign pointer"] + argv/args/1: reflect arg0 'addr + + print ["argv:" argv] + argc: make struct! [ + c: [int32] 1 + ] + + addr-argv: make struct! [ + addr: [pointer] (reflect argv 'addr) + ] + + print ["addr-argv:" addr-argv] + print ["addr of addr-argv:" reflect addr-argv 'addr] + + gtk-init (reflect argc 'addr) (reflect addr-argv 'addr) + print ["argc:" argc "argv:" argv] +] + +mk-cb: func [ + return: [function!] + args [block!] + body [block!] + /local r-args arg a tmp-func +][ + r-args: copy [] + + arg:[ + copy a word! (append r-args a) + block! + opt string! + ] + attr: [ + set-word! + block! | word! + ] + + parse args [ + opt string! + some [ arg | attr ] + ] + + print ["args:" mold args] + + tmp-func: function r-args body + + print ["tmp-func:" mold :tmp-func] + make callback! compose/deep [[(args)] :tmp-func] +] + +on-click-callback: mk-cb [ + widget [pointer] + data [pointer] +][ + print ["clicked"] + i: make struct! compose/deep [ + [ + raw-memory: (data) + raw-size: 4 + ] + i [int32] + ] + i/i: i/i + 1 + gtk-button-set-label widget spaced [ + "clicked" i/i either i/i = 1 ["time"]["times"] + ] +] + +app-quit-callback: mk-cb [ +][ + print ["app quiting"] + gtk-main-quit +] + +NULL: 0 +GTK_WINDOW_TOPLEVEL: 0 +GTK_WINDOW_POPUP: 1 + +init-gtk "./r3-view-linux" +print ["gtk initialized"] + +win: gtk-window-new GTK_WINDOW_TOPLEVEL +gtk-window-set-default-size win 10 10 +gtk-window-set-resizable win 1 +print ["win:" win] +g-signal-connect win "destroy" (reflect :app-quit-callback 'addr) NULL +gtk-window-set-title win "gtk+ from rebol" + +hbox: gtk-hbox-new +gtk-box-set-spacing hbox 10 + +gtk-container-add win hbox + +but1: gtk-button-new-with-label "button 1" +gtk-box-pack-start hbox but1 1 1 0 + +n-clicked: make struct! [i: [int32] 0] +g-signal-connect but1 "clicked" (reflect :on-click-callback 'addr) (reflect n-clicked 'addr) + +but2: gtk-button-new-with-label "button 2" +gtk-box-pack-start hbox but2 1 1 0 + +but3: gtk-toggle-button-new-with-label "toggle" +gtk-box-pack-start hbox but3 1 1 0 + +;font-chooser: gtk-font-chooser-widget-new +;gtk-box-pack-start hbox font-chooser 1 1 0 +;gtk-font-chooser-set-font font-chooser "Times Bold 18" + +font-button: gtk-font-button-new +gtk-box-pack-start hbox font-button 1 1 0 + +color-button: gtk-color-button-new +gtk-box-pack-start hbox color-button 1 1 0 + +gtk-widget-show color-button +gtk-widget-show font-button +gtk-widget-show but1 +gtk-widget-show but2 +gtk-widget-show but3 +gtk-widget-show hbox +gtk-widget-show win +print ["spacing:" gtk-box-get-spacing hbox] +gtk-main diff --git a/tests/misc/printf.r b/tests/misc/printf.r new file mode 100644 index 0000000000..106fad2d45 --- /dev/null +++ b/tests/misc/printf.r @@ -0,0 +1,19 @@ +REBOL [] + +recycle/torture + + +libc: make library! %libc.so.6 + +x64?: 40 = fifth system/version +size_t: either x64? ['int64]['int32] +printf: make-routine libc "printf" compose/deep [ + return: [int32] + f [pointer] + ... +] + +;(printf "hello^/" 0 [pointer]) +(printf "hello %s^/" "world" [pointer]) +(printf "hello^/") +(print "hi") diff --git a/tests/misc/qsort.r b/tests/misc/qsort.r new file mode 100644 index 0000000000..d99212bc77 --- /dev/null +++ b/tests/misc/qsort.r @@ -0,0 +1,58 @@ +REBOL [] + +recycle/torture + + +f: func [ + a [integer!] "pointer to an integer" + b [integer!] "pointer to an integer" +][ + i: make struct! compose/deep [ + [raw-memory: (a)] + i [int32] + ] + j: make struct! compose/deep [ + [raw-memory: (b)] + i [int32] + ] + case [ + i/i = j/i [0] + i/i < j/i [-1] + i/i > j/i [1] + ] +] + +cb: make callback! [ + [ + a [pointer] + b [pointer] + return: [int64] + ] + :f +] + +libc: make library! %libc.so.6 + +x64?: 40 = fifth system/version +size_t: either x64? ['int64]['int32] + +; This tests the compatibility shim for MAKE that lets MAKE ROUTINE! work, +; though that is deprecated (use MAKE-ROUTINE or MAKE-ROUTINE-RAW) +; +qsort: make routine! compose/deep [ + [ + base [pointer] + nmemb [(size_t)] + size [(size_t)] + comp [pointer] + ] + (libc) "qsort" +] + +array: make vector! [integer! 32 5 [10 8 2 9 5]] +print ["array:" mold array] +probe (reflect :cb 'addr) +qsort array 5 4 (reflect :cb 'addr) +print ["array:" mold array] ; [2 5 8 9 10] + +close libc diff --git a/tests/misc/qsort_r.r b/tests/misc/qsort_r.r new file mode 100644 index 0000000000..14be2b89f3 --- /dev/null +++ b/tests/misc/qsort_r.r @@ -0,0 +1,80 @@ +REBOL [ + Title: {Demo tunneling of REBVAL* through routine to callback in FFI} + Description: { + There are two versions of quicksort in the C library. Plain `qsort` + is written in such a way that if your comparator needs any information + besides the two items to compare, it has to get that from global + variables. `qsort_r` takes an additional void pointer parameter + which it passes through to the comparator, which could hold some + state information and thus eliminate the requirement to use global + variables for any parameterization of the comparator. + + This demonstrates the use of the FFI argument type of REBVAL. + While the purpose of the FFI is to talk to libraries that likely + are not linked to any Rebol APIs (and hence would not be able to + make use of a REBVAL), this shows the use of it to "tunnel" a + Rebol value through to a written-in-Rebol comparator callback. + } +] + +recycle/torture + + +f: func [ + a [integer!] "pointer to an integer" + b [integer!] "pointer to an integer" + arg [any-value!] "some tunneled argument" +][ + print mold arg + + i: make struct! compose/deep [ + [raw-memory: (a)] + i [int32] + ] + j: make struct! compose/deep [ + [raw-memory: (b)] + i [int32] + ] + case [ + i/i = j/i [0] + i/i < j/i [-1] + i/i > j/i [1] + ] +] + +cb: make callback! [ + [ + a [pointer] + b [pointer] + arg [rebval] + return: [int64] + ] + :f +] + +libc: make library! %libc.so.6 + +x64?: 40 = fifth system/version +size_t: either x64? ['int64]['int32] + +; This tests the compatibility shim for MAKE that lets MAKE ROUTINE! work, +; though that is deprecated (use MAKE-ROUTINE or MAKE-ROUTINE-RAW) +; +qsort_r: make routine! compose/deep [ + [ + base [pointer] + nmemb [(size_t)] + size [(size_t)] + comp [pointer] + arg [rebval] + ] + (libc) "qsort_r" +] + +array: make vector! [integer! 32 5 [10 8 2 9 5]] +print ["array:" mold array] +probe (reflect :cb 'addr) +qsort_r array 5 4 :cb +assert [array = make vector! [integer! 32 5 [2 5 8 9 10]]] + +close libc diff --git a/tests/misc/shttpd.r b/tests/misc/shttpd.r new file mode 100644 index 0000000000..c177352564 --- /dev/null +++ b/tests/misc/shttpd.r @@ -0,0 +1,92 @@ +REBOL [title: "A tiny static HTTP server" author: 'abolka date: 2009-11-04] + +code-map: make map! [200 "OK" 400 "Forbidden" 404 "Not Found"] +mime-map: make map! [ + "html" "text/html" "css" "text/css" "js" "application/javascript" + "gif" "image/gif" "jpg" "image/jpeg" "png" "image/png" + "r" "text/plain" "r3" "text/plain" "reb" "text/plain" +] +error-template: trim/auto { + $code $text

$text

+

Requested URI: $uri


shttpd.r on +
REBOL 3 $r3 +} + +error-response: func [code uri /local values] [ + values: [code (code) text (code-map/:code) uri (uri) r3 (system/version)] + reduce [code "text/html" reword error-template compose values] +] + +start-response: func [port res /local code text type body] [ + set [code type body] res + write port unspaced [ + "HTTP/1.0" space code space code-map/:code crlf + "Content-type:" space type crlf + "Content-length:" space length? body crlf + crlf + ] + ;; Manual chunking is only necessary because of several bugs in R3's + ;; networking stack (mainly cc#2098 & cc#2160; in some constellations also + ;; cc#2103). Once those are fixed, we should directly use R3's internal + ;; chunking instead: `write port body`. + port/locals: copy body +] + +send-chunk: func [port] [ + ;; Trying to send data >32'000 bytes at once will trigger R3's internal + ;; chunking (which is buggy, see above). So we cannot use chunks >32'000 + ;; for our manual chunking. + either empty? port/locals [ + _ + ][ + write port take/part port/locals 32'000 + ] +] + +handle-request: function [config req] [ + parse to-string req ["get " ["/ " | copy uri: to " "]] + uri: default "index.html" + print ["URI:" uri] + parse uri [some [thru "."] copy ext to end (type: mime-map/:ext)] + type: default "application/octet-stream" + if not exists? file: config/root/:uri [return error-response 404 uri] + if error? try [data: read file] [return error-response 400 uri] + reduce [200 type data] +] + +awake-client: function [event] [ + port: event/port + switch event/type [ + read [ + either find port/data to-binary join-of crlf crlf [ + res: handle-request port/locals/config port/data + start-response port res + ] [ + read port + ] + ] + wrote [unless send-chunk port [close port]] + close [close port] + ] +] + +awake-server: func [event /local client] [ + if event/type = 'accept [ + client: first event/port + client/awake: :awake-client + read client + ] +] + +serve: func [web-port web-root /local listen-port] [ + listen-port: open join-of tcp://: web-port + listen-port/locals: has compose/deep [ + config: [root: (web-root)] + ] + listen-port/awake: :awake-server + wait listen-port +] + +serve 8080 system/options/path +; vim: set syn=rebol sw=4 ts=4: + diff --git a/tests/misc/timing.c b/tests/misc/timing.c new file mode 100644 index 0000000000..660e0ba020 --- /dev/null +++ b/tests/misc/timing.c @@ -0,0 +1,24 @@ +// +// This is a small isolated template for doing C timing tests. +// + +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + clock_t begin, end; + double time_spent; + + begin = clock(); + + // Put testing code here + + end = clock(); + time_spent = (double)(end - begin) / CLOCKS_PER_SEC; + + printf("%f\n", time_spent); + return 0; +} diff --git a/tests/misc/varargs-old.r b/tests/misc/varargs-old.r new file mode 100644 index 0000000000..cc9e815d40 --- /dev/null +++ b/tests/misc/varargs-old.r @@ -0,0 +1,71 @@ +REBOL [] + +recycle/torture + +libc: switch fourth system/version [ + 3 [ + make library! %msvcrt.dll + ] + 4 [ + make library! %libc.so.6 + ] +] + +printf: make routine! [ + [ + "An example of wrapping variadic functions" + fmt [pointer] "fixed" + ... "variadic" + return: [int32] + ] + libc "printf" +] + +sprintf: make routine! [ + [ + "An example of wrapping variadic functions" + buf [pointer] "destination buffer, must be big enough" + fmt [pointer] "fixed" + ... "variadic" + return: [int32] + ] + libc "sprintf" +] + +i: 1000 +j: 0.0 +printf reduce [ + join-of "i: %d, %f" newline + i [int32] + j [float] +] + +printf compose [ + "hello %p%c" + ;10.0 + "ffi" [pointer] + ;"ffi" + (to integer! newline) [int8] +] + +printf compose [ + "hello %s%c" + "world" [pointer] + (to integer! newline) [int8] +] + +printf compose [ + "hello %s%c" + "ffi" [pointer] + (to integer! newline) [int8] +] + +h: make struct! [ + a [uint8 [128]] +] +len: sprintf reduce [ + addr-of h + join-of "hello %s" newline + "world" [pointer] +] +prin ["h:" copy/part to string! values-of h len] diff --git a/tests/misc/varargs.r b/tests/misc/varargs.r new file mode 100644 index 0000000000..d20e0a9c99 --- /dev/null +++ b/tests/misc/varargs.r @@ -0,0 +1,71 @@ +REBOL [] + +recycle/torture + +libc: switch fourth system/version [ + 3 [ + make library! %msvcrt.dll + ] + 4 [ + make library! %libc.so.6 + ] +] + +printf: make routine! [ + [ + "An example of wrapping variadic functions" + fmt [pointer] "fixed" + ... "variadic" + return: [int32] + ] + libc "printf" +] + +sprintf: make routine! [ + [ + "An example of wrapping variadic functions" + buf [pointer] "destination buffer, must be big enough" + fmt [pointer] "fixed" + ... "variadic" + return: [int32] + ] + libc "sprintf" +] + +i: 1000 +j: make struct! [x [double]] +j/x: 12.34 +(printf + join-of "1. i: %d, %f" newline + i [int64] + j [struct! [x [double]]] +) + +(printf "2. hello %p%c" + "ffi" [pointer] + (to integer! newline) [int8] +) + +| printf + "3. hello %s%c" + "world" [pointer] + (to integer! newline) [int8] +| + +do compose [ + printf + "4. hello %s%c" + "ffi" [pointer] + (to integer! newline) [int8] +] + +h: make struct! [ + a [uint8 [128]] +] +len: (sprintf + addr-of h + join-of "hello %s" newline + "world" [pointer] +) + +prin ["5. h:" copy/part to string! values-of h len] diff --git a/tests/old/r2-tests.r b/tests/old/r2-tests.r new file mode 100644 index 0000000000..93b918df09 --- /dev/null +++ b/tests/old/r2-tests.r @@ -0,0 +1,532 @@ +; +; These are tests which were extracted from core-tests that were flagged +; #r2only. That indicated that they should not be run under Rebol3. +; (Tests which were exclusive to Rebol3 were tagged #r3only.) +; +; For the Ren/C testing effort, Rebol2 testing is no longer being run, +; and a maintenance of a distinction of #r3only or #ren-c-only would +; burden the addition of further tests. +; +; BUT rather than delete the #r2only test entries entirely just yet, they +; are archived here *in case they represent an unimplemented feature*. +; (That is, as opposed to a purposefully deprecated behavior.) +; +; It is no longer expected that Ren/C's copy of the core-tests file be +; able to be used with Rebol2. For a version that can do that, see: +; +; https://github.com/rebolsource/rebol-test +; +; -HF +; + +[datatype? hash!] +[datatype? list!] +[datatype? routine!] +[datatype? symbol!] +; error types +[error? make error! [throw no-loop]] +[error? make error! [throw no-function]] +[error? make error! [throw no-catch]] +[error? make error! [note no-load]] +[error? make error! [note exited]] +[error? make error! [syntax invalid]] +[error? make error! [syntax missing]] +[error? make error! [syntax header]] +[error? make error! [script no-value]] +[error? make error! [script need-value]] +[error? make error! [script no-arg]] +[error? make error! [script expect-arg]] +[error? make error! [script expect-set]] +[error? make error! [script invalid-arg]] +[error? make error! [script invalid-op]] +[error? make error! [script no-op-arg]] +[error? make error! [script no-return]] +[error? make error! [script not-defined]] +[error? make error! [script no-refine]] +[error? make error! [script invalid-path]] +[error? make error! [script cannot-use]] +[error? make error! [script already-used]] +[error? make error! [script out-of-range]] +[error? make error! [script past-end]] +[error? make error! [script no-memory]] +[error? make error! [script block-lines]] +[error? make error! [script invalid-part]] +[error? make error! [script wrong-denom]] +[error? make error! [script else-gone]] +[error? make error! [script bad-compression]] +[error? make error! [script bad-prompt]] +[error? make error! [script bad-port-action]] +[error? make error! [script needs]] +[error? make error! [script locked-word]] +[error? make error! [script too-many-refines]] +[error? make error! [script dup-vars]] +[error? make error! [script feature-na]] +[error? make error! [script bad-bad]] +[error? make error! [script limit-hit]] +[error? make error! [script call-fail]] +[error? make error! [script face-error]] +[error? make error! [script face-reused]] +[error? make error! [script bad-refine]] +[error? make error! [math zero-divide]] +[error? make error! [math overflow]] +[error? make error! [math positive]] +[error? make error! [access cannot-open]] +[error? make error! [access not-open]] +[error? make error! [access already-open]] +[error? make error! [access already-closed]] +[error? make error! [access read-error]] +[error? make error! [access invalid-spec]] +[error? make error! [access socket-open]] +[error? make error! [access no-connect]] +[error? make error! [access no-delete]] +[error? make error! [access no-rename]] +[error? make error! [access no-make-dir]] +[error? make error! [access protocol]] +[error? make error! [access timeout]] +[error? make error! [access new-level]] +[error? make error! [access security]] +[error? make error! [access invalid-path]] +[error? make error! [access bad-image]] +[error? make error! [access would-block]] +[error? make error! [access serial-timeout]] +[error? make error! [access write-error]] +[error? make error! [command fmt-too-short]] +[error? make error! [command fmt-no-struct-size]] +[error? make error! [command fmt-no-struct-align]] +[error? make error! [command fmt-bad-word]] +[error? make error! [command fmt-type-mismatch]] +[error? make error! [command fmt-size-mismatch]] +[error? make error! [command dll-arg-count]] +[error? make error! [command empty-command]] +[error? make error! [command db-not-open]] +[error? make error! [command db-too-many]] +[error? make error! [command cant-free]] +[error? make error! [command nothing-to-free]] +[error? make error! [command ssl-error]] +[error? make error! [user message]] +[error? make error! [internal bad-path]] +[error? make error! [internal not-here]] +[error? make error! [internal stack-overflow]] +[error? make error! [internal globals-full]] +[error? make error! [internal bad-internal]] +[function? first [#[function! [] []]]] +[gf: func [:x] [:x] a: 10 10 == gf a] +; Argument passing of "literal arguments" ("lit-args") +[lf: func ['x] [:x] (quote (10 + 20)) == lf (10 + 20)] +[lf: func ['x] [:x] (quote :o/f) == lf :o/f] +; basic test for recursive function! invocation +[ + ; context-less get-word + e: disarm try [do to block! ":a"] + e/id = 'not-bound +] +; behaviour for REBOL datatypes; unset +[ + unset 'a + e: disarm try [:a] + e/id = 'no-value +] +; minimum +[hash? make hash! []] +[not hash? 1] +[hash! = type? make hash! []] +; datatypes/image.r +[ + a-value: #[image! 1x1 #{}] + equal? pick a-value 0x0 0.0.0.0 +] +[issue? #] +[# == #[issue! ""]] +[# == make issue! 0] +[# == to issue! ""] +[list? make list! []] +[not list? 1] +[list! = type? make list! []] +; datatypes/lit-path.r +[3 == do reduce [get '+ 1 2]] +[ + a-value: make image! 1x1 + 0.0.0.0 == a-value/1 +] +[ + a-value: #2 + #"2" == a-value/1 +] +[ + a-value: make port! http:// + none? a-value/user-data +] +[symbol! = type? make symbol! "xx"] +; datatypes/tag.r +[datatype? any-block!] +[datatype? any-function!] +[datatype? any-string!] +[datatype? any-word!] +[datatype? any-number!] +[datatype? any-series!] +[ + error? a-value: try [1 / 0] + same? disarm :a-value disarm a-value +] +; lit-paths are word-active +[ + a-value: first ['a/b] + a-value == to path! :a-value +] +; ops are word-active +[ + a-value: get '+ + 3 == a-value 1 2 +] +[ + a-value: make struct! [] none + same? third :a-value third a-value +] +[ + unset 'a-value + e: disarm try [a-value] + e/id = 'no-value +] +; image! alpha not specified = 0 +[equal? #[image! 1x1 #{000000} #{00}] #[image! 1x1 #{000000}]] +; date! ignores time portion +[equal? 2-Jul-2009 2-Jul-2009/22:20] +[error? try [equal? () ()]] +[error? try [equal? () none]] +[error? try [equal? none ()]] +[not equal? disarm try [equal? none ()] disarm try [equal? () none]] +[error? try [none = ()]] +[error? try [none != ()]] +[error? try [() = ()]] +[error? try [() != ()]] +[ + a-value: first ['a/b] + parse :a-value [b-value:] + not same? :a-value :b-value +] +[ + a-value: first [()] + parse a-value [b-value:] + not same? a-value b-value +] +[ + a-value: 'a/b + parse a-value [b-value:] + not same? :a-value :b-value +] +[ + a-value: first [a/b:] + parse :a-value [b-value:] + not same? :a-value :b-value +] +[same? 'a first [:a]] +[same? 'a first ['a]] +[same? 'a first [a:]] +[same? first [:a] first ['a]] +[same? first [:a] first [a:]] +[same? first ['a] first [a:]] +[ + a-value: first ['a/b] + parse :a-value [b-value:] + not strict-equal? :a-value :b-value +] +[ + a-value: first [()] + parse a-value [b-value:] + not strict-equal? a-value b-value +] +[ + a-value: 'a/b + parse a-value [b-value:] + not strict-equal? :a-value :b-value +] +[ + a-value: first [a/b:] + parse :a-value [b-value:] + not strict-equal? :a-value :b-value +] +[strict-equal? 2-Jul-2009 2-Jul-2009/22:20] +[strict-equal? 2-Jul-2009 2-Jul-2009/00:00:00+00:00] +[use [a] [unset? get/opt 'a]] +[unset? any [()]] +[unset? any [false ()]] +[unset? any [() false]] +[-2 == apply :- [2]] +[logic! = case type? [true []]] +[object? disarm try [1 / 0]] +; functions/control/do.r +[ + a-value: #{} + same? a-value do a-value +] +[ + a: 12 + a-value: first [:a] + :a-value == do :a-value +] +[ + a-value: first ['a] + :a-value == do :a-value +] +[ + a-value: first [a:] + :a-value == do :a-value +] +[ + success: false + do/next [success: true success: false] + success +] +[[1 [2]] = do/next [1 2]] +[unset? first do/next []] +; RETURN stops the evaluation +[ + f1: does [do/next [return 1 2] 2] + 1 = f1 +] +[ + blk: [do/next blk] + error? try blk +] +; are error reports for do and do/next consistent? +[ + val1: disarm try [do [1 / 0]] + val2: disarm try [do/next [1 / 0]] + val1/near = val2/near +] +[error? err: try [else] c: disarm err c/id = 'else-gone] +; char tests +[ + num: 0 + char: #"^(ff)" + not for i char char 1 [ + num: num + 1 + if num > 1 [break] + ] +] +[ + num: 0 + char: #"^(0)" + not for i char char -1 [ + num: num + 1 + if num > 1 [break] + ] +] +[ + b: head insert copy [] try [1 / 0] + pokus1: func [[catch] block [block!] /local elem] [ + for i 1 length? block 1 [ + if error? set/opt 'elem first block [ + throw make error! {Dangerous element} + ] + block: next block + ] + ] + b: disarm try [pokus1 b] + b/near = [pokus1 b] +] +; in Rebol2 the FORALL function is unable to pass a THROW error test +[ + f: func [[catch] /local x] [ + x: [1] + forall x [throw make error! ""] + ] + e: disarm try [f] + e/near = [f] +] +[ + blk: copy out: copy [] + for i #"A" #"Z" 1 [append blk i] + forskip blk 2 [append out blk/1] + out = [#"A" #"C" #"E" #"G" #"I" #"K" #"M" #"O" #"Q" #"S" #"U" #"W" #"Y"] +] +; in Rebol2 the FORSKIP function is unable to pass a THROW error test +[ + f: func [[catch] /local x] [ + x: [1] + forskip x 1 [throw make error! ""] + ] + e: disarm try [f] + e/near = [f] +] +; string! test +[ + out: copy "" + repeat i "abc" [append out i] + out = "abc" +] +; block! test +[ + out: copy [] + repeat i [1 2 3] [append out i] + out = [1 2 3] +] +; local variable type safety +[ + test: false + repeat i 2 [ + either test [i == 2] [ + test: true + i: false + true + ] + ] +] +[ + e: disarm try [1 / 0] + e/id = 'zero-divide +] +[ + a: "a" + b: as-binary a + b == to binary! a + change a "b" + b == to binary! a +] +[ + a: #{00} + b: as-string a + b == to string! a + change a #{01} + b == to string! a +] +[block? load/next "1"] +; bug#1703 bug#1711 +[ + any [ + not error? e: try [make-dir %/folder-to-save-test-files] + (e: disarm e e/type = 'access) + ] +] +[0x0 = add -2147483648x-2147483648 -2147483648x-2147483648] +[2147483647x2147483647 = add -2147483648x-2147483648 -1x-1] +[-2147483647x-2147483647 = add -2147483648x-2147483648 1x1] +[-1x-1 = add -2147483648x-2147483648 2147483647x2147483647] +[2147483647x2147483647 = add -1x-1 -2147483648x-2147483648] +[2147483646x2147483646 = add -1x-1 2147483647x2147483647] +[-2147483647x-2147483647 = add 1x1 -2147483648x-2147483648] +[-2147483648x-2147483648 = add 1x1 2147483647x2147483647] +[-1x-1 = add 2147483647x2147483647 -2147483648x-2147483648] +[2147483646x2147483646 = add 2147483647x2147483647 -1x-1] +[-2147483648x-2147483648 = add 2147483647x2147483647 1x1] +[-2x-2 = add 2147483647x2147483647 2147483647x2147483647] +; pair + ... +[#"^(00)" = add #"^(01)" #"^(ff)"] +[#"^(00)" = add #"^(ff)" #"^(01)"] +[#"^(fe)" = add #"^(ff)" #"^(ff)"] +; tuple +; string +["^(03)^(00)" and* "^(02)^(00)" = "^(02)^(00)"] +; functions/math/arccosine.r +; char +[#"^(ff)" = complement #"^@"] +[#"^@" = complement #"^(ff)"] +[#"^(fe)" = complement #"^(01)"] +[#"^(01)" = complement #"^(fe)"] +; tuple +; string +["^(ff)" = complement "^@"] +["^@" = complement "^(ff)"] +["^(fe)" = complement "^(01)"] +["^(01)" = complement "^(fe)"] +; bitset +[ + (make bitset! #{FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF}) + = complement make bitset! #{0000000000000000000000000000000000000000000000000000000000000000} +] +[ + (make bitset! #{0000000000000000000000000000000000000000000000000000000000000000}) + = complement make bitset! #{FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF} +] +[-2147483648x-2147483648 = negate -2147483648x-2147483648] +; money +[ + (make bitset! #{FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF}) + = negate make bitset! #{0000000000000000000000000000000000000000000000000000000000000000} +] +[ + (make bitset! #{0000000000000000000000000000000000000000000000000000000000000000}) + = negate make bitset! #{FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF} +] +; char +[not negative? #"^@"] +[not negative? #"^a"] +[not negative? #"^(ff)"] +; money +[false = not make hash! []] +[false = not make list! []] +; char +[not positive? #"^@"] +[positive? #"^a"] +[positive? #"^(ff)"] +; money +[error? try [round/even 2147483648.0]] +[error? try [round/even 9.2233720368547799e18]] +[$0.0 == ($0.000'000'000'000'001 - round/even/to $0.000'000'000'000'001'1 1e-15)] +[not negative? 1e-31 - abs (to money! 26e-17) - round/even/to $0.000'000'000'000'000'255 to money! 1e-17] +[$2.6 == round/even/to $2.55 1E-1] +[not negative? (to money! 1e-31) - abs (to money! -26e-17) - round/even/to -$0.000'000'000'000'000'255 to money! 1e-17] +[2147483647x2147483647 = subtract -2147483648x-2147483648 1x1] +[1x1 = subtract -2147483648x-2147483648 2147483647x2147483647] +[2147483647x2147483647 = subtract -1x-1 -2147483648x-2147483648] +[-2147483648x-2147483648 = subtract -1x-1 2147483647x2147483647] +[-2147483648x-2147483648 = subtract 0x0 -2147483648x-2147483648] +[-2147483647x-2147483647 = subtract 1x1 -2147483648x-2147483648] +[-2147483646x-2147483646 = subtract 1x1 2147483647x2147483647] +[-1x-1 = subtract 2147483647x2147483647 -2147483648x-2147483648] +[-2147483648x-2147483648 = subtract 2147483647x2147483647 -1x-1] +[2147483646x2147483646 = subtract 2147483647x2147483647 1x1] +[#"^(00)" = subtract #"^(00)" #"^(00)"] +[#"^(ff)" = subtract #"^(00)" #"^(01)"] +[#"^(01)" = subtract #"^(00)" #"^(ff)"] +[#"^(01)" = subtract #"^(01)" #"^(00)"] +[#"^(00)" = subtract #"^(01)" #"^(01)"] +[#"^(02)" = subtract #"^(01)" #"^(ff)"] +[#"^(ff)" = subtract #"^(ff)" #"^(00)"] +[#"^(fe)" = subtract #"^(ff)" #"^(01)"] +[#"^(00)" = subtract #"^(ff)" #"^(ff)"] +; tuple +[error? try [find none 1]] +[ + a: make issue! 0 + insert a #"0" + a == #0 +] +[ + a: #0 + b: make issue! 0 + insert b first a + a == b +] +[ + a: #0 + b: make issue! 0 + insert b a + a == b +] +[ + a: make binary! 0 + insert a #"^(00)" + a == #{00} +] +[error? try [first []]] +[error? try [second []]] +[error? try [third []]] +[error? try [fourth []]] +[error? try [fifth []]] +[error? try [sixth []]] +[error? try [seventh []]] +[error? try [eighth []]] +[error? try [ninth []]] +[error? try [tenth []]] +[ + i: 0 + parse "a" [any [(i: i + 1 j: if i = 2 [[end skip]]) j]] + i == 2 +] +[1 = pick at [1 2 3 4 5] 3 -2] +[2 = pick at [1 2 3 4 5] 3 -1] +[none? pick at [1 2 3 4 5] 3 0] +[#"1" = pick at "12345" 3 -2] +[#"2" = pick at "12345" 3 -1] +[none? pick at "12345" 3 0] diff --git a/tests/parse-tests.r b/tests/parse-tests.r new file mode 100644 index 0000000000..bf6f1d0103 --- /dev/null +++ b/tests/parse-tests.r @@ -0,0 +1,157 @@ +; Is PARSE working at all? + +[parse? "abc" ["abc"]] + +; Blank and empty block case handling + +[parse? [] []] +[parse? [] [[[]]]] +[parse? [] [_ _ _]] +[not parse? [x] []] +[not parse? [x] [_ _ _]] +[not parse? [x] [[[]]]] +[parse? [] [[[_ _ _]]]] +[parse? [x] ['x _]] +[parse? [x] [_ 'x]] +[parse? [x] [[] 'x []]] + +; SET-WORD! (store current input position) + +[ + res: parse ser: [x y] [pos: skip skip] + all [res | pos = ser] +][ + res: parse ser: [x y] [skip pos: skip] + all [res | pos = next ser] +][ + res: parse ser: [x y] [skip skip pos: end] + all [res | pos = tail ser] +][ + #2130 + res: parse ser: [x] [set val pos: word!] + all [res | val = 'x | pos = ser] +][ + #2130 + res: parse ser: [x] [set val: pos: word!] + all [res | val = 'x | pos = ser] +][ + #2130 + res: parse + ser: "foo" [copy val pos: skip] + all [not res | val = "f" | pos = ser] +][ + #2130 + res: parse ser: "foo" [copy val: pos: skip] + all [not res | val = "f" | pos = ser] +] + +; TO/THRU integer! + +[parse? "abcd" [to 3 "cd"]] +[parse? "abcd" [to 5]] +[parse? "abcd" [to 128]] + +[#1965 | parse? "abcd" [thru 3 "d"]] +[#1965 | parse? "abcd" [thru 4]] +[#1965 | parse? "abcd" [thru 128]] +[#1965 | parse? "abcd" ["ab" to 1 "abcd"]] +[#1965 | parse? "abcd" ["ab" thru 1 "bcd"]] + +; parse THRU tag! + +[ + #682 + t: _ + parse "text" [thru copy t to ] + t == "text" +] + +; THRU advances the input position correctly. + +[ + i: 0 + parse "a." [any [thru "a" (i: i + 1 j: to-value if i > 1 [[end skip]]) j]] + i == 1 +] + +[#1959 | parse? "abcd" [thru "d"]] +[#1959 | parse? "abcd" [to "d" skip]] + +[#1959 | parse? "" [thru ]] +[#1959 | parse? [a b c d] [thru 'd]] +[#1959 | parse? [a b c d] [to 'd skip]] + +; self-invoking rule + +[ + #1672 + a: [a] + error? try [parse [] a] +] + +; repetition + +[ + #1280 + parse "" [(i: 0) 3 [["a" |] (i: i + 1)]] + i == 3 +][ + #1268 + i: 0 + parse "a" [any [(i: i + 1)]] + i == 1 +][ + #1268 + i: 0 + parse "a" [while [(i: i + 1 j: to-value if i = 2 [[fail]]) j]] + i == 2 +] + +; THEN rule + +[ + #1267 + b: "abc" + c: ["a" | "b"] + a2: [any [b e: (d: [:e]) then fail | [c | (d: [fail]) fail]] d] + a4: [any [b then e: (d: [:e]) fail | [c | (d: [fail]) fail]] d] + equal? parse "aaaaabc" a2 parse "aaaaabc" a4 +] + +; NOT rule + +[#1246 | parse? "1" [not not "1" "1"]] +[#1246 | parse? "1" [not [not "1"] "1"]] +[#1246 | not parse? "" [not 0 "a"]] +[#1246 | not parse? "" [not [0 "a"]]] +[#1240 | parse? "" [not "a"]] +[#1240 | parse? "" [not skip]] +[#1240 | parse? "" [not fail]] + +[#100 | 1 == eval does [parse [] [(return 1)] 2]] + +; TO/THRU + bitset!/charset! + +[#1457 | parse? "a" compose [thru (charset "a")]] +[#1457 | not parse? "a" compose [thru (charset "a") skip]] +[#1457 | parse? "ba" compose [to (charset "a") skip]] +[#1457 | not parse? "ba" compose [to (charset "a") "ba"]] + +; self-modifying rule, not legal in Ren-C if it's during the parse + +[error? try [not parse? "abcd" rule: ["ab" (remove back tail rule) "cd"]]] + +[ + https://github.com/metaeducation/ren-c/issues/377 + o: make object! [a: 1] + true = parse "a" [o/a: skip] +] + +; A couple of tests for the problematic DO operation + +[parse [1 + 2] [do [quote 3]]] +[parse [1 + 2] [do integer!]] +[parse [1 + 2] [do [integer!]]] +[not parse [1 + 2] [do [quote 100]]] +[parse [reverse copy [a b c]] [do [into ['c 'b 'a]]]] +[not parse [reverse copy [a b c]] [do [into ['a 'b 'c]]]] diff --git a/tests/pending-tests.r b/tests/pending-tests.r new file mode 100644 index 0000000000..421652dc58 --- /dev/null +++ b/tests/pending-tests.r @@ -0,0 +1,667 @@ +; These test cases were formerly contained in the test list even though they +; were failing. They are being broken out so that they can be examined more +; clearly, and to make the day to day testing run with 0 errors so that a +; log diffing is not required. + + +; Having guarantees about mold--down to the tab--is something that's a bit +; outside the realm of reasonable formalism in Ren-C just yet. If it's to +; be done, it should be done in a systemic way. +; Mold recursive object +[ + o: object [a: 1 r: _] + o/r: o + (ajoin ["<" mold o ">"]) + = "" +] + +; This is a lot of different ways of saying "REDUCE errors when an expression +; evaluates to void". While this is inconvenient for using blocks to erase +; the state of variables, that is what NONE! (blank) is for...to serve as +; a reified value placeholder when you don't have a value. + +[a: 1 set [a] reduce [2 ()] a = 2] +[x: construct [a: 1] set x reduce [2 ()] x/a = 2] +[a: 1 set/opt [a] reduce [()] void? get/opt 'a] +[a: 1 b: 2 set/opt [a b] reduce [3 ()] all [a = 3 void? get/opt 'b]] +[x: construct [a: 1] set/opt x reduce [()] void? get/opt in x 'a] +[x: construct [a: 1 b: 2] set/opt x reduce [3 ()] all [a = 3 void? get/opt in x 'b]] +[ + blk: reduce [()] + blk = compose blk +] + +; UNSET! is not a datatype in Ren-C. This cites "bug#799" so investigate to +; see if that is still relevant. +; +[typeset? complement make typeset! [unset!]] + +; There is an issue with doing a MAKE FRAME! for a definitional return and +; then DOing that frame. The problem is that while the behavior of each +; RETURN looks like a unique function, it isn't. So if you try to execute +; the frame to call the "Unique" function, there is no way to target that +; instance. So the `exit_from` frame inside the definitional return has +; to get tunneled in somehow as the "function" of the definitional return +; to know where to make the call. +; +; Technically possible. Just not on the priority list ATM. +; +[1 == eval does [r3-alpha-apply :return [1] 2]] + +; For bridging purposes, MAKE is currently a "sniffing" variadic. These are +; evil, but helpful because it wants to examine its arguments before deciding +; whether to evaluate or quote them at the callsite. So long as it is evil +; it will not be easily amenable to APPLY. +; +[error? r3-alpha-apply :make [error! ""]] + +; !!! #1893 suggests that there isn't value to be gained by prohibiting the +; binding of words to frames that are off the stack. Many factors are now +; different in Ren-C from when that was written...where each function +; instance has the potential to have a unique FRAME! reified to refer to it +; and its parameters, and where words lose their relative binding in favor +; of specific binding--and won't be able to go back +; +; Mechanically it will not be possible for the binding of a dead word that +; had a "specific binding" to be reused. The specific binding will be to +; a FRAME!, and in order to allow the GC of FRAME!s the words will have to +; collapse to an ANY-FUNCTION! for documentary purposes of that binding. +; However that "relativeness" will never be exposed. +; +; It's early yet to have the last word on this, but this will give an error +; for now...so putting it in the pending tests to process later. +[ + word: eval func [x] ['x] 1 + same? word bind 'x word +] + +; !!! This former bug is now an "issue", regarding what the nature and +; intent of non-definitional return should be. A fair argument could be +; that EXIT should never be able to escape a DO or CATCH or other DO-like +; construct, and only be used in the core implementation of transparent +; (e.g. MAKE FUNCTION!) code. In any case, there will not be a definitional +; EXIT (in the default generators) so one should use RETURN (), or if the +; generator offers a definitional return +; bug#539 +; EXIT out of USE +[ + f: func [] [ + use [] [exit] + 42 + ] + unset? f +] + +; "exit should not be caught by try" +;; This is another issue where EXIT, if it is meant to be non-definitional, +;; would mean EXIT whatever function is running". Saying that EXIT shouldn't +;; exit try assumes TRY isn't a function. If you use RETURN () then you'll +;; be just fine. +;; +[a: 1 eval does [a: error? try [exit]] :a =? 1] + +; You basically can't do this when FUNC is a generator and adds RETURN. +; Until such time as there's a way to make locals truly out of band, (such +; as using set words and saying [/local a return:]) +[ + a-value: func [/local a] [a] + 1 == a-value/local 1 +] + +;; Here are some weird tests indeed, that should be fixable with the +;; set-words solution to give the *right* answer. That means getting +;; rid of /local on all the internal generators. +; bug#2076 +[ + o: context-of use [x] ['x] + 3 == length? words-of append o 'self ; !!! weird test, includes /local +] +; bug#2076 +[ + o: context-of use [x] ['x] + 3 == length? words-of append o [self: 1] ; weird test, includes /local +] + +[equal? mold/all #[email! ""] {#[email! ""]}] +[equal? mold/all #[email! "a"] {#[email! "a"]}] + +; bug#2190 +[error? try [catch/quit [attempt [quit]] print x]] + +[get-path? load "#[get-path! []]"] +[equal? mold/all load "#[get-path! []]" "#[get-path! []]"] +[equal? mold/all load "#[get-path! [a]]" "#[get-path! [a]]"] + +; bug#1477 +[get-word? first [:/]] +[get-word? first [://]] +[get-word? first [:///]] + +[equal? load mold/all #[image! 0x0 #{}] #[image! 0x0 #{}]] + +; datatypes/library.r +[ + success: library? a-library: load/library case [ + ; this needs to be system-specific + system/version/4 = 2 [%libc.dylib] ; OSX + system/version/4 = 3 [%kernel32.dll] ; Windows + all [ + system/version/4 = 4 + system/version/5 = 2 + ] [ + %/lib/libc.so.6 ; Linux libc6 + ] + system/version/4 = 4 [%libc.so] ; Linux + system/version/4 = 7 [%libc.so] ; FreeBSD + system/version/4 = 8 [%libc.so] ; NetBSD + system/version/4 = 9 [%libc.so] ; OpenBSD + system/version/4 = 10 [%libc.so] ; Solaris + ] + free a-library + success +] + +; bug #1947 +[lit-path? load "#[lit-path! []]"] +[equal? mold/all load "#[lit-path! []]" "#[lit-path! []]"] +[equal? mold/all load "#[lit-path! [a]]" "#[lit-path! [a]]"] + +; this worked in Rebol2 but Rebol3 never had the denomination feature +[money? USD$1] +[money? CZK$1] + +; The "throw" category of error had previously been used by Rebol when throws +; were errors. Now any value can be thrown, and it is disjoint from the +; error machinery. So that released the "throw" WORD! from the reserved +; categories from the system. The broader question to review is how the +; reservation of system errors should work. +[try/except [make error! [type: 'throw id: 'break]] [true]] +[try/except [make error! [type: 'throw id: 'return]] [true]] +[try/except [make error! [type: 'throw id: 'throw]] [true]] +[try/except [make error! [type: 'throw id: 'continue]] [true]] +[try/except [make error! [type: 'throw id: 'halt]] [true]] +[try/except [make error! [type: 'throw id: 'quit]] [true]] + +; division uses "full precision" +["$1.0000000000000000000000000" = mold $1 / $1] +["$1.0000000000000000000000000" = mold $1 / $1.0] +["$1.0000000000000000000000000" = mold $1 / $1.000] +["$1.0000000000000000000000000" = mold $1 / $1.000000] +["$1.0000000000000000000000000" = mold $1 / $1.000000000] +["$1.0000000000000000000000000" = mold $1 / $1.000000000000] +["$1.0000000000000000000000000" = mold $1 / $1.0000000000000000000000000] +["$0.10000000000000000000000000" = mold $1 / $10] +["$0.33333333333333333333333333" = mold $1 / $3] +["$0.66666666666666666666666667" = mold $2 / $3] + +; bug#1477 +[word? '/] +[word? '//] +[word? '///] + +; object cloning +; bug#2050 +[ + o: make object! [n: 'o b: reduce [func [] [n]]] + p: make o [n: 'p] + (o/b)/1 = 'o +] + +; bug#1947 +[path? load "#[path! []]"] + +[equal? mold/all load "#[path! []]" "#[path! []]"] +[equal? mold/all load "#[path! [a]]" "#[path! [a]]"] + +[ + a-value: USD$1 + "USD" = a-value/1 +] + +; path evaluation order +[ + a: 1x2 + any? [ + error? try [b: a/(a: [3 4] 1)] + b = 1 + b = 3 + ] +] + +; This test went through a few iterations of trying to apply apply, then +; apply eval, and now that eval is variadic then trying to get the old +; r3-alpha-apply to work with it is not worth it. Moved here to consider +; if there's some parallel test for the new apply... +; +[8 == eval does [return r3-alpha-apply :eval [:add false 4 4]]] + +; bug#1475 +[same? 1.7976931348623157e310% load mold/all 1.7976931348623157e310%] + +; 64-bit IEEE 754 minimum +[same? -1.7976931348623157E310% load mold/all -1.7976931348623157e310%] + +; datatypes/routine.r +[ + success: routine? case [ + ; this needs to be system-specific + system/version/4 = 2 [ ; OSX + a-library: load/library %libc.dylib + make routine! [ + tv [struct! []] + tz [struct! []] + return: [integer!] + ] a-library "settimeofday" + ] + system/version/4 = 3 [ ; Windows + a-library: load/library %kernel32.dll + make routine! [ + systemtime [struct! []] + return: [int] + ] a-library "SetSystemTime" + ] + all [system/version/4 = 4 system/version/5 = 2] [ ; Linux libc6 + a-library: %/lib/libc.so.6 + make routine! [ + tv [struct! []] + tz [struct! []] + return: [integer!] + ] a-library "settimeofday" + ] + system/version/4 = 4 [ ; Linux + a-library: load/library %libc.so + make routine! [ + tv [struct! []] + tz [struct! []] + return: [integer!] + ] a-library "settimeofday" + ] + system/version/4 = 7 [ ; FreeBSD + a-library: load/library %libc.so + make routine! [ + tv [struct! []] + tz [struct! []] + return: [integer!] + ] a-library "settimeofday" + ] + system/version/4 = 8 [ ; NetBSD + a-library: load/library %libc.so + make routine! [ + tv [struct! []] + tz [struct! []] + return: [integer!] + ] a-library "settimeofday" + ] + system/version/4 = 9 [ ; OpenBSD + a-library: load/library %libc.so + make routine! [ + tv [struct! []] + tz [struct! []] + return: [integer!] + ] a-library "settimeofday" + ] + system/version/4 = 10 [ ; Solaris + a-library: load/library %libc.so + make routine! [ + tv [struct! []] + tz [struct! []] + return: [integer!] + ] a-library "settimeofday" + ] + ] + free a-library + success +] + +; bug#1947 +[set-path? load "#[set-path! []]"] +[equal? mold/all load "#[set-path! []]" "#[set-path! []]"] +[equal? mold/all load "#[set-path! [a]]" "#[set-path! [a]]"] + +; bug#1477 +[set-word? first [/:]] +[set-word? first [//:]] +[set-word? first [///:]] + +; datatypes/struct.r +[struct? make struct! [i [integer!]] blank] +[not struct? 1] +[struct! = type? make struct! [] blank] +; minimum +[struct? make struct! [] blank] +; literal form +[struct? #[struct! [] []]] +[ + s: make string! 15 + addr: func [s] [copy third make struct! [s [string!]] reduce [s]] + (addr s) = (addr insert/dup s #"0" 15) +] +[false = not make struct! [] blank] +[ + a-value: make struct! [] blank + f: does [:a-value] + same? third :a-value third f +] +[ + a-value: make struct! [i [integer!]] [1] + 1 == a-value/i +] +[ + a-value: make struct! [] blank + same? third :a-value third all [:a-value] +] +[ + a-value: make struct! [] blank + same? third :a-value third all [true :a-value] +] +[ + a-value: make struct! [] blank + true = all [:a-value true] +] +[ + a-value: make struct! [] blank + same? third :a-value third do reduce [:a-value] +] +[ + a-value: make struct! [] blank + same? third :a-value third do :a-value +] +[if make struct! [] blank [true]] +[ + a-value: make struct! [] blank + same? third :a-value third any [:a-value] +] +[ + a-value: make struct! [] blank + same? third :a-value third any [false :a-value] +] +[ + a-value: make struct! [] blank + same? third :a-value third any [:a-value false] +] + + +[equal? mold/all #[tag! ""] {#[tag! ""]}] + +[equal? mold/all #[url! ""] {#[url! ""]}] +[equal? mold/all #[url! "a"] {#[url! "a"]}] + +; bug#2011 +[not equal? load "http://a.b.c/d?e=f%26" load "http://a.b.c/d?e=f&"] + +; object! complex structural equivalence +; Slight differences. +; bug#1133 +[ + a-value: construct/only [c: $1] + b-value: construct/only [c: 100%] + equal? a-value b-value +] +[ + a-value: construct/only [ + a: 1 b: 1.0 c: $1 d: 1% + e: [a 'a :a a: /a #"a" #{00}] + f: ["a" #a http://a a@a.com ] + g: :a/b/(c: 'd/e/f)/(b/d: [:f/g h/i]) + ] + b-value: construct/only [ + a: 1.0 b: $1 c: 100% d: 0.01 + e: [/a a 'a :a a: #"A" #[binary! #{0000} 2]] + f: [#a http://A a@A.com "A"] + g: :a/b/(c: 'd/e/f)/(b/d: [:f/g h/i]) + ] + equal? a-value b-value +] + +; error! difference in infix code +; bug#60: operators generate errors with offset NEAR field +[not equal? (try [1 / 0]) (try [2 / 0])] + +; "decimal tolerance" +; bug#1134 +[not equiv? to decimal! #{3FD3333333333333} to decimal! #{3FD3333333333334}] + +[not equiv? to decimal! #{3FB9999999999999} to decimal! #{3FB999999999999A}] + +; bug#2086 +[ + bind next block: [a a] use [a] ['a] + same? 'a first block +] + +[ + o: make object! [a: _] + same? context-of in o 'self context-of in o 'a +] + +; bug#1745 +[equal? error? try [set /a 1] error? try [set [/a] 1]] +; bug#1745 +[equal? error? try [set #a 1] error? try [set [#a] 1]] + +; bug#1949: RETURN/redo can break APPLY security +[same? :add attempt [apply does [return/redo :add] []]] + +[false == apply/only func [/a] [a] [#[false]]] + +['group! == apply/only :type? [() true]] + +;-- CC#2246 +[blank? case [true []]] + +; #1906 +[ + b: copy [] insert/dup b 1 32768 compose b +] + +; bug#539 +[ + f1: does [do "return 1 2" 2] + 1 = f1 +] + +; bug#1136 +#64bit +[ + num: 0 + for i 9223372036854775807 9223372036854775807 1 [ + num: num + 1 + either num > 1 [break] [true] + ] +] +#64bit +[ + num: 0 + for i -9223372036854775808 -9223372036854775808 -1 [ + num: num + 1 + either num > 1 [break] [true] + ] +] +; bug#1994 +#64bit +[ + num: 0 + for i 9223372036854775807 9223372036854775807 9223372036854775807 [ + num: num + 1 + if num <> 1 [break] + true + ] +] + +#64bit +[ + num: 0 + for i -9223372036854775808 -9223372036854775808 -9223372036854775808 [ + num: num + 1 + if num <> 1 [break] + true + ] +] + +; bug#1993 +[equal? type? for i 1 2 0 [break] type? for i 2 1 0 [break]] +[equal? type? for i -1 -2 0 [break] type? for i -2 -1 0 [break]] + +; pair! test (bug#1995) +[[1x1 2x1 1x2 2x2] == collect [repeat i 2x2 [keep i]]] + +[ ; bug#1519 + success: true + cycle?: true + while [if cycle? [cycle?: false continue success: false] cycle?] [] + success +] + +; closure mold +; bug#23 +[ + c: closure [a] [print a] + equal? "make closure! [[a] [print a]]" mold :c +] + +; bug#12 +[image? to image! make gob! []] + +; bug#1613 +[exists? http://www.rebol.com/index.html] + +[0 = sign? USD$0] + +; bug#1894 +[ + port: open/new %pokus.txt + append port newline +] + +#64bit +[[1] = copy/part tail [1] -9223372036854775808] + +#64bit +[[] = copy/part [] 9223372036854775807] + +; functions/series/deline.r +[ + equal? + "^/" + deline case [ + system/version/4 = 3 "^M^/" ; CR LF on Windows + true "^/" ; LF elsewhere + ] +] + +; functions/series/enline.r +; bug#2191 +[ + equal? + enline "^/" + case [ + system/version/4 = 3 "^M^/" ; CR LF on Windows + true "^/" ; LF elsewhere + ] +] + +; bug#647 +[string? enline ["a" "b"]] + +; bug#854 +[ + a: <0> + b: make tag! 0 + insert b first a + a == b +] + +[ + ; The results of decoding lossless encodings should be identical. + bmp-img: decode 'bmp read %fixtures/rebol-logo.bmp + gif-img: decode 'gif read %fixtures/rebol-logo.gif + png-img: decode 'gif read %fixtures/rebol-logo.png + all [ + bmp-img == gif-img + bmp-img == png-img + ] +] + +[[ "hello" ] == decode 'markup "hello"] + +; GIF encoding is not yet implemented +[out: encode 'gif decode 'gif src: read %fixtures/rebol-logo.gif out == src] +[out: encode 'png decode 'png src: read %fixtures/rebol-logo.png out == src] +; JPEG encoding is not yet implemented +[out: encode 'jpeg decode 'jpeg src: read %fixtures/rebol-logo.jpeg out == src] + +; bug#1986 +["aβc" = dehex "a%ce%b2c"] + +; bug#1986 +[(to-string #{61CEB262}) = dehex "a%ce%b2c"] + +; bug#1986 +[#{61CEB262} = to-binary dehex "a%ce%b2c"] + +; system/clipboard.r +; empty clipboard +[ + write clipboard:// "" + c: read clipboard:// + all [string? c empty? c] +] +; ASCII string +[ + write clipboard:// c: "This is a test." + d: read clipboard:// + strict-equal? c d +] +; Unicode string +[ + write clipboard:// c: "Příliš žluťoučký kůň úpěl ďábelské ódy." + strict-equal? read clipboard:// c +] +; OPEN +; bug#1968 +[ + p: open clipboard:// + append p c: "Clipboard port test" + strict-equal? c copy p +] +[ + p: open clipboard:// + write p c: "Clipboard port test" + strict-equal? c read p +] +; WRITE shall return a port in R3 +[equal? read write clipboard:// c: "test" c] + +; bug#2186 +["äöü" == read/string %fixtures/umlauts-utf32le.txt] +[["äöü"] == read/lines %fixtures/umlauts-utf32le.txt] + +; bug#2186 +["äöü" == read/string %fixtures/umlauts-utf32be.txt] +[["äöü"] == read/lines %fixtures/umlauts-utf32be.txt] + +; bug#1675 (The extra MOLD is to more consistently provoke the crash.) +; Note: these don't work due to the lack of trailing slash, +; what is the long-term policy on READ for a directory? +[files: read %. mold files block? files] +; bug#1675 +[files: read %fixtures mold files block? files] + +; empty string rule +; bug#1880 +; NOTE: It would seem that considering this a match violates the "parse +; must make progress" rule. +[parse "12" ["" to end]] + +; bug#2214 +[not parse "abcd" rule: ["ab" (clear rule) "cd"]] + +; !!! The general issue of tying up Rebol with more notions of equality +; without getting the existing ones right is suspect. Ren-C simplified matters +; and left EQUIV? as a synonym for EQUAL? at the present time, with the option +; that it may be a form of equality that returns in the future. +; +; With EQUIV? as a synonym for EQUAL, the following test (whatever it was +; supposed to test) started to fail. +; +[not equiv? 'a use [a] ['a]] diff --git a/tests/reflectors/body-of.test.reb b/tests/reflectors/body-of.test.reb new file mode 100644 index 0000000000..9c2c624929 --- /dev/null +++ b/tests/reflectors/body-of.test.reb @@ -0,0 +1,6 @@ +; functions/reflectors/body-of.r +; bug#49 +[ + f: func [] [] + not same? body-of :f body-of :f +] diff --git a/tests/run-recover.r b/tests/run-recover.r new file mode 100644 index 0000000000..da781389a2 --- /dev/null +++ b/tests/run-recover.r @@ -0,0 +1,58 @@ +Rebol [ + Title: "Core tests run with crash recovery" + File: %run-recover.r + Copyright: [2012 "Saphirion AG"] + License: { + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + } + Author: "Ladislav Mecir" + Purpose: "Core tests" +] + +do %test-framework.r + +; Example runner for the REBOL/Core tests which chooses +; appropriate flags depending on the interpreter version. + +do-core-tests: procedure [] [ + ; Check if we run R3 or R2. + flags: pick [ + [#64bit #r3only #r3] + [#32bit #r2only] + ] not blank? in system 'catalog + + ; calculate interpreter checksum + case [ + all [file? system/options/boot #"/" = first system/options/boot] [ + interpreter-checksum: checksum/method read-binary + system/options/boot 'sha1 + ] + string? system/script/args [ + interpreter-checksum: checksum/method read-binary + to-rebol-file system/script/args 'sha1 + ] + ] else [ + ; use system/build + interpreter-checksum: checksum/method to binary! + mold system/build 'sha1 + ] + + log-file-prefix: copy %r + repeat i length? version: system/version [ + append log-file-prefix "_" + append log-file-prefix mold version/:i + ] + + print "Testing ..." + result: do-recover %core-tests.r flags interpreter-checksum log-file-prefix + set [log-file summary] result + + print ["Done, see the log file:" log-file] + print summary +] + +do-core-tests diff --git a/tests/run-tests.r b/tests/run-tests.r new file mode 100644 index 0000000000..2cbb6d0a28 --- /dev/null +++ b/tests/run-tests.r @@ -0,0 +1,46 @@ +Rebol [ + Title: "Run-tests" + File: %run-tests.r + Copyright: [2014 "Saphirion AG"] + Author: "Ladislav Mecir" + License: { + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + } + Author: "Ladislav Mecir" + Purpose: {Click and run tests in a file or directory.} +] + +; define the INCLUDE function +do %include.r + +do %test-framework.r + +run-tests: func [ + tests + /local + result log-file summary log-file-prefix suffix +] [ + if dir? tests [ + tests: dirize tests + change-dir tests + for-each file read tests [ + ; check if it is a test file + if %.tst = find/last file %. [run-tests file] + ] + return () + ] + + ; having an individual file + suffix: find/last tests %. + log-file-prefix: copy/part tests suffix + + print "Testing ..." + change-dir first split-path tests + set [log-file summary] do-recover tests [] blank log-file-prefix +] + +run-tests to-rebol-file system/script/args diff --git a/tests/secure/protect.test.reb b/tests/secure/protect.test.reb new file mode 100644 index 0000000000..9901e80464 --- /dev/null +++ b/tests/secure/protect.test.reb @@ -0,0 +1,151 @@ +; functions/secure/protect.r +; bug#1748 +; block +[ + value: copy original: [1 + 2 + 3] + protect value + all [ + error? try [insert value 4] + equal? value original + ] +] +[ + value: copy original: [1 + 2 + 3] + protect value + all [ + error? try [append value 4] + equal? value original + ] +] +[ + value: copy original: [1 + 2 + 3] + protect value + all [ + error? try [change value 4] + equal? value original + ] +] +[ + value: copy original: [1 + 2 + 3] + protect value + all [ + error? try [reduce/into [4 + 5] value] + equal? value original + ] +] +[ + value: copy original: [1 + 2 + 3] + protect value + all [ + error? try [compose/into [(4 + 5)] value] + equal? value original + ] +] +[ + value: copy original: [1 + 2 + 3] + protect value + all [ + error? try [poke value 1 4] + equal? value original + ] +] +[ + value: copy original: [1 + 2 + 3] + protect value + all [ + error? try [remove/part value 1] + equal? value original + ] +] +[ + value: copy original: [1 + 2 + 3] + protect value + all [ + error? try [take value] + equal? value original + ] +] +[ + value: copy original: [1 + 2 + 3] + protect value + all [ + error? try [reverse value] + equal? value original + ] +] +[ + value: copy original: [1 + 2 + 3] + protect value + all [ + error? try [clear value] + equal? value original + ] +] +; string +[ + value: copy original: {1 + 2 + 3} + protect value + all [ + error? try [insert value 4] + equal? value original + ] +] +[ + value: copy original: {1 + 2 + 3} + protect value + all [ + error? try [append value 4] + equal? value original + ] +] +[ + value: copy original: {1 + 2 + 3} + protect value + all [ + error? try [change value 4] + equal? value original + ] +] +[ + value: copy original: {1 + 2 + 3} + protect value + all [ + error? try [poke value 1 4] + equal? value original + ] +] +[ + value: copy original: {1 + 2 + 3} + protect value + all [ + error? try [remove/part value 1] + equal? value original + ] +] +[ + value: copy original: {1 + 2 + 3} + protect value + all [ + error? try [take value] + equal? value original + ] +] +[ + value: copy original: {1 + 2 + 3} + protect value + all [ + error? try [reverse value] + equal? value original + ] +] +[ + value: copy original: {1 + 2 + 3} + protect value + all [ + error? try [clear value] + equal? value original + ] +] +; bug#1764 +[unset 'blk protect/deep 'blk true] +[unprotect 'blk true] diff --git a/tests/secure/unprotect.test.reb b/tests/secure/unprotect.test.reb new file mode 100644 index 0000000000..352a895056 --- /dev/null +++ b/tests/secure/unprotect.test.reb @@ -0,0 +1,112 @@ +; functions/secure/unprotect.r +; bug#1748 +; block +[ + value: copy original: [1 + 2 + 3] + protect value + unprotect value + not error? try [insert value 4] +] +[ + value: copy original: [1 + 2 + 3] + protect value + unprotect value + not error? try [append value 4] +] +[ + value: copy original: [1 + 2 + 3] + protect value + unprotect value + not error? try [change value 4] +] +[ + value: copy original: [1 + 2 + 3] + protect value + unprotect value + not error? try [reduce/into [4 + 5] value] +] +[ + value: copy original: [1 + 2 + 3] + protect value + unprotect value + not error? try [compose/into [(4 + 5)] value] +] +[ + value: copy original: [1 + 2 + 3] + protect value + unprotect value + not error? try [poke value 1 4] +] +[ + value: copy original: [1 + 2 + 3] + protect value + unprotect value + not error? try [remove/part value 1] +] +[ + value: copy original: [1 + 2 + 3] + protect value + unprotect value + not error? try [take value] +] +[ + value: copy original: [1 + 2 + 3] + protect value + unprotect value + not error? try [reverse value] +] +[ + value: copy original: [1 + 2 + 3] + protect value + unprotect value + not error? try [clear value] +] +; string +[ + value: copy original: {1 + 2 + 3} + protect value + unprotect value + not error? try [insert value 4] +] +[ + value: copy original: {1 + 2 + 3} + protect value + unprotect value + not error? try [append value 4] +] +[ + value: copy original: {1 + 2 + 3} + protect value + unprotect value + not error? try [change value 4] +] +[ + value: copy original: {1 + 2 + 3} + protect value + unprotect value + not error? try [poke value 1 4] +] +[ + value: copy original: {1 + 2 + 3} + protect value + unprotect value + not error? try [remove/part value 1] +] +[ + value: copy original: {1 + 2 + 3} + protect value + unprotect value + not error? try [take value] +] +[ + value: copy original: {1 + 2 + 3} + protect value + unprotect value + not error? try [reverse value] +] +[ + value: copy original: {1 + 2 + 3} + protect value + unprotect value + not error? try [clear value] +] diff --git a/tests/series/append.test.reb b/tests/series/append.test.reb new file mode 100644 index 0000000000..75cb6011c0 --- /dev/null +++ b/tests/series/append.test.reb @@ -0,0 +1,9 @@ +; functions/series/append.r +; bug#75 +[ + o: make object! [a: 1] + p: make o [] + append p [b 2] + not in o 'b +] +[block? append copy [] ()] diff --git a/tests/series/at.test.reb b/tests/series/at.test.reb new file mode 100644 index 0000000000..ce07c58341 --- /dev/null +++ b/tests/series/at.test.reb @@ -0,0 +1,90 @@ +; functions/series/at.r +[ + blk: [] + same? blk at blk 1 +] +[ + blk: [] + same? blk at blk 2147483647 +] +[ + blk: [] + same? blk at blk 0 +] +[ + blk: [] + same? blk at blk -1 +] +[ + blk: [] + same? blk at blk -2147483648 +] +[ + blk: tail [1 2 3] + same? blk at blk 1 +] +[ + blk: tail [1 2 3] + same? blk at blk 0 +] +[ + blk: tail [1 2 3] + equal? [3] at blk -1 +] +[ + blk: tail [1 2] + same? blk at blk 2147483647 +] +[ + blk: [1 2] + same? blk at blk -2147483647 +] +[ + blk: [1 2] + same? blk at blk -2147483648 +] +; string +[ + str: "" + same? str at str 1 +] +[ + str: "" + same? str at str 2147483647 +] +[ + str: "" + same? str at str 0 +] +[ + str: "" + same? str at str -1 +] +[ + str: "" + same? str at str -2147483648 +] +[ + str: tail "123" + same? str at str 1 +] +[ + str: tail "123" + same? str at str 0 +] +[ + str: tail "123" + equal? "3" at str -1 +] +[ + str: tail "12" + same? str at str 2147483647 +] +[ + str: "12" + same? str at str -2147483647 +] +[ + str: "12" + same? str at str -2147483648 +] diff --git a/tests/series/back.test.reb b/tests/series/back.test.reb new file mode 100644 index 0000000000..d226f6b3a8 --- /dev/null +++ b/tests/series/back.test.reb @@ -0,0 +1,27 @@ +; functions/series/back.r +[ + a: [1] + same? a back a +] +[ + a: tail [1] + same? head a back a +] +; path +[ + a: 'b/c + same? a back a +] +[ + a: tail 'b/c + same? head a back back a +] +; string +[ + a: tail "1" + same? head a back a +] +[ + a: "1" + same? a back a +] diff --git a/tests/series/change.test.reb b/tests/series/change.test.reb new file mode 100644 index 0000000000..54231fcf46 --- /dev/null +++ b/tests/series/change.test.reb @@ -0,0 +1,10 @@ +; functions/series/change.r +[ + blk1: at copy [1 2 3 4 5] 3 + blk2: at copy [1 2 3 4 5] 3 + change/part blk1 6 -2147483647 + change/part blk2 6 -2147483648 + equal? head blk1 head blk2 +] +; bug#9 +[equal? "tr" change/part "str" "" 1] diff --git a/tests/series/clear.test.reb b/tests/series/clear.test.reb new file mode 100644 index 0000000000..1bf23aa9b8 --- /dev/null +++ b/tests/series/clear.test.reb @@ -0,0 +1,10 @@ +; functions/series/clear.r +[[] = clear []] +[[] = clear copy [1]] +[ + block: at copy [1 2 3 4] 3 + clear block + [1 2] == head clear block +] +; blank +[blank == clear blank] diff --git a/tests/series/copy.test.reb b/tests/series/copy.test.reb new file mode 100644 index 0000000000..ec050f9bcc --- /dev/null +++ b/tests/series/copy.test.reb @@ -0,0 +1,41 @@ +; functions/series/copy.r +[ + blk: [] + all [ + blk = copy blk + not same? blk copy blk + ] +] +[ + blk: [1] + all [ + blk = copy blk + not same? blk copy blk + ] +] +[[1] = copy/part tail [1] -1] +[[1] = copy/part tail [1] -2147483647] +; bug#853 +; bug#1118 +[[1] = copy/part tail [1] -2147483648] +[[] = copy/part [] 0] +[[] = copy/part [] 1] +[[] = copy/part [] 2147483647] +[ok? try [copy blank]] +; bug#877 +[ + a: copy [] + insert/only a a + error? try [copy/deep a] + true +] +; bug#2043 +[ + f: func [] [] + error? try [copy :f] + true +] +; bug#648 +[["a"] = deline/lines "a"] +; bug#1794 +[1 = length? deline/lines "Slovenščina"] diff --git a/tests/series/difference.test.reb b/tests/series/difference.test.reb new file mode 100644 index 0000000000..591bdc4ab6 --- /dev/null +++ b/tests/series/difference.test.reb @@ -0,0 +1,6 @@ +; functions/series/difference.r +[[1 3] = difference [1 2] [2 3]] +[[[1 2] [3 4]] = difference [[1 2] [2 3]] [[2 3] [3 4]]] +[[path/1 path/3] = difference [path/1 path/2] [path/2 path/3]] +; bug#799 +[equal? make typeset! [decimal!] difference make typeset! [decimal! integer!] make typeset! [integer!]] diff --git a/tests/series/emptyq.test.reb b/tests/series/emptyq.test.reb new file mode 100644 index 0000000000..3c76a4e0bb --- /dev/null +++ b/tests/series/emptyq.test.reb @@ -0,0 +1,10 @@ +; functions/series/emptyq.r +[empty? []] +[ + blk: tail [1] + clear head blk + empty? blk +] +[empty? blank] +; bug#190 +[x: copy "xx^/" loop 20 [enline x: join-of x x] true] diff --git a/tests/series/exclude.test.reb b/tests/series/exclude.test.reb new file mode 100644 index 0000000000..f1f6102205 --- /dev/null +++ b/tests/series/exclude.test.reb @@ -0,0 +1,6 @@ +; functions/series/exclude.r +[[1] = exclude [1 2] [2 3]] +[[[1 2]] = exclude [[1 2] [2 3]] [[2 3] [3 4]]] +[[path/1] = exclude [path/1 path/2] [path/2 path/3]] +; bug#799 +[equal? make typeset! [decimal!] exclude make typeset! [decimal! integer!] make typeset! [integer!]] diff --git a/tests/series/find.test.reb b/tests/series/find.test.reb new file mode 100644 index 0000000000..35d28742f3 --- /dev/null +++ b/tests/series/find.test.reb @@ -0,0 +1,19 @@ +; functions/series/find.r +[blank? find blank 1] +[blank? find [] 1] +[ + blk: [1] + same? blk find blk 1 +] +[blank? find/part [x] 'x 0] +[equal? [x] find/part [x] 'x 1] +[equal? [x] find/reverse tail [x] 'x] +[equal? [y] find/match [x y] 'x] +[equal? [x] find/last [x] 'x] +[equal? [x] find/last [x x x] 'x] +; bug#66 +[blank? find/skip [1 2 3 4 5 6] 2 3] +; bug#88 +["c" = find "abc" charset ["c"]] +; bug#88 +[blank? find/part "ab" "b" 1] diff --git a/tests/series/indexq.test.reb b/tests/series/indexq.test.reb new file mode 100644 index 0000000000..00e38a56b2 --- /dev/null +++ b/tests/series/indexq.test.reb @@ -0,0 +1,11 @@ +; functions/series/indexq.r +[1 == index? []] +[2 == index? next [a]] +; past-tail index +[ + a: tail copy [1] + remove head a + 2 == index? a +] +; bug#1611: Allow INDEX? to take blank as an argument, return blank +[blank? index? blank] diff --git a/tests/series/insert.test.reb b/tests/series/insert.test.reb new file mode 100644 index 0000000000..bbd13d3aa6 --- /dev/null +++ b/tests/series/insert.test.reb @@ -0,0 +1,269 @@ +; functions/series/insert.r +[ + a: make block! 0 + insert a 0 + a == [0] +] +[ + a: [0] + b: make block! 0 + insert b first a + a == b +] +[ + a: [0] + b: make block! 0 + insert b a + a == b +] +; paren +[ + a: make group! 0 + insert a 0 + a == first [(0)] +] +[ + a: first [(0)] + b: make group! 0 + insert b first a + a == b +] +[ + a: first [(0)] + b: make group! 0 + insert b a + a == b +] +; path +[ + a: make path! 0 + insert a 0 + a == to path! [0] +] +[ + a: to path! [0] + b: make path! 0 + insert b first a + a == b +] +[ + a: to path! [0] + b: make path! 0 + insert :b a + a == b +] +; lit-path +[ + a: make lit-path! 0 + insert :a 0 + :a == to lit-path! [0] +] +[ + a: to lit-path! [0] + b: make lit-path! 0 + insert :b first :a + :a == :b +] +[ + a: to lit-path! [0] + b: make lit-path! 0 + insert :b :a + :a == :b +] +; set-path +[ + a: make set-path! 0 + insert :a 0 + :a == to set-path! [0] +] +[ + a: to set-path! [0] + b: make set-path! 0 + insert :b first :a + :a == :b +] +[ + a: to set-path! [0] + b: make set-path! 0 + insert :b :a + :a == :b +] +; string +[ + a: make string! 0 + insert a #"0" + a == "0" +] +[ + a: "0" + b: make string! 0 + insert b first a + a == b +] +[ + a: "0" + b: make string! 0 + insert b a + a == b +] +; file +[ + a: make file! 0 + insert a #"0" + a == %"0" +] +[ + a: %"0" + b: make file! 0 + insert b first a + a == b +] +[ + a: %"0" + b: make file! 0 + insert b a + a == b +] +; email +[ + a: make email! 0 + insert a #"0" + a == #[email! "0"] +] +[ + a: #[email! "0"] + b: make email! 0 + insert b first a + a == b +] +[ + a: #[email! "0"] + b: make email! 0 + insert b a + a == b +] +; url +[ + a: make url! 0 + insert a #"0" + a == #[url! "0"] +] +[ + a: #[url! "0"] + b: make url! 0 + insert b first a + a == b +] +[ + a: #[url! "0"] + b: make url! 0 + insert b a + a == b +] +; tag +[ + a: make tag! 0 + insert a #"0" + a == <0> +] +; bug#855 +[ + a: #{00} + b: make binary! 0 + insert b first a + a == b +] +[ + a: #{00} + b: make binary! 0 + insert b a + a == b +] +; insert/part +[ + a: make block! 0 + b: at [1 2 3 4 5 6 7 8 9] 5 + insert/part a b 1 + a == [5] +] +[ + a: make block! 0 + b: at [1 2 3 4 5 6 7 8 9] 5 + insert/part a b 5 + a == [5 6 7 8 9] +] +[ + a: make block! 0 + b: at [1 2 3 4 5 6 7 8 9] 5 + insert/part a b 6 + a == [5 6 7 8 9] +] +[ + a: make block! 0 + b: at [1 2 3 4 5 6 7 8 9] 5 + insert/part a b 2147483647 + a == [5 6 7 8 9] +] +[ + a: make block! 0 + b: at [1 2 3 4 5 6 7 8 9] 5 + insert/part a b 0 + empty? a +] +[ + a: make block! 0 + b: at [1 2 3 4 5 6 7 8 9] 5 + insert/part a b -1 + a == [4] +] +[ + a: make block! 0 + b: at [1 2 3 4 5 6 7 8 9] 5 + insert/part a b -4 + a == [1 2 3 4] +] +[ + a: make block! 0 + b: at [1 2 3 4 5 6 7 8 9] 5 + insert/part a b -5 + a == [1 2 3 4] +] +[ + a: make block! 0 + b: at [1 2 3 4 5 6 7 8 9] 5 + insert/part a b -2147483648 + a == [1 2 3 4] +] +; insert/only +[ + a: make block! 0 + b: [] + insert/only a b + same? b first a +] +; insert/dup +[ + a: make block! 0 + insert/dup a 0 2 + a == [0 0] +] +[ + a: make block! 0 + insert/dup a 0 0 + a == [] +] +[ + a: make block! 0 + insert/dup a 0 -1 + a == [] +] +[ + a: make block! 0 + insert/dup a 0 -2147483648 + a == [] +] +[ + a: make block! 0 + insert/dup a 0 -2147483648 + empty? a +] diff --git a/tests/series/intersect.test.reb b/tests/series/intersect.test.reb new file mode 100644 index 0000000000..40e8def018 --- /dev/null +++ b/tests/series/intersect.test.reb @@ -0,0 +1,6 @@ +; functions/series/intersect.r +[[2] = intersect [1 2] [2 3]] +[[[2 3]] = intersect [[1 2] [2 3]] [[2 3] [3 4]]] +[[path/2] = intersect [path/1 path/2] [path/2 path/3]] +; bug#799 +[equal? make typeset! [integer!] intersect make typeset! [decimal! integer!] make typeset! [integer!]] diff --git a/tests/series/last.test.reb b/tests/series/last.test.reb new file mode 100644 index 0000000000..63f7a88e34 --- /dev/null +++ b/tests/series/last.test.reb @@ -0,0 +1,4 @@ +; functions/series/last.r +; bug#2 +[error? try [last #"c"]] +[error? try [last 7]] diff --git a/tests/series/lengthq.test.reb b/tests/series/lengthq.test.reb new file mode 100644 index 0000000000..aa28d26a6e --- /dev/null +++ b/tests/series/lengthq.test.reb @@ -0,0 +1,4 @@ +; functions/series/lengthq.r +; bug#1626: "Allow LENGTH? to take blank as an argument, return blank" +; bug#1688: "LENGTH? NONE returns TRUE" (should return NONE) +[blank? length? blank] diff --git a/tests/series/next.test.reb b/tests/series/next.test.reb new file mode 100644 index 0000000000..2d2a6ec0d9 --- /dev/null +++ b/tests/series/next.test.reb @@ -0,0 +1,9 @@ +; functions/series/next.r +[ + blk: [1] + same? tail blk next blk +] +[ + blk: tail [1] + same? blk next blk +] diff --git a/tests/series/ordinals.test.reb b/tests/series/ordinals.test.reb new file mode 100644 index 0000000000..bffed9f80d --- /dev/null +++ b/tests/series/ordinals.test.reb @@ -0,0 +1,21 @@ +; functions/series/ordinals.r +[blank? first []] +[blank? second []] +[blank? third []] +[blank? fourth []] +[blank? fifth []] +[blank? sixth []] +[blank? seventh []] +[blank? eighth []] +[blank? ninth []] +[blank? tenth []] +[1 = first [1 2 3 4 5 6 7 8 9 10 11]] +[2 = second [1 2 3 4 5 6 7 8 9 10 11]] +[3 = third [1 2 3 4 5 6 7 8 9 10 11]] +[4 = fourth [1 2 3 4 5 6 7 8 9 10 11]] +[5 = fifth [1 2 3 4 5 6 7 8 9 10 11]] +[6 = sixth [1 2 3 4 5 6 7 8 9 10 11]] +[7 = seventh [1 2 3 4 5 6 7 8 9 10 11]] +[8 = eighth [1 2 3 4 5 6 7 8 9 10 11]] +[9 = ninth [1 2 3 4 5 6 7 8 9 10 11]] +[10 = tenth [1 2 3 4 5 6 7 8 9 10 11]] diff --git a/tests/series/pick.test.reb b/tests/series/pick.test.reb new file mode 100644 index 0000000000..5737c1a433 --- /dev/null +++ b/tests/series/pick.test.reb @@ -0,0 +1,33 @@ +; functions/series/pick.r +#64bit +[error? try [pick at [1 2 3 4 5] 3 -9223372036854775808]] +[void? pick* at [1 2 3 4 5] 3 -2147483648] +[void? pick* at [1 2 3 4 5] 3 -2147483647] +[void? pick* at [1 2 3 4 5] 3 -3] +[void? pick* at [1 2 3 4 5] 3 -2] +[1 = pick at [1 2 3 4 5] 3 -1] +[2 = pick at [1 2 3 4 5] 3 0] +[3 = pick at [1 2 3 4 5] 3 1] +[4 = pick at [1 2 3 4 5] 3 2] +[5 = pick at [1 2 3 4 5] 3 3] +[void? pick* at [1 2 3 4 5] 3 4] +[void? pick* at [1 2 3 4 5] 3 2147483647] +#64bit +[error? try [pick at [1 2 3 4 5] 3 9223372036854775807]] +; string +#64bit +[error? try [pick at "12345" 3 -9223372036854775808]] +[blank? pick at "12345" 3 -2147483648] +[blank? pick at "12345" 3 -2147483647] +[blank? pick at "12345" 3 -3] +[blank? pick at "12345" 3 -2] +[#"1" = pick at "12345" 3 -1] +; bug#857 +[#"2" = pick at "12345" 3 0] +[#"3" = pick at "12345" 3 1] +[#"4" = pick at "12345" 3 2] +[#"5" = pick at "12345" 3 3] +[blank? pick at "12345" 3 4] +[blank? pick at "12345" 3 2147483647] +#64bit +[error? try [pick at "12345" 3 9223372036854775807]] diff --git a/tests/series/poke.test.reb b/tests/series/poke.test.reb new file mode 100644 index 0000000000..a0c347e04c --- /dev/null +++ b/tests/series/poke.test.reb @@ -0,0 +1,5 @@ +; functions/series/poke.r +[ + poke a: #{00} 1 pick b: #{11} 1 + a == b +] diff --git a/tests/series/remove.test.reb b/tests/series/remove.test.reb new file mode 100644 index 0000000000..82947e2278 --- /dev/null +++ b/tests/series/remove.test.reb @@ -0,0 +1,16 @@ +; functions/series/remove.r +[[] = remove []] +[[] = head remove [1]] +; blank +[blank = remove blank] +; bitset +[ + a-bitset: charset "a" + remove/part a-bitset "a" + blank? find a-bitset #"a" +] +[ + a-bitset: charset "a" + remove/part a-bitset to integer! #"a" + blank? find a-bitset #"a" +] diff --git a/tests/series/reverse.test.reb b/tests/series/reverse.test.reb new file mode 100644 index 0000000000..f6bd53464d --- /dev/null +++ b/tests/series/reverse.test.reb @@ -0,0 +1,3 @@ +; functions/series/reverse.r +; bug#1810: REVERSE/part does not work for tuple! +[3.2.1.4.5 = reverse/part 1.2.3.4.5 3] diff --git a/tests/series/select.test.reb b/tests/series/select.test.reb new file mode 100644 index 0000000000..01e5e2d3c2 --- /dev/null +++ b/tests/series/select.test.reb @@ -0,0 +1,3 @@ +; functions/series/select.r +; bug#1936: select returns incorrect value with block argument +[4 == select [1 2 3 4 5 6] [1 2 3]] diff --git a/tests/series/skip.test.reb b/tests/series/skip.test.reb new file mode 100644 index 0000000000..17205064a7 --- /dev/null +++ b/tests/series/skip.test.reb @@ -0,0 +1,53 @@ +; functions/series/skip.r +[ + blk: [] + same? blk skip blk 0 +] +[ + blk: [] + same? blk skip blk 2147483647 +] +[ + blk: [] + same? blk skip blk -1 +] +[ + blk: [] + same? blk skip blk -2147483648 +] +[ + blk: next [1 2 3] + same? blk skip blk 0 +] +[ + blk: next [1 2 3] + equal? [3] skip blk 1 +] +[ + blk: next [1 2 3] + same? tail blk skip blk 2 +] +[ + blk: next [1 2 3] + same? tail blk skip blk 2147483647 +] +[ + blk: at [1 2 3] 3 + same? tail blk skip blk 2147483646 +] +[ + blk: at [1 2 3] 4 + same? tail blk skip blk 2147483645 +] +[ + blk: [1 2 3] + same? head blk skip blk -1 +] +[ + blk: [1 2 3] + same? head blk skip blk -2147483647 +] +[ + blk: next [1 2 3] + same? head blk skip blk -2147483648 +] diff --git a/tests/series/sort.test.reb b/tests/series/sort.test.reb new file mode 100644 index 0000000000..0757ec7c4d --- /dev/null +++ b/tests/series/sort.test.reb @@ -0,0 +1,29 @@ +; functions/series/sort.r +[[1 2 3] = sort [1 3 2]] +[[3 2 1] = sort/reverse [1 3 2]] +; bug#1152: SORT not stable (order not preserved) +[strict-equal? ["A" "a"] sort ["A" "a"]] +; bug#1152: SORT not stable (order not preserved) +[strict-equal? ["A" "a"] sort/reverse ["A" "a"]] +; bug#1152: SORT not stable (order not preserved) +[strict-equal? ["a" "A"] sort ["a" "A"]] +; bug#1152: SORT not stable (order not preserved) +[strict-equal? ["A" "a"] sort/case ["a" "A"]] +; bug#1152: SORT not stable (order not preserved) +[strict-equal? ["A" "a"] sort/case ["A" "a"]] +; bug#1152: SORT not stable (order not preserved) +[ + set [c d] sort reduce [a: "a" b: "a"] + all [ + same? c a + same? d b + not same? c b + not same? d a + ] +] +; bug#1152: SORT not stable (order not preserved) +[equal? [1 9 1 5 1 7] sort/skip/compare [1 9 1 5 1 7] 2 1] +[[1 2 3] = sort/compare [1 3 2] :<] +[[3 2 1] = sort/compare [1 3 2] :>] +; bug#1516: SORT/compare ignores the typespec of its function argument +[error? try [sort/compare reduce [1 2 _] :>]] diff --git a/tests/series/split.test.reb b/tests/series/split.test.reb new file mode 100644 index 0000000000..4b0930832d --- /dev/null +++ b/tests/series/split.test.reb @@ -0,0 +1,34 @@ +; functions/series/split.r +; Tests taken from bug#1886. +[["1234" "5678" "1234" "5678"] == split "1234567812345678" 4] +[["123" "456" "781" "234" "567" "8"] == split "1234567812345678" 3] +[["12345" "67812" "34567" "8"] == split "1234567812345678" 5] +[[[1 2 3] [4 5 6]] == split/into [1 2 3 4 5 6] 2] +[["12345678" "12345678"] == split/into "1234567812345678" 2] +[["12345" "67812" "345678"] == split/into "1234567812345678" 3] +[["123" "456" "781" "234" "5678"] == split/into "1234567812345678" 5] +; Delimiter longer than series +[["1" "2" "3" "" "" ""] == split/into "123" 6] +[[[1] [2] [3] [] [] []] == split/into [1 2 3] 6] +[[[1 2] [3] [4 5 6]] == split [1 2 3 4 5 6] [2 1 3]] +[["1234" "5678" "12" "34" "5" "6" "7" "8"] == split "1234567812345678" [4 4 2 2 1 1 1 1]] +[[(1 2 3) (4 5 6) (7 8 9)] == split first [(1 2 3 4 5 6 7 8 9)] 3] +[[#{01020304} #{050607} #{08} #{090A}] == split #{0102030405060708090A} [4 3 1 2]] +[[[1 2] [3]] == split [1 2 3 4 5 6] [2 1]] +[[[1 2] [3] [4 5 6] []] == split [1 2 3 4 5 6] [2 1 3 5]] +[[[1 2] [3] [4 5 6]] == split [1 2 3 4 5 6] [2 1 6]] +[[[1 2] [5 6]] == split [1 2 3 4 5 6] [2 -2 2]] +[["abc" "de" "fghi" "jk"] == split "abc,de,fghi,jk" #","] +[["abc" "de" "fghi" "jk"] == split "abc
de
fghi
jk"
] +[["a" "b" "c"] == split "a.b.c" "."] +[["c" "c"] == split "c c" " "] +[["1,2,3"] == split "1,2,3" " "] +[["1" "2" "3"] == split "1,2,3" ","] +[["1" "2" "3" ""] == split "1,2,3," ","] +[["1" "2" "3" ""] == split "1,2,3," charset ",."] +[["1" "2" "3" ""] == split "1.2,3." charset ",."] +[["-" "-"] == split "-a-a" ["a"]] +[["-" "-" "'"] == split "-a-a'" ["a"]] +[["abc" "de" "fghi" "jk"] == split "abc|de/fghi:jk" charset "|/:"] +[["abc" "de" "fghi" "jk"] == split "abc^M^Jde^Mfghi^Jjk" [crlf | #"^M" | newline]] +[["abc" "de" "fghi" "jk"] == split "abc de fghi jk" [some #" "]] diff --git a/tests/series/tailq.test.reb b/tests/series/tailq.test.reb new file mode 100644 index 0000000000..8b5cf6a6c6 --- /dev/null +++ b/tests/series/tailq.test.reb @@ -0,0 +1,7 @@ +; functions/series/tailq.r +[tail? []] +[ + blk: tail [1] + clear head blk + tail? blk +] diff --git a/tests/series/trim.test.reb b/tests/series/trim.test.reb new file mode 100644 index 0000000000..83d51922c7 --- /dev/null +++ b/tests/series/trim.test.reb @@ -0,0 +1,10 @@ +; functions/series/trim.r +; bug#83 +; refinement order +[strict-equal? trim/all/with "a" "a" trim/with/all "a" "a"] +; bug#1948 +["foo^/" = trim " foo ^/"] +[[a b] = trim [a b]] +[[a b] = trim [a b _]] +[[a b] = trim [_ a b _]] +[[a b] = trim [_ a _ b _]] diff --git a/tests/series/union.test.reb b/tests/series/union.test.reb new file mode 100644 index 0000000000..0b9fe68ed1 --- /dev/null +++ b/tests/series/union.test.reb @@ -0,0 +1,6 @@ +; functions/series/union.r +[[1 2 3] = union [1 2] [2 3]] +[[[1 2] [2 3] [3 4]] = union [[1 2] [2 3]] [[2 3] [3 4]]] +[[path/1 path/2 path/3] = union [path/1 path/2] [path/2 path/3]] +; bug#799 +[equal? make typeset! [decimal! integer!] union make typeset! [decimal!] make typeset! [integer!]] diff --git a/tests/series/unique.test.reb b/tests/series/unique.test.reb new file mode 100644 index 0000000000..be027444c0 --- /dev/null +++ b/tests/series/unique.test.reb @@ -0,0 +1,4 @@ +; functions/series/unique.r +[[1 2 3] = unique [1 2 2 3]] +[[[1 2] [2 3] [3 4]] = unique [[1 2] [2 3] [2 3] [3 4]]] +[[path/1 path/2 path/3] = unique [path/1 path/2 path/2 path/3]] diff --git a/tests/source-tools.reb b/tests/source-tools.reb new file mode 100644 index 0000000000..99ec38d40d --- /dev/null +++ b/tests/source-tools.reb @@ -0,0 +1,427 @@ +REBOL [ + Title: "Rebol C Source Tools" + Rights: { + Copyright 2015 Brett Handley + } + License: { + Licensed under the Apache License, Version 2.0 + See: http://www.apache.org/licenses/LICENSE-2.0 + } + Author: "Brett Handley" + Purpose: {Process Rebol C source.} +] + +ren-c-repo: any [ + if exists? %../src/tools/ [%../] + if exists? %../ren-c/src/tools/ [%../ren-c/] +] + +ren-c-repo: clean-path ren-c-repo + +do ren-c-repo/src/tools/common.r +do ren-c-repo/src/tools/common-parsers.r +do %lib/text-lines.reb + + +rebsource: context [ + + src-folder: clean-path ren-c-repo/(%src/) + ; Path to src/ + + logfn: func [message][print mold new-line/all compose/only message false] + log: :logfn + + standard: context [ + ; + ; Not counting newline, lines should be no longer than this. + ; + std-line-length: 79 + + ; Not counting newline, lines over this length have an extra warning. + ; + max-line-length: 127 + + ; Parse Rule which specifies the standard spacing between functions, + ; from final right brace of leading function + ; to intro comment of following function. + ; + function-spacing: [3 eol] + ] + + fixed-source-paths: [ + %core/ + %os/ + %os/generic/ + %os/linux/ + %os/posix/ + %os/windows/ + ] + + extensions: [ + %.c c + %.r rebol + %.reb rebol + ] + + whitelisted: [ + %core/u-bmp.c + %core/u-compress.c + %core/u-gif.c + %core/u-jpg.c + %core/u-md5.c + %core/u-png.c + %core/u-sha1.c + %core/u-zlib.c + ] ; Not analysed ... + + + analyse: context [ + + files: function [ + {Analyse the source files of REBOL.} + ][ + listing: list/source-files + + files-analysis: make block! [] + + for-each source listing [ + if not whitelisted? source [ + analysis: analyse/file source + if analysis [ + append files-analysis analysis + ] + ] + ] + + files-analysis + ] + + file: function [ + {Analyse a file returning facts.} + return: [block! blank!] + file + ][ + all [ + filetype: filetype-of file + to-value if type: in source filetype [ + data: read src-folder/:file + evaluate: get type + evaluate file data + ] + ] + ] + + source: context [ + + c: function [ + {Analyse a C file returning facts.} + file + data + ] [ + + ; + ; This analysis is at a token level (c preprocessing token). + + analysis: analyse/text file data + + data: to string! data + + identifier: c.lexical/grammar/identifier + c-pp-token: c.lexical/grammar/c-pp-token + + malloc-found: make block! [] + + malloc-check: [ + and identifier "malloc" (append malloc-found line-of head position position) + ] + + parse/case data [ + some [ + position: + malloc-check + | c-pp-token + ] + ] + + if not empty? malloc-found [ + emit analysis [malloc (file) (malloc-found)] + ] + + if all [ + not tail? data + not equal? newline last data + ] [ + emit analysis [eof-eol-missing (file)] + ] + + emit-proto: procedure [proto] [ + if all [ + 'format2015 = proto-parser/style + block? proto-parser/data + ] [ + do bind [ + if last-func-end [ + if not all [ + parse last-func-end [ + function-spacing-rule + position: + to end + ] + same? position proto-parser/parse.position + ] [ + line: line-of data proto-parser/parse.position + append any [ + non-std-func-space + set 'non-std-func-space copy [] + ] line-of data proto-parser/parse.position + ] + ] + ] c-parser-extension + + either find/match mold proto-parser/data/2 {native} [ + ; + ; It's a `some-name?: native [...]`, so we expect + ; `REBNATIVE(some_name_q)` to be correctly lined up + ; as the "to-c-name" of the Rebol set-word + ; + unless ( + equal? + proto-parser/proto.arg.1 + (to-c-name to word! proto-parser/data/1) + ) [ + line: line-of data proto-parser/parse.position + emit analysis [ + id-mismatch + (mold proto-parser/data/1) (file) (line) + ] + ] + ] [ + ; + ; ... ? (not a native) + ; + unless ( + equal? + proto-parser/proto.id + form to word! proto-parser/data/1 + ) [ + line: line-of data proto-parser/parse.position + emit analysis [ + id-mismatch + (mold proto-parser/data/1) (file) (line) + ] + ] + ] + ] + + ] + + non-std-func-space: _ + proto-parser/emit-proto: :emit-proto + proto-parser/process data + + if non-std-func-space [ + emit analysis [non-std-func-space (file) (non-std-func-space)] + ] + + analysis + ] + + rebol: function [ + {Analyse a Rebol file returning facts.} + file + data + ][ + analysis: analyse/text file data + analysis + ] + ] + + text: function [ + {Analyse a source file returning facts.} + file + data + ] [ + + ; + ; In this analysis we are interested in textual formatting irrespective of language. + + analysis: make block! [] + + data: read src-folder/:file + + bol: _ + line: _ + + stop-char: charset { ^-^M^/} + ws-char: charset { ^-} + wsp: [some ws-char] + + eol: [line-ending | alt-ending (append inconsistent-eol line)] + line-ending: _ + + ; + ; Identify line termination. + + either all [ + position: find data #{0a} + 1 < index-of position + 13 = first back position + ] [ + set [line-ending alt-ending] reduce [crlf newline] + ][ + set [line-ending alt-ending] reduce [newline crlf] + ] + + count-line: [ + ( + line-len: subtract index-of position index-of bol + if line-len > standard/std-line-length [ + append over-std-len line + if line-len > standard/max-line-length [ + append over-max-len line + ] + ] + line: 1 + line + ) + bol: + ] + + tabbed: make block! [] + eol-wsp: make block! [] + over-std-len: make block! [] + over-max-len: make block! [] + inconsistent-eol: make block! [] + + parse/case data [ + + last-pos: + + opt [bol: skip (line: 1) :bol] + + any [ + to stop-char + position: + [ + eol count-line + | #"^-" (append 'tabbed line) + | wsp and [line-ending | alt-ending] (append eol-wsp line) + | skip + ] + ] + position: + + to end + ] + + if not empty? over-std-len [ + emit analysis [ + line-exceeds + (standard/std-line-length) (file) (over-std-len) + ] + ] + + if not empty? over-max-len [ + emit analysis [ + line-exceeds + (standard/max-line-length) (file) (over-max-len) + ] + ] + + foreach list [tabbed eol-wsp] [ + if not empty? get list [ + emit analysis [(list) (file) (get list)] + ] + ] + + if not empty? inconsistent-eol [ + emit analysis [inconsistent-eol (file) (inconsistent-eol)] + ] + + if all [ + not tail? data + not equal? 10 last data ; Check for newline. + ] [ + emit analysis [eof-eol-missing (file) (reduce [line-of data tail data])] + ] + + analysis + ] + ] + + list: context [ + + source-files: function [ + {Retrieves a list of source files (relative paths).} + ][ + if not src-folder [fail {Configuration required.}] + + files: make block! 1 + (2 * length-of fixed-source-paths) + + for-each path fixed-source-paths [ + for-each file read join-of src-folder path [ + if find extensions extension-of file [ + append files join-of path file + ] + ] + ] + + sort files + new-line/all files true + + files + ] + ] + + c-parser-extension: context bind bind [ + + ; Extend parser to support checking of function spacing. + + last-func-end: _ + + lbrace: [and punctuator #"{"] + rbrace: [and punctuator #"}"] + braced: [lbrace any [braced | not rbrace skip] rbrace] + + function-spacing-rule: ( + bind/copy standard/function-spacing c.lexical/grammar + ) + + grammar/function-body: braced + + append grammar/format2015-func-section [ + last-func-end: + any [nl | eol | wsp] + ] + + append/only grammar/other-segment to group! [ + last-func-end: _ + ] + + ] proto-parser c.lexical/grammar + + emit: function [log body] [ + insert position: tail log new-line/all compose/only body false + new-line position true + ] + + extension-of: function [ + {Return file extension for file.} + file + ][ + copy any [find/last file #"." {}] + ] + + filetype-of: function [ + {Return filetype for file.} + file + ][ + to-value select extensions extension-of file + ] + + whitelisted?: function [ + {Returns true if file should not be analysed.} + file + ][ + find? whitelisted file + ] +] diff --git a/tests/source/analysis.test.reb b/tests/source/analysis.test.reb new file mode 100644 index 0000000000..8fd325a9f8 --- /dev/null +++ b/tests/source/analysis.test.reb @@ -0,0 +1,28 @@ +; source/analysis.r + +;; +;; Source analysis tests. These check the source code for adherence to +;; coding conventions (naming, indentation, column width, etc.) These +;; tests may evolve further into enforcing rules about the call graph +;; and other statically-checkable aspects. +;; +;; At the moment, there are some failures of these tests. They will be +;; addressed in an ongoing fashion as the source is brought in line +;; with the automated checking. +;; + +[ + do %source-tools.reb + source-analysis: rebsource/analyse/files + save %source-analysis.log source-analysis + true +] +[not find source-analysis 'eol-wsp] +[not find source-analysis 'eof-eol-missing] +[not find source-analysis 'tabbed] +[not find source-analysis 'id-mismatch] +[not find source-analysis 'inconsistent-eol] +;; Currently failing. Uncomment, to work on cleaning this up. +;[not find source-analysis [line-exceeds 127]] +;; Currently failing. Uncomment, to work on cleaning this up. +;[not find source-analysis 'malloc] diff --git a/tests/string/checksum.test.reb b/tests/string/checksum.test.reb new file mode 100644 index 0000000000..9c0a09398b --- /dev/null +++ b/tests/string/checksum.test.reb @@ -0,0 +1,9 @@ +; functions/string/checksum.r +[#{ACBD18DB4CC2F85CEDEF654FCCC4A4D8} = checksum/method to-binary "foo" 'md5] +[#{FC3FF98E8C6A0D3087D515C0473F8677} = checksum/method to-binary "hello world!" 'md5] +[#{0BEEC7B5EA3F0FDBC95D0DD47F3C5BC275DA8A33} = checksum/method to-binary "foo" 'sha1] +[#{430CE34D020724ED75A196DFC2AD67C77772D169} = checksum/method to-binary "hello world!" 'sha1] +; bug#1678: "Can we add CRC-32 as a checksum method?" +[(checksum/method to-binary "foo" 'CRC32) = -1938594527] +; bug#1678 +[(checksum/method to-binary "" 'CRC32) = 0] diff --git a/tests/string/compress.test.reb b/tests/string/compress.test.reb new file mode 100644 index 0000000000..9eb565e3dc --- /dev/null +++ b/tests/string/compress.test.reb @@ -0,0 +1,3 @@ +; functions/string/compress.r +; bug#1679 +[#{1F8B08000000000000034BCBCF07002165738C03000000} = compress/gzip "foo"] diff --git a/tests/string/decloak.test.reb b/tests/string/decloak.test.reb new file mode 100644 index 0000000000..4a7534c1c8 --- /dev/null +++ b/tests/string/decloak.test.reb @@ -0,0 +1,7 @@ +; functions/string/decloak.r +; bug#48 +[ + a: compress "a" + b: encloak a "a" + equal? a decloak b "a" +] diff --git a/tests/string/decode.test.reb b/tests/string/decode.test.reb new file mode 100644 index 0000000000..43f99b20bc --- /dev/null +++ b/tests/string/decode.test.reb @@ -0,0 +1,7 @@ +; functions/string/decode.r +[image? decode 'bmp read %fixtures/rebol-logo.bmp] +[image? decode 'gif read %fixtures/rebol-logo.gif] +[image? decode 'jpeg read %fixtures/rebol-logo.jpg] +[image? decode 'png read %fixtures/rebol-logo.png] +["" == decode 'text #{}] +["bar" == decode 'text #{626172}] diff --git a/tests/string/decompress.test.reb b/tests/string/decompress.test.reb new file mode 100644 index 0000000000..a8c434c439 --- /dev/null +++ b/tests/string/decompress.test.reb @@ -0,0 +1,7 @@ +; functions/string/decompress.r +; bug#1679: "Native GZIP compress/decompress suport" +["foo" == to string! decompress/gzip compress/gzip "foo"] +; bug#1679 +["foo" == to string! decompress/gzip #{1F8B0800EF46BE4C00034BCBCF07002165738C03000000}] +; bug#3 +[error? try [decompress #{AAAAAAAAAAAAAAAAAAAA}]] diff --git a/tests/string/dehex.test.reb b/tests/string/dehex.test.reb new file mode 100644 index 0000000000..2a7582c273 --- /dev/null +++ b/tests/string/dehex.test.reb @@ -0,0 +1,9 @@ +; functions/string/dehex.r +["a%b" = dehex "a%b"] +["a%~b" = dehex "a%~b"] +["a^@b" = dehex "a%00b"] +["a b" = dehex "a%20b"] +["a%b" = dehex "a%25b"] +["a+b" = dehex "a%2bb"] +["a+b" = dehex "a%2Bb"] +["abc" = dehex "a%62c"] diff --git a/tests/string/encode.test.reb b/tests/string/encode.test.reb new file mode 100644 index 0000000000..dad1d866ae --- /dev/null +++ b/tests/string/encode.test.reb @@ -0,0 +1,2 @@ +; functions/string/encode.r +[out: encode 'bmp decode 'bmp src: read %fixtures/rebol-logo.bmp out == src] diff --git a/tests/system/file.test.reb b/tests/system/file.test.reb new file mode 100644 index 0000000000..45b699a2fd --- /dev/null +++ b/tests/system/file.test.reb @@ -0,0 +1,95 @@ +; system/file.r +[#{C3A4C3B6C3BC} == read %fixtures/umlauts-utf8.txt] +["äöü" == read/string %fixtures/umlauts-utf8.txt] +[["äöü"] == read/lines %fixtures/umlauts-utf8.txt] +[#{EFBBBFC3A4C3B6C3BC} == read %fixtures/umlauts-utf8bom.txt] +["äöü" == read/string %fixtures/umlauts-utf8bom.txt] +[["äöü"] == read/lines %fixtures/umlauts-utf8bom.txt] +[#{FFFEE400F600FC00} == read %fixtures/umlauts-utf16le.txt] +["äöü" == read/string %fixtures/umlauts-utf16le.txt] +[["äöü"] == read/lines %fixtures/umlauts-utf16le.txt] +[#{FEFF00E400F600FC} == read %fixtures/umlauts-utf16be.txt] +["äöü" == read/string %fixtures/umlauts-utf16be.txt] +[["äöü"] == read/lines %fixtures/umlauts-utf16be.txt] +[#{FFFE0000E4000000F6000000FC000000} == read %fixtures/umlauts-utf32le.txt] +[#{0000FEFF000000E4000000F6000000FC} == read %fixtures/umlauts-utf32be.txt] +[block? read %./] +[block? read %fixtures/] + + +; These save tests were living in %mezz-save.r, but did not have expected +; outputs. Moved here with expected binary result given by R3-Alpha. + +[block? data: [1 1.2 10:20 "test" user@example.com [sub block]]] + +[(save blank []) = #{ +0A +}] + +[(save blank data) = #{ +3120312E322031303A3230202274657374222075736572406578616D706C652E +636F6D205B73756220626C6F636B5D0A +}] + +[(save/header blank data [title: "my code"]) = #{ +5245424F4C205B0A202020207469746C653A20226D7920636F6465220A5D0A31 +20312E322031303A3230202274657374222075736572406578616D706C652E63 +6F6D205B73756220626C6F636B5D0A +}] + +[(save/compress blank [] true) = #{ +5245424F4C205B0A202020206F7074696F6E733A205B636F6D70726573735D0A +5D0A789CE30200000B000B01000000 +}] + +[(save/compress blank data true) = #{ +5245424F4C205B0A202020206F7074696F6E733A205B636F6D70726573735D0A +5D0A789C335430D433523034B0323250502A492D2E5152282D4E2D7248AD48CC +2DC849D54BCECF55882E2E4D5248CAC94FCE8EE5020049C70EF330000000 +}] + +[(save/compress blank data 'script) = #{ +5245424F4C205B0A202020206F7074696F6E733A205B636F6D70726573735D0A +5D0A3634237B654A777A564444554D3149774E4C41794D6C42514B6B6B744C6C +46534B43314F4C584A4972556A4D4C63684A3155764F7A3157494C69354E556B +6A4B79552F4F6A75554341456E4844764D77414141417D +}] + +[(save/header/compress blank data [title: "my code"] true) = #{ +5245424F4C205B0A202020207469746C653A20226D7920636F6465220A202020 +206F7074696F6E733A205B636F6D70726573735D0A5D0A789C335430D4335230 +34B0323250502A492D2E5152282D4E2D7248AD48CC2DC849D54BCECF55882E2E +4D5248CAC94FCE8EE5020049C70EF330000000 +}] + +[(save/header/compress blank data [title: "my code"] 'script) = #{ +5245424F4C205B0A202020207469746C653A20226D7920636F6465220A202020 +206F7074696F6E733A205B636F6D70726573735D0A5D0A3634237B654A777A56 +4444554D3149774E4C41794D6C42514B6B6B744C6C46534B43314F4C584A4972 +556A4D4C63684A3155764F7A3157494C69354E556B6A4B79552F4F6A75554341 +456E4844764D77414141417D +}] + +[(save/header blank data [title: "my code" options: [compress]]) = #{ +5245424F4C205B0A202020207469746C653A20226D7920636F6465220A202020 +206F7074696F6E733A205B636F6D70726573735D0A5D0A789C335430D4335230 +34B0323250502A492D2E5152282D4E2D7248AD48CC2DC849D54BCECF55882E2E +4D5248CAC94FCE8EE5020049C70EF330000000 +}] + +;-- This gave an error in R3-Alpha: +;-- ** Script error: save does not allow none! for its method argument +; +;[(save/header/compress blank data [ +; title: "my code" options: [compress] +;] blank) = #{ +; ??? +;}] + +[(save/header blank data [title: "my code" checksum: true]) = #{ +5245424F4C205B0A202020207469746C653A20226D7920636F6465220A202020 +20636865636B73756D3A20237B42424135424634364139354332384137363438 +3036303233394546364536374246354235304144317D0A5D0A3120312E322031 +303A3230202274657374222075736572406578616D706C652E636F6D205B7375 +6220626C6F636B5D0A +}] diff --git a/tests/system/gc.test.reb b/tests/system/gc.test.reb new file mode 100644 index 0000000000..2d318c29ef --- /dev/null +++ b/tests/system/gc.test.reb @@ -0,0 +1,81 @@ +; system/gc.r +; bug#1776, bug#2072 +[ + a: copy [] + loop 200'000 [a: append/only copy [] a] + recycle + true +] +; bug#1989 +[ + loop ([comment 30000000] 300) [make gob! []] + true +] + +; !!! simplest possible LOAD/SAVE smoke test, expand! +[ + file: %simple-save-test.r + data: "Simple save test produced by %core-tests.r" + save file data + (load file) = data +] + + +;; +;; "Mold Stack" tests +;; + +; Nested ajoin +[ + nested-ajoin: func [n] [ + either n <= 1 [n] [ajoin [n space nested-ajoin n - 1]] + ] + "9 8 7 6 5 4 3 2 1" = nested-ajoin 9 +] +; Form recursive object... +[ + o: object [a: 1 r: _] o/r: o + (ajoin ["<" form o ">"]) = "" +] +; detab... +[ + (ajoin ["<" detab "aa^-b^-c" ">"]) = "" +] +; entab... +[ + (ajoin ["<" entab " a b" ">"]) = "<^- a b>" +] +; dehex... +[ + (ajoin ["<" dehex "a%20b" ">"]) = "
" +] +; form... +[ + (ajoin ["<" form [1 [2 3] "^""] ">"]) = {<1 2 3 ">} +] +; transcode... +[ + (ajoin ["<" mold transcode to binary! "a [b c]" ">"]) + = "<[a [b c] #{}]>" +] +; ... +[ + (ajoin ["<" intersect [a b c] [d e f] ">"]) = "<>" +] +; reword +[equal? reword "$1 is $2." [1 "This" 2 "that"] "This is that."] +[equal? reword/escape "A %%a is %%b." [a "fox" b "brown"] "%%" "A fox is brown." ] +[equal? reword/escape "I am answering you." ["I am" "Brian is" you "Adrian"] blank "Brian is answering Adrian."] +[equal? reword/escape "$$$a$$$ is $$$b$$$" [a Hello b Goodbye] ["$$$" "$$$"] "Hello is Goodbye"] + + +;; +;; Simplest possible HTTP and HTTPS protocol smoke test +;; +;; !!! EXPAND! +;; + +[not error? trap [read http://example.com]] +[not error? trap [read https://example.com]] + + diff --git a/tests/system/system.test.reb b/tests/system/system.test.reb new file mode 100644 index 0000000000..37ce930675 --- /dev/null +++ b/tests/system/system.test.reb @@ -0,0 +1,3 @@ +; system/system.r +; bug#76 +[date? system/build] diff --git a/tests/test-framework.r b/tests/test-framework.r new file mode 100644 index 0000000000..5fd25fb9e6 --- /dev/null +++ b/tests/test-framework.r @@ -0,0 +1,251 @@ +Rebol [ + Title: "Test-framework" + File: %test-framework.r + Copyright: [2012 "Saphirion AG"] + License: { + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + } + Author: "Ladislav Mecir" + Purpose: "Test framework" +] + +do %test-parsing.r +do %catch-any.r + +make object! compose [ + log-file: _ + + log: func [report [block!]] [ + write/append log-file join-of #{} report + ] + + ; counters + skipped: _ + test-failures: _ + crashes: _ + dialect-failures: _ + successes: _ + + exceptions: make object! [ + return: "return/exit out of the test code" + error: "error was caused in the test code" + break: "break or continue out of the test code" + throw: "throw out of the test code" + quit: "quit out of the test code" + ] + + allowed-flags: _ + + process-vector: procedure [ + flags [block!] + source [string!] + ][ + log [source] + + unless empty? exclude flags allowed-flags [ + set 'skipped (skipped + 1) + log [{ "skipped"^/}] + leave + ] + + if error? try [test-block: load source] [ + set 'test-failures (test-failures + 1) + log [{ "failed, cannot load test source"^/}] + leave + ] + + error? set/opt 'test-block catch-any test-block 'exception + + test-block: case [ + exception [spaced ["failed," exceptions/:exception]] + not logic? :test-block ["failed, not a logic value"] + test-block ["succeeded"] + ] else [ + "failed" + ] + + recycle + + either test-block = "succeeded" [ + set 'successes (successes + 1) + log [{ "} test-block {"^/}] + ][ + set 'test-failures (test-failures + 1) + log reduce [{ "} test-block {"^/}] + ] + ] + + total-tests: 0 + + process-tests: procedure [ + test-sources [block!] + emit-test [function!] + ][ + parse test-sources [ + any [ + set flags: block! set value: skip ( + emit-test flags to string! value + ) + | + set value: file! (log ["^/" mold value "^/^/"]) + | + 'dialect set value: string! ( + log [value] + set 'dialect-failures (dialect-failures + 1) + ) + ] + ] + ] + + set 'do-recover func [ + {Executes tests in the FILE and recovers from crash} + file [file!] {test file} + flags [block!] {which flags to accept} + code-checksum [binary! blank!] + log-file-prefix [file!] + /local interpreter last-vector value position next-position + test-sources test-checksum guard + ] [ + allowed-flags: flags + + ; calculate test checksum + test-checksum: checksum/method read-binary file 'sha1 + + log-file: log-file-prefix + + if code-checksum [ + append log-file "_" + append log-file copy/part skip mold code-checksum 2 6 + ] + + append log-file "_" + append log-file copy/part skip mold test-checksum 2 6 + + append log-file ".log" + log-file: clean-path log-file + + collect-tests test-sources: copy [] file + + successes: test-failures: crashes: dialect-failures: skipped: 0 + + case [ + not exists? log-file [ + print "new log" + process-tests test-sources :process-vector + ] + + all [ + parse read log-file [ + ( + last-vector: _ + guard: [end skip] + ) + any [ + any whitespace + [ + position: "%" + (set/opt [value next-position] transcode/next position) + :next-position + | + ; dialect failure? + some whitespace + {"} thru {"} + (dialect-failures: dialect-failures + 1) + | + copy last-vector ["[" test-source-rule "]"] + any whitespace + [ + end ( + ; crash found + crashes: crashes + 1 + log [{ "crashed"^/}] + guard: _ + ) + | + {"} copy value to {"} skip + ; test result found + ( + parse value [ + "succeeded" + (successes: successes + 1) + | + "failed" + (test-failures: test-failures + 1) + | + "crashed" + (crashes: crashes + 1) + | + "skipped" + (skipped: skipped + 1) + | + (do make error! "invalid test result") + ] + ) + ] + | + "system/version:" + to end + (last-vector: guard: _) + + ] position: guard break + | + :position + ] + end | (fail "log file parsing problem") + ] + last-vector + test-sources: find/last/tail test-sources last-vector + ][ + print [ + "recovering at:" + ( + successes + + test-failures + + crashes + + dialect-failures + + skipped + ) + ] + process-tests test-sources :process-vector + ] + ] then [ + summary: spaced [ + | + "system/version:" system/version + | + "code-checksum:" code-checksum + | + "test-checksum:" test-checksum + | + "Total:" ( + successes + + test-failures + + crashes + + dialect-failures + + skipped + ) + | + "Succeeded:" successes + | + "Test-failures:" test-failures + | + "Crashes:" crashes + | + "Dialect-failures:" dialect-failures + | + "Skipped:" skipped + | + ] + + log [summary] + + reduce [log-file summary] + ] else [ + reduce [log-file "testing already complete"] + ] + ] +] diff --git a/tests/test-parsing.r b/tests/test-parsing.r new file mode 100644 index 0000000000..f927717887 --- /dev/null +++ b/tests/test-parsing.r @@ -0,0 +1,196 @@ +Rebol [ + Title: "Test parsing" + File: %test-parsing.r + Copyright: [2012 "Saphirion AG"] + License: { + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + } + Author: "Ladislav Mecir" + Purpose: "Test framework" +] + +do %line-numberq.r + +whitespace: charset [#"^A" - #" " "^(7F)^(A0)"] + + +read-binary: :read + +make object! [ + + position: _ + success: _ + set 'test-source-rule [ + any [ + position: ["{" | {"}] ( + ; handle string using TRANSCODE + success: either error? try [ + position: second transcode/next position + ] [ + [end skip] + ] [ + [:position] + ] + ) success + | + ["{" | {"}] :position break + | + "[" test-source-rule "]" + | + "(" test-source-rule ")" + | + ";" [thru newline | to end] + | + "]" :position break + | + ")" :position break + | + skip + ] + ] + + set 'collect-tests procedure [ + collected-tests [block!] + {collect the tests here (modified)} + test-file [file!] + ][ + current-dir: what-dir + print ["file:" mold test-file] + + either error? try [ + if file? test-file [ + test-file: clean-path test-file + change-dir first split-path test-file + ] + test-sources: read test-file + ][ + append collected-tests reduce [ + test-file 'dialect {^/"failed, cannot read the file"^/} + ] + change-dir current-dir + leave + ][ + change-dir current-dir + append collected-tests test-file + ] + + flags: copy [] + rule: [ + any [ + some whitespace + | + ";" [thru newline | to end] + | + copy vector ["[" test-source-rule "]"] ( + append/only collected-tests flags + append collected-tests vector + flags: copy [] + ) + | + end break + | + position: ( + case [ + any [ + error? try [ + set [value: next-position:] ( + transcode/next position + ) + ] + blank? next-position + ] [stop: [:position]] + issue? get/opt 'value [ + append flags value + stop: [end skip] + ] + file? get/opt 'value [ + collect-tests collected-tests value + print ["file:" mold test-file] + append collected-tests test-file + stop: [end skip] + ] + ] else [ + stop: [:position] + ] + ) stop break + | + :next-position + ] + ] + + unless parse test-sources rule [ + append collected-tests reduce [ + 'dialect + unspaced [ + newline + {"failed, line:} space line-number? position {"} newline + ] + ] + ] + ] + + set 'collect-logs function [ + collected-logs [block!] + {collect the logged results here (modified)} + log-file [file!] + ][ + if error? try [log-contents: read log-file] [ + fail ["Unable to read " mold log-file] + ] + + parse log-contents [ + (stop: [end skip]) + any [ + any whitespace + [ + position: "%" + (set [value: next-position:] transcode/next position) + :next-position + | + ; dialect failure? + some whitespace + {"} thru {"} + | + copy last-vector ["[" test-source-rule "]"] + any whitespace + [ + end ( + ; crash found + fail "log incomplete!" + ) + | + {"} copy value to {"} skip + ; test result found + ( + parse value [ + "succeeded" (value: 'succeeded) + | + "failed" (value: 'failed) + | + "crashed" (value: 'crashed) + | + "skipped" (value: 'skipped) + | + (fail "invalid test result") + ] + append collected-logs reduce [ + last-vector + value + ] + ) + ] + | + "system/version:" to end (stop: _) + | + (fail "log file parsing problem") + ] position: stop break + | + :position + ] + ] + ] +]